diff --git a/.gitignore b/.gitignore index bafbf59a..300fda45 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ TODO .claude/ .worktrees/ .superpowers/ +# Superpowers design specs and plans: working notes, kept out of the repo. +docs/superpowers/ # mage desktop:* build outputs (rootfs.img, images-minimal.tar.zst, ap.app) # and downloaded upstream artifacts (Ubuntu cloud image, k3s release) — see diff --git a/AGENTS.md b/AGENTS.md index 430b838c..6987e7ad 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -348,9 +348,10 @@ mage manifests # always after gen:api, OR after any config/ edit ## Documentation regeneration -`mage docs:cli` and `mage docs:crd` regenerate the showcase docs site's -reference pages — one from the live cobra tree, one from the CRD schemas under -`config/crds`. Both are plain deterministic generators; neither invokes an LLM. +`mage docs:cli` and `mage docs:crd` regenerate the docs site's reference pages — +one from the live cobra tree, one from the CRD schemas under `config/crds` — +writing into `site/content/docs`. Both are plain deterministic generators; +neither invokes an LLM. `PRIMITIVES.md` (repo root) and `docs/owasp-agentic-top10-coverage.html` are both hand-maintained: update them by hand when a primitive's code moves or the @@ -625,7 +626,7 @@ invisible in the mode everyone tests.** See ## Ship gate: all three test suites must pass before anything merges -**Nothing ships — no merge to `master`, no push, no "done" — until all three +**Nothing ships — no merge to `main`, no push, no "done" — until all three suites are green:** ```bash @@ -809,7 +810,7 @@ false confidence. The integration and e2e tests are gated behind A change can leave the entire e2e suite red — or not even compiling — and the default `go test` run stays green. -This is not hypothetical: the owner-derived approver refactor merged to `master` +This is not hypothetical: the owner-derived approver refactor merged to `main` with the whole e2e suite broken (stale `started_by` model, a `pipeline.Authz` stub missing new methods, approval scenarios that no longer had a valid approver) because only the unit suite was run. Fixing it after the fact cost far @@ -1029,7 +1030,7 @@ log): here "audit" means _review the code_, not _the append-only ledger_. ### What a pass does 1. **Scope the target.** Default is the **whole repo** (`pkg/` + `cmd/`). Narrow - it when asked: "recent changes" → `git diff master...HEAD`; one or more named + it when asked: "recent changes" → `git diff main...HEAD`; one or more named packages → just those. The whole-repo pass is expensive by design — it partitions the 330+ leaf packages into subsystem groups and fans out — so use the narrowed forms for routine work and reserve the full sweep for a periodic @@ -1089,13 +1090,13 @@ it): ```bash mage audit:all # whole repo (pkg/ + internal/ + cmd/) -mage audit:recent # git diff master...HEAD (no-op if empty) +mage audit:recent # git diff main...HEAD (no-op if empty) mage audit:pkg pkg/memory # a named package/dir ``` Env overrides: `AUDIT_DRY_RUN=1` prints the prompt/command without invoking Claude; `AUDIT_CLAUDE_MODEL` (default `opus`) and `AUDIT_DIFF_BASE` (default -`master`) override the model and the "recent" diff base. Or trigger a pass in +`main`) override the model and the "recent" diff base. Or trigger a pass in plain language — "run an audit on `pkg/memory`" — following the steps above. ### Rules of thumb diff --git a/README.md b/README.md index ba43a908..95c3bdb9 100644 --- a/README.md +++ b/README.md @@ -52,15 +52,15 @@ with many different owners. Every control in OAP therefore sits outside the model. The platform decides before the call, and the decision does not depend on the agent's cooperation. -| | Typical agent platform | OAP | -| ----------------------------------- | ---------------------------------- | ------------------------------------ | -| What an agent can reach | Whatever its credentials allow | Exactly what you granted | -| Who decides an action is allowed | The model, in the moment | The platform, before the call | -| An injected instruction mid-session | Can redirect the agent | Cannot exceed the approved plan | -| Tool credentials | Shared across tools in one sandbox | Held only by the tool that uses them | -| Restricting an MCP server | Needs a narrow upstream token | Declared by you, enforced per call | -| Revoking access | Rotate credentials, redeploy | One permission graph call | -| The audit log | Append-only, enforced by the store | Signed, chained, verifiable offline | +| | Typical agent platform | OAP | +| ----------------------------------- | ---------------------------------- | --------------------------------------- | +| What an agent can reach | Whatever its credentials allow | Exactly what you granted | +| Who decides an action is allowed | The model, in the moment | The platform, before the call | +| An injected instruction mid-session | Can redirect the agent | Cannot widen what it's authorized to do | +| Tool credentials | Shared across tools in one sandbox | Held only by the tool that uses them | +| Restricting an MCP server | Needs a narrow upstream token | Declared by you, enforced per call | +| Revoking access | Rotate credentials, redeploy | One permission graph call | +| The audit log | Append-only, enforced by the store | Signed, chained, verifiable offline | ## What makes it secure @@ -192,9 +192,8 @@ On first launch, pick a model provider, enter its API key, and set a local admin password. OAP provisions the VM, configures the platform, and installs a demo agent. -Desktop is single-player: good for trying OAP, developing and demoing agents, or -running production agents one person owns and operates. Use Kubernetes when -agents need a shared environment. +Desktop is single-player: good for trying OAP and for developing and demoing +agents. Use Kubernetes for anything durable or shared. **Local Kubernetes** installs onto a `kind` cluster for development: @@ -340,10 +339,13 @@ outside it. ## Read the docs +The docs are at [openap.org/docs](https://openap.org/docs). To run them locally +instead: + ```bash -cd showcase +cd site pnpm install -pnpm docs:dev +pnpm dev ``` Then open [http://localhost:5179](http://localhost:5179) for installation diff --git a/cmd/oap/clidocs_gen_test.go b/cmd/oap/clidocs_gen_test.go index b2f94be4..677a89d1 100644 --- a/cmd/oap/clidocs_gen_test.go +++ b/cmd/oap/clidocs_gen_test.go @@ -7,7 +7,7 @@ import ( "github.com/authzed/openagentprimitives/pkg/gen/clidocs" ) -// TestGenerateCLIReference regenerates the showcase docs' CLI reference from the +// TestGenerateCLIReference regenerates the site's CLI reference from the // live oap command tree. It is the gated driver for pkg/gen/clidocs (NewRootCmd // is in package main and can't be imported there). Runs only when // OAP_GEN_CLI_DOCS names an output dir — `mage docs:cli` sets it; a normal diff --git a/cmd/oap/internal/channelcmd/root.go b/cmd/oap/internal/channelcmd/root.go index d982ff60..293d1efe 100644 --- a/cmd/oap/internal/channelcmd/root.go +++ b/cmd/oap/internal/channelcmd/root.go @@ -12,7 +12,7 @@ func NewCmd(g *apcmd.Globals) *cobra.Command { cmd := &cobra.Command{ Use: "channel", Aliases: []string{"channels", "ch"}, - Short: "Manage Channel CRs (Slack, future webhook/cron, ...).", + Short: "Manage Channel CRs (Slack, browser, GitHub webhooks, schedules, ...).", } cmd.AddCommand( newChannelListCmd(g), diff --git a/docs/assets/brand/README.md b/docs/assets/brand/README.md index cd8250f8..d693bdf1 100644 --- a/docs/assets/brand/README.md +++ b/docs/assets/brand/README.md @@ -34,6 +34,7 @@ the inlined copies too: | `pkg/platform/identityd/handlers_password.go` | The icon on the sign-in page | | `cmd/oap/internal/desktop/setupui/static/index.html` | The logomark in the desktop setup header, plus the icon | | `cmd/oap/internal/desktop/menubaricons/render/tunnel.go` | Not a copy: the menu-bar icons are drawn in code. `menuMarkCutout` carries the menu icon's cut-out; regenerate the PNGs with `mage desktop:icons`. | +| `site/components/OapMark.tsx` | `OapMark`, the logomark inlined for the landing page and site footer so it inherits `currentColor` and needs no light/dark asset pair. | -The repo README and the showcase docs site (`showcase/docs/app`) reference the -files here directly. +The repo README and the docs + landing site (`site/`: the `Wordmark` component +and the root layout icons) reference the files here directly. diff --git a/docs/owasp-agentic-top10-coverage.html b/docs/owasp-agentic-top10-coverage.html index 1b85c8d6..ef4ba2a2 100644 --- a/docs/owasp-agentic-top10-coverage.html +++ b/docs/owasp-agentic-top10-coverage.html @@ -91,7 +91,7 @@ } .distro > div { display: flex; align-items: center; justify-content: center; color: #0c050f; } .distro .s-full { background: var(--cov-full); flex: 4; } - .distro .s-partial { background: var(--cov-partial); flex: 5; } + .distro .s-partial { background: var(--cov-partial); flex: 6; } .distro .s-minimal { background: var(--cov-minimal); flex: 1; } .distro .s-na { background: var(--cov-na); flex: 1; } .distro-note { color: var(--fg-faint); font-size: 12px; } @@ -213,7 +213,7 @@

OWASP Agentic Top 10 × agentprimiti

Source: OWASP Top 10 For Agentic Applications 2026 (OWASP GenAI Security Project — Agentic Security Initiative, Dec 2025, CC BY-SA 4.0)  ·  - Assessment scope: agentprimitives @ branch master, + Assessment scope: agentprimitives @ branch main, point-in-time read of the codebase.  ·  Not an official OWASP or AuthZed artifact.

@@ -229,17 +229,16 @@

At a glance

ap's defenses concentrate at the action boundary (tools, identity, authorization, human approval) and the execution boundary (sandboxed, non-root, no-shell, default-on per-tool - circuit breakers / rate limits, and default-on runner + sandbox egress NetworkPolicies). They thin out at + circuit breakers plus opt-in rate limits, and default-on runner + sandbox egress NetworkPolicies). They thin out at the input / content-integrity boundary (memory poisoning, indirect injection of retrieved content); supply-chain provenance now has content-hash pinning across dependency kinds but - still lacks signing / attestation. Multi-agent risks (ASI07/ASI08 - fan-out) are mostly off the table because ap runs one agent per session. + still lacks signing / attestation. Multi-agent risk (ASI07) is bounded by a closed, reviewed subagent + roster and typed, separately authorized parent↔child messages, but stays within one cluster.

@@ -299,12 +298,12 @@

Memory & Context Poisoning

-
-
ASI07Architectural N/A
+
+
ASI07Partial

Insecure Inter-Agent Comms

Tampered / spoofed / replayed messages between cooperating agents (A2A, MCP, message bus).

-
-
single-agent
+
+
channelsagent-def
@@ -351,7 +350,7 @@

Rogue Agents

ASI04 — Agentic Supply Chain ASI05 — Unexpected Code Execution ASI06 — Memory & Context Poisoning - ASI07 — Insecure Inter-Agent Comms + ASI07 — Insecure Inter-Agent Comms ASI08 — Cascading Failures ASI09 — Human-Agent Trust Exploitation ASI10 — Rogue Agents @@ -374,10 +373,11 @@

What ap does

  • Every tool result is wrapped in an unpredictable per-result nonce (<untrusted-tool-output nonce="…">) the output can't forge — and tool results are the only non-user ingress path: retrieval, web search, and even memory/KG recall arrive as wrapped tool output, and wrappers persist in the transcript across turns.pkg/agent/runner/loop.go:59–98, 1561
  • System prompt rule: treat everything inside a matched pair strictly as data to observe, never instructions — even "ignore previous instructions" style text. This is delimiter-based spotlighting with unforgeable markers, and prompt-level by design: a general-purpose agent must reason over the content itself, so quarantining it behind a sub-LLM or paraphrasing it would strip or alter exactly the meaning the task needs. The rule's first line is model compliance; the enforcement backstop is structural at the action boundary — dual validation, approval gates, the isolated summarizer — not input isolation.pkg/agent/runner/prompt.go:206
  • +
  • Tool output cannot widen what the agent may do: every action a tool result might prompt is checked outside the model, at the same pre-dispatch choke point as any other call. The delimiter and system-prompt items above help the model, but are not the boundary.pkg/agent/runner/loop_dispatch.go
  • High-impact / external actions are gated by human approval, so a hijacked goal can't silently execute irreversible side effects.pkg/authz/hooks/toolcallauthz.go
  • The approver's "What" is generated by an isolated summarizer LLM that never sees the (possibly-poisoned) primary chat — a hijacked agent can't talk its way past the gate (see ASI09).
  • Closed-world tools + least privilege bound the blast radius of any redirected goal.
  • -
  • A pluggable content-inspection seam now guards the tool-I/O ingress path — the only non-user ingress (see the nonce-wrap item above). Registry-keyed contentguard Inspector plugins examine the serialized tool args (PreToolCall) and the tool result (PostToolCall) and return pass / block / approve, mapped onto the existing pipeline (Block withholds the whole result from the model; Approve raises a human gate). Default-off, referenced from tiered settings by ID as a ceiling (lower tiers add, never weaken), and fail-closed: an inspector that errors blocks, an unregistered ID / unparseable config makes the settings object Invalid. The shipped example, url-allowlist, enforces an ordered domain-glob / regex / CEL URL policy (per-rule allow/deny/approve) over URLs found in tool I/O — blocking exfiltration / injection-carrier URLs in tool output before they reach the model.pkg/authz/contentguard/; pkg/authz/contentguard/kinds/urlallowlist/; pkg/authz/contentguard/hook.go; pkg/apis/v1alpha1/settings_common_types.go (ContentInspectors)
  • +
  • A pluggable content-inspection seam now guards the tool-I/O ingress path — the only non-user ingress (see the nonce-wrap item above). Registry-keyed contentguard Inspector plugins examine the serialized tool args (PreToolCall) and the tool result (PostToolCall) and return pass / block / approve, mapped onto the existing pipeline (Block withholds the whole result from the model; Approve raises a human gate). Default-off, referenced from tiered settings by ID as a ceiling (lower tiers add, never weaken), and fail-closed: an inspector that errors blocks, an unregistered ID / unparseable config makes the settings object Invalid. Two inspectors ship. prompt-injection runs a classifier over inspected text in a per-session pod with egress denied unconditionally, and blocks content above a threshold. url-allowlist enforces an ordered domain-glob / regex / CEL URL policy (per-rule allow/deny/approve) over URLs found in tool I/O — blocking exfiltration / injection-carrier URLs in tool output before they reach the model.pkg/authz/contentguard/; pkg/authz/contentguard/kinds/promptinjection/; pkg/authz/contentguard/kinds/urlallowlist/; pkg/authz/contentguard/hook.go; pkg/apis/v1alpha1/settings_common_types.go (ContentInspectors)
@@ -385,7 +385,7 @@

What's missing

  • No goal-lock / "intent capsule." The declared goal isn't bound into a signed envelope per cycle, so subtle goal drift isn't detected.
  • No plan-divergence detection. The plans state tracks step status but never alerts when the agent deviates from its declared plan.pkg/agent/session/state/plans/
  • -
  • The content-inspection seam exists, but no prompt-injection / prompt-carrier detector ships in it yet. The contentguard framework + the url-allowlist example (see left) cover URL-policy exfil/injection-carrier guarding, but there is no built-in CDR (content disarm & reconstruction) or injection-payload classifier inspector — that detector is the next inspector kind, not a new framework. Coverage is also tool-I/O-scoped: meta tools (respond_to_user et al.) bypass the gate pipeline, and memory/KG writes aren't inspected (ASI06).
  • +
  • Inspection covers tool I/O, not the agent's own replies. The prompt-injection and url-allowlist inspectors (see left) examine tool args and results; there is no built-in CDR (content disarm & reconstruction) inspector, and memory/KG writes aren't inspected (ASI06).
@@ -410,7 +410,7 @@

What ap does

  • Secret scrubbing + out-of-band secretout: declared secrets in tool output are diverted to a session store and replaced with an opaque handle the LLM never reads.pkg/tools/redact/, pkg/agent/secretout/
  • SSRF-guarded HTTP for MCP; turn / token / duration budgets bound runaway loops.pkg/x/safehttp/, pkg/agent/runner/budget.go
  • Capability drift is enforced per the effective pinning mode: a block rule withholds drifted tools at session start, approve escalates their calls to human approval (an empty rule mode defaults to approve), and drift is recorded in observedPins + audit pin facts.cmd/runner/pindrift.go:10–24; pkg/platform/settings/pinning.go:11–15
  • -
  • Per-tool rate limits are enforced at dispatch and default-on: a toolguard Guard hook runs at PreToolCall (before the SpiceDB check) and denies calls that exceed maxCallsPerTurn or maxCalls + window; even with no policy declared, a builtin rule applies. This directly bounds the OWASP "ping in a loop to exfiltrate via DNS" pattern (ASI02 scenario 7).pkg/apis/v1alpha1/toolguard_types.go:87–101; pkg/authz/toolguard/state.go:217–239; pkg/authz/toolguard/hook_guard.go:45–102; pkg/agent/runner/pipeline_wiring.go:794–814
  • +
  • Per-tool rate limits are enforced at dispatch, opt-in: a toolguard Guard hook runs at PreToolCall (before the SpiceDB check) and denies calls that exceed maxCallsPerTurn or maxCalls + window. The builtin rule that applies with no policy declared turns the circuit breaker on and leaves rate limits off, so a limit binds once a ToolGuardPolicy tier sets one. This directly bounds the OWASP "ping in a loop to exfiltrate via DNS" pattern (ASI02 scenario 7).pkg/apis/v1alpha1/toolguard_types.go:87–101; pkg/authz/toolguard/policy.go:11; pkg/authz/toolguard/state.go:217–239; pkg/authz/toolguard/hook_guard.go:45–102; pkg/agent/runner/pipeline_wiring.go:794–814
  • Per-call data-volume budgets are enforced in both directions. A toolguard DataLimit caps the serialized tool-args size at PreToolCall (egress — over-limit denies before the tool runs) and the tool-result size at PostToolCall (ingress — over-limit withholds the result, swapping in an IsError the model never reads), so a single call can't move an unbounded payload past its cap. The ingress cap applies to error results too — a tool can't mark an oversized exfil payload IsError to slip it through — and the limit is available per-tool and as a tiered, strictest-wins ceiling.pkg/apis/v1alpha1/toolguard_types.go:105–132,164–172; pkg/authz/toolguard/hook_guard.go:58–62; pkg/authz/toolguard/hook_record.go; pkg/authz/toolguard/events.go
  • @@ -418,7 +418,7 @@

    What ap does

    What's missing

    @@ -557,28 +557,28 @@

    What's missing

    -
    -

    ASI07 Insecure Inter-Agent Communication Architectural N/A

    +
    +

    ASI07 Insecure Inter-Agent Communication Partial

    Multi-agent systems are exposed to intercepted, spoofed, or replayed messages between cooperating agents - (A2A, MCP, message bus). ap runs one agent per session; channels are - human↔agent, not agent↔agent — so this class is largely out of scope today. + (A2A, MCP, message bus). In ap, agents talk to each other only through delegation: a parent + session hands work to a subagent session, and the two exchange a small set of typed messages.

    -

    Why it's out of scope (and where the seam is)

    +

    What ap does

      -
    • Each AgentSession is a single runner pod with one LLM loop; there is no agent-to-agent delegation or A2A registry.
    • -
    • NATS carries human↔agent envelopes and operator↔runner control only — not cross-runner agent messaging.README.md (architecture)
    • -
    • Per-session pods, per-session Secrets, and scoped RBAC keep one session's traffic off another's.
    • +
    • Delegation reaches only a closed, reviewed subagent roster, validated as a DAG (no cycles); entries can be pinned to an exact bundle digest.
    • +
    • The delegation tree is bounded: maxDelegatedAgents caps its size, and each roster member has a mode ceiling. Each subagent is its own scoped, budgeted, audited AgentSession.
    • +
    • Parent and child exchange only three typed, separately authorized directions (ask_parent, return_result, reply_to_subagent), delivered as inspected tool results. Free-form agent-to-agent messages were deliberately removed.pkg/apis/v1alpha1/subagentrequest_types.go
    • +
    • The sender is authenticated by its own per-session bus subject, and the destination must match a real Channel joining the two sessions.pkg/channels/channelkinds/agent/sender.go
    • +
    • Passing data past its audience parks for the data owner's approval, so delegation can't launder a disclosure boundary.
    -

    What to watch if multi-agent lands

    +

    What's missing

      -
    • The transport that does exist isn't fully locked down: the channelsd NATS JWT subject scope is broad (ap.> + _INBOX.>), though runner per-session JWTs are already narrowly scoped.
    • -
    • No mutual-auth / signed-message / anti-replay machinery exists to build on — it would be green-field if A2A is added.
    • -
    • No attested agent registry or agent-card verification.
    • +
    • Delegation stays within one cluster; there is no protocol for agents outside it.
    @@ -701,7 +701,7 @@

    Where the gaps cluster

    Input / content integrity - A pluggable content-inspection seam (contentguard) now guards the tool-I/O ingress path with a url-allowlist example — but no prompt-injection/CDR detector inspector ships yet, meta tools bypass it, and memory/KG writes are still uninspected (no content validation on memory writes; bootstrap-poisoning via self-ingestion; no provenance scores). + A pluggable content-inspection seam (contentguard) now guards the tool-I/O ingress path with a prompt-injection classifier and a url-allowlist inspector — but inspection covers tool I/O, not the agent's own replies, no CDR inspector ships, and memory/KG writes are still uninspected (no content validation on memory writes; assistant turns ingested into the knowledge graph without separate validation; no provenance scores). ASI01 · ASI06 pkg/authz/contentguard/; facade.go:108–127, kgingestion/hooks.go @@ -719,7 +719,7 @@

    Where the gaps cluster

    Intra-session isolation - Tier-1 sandbox isolation deferred — ToolCalls in a session share UID / /proc / /work. (Per-tool rate limits / circuit breakers are now enforced and default-on via pkg/toolguard in the dispatch pipeline.) + Tier-1 sandbox isolation deferred — ToolCalls in a session share UID / /proc / /work. (Per-tool circuit breakers are now enforced and default-on via pkg/authz/toolguard in the dispatch pipeline; rate limits are enforced there too, but opt-in.) ASI05 · ASI08 Open @@ -731,11 +731,10 @@

    Where the gaps cluster

    classic agent-authorization risks — tool misuse, privilege abuse, unsafe execution, and human-approval manipulation (ASI02/03/05/09). The work ahead is mostly at two frontiers the design hasn't reached yet: content integrity (treating untrusted data and memory as adversarial, not just access-controlled) — - where a pluggable contentguard inspection seam over tool I/O is now the first foothold, awaiting a - prompt-injection/CDR detector and memory-write validation — and supply-chain provenance - (verifying where tools and descriptors come from). Multi-agent risks - (ASI07/08) stay parked until/unless ap grows agent-to-agent topology — at which point inter-agent - auth becomes green-field work, not a retrofit. + where a pluggable contentguard inspection seam over tool I/O, with a prompt-injection classifier and a + URL allowlist, is now the first foothold, awaiting CDR and memory-write validation — and supply-chain provenance + (verifying where tools and descriptors come from). Inter-agent communication (ASI07) is bounded to delegation + inside one cluster; there is no protocol for agents outside it.
    @@ -747,8 +746,8 @@

    Mapped to the six primitives

    Safe toolsASI02, ASI04, ASI05, ASI01Strong on misuse/RCE; weak on supply-chain provenance. Identity & credentialsASI03Strong; TOCTOU + sidecar-freeze are the edges. AuthorizationASI02, ASI03, ASI06, ASI10Strong per-action checks; no behavioral attestation. - Agent definitionASI01, ASI08, ASI09Approval + budgets + isolation strong; no plan-divergence. - Channels & continuityASI09, (ASI07)Human approval strong; agent↔agent absent by design. + Agent definitionASI01, ASI07, ASI08, ASI09Approval + budgets + isolation strong; no plan-divergence. + Channels & continuityASI09, ASI07Human approval strong; agent↔agent limited to typed, authorized delegation messages. Memory & knowledgeASI06, ASI10Access-control strong; content-integrity thin. @@ -756,7 +755,7 @@

    Mapped to the six primitives