fix: reject unsafe evidence documentation claims
This commit is contained in:
@@ -36,7 +36,7 @@ authentication method.
|
|||||||
## Git remote: SSH and HTTPS
|
## Git remote: SSH and HTTPS
|
||||||
|
|
||||||
Create one private repository such as `thoth-workspaces.git`. It contains canonical workspace
|
Create one private repository such as `thoth-workspaces.git`. It contains canonical workspace
|
||||||
definitions and generated artifacts only:
|
definitions, curated Evidence content, and generated artifacts:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
registry.git/
|
registry.git/
|
||||||
|
|||||||
@@ -525,6 +525,12 @@ elif mutation == "s3-token-boundary-omitted":
|
|||||||
)
|
)
|
||||||
elif mutation == "credential-literal":
|
elif mutation == "credential-literal":
|
||||||
changed = original + "\nTHT_WS_STATIC_S3_EVIDENCE_SECRET_KEY=AKIAEXAMPLECREDENTIAL\n"
|
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":
|
elif mutation == "signed-query-example":
|
||||||
signed_query = "https://evidence.example.invalid/report" + "?X-Amz-Signature=unsafe"
|
signed_query = "https://evidence.example.invalid/report" + "?X-Amz-Signature=unsafe"
|
||||||
changed = original + f"\nTHT_EVIDENCE_URI={signed_query}\n"
|
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.",
|
"P1 materializes, extracts, and indexes Evidence before publication.",
|
||||||
1,
|
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":
|
elif mutation == "config-ordering":
|
||||||
changed = original.replace(
|
changed = original.replace(
|
||||||
"tht config check -c <path>", "tht -c <path> config check", 1
|
"tht config check -c <path>", "tht -c <path> config check", 1
|
||||||
@@ -963,6 +986,12 @@ expect_evidence_fixture_rejected \
|
|||||||
expect_evidence_fixture_rejected \
|
expect_evidence_fixture_rejected \
|
||||||
"credential literal in public bindings" docs/install/examples/workspace-bindings.env.example credential-literal \
|
"credential literal in public bindings" docs/install/examples/workspace-bindings.env.example credential-literal \
|
||||||
"credential literal forbidden"
|
"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 \
|
expect_evidence_fixture_rejected \
|
||||||
"signed query in public bindings" docs/install/examples/workspace-bindings.env.example signed-query-example \
|
"signed query in public bindings" docs/install/examples/workspace-bindings.env.example signed-query-example \
|
||||||
"query-bearing public URI forbidden"
|
"query-bearing public URI forbidden"
|
||||||
@@ -972,6 +1001,36 @@ expect_evidence_fixture_rejected \
|
|||||||
expect_evidence_fixture_rejected \
|
expect_evidence_fixture_rejected \
|
||||||
"P1 Evidence scope inversion" docs/contracts/workspace-evidence-v3.md p1-scope-inversion \
|
"P1 Evidence scope inversion" docs/contracts/workspace-evidence-v3.md p1-scope-inversion \
|
||||||
"P1 scope violation"
|
"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 \
|
expect_evidence_fixture_rejected \
|
||||||
"config check option reordered" docs/contracts/workspace-evidence-v3.md config-ordering \
|
"config check option reordered" docs/contracts/workspace-evidence-v3.md config-ordering \
|
||||||
"exact config-check ordering missing"
|
"exact config-check ordering missing"
|
||||||
|
|||||||
@@ -247,14 +247,18 @@ contract_path = base / "docs/contracts/workspace-evidence-v3.md"
|
|||||||
local_path = base / "docs/install/local-workspace-registry.md"
|
local_path = base / "docs/install/local-workspace-registry.md"
|
||||||
server_path = base / "docs/install/server-workspace-registry.md"
|
server_path = base / "docs/install/server-workspace-registry.md"
|
||||||
bindings_path = base / "docs/install/examples/workspace-bindings.env.example"
|
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:
|
for path in paths:
|
||||||
if not path.is_file():
|
if not path.is_file():
|
||||||
raise SystemExit(f"missing workspace Evidence contract input: {path.relative_to(base)}")
|
raise SystemExit(f"missing workspace Evidence contract input: {path.relative_to(base)}")
|
||||||
|
|
||||||
# Generic descriptors must publish an explicit, ID-derived canonical filesystem contract.
|
# Generic descriptors must publish an explicit, ID-derived canonical filesystem contract.
|
||||||
for relative in ("deploy/workspaces/example.yaml", "deploy/workspaces/psd.yaml.example"):
|
for path in descriptor_paths:
|
||||||
path = base / relative
|
relative = path.relative_to(base).as_posix()
|
||||||
document = yaml.safe_load(path.read_text())
|
document = yaml.safe_load(path.read_text())
|
||||||
workspace_id = document["workspace"]["id"]
|
workspace_id = document["workspace"]["id"]
|
||||||
evidence = document.get("evidence")
|
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")):
|
if not all(token in p6 for token in ("commit-addressed materialization", "realpath", "recursive containment", "nested-symlink", "race")):
|
||||||
raise SystemExit("missing P6 materialization ownership")
|
raise SystemExit("missing P6 materialization ownership")
|
||||||
no_scope = "P1 performs no acquisition, extraction, preprocessing/indexing, embeddings, Qdrant writes, `ACTIVE` publication, retention, or GC."
|
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")
|
raise SystemExit("P1 scope violation")
|
||||||
|
|
||||||
installation_rows = {row[0]: row[1:] for row in table_for("Installation files")}
|
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):
|
if any(position < 0 for position in positions) or positions != sorted(positions):
|
||||||
raise SystemExit(f"{guide.name}: curator flow out of order")
|
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
|
# 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.
|
# contain paths and query-free identities, but never credential values or unsafe placeholders.
|
||||||
def dotenv_lines(path):
|
def dotenv_lines(path):
|
||||||
|
|||||||
Reference in New Issue
Block a user