fix: reject unsafe evidence documentation claims

This commit is contained in:
2026-08-09 21:08:43 +02:00
parent 4d6e91526d
commit d27be56d5c
3 changed files with 95 additions and 5 deletions
@@ -525,6 +525,12 @@ elif mutation == "s3-token-boundary-omitted":
)
elif mutation == "credential-literal":
changed = original + "\nTHT_WS_STATIC_S3_EVIDENCE_SECRET_KEY=AKIAEXAMPLECREDENTIAL\n"
elif mutation == "credential-literal-public-prose":
changed = original + "\nPublic credential example: AKIAABCDEFGHIJKLMNOP\n"
elif mutation == "credential-literal-public-yaml":
document = yaml.safe_load(original)
document["public_credential_example"] = "AKIAABCDEFGHIJKLMNOP"
changed = yaml.safe_dump(document, sort_keys=False)
elif mutation == "signed-query-example":
signed_query = "https://evidence.example.invalid/report" + "?X-Amz-Signature=unsafe"
changed = original + f"\nTHT_EVIDENCE_URI={signed_query}\n"
@@ -538,6 +544,23 @@ elif mutation == "p1-scope-inversion":
"P1 materializes, extracts, and indexes Evidence before publication.",
1,
)
elif mutation.startswith("p1-append-"):
claims = {
"p1-append-acquisition": "P1 owns Evidence acquisition.",
"p1-append-materialization": "P1 owns Evidence materialization.",
"p1-append-extraction": "P1 owns Evidence extraction.",
"p1-append-preprocessing": "P1 owns Evidence preprocessing.",
"p1-append-embeddings": "P1 owns Evidence embeddings.",
"p1-append-qdrant-writes": "P1 owns Evidence Qdrant writes.",
"p1-append-indexing": "P1 owns Evidence indexing.",
"p1-append-active": "P1 owns Evidence `ACTIVE` publication.",
"p1-append-retention": "P1 owns Evidence retention.",
"p1-append-gc": "P1 owns Evidence GC.",
}
claim = claims.get(mutation)
if claim is None:
raise SystemExit(f"unknown P1 append mutation: {mutation}")
changed = original + f"\n{claim}\n"
elif mutation == "config-ordering":
changed = original.replace(
"tht config check -c <path>", "tht -c <path> config check", 1
@@ -963,6 +986,12 @@ expect_evidence_fixture_rejected \
expect_evidence_fixture_rejected \
"credential literal in public bindings" docs/install/examples/workspace-bindings.env.example credential-literal \
"credential literal forbidden"
expect_evidence_fixture_rejected \
"credential literal in public prose" docs/contracts/workspace-evidence-v3.md credential-literal-public-prose \
"credential literal forbidden"
expect_evidence_fixture_rejected \
"credential literal in public YAML" deploy/workspaces/example.yaml credential-literal-public-yaml \
"credential literal forbidden"
expect_evidence_fixture_rejected \
"signed query in public bindings" docs/install/examples/workspace-bindings.env.example signed-query-example \
"query-bearing public URI forbidden"
@@ -972,6 +1001,36 @@ expect_evidence_fixture_rejected \
expect_evidence_fixture_rejected \
"P1 Evidence scope inversion" docs/contracts/workspace-evidence-v3.md p1-scope-inversion \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 acquisition claim" docs/contracts/workspace-evidence-v3.md p1-append-acquisition \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 materialization claim" docs/contracts/workspace-evidence-v3.md p1-append-materialization \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 extraction claim" docs/contracts/workspace-evidence-v3.md p1-append-extraction \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 preprocessing claim" docs/contracts/workspace-evidence-v3.md p1-append-preprocessing \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 embeddings claim" docs/contracts/workspace-evidence-v3.md p1-append-embeddings \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 Qdrant writes claim" docs/contracts/workspace-evidence-v3.md p1-append-qdrant-writes \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 indexing claim" docs/contracts/workspace-evidence-v3.md p1-append-indexing \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 ACTIVE publication claim" docs/contracts/workspace-evidence-v3.md p1-append-active \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 retention claim" docs/contracts/workspace-evidence-v3.md p1-append-retention \
"P1 scope violation"
expect_evidence_fixture_rejected \
"appended P1 garbage collection claim" docs/contracts/workspace-evidence-v3.md p1-append-gc \
"P1 scope violation"
expect_evidence_fixture_rejected \
"config check option reordered" docs/contracts/workspace-evidence-v3.md config-ordering \
"exact config-check ordering missing"
+35 -4
View File
@@ -247,14 +247,18 @@ contract_path = base / "docs/contracts/workspace-evidence-v3.md"
local_path = base / "docs/install/local-workspace-registry.md"
server_path = base / "docs/install/server-workspace-registry.md"
bindings_path = base / "docs/install/examples/workspace-bindings.env.example"
paths = [contract_path, local_path, server_path, bindings_path]
descriptor_paths = [
base / "deploy/workspaces/example.yaml",
base / "deploy/workspaces/psd.yaml.example",
]
paths = [contract_path, local_path, server_path, bindings_path, *descriptor_paths]
for path in paths:
if not path.is_file():
raise SystemExit(f"missing workspace Evidence contract input: {path.relative_to(base)}")
# Generic descriptors must publish an explicit, ID-derived canonical filesystem contract.
for relative in ("deploy/workspaces/example.yaml", "deploy/workspaces/psd.yaml.example"):
path = base / relative
for path in descriptor_paths:
relative = path.relative_to(base).as_posix()
document = yaml.safe_load(path.read_text())
workspace_id = document["workspace"]["id"]
evidence = document.get("evidence")
@@ -402,7 +406,25 @@ if not all(token in p1 for token in ("lexical URI", "Git tree", "same commit", "
if not all(token in p6 for token in ("commit-addressed materialization", "realpath", "recursive containment", "nested-symlink", "race")):
raise SystemExit("missing P6 materialization ownership")
no_scope = "P1 performs no acquisition, extraction, preprocessing/indexing, embeddings, Qdrant writes, `ACTIVE` publication, retention, or GC."
if no_scope not in contract or re.search(r"P1\s+(?:materializes|extracts|indexes)", contract, re.IGNORECASE):
positive_p1_operation = re.compile(
r"""\bP1\b(?:\s+(?:also|then|now|directly|itself))*\s+(?:
(?:will\s+|must\s+|may\s+|can\s+)?(?:
acquires?|materializes?|extracts?|preprocesses?|indexes?|retains?|
(?:creates?|generates?)\s+embeddings?|
writes?\s+(?:embeddings?\s+)?to\s+Qdrant|
publishes?\s+`?ACTIVE\b`?|
garbage[- ]collects?|
(?:runs?|performs?)\s+(?:retention|GC|garbage[ -]collection)
)|
(?:owns?|handles?|performs?|is\s+responsible\s+for)\s+(?:Evidence\s+)?(?:
acquisition|materialization|extraction|preprocessing|embeddings?|
Qdrant\s+writes?|indexing|`?ACTIVE`?\s+publication|retention|GC|
garbage[ -]collection
)
)\b""",
re.IGNORECASE | re.VERBOSE,
)
if no_scope not in contract or positive_p1_operation.search(contract):
raise SystemExit("P1 scope violation")
installation_rows = {row[0]: row[1:] for row in table_for("Installation files")}
@@ -454,6 +476,15 @@ for guide in (local_path, server_path):
if any(position < 0 for position in positions) or positions != sorted(positions):
raise SystemExit(f"{guide.name}: curator flow out of order")
# Public prose, YAML, and examples may name credential variables and describe forbidden shapes,
# but they must never contain a high-confidence access-key literal. Identifier-aware boundaries
# avoid treating a legitimate variable name as a credential value.
aws_access_key = re.compile(
r"(?<![A-Za-z0-9_])(?:AKIA|ASIA)[A-Z0-9]{16}(?![A-Za-z0-9_])"
)
if aws_access_key.search(all_public):
raise SystemExit("credential literal forbidden")
# Parse dotenv assignments in the core example and fenced public guide blocks. Public examples may
# contain paths and query-free identities, but never credential values or unsafe placeholders.
def dotenv_lines(path):