Repository navigation
Guardana Control 0.15.0
Upgrading from 0.14 changes four things an operator must act on. collect
writes a directory of segments of one tenant and project and requires a
credential, which the plane sends through export.headers: --dir,
--tenant, --project and --credential-file replace --out; past
--max-bytes the plane's material calls are blocked until an operator prunes
(ADR-0072).
A run belongs to one project: a 1.0 run record is refused, so open each live
run again in a new, empty directory, under a new task
(ADR-0068). A gateway
configuration that classifies a tool WRITE, DELETE or CONFIGURE needs
environment, and an MCP override classified TRANSACT or
IDENTITY_OR_ACCESS needs its upstream's tenant_id. An approval record
whose approver now reads as authn: or mixes scripts resolves as
APPROVAL_STATE_UNKNOWN and is never spent. Formats move too: the evidence
export is 1.1, whose pruned_before the evidence report raises as a gap;
a supervise report that names a source it could not rely on is 0.2; and an
egress import writes observation records 0.3. A 0.14 reader refuses each of
them. New: observe import reads an egress proxy's access log, and coverage
counts an egress path as observed by the connections of the sources it names
(ADR-0069).
Operators can sign a roster of operator keys, though no plane reads it yet
(ADR-0071). The
hook profile's qualification was not run again for this release.
Security
- The filesystem server the guide's MCP client check installs takes
glob
14.0.2, which fixes GHSA-3g3m-2fjr-6pw4, andminimatch11.0.1, which
fixes GHSA-g3hv-75wg-m249, throughoverrides: the server, at the version
its fingerprints were taken from, asks for the majors below them, where no
fix exists. Its search and tree tools ran with them. Nothing this project
ships uses either. - The gateway no longer takes an approver id from its approval store that
reads as the reservedauthn:prefix. Such an answer runs nothing: a retry
closes the held trail with the plane's own approval, expired, and a
reconciliation closes a lost holdAPPROVAL_STATE_UNKNOWN. Both shipped
stores already refused such records; a store that did not was believed. - The hook profile's qualification harness writes the runtime's stream only
through a filter that keeps the tool calls, their answers and the run's
status; the model's text and reasoning never reach the disk, where before
they sat in the work directory until the scrubbed copy was made. trail exportrefused a segment of a log named by its path but exported
one reached through a symbolic link as a whole trail file, so coverage and
supervisetook a log with pruned segments as whole. The file opened is
now judged by its real directory.- The spool's quarantine took a record carrying an event id it already held
as preserved, without comparing content, so a second record with that id,
or with no id, was acknowledged and lost. It now tells records apart by
their whole line.
Added
-
A test holds
scripts/ports.txtto the tree: every line whole, its
interface an exported interface of its package, its conformance cases
present with a negative control for every rule, its doctor key named, and
noneonly where the port's record says the component has nodoctor;
atestdata/ports/directory, or an interface whose comment calls it a
port, with no line fails it
(ADR-0066). -
The dependency rule refuses, in a non-test file under
adapters/,
importinginternal/policy/reasons(check-imports.shreads every file
whatever its build constraints; depguard too), and composing or holding a
v1Decisionby value (a type check; reflection is out of its reach),
each shown refusing a planted violation bycheck-imports-probe.sh. -
A test holds
docs/adr/README.mdto one row per record. -
guardana-control operators roster signsigns a roster of operator keys under
a trust root, each key with grants of one tenant, one project and a level,
for 24 hours unless told otherwise and at most 7 days, each signing at a
higher serial. Signers writing into one--outdirectory hold a lock on it,
so they never write one serial twice; it needsflock, and without it the
command refuses. Of two rosters of one serial written elsewhere, the roster
port's floor takes the first and refuses the second.
operators floor initstarts the floor a plane raises as it takes a
roster, for a roster id the roster port takes. The roster, operator
authentication and operator authorization are ports with these as their
defaults, and a veto may only deny. No plane reads a roster yet
(ADR-0071). -
observe importreads an egress proxy's access log. A source of kind
egress-access-lognames which top-level member of each JSON line holds
the time, host, port, method, status, byte counts and caller, and each line
becomes one connection observation; no other member is read, so a path,
query, header or body is never copied unless the mapping names it.
Fixtures cover Envoy'sjson_format, as a forward proxy and as a TLS
listener, and a Squidlogformat. The mapping'sdestination_formreads
the destination as a host alone or as a proxy request's authority,host,
host:port,[v6]or[v6]:port, whose port, when it names one, must
agree with a mapped port; any other destination is refused, and each
refused line is counted under its reason in the import report's
refused_by. Under an address basis the caller must
be an IP address, or it is dropped and counted incallers_dropped. Hosts
and addresses are written in one spelling, an IPv4-mapped IPv6 address as
its IPv4 address. Such a source may declareTRUST_INDEPENDENTon an unsigned
descriptor, and names no run
(ADR-0069). -
Source descriptor schema
0.2, which carries the egress mapping; a0.1
descriptor reads as before, with the same digest. Which members and which
trust a descriptor may carry is now decided by its kind
(ADR-0069). -
Observation record schema
0.3, for a connection, for an import report
with refusals by reason or callers dropped, and for a record naming its
source's kind, which every egress record does. A source's liveness and
doubt are read from reports of its kind alone, andobserve importrefuses,
with exit 2, a write of another kind into a log holding the source's
records. A connection's id is derived from its content, so
importing a grown or rotated log again writes only what is new
(ADR-0069). -
coveragereads an egress path as observed by the connections of the
sources it names, ends every egress line withno plane decides egress,
printsindependenttrust asindependent (unsigned descriptor), and adds
a row for each host an egress source reached that no path declares, at most
256 with a line counting the rest, then a row for each egress source whose
import refused a line, with the count by reason, then a row for each egress
source given that is not live, since its connections since its last import
are unseen. Any such row, or that line, makes the exit 1. An absent egress
descriptor whose log holds egress records is refused, with exit 2, and so is
a log whose egress records no egress descriptor given with that log names:
one replaced by another kind or renamed, or another source's log sharing
the directory (ADR-0069). -
pkg/contract.ValidateUnclassified: every rule ofValidatebut the
effect class's presence and what a class requires, for an envelope nothing
classified.Validatedoes not change (ADR-0067).
Changed
-
Breaking:
trail <file>refuses a file in a log's directory, named
directly or through a symbolic link, exit 1: name the directory instead. -
trailof a directory ends its counts line withsegments pruned <n>. -
Breaking: a supervise report names each
--sourceit could not rely
on insources_in_doubt: one not read by its state alone, one never heard
or silent by its id and heartbeat. A report that names one is schema0.2
whatever its procedure's schema, so the findings log andfindings export
now say what the exit status said. An earlier release refuses a log holding
such a line as damaged at that line, and misreads nothing.supervise
refuses more than 32 sources before reading any file. -
Both approval stores decide whether an approval may be spent in one order
(approvals). An expired
record marked multi-use in an approvals directory is left pending and
counted until the hold's own expiry, as an unexpired one already was; it
used to close the held callDENYAPPROVAL_EXPIRED. Either way nothing
runs. A record refused for its approver id blocks only the request it
names: a lookup lists it as unknown, beside every other record of its
action, and hides it once it expires; a retry tries its held approval once,
however many records name its request; a plane closing a lost hold
resolves only the approval it minted. The memory store refuses a hold that
is not a pending, unanswered approval with an approval id, a requested time
and an expiry. -
Breaking:
guardana-gateway collectwrites a directory of trail
segments bound to one tenant and project, and requires a credential:
collect --listen <addr> --dir <dir> --tenant <id> --project <id> --credential-file <file>, and--outis gone. A request without
Authorization: Bearerand that credential is answered 401 before its body
is read; a plane sends it throughexport.headers. An append of another
tenant's or project's event is refused 400, which the plane quarantines. A
segment is sealed at--segment-bytes; past--max-bytesevery append is
answered 503, so the plane's spool fills and its material calls are
blocked until an operator prunes. Nothing is pruned on its own.
guardana-gateway prune --through <n> <dir>prunes sealed segments while no
collector holds the directory,trail segments <dir>prints the manifest,
andtrail <dir>andtrail export <dir>read the segments. The directory
is refused with any group or other access. Until the collector listens on a
unix socket, an account that binds its loopback port while it is down
receives the plane's retries and the credential among them
(ADR-0072). -
Breaking: the evidence export is
1.1: its header'spruned_beforecounts the
segments of a log before the exported one that were pruned, 0 for a single
file, and coverage refuses an export as whole while it is above 0. A1.0
export reads as before; a header of a later minor without the member is
refused. A strict1.0reader, as coverage andexamples/evidence-report
of 0.14.0 are, refuses a1.1export on its new member: it fails loudly
and never misreads
(ADR-0072). -
trail exportrefuses a file inside a directory of segments, naming the
directory to export: exported on its own it would count none of the
segments pruned before it. -
examples/evidence-reportraisesevidence_gapnotedpruned, and exits
1, for an export that names no cursor and counts segments pruned before it;
an export after a state's cursor raises none for them. -
doctorno longer changes the evidence spool it checks. It reads the spool
throughspool.Inspect, which cuts nothing: a torn tail stays in place, and
the line says "a start would cut a torn tail of N byte(s), which this check
left in place"; a torn tail whose file its owner may not write fails the
check, as it fails a start. It asks access(2) whether the directory takes a
file instead of writing a probe, and no longer prints a reserved count.
Every command is marked as one that inspects or one that writes, and each
that inspects is tested to change nothing
(ADR-0016). -
Breaking: a run belongs to one project. A run record is
2.0and
carriesproject_id,runs openneeds--projectand refuses a parent of
another project, andruns listprintsproject_id. A plane resolves a
token only under its ownproject_id, so a token of another project's run
gets 401 even on a shared runs directory and listener identity. A run
tree refuses a member of another project,superviserefuses evidence of
another project than the run's record,reactnever stops a run for a
finding of another project, anddoctorlists a stop of another project's
run as unheld. A1.0record names no project and is refused: open each
live run again in a new, empty directory, under a new task, since a
reopened run starts with a clean flow state
(runs.md,
ADR-0068). -
The coverage inventory refuses an egress
host, or an egress path's
sourcename, that is not a host in the one spelling a connection's host
is written in, and names that spelling:https://x/and a non-ASCII letter
are refused, and so areExample.COM, writtenexample.com, and
::ffff:203.0.113.7, written203.0.113.7. It refuses
server_addresson an egress path's source. Neither could match before
(ADR-0069). -
superviserefuses, with exit 2, a--sourceof a kind whose
observations name no run, such as an egress access log, and one with no
kind (ADR-0069). -
observe importrefuses an input the group or others can write when the
descriptor declaresTRUST_INDEPENDENT
(ADR-0069). -
A log that holds a
0.3line cannot be read by 0.14: itsobserve import
andcoveragerefuse the log as damaged, and itsobserve exportcounts
each such line as anunsupported_versiongap
(ADR-0069). -
Under
OBSERVE, a call the adapter could not translate, for anything but
nothing classifying it, is now blocked with the kernel'sINDETERMINATE
and the refusal's code, on the trailENFORCEwrites, and counts as
blocked. An MCP answer carries that code in place ofMALFORMED_INPUT, and
an enforcement APIBLOCKno longer appendsINVALID_FIELD_VALUE
(ADR-0067). -
The gateway refuses, in every mode, an envelope whose action kind is not
tool,promptorresource, or whose protocol is not its adapter's, as
INVALID_FIELD_VALUE. The kinds, the protocol names and each protocol's
enforcement strength are one list ininternal/channel(ADR-0067).
It also refuses a resource pointer an adapter hands in that is not RFC 6901,
and at start an adapter whose name is not one of those protocols, or whose
declared enforcement is not its protocol's. -
A rewrite that moves the member a tool's
resource_fromreads is now a DENY
of the gateway's own,OBLIGATION_NOT_UNDERSTOOD, before anything is handed
out, for both adapters, in place of an aborted execution that ended
ACTION_FAILED(ADR-0067). -
coverageand the run view read a plane's strength from its protocol: a
trail that names no protocol, or one this build does not know, is decided,
not enforced, in every mode (ADR-0067). -
Breaking: an approver id that a reader could take for one the plane
authenticated is refused:approvals approveandrejectexit 1 and write
nothing,consoleexits 2, a scenario that names one is refused when it
loads, and
a record that holds one resumes nothing and its reconciliation closes as
APPROVAL_STATE_UNKNOWN. Refused are, in order, an id that begins with
authn:in any ASCII case once leading default-ignorable code points are
skipped; one holding a private-use, unassigned or noncharacter code point,
a default-ignorable one, or a blank symbol such as U+2800; one whose
letters mix scripts beyond UTS #39's highly restrictive level (one script,
or Latin with Han and Hiragana and Katakana, with Han and Bopomofo, or with
Han and Hangul); one that puts a combining mark on ASCII punctuation or a
symbol; one holding a fullwidth, mathematical, enclosed, letterlike, small,
vertical, superscript or subscript form; and one whose first six visible
characters read asauthn:with an ASCII letter among them, where any
character outside ASCII matches any position. SoВладимир,山田太郎さん
andzoëare accepted, whileаuthn:xwith a Cyrillic а,autℏn:x,
àuthn:xandauthn∶xare refused. The cost is that a name written in
fullwidth, mathematical, enclosed or halfwidth letters is refused, and so
is an id that begins likeauthné:. A lookalike written wholly in one
script other than Latin, with no ASCII letter among its first six
characters, is accepted, such as Lisuꓮꓴꓔꓧꓠ:ꓧꓠ. The plane never writes such an
id, and a machine comparing bytes never takes it for the prefix. Only the
plane will write that prefix, for an approver it authenticated
(ADR-0066). -
Breaking: an approval record written by 0.14 whose approver the rule above
now refuses, one that mixes scripts, holds a code point or a form now
refused, or reads asauthn:, resolves only asAPPROVAL_STATE_UNKNOWN
and is never spent. It refuses its own approval and no other: lookups,
holds and prunes of every other record go on, and it is pruned once it
expires. -
A gateway configuration that classifies a tool
WRITE,DELETEor
CONFIGUREis refused when it loads unlessenvironmentis set and, for an
MCP override, its upstream'senvironmenttoo; an MCP override classified
TRANSACTorIDENTITY_OR_ACCESSis refused unless its upstream's
tenant_idis set.mcp.Newandenforce.Newrefuse the same: the
contract needs these on such a call's envelope, and every call to the tool
was refused,OBSERVEincluded. -
The roadmap starts milestone 2 with the sensors independent of the agent
(B21), then the port contracts and the TypeScript client (B12), and lists
the open items of the 1.0 release bar as backlog tasks B29 to B34
(ADR-0064). -
A record now decides that each decision about identity, authentication,
authorization, approval, the policy language, evidence, observation,
detection, reaction, the signature algorithm and keys sits behind a port.
Each port has a tested default that an operator may replace, in this
repository or through one remote kind, and each fails closed. Nothing a
plane does changes with it yet
(ADR-0066). -
A pull request's gate no longer fuzzes:
make qualityruns the seeds of
every fuzz target in its tests and checks withmake fuzz-listthat each
target compiles, while the nightly run and the release commit's cold gate
fuzz each one for ten seconds.docs-checkleavesmake quality, whose
tests run the same packages
(ADR-0062). -
make testandmake test-racefail on a skipped test that
scripts/expected-skips.txtdoes not name, or one whose skip message does
not say what the list says; the sixteen tests that skip on Linux or macOS
are named there
(ADR-0063). -
make lintlints the generator programs underscripts/too, one at a
time, which no lint read before.
Fixed
policy testskipped a case named*.jsonthat is a pipe or a socket and
passed on the rest; it now refuses the directory, naming the entry.devended with status 0 after its console exited with a failure; it now
reports the console's failure.scan-binaries.shpassed over a file whose first bytes it could not read,
which could be a Go binary left unscanned; it now refuses it.fuzz-listandfuzz-smokemissed a fuzz target that a build constraint
leaves out when a comment stood betweenfuncand its name; every
declaration gofmt can leave is now read.- The qualification harness passed
blockwhen the runtime was stopped
before it answered the refused call; such a run is now unknown. - A collector whose live segment changed under it refused that append but
went on to seal it, writing a manifest its next start refused. The log now
takes nothing more until it is started again, and that holds too when the
first append after the change needs a seal or repeats events it holds. prunewith the clock before a segment's seal wrote a tombstone the
manifest reader refuses, leaving the directory unreadable. It is now refused
with nothing changed.- An export or a read of a log between a seal's manifest and its rename took
the sealed bytes as the next live segment; it now refuses them as moved and
starts again, and fails once the stopped seal outlasts its retries. - After
Pruneon an open log, an event whose line was pruned and sent again
was taken as written; the index now covers only the segments left. evidence-report -staterefused its own state after a request stayed open
across a segment, and raised again an alert of the next segment logged
before a crash. After a crash it could also take the next segment's gap
after its last newline for an earlier segment's at the same offset, and
not raise it; an alert with no cursor now names its file assource.- A deadline finding is
CONFIRMEDwhen one evidence export holds both events
it cites, whatever other exports were given; it wasSUSPECTEDwhenever
more than one export was, even the same export twice. Endpoints no one
export holds staySUSPECTED, since two planes' clocks differ. - The collector answers 503, not 400, to a body it could not read to its end,
as one cut by a reset connection or a read timeout, so the plane sends it
again instead of quarantining it. tools/listunderhideleft out a tool whose onlyALLOWrule reads
the resource id its classification points at, andannotatemarked it
VERDICT_DENY, though a call naming an allowed id ran. A tool previewed as
theDENYof no matching rule alone now stays listed, marked
DECIDED_PER_CALL, when anALLOWrule lacked nothing but that resource id
or the run's flow. AnALLOWunknown on anything else, a matchedDENY
rule,LOCKDOWNand a halt still leave it out. A tool whose preview is not
a verdict stays listed, as before: one that needs a resource id, whose
preview the contract refuses, and one an undetermined rule decides
(ADR-0070).OBSERVEhanded out a call whose refusal joined "nothing classifies it"
with another cause, such as arguments canon refused: such a refusal read as
unclassified alone. Unclassified is now that one cause, on a chain of single
wraps (ADR-0067).OBSERVEhanded out a call whose envelope the contract refuses when the
adapter reported no refusal; the kernel's refusal of the request now blocks
it whatever the adapter said (ADR-0067).OBSERVEhanded out a call the adapter said only nothing classifies, though
its envelope set an effect class, broke a rule of the contract, such as a
missing principal, a string past its bound or an unanchored delegation
chain, or carried a hash of other bytes: the kernel stops at a refusal
handed in and checked none of it. The gateway now holds every such claim to
ValidateUnclassifiedand the arguments hash and blocks the call with the
kernel's code for what is wrong (ADR-0067).- A pause entry scoped to an action kind let a call through whose kind was
spelled outside the plane's list, such asToolorfunction; such a kind
now matches every action scope, as an absent one does
(ADR-0067). - A trail whose events named two runs passed the chain check, so
guardana-gateway trailand the evidence report read it as one request
completed under its first run. Every event of a request now names the run
its first event names, or none for an action outside a run
(ADR-0065):trail
reports such a trail failed at the event that changes the run, and the
evidence report reports itunknown, in its follow mode too, whose state
moves to schema 1.2 to keep each ended request's run; a 1.1 state still
loads. Arun_idlonger than the contract's 1024-byte string bound breaks
the chain too, as the other identifiers do. guardana-control supervisechecked the rules of a request whose later
events named a run outside the ones it read, or none, as if the request
were whole. Such a request is now in doubt, and its rules are not checked.- The evidence report read an export filtered by run or by event kind as
whole, so a request's event the filter dropped went unseen. Such an export
now exits 1 as cut, with the reason, as follow mode already refused it; an
export filtered by request alone still reads. - A hold journal write that failed after its entry was linked could leave the
entry behind a trail the plane closed without it; the next start closed the
trail again, forking its chain. The entry now stays, and such a hold is
closed only once its entry has left held; when it cannot leave, the trail
stays open for the next start to close from the entry. - The hook profile's qualification passed four cases on evidence it did not
have: two approved retries with different inputs, a project it could not
list, and hooks disabled or MCP tools left out with no positive control in
the run. Each now needs its evidence or is unknown, a results file counts
only where its own controls passed, and the harness has 74 tests run by
make check-python. Judged again, the 2.1.310 run passes 19 of 20 cases:
disabledhad no control invocation. - Statements on seventeen public pages that the code contradicted are
corrected: among them an identical retry after a spent approval, which is
refused until the approval expires, how many times expiry is judged, three
answers of the enforcement API, the stateless listener's older revisions,
supervision's bounds and ordering, and the evidence report's exit 2. scripts/publish-python-dist.shcompared the release files' digests with
the build's before uploading, but a failingsha256sumgave an empty digest
on both sides; each digest is now checked to be 64 hex digits.- The uv version check every Python gate script calls took
uv 0.12.9-beta.1
for a pin of 0.12.9; it now compares uv's whole version word. - A workflow step that wrote
SHELLOPTS=noexecorBASH_ENVto
$GITHUB_ENVcould make a later step's bash, the quality gate's among them,
run nothing and succeed. Every workflow now starts its steps in a bash that
ignores the shell's startup files, options and functions from the
environment; a script a step starts still reads them, andmake quality
refuses them. make quality MAKEOVERRIDES= SHELL=/usr/bin/trueran no check and exited 0:
the guard read the command line fromMAKEOVERRIDES, which the command line
can empty. It now refuses every command-line variable butGOby its origin.
Image: ghcr.io/guardana/control-gateway@sha256:ec566a84b63268d76e388a35552a6c98835f2fd9c890c18f6d0b61d8cbb8980d