v0.17.0
Forge v0.17.0 — AARM (Autonomous Action Runtime Management) compliance: intent alignment, step-up & defer authorization, JIT credentials, and tamper-evident audit signing
Forge v0.17.0 is the largest security release yet for the open-source AI agent runtime. It implements AARM — Autonomous Action Runtime Management, an open-source security specification for governing what AI agents are allowed to do at runtime. This release lands the AARM control set end to end: per-call intent-alignment scoring (R3, #245), longitudinal intent-drift detection (R7, #246), tool-wide guardrail enforcement (R4a, #221), RFC 9470 step-up authorization (R4b, #247), pause-and-resume defer authorization (R4c, #248), a tamper-evident audit hash chain with Ed25519 signing (R5/R6, #220, #237), and just-in-time credential dispensing per tool call (R9, #236). Together they let a platform prove what an agent did, why it was allowed, and that the record wasn't altered.
This release also ships reversible context compression (ctxzip) that cuts 60–95% of tokens on bulky tool outputs losslessly (#241, #279), an opt-in headless-browser tool family driven through the egress proxy (#260), a pluggable session store with a remote backend for stateless Kubernetes replicas (#244), guardrails that now apply to every tool rather than just cli_execute (#221), a platform guardrails policy overlay (#285), and a wave of Skill Builder improvements — structured JSON output (#276), built-in-tool awareness (#297), and skill-first activation routing (#298).
Highlights
- Intent alignment — every tool call scored against the user's stated goal (#245, governance R3). Each action is embedded (
tool_description + args_json) and compared by cosine similarity to the task's stated intent. Fail-closed: when enabled and the embedder is unavailable, calls DENY rather than silently bypass. Off by default (security.intent_alignment.enabled), with a recommended warn-only first rollout. Emits anintent_alignmentaudit event per call. See Intent Alignment. - Intent drift detection — catches the "boiling frog" (#246, governance R7). A rolling window watches the trend of intent-alignment scores across a task's lifetime and trips on either a mean-below-threshold or a monotone-decrease pattern.
intent_driftfires only on state changes (enter / recover), so long drift stretches never flood the audit stream. - Step-up authorization — RFC 9470 challenges (#247, governance R4b). A high-assurance tool call whose caller lacks a sufficient
acrclaim returns HTTP 401 with aWWW-Authenticate: Bearer error="step_up_required"challenge instead of running. Optionalacr_hierarchylets a stronger assurance satisfy a weaker requirement. Emitsauth_step_up_required. See Step-up Authorization. - Defer authorization — pause, approve out-of-band, resume (#248, governance R4c). A high-risk tool call pauses the executor; an external approver POSTs a decision and the executor resumes exactly where it left off. The A2A task status flips to
deferredwhile blocked. Distinct from DENY (refuses) and STEP_UP (re-auths). See Deferred Authorization. - JIT credential dispensing — short-lived secrets per tool call (#236, governance R9). A new
forge-core/credentials/package materializes credentials at the moment of a tool call and revokes them after, so long-lived secrets never sit in the agent's environment. Shipsstaticandsts_assume_role(AWS STS AssumeRole via SDK-free hand-rolled SigV4) providers. Emitscredential_issued/credential_revoked/credential_failed— never the credential material itself. See Least-privilege credentials. - Tamper-evident audit logging — Ed25519 signatures + hash chain + JWKS (#220, #237). Every audit event now carries a
prev_hashlink, and — when signing is enabled — akid+sig. The signature coversprev_hash, so altering the chain breaks both the chain walk and the signature check. A JWKS endpoint publishes the verification key. See Audit signing. - Reversible context compression (ctxzip) — 60–95% fewer tokens, losslessly (#241, #279). Bulky tool outputs (JSON arrays, logs, grep,
kubectl get -o json) are compressed before reaching the LLM behind an inline<<ctxzip:HASH …>>marker, and recovered on demand via a newcontext_expandtool. Lossy on the wire, lossless end-to-end. v0.3.0 adds whole-recorditems[]crushing, categorical drop summaries, and a YAML/describecrusher, plus akeep_patternslearning loop. Off by default (compression.enabled). See Context Compression. - Opt-in headless-browser tool family (#260, issue #94). Agents can drive a real headless Chromium (via
chromedp) to navigate, read, click, fill, and screenshot — registered only when an active skill declaresrequires.capabilities: [browser]. The LLM acts on a compact indexed page digest (~1–3 KB), not raw HTML. All traffic is forced through the existingEgressProxy(allowlist / SSRF / DNS-rebinding protection);browser_fillrefuses password and payment fields unless the skill opts in. See Browser Tools. - Guardrails now apply to every tool, not just
cli_execute(#221, governance R4a).deny_commands/deny_outputpatterns previously no-op'd forweb_search,http_request, MCP, and custom tools. That short-circuit is removed. Potentially breaking — see Upgrade notes. - Platform guardrails overlay (#285). A platform
policy.yamlcan now layer additional, most-restrictive-wins guardrails on top of an agent'sguardrails.json, and the unused MongoDB "DB mode" guardrails path is removed. See Platform Policy. - Pluggable session store with a remote backend (#244).
memory.session_store: remotepushes per-task session snapshots to a platform service over HTTP with conditional GET (If-None-Match) andIf-MatchCAS commits, so stateless Kubernetes replicas resume any task on any pod without a PVC. Defaults to the localfilebackend. See Memory System. - Audit event correlation + quieter export heartbeat (#300, #301). Auth events are now attributed to their invocation via an ingress-minted correlation id (#278), and the
audit_export_statusheartbeat moved to a hybrid cadence — it emits immediately on a sink health change and otherwise a slow keepalive, cutting ~1,440 rows/agent/day to a handful (#280).
What's new
AARM — Autonomous Action Runtime Management
AARM (Autonomous Action Runtime Management) is an open-source security specification for governing what autonomous AI agents are allowed to do at runtime. v0.17.0 implements its control set, keyed by requirement ID (R3–R9); see the security architecture overview for how these layers fit together. At the center is the R4 policy-decision taxonomy — Allow < Modify < StepUp < Defer < Deny, ordered by restrictiveness — with the R3/R7 intent surface and R9 credentials around it. Every AARM control is off by default and fail-closed when enabled.
| AARM control | Requirement | PR |
|---|---|---|
| Intent alignment (per-call semantic gate) | R3 | #245 |
| Guardrail MODIFY/DENY across all tools | R4a | #221 |
| Step-up authorization (RFC 9470) | R4b | #247 |
| Defer authorization (pause / approve / resume) | R4c | #248 |
| Audit hash chain | R5 | #237 |
| Ed25519 audit signing + JWKS | R6 | #220, #237 |
| Intent drift detection (longitudinal) | R7 | #246 |
| JIT credential dispensing | R9 | #236 |
Intent alignment (#245) & drift (#246)
security:
intent_alignment:
enabled: true # off by default; recommend warn-only first
provider: openai # openai | gemini | ollama
model: text-embedding-3-small
threshold: 0.5 # soft: below → warn
hard_threshold: 0.3 # hard: below → deny
cache_size: 1024 # LRU size for action-side embeddingsIntent is anchored on the tool description (not its name — MCP names like fn_42 carry no meaning) plus the arguments JSON. Both sides are embedded; the action side hits an LRU keyed on sha256(action_text) so repeated identical calls skip the round-trip. R7 drift layers longitudinal telemetry on top: it trips on a rolling-window mean below the drift threshold or a monotone decrease of the last N scores.
Step-up (#247) & defer (#248) authorization
security:
step_up: # R4b — require stronger auth for risky tools
enabled: true
tools:
cli_execute: acr:mfa # per-tool required acr value
acr_hierarchy: [acr:password, acr:mfa, acr:hardware] # optional: stronger satisfies weaker
defer: # R4c — pause for out-of-band approval
enabled: true
tools:
cli_execute:
to: channel:slack:#oncall
timeout: 10m
context_template: "Agent wants to run {tool} with args: {args}"Step-up returns an RFC 9470 challenge on insufficient assurance; defer blocks the executor goroutine on a decision channel and flips the A2A task to deferred until an approver POSTs a decision (or the timeout auto-denies). Both hooks run after guardrails so a guardrail-denied call never produces an unnecessary challenge or deferral.
JIT credentials (#236)
credentials:
- tool: cli_execute
binary: aws
provider: sts_assume_role
spec:
role_arn: arn:aws:iam::123456789012:role/forge-skill-read
external_id: skill-alpha
session_name: forge-agent-jit
duration: 15mCredentials are materialized on every Execute, merged into the subprocess environment, and revoked on completion. The sts_assume_role provider uses Forge's SDK-free SigV4 signer (no aws-sdk-go-v2), enforces AWS 15m–12h duration bounds, and supports external_id, session_name, and an inline session_policy.
Tamper-evident audit logging (#220, #237)
Every event carries prev_hash unconditionally; enabling FORGE_AUDIT_SIGNING_KEY_B64 adds kid + sig (Ed25519 over the JCS-canonicalized event, covering prev_hash). Because the signature commits to the previous hash, editing or reordering any line breaks both the chain walk and the per-event signature. A JWKS endpoint publishes the public key for offline verification. See Audit signing and the audit env-var reference (#254).
Reversible context compression (#241, #279)
compression:
enabled: truectxzip compresses bulky tool outputs once at production time (an AfterToolExec seam), so the conversation prefix stays byte-stable and provider prompt caches keep hitting. Dropped content is stored in a durable local bbolt store behind an inline <<ctxzip:HASH …>> marker and retrieved via the context_expand tool. v0.3.0 (#279) routes list-API shapes like kubectl get -o json through whole-record items[] crushing — anomalous records survive intact while the rest offload as a retrievable array — and adds categorical drop summaries ([offloaded: 139 running, 24 batch completed]) plus a YAML/describe crusher. A keep_patterns learning loop mines context_expand retrievals so frequently-needed fields stop being offloaded.
Opt-in headless-browser tools (#260)
The browser_navigate / browser_state / browser_click / browser_fill / browser_extract / browser_screenshot family registers only when a skill declares requires.capabilities: [browser] and a Chromium binary is present. Every request routes through the EgressProxy (same allowlist / SSRF / DNS-rebinding protection as in-process tools); the analyzer scores the capability high-risk and flags a browser + trust_hints.network: false contradiction as a Critical violation. forge build installs Chromium into the image only for browser agents.
Skill Builder & skills runtime
- Structured
{message, skill}JSON output (#276, #252 part 2) — the Skill Builder now returns a machine-parseable envelope so the dashboard reliably separates chat from the drafted skill, with a hardened parser that rejects wrapper objects and incidental JSON. - Built-in-tool awareness (#297, closes #270) — the builder knows Forge's built-in tools (
read_file,write_file,edit_file,glob,grep,file_create, scheduling, …), authors rather than role-plays, and gates scheduling behind an explicit ask. - Skill-first activation routing (#298, closes #271) — the runtime checks whether a request matches an attached skill before answering from model defaults.
- Interview convergence + custom-binary install recipes (#258, #252 part 1), robust draft extraction so the preview always populates (#253), a persistently-visible Edit button (#262, closes #261), and skill-catalog names aligned with
read_skillresolution (#250). - Skill-relative file reads + multi-language script execution (#257, closes #251) and
env_required/env_optionalsurfaced inbuild-manifest.json(#259).forge buildno longer purgescurlwhen a skill needs it at runtime (#242).
Platform & operations
- Guardrails overlay in
policy.yaml(#285, #288) — most-restrictive-wins platform layering over an agent'sguardrails.json; the unused MongoDB DB-mode path is removed. - Remote session store for stateless replicas (#244) — resume any task on any Kubernetes pod without a PVC; 412 CAS conflicts yield to the newer state instead of clobbering it.
- Rolling
vnextprerelease on every merge to main (#249) — a continuously-built prerelease of the runtime for early adopters. - Dependency hardening —
golang.org/x/cryptobumped to 0.52.0 acrossforge-coreandforge-cli(#274, #275).
Upgrade notes
- Guardrail
MODIFY/DENYnow apply to every tool (#221 / governance R4a) — potentially breaking. TheSkillGuardrailEngineno longer short-circuits on non-cli_executetools, sodeny_commands/deny_outputpatterns that previously no-op'd forweb_search,http_request, MCP calls, and custom tools now fire. Review your guardrail patterns before upgrading.- Match-target asymmetry: for
cli_executethe match target is still the reconstructed shell command line (binary arg1 arg2 …), so existing shell-style patterns keep working. For every other tool the match target is the raw tool-input JSON as the LLM produced it — write patterns accordingly. - API change:
GuardrailChecker.CheckInbound/CheckOutboundnow return(PolicyResult, error). The newPolicyDecisionenum is ordered by restrictiveness (Allow < Modify < StepUp < Defer < Deny). Callers today read onlyAllow/Modify/Deny;StepUp/Deferare emitted by the R4b (#210) / R4c (#211) hooks. Theguardrail_checkaudit wire shape is unchanged. - Migration guidance: docs/security/policy-decisions.md.
- Match-target asymmetry: for
- Guardrails MongoDB "DB mode" removed (#285). If you set
FORGE_GUARDRAILS_DB, migrate toguardrails.jsonplus the platformpolicy.yamloverlay. See Platform Policy. - All governance features are off by default. Intent alignment, drift, step-up, defer, and JIT credentials ship disabled and fail-closed when enabled — turn them on deliberately, and roll intent alignment out in warn-only mode first.
Full changelog
Full changelog: v0.16.0...v0.17.0