Skip to content

Agent Guard 1.0.0

Choose a tag to compare

@VODuda VODuda released this 12 Nov 23:22
· 31 commits to main since this release
4f4cb5d

Agent Guard 1.0.0

The first stable release of @shieldaai/agent-guard, -mcp, -scenarios and
-executor. @shieldaai/agent-map is published at the same version but stays
experimental. The last published version was 0.1.0-alpha.3; everything since then is
in this release. The full list is in the changelog, grouped by
package.

What 1.0 promises

Package Promise
@shieldaai/agent-guard, -mcp, -scenarios Root exports follow semantic versioning.
@shieldaai/agent-guard-executor Root, /sandbox, /pilot and /ops follow semantic versioning. Every 1.x executor opens ledgers written by earlier 1.x versions and by 0.1.0-alpha.3; downgrade is not supported. /inventory is experimental; /internal has no promise.
@shieldaai/agent-map Experimental. Its API and graph model may change in a minor release.

Agent Guard limits what a misled agent can do through the calls routed to it. It does not
detect prompt injection and it is not a sandbox. Risks and how Agent Guard handles
them
and Support and limits say what is and is
not covered.

What is in 1.0

If you did not use the alphas, this is what the release contains:

  • The guard (@shieldaai/agent-guard): tools you register and approve, tasks with
    grants on exact resources, tenants and recipients, approvals tied to one exact action,
    budgets, delegation to sub-tasks, and revocation. In memory, in one process, with no
    third-party runtime dependencies.
  • MCP adapter: the tools, resources and prompts of a Model Context Protocol server
    behind the same checks, with a digest for noticing a changed tool definition.
  • Durable executor: the same checks with an encrypted SQLite ledger, a token per task
    over HTTP, governed memory and an optional Docker profile for untrusted code.
  • Scenarios: thirteen hostile and ordinary scenarios (twenty-six cases) to run against
    your own integration.
  • Experimental: Agent Map, an inventory of declared agents and what they can reach.

New since the alphas

  • Outside-content labels (readsUntrusted, untrustedContext, sendsExternal) in core,
    executor, MCP and governed memory, off by default.
  • executeDetailed(), retireTask(), unapproveTool(), unregisterTool(), onDecision,
    limits, monotonicClock, GuardErrorCatalog and isGuardError() in core.
  • A restart-stable descriptorDigest for pinning reviewed MCP tool definitions.
  • A trusted read-back of prepared actions for review screens
    (executor.preparedAction(), POST /v1/admin/action).
  • An explicit executor ledger format version with migrations.
  • Agent Map boundary flags, outside-guard reach, discovery leads and host manifest v3.
  • Scenario catalog 1.3.0 (thirteen scenarios, twenty-six cases).

Known limits

  • The inventory observer is experimental; one logical CPU with 250 ms polling is not
    supported.
  • Agent Map full-graph reads are estimated to work up to roughly 150 agents with default
    limits and roughly 800 at the maximum. Limits above the defaults have not been run against
    a Neo4j database, and the 5-second read deadline does not grow with them: test your
    largest reads against your own database first.
  • Agent Ops needs a per-client limit at the load balancer or firewall when clients share an
    address or an attacker controls many addresses.
  • The core keeps state in memory in one process; the executor is single-owner. There are
    no distributed quotas, no distributed revocation and no high availability.

Verification

All 19 checks passed on the release candidate 424810a
(pull request 56): npm run check on
Node 22, 24 and 26 on Linux and on macOS and Windows; the browser checks in Chromium,
Firefox and WebKit; the Neo4j integration tests; the Docker isolation and production-image
checks; CodeQL with no open alerts. Main CI run:
43310105088.

Upgrading from 0.1.0-alpha.3

Core and executor

  • Root aliases are gone. Import the internal building blocks from
    @shieldaai/agent-guard/internal, and the executor's sandbox, inventory, pilot and
    operator names from /sandbox, /inventory, /pilot and /ops.
  • Handler errors (executor). Whatever a host tool handler throws, execute() now fails
    with TOOL_FAILED, or EXECUTION_ABORTED if the call was canceled, revoked or past its
    deadline. In the alphas a GuardError or ExecutorError thrown by a handler reached the
    caller with its own code. That code is now in error.detail; match on detail instead of
    code. The built-in memory, model, HTTP, private-file and sandbox tools keep their codes.
  • Callbacks inside built-in tools. An error thrown by a host callback inside a built-in
    tool (model complete, outbound credential, sandbox onResult, artifact broker clock)
    is always TOOL_FAILED with the error's code as detail.
  • Aborted handlers. A handler that is aborted before it performs its effect must throw
    (for example signal.throwIfAborted()). A handler that returns normally is taken to have
    performed the effect and is reported as EXECUTION_ABORTED with detail EFFECT_COMMITTED.
  • Executor task server statuses. Denials use 401, 403, 404, 409, 429 and 503 instead
    of 400, and the body adds retryable. Upgrade ExecutorClient and the server together.
    requestsPerMinute counts only task requests that authenticated.
  • Failed revocation. revokeTask() whose ledger write fails throws OPERATION_FAILED
    with detail: 'REVOCATION_NOT_DURABLE' (the store error is the cause), and
    POST /v1/admin/revoke answers 503 with "retryable": true and that detail. The task
    is denied in the running process and the revocation is recorded by the next ledger write
    that succeeds. Treat it as "not revoked yet" and repeat the call: if the process exits
    before any ledger write succeeds, the revocation is lost.
  • Tool registration at startup. Registering an unchanged tool after a start no longer
    writes a TOOL_REGISTERED event or raises the tool's revision. Approvals are still
    withdrawn at every start.
  • ADMITTED status. execution() no longer returns ADMITTED; an admitted action is
    RUNNING at once. Remove code that waits for ADMITTED. The value stays in
    ExecutionStatus because alpha ledgers can contain it; they still open and recover. The
    ACTION_ADMITTED audit event is unchanged.
  • Receipt retention. Settled receipts are now kept per task family (maxReceiptsPerFamily,
    default 64) and per tenant (256); the alphas kept 256 for the whole ledger. An evicted
    receipt reads as status: 'PRUNED' with its outcome and charged. Once its stub is
    evicted too (256 per family, 1,024 per tenant), execution() returns status: 'PRUNED'
    with no outcome, and later fails with INVALID_PREPARED. Do not assume outcome is
    present. audit() keeps 256 events per tenant by default (maxRecentEventsPerTenant,
    up to 1,000) instead of one shared ring of 1,000.
  • Ledger soft limit. Calls that add state (createTask, delegateTask, prepare,
    approve, execute, registering a new or changed tool, memory writes) fail with
    STORE_CAPACITY_EXCEEDED and detail: 'SOFT_LIMIT' once the ledger would pass
    maxLedgerBytes minus a reserve of one eighth, at most 1 MiB: 15 MiB with the default
    16 MiB. Revoke, cancel, settlement and reconcile use the reserve; if that is full they
    drop the oldest half of the audit events. Archive or raise maxLedgerBytes earlier than
    you did with the alphas. A ledger past its soft limit still starts and can register its
    existing tools, revoke, reconcile and archive.
  • Path rule. Paths in artifact receipts and build-artifact policy must be at most 240
    UTF-8 bytes in Unicode NFC (they could be 1,024 characters). Sandbox and private-file
    paths are measured in bytes, not characters, so a non-ASCII path under 240 characters can
    now be refused. A receipt signed by an alpha with a longer path fails ARTIFACT_INVALID
    and a policy entry fails BUILD_PROVENANCE_INVALID: shorten the path and issue them again.
  • Ledger format. The executor writes formatVersion 1 when it opens an alpha ledger.
    An alpha build still opens that ledger only while it stays at schema 1; retiring or
    archiving a task, key custody and the recovery mirror raise the schema, and the alphas
    refuse it then. Downgrade is not supported.
  • Delegation errors. DELEGATION_DENIED now has a detail naming the exceeded limit.

MCP adapter

  • Error names. A non-object argument value fails with ARGUMENTS_INVALID;
    INVALID_ARGUMENTS no longer exists.
  • failureReason. A malformed name advertised by the server is reported as
    REGISTRY_REJECTED (or SCHEMA_INVALID in skipped[].reason), not
    INVALID_CONFIGURATION. After the transport closes unexpectedly, failureReason is
    CONNECTION_UNAVAILABLE, not undefined.
  • VerifiedMcpHttpSession.connect() rejects bad options with INVALID_CONFIGURATION or
    REGISTRY_LIMIT before any network call; handle these separately from
    AUTHORIZATION_DENIED.
  • Reviewed digests. descriptorDigest does not cover the server. If you store reviewed
    digests keyed by remote name only and connect to more than one server, key them by server
    ID and remote name.

Agent Map

  • Neo4j 5.23 or later is required.
  • Sort order. Listings sort by name in Unicode code-point order, field by field. Do not
    keep page offsets across the upgrade.
  • Policy evidence that was not evaluated. Read policyCoverage before policyEvidence:
    an empty list with status unknown was not evaluated. get_agent_inventory reports
    counts.policies and the policies section total as null in that case.
  • Degraded answers instead of failures. Check data.graphStatus before using graph
    from get_agent_access (too_large means it was left out), and truncatedReason on
    find_agents_with_access results.
  • Owners. agentCount and resourceCount are no longer on the owners in
    get_agent_inventory; take them from list_owners.
  • Errors. Invalid store options throw MAP_CONFIG_INVALID (was MAP_REQUEST_INVALID).
    An unmet Expect header answers 417, not 403. MAP_TIMEOUT answers 504, not 408, when
    the request had arrived in full and the read ran out of time; read the error code, not
    the status, to recognise a timeout.
  • Viewer files. viewerDirectory must hold four files: index.html, app.js,
    demo.js and app.css. Rebuild the viewer; a directory without demo.js is refused at
    startup with MAP_CONFIG_INVALID.
  • Custom stores. A custom MapStore receives owners and policy in the read request;
    it may ignore them but must not reject unknown keys.

Collector

  • Commands. Recovery and the difference report are installed commands:
    agent-guard-recovery (was python -m agent_guard_cartography.pilot_recovery) and
    agent-guard-map-report (was python -m agent_guard_cartography.reference_report). The
    python -m forms still work. agent-guard-source-lifecycle no longer creates a missing
    --archive-dir; pass --create.
  • Lock. Every command that names an archive locks <archive directory>.lock beside it,
    so commands on one archive exclude each other from any working directory. Stop every
    collector, lifecycle and recovery command before installing: an old command and a new one
    do not share a lock. The collector's user must be able to create that file in the
    archive's parent directory, or create it once with mode 0600; otherwise commands fail
    with PROCESS_LOCK_PATH_INVALID. A --lock-file you already pass is held as well.
  • Errors. All four commands report a failure as one JSON line on standard error,
    {"status":"failed","code":"…"}. Lifecycle and recovery printed {"error":"…"}; the
    collector and the report printed a bare code. Neo4j and path failures have specific codes
    and exit 2; agent-guard-map-report exits 2 for a specific code (it always exited 1).
  • Refused captures. A newly collected snapshot refused as older than the published view
    (GENERATED_AT_ROLLBACK, REVISION_ROLLBACK) is reported and not kept; its failure line
    has no receipt_id.
  • Snapshots. ledgerRevision and policyEpoch must be true integers; 1.0 is rejected
    with INVENTORY_SCHEMA_INVALID.

Upgrading Agent Ops from an unpublished source build

Agent Ops was never in a published version; this applies to builds from earlier source.

  • A client that drives sign-in itself must send the cookie set by the POST /auth/login
    response with the callback; the earlier cookie is refused with 401. POST /auth/login
    from a signed-in session answers 409 ALREADY_SIGNED_IN.
  • GET /api/maintenance: each archiveCandidates entry is
    { taskId, requestId, ticketId, state, requesterId, createdAt }; label is removed.
    POST /api/maintenance/archive/preview: preview gains requestId and loses
    retainedCount.
  • Request rows add operationId, requesterKind, and, when present, denialReason and
    cause; a request can have the state withdrawn. GET /api/session adds
    policy.forbidSelfApproval when signed in.
  • An unknown key under requests in the configuration fails with REQUEST_POLICY_INVALID
    (was INVALID_INPUT). New keys: requests.shareDenialReason, signInFlowsPerClient,
    sessionTtlMs and rateLimitPerMinute.
  • Approve and deny can answer 409 REVIEW_STALE. An agent proposal for a ticket outside
    its allowlist answers 403 whether or not the ticket exists.
  • An approval whose outcome is not known ends unconfirmed, never failed. An operator
    resolves it with "Check whether it was applied" or the close-out. Close-out evidence
    shows the ticket as reviewed beside the ticket as it is now; a changed ticket no longer
    answers CLOSE_OUT_UNPROVEN.
  • LOGIN_CAPACITY_REACHED is gone: when the sign-in table is full, the oldest pending
    sign-in gives way. A client that is not signed in can get 503 BUSY while four other
    such requests from its address are in progress. Put a per-client limit in front of the
    service when clients share an address.
  • Automatic archiving records system:retention as the operator (was retention), and
    enrolled operator or agent ids may not start with system:.