ADR-0018: Security-by-Default for Web Specifications
Context and Problem Statement
A review of three production projects built with the SDD plugin (spotter, joe-links, claude-ops) revealed that the plugin treats security as entirely opt-in. None of the existing skills prompt for security requirements, inject security checklists, or flag dangerous code patterns. The result is that security posture varies wildly across projects and depends entirely on whether someone remembers to ask for it.
How should the plugin ensure that web-facing specifications include security requirements by default, so that authentication, rate limiting, input validation, and other baseline protections are part of every web spec rather than an afterthought?
Decision Drivers
- Evidence from production failures: claude-ops shipped an entirely unauthenticated dashboard where any network-adjacent user could make arbitrary configuration changes. joe-links has an open redirect vulnerability in its OIDC callback. All three repos use
io.ReadAll(r.Body)withoutMaxBytesReader, allowing unbounded memory allocation from a single request. - Security should be a default, not a feature: The plugin's strength is making good practices the path of least resistance. Security is the most consequential area where the plugin currently provides no guidance.
- Spotter proves retrofitting works but is expensive: Spotter has the best security posture of the three projects, but only because dedicated security audit issues (#94, #101, #107) were created and implemented after the initial build. The plugin should have prompted for these requirements from the start.
- Minimal friction for spec authors: Security sections should be injected automatically with sensible defaults, not require spec authors to remember a checklist.
- Auth-by-default with explicit opt-out: Public endpoints should be the exception that requires justification, not the default that happens when you forget authentication.
Considered Options
- Option 1: Separate
/sdd:securityskill for security auditing and requirements - Option 2: Post-hoc security audit only (add security checks to
/sdd:audit) - Option 3: Security checklist in CLAUDE.md that developers reference manually
- Option 4: Integrated into existing skills —
/sdd:spec,/sdd:plan, and/sdd:checkall gain security awareness (chosen)
Decision Outcome
Chosen option: "Option 4 — Integrated into existing skills", because security is not a separate concern that lives in its own silo; it is a cross-cutting quality attribute that must be present at every stage of the design-to-implementation pipeline. Injecting security into the skills developers already use (spec, plan, check) ensures coverage without requiring anyone to remember to invoke a separate security skill. This mirrors how the plugin already handles traceability (governing comments are part of the normal workflow, not a separate step).
The integration touches four points in the plugin's workflow:
-
/sdd:specinjects a mandatory "Security Requirements" section into every web-facing specification. This section covers authentication, rate limiting, security headers, request body size limits, CSRF protection, and redirect validation. The spec author must explicitly justify any public (unauthenticated) endpoints rather than leaving them unauthenticated by default. -
/sdd:planadds a security checklist to every issue that involves an HTTP endpoint. The checklist covers authentication middleware, input validation, output encoding, rate limiting, and body size limits. This ensures developers see the security requirements at the point of implementation, not buried in a spec they may not re-read. -
/sdd:checkgains security lint patterns that flag known dangerous code constructs:io.ReadAll(r.Body)withouthttp.MaxBytesReader(unbounded memory allocation)template.HTMLwith unsanitized user content (XSS via Go template bypass)http.Redirectwith user-controlled URLs (open redirect)- Endpoint registration without auth middleware (unauthenticated routes)
json.NewDecoderwithoutDisallowUnknownFields(loose input parsing)- CDN
<script>tags withoutintegrityattributes (supply-chain risk) innerHTMLassignments in JavaScript (DOM-based XSS)
-
Auth-by-default: When generating web server specs, all endpoints require authentication. The spec author must explicitly declare which endpoints are public and provide justification for each (e.g., health checks, login pages, OAuth callbacks). This inverts the current default where endpoints are unauthenticated unless someone remembers to add auth.
Evidence: Security Findings Across Production Projects
| Finding | Severity | spotter | joe-links | claude-ops |
|---|---|---|---|---|
| Unauthenticated endpoints | CRITICAL | None (all authenticated) | None (all authenticated) | Dashboard entirely unauthenticated — arbitrary config changes possible |
| Open redirect | HIGH | N/A | OIDC callback vulnerable — user-controlled redirect target | N/A |
| Rate limiting | HIGH | Login endpoint only | None on any endpoint | None on any endpoint |
| Security headers (CSP, X-Frame-Options, etc.) | MEDIUM | Present (post-audit retrofit) | Missing entirely | Missing entirely |
| Request body size limits | MEDIUM | io.ReadAll(r.Body) unbounded | io.ReadAll(r.Body) unbounded | io.ReadAll(r.Body) unbounded |
| CSRF protection | MEDIUM | SameSite=Lax cookies | SameSite=Lax cookies | No CSRF protection |
| Input validation | LOW | Strong | Moderate | Weak |
Spotter's security posture is the best of the three, but only because issues #94 (security headers), #101 (rate limiting), and #107 (auth hardening) were created and implemented as a dedicated security audit effort — the plugin itself never prompted for any of these requirements during the original spec or planning phases.