---
name: public-endpoint-review
description: OWASP-grounded security audit for public-facing write endpoints (Workers, form handlers, API routes). Produces a structured findings list with severity + location + remediation. Invoke before launch, on material change, and as part of the appsec-reviewer subagent checklist.
---

# Public Endpoint Review

Applies the portfolio's Application Security standard (`Operations/.ops/standards/application-security.md`) to a concrete surface. Produces findings grounded in OWASP Top 10 (2021) + the standard's seven required controls.

## When to Use

- Before a new public write endpoint launches
- After a material change to an existing endpoint (new fields, new method, new side effects)
- As the core audit step inside the appsec-reviewer subagent
- When a security incident prompts a full re-review
- Quarterly on surfaces with sustained traffic

## Prerequisites

Before running this skill, identify:

1. The endpoint's source file (Worker script, route handler, etc.)
2. The frontend surfaces that call it (HTML forms, client JS)
3. The site's `_headers` (or equivalent response-header config)
4. The site's `THREAT-MODEL.md` (run `threat-model-walkthrough` first if missing)
5. The backing store and credential flow

If any are missing, stop and fill the gap before reviewing. A review without a threat model is a checklist, not an audit.

## Review Checklist

Walk the endpoint through all 17 checks. For each, record: PASS / PARTIAL / FAIL / N/A + finding + severity + location + remediation. "N/A" requires a one-line justification.

### Origin + CORS (OWASP A01 Broken Access Control)

**C-01 Origin allowlist enforced server-side**
- Server rejects POST where `Origin` header ≠ allowlist
- Allowlist is explicit, not wildcarded
- Rejection is 403 + generic message (no leak of allowlist)

**C-02 CORS headers do not override origin enforcement**
- `Access-Control-Allow-Origin: *` is not set on write endpoints
- Preflight (OPTIONS) returns the same allowlist as the POST check

**C-03 Method allowlist**
- Endpoint rejects methods outside its contract (e.g. GET on a POST-only route returns 405)
- No ambiguous methods (TRACE, CONNECT) accepted

### Input validation (OWASP A03 Injection, A08 Software and Data Integrity)

**C-04 Required fields enforced + length-capped**
- Missing required field -> 400
- Each field has a documented length cap; rejections report the cap, not the submitted value
- Email validated with shape check; reject obvious malformations (empty local part, no domain, no TLD dot)

**C-05 Character-class validation**
- URL fields match URL shape
- Numeric fields are within allowed range
- Enum fields match an explicit allowlist (segments, statuses, categories)
- Free-text fields have reasonable upper bound + optional regex for clearly disallowed control characters

**C-06 Unicode safety**
- Text fields NFC-normalized before validation
- Zero-width and directional-override characters stripped or rejected from fields displayed back to operators

**C-07 Injection surface**
- Database / Airtable formula queries use the vendor's parameterization (not string interpolation)
- No user-controlled values concatenated into `filterByFormula` or equivalent without escaping
- If shell / exec is ever invoked, arguments are arrays (never shell strings with interpolation)

### Authentication + Secrets (OWASP A02 Cryptographic, A07 Auth Failures)

**C-08 Secrets live in platform secret stores, not in source**
- PAT / API key / shared secrets in `wrangler secret` or equivalent, not in repo
- Env var names not logged in error messages

**C-09 Secrets scope is minimum viable**
- PAT permissions limited to the specific base / table / scope needed
- Secret rotation cadence documented
- Secret has a named location in the password manager

**C-10 No secrets in client bundle**
- Grep the client JS: no PATs, API keys, webhook URLs with auth tokens
- Public keys only (Turnstile site key, etc.)

### Rate limiting + bot mitigation (OWASP A04 Insecure Design)

**C-11 Edge rate limiting**
- Cloudflare Rate Limiting rule (or equivalent) protects the path
- Threshold chosen so legitimate submitter never hits it
- 429 returned with `Retry-After` header

**C-12 Application rate limiting**
- Worker-level counter (Durable Object / KV with TTL) keyed to IP + endpoint
- Protects against bots beating the edge limit

**C-13 Honeypot**
- Every form has a honeypot field (tab-index -1, aria-hidden, off-screen, plausible name)
- Non-empty honeypot -> fake 200 success, no write
- Log the detection

**C-14 High-value form Turnstile / CAPTCHA**
- High-value forms (threat model identifies which) have Turnstile
- Server verifies Turnstile response before processing submission
- Failed verification returns 403; logs the reason

### Logging + error handling (OWASP A09 Security Logging Failures)

**C-15 Structured audit log**
- Every request (success + failure) emits a structured log line
- Log includes: trace ID, timestamp, endpoint, status, reason code, source IP
- Log does NOT include: full message bodies, PII beyond what's needed for debugging
- Log sink is durable (Cloudflare Logs or equivalent), retention ≥ 30 days

**C-16 No information leak via errors**
- Client errors are generic; server logs are specific (tied by trace ID)
- No stack traces, DB error strings, or Airtable error messages in client response
- Submitted values not echoed back unvalidated

### Response headers (OWASP A05 Security Misconfiguration)

**C-17 Security headers + CSP**
- `Strict-Transport-Security` with `max-age >= 31536000; includeSubDomains; preload`
- `X-Content-Type-Options: nosniff`
- `Referrer-Policy: strict-origin-when-cross-origin` or stricter
- `Permissions-Policy` locks down unused features (camera, microphone, geolocation)
- CSP present and does not use `'unsafe-eval'`
- Any `'unsafe-inline'` for scripts documented in the site's THREAT-MODEL.md
- `form-action 'self'` set
- `frame-ancestors 'none'` or equivalent `X-Frame-Options: DENY`

## Finding format

Each finding follows this schema:

```
## [C-NN] Short title

- Severity: critical | high | medium | low | info
- Status: FAIL | PARTIAL
- Location: file:line (or URL)
- Evidence: what you saw (quoted code or response body)
- Remediation: the specific change
- Reference: standard section, OWASP ref, related skill
```

Severity rubric:

- **Critical**: Active exploitability, data loss possible, or credential exposure. Stop launch.
- **High**: Exploitable with effort, or no exploitability today but one pivot away from critical.
- **Medium**: Hardening gap; not directly exploitable but expected in mature posture.
- **Low**: Nice-to-have; no real risk at current scale.
- **Info**: Observation without a rule violation; context for future reviews.

## Output artifact

Produce a findings document at `<surface-root>/security-review-<YYYY-MM-DD>.md` with:

1. Review metadata (reviewer, date, target files, standard version referenced)
2. Summary line: N critical, N high, N medium, N low
3. Findings grouped by severity, descending
4. "No findings" sections for passes, collapsed but enumerated (so re-reviews can see if coverage changed)
5. Refinements suggested for this skill itself (what was unclear, what was missing)

## Remediation priority

Findings must be closed in severity order. Critical before launch; high before launch; medium on a named timeline; low as capacity permits. Re-run the skill after remediation to confirm findings closed (and that no regressions were introduced).

## Feeding the recursion

The last section of every findings doc is "Skill refinements." If during review you:

- Found an issue the checklist did not surface -> add a check item
- Marked something PARTIAL because the check was ambiguous -> sharpen the check wording
- Found the severity rubric insufficient -> propose refinement
- Found the output format didn't capture something the operator needed -> adjust

Update this SKILL.md with the refinement after the review closes. Skill improvements are part of the deliverable, not separate from it.

## Reference

- Standard: `Operations/.ops/standards/application-security.md`
- Companion skills: `threat-model-walkthrough`, `security-hardening-deploy`, `publish-security`
- OWASP Top 10 2021: https://owasp.org/Top10/
- OWASP ASVS: https://owasp.org/www-project-application-security-verification-standard/
