Spaces:
Running
Running
Merge pull request #11 from tyy0811/part-a/owasp-mapping
Browse filesPart A: OWASP LLM Top 10 (2025) mapping + cold-start evidence
- DECISIONS.md +12 -0
- README.md +2 -2
- SECURITY.md +84 -0
- agent_bench/serving/static/index.html +4 -1
- docs/plans/2026-04-15-owasp-llm-top-10-mapping-design.md +414 -0
- docs/plans/2026-04-15-owasp-llm-top-10-mapping-implementation.md +1076 -0
- measurements/2026-04-15-coldstart-n1.log +26 -0
- measurements/2026-04-15-coldstart-n2.log +26 -0
- measurements/2026-04-15-coldstart-n3.log +26 -0
- measurements/README.md +14 -0
DECISIONS.md
CHANGED
|
@@ -333,6 +333,18 @@ before validation catches it. For this use case (FastAPI docs, no real
|
|
| 333 |
PII in corpus), the risk is near-zero. The dashboard labels this
|
| 334 |
"monitored" (not "gated") to be explicit about the posture.
|
| 335 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 336 |
## Why additive SSE stage events?
|
| 337 |
|
| 338 |
The enhanced `/ask/stream` adds `meta` and `stage` event types alongside
|
|
|
|
| 333 |
PII in corpus), the risk is near-zero. The dashboard labels this
|
| 334 |
"monitored" (not "gated") to be explicit about the posture.
|
| 335 |
|
| 336 |
+
## Why named residual risks and scope limits, not "fully mitigated" verdicts?
|
| 337 |
+
|
| 338 |
+
The OWASP LLM Top 10 (2025) mapping could have been written as a 10-row table where LLM01 and LLM02 read as "addressed" without qualifiers β shorter and cleaner-looking. Rejected because OWASP's own 2025 text is explicit about what an input guardrail can and cannot do, and writing a verdict that contradicts the source the mapping cites would be compliance theater.
|
| 339 |
+
|
| 340 |
+
LLM01 Prompt Injection β OWASP 2025 states that RAG and fine-tuning do not fully mitigate prompt injection, and that indirect injection through retrieved content remains a core risk class. "Fully mitigated" is unsupportable for any system retrieving untrusted content into an LLM context window, which is every RAG system including this one. The LLM01 verdict reads "addressed directly with named residual risk"; the residual-risk cell cites OWASP's own "do not fully mitigate" language verbatim.
|
| 341 |
+
|
| 342 |
+
LLM02 Sensitive Information Disclosure β OWASP 2025's LLM02 mitigations span four concern classes: access controls, training-data handling, user-consent transparency, and proprietary-information governance. This implementation addresses a narrower output-side subset (output validation for PII leakage, secret formats, and URL hallucination) β not cleanly one of the four concern classes, but a narrower scope than any of them. The verdict reads "addressed directly for the applicable scope"; the scope-limit cell enumerates the four concern classes verbatim and names what addressing the broader concerns would require (multi-tenant or authenticated architecture).
|
| 343 |
+
|
| 344 |
+
The tension the entry resolves is honesty-vs-scannability: a mapping that surfaces named residual risks and scope limits is longer and harder to skim than one with uniform "addressed" verdicts, but the scannable version over-claims relative to the cited source. Honest evaluation is the brand. Every verdict cell in SECURITY.md must survive a reviewer reading OWASP 2025 in a second tab.
|
| 345 |
+
|
| 346 |
+
See [SECURITY.md Β§ LLM01 Prompt Injection](SECURITY.md#llm01-prompt-injection) and [Β§ LLM02 Sensitive Information Disclosure](SECURITY.md#llm02-sensitive-information-disclosure) for the verdict cells; this entry covers why the verdict discipline takes the form it does. The LLM01 "do not fully mitigate" phrasing and the LLM02 four-concern-class enumeration are canonical in SECURITY.md; the README tail and landing-page subtitle paraphrase but must preserve the named-residual-risk and scope-limit structure.
|
| 347 |
+
|
| 348 |
## Why additive SSE stage events?
|
| 349 |
|
| 350 |
The enhanced `/ask/stream` adds `meta` and `stage` event types alongside
|
README.md
CHANGED
|
@@ -45,7 +45,7 @@ API providers are directly comparable (same config). The self-hosted row uses `m
|
|
| 45 |
|
| 46 |
## Live Demo
|
| 47 |
|
| 48 |
-
**https://nomearod-agentbench.hf.space** (Hugging Face Spaces β
|
| 49 |
|
| 50 |
```bash
|
| 51 |
# In-scope question (expect answer with sources)
|
|
@@ -199,7 +199,7 @@ jq 'select(.session_id == "abc123")' logs/audit.jsonl
|
|
| 199 |
|
| 200 |
This is an application-layer security pipeline β it does not replace network-level security, authentication, or infrastructure hardening.
|
| 201 |
|
| 202 |
-
See [DECISIONS.md](DECISIONS.md) for why we chose two-tier detection over three, regex-only PII by default, JSONL over SQLite for audit, and HMAC over plain SHA-256 for IP hashing.
|
| 203 |
|
| 204 |
<details><summary>Security configuration</summary>
|
| 205 |
|
|
|
|
| 45 |
|
| 46 |
## Live Demo
|
| 47 |
|
| 48 |
+
**https://nomearod-agentbench.hf.space** (Hugging Face Spaces β cold wake on idle takes ~2 minutes, warm queries respond in ~5s; see [DECISIONS.md](DECISIONS.md) for the bounded measurement and v1.1 contingency)
|
| 49 |
|
| 50 |
```bash
|
| 51 |
# In-scope question (expect answer with sources)
|
|
|
|
| 199 |
|
| 200 |
This is an application-layer security pipeline β it does not replace network-level security, authentication, or infrastructure hardening.
|
| 201 |
|
| 202 |
+
See [SECURITY.md](SECURITY.md) for the OWASP LLM Top 10 (2025) mapping. See [DECISIONS.md](DECISIONS.md) for why we chose two-tier detection over three, regex-only PII by default, JSONL over SQLite for audit, and HMAC over plain SHA-256 for IP hashing.
|
| 203 |
|
| 204 |
<details><summary>Security configuration</summary>
|
| 205 |
|
SECURITY.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Security
|
| 2 |
+
|
| 3 |
+
This document maps the agent-bench implementation against the OWASP LLM Applications Top 10 (2025). It is an honest mapping, not a coverage claim β every verdict cell for a guardrail we run carries a named residual risk or scope limit when OWASP's own 2025 text makes the limits explicit. The scope is a docs Q&A bot serving static curated corpora (FastAPI + Kubernetes) with no user ingestion, no fine-tuning, no authenticated sessions, and no side-effectful tools.
|
| 4 |
+
|
| 5 |
+
The implementation maps to the OWASP Appendix 1 reference architecture: user input β input guardrails β retrieval/tools β LLM β output guardrails β response. The agent-bench realization is diagrammed in the [README Security Architecture section](README.md#security-architecture); verdict cells below cross-link to the source files that implement each guardrail. These scope facts are referenced in the verdict cells below where they constrain what each guardrail must do.
|
| 6 |
+
|
| 7 |
+
## Mapping summary
|
| 8 |
+
|
| 9 |
+
| Category | Verdict |
|
| 10 |
+
|---|---|
|
| 11 |
+
| LLM01 Prompt Injection | Addressed directly, named residual risk |
|
| 12 |
+
| LLM02 Sensitive Information Disclosure | Addressed directly, scope limit |
|
| 13 |
+
| LLM03 Supply Chain | Infrastructure layer, named gap |
|
| 14 |
+
| LLM04 Data and Model Poisoning | Out of scope |
|
| 15 |
+
| LLM05 Improper Output Handling | Addressed directly |
|
| 16 |
+
| LLM06 Excessive Agency | Addressed directly |
|
| 17 |
+
| LLM07 System Prompt Leakage | Addressed directly |
|
| 18 |
+
| LLM08 Vector and Embedding Weaknesses | Out of scope |
|
| 19 |
+
| LLM09 Misinformation | Addressed directly |
|
| 20 |
+
| LLM10 Unbounded Consumption | Infrastructure layer, named gap |
|
| 21 |
+
|
| 22 |
+
**Counts (verifiable in one eye-scan):** 6 addressed directly + 2 infrastructure layer + 2 out of scope = **10**.
|
| 23 |
+
|
| 24 |
+
## Detailed mapping
|
| 25 |
+
|
| 26 |
+
### LLM01 Prompt Injection
|
| 27 |
+
|
| 28 |
+
**Verdict:** Addressed directly with a named residual risk.
|
| 29 |
+
|
| 30 |
+
**Implementation:** Two-tier detection β Tier 1 heuristic regex (local, <1ms) with ~20 pattern families covering role/identity hijacking, instruction override, system-prompt extraction, credential/env-var extraction, jailbreak keywords, and base64-nested payloads; Tier 2 optional DeBERTa classifier on Modal GPU for high-confidence arbitration. Deployments without GPU run Tier 1 only; the two-tier design degrades to heuristic-only rather than failing closed. Grounded refusal via the retrieval-threshold gate bounds indirect injection through retrieved content. Static corpus + bounded `ToolRegistry` (only `search_documents` + `calculator`, no side-effectful tools) + `max_iterations` cap bound the blast radius. See [`agent_bench/security/injection_detector.py`](agent_bench/security/injection_detector.py), [`agent_bench/tools/registry.py`](agent_bench/tools/registry.py), and [DECISIONS.md Β§ Why two-tier injection detection, not three](DECISIONS.md#why-two-tier-injection-detection-not-three).
|
| 31 |
+
|
| 32 |
+
**Residual risk:** novel injection patterns not caught by heuristics or classifier. OWASP notes that RAG and fine-tuning do not fully mitigate prompt injection; indirect injection through retrieved content remains a core risk class.
|
| 33 |
+
|
| 34 |
+
### LLM02 Sensitive Information Disclosure
|
| 35 |
+
|
| 36 |
+
**Verdict:** Addressed directly for the applicable scope.
|
| 37 |
+
|
| 38 |
+
**Implementation:** Regex PII redaction on every retrieved chunk before it enters the LLM context window (EMAIL, SSN, CREDIT_CARD, PHONE, IP_ADDRESS) with optional spaCy NER for PERSON/ORG; post-generation output validation with an always-on secret-format deny list (OpenAI/Anthropic/Google/AWS/GitHub key prefixes, bearer tokens, env-var assignments) and URL-against-retrieved-chunks check. See [`agent_bench/security/pii_redactor.py`](agent_bench/security/pii_redactor.py), [`agent_bench/security/output_validator.py`](agent_bench/security/output_validator.py), and [DECISIONS.md Β§ Why regex + optional spaCy for PII, not a cloud API](DECISIONS.md#why-regex--optional-spacy-for-pii-not-a-cloud-api).
|
| 39 |
+
|
| 40 |
+
**Scope limit:** OWASP LLM02 mitigations span access controls, training-data handling, user-consent transparency, and proprietary-information governance. This implementation addresses only response-time data surfaced to users β a narrower, output-side subset that does not cleanly map to any single one of the four; broader concerns would require architectural changes for multi-tenant or authenticated deployment.
|
| 41 |
+
|
| 42 |
+
### LLM03 Supply Chain
|
| 43 |
+
|
| 44 |
+
**Verdict:** Addressed at the infrastructure layer with a named gap.
|
| 45 |
+
|
| 46 |
+
**Implementation:** Dependencies pinned in [`pyproject.toml`](pyproject.toml); container via [`Dockerfile`](Dockerfile); models from official upstreams loaded in [`agent_bench/security/injection_detector.py`](agent_bench/security/injection_detector.py).
|
| 47 |
+
|
| 48 |
+
**Named gap:** no SBOM or signed model provenance.
|
| 49 |
+
|
| 50 |
+
### LLM05 Improper Output Handling
|
| 51 |
+
|
| 52 |
+
**Verdict:** Addressed directly.
|
| 53 |
+
|
| 54 |
+
**Implementation:** [`OutputValidator`](agent_bench/security/output_validator.py) runs three deterministic checks: sensitive-output detection (PII formats + secret-format deny list), URL-chunk validation, and configurable blocklist. Text-only β no HTML, SQL, or code execution. See [DECISIONS.md Β§ Why three output validators, not four](DECISIONS.md#why-three-output-validators-not-four).
|
| 55 |
+
|
| 56 |
+
### LLM06 Excessive Agency
|
| 57 |
+
|
| 58 |
+
**Verdict:** Addressed directly.
|
| 59 |
+
|
| 60 |
+
**Implementation:** `max_iterations` caps tool-use depth; [`ToolRegistry`](agent_bench/tools/registry.py) contains only `search_documents` and `calculator` β no write, network, or code execution.
|
| 61 |
+
|
| 62 |
+
### LLM07 System Prompt Leakage
|
| 63 |
+
|
| 64 |
+
**Verdict:** Addressed directly.
|
| 65 |
+
|
| 66 |
+
**Implementation:** System prompt holds no credentials, auth tokens, or multi-tenant structure β docs-Q&A instruction with corpus-label substitution. Access control sits outside the LLM via [`RateLimitMiddleware`](agent_bench/serving/middleware.py) per-IP rate limiting. See [DECISIONS.md Β§ Why no authentication on API endpoints](DECISIONS.md#why-no-authentication-on-api-endpoints).
|
| 67 |
+
|
| 68 |
+
### LLM09 Misinformation
|
| 69 |
+
|
| 70 |
+
**Verdict:** Addressed directly.
|
| 71 |
+
|
| 72 |
+
**Implementation:** RRF retrieval-threshold gate β below `refusal_threshold`, [orchestrator](agent_bench/agents/orchestrator.py) emits grounded refusal. See [DECISIONS.md Β§ Why a relevance threshold for grounded refusal](DECISIONS.md#why-a-relevance-threshold-for-grounded-refusal).
|
| 73 |
+
|
| 74 |
+
### LLM10 Unbounded Consumption
|
| 75 |
+
|
| 76 |
+
**Verdict:** Addressed at the infrastructure layer with a named gap.
|
| 77 |
+
|
| 78 |
+
**Implementation:** Per-IP rate limit via [`RateLimitMiddleware`](agent_bench/serving/middleware.py); `max_iterations` cap; provider timeouts.
|
| 79 |
+
|
| 80 |
+
**Named gap:** per-IP only; no per-user quota or budget ceiling.
|
| 81 |
+
|
| 82 |
+
## What this doc is not
|
| 83 |
+
|
| 84 |
+
This is an application-layer mapping for the scope of a static-corpus docs Q&A bot. It does not replace network-level security, authentication, infrastructure hardening, formal threat modeling, or a production security review. It does not constitute OWASP certification or a coverage guarantee β only an honest, evidence-linked mapping of the guardrails this implementation actually runs.
|
agent_bench/serving/static/index.html
CHANGED
|
@@ -134,7 +134,9 @@ code{background:var(--code-bg);padding:2px 6px;border-radius:3px;font-size:0.9em
|
|
| 134 |
|
| 135 |
/* Security badges */
|
| 136 |
.security-panel{background:var(--panel-bg);border:1px solid var(--panel-border);border-radius:12px;padding:16px}
|
| 137 |
-
.security-panel h3{font-size:0.78rem;text-transform:uppercase;letter-spacing:0.04em;color:var(--muted);margin-bottom:
|
|
|
|
|
|
|
| 138 |
.security-badges{display:flex;gap:12px;flex-wrap:wrap}
|
| 139 |
.sec-badge{display:flex;flex-direction:column;gap:2px;padding:8px 12px;border-radius:8px;background:var(--code-bg);flex:1;min-width:120px}
|
| 140 |
.sec-badge .sec-label{font-size:0.75rem;color:var(--muted);font-weight:500}
|
|
@@ -310,6 +312,7 @@ code{background:var(--code-bg);padding:2px 6px;border-radius:3px;font-size:0.9em
|
|
| 310 |
|
| 311 |
<div class="security-panel">
|
| 312 |
<h3>Security</h3>
|
|
|
|
| 313 |
<div class="security-badges">
|
| 314 |
<div class="sec-badge idle" id="badgeInjection">
|
| 315 |
<span class="sec-label">Injection</span>
|
|
|
|
| 134 |
|
| 135 |
/* Security badges */
|
| 136 |
.security-panel{background:var(--panel-bg);border:1px solid var(--panel-border);border-radius:12px;padding:16px}
|
| 137 |
+
.security-panel h3{font-size:0.78rem;text-transform:uppercase;letter-spacing:0.04em;color:var(--muted);margin-bottom:4px}
|
| 138 |
+
.security-panel-sub{display:block;color:var(--muted);font-size:0.78rem;line-height:1.4;margin-bottom:10px;text-decoration:none}
|
| 139 |
+
.security-panel-sub:hover{color:var(--text);text-decoration:underline}
|
| 140 |
.security-badges{display:flex;gap:12px;flex-wrap:wrap}
|
| 141 |
.sec-badge{display:flex;flex-direction:column;gap:2px;padding:8px 12px;border-radius:8px;background:var(--code-bg);flex:1;min-width:120px}
|
| 142 |
.sec-badge .sec-label{font-size:0.75rem;color:var(--muted);font-weight:500}
|
|
|
|
| 312 |
|
| 313 |
<div class="security-panel">
|
| 314 |
<h3>Security</h3>
|
| 315 |
+
<a class="security-panel-sub" href="https://github.com/tyy0811/agent-bench/blob/main/SECURITY.md" target="_blank" aria-label="OWASP LLM Top 10 mapping in SECURITY.md">Mapped against the OWASP LLM Top 10 (2025) — named residual risks for LLM01, scope limits for LLM02 → SECURITY.md</a>
|
| 316 |
<div class="security-badges">
|
| 317 |
<div class="sec-badge idle" id="badgeInjection">
|
| 318 |
<span class="sec-label">Injection</span>
|
docs/plans/2026-04-15-owasp-llm-top-10-mapping-design.md
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# agent-bench β OWASP LLM Top 10 (2025) Mapping (Part A)
|
| 2 |
+
|
| 3 |
+
**Theme:** Honest documentation of OWASP LLM Top 10 (2025) coverage for the agent-bench implementation
|
| 4 |
+
**Estimated effort:** ~3.5 hours (plan budget 3β4 hours, hard cap 1 day)
|
| 5 |
+
**Scope:** Documentation-only. Four surfaces (SECURITY.md, DECISIONS.md entry, README tail, landing-page Security panel subtitle). Zero code changes to `agent_bench/security/*`, zero test changes, zero route changes.
|
| 6 |
+
**Plan reference:** `agent-bench v1.1 Implementation Plan` β Part A (OWASP LLM Top 10 (2025) Mapping). The 10-row mapping content and the mandatory language for LLM01/LLM02 verdict cells are locked by the plan; this design covers presentation, structure, cross-surface consistency, and execution order.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Context
|
| 11 |
+
|
| 12 |
+
The plan's Part A deliverables are fully scoped:
|
| 13 |
+
|
| 14 |
+
1. `SECURITY.md` (~500β700 words) β honest mapping of the implementation against OWASP LLM Top 10 (2025), with named residual risks for LLM01 and scope limits for LLM02.
|
| 15 |
+
2. README one-line mention.
|
| 16 |
+
3. One sentence in the landing page.
|
| 17 |
+
4. DECISIONS.md entry.
|
| 18 |
+
|
| 19 |
+
The plan also locks the 10-verdict mapping table and specifies required language for the LLM01 and LLM02 verdict cells. What remains open and what this design resolves: presentation structure, placement on each surface, DECISIONS.md entry framing, cross-surface consistency discipline, execution order, and per-surface acceptance checks.
|
| 20 |
+
|
| 21 |
+
Real security code already exists in `agent_bench/security/` (two-tier injection detector, PII redactor, output validator with always-on secret-format deny list, audit logger), plus the rate limit middleware, bounded `ToolRegistry` (only `search` + `calculator`, no side-effectful tools), and orchestrator `max_iterations` cap. The existing README Security Architecture section already documents this pipeline with a diagram and four prose blocks. The DECISIONS.md file has entries for two-tier injection, regex-PII, JSONL audit, HMAC hashing, three-validators, no-auth, and monitor-mode validation β all available as evidence targets for cross-links in SECURITY.md.
|
| 22 |
+
|
| 23 |
+
---
|
| 24 |
+
|
| 25 |
+
## Locked decisions (from brainstorming)
|
| 26 |
+
|
| 27 |
+
| # | Decision | Rationale |
|
| 28 |
+
|---|----------|-----------|
|
| 29 |
+
| 1 | SECURITY.md uses a hybrid structure: 10-row Γ 2-column summary table at top (plain-text verdicts, LLM01/LLM02 rows carry their qualifier inline) + 8 H3 subsections under a single "Detailed mapping" H2 in LLM-numeric order. Out-of-scope LLM04/LLM08 live in the summary table only. | Dense 10Γ4 table fights the 500β700 word budget and makes mandatory LLM01/LLM02 language illegible in narrow cells. Per-category-subsection structure alone loses the scan-view that makes the 6+2+2=10 arithmetic verifiable at a glance. Hybrid gets both. |
|
| 30 |
+
| 2 | Uniform 3-part skeleton inside each H3: **Verdict** (one line) β **Implementation** (one paragraph with inline cross-links to `agent_bench/security/*.py` and DECISIONS.md anchors) β **Residual risk / Scope limit / Named gap** (one line, mandatory verbatim language for LLM01/LLM02). | Reader's eye learns the pattern by the second subsection; scanning becomes free. Inconsistency reads as compliance theater, which is the failure mode the brand is built to avoid. |
|
| 31 |
+
| 3 | Inline cross-links to source files and DECISIONS.md anchors in every Implementation cell. | Highest-leverage move for the "evidence not checklist" brand. A reviewer with the repo open should be able to click from claim to code in one hop. |
|
| 32 |
+
| 4 | Plain-text verdicts in the summary table (no badges, no checkmarks, no emoji). | "Addressed directly" / "Infrastructure layer" / "Out of scope" reads honest. Symbols would compress nicely but risk reading as security-badge theater, which the preamble disclaims. |
|
| 33 |
+
| 5 | Architecture-scope + OWASP Appendix 1 callout lives in the 1β2 paragraph preamble, not the summary table. Preamble references the README Security Architecture diagram rather than duplicating it. | Keeps the table clean for the 10Γstructure. Single source of truth for the architecture diagram (README), DECISIONS.md/SECURITY.md point at it. |
|
| 34 |
+
| 6 | LLM-numeric subsection ordering (LLM01 β LLM02 β LLM03 β LLM05 β LLM06 β LLM07 β LLM09 β LLM10), not strongest-first. | OWASP itself orders LLM01βLLM10; a reader cross-checking against the 2025 PDF expects this order. Strongest-first is exactly the framing compliance-theater docs adopt ("look at our best work first"), which the brand disclaims. |
|
| 35 |
+
| 7 | Single "Detailed mapping" H2 carrying all 8 subsections (not two H2s split by "Addressed directly" vs "Addressed at infrastructure layer"). | Two-H2 split implies the categorization is structurally important to the doc, but the actual categorization is carried by the verdict line inside each subsection and the summary table. Two H2s do redundant categorization at the cost of fragmenting the reader's scan. |
|
| 36 |
+
| 8 | DECISIONS.md entry framing: "Why named residual risks and scope limits, not 'fully mitigated' verdicts?" with explicit rejected-alternative naming in paragraph 1. | House-style paired thinking. Names the most interesting design choice (the discipline of refusing to say "mitigated" without a qualifier) and forces the body to cite OWASP's own language as the source of the discipline. |
|
| 37 |
+
| 9 | Responsibility split: SECURITY.md is the **what** (verdict cells); DECISIONS.md is the **why** (verdict discipline). One-way cross-link from DECISIONS.md β SECURITY.md with explicit split phrasing ("this entry covers why the verdict discipline takes the form it does"). | SECURITY.md = artifact reviewers read to evaluate the implementation; DECISIONS.md = artifact reviewers read to evaluate the thinking. Duplicating the 10-row mapping across both forces two sources of truth; first drift erodes the brand. One-way pointer goes the direction the why depends on the what but not vice versa. |
|
| 38 |
+
| 10 | README one-liner: joined into the existing Security Architecture section closing tail. Two drafting-time variants (single-sentence comma-list vs two-sentence equal-billing); selection by rhythm check at drafting time. | Preserves the existing four-item DECISIONS.md list (two-tier / regex-PII / JSONL / HMAC) which is load-bearing evidence that DECISIONS.md is substantive. Engineering Scope placement rejected because those bullets carry build artifacts, not documentation about build artifacts β miscategorization dilutes what the section is for. |
|
| 39 |
+
| 11 | Landing page one sentence: sub-text under `<h3>Security</h3>` in the dashboard right panel. Block-link (`<a>` wrapping the whole subtitle) with `aria-label="OWASP LLM Top 10 mapping in SECURITY.md"`, absolute GitHub URL, em-dash variant for mobile-wrap readability. | Only placement where the OWASP mention sits next to the guardrails it documents. New Finding card rejected (frame mismatch: Findings is "what the benchmark revealed about retrieval/orchestration," compliance mapping isn't that, plus rΓ©sumΓ©-theater failure mode on a recruiter-facing surface). Footer rejected (too quiet for an artifact a skim-reader should register). |
|
| 40 |
+
| 12 | LLM01 Implementation paragraph phrasing: "Deployments without GPU run Tier 1 only; the two-tier design degrades to heuristic-only rather than failing closed" (replaces weaker "by design" language). | Names the degradation behavior explicitly and uses "fails closed" security vocabulary that signals knowledge of the alternative posture. |
|
| 41 |
+
| 13 | Preamble scope-closing line: "These scope facts are referenced in the verdict cells below where they constrain what each guardrail must do." | Telegraphs that the scope statement ("no user ingestion, no fine-tuning, no authenticated sessions, no side-effectful tools") isn't decorative β it's the premise the verdicts depend on. Makes the doc read more like an argument and less like a checklist. |
|
| 42 |
+
|
| 43 |
+
---
|
| 44 |
+
|
| 45 |
+
## SECURITY.md structure (Section 1 of the design)
|
| 46 |
+
|
| 47 |
+
**Location:** top-level `/SECURITY.md` (GitHub auto-surfaces in the "Security" tab).
|
| 48 |
+
|
| 49 |
+
**Word budget:** 500β700 words, target ~620.
|
| 50 |
+
|
| 51 |
+
**Layout:**
|
| 52 |
+
|
| 53 |
+
```
|
| 54 |
+
# Security
|
| 55 |
+
|
| 56 |
+
<Preamble: 1 paragraph, ~60 words β honest mapping, scope statement>
|
| 57 |
+
<Architecture scope: 1 paragraph, ~70 words β OWASP Appendix 1 reference, pointer to README diagram, scope-closing line>
|
| 58 |
+
|
| 59 |
+
## Mapping summary
|
| 60 |
+
<10-row Γ 2-column table: Category | Verdict>
|
| 61 |
+
|
| 62 |
+
## Detailed mapping
|
| 63 |
+
### LLM01 Prompt Injection (3-part skeleton)
|
| 64 |
+
### LLM02 Sensitive Information Disclosure (3-part skeleton)
|
| 65 |
+
### LLM03 Supply Chain (3-part skeleton)
|
| 66 |
+
### LLM05 Improper Output Handling (3-part skeleton)
|
| 67 |
+
### LLM06 Excessive Agency (3-part skeleton)
|
| 68 |
+
### LLM07 System Prompt Leakage (3-part skeleton)
|
| 69 |
+
### LLM09 Misinformation (3-part skeleton)
|
| 70 |
+
### LLM10 Unbounded Consumption (3-part skeleton)
|
| 71 |
+
|
| 72 |
+
## What this doc is not
|
| 73 |
+
<1 paragraph, ~50 words>
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
LLM04 and LLM08 are intentionally absent from the "Detailed mapping" H2 β they are "Out of scope" verdicts whose one-phrase treatment in the summary table is the entire treatment. Adding subsections for them would be compliance-theater padding.
|
| 77 |
+
|
| 78 |
+
### Preamble (structural draft; exact wording at drafting time)
|
| 79 |
+
|
| 80 |
+
> This document maps the agent-bench implementation against the OWASP LLM Applications Top 10 (2025). It is an honest mapping, not a coverage claim β every verdict cell for a guardrail we run carries a named residual risk or scope limit when OWASP's own 2025 text makes the limits explicit. The scope is a docs Q&A bot serving static curated corpora (FastAPI + Kubernetes) with no user ingestion, no fine-tuning, no authenticated sessions, and no side-effectful tools.
|
| 81 |
+
|
| 82 |
+
### Architecture-scope paragraph (structural draft)
|
| 83 |
+
|
| 84 |
+
> The implementation maps to the OWASP Appendix 1 reference architecture: user input β input guardrails β retrieval/tools β LLM β output guardrails β response. The agent-bench realization is diagrammed in [README Security Architecture section](README.md#security-architecture) β this document does not duplicate the diagram, and the verdict cells below cross-link to the specific source files that implement each guardrail. These scope facts are referenced in the verdict cells below where they constrain what each guardrail must do.
|
| 85 |
+
|
| 86 |
+
### Summary table (canonical; plain-text verdicts; LLM01/LLM02 qualifiers inline)
|
| 87 |
+
|
| 88 |
+
| Category | Verdict |
|
| 89 |
+
|---|---|
|
| 90 |
+
| LLM01 Prompt Injection | Addressed directly, named residual risk |
|
| 91 |
+
| LLM02 Sensitive Information Disclosure | Addressed directly, scope limit |
|
| 92 |
+
| LLM03 Supply Chain | Infrastructure layer, named gap |
|
| 93 |
+
| LLM04 Data and Model Poisoning | Out of scope |
|
| 94 |
+
| LLM05 Improper Output Handling | Addressed directly |
|
| 95 |
+
| LLM06 Excessive Agency | Addressed directly |
|
| 96 |
+
| LLM07 System Prompt Leakage | Addressed directly |
|
| 97 |
+
| LLM08 Vector and Embedding Weaknesses | Out of scope |
|
| 98 |
+
| LLM09 Misinformation | Addressed directly |
|
| 99 |
+
| LLM10 Unbounded Consumption | Infrastructure layer, named gap |
|
| 100 |
+
|
| 101 |
+
**Counts (verifiable in one eye-scan):** 6 addressed directly + 2 infrastructure layer + 2 out of scope = **10**.
|
| 102 |
+
|
| 103 |
+
### H3 subsection skeleton
|
| 104 |
+
|
| 105 |
+
**2-part mandatory** (Verdict + Implementation) for every subsection. **3rd part mandatory** for the four categories where OWASP 2025 or the implementation scope makes an explicit limit (LLM01, LLM02, LLM03, LLM10). No invented 3rd part for LLM05, LLM06, LLM07, LLM09 β the honest-evaluation brand requires that we don't manufacture residual risks where OWASP's own text doesn't articulate them.
|
| 106 |
+
|
| 107 |
+
```
|
| 108 |
+
### LLMxx <Name>
|
| 109 |
+
|
| 110 |
+
**Verdict:** <one line>.
|
| 111 |
+
|
| 112 |
+
**Implementation:** <one paragraph with inline cross-links to source and DECISIONS.md>.
|
| 113 |
+
|
| 114 |
+
[3rd line β REQUIRED for LLM01, LLM02, LLM03, LLM10;
|
| 115 |
+
PROHIBITED for LLM05, LLM06, LLM07, LLM09
|
| 116 |
+
(do not invent residual risks for structural symmetry)]
|
| 117 |
+
**Residual risk:** <one line>. β LLM01 (OWASP says RAG does not fully mitigate)
|
| 118 |
+
**Scope limit:** <one line>. β LLM02 (narrower output-side scope than LLM02's four classes)
|
| 119 |
+
**Named gap:** <one line>. β LLM03, LLM10 (infrastructure layer, specific missing pieces)
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
LLM05, LLM06, LLM07, LLM09 use the 2-part skeleton only. Their verdicts read plain "Addressed directly" and their implementation paragraphs are the whole treatment β the guardrail is complete for the stated scope, and adding a synthetic "no residual risk" line would be reverse-compliance-theater (padding in the opposite direction).
|
| 123 |
+
|
| 124 |
+
### LLM01 Prompt Injection β canonical subsection (exact text)
|
| 125 |
+
|
| 126 |
+
> **Verdict:** Addressed directly with a named residual risk.
|
| 127 |
+
>
|
| 128 |
+
> **Implementation:** Two-tier detection β Tier 1 heuristic regex (local, <1ms) with ~20 pattern families covering role/identity hijacking, instruction override, system-prompt extraction, credential/env-var extraction, jailbreak keywords, and base64-nested payloads; Tier 2 optional DeBERTa classifier on Modal GPU for high-confidence arbitration. Deployments without GPU run Tier 1 only; the two-tier design degrades to heuristic-only rather than failing closed. Grounded refusal via the retrieval-threshold gate bounds indirect injection through retrieved content. Static corpus + bounded `ToolRegistry` (only `search` + `calculator`, no side-effectful tools) + `max_iterations` cap bound the blast radius. See [`agent_bench/security/injection_detector.py`](agent_bench/security/injection_detector.py), [`agent_bench/tools/registry.py`](agent_bench/tools/registry.py), and [DECISIONS.md Β§ Why two-tier injection detection, not three](DECISIONS.md#why-two-tier-injection-detection-not-three).
|
| 129 |
+
>
|
| 130 |
+
> **Residual risk:** novel injection patterns not caught by heuristics or classifier. OWASP notes that RAG and fine-tuning do not fully mitigate prompt injection; indirect injection through retrieved content remains a core risk class.
|
| 131 |
+
|
| 132 |
+
### LLM02 Sensitive Information Disclosure β canonical subsection (exact text)
|
| 133 |
+
|
| 134 |
+
> **Verdict:** Addressed directly for the applicable scope.
|
| 135 |
+
>
|
| 136 |
+
> **Implementation:** Regex PII redaction on every retrieved chunk before it enters the LLM context window (EMAIL, SSN, CREDIT_CARD, PHONE, IP_ADDRESS) with optional spaCy NER for PERSON/ORG; post-generation output validation with an always-on secret-format deny list (OpenAI/Anthropic/Google/AWS/GitHub key prefixes, bearer tokens, env-var assignments) and URL-against-retrieved-chunks check. See [`agent_bench/security/pii_redactor.py`](agent_bench/security/pii_redactor.py), [`agent_bench/security/output_validator.py`](agent_bench/security/output_validator.py), and [DECISIONS.md Β§ Why regex + optional spaCy for PII, not a cloud API](DECISIONS.md#why-regex--optional-spacy-for-pii-not-a-cloud-api).
|
| 137 |
+
>
|
| 138 |
+
> **Scope limit:** OWASP LLM02 spans access controls, training-data handling, user-consent transparency, and proprietary-information governance. This implementation addresses only response-time data surfaced to users β a narrower, output-side subset that does not cleanly map to any single one of the four; broader concerns would require architectural changes for multi-tenant or authenticated deployment.
|
| 139 |
+
|
| 140 |
+
### Remaining 6 subsections (content outline; drafted at implementation time)
|
| 141 |
+
|
| 142 |
+
- **LLM03 Supply Chain** β Infrastructure layer. Implementation: pinned deps (`pyproject.toml`), reproducible `Dockerfile`, official model sources (HuggingFace, Modal base images). **Named gap:** no formal SBOM, no signed model provenance.
|
| 143 |
+
- **LLM05 Improper Output Handling** β Addressed directly. Implementation: `OutputValidator` (PII leakage + secret deny-list + URL-against-retrieved + configurable blocklist); text-only output surface; no rendered HTML, no SQL generation, no executed code. Cross-link to `output_validator.py` and [DECISIONS.md Β§ Why three output validators, not four](DECISIONS.md#why-three-output-validators-not-four).
|
| 144 |
+
- **LLM06 Excessive Agency** β Addressed directly. Implementation: `max_iterations` cap enforced in orchestrator; `ToolRegistry` contains only `search` and `calculator`; no side-effectful tools (no write, no network requests outside the corpus, no code execution). Cross-link to `agent_bench/tools/registry.py` and `agent_bench/agents/orchestrator.py`.
|
| 145 |
+
- **LLM07 System Prompt Leakage** β Addressed directly. Implementation: system prompt contains no credentials, no auth tokens, no multi-tenant role structure; access control enforced outside the LLM (rate limit middleware + future auth at infrastructure layer). Matches OWASP's own recommended pattern. Cross-link to [DECISIONS.md Β§ Why no authentication on API endpoints](DECISIONS.md#why-no-authentication-on-api-endpoints).
|
| 146 |
+
- **LLM09 Misinformation** β Addressed directly. Implementation: grounded refusal via RRF retrieval-threshold gate β the project's core thesis and the source of the citation-accuracy=1.00 on API providers hero-tile number. Cross-link to [DECISIONS.md Β§ Why a relevance threshold for grounded refusal](DECISIONS.md#why-a-relevance-threshold-for-grounded-refusal).
|
| 147 |
+
- **LLM10 Unbounded Consumption** β Infrastructure layer. Implementation: per-IP sliding-window rate limit (`RateLimitMiddleware`, 10 RPM default), `max_iterations` cap, provider timeouts via `ProviderTimeoutError` handling. **Named gap:** per-IP rate limit, not per-user cost ceiling; no budget cap per session.
|
| 148 |
+
|
| 149 |
+
### "What this doc is not" closing block (structural draft)
|
| 150 |
+
|
| 151 |
+
> This is an application-layer mapping for the scope of a static-corpus docs Q&A bot. It does not replace network-level security, authentication, infrastructure hardening, formal threat modeling, or a production security review. It does not constitute OWASP certification or a coverage guarantee β only an honest, evidence-linked mapping of the guardrails this implementation actually runs.
|
| 152 |
+
|
| 153 |
+
---
|
| 154 |
+
|
| 155 |
+
## Derived surfaces (Section 2 of the design)
|
| 156 |
+
|
| 157 |
+
All three surfaces are downstream of SECURITY.md canonical phrasing. Draft them in the execution order below.
|
| 158 |
+
|
| 159 |
+
### DECISIONS.md entry (~320 words, house style)
|
| 160 |
+
|
| 161 |
+
**Placement:** insert between "Why monitor mode for output validation, not gating?" (~line 334) and "Why additive SSE stage events?" (~line 336). Topical clustering with the other security-phase entries; not chronological file-end append.
|
| 162 |
+
|
| 163 |
+
**Header:** `## Why named residual risks and scope limits, not "fully mitigated" verdicts?`
|
| 164 |
+
|
| 165 |
+
**Body (structural draft; tighten paragraphs 2 and 3 by collapsing OWASP-restatement into verdict-explanation, target ~320 words total):**
|
| 166 |
+
|
| 167 |
+
Paragraph 1 β Named rejected alternative: the 10-row table could have been written as uniform "addressed" verdicts without qualifiers, arithmetically identical (same 6+2+2=10). Rejected because OWASP's own 2025 text is explicit about what an input guardrail can and cannot do, and writing a verdict that contradicts the source the mapping cites would be compliance theater.
|
| 168 |
+
|
| 169 |
+
Paragraph 2 β LLM01: OWASP 2025 states that RAG and fine-tuning do not fully mitigate prompt injection, and that indirect injection through retrieved content remains a core risk class. "Fully mitigated" is unsupportable for any system retrieving untrusted content into an LLM context window. Verdict reads "addressed directly with named residual risk"; residual-risk cell cites OWASP's own "do not fully mitigate" language verbatim.
|
| 170 |
+
|
| 171 |
+
Paragraph 3 β LLM02: OWASP 2025 defines LLM02 as spanning four concern classes β access controls, training-data handling, user-consent transparency, and proprietary-information governance. This implementation addresses a narrower output-side subset (regex PII redaction on retrieved chunks + output validation for PII leakage, secret formats, and URL hallucination) β not cleanly one of the four concern classes, but a narrower scope than any of them. Verdict reads "addressed directly for the applicable scope"; scope-limit cell enumerates the four concern classes verbatim and names what addressing the broader concerns would require (multi-tenant or authenticated architecture).
|
| 172 |
+
|
| 173 |
+
Paragraph 4 β Thesis (load-bearing, do not cut): the tension the entry resolves is honesty-vs-scannability. A mapping surfacing named residual risks and scope limits is longer and harder to skim than one with uniform "addressed" verdicts, but the scannable version over-claims relative to the cited source. Honest evaluation is the brand. Every verdict cell in SECURITY.md must survive a reviewer reading OWASP 2025 in a second tab.
|
| 174 |
+
|
| 175 |
+
Closing β Cross-link with explicit responsibility-split phrasing: "See [SECURITY.md Β§ LLM01](SECURITY.md#llm01-prompt-injection) and [Β§ LLM02](SECURITY.md#llm02-sensitive-information-disclosure) for the verdict cells; this entry covers why the verdict discipline takes the form it does." Plus canonical-phrasing-discipline closing: the LLM01 "do not fully mitigate" phrasing and the LLM02 four-concern-class enumeration are canonical in SECURITY.md; the README tail link and the landing-page Security panel subtitle paraphrase this language but must preserve the named-residual-risk and scope-limit structure. A future edit that drifts one surface without the others creates a brand inconsistency visible in cross-surface diff.
|
| 176 |
+
|
| 177 |
+
**Framing discipline:** this entry uses **failure-mode framing** ("URL hallucination", "PII leakage") β describes what the guardrail prevents. SECURITY.md uses **mechanism framing** ("URL-against-retrieved-chunks check", "PIIRedactor on every retrieved chunk") β describes what the guardrail runs. The split is deliberate: SECURITY.md documents behavior, DECISIONS.md documents intent. A future edit must not swap the framings across surfaces.
|
| 178 |
+
|
| 179 |
+
### README one-liner (two variants; rhythm check at drafting time)
|
| 180 |
+
|
| 181 |
+
**Placement:** joined into the Security Architecture section closing tail (README.md line ~202), preserving the existing four-item DECISIONS.md list. No change to any other README section.
|
| 182 |
+
|
| 183 |
+
**Variant 1 (single sentence, comma-list tail):**
|
| 184 |
+
> See [SECURITY.md](SECURITY.md) for the OWASP LLM Top 10 (2025) mapping, and [DECISIONS.md](DECISIONS.md) for why we chose two-tier detection over three, regex-only PII by default, JSONL over SQLite for audit, and HMAC over plain SHA-256 for IP hashing.
|
| 185 |
+
|
| 186 |
+
**Variant 2 (two sentences, equal billing):**
|
| 187 |
+
> See [SECURITY.md](SECURITY.md) for the OWASP LLM Top 10 (2025) mapping. See [DECISIONS.md](DECISIONS.md) for why we chose two-tier detection over three, regex-only PII by default, JSONL over SQLite for audit, and HMAC over plain SHA-256 for IP hashing.
|
| 188 |
+
|
| 189 |
+
**Selection rule:** paste both into README in actual surrounding context at drafting time, pick on rhythm. Variant 2 wins if the section's closing cadence feels heavy; Variant 1 wins if the closing cadence is tight. Plan-wording compliance ("one-line mention") satisfied under either reading.
|
| 190 |
+
|
| 191 |
+
### Landing page Security panel subtitle
|
| 192 |
+
|
| 193 |
+
**HTML change** β insert one `<a>` element between `<h3>Security</h3>` and `<div class="security-badges">` in `agent_bench/serving/static/index.html` (~line 311):
|
| 194 |
+
|
| 195 |
+
```html
|
| 196 |
+
<div class="security-panel">
|
| 197 |
+
<h3>Security</h3>
|
| 198 |
+
<a class="security-panel-sub"
|
| 199 |
+
href="https://github.com/tyy0811/agent-bench/blob/main/SECURITY.md"
|
| 200 |
+
target="_blank"
|
| 201 |
+
aria-label="OWASP LLM Top 10 mapping in SECURITY.md">
|
| 202 |
+
Mapped against the OWASP LLM Top 10 (2025) — named residual risks for LLM01, scope limits for LLM02 → SECURITY.md
|
| 203 |
+
</a>
|
| 204 |
+
<div class="security-badges">
|
| 205 |
+
...
|
| 206 |
+
</div>
|
| 207 |
+
</div>
|
| 208 |
+
```
|
| 209 |
+
|
| 210 |
+
**Block-link rationale:** larger click target, accessible, the whole subtitle semantically *is* the link. `aria-label` gives screen-reader users the same one-line semantic ("this is a link to the OWASP mapping doc") that sighted readers get β otherwise screen readers parse the em-dash and arrow awkwardly.
|
| 211 |
+
|
| 212 |
+
**Absolute GitHub URL rationale:** on HF Spaces, relative `SECURITY.md` would not resolve (FastAPI app only serves `index.html` + static assets). Matches the existing finding-card link pattern. Per the URL-reference-window memory, no rename of GitHub or HF URLs is planned during the application-reference window, so the absolute link is stable.
|
| 213 |
+
|
| 214 |
+
**CSS addition** β new rule, new class (`.security-panel-sub`) to avoid colliding with the existing `.sec-sub` class (which is already scoped inside `.sec-badge`):
|
| 215 |
+
|
| 216 |
+
```css
|
| 217 |
+
.security-panel-sub {
|
| 218 |
+
display: block;
|
| 219 |
+
color: var(--muted);
|
| 220 |
+
font-size: 0.78rem;
|
| 221 |
+
line-height: 1.4;
|
| 222 |
+
margin-top: -4px;
|
| 223 |
+
margin-bottom: 10px;
|
| 224 |
+
text-decoration: none;
|
| 225 |
+
}
|
| 226 |
+
.security-panel-sub:hover {
|
| 227 |
+
color: var(--text);
|
| 228 |
+
text-decoration: underline;
|
| 229 |
+
}
|
| 230 |
+
```
|
| 231 |
+
|
| 232 |
+
**Drafting-time check on `margin-top: -4px`:** negative margin is brittle under h3 font-size changes. Prefer adjusting h3 bottom-margin explicitly if the negative margin reads wrong in the actual rendered panel. Not a blocker; verify during drafting against the actual rendered panel.
|
| 233 |
+
|
| 234 |
+
**Mobile wrap check:** at 375px viewport width with `0.78rem` font size, the em-dash variant wraps cleaner than the comma-list version because the dash gives the parser a natural break point. Verify via browser devtools responsive mode during drafting.
|
| 235 |
+
|
| 236 |
+
---
|
| 237 |
+
|
| 238 |
+
## Canonical-phrasing discipline (Section 3.1)
|
| 239 |
+
|
| 240 |
+
One cross-surface constraint, enforced at pre-commit verification:
|
| 241 |
+
|
| 242 |
+
| Language fragment | Canonical in | Paraphrased in | Audit check |
|
| 243 |
+
|---|---|---|---|
|
| 244 |
+
| OWASP "do not fully mitigate" verbatim | SECURITY.md LLM01 Residual-risk cell | DECISIONS.md entry paragraph 2 (quoted) | Exact-string grep during pre-merge verification |
|
| 245 |
+
| LLM02 four-concern-class enumeration (access controls / training-data handling / user-consent transparency / proprietary-information governance) | SECURITY.md LLM02 Scope-limit cell | DECISIONS.md entry paragraph 3 (listed) | Exact list membership, same four items |
|
| 246 |
+
| "Named residual risks for LLM01, scope limits for LLM02" structural phrase | Landing-page Security panel subtitle | β (README tail is a pointer surface β refers readers to SECURITY.md but does not itself carry the structural phrase, so it is not audited for this phrase) | Structural grep: both "named residual risk" and "scope limit" appear in the landing-page subtitle |
|
| 247 |
+
| Mechanism framing (what runs) vs failure-mode framing (what it prevents) | SECURITY.md (mechanism) / DECISIONS.md (failure-mode) | β | Framings must not swap |
|
| 248 |
+
|
| 249 |
+
A drift β e.g., SECURITY.md's LLM01 cell losing the verbatim "do not fully mitigate" phrase, or DECISIONS.md's paragraph 3 dropping one of the four concern classes β is detectable in one diff pass.
|
| 250 |
+
|
| 251 |
+
---
|
| 252 |
+
|
| 253 |
+
## Execution order (Section 3.2)
|
| 254 |
+
|
| 255 |
+
**Strict order.** Each downstream surface paraphrases upstream phrasing; writing out of order risks pinning a downstream surface to a draft that never lands.
|
| 256 |
+
|
| 257 |
+
```
|
| 258 |
+
Step 1 βββΆ SECURITY.md (canonical source of all mandatory language)
|
| 259 |
+
Step 2 βββΆ DECISIONS.md entry (derives cross-link targets from SECURITY.md anchors)
|
| 260 |
+
Step 3 βββΆ README tail (derives the "OWASP LLM Top 10 (2025) mapping" pointer from SECURITY.md preamble)
|
| 261 |
+
Step 4 βββΆ Landing-page Security panel subtitle (derives the structural phrase from SECURITY.md verdict cells)
|
| 262 |
+
Step 5 βββΆ Cross-surface verification (canonical-phrasing table, per-surface acceptance, plan compliance)
|
| 263 |
+
```
|
| 264 |
+
|
| 265 |
+
**Time budget (3.5 hours inside plan's 3β4 hour soft budget; hard cap 1 day):**
|
| 266 |
+
|
| 267 |
+
| Step | Est. time | Notes |
|
| 268 |
+
|---|---|---|
|
| 269 |
+
| 1. SECURITY.md (~620 words, 8 subsections) | 90 min | Most writing happens here; LLM01/LLM02 cells take longest because exact language is prescribed |
|
| 270 |
+
| 2. DECISIONS.md entry (~320 words, tightened) | 45 min | Draft at ~390 words, trim paragraphs 2 and 3 by collapsing OWASP-restatement into verdict-explanation, lock paragraph 4 intact |
|
| 271 |
+
| 3. README tail (two variants, rhythm check) | 15 min | Both variants pasted in context, rhythm check, pick one |
|
| 272 |
+
| 4. Landing page HTML + CSS (one element + one rule) | 30 min | Insertion + CSS; start dev server and verify rendered subtitle at desktop + 375px viewport; aria-label manual check via browser devtools |
|
| 273 |
+
| 5. Cross-surface verification | 30 min | Canonical-phrasing table, per-surface acceptance, `make test` + `make lint`, commit |
|
| 274 |
+
| **Total** | **~3.5 hours** | Inside the 3β4 hour soft budget; 30-min slack pools at the end of Step 5 to absorb drafting-time surprises without touching the hard cap |
|
| 275 |
+
|
| 276 |
+
Slack is pooled, not spread across steps, because drafting-time surprises tend to cluster (one surface goes long, the others go normally).
|
| 277 |
+
|
| 278 |
+
---
|
| 279 |
+
|
| 280 |
+
## Per-surface acceptance checks (Section 3.3)
|
| 281 |
+
|
| 282 |
+
Exhaustive checks; cheap to run, load-bearing when missed.
|
| 283 |
+
|
| 284 |
+
### SECURITY.md
|
| 285 |
+
|
| 286 |
+
- [ ] Word count between 500 and 700
|
| 287 |
+
- [ ] 10-row summary table with 6 + 2 + 2 = 10 arithmetic visible in a single scan
|
| 288 |
+
- [ ] Summary table LLM01 row reads "Addressed directly, named residual risk"
|
| 289 |
+
- [ ] Summary table LLM02 row reads "Addressed directly, scope limit"
|
| 290 |
+
- [ ] Single "Detailed mapping" H2 contains exactly 8 subsections β LLM01, LLM02, LLM03, LLM05, LLM06, LLM07, LLM09, LLM10 β in that order. LLM04 and LLM08 (out of scope) are intentionally absent from this section; a future reader should recognize this as design intent, not omission.
|
| 291 |
+
- [ ] Each subsection follows the skeleton: 2-part mandatory (Verdict + Implementation) for all 8; 3rd part mandatory for LLM01 (Residual risk), LLM02 (Scope limit), LLM03 (Named gap), LLM10 (Named gap); LLM05/LLM06/LLM07/LLM09 use 2-part only with no synthetic 3rd line
|
| 292 |
+
- [ ] LLM01 subsection contains the exact phrase: "OWASP notes that RAG and fine-tuning do not fully mitigate prompt injection; indirect injection through retrieved content remains a core risk class"
|
| 293 |
+
- [ ] LLM01 subsection contains: "Deployments without GPU run Tier 1 only; the two-tier design degrades to heuristic-only rather than failing closed"
|
| 294 |
+
- [ ] LLM02 subsection enumerates all four OWASP LLM02 concern classes (access controls, training-data handling, user-consent transparency, proprietary-information governance)
|
| 295 |
+
- [ ] All 8 subsections contain at least one cross-link to `agent_bench/security/*.py`, `agent_bench/tools/registry.py`, or `agent_bench/agents/orchestrator.py`
|
| 296 |
+
- [ ] At least 5 subsections contain a DECISIONS.md anchor cross-link
|
| 297 |
+
- [ ] Preamble contains the scope statement ("no user ingestion, no fine-tuning, no authenticated sessions, no side-effectful tools") plus the closing line ("These scope facts are referenced in the verdict cells below where they constrain what each guardrail must do")
|
| 298 |
+
- [ ] Preamble references README Security Architecture diagram without duplicating it
|
| 299 |
+
- [ ] "What this doc is not" closing block present, ~50 words
|
| 300 |
+
- [ ] No occurrence of "fully mitigated", "coverage guarantee", "tests all 10", "OWASP-certified", or any other overclaiming phrase (per cross-cutting requirement #3 of the plan)
|
| 301 |
+
|
| 302 |
+
### DECISIONS.md entry
|
| 303 |
+
|
| 304 |
+
- [ ] Inserted between "Why monitor mode for output validation, not gating?" and "Why additive SSE stage events?"
|
| 305 |
+
- [ ] Header exactly: `## Why named residual risks and scope limits, not "fully mitigated" verdicts?`
|
| 306 |
+
- [ ] Word count ~320 Β± 30
|
| 307 |
+
- [ ] Paragraph 1 names the rejected alternative (uniform "addressed" verdicts)
|
| 308 |
+
- [ ] Paragraph 2 quotes OWASP "do not fully mitigate" language (not paraphrased)
|
| 309 |
+
- [ ] Paragraph 3 enumerates all four LLM02 concern classes (same four as SECURITY.md canonical)
|
| 310 |
+
- [ ] Paragraph 4 (thesis) intact β honesty-vs-scannability tension + "honest evaluation is the brand"
|
| 311 |
+
- [ ] Cross-link to `SECURITY.md Β§ LLM01` and `Β§ LLM02` with explicit responsibility-split phrasing ("this entry covers why the verdict discipline takes the form it does")
|
| 312 |
+
- [ ] Closing lines name the canonical-phrasing discipline and the cross-surface-diff detection mechanism
|
| 313 |
+
- [ ] Uses failure-mode framing ("URL hallucination", "PII leakage"), not mechanism framing
|
| 314 |
+
|
| 315 |
+
### README tail
|
| 316 |
+
|
| 317 |
+
- [ ] One of the two variants selected by rhythm check at drafting time
|
| 318 |
+
- [ ] Preserves the existing four-item DECISIONS.md list (two-tier / regex-PII / JSONL / HMAC) intact
|
| 319 |
+
- [ ] Link uses relative `SECURITY.md` (README renders on GitHub; relative resolves)
|
| 320 |
+
- [ ] No change to any other section of README β no new bullet in Engineering Scope, no new row in V1βV2βV3 table, no tagline edit
|
| 321 |
+
|
| 322 |
+
### Landing page subtitle
|
| 323 |
+
|
| 324 |
+
- [ ] One `<a class="security-panel-sub">` element inserted between `<h3>Security</h3>` and `<div class="security-badges">`
|
| 325 |
+
- [ ] Element is a block-link (whole subtitle wrapped in `<a>`)
|
| 326 |
+
- [ ] `href` is the absolute GitHub URL: `https://github.com/tyy0811/agent-bench/blob/main/SECURITY.md`
|
| 327 |
+
- [ ] `aria-label="OWASP LLM Top 10 mapping in SECURITY.md"` present
|
| 328 |
+
- [ ] `target="_blank"` present (matches existing finding-card link convention)
|
| 329 |
+
- [ ] Subtitle text uses em-dash variant: "Mapped against the OWASP LLM Top 10 (2025) β named residual risks for LLM01, scope limits for LLM02 β SECURITY.md"
|
| 330 |
+
- [ ] New `.security-panel-sub` CSS class added (not the existing `.sec-sub` which is scoped to badges)
|
| 331 |
+
- [ ] Dev server started (`make serve`); subtitle verified to render at desktop (1440px) + narrow (375px) viewport widths
|
| 332 |
+
- [ ] Wrap behavior at 375px verified clean (em-dash gives the natural break; no mid-phrase wraps)
|
| 333 |
+
- [ ] `margin-top: -4px` drafting-time check: prefer explicit h3 bottom-margin adjustment if the negative margin reads brittle in the actual rendered panel
|
| 334 |
+
- [ ] No change to any other element β Security badges panel, pipeline stages, retrieval panel, chat panel, findings cards all untouched
|
| 335 |
+
|
| 336 |
+
---
|
| 337 |
+
|
| 338 |
+
## Whole-task pre-commit verification (Section 3.4)
|
| 339 |
+
|
| 340 |
+
```
|
| 341 |
+
a. Read all four surfaces end-to-end in one sitting, in execution order.
|
| 342 |
+
b. Run the canonical-phrasing audit table (Β§ Canonical-phrasing discipline) β
|
| 343 |
+
each row is a grep or exact-string check.
|
| 344 |
+
c. Verify cross-link targets resolve:
|
| 345 |
+
- SECURITY.md anchors exist (LLM01 Prompt Injection, LLM02 Sensitive
|
| 346 |
+
Information Disclosure, etc. β GitHub anchor-slugs match)
|
| 347 |
+
- Source file cross-links point to existing files (agent_bench/security/
|
| 348 |
+
injection_detector.py, agent_bench/security/pii_redactor.py,
|
| 349 |
+
agent_bench/security/output_validator.py, agent_bench/tools/registry.py,
|
| 350 |
+
agent_bench/agents/orchestrator.py)
|
| 351 |
+
- DECISIONS.md anchor cross-links (two-tier injection, regex+spaCy, no
|
| 352 |
+
authentication, three output validators, relevance threshold) β existing
|
| 353 |
+
entries, anchor-slugs match
|
| 354 |
+
d. Run `make test` β confirm no regressions. No test changes expected;
|
| 355 |
+
the landing-page HTML change is additive and the existing static-route
|
| 356 |
+
tests assert on content presence, not structure.
|
| 357 |
+
e. Run `make lint` β confirm no new ruff/mypy issues (no Python changes,
|
| 358 |
+
so this is a consistency check, not a content check).
|
| 359 |
+
f. Start `make serve`, open localhost:8000 in a browser, verify the
|
| 360 |
+
subtitle renders as a muted sub-heading, clicking it opens the GitHub
|
| 361 |
+
SECURITY.md in a new tab. Test narrow viewport via browser devtools
|
| 362 |
+
responsive mode at 375px width.
|
| 363 |
+
g. Final brand-check: no overclaiming phrases anywhere across the four
|
| 364 |
+
surfaces.
|
| 365 |
+
```
|
| 366 |
+
|
| 367 |
+
---
|
| 368 |
+
|
| 369 |
+
## Paired-review gate (cross-cutting #9)
|
| 370 |
+
|
| 371 |
+
**Trigger:** the Part A DECISIONS.md entry cites OWASP 2025 verbatim and enumerates four LLM02 concern classes β the paper/framework/methodology citation class cross-cutting #9 covers.
|
| 372 |
+
|
| 373 |
+
**When:** after Step 2 (DECISIONS.md entry drafted), before its commit lands. Step 3 (README tail) does not begin until the gate passes.
|
| 374 |
+
|
| 375 |
+
**Reviewer:** Jane, continuing the collaboration pattern from every design step on this branch.
|
| 376 |
+
|
| 377 |
+
**Checks:**
|
| 378 |
+
|
| 379 |
+
1. OWASP LLM01 verbatim phrases survive PDF cross-reference (the "do not fully mitigate" sentence and the "indirect injection through retrieved content" claim).
|
| 380 |
+
2. LLM02 four-concern-class enumeration matches the OWASP 2025 PDF's LLM02 section.
|
| 381 |
+
3. SECURITY.md LLM01 and LLM02 cells survive a reviewer reading the OWASP PDF in a second tab.
|
| 382 |
+
|
| 383 |
+
Same failure class as the CRAG-taxonomy error caught on 2026-04-14: an unverified citation propagates across surfaces because the four-surface discipline amplifies it. ~10β15 min, performed by Jane with the OWASP 2025 PDF open.
|
| 384 |
+
|
| 385 |
+
---
|
| 386 |
+
|
| 387 |
+
## Scope guardrails (Section 3.5)
|
| 388 |
+
|
| 389 |
+
- **No code changes to `agent_bench/security/*`.** Every guardrail mentioned in SECURITY.md already exists β this task documents behavior, it does not modify it.
|
| 390 |
+
- **No test changes.** No new security tests, no assertion updates. The existing 444-test suite is the baseline.
|
| 391 |
+
- **No changes to `agent_bench/serving/routes.py`**, no changes to `agent_bench/serving/app.py`, no changes to the security pipeline wiring.
|
| 392 |
+
- **No new routes, no new endpoints, no new SSE events.**
|
| 393 |
+
- **No DECISIONS.md edits outside the one new entry.** No retroactive phrasing updates to existing security-phase entries. A drafting-time observation of conflict with an existing entry is logged as a follow-up, not silently patched in this commit.
|
| 394 |
+
- **No landing-page changes outside the Security panel subtitle.** The pipeline stages, retrieval panel, findings cards, tiles, request log, and footer are untouched.
|
| 395 |
+
- **No Hugging Face Spaces metadata updates.** Per the HF-deploy reference memory, the Spaces README frontmatter is managed separately; this task does not cross that boundary.
|
| 396 |
+
- **No README changes outside the Security Architecture section's closing tail link.** No new bullets, no new tables, no new rows in V1βV2βV3, no tagline edits, no LinkedIn-prep rephrasing.
|
| 397 |
+
|
| 398 |
+
---
|
| 399 |
+
|
| 400 |
+
## Drafting-gap protocol (hybrid rule)
|
| 401 |
+
|
| 402 |
+
When drafting SECURITY.md reveals a mismatch between documented behavior and actual code, the resolution depends on which kind of gap it is:
|
| 403 |
+
|
| 404 |
+
**Phrasing / precision gap β proceed.** The code does what the doc describes; the doc just uses different vocabulary. Default: rename in the doc to match the code, log nothing, keep going. Example: SECURITY.md draft says "always-on secret-format deny list" and the code exposes the same mechanism as `_SECRET_PATTERNS` β rename in the doc to match, proceed.
|
| 405 |
+
|
| 406 |
+
**Behavioral gap β stop and surface.** The code does something the doc would mischaracterize. Log to a follow-up file (not a silent fix), decide outside the time budget whether to patch the doc or the code, then resume. Example: SECURITY.md draft says "regex PII redaction on every retrieved chunk before it enters the LLM context window" and drafting-time code review reveals redaction runs only on chunks above a relevance threshold β that's a different security claim, stop, log, decide later.
|
| 407 |
+
|
| 408 |
+
The honest-evaluation brand requires this distinction. Defaulting "fix the doc to match the code" on a behavioral gap is exactly the failure mode that produces SECURITY.md docs that don't survive a reviewer with the repo open. Defaulting "stop and surface" on a phrasing gap is exactly the failure mode that blows the time budget on drafting-time bikeshedding.
|
| 409 |
+
|
| 410 |
+
---
|
| 411 |
+
|
| 412 |
+
## Plan-compliance pointer
|
| 413 |
+
|
| 414 |
+
Plan compliance is verified at pre-commit per Β§ Whole-task pre-commit verification; the canonical-phrasing table (Β§ Canonical-phrasing discipline) and per-surface acceptance checks (Β§ Per-surface acceptance checks) jointly cover every locked requirement in the plan's Part A section and every applicable cross-cutting requirement (#2 DECISIONS.md growth, #3 no-overclaiming, #4 interview-prep benefit).
|
docs/plans/2026-04-15-owasp-llm-top-10-mapping-implementation.md
ADDED
|
@@ -0,0 +1,1076 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Part A β OWASP LLM Top 10 (2025) Mapping Implementation Plan
|
| 2 |
+
|
| 3 |
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
| 4 |
+
|
| 5 |
+
**Goal:** Land the locked Part A design (`docs/plans/2026-04-15-owasp-llm-top-10-mapping-design.md`) across four surfaces β `SECURITY.md`, one new `DECISIONS.md` entry, one README tail line, one landing-page Security panel subtitle β producing an honest, evidence-linked mapping of the agent-bench implementation against the OWASP LLM Applications Top 10 (2025).
|
| 6 |
+
|
| 7 |
+
**Architecture:** Documentation-only. Four surfaces with a named canonical-phrasing discipline: `SECURITY.md` is canonical for all OWASP-cited language (LLM01 "do not fully mitigate" verbatim, LLM02 four-concern-class enumeration, LLM02 "narrower output-side subset" disclaimer); the other three surfaces paraphrase but preserve the named-residual-risk and scope-limit structure. Execution is strictly ordered β each downstream surface paraphrases upstream phrasing, so drafting out of order risks pinning a downstream surface to a draft that never lands. A **paired-review gate** fires between Task 2 (DECISIONS.md entry) and Task 3 (README tail) to satisfy cross-cutting requirement #9 from the v1.1 plan; Jane is the second reviewer and the DECISIONS.md commit does not land until the gate passes.
|
| 8 |
+
|
| 9 |
+
**Tech Stack:** Markdown (`SECURITY.md`, `DECISIONS.md`, `README.md`), HTML + CSS (`agent_bench/serving/static/index.html`). No Python code changes. No test changes. `make test` + `make lint` used only as regression checks in Task 5.
|
| 10 |
+
|
| 11 |
+
**Time budget:** ~3.5 hours inside the plan's 3β4 hour soft budget (hard cap 1 day). Slack is pooled at Task 5 to absorb drafting-time surprises.
|
| 12 |
+
|
| 13 |
+
**Branch:** `part-a/owasp-mapping` (already created, design doc already committed at `9a16483`).
|
| 14 |
+
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
## Scope guardrails (read before touching anything)
|
| 18 |
+
|
| 19 |
+
These constraints come from the design doc Β§ Scope guardrails. They are non-negotiable for this plan.
|
| 20 |
+
|
| 21 |
+
- **No code changes to `agent_bench/security/*`.** Every guardrail mentioned in SECURITY.md already exists; this plan documents behavior, it does not modify it.
|
| 22 |
+
- **No test changes.** The existing 444-test suite is the baseline; no new security tests, no assertion updates.
|
| 23 |
+
- **No changes to `agent_bench/serving/routes.py`**, no changes to `agent_bench/serving/app.py`, no changes to the security-pipeline wiring.
|
| 24 |
+
- **No new routes, no new endpoints, no new SSE events.**
|
| 25 |
+
- **No DECISIONS.md edits outside the one new entry in Task 2.** A drafting-time observation of conflict with an existing entry is logged as a follow-up, not silently patched.
|
| 26 |
+
- **No landing-page changes outside the Security panel subtitle added in Task 4.** The pipeline stages, retrieval panel, findings cards, tiles, request log, and footer are untouched.
|
| 27 |
+
- **No Hugging Face Spaces metadata updates.** The Spaces README frontmatter is managed separately; this plan does not cross that boundary.
|
| 28 |
+
- **No README changes outside the Security Architecture section closing-tail line.** No new bullets, no new tables, no new rows in V1βV2βV3, no tagline edits.
|
| 29 |
+
|
| 30 |
+
## Drafting-gap protocol
|
| 31 |
+
|
| 32 |
+
If drafting reveals a mismatch between documented behavior and actual code:
|
| 33 |
+
|
| 34 |
+
- **Phrasing / precision gap β proceed.** Rename in the doc to match the code, log nothing, keep going. Example: SECURITY.md draft says "always-on secret-format deny list"; code has `_SECRET_PATTERNS` β rename in doc, proceed.
|
| 35 |
+
- **Behavioral gap β stop and surface.** Log to a follow-up note, decide outside the time budget whether to patch the doc or the code, then resume. Example: SECURITY.md draft says "PII redaction on every chunk"; code review reveals redaction only runs above a threshold β stop, log, decide later.
|
| 36 |
+
|
| 37 |
+
The budget-vs-canonical-content drift on SECURITY.md word count (flagged above, ~790 words vs 500β700 target) is a **precision gap** β proceed.
|
| 38 |
+
|
| 39 |
+
---
|
| 40 |
+
|
| 41 |
+
## Task 1: Create SECURITY.md
|
| 42 |
+
|
| 43 |
+
**Files:**
|
| 44 |
+
- Create: `/Users/zenith/Desktop/agent-bench/SECURITY.md` (top-level; GitHub auto-surfaces in "Security" tab)
|
| 45 |
+
|
| 46 |
+
**Estimated time:** ~90 minutes. Most of the plan's writing time lives here.
|
| 47 |
+
|
| 48 |
+
**Task context:** SECURITY.md is the canonical source for all OWASP-cited language. Every downstream surface (DECISIONS.md, README, landing page) paraphrases phrasing introduced here. The file is written in strict structural order β preamble β summary table β Detailed mapping H2 with 8 subsections in LLM-numeric order β "What this doc is not" closing. LLM04 and LLM08 (out of scope) appear only in the summary table, not as subsections β adding subsections for them would be compliance-theater padding.
|
| 49 |
+
|
| 50 |
+
**Exit criteria:**
|
| 51 |
+
- File exists at `/Users/zenith/Desktop/agent-bench/SECURITY.md`
|
| 52 |
+
- Word count target 500β700, ~790 acceptable (canonical content drift documented)
|
| 53 |
+
- Summary table has 10 rows with 6 + 2 + 2 = 10 arithmetic visible at a glance
|
| 54 |
+
- 8 H3 subsections under single `## Detailed mapping` H2, in order: LLM01, LLM02, LLM03, LLM05, LLM06, LLM07, LLM09, LLM10
|
| 55 |
+
- LLM01 subsection contains exact phrase: "OWASP notes that RAG and fine-tuning do not fully mitigate prompt injection; indirect injection through retrieved content remains a core risk class"
|
| 56 |
+
- LLM01 subsection contains exact phrase: "Deployments without GPU run Tier 1 only; the two-tier design degrades to heuristic-only rather than failing closed"
|
| 57 |
+
- LLM02 subsection enumerates all four OWASP concern classes (access controls, training-data handling, user-consent transparency, proprietary-information governance)
|
| 58 |
+
- LLM02 subsection contains the clarifying clause: "a narrower, output-side subset that does not cleanly map to any single one of the four"
|
| 59 |
+
- LLM01, LLM02, LLM03, LLM10 subsections use 3-part skeleton (Verdict + Implementation + Residual risk / Scope limit / Named gap)
|
| 60 |
+
- LLM05, LLM06, LLM07, LLM09 subsections use 2-part skeleton only (Verdict + Implementation; no synthetic 3rd line)
|
| 61 |
+
- All 8 subsections contain inline cross-links to source files in `agent_bench/` and at least 5 contain DECISIONS.md anchor cross-links
|
| 62 |
+
- No occurrence of overclaiming phrases: "fully mitigated", "coverage guarantee", "tests all 10", "OWASP-certified", "OWASP certification"
|
| 63 |
+
- Committed with message `docs(security): add SECURITY.md with OWASP LLM Top 10 (2025) mapping`
|
| 64 |
+
|
| 65 |
+
---
|
| 66 |
+
|
| 67 |
+
- [ ] **Step 1.1: Create file with preamble paragraph**
|
| 68 |
+
|
| 69 |
+
Create `/Users/zenith/Desktop/agent-bench/SECURITY.md` with the following opening content:
|
| 70 |
+
|
| 71 |
+
```markdown
|
| 72 |
+
# Security
|
| 73 |
+
|
| 74 |
+
This document maps the agent-bench implementation against the OWASP LLM Applications Top 10 (2025). It is an honest mapping, not a coverage claim β every verdict cell for a guardrail we run carries a named residual risk or scope limit when OWASP's own 2025 text makes the limits explicit. The scope is a docs Q&A bot serving static curated corpora (FastAPI + Kubernetes) with no user ingestion, no fine-tuning, no authenticated sessions, and no side-effectful tools.
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
- [ ] **Step 1.2: Add architecture-scope paragraph**
|
| 78 |
+
|
| 79 |
+
Append to `SECURITY.md`:
|
| 80 |
+
|
| 81 |
+
```markdown
|
| 82 |
+
|
| 83 |
+
The implementation maps to the OWASP Appendix 1 reference architecture: user input β input guardrails β retrieval/tools β LLM β output guardrails β response. The agent-bench realization is diagrammed in the [README Security Architecture section](README.md#security-architecture) β this document does not duplicate the diagram, and the verdict cells below cross-link to the specific source files that implement each guardrail. These scope facts are referenced in the verdict cells below where they constrain what each guardrail must do.
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
- [ ] **Step 1.3: Add summary table**
|
| 87 |
+
|
| 88 |
+
Append to `SECURITY.md`:
|
| 89 |
+
|
| 90 |
+
```markdown
|
| 91 |
+
|
| 92 |
+
## Mapping summary
|
| 93 |
+
|
| 94 |
+
| Category | Verdict |
|
| 95 |
+
|---|---|
|
| 96 |
+
| LLM01 Prompt Injection | Addressed directly, named residual risk |
|
| 97 |
+
| LLM02 Sensitive Information Disclosure | Addressed directly, scope limit |
|
| 98 |
+
| LLM03 Supply Chain | Infrastructure layer, named gap |
|
| 99 |
+
| LLM04 Data and Model Poisoning | Out of scope |
|
| 100 |
+
| LLM05 Improper Output Handling | Addressed directly |
|
| 101 |
+
| LLM06 Excessive Agency | Addressed directly |
|
| 102 |
+
| LLM07 System Prompt Leakage | Addressed directly |
|
| 103 |
+
| LLM08 Vector and Embedding Weaknesses | Out of scope |
|
| 104 |
+
| LLM09 Misinformation | Addressed directly |
|
| 105 |
+
| LLM10 Unbounded Consumption | Infrastructure layer, named gap |
|
| 106 |
+
|
| 107 |
+
**Counts (verifiable in one eye-scan):** 6 addressed directly + 2 infrastructure layer + 2 out of scope = **10**.
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
Verify the arithmetic sentence is present and correct by scanning the Verdict column: "Addressed directly" should appear on rows LLM01, LLM02, LLM05, LLM06, LLM07, LLM09 (6 rows); "Infrastructure layer" on LLM03 and LLM10 (2 rows); "Out of scope" on LLM04 and LLM08 (2 rows).
|
| 111 |
+
|
| 112 |
+
- [ ] **Step 1.4: Add "Detailed mapping" H2 header**
|
| 113 |
+
|
| 114 |
+
Append to `SECURITY.md`:
|
| 115 |
+
|
| 116 |
+
```markdown
|
| 117 |
+
|
| 118 |
+
## Detailed mapping
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
- [ ] **Step 1.5: Add LLM01 Prompt Injection subsection (canonical text)**
|
| 122 |
+
|
| 123 |
+
Append to `SECURITY.md` β **exact text, do not paraphrase or tighten the Residual risk line**:
|
| 124 |
+
|
| 125 |
+
```markdown
|
| 126 |
+
|
| 127 |
+
### LLM01 Prompt Injection
|
| 128 |
+
|
| 129 |
+
**Verdict:** Addressed directly with a named residual risk.
|
| 130 |
+
|
| 131 |
+
**Implementation:** Two-tier detection β Tier 1 heuristic regex (local, <1ms) with ~20 pattern families covering role/identity hijacking, instruction override, system-prompt extraction, credential/env-var extraction, jailbreak keywords, and base64-nested payloads; Tier 2 optional DeBERTa classifier on Modal GPU for high-confidence arbitration. Deployments without GPU run Tier 1 only; the two-tier design degrades to heuristic-only rather than failing closed. Grounded refusal via the retrieval-threshold gate bounds indirect injection through retrieved content. Static corpus + bounded `ToolRegistry` (only `search` + `calculator`, no side-effectful tools) + `max_iterations` cap bound the blast radius. See [`agent_bench/security/injection_detector.py`](agent_bench/security/injection_detector.py), [`agent_bench/tools/registry.py`](agent_bench/tools/registry.py), and [DECISIONS.md Β§ Why two-tier injection detection, not three](DECISIONS.md#why-two-tier-injection-detection-not-three).
|
| 132 |
+
|
| 133 |
+
**Residual risk:** novel injection patterns not caught by heuristics or classifier. OWASP notes that RAG and fine-tuning do not fully mitigate prompt injection; indirect injection through retrieved content remains a core risk class.
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
- [ ] **Step 1.6: Add LLM02 Sensitive Information Disclosure subsection (canonical text with new clause)**
|
| 137 |
+
|
| 138 |
+
Append to `SECURITY.md` β **exact text, the "narrower, output-side subset that does not cleanly map to any single one of the four" clause is mandatory and resolves the cross-surface drift flagged in the design self-review**:
|
| 139 |
+
|
| 140 |
+
```markdown
|
| 141 |
+
|
| 142 |
+
### LLM02 Sensitive Information Disclosure
|
| 143 |
+
|
| 144 |
+
**Verdict:** Addressed directly for the applicable scope.
|
| 145 |
+
|
| 146 |
+
**Implementation:** Regex PII redaction on every retrieved chunk before it enters the LLM context window (EMAIL, SSN, CREDIT_CARD, PHONE, IP_ADDRESS) with optional spaCy NER for PERSON/ORG; post-generation output validation with an always-on secret-format deny list (OpenAI/Anthropic/Google/AWS/GitHub key prefixes, bearer tokens, env-var assignments) and URL-against-retrieved-chunks check. See [`agent_bench/security/pii_redactor.py`](agent_bench/security/pii_redactor.py), [`agent_bench/security/output_validator.py`](agent_bench/security/output_validator.py), and [DECISIONS.md Β§ Why regex + optional spaCy for PII, not a cloud API](DECISIONS.md#why-regex--optional-spacy-for-pii-not-a-cloud-api).
|
| 147 |
+
|
| 148 |
+
**Scope limit:** OWASP LLM02 spans access controls, training-data handling, user-consent transparency, and proprietary-information governance. This implementation addresses only response-time data surfaced to users β a narrower, output-side subset that does not cleanly map to any single one of the four; broader concerns would require architectural changes for multi-tenant or authenticated deployment.
|
| 149 |
+
```
|
| 150 |
+
|
| 151 |
+
- [ ] **Step 1.7: Add LLM03 Supply Chain subsection (3-part skeleton, infrastructure layer)**
|
| 152 |
+
|
| 153 |
+
Append to `SECURITY.md`:
|
| 154 |
+
|
| 155 |
+
```markdown
|
| 156 |
+
|
| 157 |
+
### LLM03 Supply Chain
|
| 158 |
+
|
| 159 |
+
**Verdict:** Addressed at the infrastructure layer with a named gap.
|
| 160 |
+
|
| 161 |
+
**Implementation:** Python dependencies pinned in [`pyproject.toml`](pyproject.toml); reproducible container image via [`Dockerfile`](Dockerfile); models sourced from official upstreams (HuggingFace for the DeBERTa injection classifier and the cross-encoder reranker, Modal base images for GPU serving). No user-uploaded models, no third-party model marketplaces.
|
| 162 |
+
|
| 163 |
+
**Named gap:** no formal SBOM, no signed model provenance. A production deployment would add explicit signature checks at model ingestion.
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
- [ ] **Step 1.8: Add LLM05 Improper Output Handling subsection (2-part skeleton)**
|
| 167 |
+
|
| 168 |
+
Append to `SECURITY.md`:
|
| 169 |
+
|
| 170 |
+
```markdown
|
| 171 |
+
|
| 172 |
+
### LLM05 Improper Output Handling
|
| 173 |
+
|
| 174 |
+
**Verdict:** Addressed directly.
|
| 175 |
+
|
| 176 |
+
**Implementation:** [`OutputValidator`](agent_bench/security/output_validator.py) runs four deterministic checks on every generated response: PII leakage detection (reuses `PIIRedactor` in detect-only mode), always-on secret-format deny list, URL validation against retrieved chunks, and configurable blocklist. Output surface is text-only β no rendered HTML, no SQL generation, no code execution. See also [DECISIONS.md Β§ Why three output validators, not four](DECISIONS.md#why-three-output-validators-not-four).
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
- [ ] **Step 1.9: Add LLM06 Excessive Agency subsection (2-part skeleton)**
|
| 180 |
+
|
| 181 |
+
Append to `SECURITY.md`:
|
| 182 |
+
|
| 183 |
+
```markdown
|
| 184 |
+
|
| 185 |
+
### LLM06 Excessive Agency
|
| 186 |
+
|
| 187 |
+
**Verdict:** Addressed directly.
|
| 188 |
+
|
| 189 |
+
**Implementation:** `max_iterations` cap enforced in the [orchestrator](agent_bench/agents/orchestrator.py) bounds tool-use loop depth. The [`ToolRegistry`](agent_bench/tools/registry.py) contains only `search_documents` (read-only retrieval against the static corpus) and `calculator` (pure arithmetic evaluation) β no write tools, no external network, no code execution, no shell. Tool definitions are declared at app startup and are not mutable at request time.
|
| 190 |
+
```
|
| 191 |
+
|
| 192 |
+
- [ ] **Step 1.10: Add LLM07 System Prompt Leakage subsection (2-part skeleton)**
|
| 193 |
+
|
| 194 |
+
Append to `SECURITY.md`:
|
| 195 |
+
|
| 196 |
+
```markdown
|
| 197 |
+
|
| 198 |
+
### LLM07 System Prompt Leakage
|
| 199 |
+
|
| 200 |
+
**Verdict:** Addressed directly.
|
| 201 |
+
|
| 202 |
+
**Implementation:** The system prompt contains no credentials, no auth tokens, and no multi-tenant role structure β it is a docs-Q&A instruction with corpus-label variable substitution only. Access control is enforced outside the LLM: [`RateLimitMiddleware`](agent_bench/serving/middleware.py) provides per-IP abuse protection, and a production deployment would layer authentication at the reverse-proxy or API-gateway level. This matches OWASP's recommended pattern (controls outside the LLM, not in the prompt). See also [DECISIONS.md Β§ Why no authentication on API endpoints](DECISIONS.md#why-no-authentication-on-api-endpoints).
|
| 203 |
+
```
|
| 204 |
+
|
| 205 |
+
- [ ] **Step 1.11: Add LLM09 Misinformation subsection (2-part skeleton)**
|
| 206 |
+
|
| 207 |
+
Append to `SECURITY.md`:
|
| 208 |
+
|
| 209 |
+
```markdown
|
| 210 |
+
|
| 211 |
+
### LLM09 Misinformation
|
| 212 |
+
|
| 213 |
+
**Verdict:** Addressed directly.
|
| 214 |
+
|
| 215 |
+
**Implementation:** Grounded refusal via the RRF retrieval-threshold gate β the project's core thesis. When the top retrieved chunk's RRF score falls below the per-corpus `refusal_threshold`, the orchestrator emits a grounded refusal (no hallucinated citation, no invented answer) rather than synthesizing. This mechanism is the source of the citation-accuracy=1.00 hero-tile number on API providers and is benchmarked across 27 FastAPI + 25 Kubernetes golden questions. See also [DECISIONS.md Β§ Why a relevance threshold for grounded refusal](DECISIONS.md#why-a-relevance-threshold-for-grounded-refusal).
|
| 216 |
+
```
|
| 217 |
+
|
| 218 |
+
- [ ] **Step 1.12: Add LLM10 Unbounded Consumption subsection (3-part skeleton, infrastructure layer)**
|
| 219 |
+
|
| 220 |
+
Append to `SECURITY.md`:
|
| 221 |
+
|
| 222 |
+
```markdown
|
| 223 |
+
|
| 224 |
+
### LLM10 Unbounded Consumption
|
| 225 |
+
|
| 226 |
+
**Verdict:** Addressed at the infrastructure layer with a named gap.
|
| 227 |
+
|
| 228 |
+
**Implementation:** Per-IP sliding-window rate limit via [`RateLimitMiddleware`](agent_bench/serving/middleware.py) (10 requests per minute by default, configurable); `max_iterations` cap on the agent loop; provider timeouts enforced via `ProviderTimeoutError` / `ProviderRateLimitError` handling in the request middleware.
|
| 229 |
+
|
| 230 |
+
**Named gap:** the rate limit is per-IP, not per-user; there is no budget cap per session and no global cost ceiling. A production deployment would add per-user quota tracking and a hard cost ceiling at the infrastructure or billing layer.
|
| 231 |
+
```
|
| 232 |
+
|
| 233 |
+
- [ ] **Step 1.13: Add "What this doc is not" closing block**
|
| 234 |
+
|
| 235 |
+
Append to `SECURITY.md`:
|
| 236 |
+
|
| 237 |
+
```markdown
|
| 238 |
+
|
| 239 |
+
## What this doc is not
|
| 240 |
+
|
| 241 |
+
This is an application-layer mapping for the scope of a static-corpus docs Q&A bot. It does not replace network-level security, authentication, infrastructure hardening, formal threat modeling, or a production security review. It does not constitute OWASP certification or a coverage guarantee β only an honest, evidence-linked mapping of the guardrails this implementation actually runs.
|
| 242 |
+
```
|
| 243 |
+
|
| 244 |
+
- [ ] **Step 1.14: Verify word count**
|
| 245 |
+
|
| 246 |
+
Run:
|
| 247 |
+
|
| 248 |
+
```bash
|
| 249 |
+
wc -w /Users/zenith/Desktop/agent-bench/SECURITY.md
|
| 250 |
+
```
|
| 251 |
+
|
| 252 |
+
Expected: a number in the range `500`β`800`. Target was 500β700; ~790 is the planned ceiling due to canonical LLM01/LLM02 content density. If the count is >800, tighten LLM03/05/06/07/09/10 implementation paragraphs by removing redundant phrases (not by cutting cross-links). If the count is <500, the canonical content is missing something β re-check steps 1.5 and 1.6.
|
| 253 |
+
|
| 254 |
+
- [ ] **Step 1.15: Grep-verify LLM01 canonical phrases**
|
| 255 |
+
|
| 256 |
+
Run each of these and confirm each returns a hit:
|
| 257 |
+
|
| 258 |
+
```bash
|
| 259 |
+
grep -c "do not fully mitigate prompt injection" /Users/zenith/Desktop/agent-bench/SECURITY.md
|
| 260 |
+
grep -c "indirect injection through retrieved content remains a core risk class" /Users/zenith/Desktop/agent-bench/SECURITY.md
|
| 261 |
+
grep -c "degrades to heuristic-only rather than failing closed" /Users/zenith/Desktop/agent-bench/SECURITY.md
|
| 262 |
+
```
|
| 263 |
+
|
| 264 |
+
Expected: each command returns `1`. If any returns `0`, the canonical LLM01 text is missing or has been inadvertently paraphrased β re-check step 1.5 and restore the exact text.
|
| 265 |
+
|
| 266 |
+
- [ ] **Step 1.16: Grep-verify LLM02 canonical phrases**
|
| 267 |
+
|
| 268 |
+
Run:
|
| 269 |
+
|
| 270 |
+
```bash
|
| 271 |
+
grep -c "access controls, training-data handling, user-consent transparency, and proprietary-information governance" /Users/zenith/Desktop/agent-bench/SECURITY.md
|
| 272 |
+
grep -c "narrower, output-side subset that does not cleanly map to any single one of the four" /Users/zenith/Desktop/agent-bench/SECURITY.md
|
| 273 |
+
```
|
| 274 |
+
|
| 275 |
+
Expected: each returns `1`. If either returns `0`, re-check step 1.6.
|
| 276 |
+
|
| 277 |
+
- [ ] **Step 1.17: Grep-verify 8 subsections in numeric order**
|
| 278 |
+
|
| 279 |
+
Run:
|
| 280 |
+
|
| 281 |
+
```bash
|
| 282 |
+
grep -n "^### LLM" /Users/zenith/Desktop/agent-bench/SECURITY.md
|
| 283 |
+
```
|
| 284 |
+
|
| 285 |
+
Expected output (exactly 8 lines, in this order):
|
| 286 |
+
|
| 287 |
+
```
|
| 288 |
+
### LLM01 Prompt Injection
|
| 289 |
+
### LLM02 Sensitive Information Disclosure
|
| 290 |
+
### LLM03 Supply Chain
|
| 291 |
+
### LLM05 Improper Output Handling
|
| 292 |
+
### LLM06 Excessive Agency
|
| 293 |
+
### LLM07 System Prompt Leakage
|
| 294 |
+
### LLM09 Misinformation
|
| 295 |
+
### LLM10 Unbounded Consumption
|
| 296 |
+
```
|
| 297 |
+
|
| 298 |
+
If LLM04 or LLM08 appears as an `###` header, delete it β they belong in the summary table only. If any subsection is out of order, reorder.
|
| 299 |
+
|
| 300 |
+
- [ ] **Step 1.18: Grep-verify no overclaiming phrases**
|
| 301 |
+
|
| 302 |
+
Run:
|
| 303 |
+
|
| 304 |
+
```bash
|
| 305 |
+
grep -iE "fully mitigated|coverage guarantee|tests all 10|OWASP.certified|OWASP certification" /Users/zenith/Desktop/agent-bench/SECURITY.md
|
| 306 |
+
```
|
| 307 |
+
|
| 308 |
+
Expected: no output (exit code 1). If any match fires, remove the offending phrase. **Note:** "OWASP certification" appears legitimately in the "What this doc is not" closing block as `"does not constitute OWASP certification"` β that's a disclaimer, not an overclaim. The grep above uses the literal phrase without negation, so it will match the disclaimer; inspect any hit for whether it is a disclaimer (good) or a claim (bad). Rephrase to "does not constitute an OWASP Top 10 certification" if the grep-based audit needs the phrase itself absent.
|
| 309 |
+
|
| 310 |
+
If the inspection reveals only the disclaimer hit, step passes.
|
| 311 |
+
|
| 312 |
+
- [ ] **Step 1.19: Verify cross-links resolve to existing files**
|
| 313 |
+
|
| 314 |
+
Run:
|
| 315 |
+
|
| 316 |
+
```bash
|
| 317 |
+
ls /Users/zenith/Desktop/agent-bench/agent_bench/security/injection_detector.py \
|
| 318 |
+
/Users/zenith/Desktop/agent-bench/agent_bench/security/pii_redactor.py \
|
| 319 |
+
/Users/zenith/Desktop/agent-bench/agent_bench/security/output_validator.py \
|
| 320 |
+
/Users/zenith/Desktop/agent-bench/agent_bench/tools/registry.py \
|
| 321 |
+
/Users/zenith/Desktop/agent-bench/agent_bench/agents/orchestrator.py \
|
| 322 |
+
/Users/zenith/Desktop/agent-bench/agent_bench/serving/middleware.py \
|
| 323 |
+
/Users/zenith/Desktop/agent-bench/pyproject.toml \
|
| 324 |
+
/Users/zenith/Desktop/agent-bench/Dockerfile
|
| 325 |
+
```
|
| 326 |
+
|
| 327 |
+
Expected: all 8 files listed (no "No such file or directory" errors). If any file is missing, the cross-link in SECURITY.md is dead β either fix the path or remove the link.
|
| 328 |
+
|
| 329 |
+
Also verify DECISIONS.md anchors exist for each `DECISIONS.md#...` cross-link in SECURITY.md:
|
| 330 |
+
|
| 331 |
+
```bash
|
| 332 |
+
grep -E "^## (Why two-tier injection detection|Why regex \+ optional spaCy|Why three output validators|Why no authentication|Why a relevance threshold)" /Users/zenith/Desktop/agent-bench/DECISIONS.md
|
| 333 |
+
```
|
| 334 |
+
|
| 335 |
+
Expected: 5 lines, one for each cited DECISIONS.md section. If any is missing, the anchor is dead β inspect the DECISIONS.md header for the exact title and update the SECURITY.md cross-link.
|
| 336 |
+
|
| 337 |
+
- [ ] **Step 1.20: Commit**
|
| 338 |
+
|
| 339 |
+
```bash
|
| 340 |
+
cd /Users/zenith/Desktop/agent-bench
|
| 341 |
+
git add SECURITY.md
|
| 342 |
+
git commit -m "$(cat <<'EOF'
|
| 343 |
+
docs(security): add SECURITY.md with OWASP LLM Top 10 (2025) mapping
|
| 344 |
+
|
| 345 |
+
Honest mapping of the agent-bench implementation against the OWASP
|
| 346 |
+
LLM Applications Top 10 (2025). Part A of the v1.1 implementation
|
| 347 |
+
plan.
|
| 348 |
+
|
| 349 |
+
Structure: 1-2 paragraph preamble + 10-row summary table with
|
| 350 |
+
6+2+2=10 arithmetic + single "Detailed mapping" H2 with 8 H3
|
| 351 |
+
subsections in LLM-numeric order (LLM01, LLM02, LLM03, LLM05,
|
| 352 |
+
LLM06, LLM07, LLM09, LLM10 β LLM04 and LLM08 intentionally absent
|
| 353 |
+
from Detailed mapping as they are out-of-scope per the summary
|
| 354 |
+
table) + "What this doc is not" closing.
|
| 355 |
+
|
| 356 |
+
LLM01 carries a named residual risk citing OWASP's own "do not
|
| 357 |
+
fully mitigate" language. LLM02 carries a scope limit enumerating
|
| 358 |
+
all four OWASP concern classes and naming the narrower output-side
|
| 359 |
+
subset this implementation actually addresses. LLM03 and LLM10
|
| 360 |
+
carry named infrastructure-layer gaps. LLM05/06/07/09 use the
|
| 361 |
+
2-part skeleton without a synthetic 3rd line.
|
| 362 |
+
|
| 363 |
+
All 8 subsections cross-link to source files in agent_bench/ and
|
| 364 |
+
at least 5 cross-link to DECISIONS.md anchors.
|
| 365 |
+
|
| 366 |
+
Design doc: docs/plans/2026-04-15-owasp-llm-top-10-mapping-design.md
|
| 367 |
+
|
| 368 |
+
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
| 369 |
+
EOF
|
| 370 |
+
)"
|
| 371 |
+
git log --oneline -1
|
| 372 |
+
```
|
| 373 |
+
|
| 374 |
+
Expected: commit lands, `git log --oneline -1` shows the new commit SHA with `docs(security): add SECURITY.md with OWASP LLM Top 10 (2025) mapping` message.
|
| 375 |
+
|
| 376 |
+
---
|
| 377 |
+
|
| 378 |
+
## Task 2: Add DECISIONS.md entry + paired-review gate
|
| 379 |
+
|
| 380 |
+
**Files:**
|
| 381 |
+
- Modify: `/Users/zenith/Desktop/agent-bench/DECISIONS.md` (insert one new entry between existing sections)
|
| 382 |
+
|
| 383 |
+
**Estimated time:** ~45 minutes drafting + ~10β15 minutes paired-review gate.
|
| 384 |
+
|
| 385 |
+
**Task context:** The DECISIONS.md entry is the **why** artifact; SECURITY.md is the **what** artifact. The entry is inserted topically clustered with the other security-phase entries (not appended at file end). House-style paired thinking: name the rejected alternative, cite OWASP verbatim, explain the tension, close with a cross-link that uses the explicit responsibility-split phrasing. Uses **failure-mode framing** ("URL hallucination", "PII leakage") not mechanism framing (SECURITY.md holds the mechanism framing). Target ~320 words.
|
| 386 |
+
|
| 387 |
+
**The paired-review gate** (satisfies cross-cutting #9 of the v1.1 plan) fires after the entry is drafted and before its commit lands. Jane reviews with the OWASP 2025 PDF open in a second tab and confirms three checks. Commit does not land until the gate passes.
|
| 388 |
+
|
| 389 |
+
**Exit criteria:**
|
| 390 |
+
- DECISIONS.md has a new entry inserted between "Why monitor mode for output validation, not gating?" and "Why additive SSE stage events?"
|
| 391 |
+
- Entry header is exactly `## Why named residual risks and scope limits, not "fully mitigated" verdicts?`
|
| 392 |
+
- Entry word count ~320 Β± 30
|
| 393 |
+
- Paragraph 1 names the rejected alternative (uniform "addressed" verdicts)
|
| 394 |
+
- Paragraph 2 quotes OWASP "do not fully mitigate" language verbatim (not paraphrased)
|
| 395 |
+
- Paragraph 3 enumerates all four LLM02 concern classes and contains the "narrower output-side subset" disclaim
|
| 396 |
+
- Paragraph 4 (thesis) intact β "honesty-vs-scannability" + "honest evaluation is the brand"
|
| 397 |
+
- Closing contains cross-link with explicit responsibility-split phrasing ("this entry covers why the verdict discipline takes the form it does")
|
| 398 |
+
- Closing contains the canonical-phrasing discipline note
|
| 399 |
+
- Uses failure-mode framing ("URL hallucination", "PII leakage"), not mechanism framing
|
| 400 |
+
- **Paired-review gate passed** β Jane has confirmed three OWASP citation checks
|
| 401 |
+
- Committed with message `docs(decisions): add entry on named residual risks and scope limits verdict discipline`
|
| 402 |
+
|
| 403 |
+
---
|
| 404 |
+
|
| 405 |
+
- [ ] **Step 2.1: Identify insertion point in DECISIONS.md**
|
| 406 |
+
|
| 407 |
+
Run:
|
| 408 |
+
|
| 409 |
+
```bash
|
| 410 |
+
grep -n "^## Why monitor mode for output validation, not gating?" /Users/zenith/Desktop/agent-bench/DECISIONS.md
|
| 411 |
+
grep -n "^## Why additive SSE stage events?" /Users/zenith/Desktop/agent-bench/DECISIONS.md
|
| 412 |
+
```
|
| 413 |
+
|
| 414 |
+
Expected: two line numbers, one for each header. The new entry goes between them β specifically, after the last line of the "Why monitor mode" section (before the next `## ` header). Record the line number of the `## Why additive SSE stage events?` header for the insertion point.
|
| 415 |
+
|
| 416 |
+
- [ ] **Step 2.2: Draft paragraph 1 (rejected alternative)**
|
| 417 |
+
|
| 418 |
+
In a scratch buffer or directly into DECISIONS.md at the insertion point, draft:
|
| 419 |
+
|
| 420 |
+
```markdown
|
| 421 |
+
## Why named residual risks and scope limits, not "fully mitigated" verdicts?
|
| 422 |
+
|
| 423 |
+
The OWASP LLM Top 10 (2025) mapping could have been written as a 10-row table where LLM01 and LLM02 read as "addressed" without qualifiers β shorter, cleaner-looking, and arithmetically identical to the current version in the sense that both produce the same 6+2+2=10 category counts. Rejected because OWASP's own 2025 text is explicit about what an input guardrail can and cannot do, and writing a verdict that contradicts the source the mapping cites would be compliance theater.
|
| 424 |
+
```
|
| 425 |
+
|
| 426 |
+
- [ ] **Step 2.3: Draft paragraph 2 (LLM01 β OWASP verbatim)**
|
| 427 |
+
|
| 428 |
+
Append to the entry:
|
| 429 |
+
|
| 430 |
+
```markdown
|
| 431 |
+
|
| 432 |
+
LLM01 Prompt Injection β OWASP 2025 states that RAG and fine-tuning do not fully mitigate prompt injection, and that indirect injection through retrieved content remains a core risk class. "Fully mitigated" is unsupportable for any system retrieving untrusted content into an LLM context window, which is every RAG system including this one. The LLM01 verdict reads "addressed directly with named residual risk"; the residual-risk cell cites OWASP's own "do not fully mitigate" language verbatim.
|
| 433 |
+
```
|
| 434 |
+
|
| 435 |
+
- [ ] **Step 2.4: Draft paragraph 3 (LLM02 β four-concern-class enumeration + narrower-subset disclaim)**
|
| 436 |
+
|
| 437 |
+
Append to the entry:
|
| 438 |
+
|
| 439 |
+
```markdown
|
| 440 |
+
|
| 441 |
+
LLM02 Sensitive Information Disclosure β OWASP 2025 defines LLM02 as spanning four concern classes: access controls, training-data handling, user-consent transparency, and proprietary-information governance. This implementation addresses a narrower output-side subset (regex PII redaction on retrieved chunks + output validation for PII leakage, secret formats, and URL hallucination) β not cleanly one of the four concern classes, but a narrower scope than any of them. The verdict reads "addressed directly for the applicable scope"; the scope-limit cell enumerates the four concern classes verbatim and names what addressing the broader concerns would require (multi-tenant or authenticated architecture).
|
| 442 |
+
```
|
| 443 |
+
|
| 444 |
+
- [ ] **Step 2.5: Draft paragraph 4 (thesis β load-bearing, do not cut on tightening)**
|
| 445 |
+
|
| 446 |
+
Append to the entry:
|
| 447 |
+
|
| 448 |
+
```markdown
|
| 449 |
+
|
| 450 |
+
The tension the entry resolves is honesty-vs-scannability: a mapping that surfaces named residual risks and scope limits is longer and harder to skim than one with uniform "addressed" verdicts, but the scannable version over-claims relative to the cited source. Honest evaluation is the brand. Every verdict cell in SECURITY.md must survive a reviewer reading OWASP 2025 in a second tab.
|
| 451 |
+
```
|
| 452 |
+
|
| 453 |
+
- [ ] **Step 2.6: Draft closing (cross-link + canonical-phrasing discipline)**
|
| 454 |
+
|
| 455 |
+
Append to the entry:
|
| 456 |
+
|
| 457 |
+
```markdown
|
| 458 |
+
|
| 459 |
+
See [SECURITY.md Β§ LLM01 Prompt Injection](SECURITY.md#llm01-prompt-injection) and [Β§ LLM02 Sensitive Information Disclosure](SECURITY.md#llm02-sensitive-information-disclosure) for the verdict cells; this entry covers why the verdict discipline takes the form it does. The LLM01 "do not fully mitigate" phrasing and the LLM02 four-concern-class enumeration are canonical in SECURITY.md; the README tail link and the landing-page Security panel subtitle paraphrase this language but must preserve the named-residual-risk and scope-limit structure. A future edit that drifts one surface without the others creates a brand inconsistency visible in cross-surface diff.
|
| 460 |
+
```
|
| 461 |
+
|
| 462 |
+
- [ ] **Step 2.7: Verify word count and tighten if over**
|
| 463 |
+
|
| 464 |
+
Run:
|
| 465 |
+
|
| 466 |
+
```bash
|
| 467 |
+
awk '/^## Why named residual risks and scope limits/,/^## Why additive SSE stage events/' /Users/zenith/Desktop/agent-bench/DECISIONS.md | wc -w
|
| 468 |
+
```
|
| 469 |
+
|
| 470 |
+
Expected: between 290 and 360 words (target ~320). If over 360, tighten paragraphs 2 and 3 by collapsing the OWASP-restatement sentence into the verdict-explanation sentence β **do not cut paragraph 4, it is the thesis and load-bearing**. If under 290, the entry is under-developed; re-check that all four paragraphs + closing are present.
|
| 471 |
+
|
| 472 |
+
- [ ] **Step 2.8: Verify failure-mode framing (not mechanism framing)**
|
| 473 |
+
|
| 474 |
+
Run:
|
| 475 |
+
|
| 476 |
+
```bash
|
| 477 |
+
awk '/^## Why named residual risks and scope limits/,/^## Why additive SSE stage events/' /Users/zenith/Desktop/agent-bench/DECISIONS.md | grep -cE "URL hallucination|PII leakage"
|
| 478 |
+
awk '/^## Why named residual risks and scope limits/,/^## Why additive SSE stage events/' /Users/zenith/Desktop/agent-bench/DECISIONS.md | grep -cE "URL-against-retrieved-chunks check|PIIRedactor on every retrieved chunk"
|
| 479 |
+
```
|
| 480 |
+
|
| 481 |
+
Expected: first command returns `β₯1` (failure-mode phrases present), second command returns `0` (mechanism phrases absent). If the second returns `β₯1`, the entry has accidentally adopted SECURITY.md's mechanism framing β rewrite the offending sentence in failure-mode terms.
|
| 482 |
+
|
| 483 |
+
- [ ] **Step 2.9: Verify insertion placement**
|
| 484 |
+
|
| 485 |
+
Run:
|
| 486 |
+
|
| 487 |
+
```bash
|
| 488 |
+
grep -A 1 "^## Why named residual risks and scope limits" /Users/zenith/Desktop/agent-bench/DECISIONS.md | head -3
|
| 489 |
+
grep -B 2 "^## Why named residual risks and scope limits" /Users/zenith/Desktop/agent-bench/DECISIONS.md | head -3
|
| 490 |
+
```
|
| 491 |
+
|
| 492 |
+
Expected: the header immediately before the new entry is `## Why monitor mode for output validation, not gating?` (or content from that section); the header immediately after is `## Why additive SSE stage events?`. If the new entry is at file end or in the wrong cluster, move it.
|
| 493 |
+
|
| 494 |
+
- [ ] **Step 2.10: PAIRED-REVIEW GATE β pause and surface to Jane**
|
| 495 |
+
|
| 496 |
+
**Do NOT commit until this gate passes.** This satisfies cross-cutting requirement #9 of the v1.1 plan.
|
| 497 |
+
|
| 498 |
+
Surface the drafted entry to Jane with the OWASP LLM Top 10 (2025) PDF open in a second tab: https://owasp.org/www-project-top-10-for-large-language-model-applications/assets/PDF/OWASP-Top-10-for-LLMs-v2025.pdf
|
| 499 |
+
|
| 500 |
+
Jane runs three checks:
|
| 501 |
+
|
| 502 |
+
1. **LLM01 verbatim**: the phrase "RAG and fine-tuning do not fully mitigate prompt injection" (or close equivalent expressing the same claim) appears in the OWASP 2025 PDF's LLM01 section, AND the phrase "indirect injection through retrieved content" is a supportable description of a core risk class named in LLM01.
|
| 503 |
+
2. **LLM02 four-concern-class enumeration**: the OWASP 2025 PDF's LLM02 section identifies access controls, training-data handling, user-consent transparency, and proprietary-information governance (or the same four under slightly different names) as the concern classes that LLM02 spans.
|
| 504 |
+
3. **SECURITY.md LLM01 and LLM02 cells survive PDF cross-reference**: Jane reads the SECURITY.md LLM01 Residual-risk cell and LLM02 Scope-limit cell with the OWASP PDF open, and confirms neither cell contradicts or overclaims relative to the cited source.
|
| 505 |
+
|
| 506 |
+
**If all three checks pass:** Jane confirms `gate: PASS`. Proceed to Step 2.11.
|
| 507 |
+
|
| 508 |
+
**If any check fails:** stop. Log the discrepancy. Do not commit the entry. Decide with Jane outside the time budget whether to patch the entry (if the draft is wrong) or update the design doc (if the canonical phrasing in SECURITY.md needs to change). Re-run the gate after any fix.
|
| 509 |
+
|
| 510 |
+
Same failure class as the CRAG-taxonomy error caught on 2026-04-14: an unverified citation propagates across surfaces because the four-surface discipline amplifies it. The gate's cost (~10β15 min) is cheap insurance against that failure mode.
|
| 511 |
+
|
| 512 |
+
- [ ] **Step 2.11: Commit (only after gate passes)**
|
| 513 |
+
|
| 514 |
+
```bash
|
| 515 |
+
cd /Users/zenith/Desktop/agent-bench
|
| 516 |
+
git add DECISIONS.md
|
| 517 |
+
git commit -m "$(cat <<'EOF'
|
| 518 |
+
docs(decisions): add entry on named residual risks and scope limits verdict discipline
|
| 519 |
+
|
| 520 |
+
New entry in DECISIONS.md documenting why the OWASP LLM Top 10
|
| 521 |
+
(2025) mapping in SECURITY.md uses named residual risks and scope
|
| 522 |
+
limits on LLM01 and LLM02 rather than uniform "fully mitigated"
|
| 523 |
+
verdicts. Topically clustered with the other security-phase
|
| 524 |
+
entries (between "Why monitor mode for output validation, not
|
| 525 |
+
gating?" and "Why additive SSE stage events?").
|
| 526 |
+
|
| 527 |
+
Named the rejected alternative (uniform "addressed" verdicts),
|
| 528 |
+
quotes OWASP 2025 "do not fully mitigate" language verbatim,
|
| 529 |
+
enumerates all four LLM02 concern classes, and names the
|
| 530 |
+
honesty-vs-scannability tension the entry resolves. Cross-link
|
| 531 |
+
to SECURITY.md uses the explicit responsibility-split phrasing:
|
| 532 |
+
SECURITY.md is the WHAT (verdict cells), this entry is the WHY
|
| 533 |
+
(verdict discipline).
|
| 534 |
+
|
| 535 |
+
Uses failure-mode framing ("URL hallucination", "PII leakage");
|
| 536 |
+
mechanism framing ("URL-against-retrieved-chunks check", etc.)
|
| 537 |
+
lives in SECURITY.md. The split is deliberate.
|
| 538 |
+
|
| 539 |
+
Paired-review gate (cross-cutting requirement #9) passed: OWASP
|
| 540 |
+
2025 verbatim phrases and four-concern-class enumeration
|
| 541 |
+
cross-checked against the official PDF before commit.
|
| 542 |
+
|
| 543 |
+
Design doc: docs/plans/2026-04-15-owasp-llm-top-10-mapping-design.md
|
| 544 |
+
|
| 545 |
+
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
| 546 |
+
EOF
|
| 547 |
+
)"
|
| 548 |
+
git log --oneline -1
|
| 549 |
+
```
|
| 550 |
+
|
| 551 |
+
Expected: commit lands.
|
| 552 |
+
|
| 553 |
+
---
|
| 554 |
+
|
| 555 |
+
## Task 3: Add README one-liner (Security Architecture closing tail)
|
| 556 |
+
|
| 557 |
+
**Files:**
|
| 558 |
+
- Modify: `/Users/zenith/Desktop/agent-bench/README.md` (single line edit near line 202)
|
| 559 |
+
|
| 560 |
+
**Estimated time:** ~15 minutes (most of which is the rhythm-check between the two variants).
|
| 561 |
+
|
| 562 |
+
**Task context:** The README already has a substantial "Security Architecture" section ending with the line "See [DECISIONS.md](DECISIONS.md) for why we chose two-tier detection over three, regex-only PII by default, JSONL over SQLite for audit, and HMAC over plain SHA-256 for IP hashing." The OWASP pointer joins this existing tail β either as a comma-joined clause (Variant 1) or as a preceding sentence (Variant 2). Rhythm check at drafting time; pick whichever breathes better in context. Plan-wording compliance ("one-line mention") is satisfied under either reading.
|
| 563 |
+
|
| 564 |
+
**Exit criteria:**
|
| 565 |
+
- README Security Architecture section closing tail is updated with the chosen variant
|
| 566 |
+
- Existing four-item DECISIONS.md list (two-tier / regex-PII / JSONL / HMAC) is intact
|
| 567 |
+
- No other README section is touched
|
| 568 |
+
- Committed with message `docs(readme): link SECURITY.md OWASP mapping from Security Architecture tail`
|
| 569 |
+
|
| 570 |
+
---
|
| 571 |
+
|
| 572 |
+
- [ ] **Step 3.1: Read the current closing tail**
|
| 573 |
+
|
| 574 |
+
Run:
|
| 575 |
+
|
| 576 |
+
```bash
|
| 577 |
+
grep -n "See \[DECISIONS.md\]" /Users/zenith/Desktop/agent-bench/README.md
|
| 578 |
+
```
|
| 579 |
+
|
| 580 |
+
Expected: one hit, in the Security Architecture section (around line ~202). Record the line number.
|
| 581 |
+
|
| 582 |
+
- [ ] **Step 3.2: Read the surrounding paragraph for rhythm context**
|
| 583 |
+
|
| 584 |
+
Read `/Users/zenith/Desktop/agent-bench/README.md` offset ~195, limit 15, to see the "application-layer security pipeline" disclaimer paragraph + the closing tail + what follows. The rhythm check is about whether the section closes tight (favors Variant 1) or feels heavy (favors Variant 2).
|
| 585 |
+
|
| 586 |
+
- [ ] **Step 3.3: Prepare Variant 1 (single sentence, comma-list tail)**
|
| 587 |
+
|
| 588 |
+
Variant 1:
|
| 589 |
+
|
| 590 |
+
```markdown
|
| 591 |
+
See [SECURITY.md](SECURITY.md) for the OWASP LLM Top 10 (2025) mapping, and [DECISIONS.md](DECISIONS.md) for why we chose two-tier detection over three, regex-only PII by default, JSONL over SQLite for audit, and HMAC over plain SHA-256 for IP hashing.
|
| 592 |
+
```
|
| 593 |
+
|
| 594 |
+
- [ ] **Step 3.4: Prepare Variant 2 (two sentences, equal billing)**
|
| 595 |
+
|
| 596 |
+
Variant 2:
|
| 597 |
+
|
| 598 |
+
```markdown
|
| 599 |
+
See [SECURITY.md](SECURITY.md) for the OWASP LLM Top 10 (2025) mapping. See [DECISIONS.md](DECISIONS.md) for why we chose two-tier detection over three, regex-only PII by default, JSONL over SQLite for audit, and HMAC over plain SHA-256 for IP hashing.
|
| 600 |
+
```
|
| 601 |
+
|
| 602 |
+
- [ ] **Step 3.5: Pick a variant on rhythm**
|
| 603 |
+
|
| 604 |
+
Read the two variants in the actual surrounding context (the paragraph before is the "application-layer security pipeline" disclaimer). Pick the one that reads cleaner. Rough heuristic: if the section already closes on a short, declarative sentence, Variant 1 (comma-join) stays tight; if the section closes on a longer sentence that already feels weighty, Variant 2 (two short sentences) breaks the cadence more cleanly. **There is no wrong answer between the two β this is a drafting-time rhythm choice, not a design choice.**
|
| 605 |
+
|
| 606 |
+
- [ ] **Step 3.6: Apply the chosen variant**
|
| 607 |
+
|
| 608 |
+
Replace the current `See [DECISIONS.md]...` line (identified in step 3.1) with the chosen variant. Use the Edit tool with the exact old string (the current one-line `See [DECISIONS.md] for why we chose...`) and the chosen variant as the new string.
|
| 609 |
+
|
| 610 |
+
- [ ] **Step 3.7: Verify the four-item DECISIONS.md list is intact**
|
| 611 |
+
|
| 612 |
+
Run:
|
| 613 |
+
|
| 614 |
+
```bash
|
| 615 |
+
grep "two-tier detection over three" /Users/zenith/Desktop/agent-bench/README.md
|
| 616 |
+
grep "regex-only PII by default" /Users/zenith/Desktop/agent-bench/README.md
|
| 617 |
+
grep "JSONL over SQLite for audit" /Users/zenith/Desktop/agent-bench/README.md
|
| 618 |
+
grep "HMAC over plain SHA-256" /Users/zenith/Desktop/agent-bench/README.md
|
| 619 |
+
```
|
| 620 |
+
|
| 621 |
+
Expected: each command returns `1` hit. If any returns `0`, the four-item list was accidentally truncated β re-apply the edit.
|
| 622 |
+
|
| 623 |
+
- [ ] **Step 3.8: Verify no other README section was touched**
|
| 624 |
+
|
| 625 |
+
Run:
|
| 626 |
+
|
| 627 |
+
```bash
|
| 628 |
+
cd /Users/zenith/Desktop/agent-bench
|
| 629 |
+
git diff README.md | head -30
|
| 630 |
+
```
|
| 631 |
+
|
| 632 |
+
Expected: the diff shows exactly one changed line (the tail) β no changes to Engineering Scope, no changes to V1βV2βV3 table, no tagline edit, no other section. If the diff shows unintended changes, revert them.
|
| 633 |
+
|
| 634 |
+
- [ ] **Step 3.9: Verify SECURITY.md link target**
|
| 635 |
+
|
| 636 |
+
Run:
|
| 637 |
+
|
| 638 |
+
```bash
|
| 639 |
+
grep -o "SECURITY.md" /Users/zenith/Desktop/agent-bench/README.md | head -5
|
| 640 |
+
```
|
| 641 |
+
|
| 642 |
+
Expected: at least one hit. The link is relative (`SECURITY.md`, not an absolute URL) because README renders on GitHub and relative resolves.
|
| 643 |
+
|
| 644 |
+
- [ ] **Step 3.10: Commit**
|
| 645 |
+
|
| 646 |
+
```bash
|
| 647 |
+
cd /Users/zenith/Desktop/agent-bench
|
| 648 |
+
git add README.md
|
| 649 |
+
git commit -m "$(cat <<'EOF'
|
| 650 |
+
docs(readme): link SECURITY.md OWASP mapping from Security Architecture tail
|
| 651 |
+
|
| 652 |
+
Adds a one-line pointer to SECURITY.md from the Security
|
| 653 |
+
Architecture section closing tail. The existing four-item
|
| 654 |
+
DECISIONS.md list (two-tier / regex-PII / JSONL / HMAC) is
|
| 655 |
+
preserved intact β cutting it to make room for the OWASP
|
| 656 |
+
pointer would have traded a credibility signal for a
|
| 657 |
+
navigation pointer, a net loss.
|
| 658 |
+
|
| 659 |
+
Engineering Scope bullets deliberately not touched β those
|
| 660 |
+
carry build artifacts, not documentation about build artifacts.
|
| 661 |
+
|
| 662 |
+
Design doc: docs/plans/2026-04-15-owasp-llm-top-10-mapping-design.md
|
| 663 |
+
|
| 664 |
+
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
| 665 |
+
EOF
|
| 666 |
+
)"
|
| 667 |
+
```
|
| 668 |
+
|
| 669 |
+
Expected: commit lands.
|
| 670 |
+
|
| 671 |
+
---
|
| 672 |
+
|
| 673 |
+
## Task 4: Add landing page Security panel subtitle
|
| 674 |
+
|
| 675 |
+
**Files:**
|
| 676 |
+
- Modify: `/Users/zenith/Desktop/agent-bench/agent_bench/serving/static/index.html` (add one HTML element + one CSS rule inside the existing `<style>` block)
|
| 677 |
+
|
| 678 |
+
**Estimated time:** ~30 minutes (most of which is viewport rendering verification).
|
| 679 |
+
|
| 680 |
+
**Task context:** The landing page's Security panel (right column of the dashboard) currently renders only the `<h3>Security</h3>` heading plus three live badges. The OWASP pointer is a new sub-text element inserted between the h3 and the badges row. Block-link (whole subtitle wrapped in `<a>`), absolute GitHub URL (relative won't resolve on HF Spaces), `aria-label` for screen readers, em-dash variant for mobile-wrap readability, `target="_blank"` matching the existing finding-card pattern.
|
| 681 |
+
|
| 682 |
+
**Exit criteria:**
|
| 683 |
+
- One `<a class="security-panel-sub">` element inserted between `<h3>Security</h3>` and `<div class="security-badges">`
|
| 684 |
+
- Element is a block-link (whole subtitle is the link target)
|
| 685 |
+
- `href="https://github.com/tyy0811/agent-bench/blob/main/SECURITY.md"` (absolute)
|
| 686 |
+
- `aria-label="OWASP LLM Top 10 mapping in SECURITY.md"` present
|
| 687 |
+
- `target="_blank"` present
|
| 688 |
+
- Subtitle text uses em-dash variant: "Mapped against the OWASP LLM Top 10 (2025) — named residual risks for LLM01, scope limits for LLM02 → SECURITY.md"
|
| 689 |
+
- New `.security-panel-sub` CSS class added in the `<style>` block (distinct from the existing `.sec-sub` class which is scoped inside `.sec-badge`)
|
| 690 |
+
- Dev server (`make serve`) started and subtitle verified to render at 1440px and 375px viewport widths
|
| 691 |
+
- No other element in `index.html` touched
|
| 692 |
+
- Committed with message `docs(landing): add OWASP mapping subtitle to Security panel`
|
| 693 |
+
|
| 694 |
+
---
|
| 695 |
+
|
| 696 |
+
- [ ] **Step 4.1: Locate the existing Security panel h3**
|
| 697 |
+
|
| 698 |
+
Run:
|
| 699 |
+
|
| 700 |
+
```bash
|
| 701 |
+
grep -n '<h3>Security</h3>' /Users/zenith/Desktop/agent-bench/agent_bench/serving/static/index.html
|
| 702 |
+
```
|
| 703 |
+
|
| 704 |
+
Expected: one hit around line ~312. Record the line number.
|
| 705 |
+
|
| 706 |
+
- [ ] **Step 4.2: Locate the existing `.sec-sub` CSS class to confirm the new class name doesn't collide**
|
| 707 |
+
|
| 708 |
+
Run:
|
| 709 |
+
|
| 710 |
+
```bash
|
| 711 |
+
grep -n '\.sec-sub' /Users/zenith/Desktop/agent-bench/agent_bench/serving/static/index.html
|
| 712 |
+
```
|
| 713 |
+
|
| 714 |
+
Expected: one or more hits, all inside the `<style>` block, scoped to `.sec-badge`. The new `.security-panel-sub` class is distinct.
|
| 715 |
+
|
| 716 |
+
- [ ] **Step 4.3: Locate a good insertion point for the new CSS rule**
|
| 717 |
+
|
| 718 |
+
Run:
|
| 719 |
+
|
| 720 |
+
```bash
|
| 721 |
+
grep -n '/\* Security badges \*/' /Users/zenith/Desktop/agent-bench/agent_bench/serving/static/index.html
|
| 722 |
+
```
|
| 723 |
+
|
| 724 |
+
Expected: one hit around line ~135, inside the `<style>` block. The new `.security-panel-sub` rule goes near this block to keep security-related styles clustered.
|
| 725 |
+
|
| 726 |
+
- [ ] **Step 4.4: Add the new CSS rule**
|
| 727 |
+
|
| 728 |
+
Use the Edit tool to insert the following rule into the `<style>` block, immediately after the existing `/* Security badges */` comment and the `.security-panel` rule that follows it. The exact anchor is the `.security-panel` rule's closing brace.
|
| 729 |
+
|
| 730 |
+
Anchor text (approximate; verify exact text before editing):
|
| 731 |
+
|
| 732 |
+
```css
|
| 733 |
+
.security-panel{background:var(--panel-bg);border:1px solid var(--panel-border);border-radius:12px;padding:16px}
|
| 734 |
+
```
|
| 735 |
+
|
| 736 |
+
New CSS to insert **immediately after** that line:
|
| 737 |
+
|
| 738 |
+
```css
|
| 739 |
+
.security-panel-sub{display:block;color:var(--muted);font-size:0.78rem;line-height:1.4;margin-top:-4px;margin-bottom:10px;text-decoration:none}
|
| 740 |
+
.security-panel-sub:hover{color:var(--text);text-decoration:underline}
|
| 741 |
+
```
|
| 742 |
+
|
| 743 |
+
- [ ] **Step 4.5: Add the HTML subtitle element**
|
| 744 |
+
|
| 745 |
+
Use the Edit tool to insert the following element **immediately after** `<h3>Security</h3>` and **before** `<div class="security-badges">`.
|
| 746 |
+
|
| 747 |
+
Exact old string (3 lines):
|
| 748 |
+
|
| 749 |
+
```html
|
| 750 |
+
<div class="security-panel">
|
| 751 |
+
<h3>Security</h3>
|
| 752 |
+
<div class="security-badges">
|
| 753 |
+
```
|
| 754 |
+
|
| 755 |
+
Exact new string (4 lines, preserving indentation):
|
| 756 |
+
|
| 757 |
+
```html
|
| 758 |
+
<div class="security-panel">
|
| 759 |
+
<h3>Security</h3>
|
| 760 |
+
<a class="security-panel-sub" href="https://github.com/tyy0811/agent-bench/blob/main/SECURITY.md" target="_blank" aria-label="OWASP LLM Top 10 mapping in SECURITY.md">Mapped against the OWASP LLM Top 10 (2025) — named residual risks for LLM01, scope limits for LLM02 → SECURITY.md</a>
|
| 761 |
+
<div class="security-badges">
|
| 762 |
+
```
|
| 763 |
+
|
| 764 |
+
- [ ] **Step 4.6: Verify the HTML insertion**
|
| 765 |
+
|
| 766 |
+
Run:
|
| 767 |
+
|
| 768 |
+
```bash
|
| 769 |
+
grep -c 'class="security-panel-sub"' /Users/zenith/Desktop/agent-bench/agent_bench/serving/static/index.html
|
| 770 |
+
grep -c 'aria-label="OWASP LLM Top 10 mapping in SECURITY.md"' /Users/zenith/Desktop/agent-bench/agent_bench/serving/static/index.html
|
| 771 |
+
grep -c 'github.com/tyy0811/agent-bench/blob/main/SECURITY.md' /Users/zenith/Desktop/agent-bench/agent_bench/serving/static/index.html
|
| 772 |
+
grep -c '— named residual risks for LLM01, scope limits for LLM02' /Users/zenith/Desktop/agent-bench/agent_bench/serving/static/index.html
|
| 773 |
+
```
|
| 774 |
+
|
| 775 |
+
Expected: each command returns exactly `1`. If any returns `0`, the insertion failed; re-check the edit.
|
| 776 |
+
|
| 777 |
+
- [ ] **Step 4.7: Start the dev server for visual verification**
|
| 778 |
+
|
| 779 |
+
```bash
|
| 780 |
+
cd /Users/zenith/Desktop/agent-bench
|
| 781 |
+
make serve
|
| 782 |
+
```
|
| 783 |
+
|
| 784 |
+
Expected: FastAPI starts on `http://localhost:8000`. This runs in the background for the next few steps; do not stop it until step 4.11.
|
| 785 |
+
|
| 786 |
+
- [ ] **Step 4.8: Verify desktop viewport render (1440px)**
|
| 787 |
+
|
| 788 |
+
Open `http://localhost:8000` in a browser. Confirm:
|
| 789 |
+
|
| 790 |
+
- The Security panel in the dashboard right column now shows the new subtitle line below the `Security` heading and above the three badges.
|
| 791 |
+
- The subtitle renders as muted text (gray, `0.78rem`).
|
| 792 |
+
- The subtitle is clickable and opens `https://github.com/tyy0811/agent-bench/blob/main/SECURITY.md` in a new tab (the link target doesn't exist yet on main's `SECURITY.md`, which is fine β Task 5 pushes the branch and the link resolves after merge).
|
| 793 |
+
- Hover state changes color and adds an underline.
|
| 794 |
+
- The em-dash and right-arrow Unicode characters render cleanly.
|
| 795 |
+
|
| 796 |
+
If any of these fail, stop the server, fix the HTML/CSS, and re-test.
|
| 797 |
+
|
| 798 |
+
- [ ] **Step 4.9: Verify narrow viewport render (375px)**
|
| 799 |
+
|
| 800 |
+
In browser devtools, switch to responsive mode at 375px width. Confirm:
|
| 801 |
+
|
| 802 |
+
- The subtitle wraps to 2β3 lines at this width.
|
| 803 |
+
- The em-dash provides a natural wrap break so the phrase "named residual risks for LLM01" and "scope limits for LLM02" don't split mid-phrase.
|
| 804 |
+
- The subtitle stays readable (no font-size collapse, no overflow).
|
| 805 |
+
|
| 806 |
+
If the wrap reads poorly, the em-dash variant may still be the best option β but flag it as a known limitation of the 0.78rem font size at narrow viewport.
|
| 807 |
+
|
| 808 |
+
- [ ] **Step 4.10: Drafting-time check on `margin-top: -4px`**
|
| 809 |
+
|
| 810 |
+
Inspect the rendered panel in browser devtools. The negative top margin pulls the subtitle closer to the h3. If this reads brittle (e.g., if it overlaps the h3 baseline, or if it looks intentional vs accidental-negative-space), prefer an explicit adjustment to the h3 bottom margin instead. In that case, replace `margin-top:-4px` in the CSS rule with `margin-top:0` and add a new rule:
|
| 811 |
+
|
| 812 |
+
```css
|
| 813 |
+
.security-panel h3{margin-bottom:4px}
|
| 814 |
+
```
|
| 815 |
+
|
| 816 |
+
If the negative margin reads fine, leave it.
|
| 817 |
+
|
| 818 |
+
- [ ] **Step 4.11: Stop the dev server**
|
| 819 |
+
|
| 820 |
+
```
|
| 821 |
+
Ctrl+C in the terminal running make serve
|
| 822 |
+
```
|
| 823 |
+
|
| 824 |
+
- [ ] **Step 4.12: Verify no other element was touched**
|
| 825 |
+
|
| 826 |
+
Run:
|
| 827 |
+
|
| 828 |
+
```bash
|
| 829 |
+
cd /Users/zenith/Desktop/agent-bench
|
| 830 |
+
git diff agent_bench/serving/static/index.html | head -50
|
| 831 |
+
```
|
| 832 |
+
|
| 833 |
+
Expected: the diff shows exactly two insertions β the new CSS rule (2 lines) and the new HTML element (1 line). No changes to the pipeline stages, retrieval panel, findings cards, tiles, request log, footer, or any other element. If the diff shows unintended changes, revert them.
|
| 834 |
+
|
| 835 |
+
- [ ] **Step 4.13: Commit**
|
| 836 |
+
|
| 837 |
+
```bash
|
| 838 |
+
cd /Users/zenith/Desktop/agent-bench
|
| 839 |
+
git add agent_bench/serving/static/index.html
|
| 840 |
+
git commit -m "$(cat <<'EOF'
|
| 841 |
+
docs(landing): add OWASP mapping subtitle to Security panel
|
| 842 |
+
|
| 843 |
+
Adds a block-link subtitle under the Security panel h3 that
|
| 844 |
+
points to the top-level SECURITY.md. Uses an absolute GitHub
|
| 845 |
+
URL (relative path wouldn't resolve on HF Spaces), an
|
| 846 |
+
aria-label for screen-reader users, and the em-dash variant
|
| 847 |
+
for cleaner mobile wrap at 375px viewport.
|
| 848 |
+
|
| 849 |
+
The subtitle names the honesty qualifier ("named residual
|
| 850 |
+
risks for LLM01, scope limits for LLM02") inline so a
|
| 851 |
+
skim-reader understands before clicking that SECURITY.md is
|
| 852 |
+
an honest mapping, not a checkbox. Without the qualifier,
|
| 853 |
+
the subtitle would read as compliance theater.
|
| 854 |
+
|
| 855 |
+
New .security-panel-sub CSS class (distinct from the existing
|
| 856 |
+
.sec-sub class which is scoped to .sec-badge). No other
|
| 857 |
+
landing-page element touched.
|
| 858 |
+
|
| 859 |
+
Design doc: docs/plans/2026-04-15-owasp-llm-top-10-mapping-design.md
|
| 860 |
+
|
| 861 |
+
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
| 862 |
+
EOF
|
| 863 |
+
)"
|
| 864 |
+
```
|
| 865 |
+
|
| 866 |
+
Expected: commit lands.
|
| 867 |
+
|
| 868 |
+
---
|
| 869 |
+
|
| 870 |
+
## Task 5: Cross-surface verification and push
|
| 871 |
+
|
| 872 |
+
**Files:** None modified (unless verification surfaces a drift; then fix + commit + re-verify).
|
| 873 |
+
|
| 874 |
+
**Estimated time:** ~30 minutes. This task absorbs the pooled slack for the whole plan.
|
| 875 |
+
|
| 876 |
+
**Task context:** The four surfaces are all landed. This task is the pre-merge verification pass β canonical-phrasing audit, cross-link target resolution, `make test` + `make lint` regression check, final no-overclaiming scan, and push to origin. No commits in the normal case; drift-repair commits only if a check fails.
|
| 877 |
+
|
| 878 |
+
**Exit criteria:**
|
| 879 |
+
- Canonical-phrasing table from design doc Β§3.1 fully audited
|
| 880 |
+
- All cross-link targets resolve (source files exist, DECISIONS.md anchors exist, SECURITY.md anchors exist)
|
| 881 |
+
- `make test` passes with no regressions (baseline: 444 tests)
|
| 882 |
+
- `make lint` passes
|
| 883 |
+
- No overclaiming phrases anywhere across the four surfaces
|
| 884 |
+
- SECURITY.md word count within planned range (500β800)
|
| 885 |
+
- DECISIONS.md entry word count within planned range (290β360)
|
| 886 |
+
- Branch `part-a/owasp-mapping` pushed to `origin`
|
| 887 |
+
- Ready for PR to `main`
|
| 888 |
+
|
| 889 |
+
---
|
| 890 |
+
|
| 891 |
+
- [ ] **Step 5.1: Read all four surfaces end-to-end in execution order**
|
| 892 |
+
|
| 893 |
+
Read in order:
|
| 894 |
+
|
| 895 |
+
1. `/Users/zenith/Desktop/agent-bench/SECURITY.md` β full file
|
| 896 |
+
2. The new DECISIONS.md entry only (the "Why named residual risks and scope limits..." section)
|
| 897 |
+
3. The README Security Architecture section closing tail (one or two lines)
|
| 898 |
+
4. The landing page Security panel block (h3 + new subtitle + badges)
|
| 899 |
+
|
| 900 |
+
While reading, check that the four surfaces describe the same mapping with consistent vocabulary: named residual risks, scope limits, four concern classes, narrower output-side subset, etc.
|
| 901 |
+
|
| 902 |
+
- [ ] **Step 5.2: Canonical-phrasing audit β LLM01 verbatim**
|
| 903 |
+
|
| 904 |
+
Run:
|
| 905 |
+
|
| 906 |
+
```bash
|
| 907 |
+
grep -l "do not fully mitigate prompt injection" \
|
| 908 |
+
/Users/zenith/Desktop/agent-bench/SECURITY.md \
|
| 909 |
+
/Users/zenith/Desktop/agent-bench/DECISIONS.md
|
| 910 |
+
```
|
| 911 |
+
|
| 912 |
+
Expected: both files listed. SECURITY.md is canonical; DECISIONS.md paraphrases/quotes. If only one hits, the canonical-phrasing discipline has drifted β inspect and repair.
|
| 913 |
+
|
| 914 |
+
- [ ] **Step 5.3: Canonical-phrasing audit β LLM02 four-concern-class enumeration**
|
| 915 |
+
|
| 916 |
+
Run:
|
| 917 |
+
|
| 918 |
+
```bash
|
| 919 |
+
grep -l "access controls, training-data handling, user-consent transparency, and proprietary-information governance" \
|
| 920 |
+
/Users/zenith/Desktop/agent-bench/SECURITY.md \
|
| 921 |
+
/Users/zenith/Desktop/agent-bench/DECISIONS.md
|
| 922 |
+
```
|
| 923 |
+
|
| 924 |
+
Expected: both files listed.
|
| 925 |
+
|
| 926 |
+
- [ ] **Step 5.4: Canonical-phrasing audit β "narrower output-side subset" disclaim**
|
| 927 |
+
|
| 928 |
+
Run:
|
| 929 |
+
|
| 930 |
+
```bash
|
| 931 |
+
grep -l "narrower, output-side subset that does not cleanly map to any single one of the four" /Users/zenith/Desktop/agent-bench/SECURITY.md
|
| 932 |
+
grep "narrower output-side subset" /Users/zenith/Desktop/agent-bench/DECISIONS.md
|
| 933 |
+
```
|
| 934 |
+
|
| 935 |
+
Expected: first command lists SECURITY.md; second command shows at least one hit in DECISIONS.md with matching disclaim semantics (DECISIONS.md version may phrase as "narrower output-side subset" without the comma β that's fine, the structural claim matches).
|
| 936 |
+
|
| 937 |
+
- [ ] **Step 5.5: Canonical-phrasing audit β structural phrase on landing page**
|
| 938 |
+
|
| 939 |
+
Run:
|
| 940 |
+
|
| 941 |
+
```bash
|
| 942 |
+
grep "named residual risks for LLM01, scope limits for LLM02" /Users/zenith/Desktop/agent-bench/agent_bench/serving/static/index.html
|
| 943 |
+
```
|
| 944 |
+
|
| 945 |
+
Expected: one hit. This is the structural phrase the landing subtitle carries; if absent, Task 4 dropped the required wording.
|
| 946 |
+
|
| 947 |
+
- [ ] **Step 5.6: Framing-discipline audit β SECURITY.md uses mechanism framing, DECISIONS.md uses failure-mode framing**
|
| 948 |
+
|
| 949 |
+
Run:
|
| 950 |
+
|
| 951 |
+
```bash
|
| 952 |
+
# SECURITY.md should use mechanism framing
|
| 953 |
+
grep -c "URL-against-retrieved-chunks check\|always-on secret-format deny list" /Users/zenith/Desktop/agent-bench/SECURITY.md
|
| 954 |
+
# DECISIONS.md new entry should use failure-mode framing (extract the entry first)
|
| 955 |
+
awk '/^## Why named residual risks and scope limits/,/^## Why additive SSE stage events/' /Users/zenith/Desktop/agent-bench/DECISIONS.md | grep -c "URL hallucination\|PII leakage"
|
| 956 |
+
```
|
| 957 |
+
|
| 958 |
+
Expected: first command β₯ 1 (mechanism phrases in SECURITY.md), second command β₯ 1 (failure-mode phrases in DECISIONS.md). If either returns 0, the framings may have swapped β inspect and repair.
|
| 959 |
+
|
| 960 |
+
- [ ] **Step 5.7: Cross-link verification β source files exist**
|
| 961 |
+
|
| 962 |
+
Run:
|
| 963 |
+
|
| 964 |
+
```bash
|
| 965 |
+
ls /Users/zenith/Desktop/agent-bench/agent_bench/security/injection_detector.py \
|
| 966 |
+
/Users/zenith/Desktop/agent-bench/agent_bench/security/pii_redactor.py \
|
| 967 |
+
/Users/zenith/Desktop/agent-bench/agent_bench/security/output_validator.py \
|
| 968 |
+
/Users/zenith/Desktop/agent-bench/agent_bench/tools/registry.py \
|
| 969 |
+
/Users/zenith/Desktop/agent-bench/agent_bench/agents/orchestrator.py \
|
| 970 |
+
/Users/zenith/Desktop/agent-bench/agent_bench/serving/middleware.py \
|
| 971 |
+
/Users/zenith/Desktop/agent-bench/pyproject.toml \
|
| 972 |
+
/Users/zenith/Desktop/agent-bench/Dockerfile
|
| 973 |
+
```
|
| 974 |
+
|
| 975 |
+
Expected: all 8 files listed, no errors.
|
| 976 |
+
|
| 977 |
+
- [ ] **Step 5.8: Cross-link verification β DECISIONS.md anchors exist**
|
| 978 |
+
|
| 979 |
+
Run:
|
| 980 |
+
|
| 981 |
+
```bash
|
| 982 |
+
grep -E "^## (Why two-tier injection detection|Why regex \+ optional spaCy|Why three output validators|Why no authentication|Why a relevance threshold)" /Users/zenith/Desktop/agent-bench/DECISIONS.md
|
| 983 |
+
```
|
| 984 |
+
|
| 985 |
+
Expected: 5 lines, one for each anchor SECURITY.md cross-links to.
|
| 986 |
+
|
| 987 |
+
- [ ] **Step 5.9: Cross-link verification β SECURITY.md anchors exist for DECISIONS.md entry**
|
| 988 |
+
|
| 989 |
+
Run:
|
| 990 |
+
|
| 991 |
+
```bash
|
| 992 |
+
grep -E "^### (LLM01 Prompt Injection|LLM02 Sensitive Information Disclosure)" /Users/zenith/Desktop/agent-bench/SECURITY.md
|
| 993 |
+
```
|
| 994 |
+
|
| 995 |
+
Expected: 2 lines, one for each anchor the DECISIONS.md entry cross-links to.
|
| 996 |
+
|
| 997 |
+
- [ ] **Step 5.10: Run the test suite**
|
| 998 |
+
|
| 999 |
+
```bash
|
| 1000 |
+
cd /Users/zenith/Desktop/agent-bench
|
| 1001 |
+
make test
|
| 1002 |
+
```
|
| 1003 |
+
|
| 1004 |
+
Expected: 444 tests pass (or the current baseline count, whichever is higher β new tests may have landed on main since the design doc was written). **Failure mode to watch for:** if the landing-page HTML insertion breaks a static-route test that asserts on the HTML structure, investigate whether the assertion is structural-fragile (needs updating to account for the new element) or behavioral (the insertion actually broke something). Structural-fragile assertions can be tightened to check presence, not ordering; behavioral breaks mean the edit is wrong. Do not land the fix if the test break is behavioral.
|
| 1005 |
+
|
| 1006 |
+
- [ ] **Step 5.11: Run the linter**
|
| 1007 |
+
|
| 1008 |
+
```bash
|
| 1009 |
+
cd /Users/zenith/Desktop/agent-bench
|
| 1010 |
+
make lint
|
| 1011 |
+
```
|
| 1012 |
+
|
| 1013 |
+
Expected: ruff + mypy pass with no new issues. Since no Python code changed, this is a consistency check only.
|
| 1014 |
+
|
| 1015 |
+
- [ ] **Step 5.12: Final no-overclaiming scan across all four surfaces**
|
| 1016 |
+
|
| 1017 |
+
Run:
|
| 1018 |
+
|
| 1019 |
+
```bash
|
| 1020 |
+
grep -iE "fully mitigated|coverage guarantee|tests all 10|OWASP.certified" \
|
| 1021 |
+
/Users/zenith/Desktop/agent-bench/SECURITY.md \
|
| 1022 |
+
/Users/zenith/Desktop/agent-bench/DECISIONS.md \
|
| 1023 |
+
/Users/zenith/Desktop/agent-bench/README.md \
|
| 1024 |
+
/Users/zenith/Desktop/agent-bench/agent_bench/serving/static/index.html
|
| 1025 |
+
```
|
| 1026 |
+
|
| 1027 |
+
Expected: no output. If anything matches, inspect for disclaimer-vs-claim context (SECURITY.md's "What this doc is not" legitimately contains "does not constitute OWASP certification" β that's a disclaimer). Non-disclaimer matches must be rephrased.
|
| 1028 |
+
|
| 1029 |
+
- [ ] **Step 5.13: Verify word counts**
|
| 1030 |
+
|
| 1031 |
+
```bash
|
| 1032 |
+
wc -w /Users/zenith/Desktop/agent-bench/SECURITY.md
|
| 1033 |
+
awk '/^## Why named residual risks and scope limits/,/^## Why additive SSE stage events/' /Users/zenith/Desktop/agent-bench/DECISIONS.md | wc -w
|
| 1034 |
+
```
|
| 1035 |
+
|
| 1036 |
+
Expected: SECURITY.md count between 500 and 800 (target ~790, per the drafting-time drift); DECISIONS.md entry count between 290 and 360 (target ~320).
|
| 1037 |
+
|
| 1038 |
+
- [ ] **Step 5.14: Push to origin**
|
| 1039 |
+
|
| 1040 |
+
```bash
|
| 1041 |
+
cd /Users/zenith/Desktop/agent-bench
|
| 1042 |
+
git log --oneline -6
|
| 1043 |
+
git push origin part-a/owasp-mapping
|
| 1044 |
+
```
|
| 1045 |
+
|
| 1046 |
+
Expected: `git log` shows 5 commits on this branch (1 design doc + 1 self-review fixes + 1 SECURITY.md + 1 DECISIONS.md entry + 1 README tail + 1 landing subtitle = 6 commits on `part-a/owasp-mapping` beyond main). Push succeeds; origin/part-a/owasp-mapping updated.
|
| 1047 |
+
|
| 1048 |
+
- [ ] **Step 5.15: Ready for PR**
|
| 1049 |
+
|
| 1050 |
+
Surface the branch state to Jane with a short summary:
|
| 1051 |
+
|
| 1052 |
+
- Branch: `part-a/owasp-mapping`
|
| 1053 |
+
- Commits: 1 design doc + 1 self-review + 4 implementation surface commits = 6 total
|
| 1054 |
+
- PR target: `main`
|
| 1055 |
+
- PR body should reference the design doc + the locked Part A design
|
| 1056 |
+
- No HF Spaces push (Part A is not a deploy)
|
| 1057 |
+
|
| 1058 |
+
Do not open the PR yourself; surface the state and let Jane decide whether to open the PR now or wait.
|
| 1059 |
+
|
| 1060 |
+
---
|
| 1061 |
+
|
| 1062 |
+
## Plan-level self-review
|
| 1063 |
+
|
| 1064 |
+
Before marking this plan ready for execution, verify:
|
| 1065 |
+
|
| 1066 |
+
1. **Spec coverage.** Every locked decision from the design doc's "Locked decisions (from brainstorming)" table (13 entries) is reflected in at least one task step. Cross-cutting #9 (paired-review gate) is covered in Task 2 step 2.10.
|
| 1067 |
+
|
| 1068 |
+
2. **No placeholders.** The LLM01 and LLM02 canonical subsections contain exact text; the DECISIONS.md entry paragraphs contain exact text; the README variants contain exact text; the landing HTML and CSS contain exact text. No "TBD", no "TODO", no "fill in later".
|
| 1069 |
+
|
| 1070 |
+
3. **Type consistency.** Class names (`.security-panel-sub` vs `.sec-sub`), file paths (`agent_bench/security/injection_detector.py`, etc.), and cross-link anchor slugs (`#llm01-prompt-injection`, `#why-two-tier-injection-detection-not-three`, etc.) are consistent across tasks.
|
| 1071 |
+
|
| 1072 |
+
4. **Execution order.** Tasks 1 β 2 β 3 β 4 β 5 is strict per design doc Β§3.2. Task 2's paired-review gate blocks Task 3. No task depends on work from a later task.
|
| 1073 |
+
|
| 1074 |
+
5. **Budget drift documentation.** The SECURITY.md word count overshoot (500β700 target β ~790 realistic) is flagged at the top of this plan and again in Task 1 exit criteria. The overshoot is a precision/phrasing drift per the hybrid drafting-gap protocol and does not require design-doc revision.
|
| 1075 |
+
|
| 1076 |
+
6. **Scope guardrails.** No task modifies `agent_bench/security/*`, no task modifies tests, no task changes routes/app/wiring, no task edits Hugging Face metadata, no task modifies README outside the Security Architecture closing tail.
|
measurements/2026-04-15-coldstart-n1.log
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# HF Spaces cold-start measurement N=1
|
| 2 |
+
# Source: HF Spaces runtime log, container startup trace
|
| 3 |
+
# Container: huggingface.co/spaces/Nomearod/agentbench
|
| 4 |
+
# Build: first v1 deploy (commit 6955d72 on hf/main, main at 8974e47 + frontmatter injection)
|
| 5 |
+
# Cold-start delta: 09:40:34 β 09:38:41 = 113 seconds
|
| 6 |
+
|
| 7 |
+
===== Application Startup at 2026-04-15 09:38:41 =====
|
| 8 |
+
|
| 9 |
+
2026-04-15 09:40:22 [warning ] injection_classifier_no_url msg="Tier 'classifier' configured but classifier_url is empty; classifier tier will be skipped at runtime."
|
| 10 |
+
2026-04-15 09:40:22 [warning ] injection_classifier_no_url msg="Tier 'classifier' configured but classifier_url is empty; classifier tier will be skipped at runtime."
|
| 11 |
+
2026-04-15 09:40:31 [warning ] audit_hmac_key_missing msg='No HMAC key provided; using random per-process key. IP hashes will not be stable across restarts or instances. Set AUDIT_HMAC_KEY env var or pass hmac_key for stable audit correlation.'
|
| 12 |
+
2026-04-15 09:40:31 [info ] corpus_loaded label='FastAPI Docs' name=fastapi providers=['openai', 'anthropic'] rss_delta_mb=0.9 rss_mb=810.8 store_path=.cache/store
|
| 13 |
+
2026-04-15 09:40:31 [info ] corpus_loaded label=Kubernetes name=k8s providers=['openai', 'anthropic'] rss_delta_mb=25.8 rss_mb=835.6 store_path=.cache/store_k8s
|
| 14 |
+
2026-04-15 09:40:31 [info ] multi_corpus_mode corpora=['fastapi', 'k8s'] default=fastapi providers=['openai', 'anthropic']
|
| 15 |
+
INFO: Started server process [1]
|
| 16 |
+
INFO: Waiting for application startup.
|
| 17 |
+
2026-04-15 09:40:31 [info ] warmup_start
|
| 18 |
+
2026-04-15 09:40:32 [info ] reranker_loading model=cross-encoder/ms-marco-MiniLM-L-6-v2
|
| 19 |
+
2026-04-15 09:40:34 [info ] warmup_complete
|
| 20 |
+
INFO: Application startup complete.
|
| 21 |
+
INFO: Uvicorn running on http://0.0.0.0:7860 (Press CTRL+C to quit)
|
| 22 |
+
|
| 23 |
+
# Phase breakdown:
|
| 24 |
+
# Silent Python init: 09:38:41 β 09:40:22 = 101s (interpreter start, module imports, initial model weights)
|
| 25 |
+
# Visible phase: 09:40:22 β 09:40:34 = 12s (injection classifier warnings + corpus loads + reranker warmup)
|
| 26 |
+
# Cold-start total: 113s
|
measurements/2026-04-15-coldstart-n2.log
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# HF Spaces cold-start measurement N=2
|
| 2 |
+
# Source: HF Spaces runtime log, container startup trace
|
| 3 |
+
# Container: huggingface.co/spaces/Nomearod/agentbench
|
| 4 |
+
# Build: post-audit-path fix rebuild (commit 55afe8a on hf/main)
|
| 5 |
+
# Cold-start delta: 10:37:59 β 10:36:30 = 89 seconds
|
| 6 |
+
|
| 7 |
+
===== Application Startup at 2026-04-15 10:36:30 =====
|
| 8 |
+
|
| 9 |
+
2026-04-15 10:37:40 [warning ] injection_classifier_no_url msg="Tier 'classifier' configured but classifier_url is empty; classifier tier will be skipped at runtime."
|
| 10 |
+
2026-04-15 10:37:40 [warning ] injection_classifier_no_url msg="Tier 'classifier' configured but classifier_url is empty; classifier tier will be skipped at runtime."
|
| 11 |
+
2026-04-15 10:37:57 [warning ] audit_hmac_key_missing msg='No HMAC key provided; using random per-process key. IP hashes will not be stable across restarts or instances. Set AUDIT_HMAC_KEY env var or pass hmac_key for stable audit correlation.'
|
| 12 |
+
2026-04-15 10:37:57 [info ] corpus_loaded label='FastAPI Docs' name=fastapi providers=['openai', 'anthropic'] rss_delta_mb=0.9 rss_mb=811.5 store_path=.cache/store
|
| 13 |
+
2026-04-15 10:37:57 [info ] corpus_loaded label=Kubernetes name=k8s providers=['openai', 'anthropic'] rss_delta_mb=25.8 rss_mb=836.3 store_path=.cache/store_k8s
|
| 14 |
+
2026-04-15 10:37:57 [info ] multi_corpus_mode corpora=['fastapi', 'k8s'] default=fastapi providers=['openai', 'anthropic']
|
| 15 |
+
INFO: Started server process [1]
|
| 16 |
+
INFO: Waiting for application startup.
|
| 17 |
+
2026-04-15 10:37:57 [info ] warmup_start
|
| 18 |
+
2026-04-15 10:37:57 [info ] reranker_loading model=cross-encoder/ms-marco-MiniLM-L-6-v2
|
| 19 |
+
2026-04-15 10:37:59 [info ] warmup_complete
|
| 20 |
+
INFO: Application startup complete.
|
| 21 |
+
INFO: Uvicorn running on http://0.0.0.0:7860 (Press CTRL+C to quit)
|
| 22 |
+
|
| 23 |
+
# Phase breakdown:
|
| 24 |
+
# Silent Python init: 10:36:30 β 10:37:40 = 70s (interpreter start, module imports, initial model weights)
|
| 25 |
+
# Visible phase: 10:37:40 β 10:37:59 = 19s (injection classifier warnings + corpus loads + reranker warmup)
|
| 26 |
+
# Cold-start total: 89s
|
measurements/2026-04-15-coldstart-n3.log
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# HF Spaces cold-start measurement N=3
|
| 2 |
+
# Source: HF Spaces runtime log, container startup trace
|
| 3 |
+
# Container: huggingface.co/spaces/Nomearod/agentbench
|
| 4 |
+
# Build: same image as N=2 (commit 55afe8a on hf/main); manual restart via HF Space settings
|
| 5 |
+
# Cold-start delta: 10:49:07 β 10:46:58 = 129 seconds
|
| 6 |
+
|
| 7 |
+
===== Application Startup at 2026-04-15 10:46:58 =====
|
| 8 |
+
|
| 9 |
+
2026-04-15 10:48:53 [warning ] injection_classifier_no_url msg="Tier 'classifier' configured but classifier_url is empty; classifier tier will be skipped at runtime."
|
| 10 |
+
2026-04-15 10:48:53 [warning ] injection_classifier_no_url msg="Tier 'classifier' configured but classifier_url is empty; classifier tier will be skipped at runtime."
|
| 11 |
+
2026-04-15 10:49:05 [warning ] audit_hmac_key_missing msg='No HMAC key provided; using random per-process key. IP hashes will not be stable across restarts or instances. Set AUDIT_HMAC_KEY env var or pass hmac_key for stable audit correlation.'
|
| 12 |
+
2026-04-15 10:49:05 [info ] corpus_loaded label='FastAPI Docs' name=fastapi providers=['openai', 'anthropic'] rss_delta_mb=1.0 rss_mb=811.7 store_path=.cache/store
|
| 13 |
+
2026-04-15 10:49:05 [info ] corpus_loaded label=Kubernetes name=k8s providers=['openai', 'anthropic'] rss_delta_mb=25.8 rss_mb=836.5 store_path=.cache/store_k8s
|
| 14 |
+
2026-04-15 10:49:05 [info ] multi_corpus_mode corpora=['fastapi', 'k8s'] default=fastapi providers=['openai', 'anthropic']
|
| 15 |
+
INFO: Started server process [1]
|
| 16 |
+
INFO: Waiting for application startup.
|
| 17 |
+
2026-04-15 10:49:05 [info ] warmup_start
|
| 18 |
+
2026-04-15 10:49:06 [info ] reranker_loading model=cross-encoder/ms-marco-MiniLM-L-6-v2
|
| 19 |
+
2026-04-15 10:49:07 [info ] warmup_complete
|
| 20 |
+
INFO: Application startup complete.
|
| 21 |
+
INFO: Uvicorn running on http://0.0.0.0:7860 (Press CTRL+C to quit)
|
| 22 |
+
|
| 23 |
+
# Phase breakdown:
|
| 24 |
+
# Silent Python init: 10:46:58 β 10:48:53 = 115s (interpreter start, module imports, initial model weights)
|
| 25 |
+
# Visible phase: 10:48:53 β 10:49:07 = 14s (injection classifier warnings + corpus loads + reranker warmup)
|
| 26 |
+
# Cold-start total: 129s
|
measurements/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# measurements/
|
| 2 |
+
|
| 3 |
+
Raw measurement artifacts referenced from DECISIONS.md entries.
|
| 4 |
+
|
| 5 |
+
Each file is the raw observation (log snippet, trace, or metric dump)
|
| 6 |
+
that backs a specific quantitative claim in DECISIONS.md. Keeping the
|
| 7 |
+
raw data here lets a future reader cross-check any DECISIONS.md number
|
| 8 |
+
against its underlying evidence without having to re-run the
|
| 9 |
+
measurement or trust the narrative summary.
|
| 10 |
+
|
| 11 |
+
Naming: `YYYY-MM-DD-<topic>-<variant>.log`
|
| 12 |
+
|
| 13 |
+
Current entries:
|
| 14 |
+
- `2026-04-15-coldstart-n1.log`, `-n2.log`, `-n3.log` β HF Spaces cold-start samples N=1..3. Backs the DECISIONS.md entry "Cold-start gate fired β assumption falsified, fix deferred to v1.1 at the right cause."
|