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 @@
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.
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.
Tampered / spoofed / replayed messages between cooperating agents (A2A, MCP, message bus).
-<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, 1561external actions are gated by human approval, so a hijacked goal can't silently execute irreversible side effects.pkg/authz/hooks/toolcallauthz.gocontentguard 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)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)plans state tracks step status but never alerts when the agent deviates from its declared plan.pkg/agent/session/state/plans/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).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).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/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–15toolguard 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–814toolguard 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–814toolguard 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.gonone or a missing class is a fail-closed full egress deny), but enforcement is L3/L4 only — label selectors, CIDRs, and ports. Allowlist-mode sandboxes get coarse TCP 443/80 to any address; the recorded effectiveAllowedHosts hostnames are not enforced without a DNS-aware CNI.pkg/controllers/agentsession/netpol.go:167–180,323
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.
AgentSession is a single runner pod with one LLM loop; there is no agent-to-agent delegation or A2A registry.maxDelegatedAgents caps its size, and each roster member has a mode ceiling. Each subagent is its own scoped, budgeted, audited AgentSession.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.goap.> + _INBOX.>), though runner per-session JWTs are already narrowly scoped.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).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)./proc / /work. (Per-tool rate limits / circuit breakers are now enforced and default-on via pkg/toolguard in the dispatch pipeline.)/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.)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.
Methodology. Risk definitions are taken from the OWASP Top 10 for Agentic Applications 2026.
Coverage verdicts come from a point-in-time read of the agentprimitives codebase (branch
- master): file references point at the mechanism that mitigates each risk,
+ main): file references point at the mechanism that mitigates each risk,
Meter fills are a qualitative judgment of coverage breadth, not a quantitative score.
Caveats. This is an internal mapping for orientation, not a formal security audit or a
penetration test. It does not certify that the cited controls are free of implementation bugs — only that the
diff --git a/magefiles/clidocs.go b/magefiles/clidocs.go
index fa7f937e..049d1dd0 100644
--- a/magefiles/clidocs.go
+++ b/magefiles/clidocs.go
@@ -13,14 +13,14 @@ import (
"github.com/authzed/openagentprimitives/pkg/gen/crddocs"
)
-// docsGuidesDir is where the generated reference MDX lands, alongside the rest of
-// the showcase docs guides.
-const cliDocsDir = "showcase/docs/guides"
+// cliDocsDir is where the generated reference MDX lands, alongside the
+// hand-written guides the site renders.
+const cliDocsDir = "site/content/docs"
// crdSchemaDir holds the controller-gen CRD YAMLs the CRD reference is built from.
const crdSchemaDir = "config/crds"
-// Crd regenerates the showcase docs' CRD reference (one MDX page per CustomResource
+// Crd regenerates the site's CRD reference (one MDX page per CustomResource
// kind) from the CRD schemas under config/crds. Unlike Cli it runs in-process — it
// reads YAML, so it needs no seam into package main. Run `mage gen:api` first if
// the CRD schemas are stale relative to the *_types.go.
@@ -32,7 +32,7 @@ func (Docs) Crd() error {
return nil
}
-// Cli regenerates the showcase docs' CLI reference (one MDX page per `oap`
+// Cli regenerates the site's CLI reference (one MDX page per `oap`
// command family) from the live cobra tree. The walk lives in pkg/gen/clidocs;
// it is driven here by the gated TestGenerateCLIReference in cmd/oap, because
// NewRootCmd is package main and can only be reached from within that package.
diff --git a/magefiles/fmt.go b/magefiles/fmt.go
index e850ef51..23422c14 100644
--- a/magefiles/fmt.go
+++ b/magefiles/fmt.go
@@ -30,6 +30,8 @@ var fmtTargets = []string{
"web/**/*.tsx",
"showcase/**/*.ts",
"showcase/**/*.tsx",
+ "site/**/*.ts",
+ "site/**/*.tsx",
}
// All rewrites Markdown and TypeScript in place.
diff --git a/magefiles/magefile.go b/magefiles/magefile.go
index 02aa998c..ddb90014 100644
--- a/magefiles/magefile.go
+++ b/magefiles/magefile.go
@@ -417,9 +417,8 @@ func (Test) Unit() error {
}
// defaultWebCheckDiffBase is the git ref checkWebBundleFreshness diffs
-// against by default, mirroring auditgen's DiffBase convention
-// (AUDIT_DIFF_BASE, defaulting to "master").
-const defaultWebCheckDiffBase = "master"
+// against by default: this repo's base branch.
+const defaultWebCheckDiffBase = "main"
// checkWebBundleFreshness runs `mage web:check` when this branch's diff
// touches pkg/**/ui/** — the TypeScript source web/vite.config.ts's
@@ -443,7 +442,7 @@ const defaultWebCheckDiffBase = "master"
// already needs node to have made that edit, so paying it here costs them
// nothing new.
//
-// WEB_CHECK_DIFF_BASE overrides the default "master" base. A diff-computation
+// WEB_CHECK_DIFF_BASE overrides the default "main" base. A diff-computation
// failure (no such ref, a shallow clone missing history) is logged and SKIPS
// the check rather than failing test:unit outright — test:unit is the fast
// gate every developer runs constantly (see (Test).Postgres's own doc), and a
@@ -1356,7 +1355,7 @@ func shortDockerID(id string) string {
return id
}
-// Docs regenerates the showcase docs site's generated reference pages from the
+// Docs regenerates the site's generated reference pages from the
// live source of truth (the cobra tree, the CRD schemas) — see Cli and Crd in
// clidocs.go. Both are deterministic; neither invokes an LLM.
type Docs mg.Namespace
@@ -1385,8 +1384,8 @@ func (Audit) All() error {
return auditGenerator().All(context.Background())
}
-// Recent audits only the code changed vs AUDIT_DIFF_BASE (default master):
-// `git diff master...HEAD`. A no-op when that diff is empty. Writes
+// Recent audits only the code changed vs AUDIT_DIFF_BASE (default main):
+// `git diff main...HEAD`. A no-op when that diff is empty. Writes
// docs/audits/
- Open Agent Primitives (OAP) is a Kubernetes-native runtime for LLM agents. Defining an agent
- is easy — a prompt, some tools, a loop. Operating one safely is the hard part, and that's what OAP is
- for: an agent's identity, authorization, tools, memory, and channels are first-class, governed things rather
- than an afterthought.
- `, then `##` sections, a security
+ ` `, so a ` ` you write for the lede nests
+ inside that ` `. A ` ` inside a ` ` is invalid HTML — the browser
+ re-parents it at parse time, the server-rendered markup and the client's DOM
+ disagree, and React throws hydration error #418, but only in production.
+ `pnpm check` fails the build if a guide does this.
+
+### Regenerate the CLI / CRD reference
+
+Never hand-edit `content/docs/oap-*.mdx` or `crd-*.mdx`. From the repo root:
+`mage docs:cli` / `mage docs:crd`. If the CRD schemas are stale, `mage gen:api`
+first. Read the diff before committing.
+
+### Add media
+
+Capture stills and clips with the `showcase/` engine (see
+[`showcase/AGENTS.md`](../showcase/AGENTS.md)), then copy the output into
+`site/public/media/`, add an entry to `site/content/_manifest.json`, and
+reference it from a page with ` {kicker}
+ Building blocks for running enterprise agents in your own cluster,
+ on the models you choose.{" "}
+
+ The AI never decides what it’s allowed to do. OAP checks
+ every action before it runs.
+
+
+ Trusting an agent means answering four questions:
+
+ OAP answers each one in the platform, before the agent acts.
+
+ A token for one repository usually reaches every repository. An
+ agent inherits all of it.
+
+ A model can’t reliably tell instructions from data, so its
+ judgment can’t be the boundary. A better prompt is still just
+ an instruction.
+
+
+ So OAP enforces every rule itself, before each action runs, where
+ no prompt can change it.
+
+
+ A primitive is a basic building block that agents rely on, whatever
+ they do. OAP ships a working implementation of every one.
+
+ #{i + 1} {p.verb}
+ {p.body}
+ The platform enforces every control. No prompt or tool output can
+ affect enforcement.
+
+ {a.links.map(([label, href]) => (
+
+ {label}
+
+ ))}
+
+ Plan gating and leakage tracking are opt-in per agent class. Network
+ policy is on by default.
+
+ Agent Builder is an OAP agent that creates other agents. Describe
+ what you need in plain language, then test it live.
+ {b.step} {b.body}
+ Off by default. You turn it on by naming who may use it, and it
+ follows every control above.
+
+ The capabilities you’d expect from any agent platform.
+ {e.body}
+ How OAP maps to each risk in the OWASP Top 10 for Agentic
+ Applications, with the remaining gaps listed alongside. This is a
+ self-assessment, not a certification.
+ How we know the gates still hold
+ Every merge runs whole-session scenarios, golden authorization
+ traces and replays of real sessions. A permission check that stops
+ working fails the build.
+
+ Based on the{" "}
+
+ OWASP Top 10 for Agentic Applications (2026)
+ {" "}
+ by the OWASP GenAI Security Project, licensed under{" "}
+
+ CC BY-SA 4.0
+
+ . This assessment is not affiliated with or endorsed by OWASP.
+ Defense in depth
+ To misuse an agent, an attacker has to get past an approved plan, a
+ locked slot, a tool spec, a check on every call, and a sandbox that
+ never held the credential.
+ 404
+ It may have moved when the docs were reorganized. Try the docs, or
+ start from the home page.
+
+ {announce(state)}
+ {UNAVAILABLE} Search failed. Try again.oap audit verify.
+ >,
+ ],
+ [
+ "Pinning and budgets.",
+ "Approved versions, capped spend and circuit breakers.",
+ ],
+ ],
+ links: [
+ ["Audit log", "/docs/audit-log"],
+ ["Revocation", "/docs/revocation"],
+ ["Pinning", "/docs/supply-chain-pinning"],
+ ],
+ },
+ {
+ title: "Isolation in the platform",
+ points: [
+ [
+ "Sanitized output.",
+ "Each content type has its own sanitizer, and rendered output runs under a strict CSP.",
+ ],
+ [
+ "Separate pods.",
+ "Runner, operator and sanitizers run apart, on micro-VMs where available.",
+ ],
+ [
+ "Scoped credentials.",
+ "Each component reaches only its own session on the control-plane bus.",
+ ],
+ [
+ "Reviewable packages.",
+ "Each agent ships as one OCI package you can gate like any artifact.",
+ ],
+ ],
+ links: [
+ ["Artifact safety", "/docs/artifact-safety"],
+ ["Packaging", "/docs/packaging-oap"],
+ ],
+ },
+];
+
+const BUILDER = [
+ {
+ step: "Describe",
+ title: "Say what it's for",
+ body: "Agent Builder picks the tools, connects accounts, and sets what the new agent may do.",
+ },
+ {
+ step: "Test",
+ title: "Try it in a workshop",
+ body: "Drafts run in isolation and can't touch your live agents.",
+ },
+ {
+ step: "Deliver",
+ title: "Hand over a bundle",
+ body: "You get a portable .oap bundle for an administrator to review and install.",
+ },
+];
+
+const EVERYTHING = [
+ {
+ title: "Your choice of model",
+ body: "Anthropic, OpenAI or OpenRouter, swappable per deployment.",
+ },
+ {
+ title: "Your infrastructure",
+ body: "A laptop, a local cluster, GKE, EKS, AKS or self-managed Kubernetes.",
+ },
+ {
+ title: "Channels",
+ body: "Slack, the browser, the CLI and GitHub, plus signed webhooks and scheduled runs. Transports are pluggable, so you can add your own.",
+ },
+ {
+ title: "Memory and knowledge graph",
+ body: "Structured recall, ranked search and graph queries.",
+ },
+ {
+ title: "Built in and swappable",
+ body: "The runner, authorization, approvals, sandboxing, credentials, memory and audit ship built in, and each can be swapped for one you already run.",
+ },
+ {
+ title: "Under your control",
+ body: "OAP runs inside a cluster you operate. Your team owns its network, access and upgrades.",
+ },
+];
+
+const COV_LABEL: Record
+ {current.steps.map((s) => (
+
+ $
+ {s.cmd}
+ {"\n"}
+
+ ))}
+ {current.comment.map((line) => (
+
+ {line}
+ {"\n"}
+
+ ))}
+
+ {children}
+ >
+ );
+}
+
+/* ------------------------------------------------------------------ page --- */
+
+export function Landing() {
+ return (
+
+ A secure way to run enterprise AI agents.
+
+
+ {QUESTIONS.map((q) => (
+
+
+
+
+
+
+
+
+ {COMPARE.map(([q, typical, oap]) => (
+
+ Question
+
+ Typical agent platform
+ OAP
+
+
+ ))}
+
+ {q}
+ {typical}
+ {oap}
+
+ {p.title}
+
+
+ {AREAS.map((a) => (
+
+ {a.title}
+
+ {a.points.map(([lead, body]) => (
+
+ {b.title}
+ {e.title}
+
+
+
+
+
+
+ {OWASP.map(([id, title, level, gap]) => (
+ Item
+ Risk
+ Coverage
+ Known gap
+
+
+ ))}
+
+
+ {id}
+
+ {title}
+
+
+ {COV_LABEL[level]}
+
+
+ {gap}
+ Every layer assumes the others may fail.
+
+ {block.guides.map((g) => (
+
+ This page doesn’t exist.
+ pnpm build.
+ >
+ );
+
+/** What the hidden status line announces for each state. */
+function announce(state: State): string {
+ switch (state.kind) {
+ case "idle":
+ return "";
+ case "unavailable":
+ return "Search is unavailable.";
+ case "error":
+ return "Search failed. Try again.";
+ case "results":
+ return state.items.length === 0
+ ? "No matches."
+ : `${state.items.length} result${state.items.length === 1 ? "" : "s"}.`;
+ }
+}
+
+export function Search() {
+ const pf = useRef
+ {state.items.length === 0 && (
+
+ )}
+
+
+ >
+ );
+}
diff --git a/showcase/docs/app/components/media.tsx b/site/components/media.tsx
similarity index 73%
rename from showcase/docs/app/components/media.tsx
rename to site/components/media.tsx
index 26f9dd81..c70b3ba3 100644
--- a/showcase/docs/app/components/media.tsx
+++ b/site/components/media.tsx
@@ -1,12 +1,35 @@
-import { useEffect, useState } from "react";
-import { getAsset } from "../manifest";
+"use client";
+import {
+ useEffect,
+ useRef,
+ useState,
+ type KeyboardEvent as ReactKeyboardEvent,
+} from "react";
+import { getAsset } from "@/lib/manifest";
function Missing({ name }: { name: string }) {
return