Add value-safe verification and audit reporting
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a0217e-8c4c-7383-be6b-f50a6e485306
This commit is contained in:
tegwick 2026-08-23 12:33:38 +02:00
parent 491e706a70
commit c4504c6de9
19 changed files with 598 additions and 50 deletions

102
tests/test_audit.py Normal file
View file

@ -0,0 +1,102 @@
import json
import urllib.error
from pathlib import Path
from types import SimpleNamespace
from secrets_engine.audit import summarize_lane_evidence
from secrets_engine.cli import build_parser
from secrets_engine.evidence import EvidenceWriter
DECISION_ID = "e6381a56-3e55-4fac-b22c-63ee1c152ce8"
def test_lane_audit_summarizes_allowlisted_fields_and_cleanup(tmp_path, monkeypatch):
writer = EvidenceWriter(
evidence_dir=tmp_path,
hub_url="http://hub.invalid",
topic_id="topic-id",
)
monkeypatch.setattr(
"urllib.request.urlopen", lambda *_args, **_kwargs: SimpleNamespace(read=lambda: b"{}")
)
writer.record(
"exec",
result="exit-0",
catalog_id="test-lane",
stage="test",
decision_id=DECISION_ID,
detail={
"session": {
"session_handle": "safe-fingerprint",
"revocation_attempted": True,
"revocation_succeeded": True,
}
},
)
def offline(*_args, **_kwargs):
raise urllib.error.URLError("offline")
monkeypatch.setattr("urllib.request.urlopen", offline)
writer.record(
"verify",
result="positive-pass",
catalog_id="test-lane",
stage="test",
decision_id=DECISION_ID,
detail={
"session": {
"session_handle": "another-safe-fingerprint",
"revocation_attempted": True,
"revocation_succeeded": False,
}
},
)
path = next(tmp_path.glob("evidence-*.jsonl"))
with path.open("a", encoding="utf-8") as fh:
fh.write("not-json\n")
fh.write(
json.dumps(
{
"catalog_id": "test-lane",
"action": "fake-SUPER-SECRET-value",
"result": "fake-SUPER-SECRET-value",
"decision_id": "fake-SUPER-SECRET-value",
"ts": "not-a-time",
"detail": {"value": "fake-SUPER-SECRET-value"},
}
)
+ "\n"
)
summary = summarize_lane_evidence(tmp_path, "test-lane")
rendered = json.dumps(summary.to_json()) + summary.render()
assert summary.operation_records == 3
assert summary.malformed_records == 1
assert summary.actions == {"exec": 1, "invalid-label": 1, "verify": 1}
assert summary.results == {
"exit-0": 1,
"invalid-label": 1,
"positive-pass": 1,
}
assert summary.decision_refs == [DECISION_ID]
assert summary.session_cleanup == {"failed": 1, "succeeded": 1}
assert summary.hub_delivery == {"delivered": 1, "failed": 1}
assert "fake-SUPER-SECRET-value" not in rendered
assert "safe-fingerprint" not in rendered
def test_lane_audit_empty_directory_is_a_valid_empty_summary(tmp_path):
summary = summarize_lane_evidence(tmp_path, "test-lane")
assert summary.operation_records == 0
assert summary.actions == {}
assert summary.render().startswith("Lane audit summary for 'test-lane'")
def test_cli_parser_exposes_audit_json_command():
args = build_parser().parse_args(["audit", "test-lane", "--json"])
assert args.catalog_id == "test-lane"
assert args.json is True

View file

@ -90,9 +90,32 @@ def test_full_chain(bao_dev, tmp_path):
pos = verify_positive(client, entry, "npm_token")
assert pos.passed, pos.detail
neg = verify_negative(client, entry)
client.write_policy(
"se-unrelated-denied",
f'path "{entry.mount}/data/{entry.path}" {{ capabilities = ["deny"] }}\n',
)
unrelated = json.loads(
client._run_ok(
["token", "create", "-format=json", "-policy=se-unrelated-denied"]
)
)["auth"]["client_token"]
neg = verify_negative(client, entry, unrelated_token=unrelated)
assert neg.passed, neg.detail
# A real unrelated identity with accidental policy overlap must make the
# negative check fail; an invalid/garbage token could not detect this.
client.write_policy(
"se-unrelated-overlap",
f'path "{entry.mount}/data/{entry.path}" {{ capabilities = ["read"] }}\n',
)
overlapping = json.loads(
client._run_ok(
["token", "create", "-format=json", "-policy=se-unrelated-overlap"]
)
)["auth"]["client_token"]
leaked = verify_negative(client, entry, unrelated_token=overlapping)
assert not leaked.passed, leaked.detail
# exec delivery: child can resolve token via npmrc; assert via a probe script
probe = tmp_path / "probe.sh"
probe.write_text(

View file

@ -83,8 +83,11 @@ def test_verify_defaults_to_every_declared_field_and_one_path_denial(
monkeypatch.setattr(cli, "_require_lane_approval", lambda *_args: None)
monkeypatch.setattr(cli.OpenBaoClient, "resolve", lambda *_args, **_kwargs: object())
def fake_verify(_client, _entry, field, *, positive, negative):
def fake_verify(
_client, _entry, field, *, positive, negative, unrelated_token=None
):
calls.append((field, positive, negative))
assert unrelated_token is None
check = "positive" if positive else "negative"
return [VerifyResult(check, True, {"field": field, "reason": "test"})]
@ -100,6 +103,7 @@ def test_verify_defaults_to_every_declared_field_and_one_path_denial(
field=None,
positive=False,
negative=False,
negative_token_file=None,
)
assert cli.cmd_verify(_config(tmp_path), args) == 0

View file

@ -1,4 +1,6 @@
import json
import urllib.error
from types import SimpleNamespace
from secrets_engine.evidence import EvidenceWriter, _scrub
from secrets_engine.redact import looks_secret, redact_text
@ -39,3 +41,48 @@ def test_evidence_record_has_no_value(tmp_path):
# written to disk too
files = list(tmp_path.glob("evidence-*.jsonl"))
assert files and "npm_shouldnotappear123" not in files[0].read_text()
def test_evidence_records_append_only_hub_delivery_success(tmp_path, monkeypatch):
monkeypatch.setattr(
"urllib.request.urlopen",
lambda *_args, **_kwargs: SimpleNamespace(read=lambda: b"{}"),
)
writer = EvidenceWriter(
evidence_dir=tmp_path,
hub_url="http://hub.invalid",
topic_id="topic-id",
)
primary = writer.record("verify", result="pass", catalog_id="lane")
lines = [
json.loads(line)
for line in next(tmp_path.glob("evidence-*.jsonl")).read_text().splitlines()
]
assert len(lines) == 2
assert lines[0] == primary
assert lines[1]["action"] == "evidence-delivery"
assert lines[1]["result"] == "delivered"
assert lines[1]["related_record_id"] == primary["record_id"]
def test_evidence_records_hub_failure_without_raising(tmp_path, monkeypatch):
def offline(*_args, **_kwargs):
raise urllib.error.URLError("offline")
monkeypatch.setattr("urllib.request.urlopen", offline)
writer = EvidenceWriter(
evidence_dir=tmp_path,
hub_url="http://hub.invalid",
topic_id="topic-id",
)
writer.record("verify", result="pass", catalog_id="lane")
lines = [
json.loads(line)
for line in next(tmp_path.glob("evidence-*.jsonl")).read_text().splitlines()
]
assert lines[-1]["action"] == "evidence-delivery"
assert lines[-1]["result"] == "failed"

View file

@ -0,0 +1,61 @@
import copy
import os
import pytest
from secrets_engine.catalog import validate_entry
from secrets_engine.errors import ProvisioningError
from secrets_engine.openbao import read_strict_token_file
from secrets_engine.verify import verify_negative
from tests.test_catalog import VALID
def _entry():
return validate_entry(copy.deepcopy(VALID))
class NegativeClient:
def __init__(self, can_read):
self.can_read = can_read
self.tokens = []
def kv_can_read(self, _mount, _path, *, token):
self.tokens.append(token)
return self.can_read
def test_negative_check_fails_closed_without_real_unrelated_token():
client = NegativeClient(can_read=False)
result = verify_negative(client, _entry(), unrelated_token=None)
assert result.passed is False
assert "not proven" in result.detail["reason"]
assert client.tokens == []
def test_negative_check_uses_real_token_and_detects_policy_overlap():
denied = NegativeClient(can_read=False)
assert verify_negative(
denied, _entry(), unrelated_token="test-unrelated-token"
).passed
assert denied.tokens == ["test-unrelated-token"]
overlapping = NegativeClient(can_read=True)
result = verify_negative(
overlapping, _entry(), unrelated_token="test-overlapping-token"
)
assert result.passed is False
assert "LEAK RISK" in result.detail["reason"]
def test_negative_token_file_requires_mode_0600(tmp_path):
path = tmp_path / "unrelated.token"
path.write_text("test-unrelated-token", encoding="utf-8")
os.chmod(path, 0o644)
with pytest.raises(ProvisioningError, match="must be 0600"):
read_strict_token_file(path, purpose="negative verification token")
os.chmod(path, 0o600)
assert (
read_strict_token_file(path, purpose="negative verification token")
== "test-unrelated-token"
)