Skip to content

Add a Python Guardian reference implementation - #183

Draft
dtduc-git wants to merge 3 commits into
GenAI-Security-Project:integrationfrom
dtduc-git:refimpl/python-guardian
Draft

dtduc-git wants to merge 3 commits into
GenAI-Security-Project:integrationfrom
dtduc-git:refimpl/python-guardian

Conversation

@dtduc-git

Copy link
Copy Markdown

What changed

Adds a Python reference Guardian implementation under
reference-implementations/agt-python/: the ACS-Core wire contract
(JSON-RPC envelopes, handshake negotiation, the 19 hooks, wrapped MCP, the
five dispositions, the SessionContext chain with a published chain_hash,
replay and skew protection, the HMAC-SHA256 baseline signature), a policy
engine interface with a small built-in engine, and a test suite that validates
envelopes against this repository's schemas by relative path.

Which issue does this implement

Closes #86

  • This is an editorial correction (typo, grammar, link, formatting) with no change in
    meaning, so it needs no issue

Base branch

  • integration, because this touches the specification, schemas, a reference
    implementation, an adapter, tests, or CI
  • main, because every changed path is on the documentation lane allowlist in
    CONTRIBUTING.md

Type of change

  • Specification change (schema, hooks, events, AgBOM)
  • Reference implementation or adapter
  • Documentation
  • Tooling or CI
  • Governance (licensing, security policy, contributor docs)

I tested this

  • I synced my branch with the base branch before opening this
  • uv run pytest -v passes on my machine (110 tests, from
    reference-implementations/agt-python/)
  • uv run mkdocs build --strict passes on my machine

Checklist

  • Commits are signed off with git commit -s (required by the DCO)
  • Prose follows STYLE.md
  • No secrets, tokens, or internal URLs in the diff

Security

  • This change has no security impact

Coordination

I posted on #78 on 2026-09-22 asking whether that sample is still active, and
flagged the structural question (samples/ vs reference-implementations/)
to @rocklambros; no reply yet at the time of opening. This is a draft until
the workstream decides where Python lives.
It currently follows the
agt-go precedent under reference-implementations/agt-python/. It builds on
#78's FastMCP instrumentation idea rather than duplicating it — a host adapter
is not in this PR.

What it implements, and what it does not

Every ACS-Core item in docs/spec/conformance.md is implemented, so the
ServerHello accepts acs-core. Not claimed: acs-trace, acs-inspect,
acs-provenance, acs-crypto, acs-audit; agbom/* is refused with
-32003; Intent/IBAC is not implemented (Intent is optional and
IBAC-conditional, and ask_details passes through). README.md carries the
per-item status table naming the tests that cover each row.

Evidence

  • The ten black-box probes of the conformance harness's external mode are
    ported (tests/test_external_probes.py), nine of them signature-verified;
    the external mode itself is contributed in Add a Go Guardian reference implementation #169, which is not merged, so
    this port is the local gate until it lands.
  • The HKDF/JCS/HMAC vectors from Add a Go Guardian reference implementation #169 are vendored, with provenance recorded
    in tests/testdata/README.md, and pinned byte for byte.
  • Envelopes are validated against specification/v0.1.0/ by relative path, so
    a schema change fails the suite.
  • CI: .github/workflows/reference-implementation-python.yml is path-scoped
    to this tree; the existing TypeScript workflow's path filter is narrowed to
    its own tree so a Python-only change does not pay for a Bun install.

One spec inconsistency this PR works around

The examples in docs/spec/instrument/extend_mcp.md place the wrapped MCP
message directly in the ACS envelope's params — a shape
request-envelope.json rejects (params requires acs_version,
request_id, timestamp, metadata, and payload). This tree follows the
schema (the MCP message is carried intact in params.payload), as the Go port
in #169 does, and says so in its README. Worth an upstream issue if you agree.

An independent implementation of the ACS v0.1.0 wire contract under
reference-implementations/agt-python/: JSON-RPC envelopes, handshake
negotiation, the hook taxonomy, the five dispositions, the SessionContext
chain with a published chain_hash, replay and skew protection, and the
HMAC-SHA256 baseline signature.

Wrapped MCP is not implemented yet, so the ServerHello answers
profiles_accepted: [] rather than claiming acs-core; the README's status
table says exactly which parts are implemented.

The suite validates envelopes against this repository's schemas by relative
path, pins the cross-implementation signature vectors from the open Go port
(GenAI-Security-Project#169), ports the conformance harness's ten external probes, and pins every
hook's schema to its method name. A path-scoped workflow runs it in CI.

Signed-off-by: Duke - Duc Dinh <dtduc.contact@gmail.com>
protocols/MCP/* and the explicit-version wrapped:mcp-<version>/* form now
route to an MCP handler that reads the wrapped JSON-RPC 2.0 message (a
request or the response it answers), checks it, and hands it to the policy
engine intact; a malformed message is INVALID_PARAMS. A wrapped method is
negotiated only when the ClientHello declares the protocol in
wrapped_protocols, and the explicit-version form only at a declared version.

The method table now carries hooks.md's per-hook permitted dispositions: a
disposition a hook cannot carry is substituted (DENY where the hook permits
it, ALLOW with a reason at audit-only hooks and postCompact), including on
every Guardian-authored denial, so no DENY is ever sent at a hook that
permits none. Decisions are validated against modifications.json,
ask-details.json, and defer-details.json, plus §6.3's disjoint-target rule
with the wrapped call's own parameter-override pointer.

steps/sessionEnd closes the session (a later step is answered with a
session_closed denial, replay history survives), a key_id or agent_id other
than the session's is answered with a signed denial rather than an error, and
an undefined method is METHOD_NOT_FOUND while agbom/* stays
CAPABILITY_NOT_NEGOTIATED. The handshake now enforces the timestamp skew like
any request, responses are signed under the deployment's key_id throughout,
the transport refuses a missing or malformed Content-Length and closes
cleanly, and the TypeScript workflow's path filter is narrowed to its own
tree so a Python-only change does not pay for a Bun install.

Signed-off-by: Duke - Duc Dinh <dtduc.contact@gmail.com>
One recv() is not the whole response: http.server flushes headers and body
as two writes, and Linux split them across segments, so the tests failed on
CI while passing on macOS. The loop ending is itself the proof the server
closed the connection.

Signed-off-by: Duke - Duc Dinh <dtduc.contact@gmail.com>
@acs-board-reconciler acs-board-reconciler Bot moved this from Needs triage to In progress in ACS Project Tracker Sep 23, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

Port the AGT reference implementation to Python

1 participant