Repository navigation
Conversation
Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
…179) CodeQL default setup was turned on for this repository on 2026-09-21 and covers the Actions language. This adds zizmor, which the [OWASP GitHub Actions Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/GitHub_Actions_Security_Cheat_Sheet.html) recommends beside CodeQL for defense in depth. zizmor reads each workflow's logic and checks what CodeQL does not: hash pins no tag points to, version comments that disagree with their pin, App tokens broader than the job needs, and credentials a checkout leaves in `.git/config`. Run against `integration` before this change with online audits on, zizmor 1.30.1 reported 15 findings. This clears 13. The other two are `github-app` findings on `board-reconcile.yml`, whose App token takes the installation's full grant instead of naming its repository and permissions. Token scopes are not changing right now, so those two stay open in code scanning, where they record the deferred decision instead of disappearing behind a suppression. - **`pr-intake.yml`, 1 high.** zizmor flags every `pull_request_target`. This one checks out no code and reads the pull request body as data through an environment variable, which the workflow's header already explains. It carries an inline `zizmor: ignore` that points at that explanation. - **Eleven checkouts across nine workflows, 11 low.** Each left the job's token in `.git/config`. None of those jobs pushes with git. They call `gh` with `GH_TOKEN` or touch no network at all, and `sync_version.yml` hands its push to `create-pull-request`, which configures its own token. All eleven now set `persist-credentials: false`. The checkout in `validate-owasp-metadata.yaml` also gains the `# v7.0.1` comment every other pin carries. - **`reference-implementation.yml`, 1 medium.** The setup-bun pin `735343b` is v2.0.2, and its comment said v2.0.1. Only the comment changes. Dependabot's #83 already treats it as 2.0.2. The new workflow runs zizmor from a `zizmor` dependency group in `uv.lock`, pinned with hashes. No new action enters the Actions allowlist, and Dependabot's uv ecosystem moves zizmor under the existing 7-day cooldown. The group is not a default group, so `uv sync --locked` in the deploy and test jobs never installs it. A default sync against this lockfile would uninstall it. It runs on pull requests and pushes that touch `.github/`, an action definition, `pyproject.toml`, or `uv.lock`, and weekly, because the online audits compare pins against advisories published after the pin. Findings upload as SARIF under the category `zizmor`, and the job stays green on findings so code scanning tracks them and a ruleset can gate on them later. A tool or upload failure still fails the job. A pull request from a fork cannot upload with its read-only token, so it gets annotations instead. `upload-sarif` is pinned at v4.38.0 because v4.38.1 was three days old, inside the cooldown this repository applies to its dependencies. Open pull requests #162 and #169 add workflows that zizmor will flag once they land, for unset `persist-credentials` and the same `# v2.0.1` setup-bun comment. Verified locally: zizmor 1.30.1 with online audits reports only the two deferred `github-app` findings, with 1 ignored and 26 low-confidence findings suppressed by the default persona. The workflow's own command produces valid SARIF 2.1.0. `actionlint` is clean. `uv lock --check` passes under uv 0.9.9, the version CI pins, and the lockfile diff adds only zizmor. `uv run pytest` gives 278 passed, 1 skipped. Signed-off-by: Rock Lambros <rock@rockcyber.com>
…comment in the Go workflow zizmor, added in GenAI-Security-Project#179, reports both once this workflow lands. Nothing in the job pushes with git, so the checkout sets persist-credentials to false. The setup-bun pin 735343b is v2.0.2, and only its comment changes. Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
…efore the code On the 24 Sep call the pull request was called too big to review. This commit changes documentation only. - docs/architecture.md, new: what the Guardian is made of, a diagram for each path a request takes, and the order to read the code in. - docs/embedding.md is now docs/extending.md: what your own implementation of each interface must do. - README: "Start here" links every section and document. - Each fact lives in one document; a Mermaid diagram GitHub could not render and several statements that did not match the code are fixed. No code changed. make check, uv run pytest (279 passed) and uv run mkdocs build --strict pass. Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
|
@afogel @rocklambros Following today's call I reorganised the docs so the review can go in two steps.
No code changed. Happy to answer questions while you go through it. |
|
I completed an independent substantive review of the Go Guardian against the ACS v0.1.0 requirements and existing TypeScript Guardian behavior. I checked the handshake/envelopes, duplicate request_id handling, chain hashing, all five dispositions, MODIFY/ASK/DEFER behavior, replay/skew handling, HMAC, session and chain handling, intent immutability, decision honoring, system/ping, wrapped MCP, PolicyEngine/delegate_to, hook-specific behavior, failure/state consistency, concurrency, TypeScript↔Go parity, external Guardian wire behavior, AGT bridge parity, ACS-Core vectors, and the existing #160–#167 regression areas. |
|
Not a review of the Go implementation, I have not read it at that depth. But I did check it against the six reference-Guardian bugs in the #160 to #167 range, and each one has a named mechanism here. Capability enforcement returns Worth adding that the wire here is authenticated. Anything other than All of those are filed against the TypeScript reference, so they stay open on it. But a second implementation that does not reproduce them is useful evidence for what the reference should be doing, and every claim above is cheap to re-check from the name. |
Brings in PR GenAI-Security-Project#21, which changed specification/v0.1.0/hooks/subagent-stop.json. Claude-Session: https://claude.ai/code/session_01NFMr7gTJSEMihfcUTXMhR4 Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
…ecurity-Project#21) - Refresh the embedded hooks/subagent-stop.json: final_chain_hash is now optional, and the minimal subagentStop in the tests omits it. - The Codex and OpenCode host adapters deny a MODIFY their host cannot apply with modify_unsupported and record it in their audit log (§6.5). They record a signed MODIFY of invalid shape as modify_invalid (§6.3). - The docs cite GenAI-Security-Project#21 as merged ACS-Core, not an open proposal. Claude-Session: https://claude.ai/code/session_01NFMr7gTJSEMihfcUTXMhR4 Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
|
Merged integration (with #21) into this branch and updated the code for it:
The Guardian works the same as before. |
|
Re-checked the eight against 2b8ccf5 after the merge, since my earlier note named code at 158243d. All still hold: On the section 6.5 change, the One thing the merge changed in this PR's favour. The acs-core row #21 landed now names replay protection and a baseline HMAC-SHA256 signature in the mandatory column. Those are the two things this tree has and the TypeScript reference discloses as absent, so the floor moving today made the gap between them wider rather than narrower. |
The host adapters answered DENY at every hook when they could not honor a Guardian decision. §6.5 forbids DENY at postCompact, and hooks.md permits no DENY at five hooks, whose actions have already happened. - Where the hook permits no DENY, the host's refusal is ALLOW plus an audit record. This covers a MODIFY the host cannot apply (modify_unsupported), a MODIFY of invalid shape (modify_invalid), a refused handshake (now recorded as session_refused) and the decision failure posture. - The handshake diagram shows the AuditLog envelopes. Claude-Session: https://claude.ai/code/session_01NFMr7gTJSEMihfcUTXMhR4 Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
The default build uses the pinned AGT Rust library through its C ABI. Set EVALUATOR=go to build or run the Go evaluator explicitly. Keep one projection and one evaluator interface. Verify the loaded library against its build record. Refuse changed sources and missing native support. Compare both evaluators against recorded decisions. Preserve empty result labels and retain decision slots until uninterruptible engines return. Use GOOS and GOARCH for Linux amd64 and arm64, both Mac architectures, and Windows amd64. Package the library and build record with the binary. Use MinGW for Windows and the Apple SDK for native macOS builds. Add portable timeout and shutdown tests and a five-platform CI matrix. Build Linux amd64 and arm64 images with Docker Buildx. Keep secrets outside the image. Run signed conformance probes through a published host port. Update build, configuration, architecture, and deployment documentation. Validation: Linux make check passes. Windows AGT, CLI shutdown, and recorded decisions pass under Wine. Both Linux images build and pass all 10 external probes; arm64 executes through Docker emulation. Real Windows and macOS runner results remain unverified. Claude-Session: https://claude.ai/code/session_01NFMr7gTJSEMihfcUTXMhR4 Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
Claude-Session: https://claude.ai/code/session_01SzmLMNjG3KSaCFfg8aKB11 Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
Remove agtbridge/goeval, the port's Go reimplementation of AGT's policy runtime. The README merged in GenAI-Security-Project#256 requires maintained implementations to ship only AGT's runtime as their evaluator. AGT is now the only evaluator. Remove OPA from go.mod. The README allows a second evaluator in tests as a drift check; do not keep goeval for that, because it would have to follow every AGT update. A build without the agteval tag still compiles, and the command then refuses to start. Leave policy.evaluator, EVALUATOR and the agtbridge.Evaluator interface in place, with "agt" as the only value, so another evaluator is one new case. The bridge tests also use the interface to plug in fake evaluators. Switch the bridge tests that used the Go evaluator to AGT. They used it because it was fast and needed no Rust. AGT already ran all 49 recorded sessions before this change. Drop TestNewRefusesAnUnsupportedManifest: it tested which manifest features the Go evaluator refused. Drop the load measurements that only the Go evaluator produced. Regenerate the spec's JSON schema copies and re-record the TypeScript Guardian's answers. No recorded decision changed. Add a signer test against the example key in spec section 10. GenAI-Security-Project#272 defines the HKDF key-derivation parameters there. The Go signer already used exactly those parameters. In docs/conformance.md, mark three expectations of the GenAI-Security-Project#256 README as not met yet: - The command accepts one HMAC key. The README requires at least two keys at once to support key rotation. - A full audit queue, or a write failure, loses audit events the spec requires while decisions continue. The README requires keeping them and denying instead. - An oversized or unreadable request body gets an error, and the engine's deadline equals the declared timeout. The README requires a deny and a lower deadline. Mark the interim verification as met: make check runs the TypeScript conformance probes against this port. GenAI-Security-Project#273 needs no change. TestDecisionFailureRecordsItsClass and TestHandshakeFailureRecordsItsClass in internal/hostadapter already cover recording every decision failure. Verified locally: make check passes, including all 10 external wire checks. The container image and other CI platforms were not tested locally. CI runs those checks. Claude-Session: https://claude.ai/code/session_01SzmLMNjG3KSaCFfg8aKB11 Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
The GenAI-Security-Project#256 README expects the configuration to map each key_id to one secret and accept at least two at once, so a secret is replaced without an outage. The library already took several keys; the command took one. Add security.hmac_previous_keys, a list of key_id and secret_file pairs. The Guardian verifies a request under any configured key and answers under the same key, as section 10 requires. An unknown key_id is still refused with SIGNATURE_INVALID. docs/configuration.md gives the steps to replace a secret. TestServesThePreviousHMACKey runs the command with a current and a previous key and checks that both are answered; it fails when the previous keys do not reach the signer. Claude-Session: https://claude.ai/code/session_01SzmLMNjG3KSaCFfg8aKB11 Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
The GenAI-Security-Project#256 README expects the default time limit to be below the timeout_config the Guardian declares. The Guardian declared decision_timeout and gave the engine the same time, counted from the engine call, so a step denied for time reached the agent after it stopped waiting, and the agent applied on_decision_failure. Count the deadline from the request's arrival and end it protocol.answer_margin before the declared timeout. Reading the body, the wait for the session lock and the session's load run under this Guardian's own deadline, since a session's terms are known only once it is loaded; the rest of the step, the store and the engine included, runs under the session's negotiated timeout. Recording the decision gets half the margin, past the decision's deadline but not past a lost session lock, a shutdown or the caller's deadline; signing and sending keep the other half. The margin scales with a session's negotiated timeout. guardian.yaml ships 500ms. policy.opa_timeout must stay below decision_timeout minus the margin, which keeps the GenAI-Security-Project#269 rule, and server.read_timeout at least that, since the body's read ends there. An engine that never answers, a store that stops answering, a step that waits too long for its session's lock, a late decision whose recording stalls, and a stalled store under a session negotiated with a shorter timeout all get an answer in time (TestDenialArrivesBeforeTheDeclaredTimeout, TestSlowStoreAnswersBeforeTheDeclaredTimeout, TestLockWaitAnswersBeforeTheDeclaredTimeout, TestSlowRecordingAnswersInTime, TestRecoveredSessionAnswersInItsOwnTime), and a session negotiated with a longer timeout keeps it (TestRecoveredSessionKeepsItsLongerTimeout); each fails without the change. A lost lock and a shutdown after the decision's deadline, and the caller's deadline, still stop the recording (TestLostLockStopsRecording, TestShutdownStopsRecording, TestCallerDeadlineStopsRecording). docs/conformance.md cites GenAI-Security-Project#324 for the body size case: the Guardian cannot sign a deny without the key_id and session_id inside the body it does not read. Claude-Session: https://claude.ai/code/session_01SzmLMNjG3KSaCFfg8aKB11 Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
The GenAI-Security-Project#256 README expects the audit events §6.4, §6.5 and §9.2 require, and the §8 session chain, to be never dropped: if the Guardian cannot keep them, it answers deny. §6.4's record is the Observed Agent's, and the host adapter already denies when it cannot write it; the §8 chain lives in the store, and a step whose entry is not appended is already denied, or allowed with the reason at a hook that permits no DENY. The Guardian's own events, those of §6.5 and §9.2 and the unresolved request_id_ref hooks.md requires at toolCallResult, were written in the background and dropped when the queue was full or the disk failed, and rotation deleted the oldest file. Add AuditLog.RequiredEvent, which returns once the event is written or its context ends. modify_unsupported, which names the rewrite not delivered, ask_substituted and request_id_ref_unresolved go through it; when it fails, the step is answered DENY with the substitute's reason code, which §6.5 and §9.2 require, and audit_unavailable, a reason code of this port. An embedder that implements AuditLog adds the method. A nil AuditLog records nothing, so such a step is denied. At postCompact, where §6.5 permits no DENY, the step is answered ALLOW with modify_unsupported and audit_unavailable, and the failure is reported to the audit log and its error callback. JSONLAuditLog writes a required event in queue order and reports the write by the step's deadline; a write still pending then also reaches the error callback and Close. An event whose waiter gave up before the writer took it is not written; a write already under way may still land, followed by the Guardian's failure record for the same request. Each file gets its own size and file count: audit.max_envelope_file_bytes and audit.max_envelope_files replace audit.max_file_bytes and audit.max_files, and audit.max_event_file_bytes and audit.max_event_files are new. The envelope file deletes its oldest file. The event file deletes none: while audit.max_event_files remain, each event is dropped and counted and a required one fails, until the deployment archives them; each new fill reports once. guardian.yaml keeps at most 1 GiB of envelopes and 2.25 GiB of events. NewJSONLAuditLog takes one JSONLRotation per file and refuses two paths that rotate onto each other. Tests cover a failing, a panicking and a missing log for each of the three events, a stalled, a queued-out and a failed write, an abandoned event, retention across rotations, a full and a refilled event file and overlapping paths; each fails without its fix. Claude-Session: https://claude.ai/code/session_01SzmLMNjG3KSaCFfg8aKB11 Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
Older edge cases, found in review, in code that predates the README changes. A §6.5 or §9.2 substitution answers with modify_unsupported or approver_unavailable and reasoning that names the modification or the missing approval capability. A later answer in the same step replaced it with a plain DENY and lost both: a store failure, a store panic, the session's deferral bound and an invalid intent grant. Each now keeps the substitute's reason code and reasoning, and audit_unavailable when the required audit event was not written. A crash during a write could leave an audit file ending inside a record, and the next record then joined that line. The file now gets the line ended when it is opened. Each case has a test that fails without its fix. Claude-Session: https://claude.ai/code/session_01SzmLMNjG3KSaCFfg8aKB11 Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
|
@rocklambros @artmaro, hi! On the call I promised to double-check #169 after the latest merges into Done - #169 now includes the latest
One case the README leaves open: a body over the size cap can't get a signed deny in any Guardian - opened #324, marked "Not met yet". Also:
|
artmaro
left a comment
There was a problem hiding this comment.
Thanks, Konstantin (@President), for bringing this in line with the README so quickly.
I reviewed #169 against the expectations in reference-implementations/README.md (#256). I also built and ran it locally with the native AGT runtime. make check passes, and the Guardian starts with the AGT evaluator and answers over HTTP.
From the Reference Implementation side this is ready to merge.
Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
GenAI-Security-Project#309 changed packages/guardian/src. Only its tree ID changes; all 49 answers stay the same. Claude-Session: https://claude.ai/code/session_01Rhwvb5CFUg1pyMvKKnbHXt Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
|
@rocklambros @artmaro - synced with |
Claude-Session: https://claude.ai/code/session_01NFeGFKPFpAQbJj5ZhbHkRy Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
go generate ./internal/schema copies specification/ into the module, and TestSchemasMatchSpecification compares the two. Claude-Session: https://claude.ai/code/session_01NFeGFKPFpAQbJj5ZhbHkRy Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
|
Synced with |
The library takes an approver (agtbridge.Options.Approver), but the acs-guardian binary had no setting for it, so every escalate verdict was DENY approver_unavailable, whatever policy.ask_substitution said: with defer, the agent got DENY, not DEFER. policy.ask_approver (type and id) in guardian.yaml now names it. The §9.2 substitution applies first, so deny and defer work with or without an approver. Under none, an ASK names the approver; without one it is DENY approver_unavailable, and the reasoning and the audit event say the Guardian names no approver. The shipped guardian.yaml keeps ask_substitution: deny for its Codex and OpenCode examples and names the approver the TypeScript Guardian of GenAI-Security-Project#229 uses, deployment-default. TestServesAnAsk runs the binary over the reference policy with its approval rule on, for none, deny and defer, each with and without an approver. Claude-Session: https://claude.ai/code/session_01NFeGFMmjiqJzqraaWso548 Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
ACS §12.1 gives the policy engine the request envelope, SessionContext, Intent and provenance. The Go core already passes them to the engine, but the AGT bridge built the snapshot from the call alone, so a rule could not see who sent the step or where an argument came from. The snapshot now places them where AGT's snapshot specification names them. envelope carries agent, session, intervention_point, timestamp and tenant. tool_call carries the whole call payload, each argument's provenance keyed by argument name because args holds plain values, and tool_result the whole result payload with each output's provenance. actor carries metadata.user_context, metadata the rest of metadata, and intent the session's Intent. The session's chain stays out: it grows with every step, and AGT refuses a snapshot over 1 MiB. docs/architecture.md, "What the policy receives", lists every member. TestPolicyReadsTheRequestAndSession and TestPolicyReadsTheToolResult decide on the new members at both gates and pin the snapshot's members. make load on the same host shows no change in decisions per second or latency, with sessions of 20 and of 500 steps. Claude-Session: https://claude.ai/code/session_01NFeGFMmjiqJzqraaWso548 Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
When mapping.yaml named no policy_target_argument for a tool, the AGT bridge returned an error before AGT ran, and the Guardian denied the call. A manifest whose policy_target is not the copied argument, such as $.tool_call.name, could not govern any tool, and a tool with no arguments could never be allowed. The TypeScript Guardian has the same behaviour. Now the bridge copies nothing into acs_policy_target in that case, and AGT evaluates the snapshot. The check that refuses an agent-supplied acs_policy_target stays on. A transform of the policy target has no argument to change, so the Guardian still denies it. The reference mapping names an argument for every tool, so the recorded TypeScript answers do not change. TestPolicyTargetOutsideTheArguments allows a call with no arguments and denies a transform with no argument named. Claude-Session: https://claude.ai/code/session_01NFeGFMmjiqJzqraaWso548 Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
|
@rocklambros @artmaro - sorry, three more commits, and not small ones. I started to use the Go Guardian in practice, with an OpenClaw agent (#287), and found real gaps:
The TypeScript Guardian has gaps 2) and 3) too - I will open separate issues for them.
|
artmaro
left a comment
There was a problem hiding this comment.
@President Konstantin, thanks for checking these under real work load as well. I testing it with claude and it looks good, I see no blockers to merging from my side.
@rocklambros @afogel please take a look to approve. Thanks
|
@rocklambros @bar-capsule @afogel, following today’s call: this PR closes #87, which is accepted and assigned to me.
Is anything left for me to fix? |
Closes #87. A Guardian written in Go. It is in
reference-implementations/agt-go/, next to the TypeScript Guardian.I compared the two Guardians on the same sessions
The policy files are the same. Both Guardians read the AGT Rego files in this repository. The Go Guardian runs them with the AGT runtime itself: AGT's Rust library, linked through its C ABI (cgo), built from source at the commit
agt-native.lockpins. A test requires that commit to be the oneagt/agt.lockpins for the policy bundle. AGT is the only evaluator, because the #256 README asks maintained implementations to ship AGT's runtime. A build without cgo still compiles, and the command then refuses to start.I wrote 49 test sessions and recorded the TypeScript Guardian's answers to them. Then I sent the same steps to the Go Guardian, signed, the way an agent sends them.
Two declared exceptions, both in
docs/differences.md. When the engine fails, both Guardians deny withevaluation_failed, and each writes its own reasoning. And in two sessions the policy target is not a string: the Go Guardian denies it, as the TypeScript fix in #229 does, while the recording still holds the old ALLOW. The test fails once a new recording carries that fix, so the exception cannot outlive it.The recorded file also stores the Git object IDs of the TypeScript paths that decide an answer: the Guardian and bridge sources,
mapping.yaml, the policies,agt.lock, and the bridge'spackage.json, which pins the AGT SDK. The testTestRecordingIsCurrentcompares them with the current ones. Object IDs depend only on content, so a squash merge does not change them. If someone changes that code, this test fails, and the sessions have to be recorded again withscripts/record-ts-cases.sh.What it does
cmd/guardianis a program. You run it next to an agent.make runstarts it withguardian.yaml. It uses AGT as its policy engine.make agt-nativebuilds AGT's library and fetchesopa;docs/building.mdcovers Linux, macOS and the Windows cross-build. Each decision under AGT runs oneopaprocess, somake loadmeasures latency and memory under concurrent clients;docs/load.mdhas the results behind the shippedlimits.max_engine_calls. The HMAC secret file holds base64 of at least 32 bytes, under a configuredkey_id; the audit logs rotate by size;docs/configuration.mdlists every setting, anddocs/conformance.mdmaps each expectation of #256 to its tests.guardianis also a Go library. You give it four things: the policy engine, the keys, the session storage, and the place to write the audit log.CONTRIBUTING.mdleaves the policy engine and the signature algorithm to deployments. The library treats storage and the audit log the same way, so it takes each of the four as an interface.examples/custom-engineis a second Go module. It gives its own four and answers a step.What checks each claim
docs/conformance.mdhas one row per item. Each row names its tests.make docs-checkfails if a named test does not exist.specification/v0.1.0/. The test reads those files by relative path, so if the specification changes, the test fails.denythat was changed to anallow. It also refuses a response signed with the key of another session.docs/conformance.mdsays what it claims and what it does not claim. ACS v0.1.0 has no certification, so the claim is a self-declaration, and each row names the test that covers it.docs/differences.mdlists every difference from the TypeScript Guardian.What I ran
uv run pytest -vpasses, 1206 tests.uv run mkdocs build --strictpasses.make checkin the module runs gofmt,go vet, Staticcheck,govulncheck, the tests with the race detector, theexamples/custom-enginemodule, the Codex and OpenCode adapter tests, the shared conformance check against a Guardian that is running, the coverage check, andmake docs-check. It passes. Coverage of the protocol packages, the AGT evaluator, the host adapter and the test Observed Agent is 86.8%. The floor is 80%.Two defaults you may ask about
on_decision_failure. The library keeps the default from §6.4, which isproceed. Theguardian.yamlin this repository setsdeny. So the Guardian you can run here blocks the step when it cannot decide.Two Codex settings in
make codex-example.-s workspace-writestops Codex from asking the person about each command, so the Guardian is the one that decides. Codex also asks once whether it may run the hook file of this project, and--dangerously-bypass-hook-trustskips that question. Both settings apply only to the Codex program that this command starts.examples/codex/README.mdsays what each one opens.Examples
examples/codex/andexamples/opencode/show that a host uses the decision the Guardian sends.make codex-exampleandmake opencode-examplestart the real Codex and OpenCode programs.make checkdoes not start them. It sends recorded host events to the same adapter code. When reference adapters for these hosts are added, these examples can point to them.Files outside the new directory
There are seven. Two are under
.github/: the Go job,.github/workflows/reference-implementation-go.yml, which also runs weekly sogovulnchecksees new advisories, and thegomodentries in.github/dependabot.yml. Theagt/README.mdchange updates its count of test files, rewords the two sentences around it, and tells a TypeScript contributor when to re-record the answers the Go Guardian replays.The other four add one check to the TypeScript conformance runner. The check sends signed requests over HTTP to any Guardian and reads the answers. It works for any Guardian, not only this one. That is why it sits next to the runner and not inside the Go module.
packages/conformance/src/external-guardian.tsis the check.packages/conformance/test/external-guardian.test.tsis its test.packages/conformance/src/main.tscalls the check and prints whether it ran.package.jsonadds theconformance:guardianscript.