Repository navigation
feat(reach): declare what an embedded agent can reach with @agents, @reaches, @effects and @gates - #53
Merged
Conversation
…reaches, @effects and @gates An application that embeds an LLM agent hands it capabilities through its harness: tool definitions, MCP servers, file, shell and database access, internal methods. None of that could be declared, so a reviewer or scanner could not see an agent's blast radius, and `guardlink diff` did not move when a `run_sql` tool was registered. Four relation verbs, each written where its fact lives (SPEC 3.2.1): - `@agents <agent actor> to <capability> [on <asset>] [as <identity>]` on the tool registration. `@reaches` has the same shape for a principal that is not an LLM agent, such as a CI runner. Writing `@agents` about an actor is what marks it as an agent, and naming one actor under both verbs is a validation error (`agent-reach-conflict`), because every agent-only check would otherwise answer two ways. - `@effects <read|write|delete|execute|spend|notify> on <asset> [as <identity>]` on the code that acts. The set is closed, like `@handles`; egress stays on `@flows ... -> External.X`. - `@gates <asset> by <approver actor> [for <capability>]` on an approval step. `by` is required and there is no advisory/blocking qualifier. The capability is the token `@entitles` uses, so "can minus may" is a join: `findUnentitledReaches` (src/parser/reach.ts) returns every reach that no cited entitlement for the same actor and capability covers, and, when the reach names an asset, for the same asset. An uncited entitlement or one that names no asset never covers a reach that names one; each such near miss is reported with its reason. lookup and diff both answer from it. - Parser and model: `reaches[]` (both verbs, with `agent: true|false`), `effects[]`, `gates[]`; claim keys, annotation-hash records, workspace merge, feature filter, graph subgraph kinds, file context and the dashboard's annotation list. A model without the verbs hashes and exports exactly as before. - validate (CLI, MCP, TUI): `undeclared-actor` now covers the actor on `@agents`/`@reaches` and the approver on `@gates`; undefined assets and identities are `dangling-ref`; `agent-reach-conflict` is new. - lookup: `reaches`, `agents`, `unentitled reaches`, `effects`, `gates`, each with an optional `for <actor|asset>`; `actors` rows gain `agent` and `reaches`. `guardlink lookup <query...>` answers the same forms from the CLI as JSON, with `--fail-on-found` for CI. - diff: reach, effect and gate changes, and a New Unentitled Reaches section for a reach that is newly uncovered, whether added or because its entitlement was withdrawn. - Agent writes: all four verbs are accepted by `guardlink_annotate_apply` and the annotate gate; `@entitles` and `@accepts` stay refused. The gate warns on a `@gates` that does not say what the approval step is. - SPEC 2.12, 3.1, 3.2.1, 5.1, 6.1 and 7.1, the reference doc, the annotate prompt and the agent instruction templates describe the verbs. None of the verbs changes the SARIF export.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
An application that embeds an LLM agent hands it capabilities through its harness: tool definitions, MCP servers, file, shell and database access, internal methods. Until now none of that could be declared. A reviewer or scanner could not see an agent's blast radius, and
guardlink diffdid not move when someone registered arun_sqltool.This PR adds four relation verbs. Each one is written where its fact lives in the code (new SPEC §3.2.1):
The capability is the same token
@entitlesuses, so "can minus may" is a join.guardlink_lookup("unentitled reaches")returns the capabilities the code hands out that no human approved, which for an agent is the OWASP LLM06 Excessive Agency list.Two rules that follow from splitting agents from other principals
Please review these two specifically:
@agentsabout an actor is what marks it as an agent. There is no definition-level flag. An@actorbecomes an agent when some@agentsnames it.lookup actorsnow reportsagent: true|falsefor each actor.@agentsand@reachesis a validation error,agent-reach-conflict. Any check that applies only to agents would otherwise answer two ways for that actor. Actor spellings are resolved first, so#support-agentandSupport_Agentcount as the same actor. The error is reported on each@reachesline and names the@agentsline it conflicts with.The can-minus-may join (
src/parser/reach.ts)An entitlement covers a reach when all of these hold:
An entitlement that names no asset does not cover a reach that names one. Every rule errs toward reporting the reach. When an entitlement for the same actor and capability exists but does not count, the row carries it in
near_misseswith the reason:uncited,no-assetorother-asset.lookupanddiffboth use this one function.Whether an
@entitlesin source has an accepted proposal is not checked here. That needs the proposal ledger on disk, andvalidatealready reports such an entitlement as an error.Changes
reaches[](both verbs, withagent: boolean),effects[]andgates[]. The new verbs are also wired into claim keys, annotation-hash records, workspace merge, the feature filter,guardlink_graphkinds,guardlink_contextand the dashboard's annotation list. A model that uses none of the verbs hashes and exports exactly as before, so no hash version bump is needed.tocounts as a keyword for@agents/@reaches. For@effects, an effect word is a keyword only as the first word of the arguments.@gateshas no keyword, for the same reasonbyis not one for@accepts.undeclared-actornow also covers the actor on@agents/@reachesand the approver on@gates.#idon any of the verbs is adangling-ref.agent-reach-conflictis new.reaches,agents,unentitled reaches,effectsandgates, each with an optionalfor <actor|asset>.effects forandgates forresolve the asset the wayasset Xdoes.actorsrows gainagentandreaches.guardlink lookup <query...>answers the same forms as JSON (there was no CLI path tolookupbefore).--fail-on-foundmakes it usable as a CI check.guardlink_annotate_applyand the annotate gate accept them, and still refuse@entitlesand@accepts. The gate warns (description-vague) on a@gatesthat does not say what the approval step is.docs/GUARDLINK_REFERENCE.md, the annotate prompt and the agent instruction templates now describe the verbs. The second commit regenerates the synced instruction files and graph artifacts.Verification
npm run build && npm test: 118 files, 2170 passed, 1 skipped.npm run lintis clean.node dist/cli/index.js validate . --artifactspasses: artifacts are current and drawable, with 0 errors and the same 16 unmitigated exposures asmain.tests/agent-reach.test.ts(30 tests) covers:undeclared-actoranddangling-refon each verb;run-sql,fetch-url(uncited entitlement),search-kb(entitlement on another asset),mcp-filesand the CI runner's publish come back unentitled;tests/relation-coverage.test.tsnow covers the three new relation arrays.tests/diagnostic-codes.test.tscoversagent-reach-conflict.Follow-ups (not in this PR)
pentestprofile.@agentsfrom tool registrations andmcp.json, then verify them.@agentsto each@effects, and whether a@gatessits on it.unentitled reaches.