Repository navigation
Guardana Control 0.11.0
A tool outside MCP can now ask for a decision before it acts and report how
the call ended, through the experimental enforcement API (ADR-0051). The
same policy, approvals and trail apply as to an MCP call. A reference client and
conformance cases that a client in another language can run come with it.
REPEATED_DENIAL is version 2, which needs a signed route upgrade (see
Changed). Fixes from a code audit bound several waits and refuse records
another account could rewrite. From this release a version is a plain
X.Y.Z without the -alpha suffix
(ADR-0025); the
project is still alpha, and the 0.x series still makes no compatibility
promise between minor versions.
Added
- The enforcement API contract,
guardana.control.enforce.v1alpha1: the
admit,completeandabortmessages a tool outside MCP exchanges with
the plane as JSON over HTTP on a Unix socket (ADR-0051). Unstable and
outside the v1 promise;buf breakingdoes not compare it. guardana-gateway runserves it underlistener.kind: enforce_api, with
listener.socket,listener.execution_lifetime(1s to 1h, no default),
listener.max_body_bytes(1 KiB to 256 KiB, default 128 KiB),
listener.max_open_per_run(default 16, belowapprovals.max_open) and a
toolstable that classifies each tool by name. Such a plane needs
runs.dirand refuses upstreams, overrides and the keys only an MCP
listener reads; an MCP listener refuses the new keys. A sweep abandons
expired handles. A stop abandons every open execution before the spool
drains./healthzreports the adapter's counts underenforce, and
doctorchecks an enforcement API plane.
A guide shows a tool of your
own decided through it.adapters/enforce, its adapter, builds a tool's envelope from the run
token and the operator's tool table. A handle is single use and expires.
The adapter refuses a duplicate request key, closes an expired execution as
abandoned and limits a run's open executions. It blocks a rewrite that
moves the decided resource, and answersRECORDEDonly once the closing
record is written. It declares cooperative enforcement and reads a
resource and a run token the way the MCP adapter does
(internal/adapterkit).internal/enforceclient, the reference client, runs a tool only with the
bytes anEXECUTEanswer authorized. It aborts on an obligation it does
not know, completesUNKNOWNwhen the tool cannot tell how the call ended,
and never sends a request twice.testdata/enforce/holds the client and the receiver
conformance cases, each with a negative control.- The coverage inventory kind
enforce_tool: a tool an enforcement API plane
classifies is "decided, not enforced" in every mode, since a cooperating
client, not the plane, keeps it from its effect. - The pipeline can close an execution nobody completed or aborted as failed,
with result unknown and the new reason codeEXECUTION_ABANDONED. It never
closes one as a success and does not halt the plane for it. A byte mismatch
a client reports through a cooperating adapter blocks only that run's
material calls; one reported through an inline adapter halts the plane.
Metrics count them:
pipeline_runs_halted,pipeline_abandoned_total,
pipeline_sink_failures_abandoned_total,pipeline_halted_mismatchand
pipeline_halted_sink. - Control and supervision
explains the 1.0 goal and what the alpha does today: observation across
connected channels, procedure checks and reactions within scoped authority.
It keeps the AuthZEN, enforcement and operator API roles apart.
Backlog task B28 plans continuous supervision. - Planned requirements (B23–B27, proposed ADR-0050): previewing MCP setup,
qualifying coding-agent hooks, reviewing tool-definition changes,
simulating candidate policies and reviewing findings with an incident
export. None is built.
Changed
- On an
enforce_apiplane/metricswrites the enforcement API adapter's
counts (enforce_*) and the flaglistener_enforce_api, and leaves out the
MCP adapter'sadapter_*metrics.doctorchecks the socket's directory as
rundoes, and judges pause entries against the tool table. approvals.max_held_per_run(default 16, only withruns.dir, below
approvals.max_held) bounds one run's held requests; an expired hold no
longer counts against its run. A configuration withruns.dirand an
approvals.max_heldof 16 or less no longer loads until
approvals.max_held_per_runis set below it. A tool'sproviderandresource_typeare
bounded at 1024 bytes. Underpolicy.fail_open_readanenforce_toolline
says a read runs undecided while the policy is unavailable.- The configuration loader refuses a tool name over 1024 bytes, a
resource_fromthe adapters refuse, and a socket path over 103 bytes. examples/evidence-reportreports a timeout or unknown outcome as
uncertain, notfailed, exits 1 on it, and raiseseffect_uncertain.REPEATED_DENIALis version 2. When some denials it would cite are in doubt
(a broken chain or an event id read with two contents), it cites only the
clean ones and staysCONFIRMEDif at leastmax_denialsare clean;
otherwise nothing changes. Its finding ids change with the version.
route sign,react, thestopswriters and a plane refuse a route naming
rule_version"1". To upgrade, set it to"2", raise the route'sserial
and sign it, runstops init --route <new> --public-key <file> --carry <old dir> --carry-route <old route> <new dir>, pointreaction.route,
reaction.stopsandreactat the new route and list, and restart the
plane. The carry keeps every unlifted stop, at version 1. A lifted run whose
denials still meet the rule is stopped again by its version 2 finding, which
has another id, and is lifted again. Only--carry-routereads the old
version.- The adapter capability
Blockis nowEnforcement: none, cooperative or
inline.ENFORCE,APPROVEandLOCKDOWNneed at least a cooperating
adapter, and the MCP adapter is inline;doctorand the modes table name
cooperative and inline enforcement apart. - Two approvals from the security maintainers now cover every file ADR-0008
says the gate is built from (all ofscripts/and.github/, the
Makefile, the linter and buf configurations,go.modandgo.sum) and
the gateway's wiring of its decision point, adapter, approvals and stop. A
test holds the declaration to the record's list. - README and the home page show exact-action approval, changed tool definitions
and a denied refund retried through another tool, with their coverage limits
and links to the policies and runnable example. - README, the home page and the social card lead with observing agents and
controlling how they act. The 1.0 goal is markedplannedbeside what the
alpha does, and the declared repository description says the same.
Fixed
scripts/github-settings-check.shand the bootstrap, run by macOS's bash
3.2 on a loaded machine, could build a declared ruleset as nothing and
report it unknown: bash 3.2 fails a builtin's write to a pipe that a child's
exit interrupts ("printf: write error: Interrupted system call"). The
declarations are now printed throughcat, which nothing interrupts./healthzcould show the spool drained while the exporter's
acknowledgedcount did not yet include what it had released, since it
read the exporter first and the exporter counted a release after making it.
On a loaded machineguardana-gateway scenario runthen failed a foreign
trail file with the wrong reason. The spool is now read first, and a release
is counted under the lock the exporter's counters are read under.guardana-control notifycould count a delivered alert as failed with
"killing the program's process group: operation not permitted". On a loaded
macOS host the kernel can refuse the program's group with EPERM for a moment
after its leader was reaped. It is now asked again for up to a second; a
group still refused after that is reported as before.- Opening a findings log, an observation log or a trail file cut a torn last
line by the length read before the file was locked. When a second writer
opened the same file, cut the tail and committed a write between the first
one reading the length and taking the lock, the first then truncated that
committed write. Open now reads the length and judges the file again once
it holds the lock. - The records, markers, state and lock files under an approvals, a runs and
a hold journal directory were read when a group or the world could write
them, and all but the hold journal's also when another account owned them.
An account with write access to such a file could rewrite it in place,
granting an approval, extending a run or washing a root clean. Each is now
refused as a file that is not the directory's. - A hold journal entry was read whatever schema version its approval carried.
An approval of another major, or with none, now leaves the entry unreadable. - Closing an approvals handle while a call ran on it was a data race on whether
the handle was open. - A pause, a stop or a halt that came while an opened run's root was being
raised was missed, and the call was recorded as started anyway. The plane
now checks them again after a raise that wrote, and a halt raised after
admission also blocks a material call before a resume spends its approval
and before it starts. - One opened run can hold at most 16 requests for approval, so it cannot fill
approvals.max_heldfor every other run; a hold past that is blocked
EVIDENCE_UNAVAILABLE, like one pastapprovals.max_held. /healthznames only the plane's own halts: a mismatch that halted one run
no longer setshalt.executed_args_mismatch, andhalt.evidence_unwritable
is set beside it when both stand.- A client's abort whose record the sink refused counts as a lost closing
record, sorunexits 1, as a lost completion did. make qualityruns every check even when make is told one is up to date
(-o,--old-file, or either inMAKEFLAGSorGNUMAKEFLAGS), and refuses
aMAKEorGOfrom the environment, any command-line variable but
GO=go,-e, aFUZZTIMEorRANGEfrom the environment, a makefile read
beside theMakefile, and an--evalor-Estring for any target; its
second make starts with none of the first's flags, and CI clears make's
flag variables before running it.- The test report refuses a passing package in which a test started and never
reported a result, as when the test ends the process with exit status 0. A
test that writes its own pass before it exits still hides the tests after
it; review catches that. - The documentation checks read the commit's own file list in a gate-commit
export instead of a walk that skips dot-directories. - The release gate refuses a commit that is not main's head, and refuses
unless main's newest CodeQL analysis is of the commit being released. guardana-gatewaywaited at start, with no listener bound, on an upstream
that took the connection and never answered its initialization. The
connect is now bounded byupstream.list_timeout, and the configuration
reference says so.runs open,runs close,runs listandsupervise --runswaited without
a bound for another operator command's lock while opening the directory;
their ten-second bound now covers the open too.superviseread a request whose events name two runs as one run's request.
Such a request is now in doubt and confirms nothing.- A floor store that stopped answering kept one call per policy poll waiting
in the background; calls to it now take turns, so at most one waits. - A raise of a policy floor could wait out its deadline while reads of the
same floor directory in the same process ran back to back, since each read
took the lock again before the raise next tried. Reads that start while a
raise of the process waits now go after it; across processes the lock
still favours no one.
Image: ghcr.io/guardana/control-gateway@sha256:e556ba3f4ca62614ce6e55678b437dd16a0b1b2ee6fe7160ca611f8e31654793