OWASP Default Template¶
What you'll learn: The 8 OWASP security policies in the owasp_agentic_default manifest, what each one does, which OWASP risks they cover, and how to customize them.
Overview¶
The OWASP Top 10 for Agentic Applications (2026) manifest provides baseline security coverage for any Kyvvu agent. It is applied to every agent at registration as part of the bundled baseline (Claude Code agents get Claude Code Safety in its place); assigning it again is refused with 409. The copy in the Kyvvu/manifests repository is the starting point for a customised version.
Design principles: - Immediate value — every policy works on install with zero configuration. - Structural over content-based — catches missing gates, runaway loops, and undeclared tools. Does not require environment-specific patterns (regex, domains, budgets). - Demonstrates path-dependence — policies 7 and 8 read task history, showcasing the "policies on paths" model.
The 8 policies¶
Registration policies (3)¶
| # | Name | Rule | Severity | OWASP risks |
|---|---|---|---|---|
| 1 | Agent must declare a substantive purpose | field_matches_regex |
high | ASI01, ASI10 |
| 2 | Agent must declare a tool allowlist | field_not_empty |
high | ASI02, ASI04 |
| 3 | Agent must declare a valid risk classification | field_in_list |
critical | (enables classification-aware policies) |
Policy 1 requires the purpose field to be at least 30 characters (^.{30,}$). Prevents placeholder values like "chat" or "assistant". Without an explicit purpose, goal drift cannot be detected and hijacking has no baseline to violate.
Policy 2 requires the declared_tools field to be non-empty. This is the precondition for runtime policy 4 (tool allowlist enforcement). Together they implement least-agency.
Policy 3 requires risk_classification to be one of high, limited, or minimal. Bridges to EU AI Act Article 6 for installations that add compliance templates.
Runtime policies (5)¶
| # | Name | Rule | Severity | OWASP risks |
|---|---|---|---|---|
| 4 | Tool calls must be in the declared allowlist | step_name_in_allowlist |
critical | ASI02, ASI04 |
| 5 | Code execution requires a preceding gate | step_requires_gate |
critical | ASI05 |
| 6 | Destructive resource operations require a preceding gate | step_requires_gate |
critical | ASI09 |
| 7 | External content taint: high-impact actions require a fresh gate | not(all_of(...)) |
critical | ASI01, ASI06, ASI09 |
| 8 | Bound resource calls per task to prevent runaway loops | execution_max_steps |
high | ASI08, ASI02 |
Policy 4 blocks tool calls whose step_name is not in the agent's declared_tools allowlist. Catches typosquatting, supply-chain swap-ins, and silent capability creep.
Policy 5 requires any step.exec to be preceded by a step.gate (human approval, static check, or sandbox dry-run) that records output.decision: "pass" — a bare step.gate, or one whose decision is anything else, does not clear the block. Direct mitigation for OWASP ASI05 (Unexpected Code Execution).
Policy 6 requires step.resource with verb DELETE to be preceded by a step.gate. Narrows to DELETE only so development iteration isn't constantly blocked. Production deployments typically widen to include POST and PATCH.
Policy 7 is a compound path-dependent policy — the headline demonstration of "policies on paths". See below.
Policy 8 blocks tasks that issue more than 50 step.resource calls. Catches runaway loops, the most common dev-time issue.
Policy 7: the taint policy explained¶
This policy enforces: if the agent has fetched external content earlier in this task, any subsequent high-impact action must be immediately preceded by a step.gate.
It addresses the indirect prompt injection pattern (EchoLeak): malicious instructions hidden in retrieved content silently redirect the agent's plan.
The logic uses not(all_of(...)):
not(
all_of(
# Current step IS a high-impact action
any_of(
current_is(step.exec),
current_is(step.message, POST),
current_is(step.resource, POST),
current_is(step.resource, PATCH),
current_is(step.resource, DELETE)
),
# External content HAS been fetched earlier
history_contains(step.resource, GET, target.trust="external"),
# Current step is NOT directly preceded by a gate
not(step_directly_preceded_by(step.gate))
)
)
Why not(all_of(...)) instead of just all_of(...)? Rule functions return True to pass and False to block. all_of returns True when all conditions match. If all three danger conditions are present, that means the situation is dangerous — but all_of would return True (pass). The not wrapper inverts it to False (block). See Compound Policies for a full explanation of this pattern.
Key property convention: step.resource GETs must set target.trust = "external" when fetching from outside an internal allowlist. No built-in template sets this property. Set it in a template rule, in @kv.step(properties=...), or in the tool's metadata["kyvvu"]; until it is set, policy 7 does not fire.
OWASP coverage summary¶
| OWASP Risk | Coverage | Policies |
|---|---|---|
| ASI01 Agent Goal Hijack | Partial | 1, 7 |
| ASI02 Tool Misuse | Strong | 2, 4, 6, 8 |
| ASI03 Identity & Privilege Abuse | Gap | (add credential-taint policy when deploying step.credential events) |
| ASI04 Agentic Supply Chain | Partial | 2, 4 |
| ASI05 Unexpected Code Execution | Strong | 5 |
| ASI06 Memory & Context Poisoning | Partial | 7 |
| ASI07 Insecure Inter-Agent Comms | Out of scope | (transport-layer concern, not runtime policy) |
| ASI08 Cascading Failures | Moderate | 8 |
| ASI09 Human-Agent Trust Exploit | Partial | 5, 6, 7 |
| ASI10 Rogue Agents | Partial | 1 |
Policy 7 requires target.trust to be set (see above).
What the demo agent demonstrates¶
The demo agent from kyvvu init runs four steps:
1. step.resource (GET) — resource read. Passes: reading is allowed.
2. step.model (no verb) — model call. Passes: no policies restrict model calls in the default template, and the EU AI Act baseline's content-marking policy applies to delivering model output, not to the call.
3. step.message (POST) — the reply is delivered. Nothing in this template objects; the EU AI Act baseline warns (content marking, Art. 50(2), and chatbot disclosure, Art. 50(1)) because no gate follows the model call. A compliance finding: the step still runs.
4. step.exec — code execution. Blocked: no passing step.gate precedes it (policy 5; a gate counts only when it records output.decision: "pass"). The security stop.
This demonstrates that the same agent code is allowed or blocked depending on the task's history and the policies in effect.
Customizing¶
Disable a policy¶
curl -X PUT https://platform.kyvvu.com/api/v1/policies/{id} \
-H "Authorization: Bearer <JWT>" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
Change severity¶
curl -X PUT https://platform.kyvvu.com/api/v1/policies/{id} \
-H "Authorization: Bearer <JWT>" \
-H "Content-Type: application/json" \
-d '{"severity": "high"}'
Expansion path¶
The template deliberately excludes rules that require environment-specific configuration. The recommended additions, in order of coverage value:
| Priority | Add | Covers | When |
|---|---|---|---|
| 1 | Credential read taint policy (tainted_path_block with taint_kind: step.credential and taint_verb: GET) |
ASI03 | Your deployment instruments step.credential events |
| 2 | Sensitive data read gate (step_requires_gate) |
ASI06, EU AI Act Art. 10 | A review gate is recorded before external calls |
| 3 | Domain allowlist for resource reads (domain_allowlist, with allowed_domains and target_kinds: [step.resource]) |
Closes "what counts as external" for policy 7 | Internal/external domain split is known. A listed step with no target.host is a violation, so declare a host on every step.resource |
| 4 | Cost ceiling (usage_budget with kind: step.model over usage.cost_usd) |
ASI08 economic DoS | Cost properties are emitted on step.model behaviours; the budget applies to that action kind only |
Next steps¶
- Manifests — other available manifests (EU AI Act, data minimization)
- Compound Policies — author your own
all_of/any_of/notpolicies - Built-in Rules Reference — all 25 rules