Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ jobs:
tests/test_bounded_scan.py::test_default_isolated_scan_supports_standalone_programmatic_call
tests/test_public_api.py::test_public_scanners_support_unguarded_consumer_with_guarded_parity
tests/test_content_guard.py::test_new_mode_applies_windows_git_filename_rejections
tests/cli/test_evidence_pack.py::test_audit_event_artifacts_preserve_nested_paths_for_duplicate_basenames
tests/test_windows_file_boundaries.py
- name: Verify Windows CLI report consumer contract
shell: bash
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ Why: keep static guard releases auditable while the package is still alpha.

## Unreleased

- Isolated repository-controlled context-policy regular-expression matching
behind the existing bounded scanner worker and added fixed pattern-count and
pattern-length limits. Timeout and limit failures remain deterministic,
sanitized configuration errors; no raw pattern or context text is emitted.
- Content-bound optional `agent-policy` audit-event references with a
canonical-JSON, profile-bound, public-safe digest. Producers now require a
reviewed repo-local event file and explicit profile, while consumers require
the separately supplied event and reject missing, malformed, or replaced
content. The event body remains outside the fixed seven-file public bundle.
- Defined a bounded demand-validation window through 2026-09-20 and froze
feature releases pending an explicit maintainer decision after the 2026-09-21
review. Marketplace publication remains separately prohibited without
Expand Down
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,13 +350,15 @@ Add `--digest-policy .agent-guard/context-digest-policy.yaml` to the `report`
command only after that policy is reviewed and committed.

**Optional reviewed audit event.** To record a companion `agent-policy` audit
event, add the same
`--agent-policy-audit-event <reviewed-audit-event-path>` option to both the
event, add the same `--agent-policy-audit-event <reviewed-audit-event-path>` and
`--agent-policy-audit-event-profile <reviewed-profile>` options to both the
`report` command and the standalone `evidence-pack manifest` command. Generate
both artifacts again after the producer has written the event and a maintainer
has reviewed it. The public bundle consumer requires the standalone manifest to
match the manifest embedded in the report. `agent-guard` records only the path
and does not validate the file's existence or content. Keep the event outside
both artifacts again after the producer has written the repo-local JSON event
and a maintainer has reviewed it. The manifest records only a sanitized
repository-relative path and a profile-bound canonical-content digest, not the
event body. If the standalone manifest is present, the public bundle consumer
requires it to match the manifest embedded in the report. It also requires the
event path and profile again to verify the binding. Keep the event outside
`.agent-guard/evidence`: the seven-file public bundle allow-list rejects it.

The following optional PR review command is available in `0.3.0`:
Expand Down Expand Up @@ -603,7 +605,10 @@ MCP 2026-07-28 protocol/runtime/OAuth changes do not justify runtime execution o
validation. No changelog item directly invalidates the current static committed-config labels, so
this update does not change their taxonomy or code. With `--evidence-pack-manifest`,
it embeds a public-safe artifact handoff manifest for pull request review. Add
`--agent-policy-audit-event <path>` to include a sanitized artifact reference to a companion `agent-policy` audit event without reading or embedding the event body.
`--agent-policy-audit-event <path>` and
`--agent-policy-audit-event-profile <profile>` to bind a reviewed repo-local
companion `agent-policy` audit event without embedding its body. Consumers must
receive that event separately and verify it with the same expected profile.

Read `recommended` as the reviewed static evidence baseline, not as the full
pin-integrity profile. The recommended preset can emit digest and context-lock
Expand Down Expand Up @@ -1006,7 +1011,7 @@ agent-guard context inventory --root <repo> --policy <yaml> [--json]
agent-guard context lock --root <repo> --policy <yaml> [--check --digest-policy <yaml>] [--json]
agent-guard mcp check --root <repo> [--policy <yaml>] [--json]
agent-guard surface inventory --root <repo> --context-policy <yaml> [--schema-version <v1|v2>] [--json]
agent-guard report --root <repo> --context-policy <yaml> [--evidence-preset recommended] [--path-policy <yaml>] [--content-policy <yaml>] [--content-scan-dir <dir>] [--api-policy <yaml>] [--mcp-config-check] [--mcp-policy <yaml>] [--digest-policy <yaml>] [--workflow-policy <yaml>] [--drift-check] [--drift-base-ref <ref>] [--agent-policy-audit-event <path>] [--format <markdown|json|github-annotations|sarif>] [--output <path>] [--stderr-summary]
agent-guard report --root <repo> --context-policy <yaml> [--evidence-preset recommended] [--path-policy <yaml>] [--content-policy <yaml>] [--content-scan-dir <dir>] [--api-policy <yaml>] [--mcp-config-check] [--mcp-policy <yaml>] [--digest-policy <yaml>] [--workflow-policy <yaml>] [--drift-check] [--drift-base-ref <ref>] [--agent-policy-audit-event <path> --agent-policy-audit-event-profile <profile>] [--format <markdown|json|github-annotations|sarif>] [--output <path>] [--stderr-summary]
agent-guard render-report --root <repo> --input <agent-guard-report.json> [--format <markdown|json|github-annotations|sarif>] [--output <path>]
agent-guard path check --root <repo> --policy <yaml> [--json]
agent-guard digest check --root <repo> --policy <yaml> [--json]
Expand Down
26 changes: 21 additions & 5 deletions docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,12 @@ Public-safe is a bounded sanitization contract over declared controlled fields
and controlled patterns. It is not a generic guarantee that an artifact contains
no secrets or PII, and it does not replace dedicated secret scanners.

API, content, and path policies also have fail-closed resource ceilings. The
current implementation accepts policy files up to 256 KiB and at most 64
policy-controlled regular expressions per scanner, bounds include lists and
filesystem walks, rejects repository-scoped include targets that resolve
outside the repository root, and limits each isolated matching run to five
API, content, context, and path policies also have fail-closed resource
ceilings. The current implementation accepts policy files up to 256 KiB and at
most 64 policy-controlled regular expressions per scanner, limits each pattern
to 4 KiB, bounds include lists and filesystem walks, rejects repository-scoped
include targets that resolve outside the repository root, and limits each
isolated matching run to five
seconds after worker startup. Registered and preregistration content target
walks also share a monotonic five-second enumeration deadline and charge
directory entries plus pattern/path glob-state work against the fixed traversal
Expand All @@ -78,6 +79,21 @@ with exit `2`; raw patterns, paths, or file contents are not included in the
error. These ceilings are implementation safety limits, not evidence-schema
fields or a promise of generic content/credential scanning.

Optional `agent-policy` audit-event entries are an additive v1 manifest field:
the existing required `path` and `role` fields retain their meaning, while new
producer output adds `content_binding`. The binding uses canonical JSON, an
explicit expected event profile, and a domain-separated SHA-256 digest encoded
as lowercase base32 with a controlled `b` prefix. This controlled digest is not
a raw hexadecimal hash and is the only hash-like value admitted on this field.
Current packaged consumers fail closed when an audit-event role lacks the
binding or when the separately supplied event does not match. Older v1
consumers may ignore the optional field, so callers that require content
binding must use a consumer version that documents this contract. The event
profile identifies the caller-selected contract but does not itself validate
the event against an `agent-policy` JSON Schema; that remains producer-owned.
The event body remains outside the fixed public bundle. Replacing both a
trusted manifest and its event is outside this binding's threat model.

Workflow inputs use the same fail-closed approach. Workflow policies are capped
at 256 KiB, individual policy strings at 4 KiB, and workflow files at 1 MiB.
Distinct workflow input is capped in aggregate, while duplicate normalized paths
Expand Down
27 changes: 21 additions & 6 deletions docs/evidence-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,20 +147,33 @@ agent-guard report --root . \
--mcp-policy .agent-guard/mcp-policy.yaml \
--digest-policy .agent-guard/context-digest-policy.yaml \
--agent-policy-audit-event path/to/reviewed-policy-admission-event.json \
--agent-policy-audit-event-profile agent-policy.audit_event.v1.1 \
--format json \
--output .agent-guard/evidence/agent-guard-report.json
agent-guard evidence-pack manifest --root . \
--report .agent-guard/evidence/agent-guard-report.json \
--artifact .agent-guard/evidence/agent-guard-report.json \
--agent-policy-audit-event path/to/reviewed-policy-admission-event.json \
--agent-policy-audit-event-profile agent-policy.audit_event.v1.1 \
--json
python examples/evidence_consumer.py \
.agent-guard/evidence/agent-guard-report.json \
--evidence-dir .agent-guard/evidence \
--agent-policy-audit-event path/to/reviewed-policy-admission-event.json \
--agent-policy-audit-event-profile agent-policy.audit_event.v1.1
```

The referenced event must already be produced and reviewed. Pass the identical
path to both commands: the public bundle consumer requires the standalone
manifest to match the manifest embedded in the report. `agent-guard` records
only the sanitized path and does not verify file existence or content. The
event itself is not part of the fixed seven-file public bundle.
The referenced event must already be produced, reviewed, and stored as a
repo-local regular JSON file. Pass the identical path and explicit expected
profile to both producers. The manifest records a sanitized repository-relative
path and a profile-bound digest. `agent-guard` reads and canonicalizes the
bounded event JSON locally to compute that binding, but never embeds the event
body. The consumer requires the event separately and fails closed when the
event is missing, malformed, supplied under a different expected profile, or
changed. The event itself is not part of the fixed seven-file public bundle.
The binding does not protect an attacker who can replace both the evidence
manifest and the event; use a signature, attestation, or immutable trusted
storage for that threat model.

When CI uploads evidence, pin third-party actions to versions or commit SHAs
according to the repository's normal supply-chain policy, and keep generated
Expand Down Expand Up @@ -216,7 +229,9 @@ The JSON report is a compact statement of what `agent-guard` checked:
evidence-pack expectations part of conformance.
- Optional `evidence_pack_manifest` records the sanitized artifact manifest for
reviewer handoff. Artifact roles are limited to `report` and
`agent-policy-audit-event`.
`agent-policy-audit-event`. Audit-event entries include a controlled binding
profile and public-safe canonical-content digest; consumers verify the event
supplied outside the public bundle.
- `context_lock` records whether discovered context files are covered by digest
policy, without emitting hash values.
- Optional `path`, `content`, `api`, `digest`, and `workflow` sections summarize
Expand Down
2 changes: 1 addition & 1 deletion docs/evidence-samples/agent-guard-report.json

Large diffs are not rendered by default.

15 changes: 8 additions & 7 deletions docs/quickstart-existing-repo.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,14 +273,15 @@ recommended report already contains the same conformance and manifest
sections.

If a reviewed `agent-policy` admission event already exists, add the same
`--agent-policy-audit-event path/to/reviewed-policy-admission-event.json` option
`--agent-policy-audit-event path/to/reviewed-policy-admission-event.json` and
`--agent-policy-audit-event-profile agent-policy.audit_event.v1.1` options
to both the `report` and standalone `evidence-pack manifest` commands, then
generate both artifacts again. The public bundle consumer requires the
standalone manifest to match the manifest embedded in the report. Keep the
event outside `.agent-guard/evidence`; it is not one of the seven allowed public
bundle files. `agent-guard` records only the sanitized path and does not verify
the event's existence or content, so pass it only after the producer has written
it and a maintainer has reviewed it.
generate both artifacts again. The event must be a repo-local regular JSON
file. If the standalone manifest is present, the public bundle consumer
requires it to match the manifest embedded in the report. It also requires the
event and expected profile again to verify its canonical-content binding. Keep
the event outside `.agent-guard/evidence`; it is not one of the seven allowed
public bundle files, and its body is never copied into public evidence.

## 5. Consume Evidence Safely

Expand Down
53 changes: 50 additions & 3 deletions scripts/check_wheel_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from __future__ import annotations

import base64
from email.parser import BytesParser
from email.policy import compat32
import hashlib
Expand Down Expand Up @@ -1904,6 +1905,18 @@ def main() -> int:
assert conformance_payload["status"] == "ok"
assert conformance_payload["conformance"]["schema_version"] == "agent-guard.conformance.v1"

audit_event_profile = "agent-policy.audit_event.v1.1"
audit_event_marker = "reviewed-wheel-contract-event"
audit_event_path = report_output.parent / "policy-admission-event.json"
audit_event_payload = {
"schema_version": audit_event_profile,
"status": "reviewed",
"marker": audit_event_marker,
}
audit_event_path.write_text(
json.dumps(audit_event_payload, sort_keys=True),
encoding="utf-8",
)
manifest_cli = run(
isolated_module_command(
python,
Expand All @@ -1919,21 +1932,55 @@ def main() -> int:
"--artifact",
r"C:\Users\alice\secret\agent-guard-report.json",
"--agent-policy-audit-event",
str(repo / ".agent-guard" / "evidence" / "policy-admission-event.json"),
str(audit_event_path),
"--agent-policy-audit-event-profile",
audit_event_profile,
"--json",
),
cwd=temp,
)
manifest_payload = json.loads(manifest_cli.stdout)
assert manifest_payload["status"] == "ok"
assert manifest_payload["evidence_pack_manifest"]["schema_version"] == "agent-guard.evidence_pack_manifest.v1"
assert manifest_payload["evidence_pack_manifest"]["artifacts"] == [
manifest_artifacts = manifest_payload["evidence_pack_manifest"]["artifacts"]
assert manifest_artifacts[:2] == [
{"path": ".agent-guard/evidence/agent-guard-report.json", "role": "report"},
{"path": "agent-guard-report.json", "role": "report"},
{"path": ".agent-guard/evidence/policy-admission-event.json", "role": "agent-policy-audit-event"},
]
audit_artifact = manifest_artifacts[2]
assert audit_artifact["path"] == ".agent-guard/evidence/policy-admission-event.json"
assert audit_artifact["role"] == "agent-policy-audit-event"
binding = audit_artifact["content_binding"]
canonical_event = json.dumps(
audit_event_payload,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True,
).encode("utf-8")
digest_domain = (
b"agent-guard.agent_policy_audit_event_binding.v1\0"
+ audit_event_profile.encode("ascii")
+ b"\0"
+ canonical_event
)
expected_digest = (
"b"
+ base64.b32encode(hashlib.sha256(digest_domain).digest())
.decode("ascii")
.rstrip("=")
.lower()
)
assert binding == {
"schema_version": "agent-guard.agent_policy_audit_event_binding.v1",
"event_profile": audit_event_profile,
"canonicalization": "canonical-json-v1",
"digest_algorithm": "sha256",
"digest_encoding": "base32-lower-no-padding",
"digest": expected_digest,
}
assert r"C:\Users\alice" not in manifest_cli.stdout
assert str(temp) not in manifest_cli.stdout
assert audit_event_marker not in manifest_cli.stdout

print(f"wheel contract OK: {wheel.name}")
return 0
Expand Down
7 changes: 6 additions & 1 deletion src/agent_guard/cli/evidence_pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ def add_evidence_pack_parser(top) -> None:
default=[],
help="optional repo-relative agent-policy audit event artifact path",
)
evidence_pack_manifest.add_argument(
"--agent-policy-audit-event-profile",
default="",
help="validated public profile identifier for every attached agent-policy audit event",
)
evidence_pack_manifest.add_argument("--json", action="store_true", help="emit JSON")


Expand All @@ -37,6 +42,7 @@ def run_evidence_pack_manifest(args: argparse.Namespace) -> int:
report_payload=payload,
artifact_paths=list(args.artifact or []),
agent_policy_audit_event_paths=list(args.agent_policy_audit_event or []),
agent_policy_audit_event_profile=str(args.agent_policy_audit_event_profile),
root=root,
)
except Exception as exc:
Expand Down Expand Up @@ -71,4 +77,3 @@ def run_evidence_pack_manifest(args: argparse.Namespace) -> int:
else:
print(json.dumps(manifest, ensure_ascii=False, sort_keys=True))
return 0

Loading