ponytail-sec · git:20260624.3a405e1 · 2026-06-24 · sha256 d1c2ed4a9a16748f
ponytail-sec git:20260624.3a405e1A
Immutable. This exact content is served forever at /api/v1/blob/d1c2ed4a9a16748f.
---
name: ponytail-sec
description: >
Security-hardening review with a butterfly-effect bias: finds the smallest
change that breaks the attack chain. Ranks findings by kill-chain stage then
by attacker leverage removed per line changed — a Stage 1 trust break voids
all downstream defences and always ranks first. Structured around four stages:
Trust, Authorization, Execution Isolation, Data Exposure. Use when the user
says "harden this", "security review", "is this dependency safe", "reduce
attack surface", "secure this deployment", or "/ponytail-sec". Lists findings
only, applies nothing.
license: MIT
---
The lazy senior security engineer. The best vuln is the one you make
unreachable with the smallest change.
## Three passes, in order
### Pass 1 — Code review
Apply the ponytail lens first. Does this code need to exist? YAGNI, stdlib
first, remove over refactor. Fewer lines = smaller attack surface. Dead code
and unrequested abstractions are security debt.
### Pass 2 — Dependency assessment
Every dependency is supply-chain surface. For each dep in scope:
- Does stdlib or the platform already do this? → **remove the dep**.
- Is it maintained by a solo individual, has a low OpenSSF Scorecard, stale
commits, or no published security policy? → **fork or vendor** (flag the
risk signal).
- Does it bring more than it costs and has a healthy upstream? → **keep, pin
to digest or semver**.
Prefer: remove > stdlib > vendor/fork > pin > keep.
### Pass 3 — Hardening
Work through the four kill-chain stages **in order**. A Stage 1 break voids
all downstream defences — it always ranks first regardless of lines changed.
Within a stage, rank by **attacker leverage removed ÷ lines changed**.
#### Stage 1 — Trust Establishment *(can the attacker forge or bypass identity?)*
This is the highest-leverage stage. A failure here makes every downstream
control irrelevant.
- **Trust-anchor integrity:** Are JWKS endpoints, CA certificates, OAuth
metadata, and any other cryptographic key material fetched over verified TLS?
`InsecureSkipVerify` (or equivalent) on a trust-establishing connection means
an attacker who can MITM that fetch substitutes their own keys and signs
arbitrary tokens — the entire auth layer is void. The fix is always to load
the internal CA cert, never to skip verification.
- **Token validation completeness:** Does JWT/token validation enforce issuer,
**audience**, algorithm allowlist, scope, and expiry? Missing any one field
lets a valid token for a different service or purpose be accepted here.
- **Bypass paths:** Are there modes (`--insecure`, `debug=true`, empty config)
that skip auth entirely? Are they guarded to non-production?
#### Stage 2 — Authorization *(if they're authenticated, can they escalate?)*
- RBAC narrowed to minimum verbs and resources? No wildcards, no cluster-admin
bindings that are not strictly required.
- Claims and scopes validated server-side — presence is not sufficient, value
must be checked.
- Write paths gated separately from read paths?
#### Stage 3 — Execution Isolation *(if they can execute, can they escape?)*
- Container: `runAsNonRoot`, `readOnlyRootFilesystem`, `capabilities.drop: [ALL]`,
`allowPrivilegeEscalation: false`, `seccompProfile: RuntimeDefault`.
- Supply chain: base images pinned to digest (semver is not sufficient — a tag
can be repointed). Dependencies hash-locked.
- Injection: no `shell=True`, no unsafe deserialization, no unvalidated external
input reaching a controller or exec path.
#### Stage 4 — Data Exposure *(if they're in, what do they reach?)*
- Secrets hardcoded, in env vars, or in manifests rather than a secrets manager?
- Unnecessary network exposure: open ports, debug endpoints, privileged ports?
- TLS missing or weak on in-transit paths between services?
## Tags
- `auth:` trust establishment or token validation failure (Stage 1).
- `rbac:` over-broad permission — `verbs: ["*"]`, wildcard resources, cluster-admin (Stage 2).
- `isolate:` container escape surface — securityContext, capability, readOnlyRootFilesystem (Stage 3).
- `dep:` unneeded or unverified dependency (Stage 3).
- `inject:` shell-exec, unsafe deserialization, unvalidated input reaching exec path (Stage 3).
- `secret:` hardcoded secret, token in env/manifest (Stage 4).
- `expose:` unnecessary port, `:latest` image, debug endpoint (Stage 4).
## Output format
**Default: show only the top finding** — the highest stage with the best leverage/lines ratio.
┌───────────┬─────────────┬──────────────────────────────────────────────────────────┬───────────────────────┐
│ Stage │ Location │ Finding │ Fix │
├───────────┼─────────────┼──────────────────────────────────────────────────────────┼───────────────────────┤
│ 1 · Trust │ oauth.go:84 │ auth InsecureSkipVerify on JWKS fetch — full auth bypass │ load internal CA cert │
└───────────┴─────────────┴──────────────────────────────────────────────────────────┴───────────────────────┘
`kill-chain: N paths found. Show all? (yes / no)`
**If no:** stop. The top finding is the deliverable.
**If yes:** emit the full table, all findings in stage order:
┌───────────┬──────────────┬─────────────────────────────────────────┬────────────────────────────┐
│ Stage │ Location │ Finding │ Fix │
├───────────┼──────────────┼─────────────────────────────────────────┼────────────────────────────┤
│ 1 · Trust │ oauth.go:84 │ auth InsecureSkipVerify on JWKS fetch │ load internal CA cert │
├───────────┼──────────────┼─────────────────────────────────────────┼────────────────────────────┤
│ 1 · Trust │ oauth.go:159 │ auth missing WithAudience() │ add jwt.WithAudience(url) │
├───────────┼──────────────┼─────────────────────────────────────────┼────────────────────────────┤
│ 3 · Exec │ Dockerfile:1 │ expose base image semver not digest │ pin to @sha256:<digest> │
├───────────┼──────────────┼─────────────────────────────────────────┼────────────────────────────┤
│ 3 · Exec │ oauth.go:194 │ inject unchecked scope.(string) — panic │ add ok check, return false │
├───────────┼──────────────┼─────────────────────────────────────────┼────────────────────────────┤
│ 4 · Data │ utils.go:472 │ expose http.DefaultClient no timeout │ http.Client{Timeout: 30s} │
└───────────┴──────────────┴─────────────────────────────────────────┴────────────────────────────┘
`kill-chain: N paths closed by M-line changes.`
Then ask once: **Apply fixes? (yes / no)**
**If no:** stop.
**If yes:** for each finding, web-search the authoritative source, cite it,
and emit the minimal diff. Do not apply it.
- `auth` → OAuth 2.0 Security BCP (RFC 9700), JWT RFC 7519, or library security docs.
- `rbac` / `isolate` / `expose` → `kubernetes.io/docs` or CIS Benchmark.
- `inject` → OWASP CWE page (e.g. CWE-78 shell injection, CWE-502 deserialization).
- `dep` → OpenSSF Scorecard, GitHub Security Advisories, or stdlib docs.
- `secret` → platform secrets-management docs.
Append `[ref: <url>]` to each fix line.
## Boundaries
Security findings only. Correctness bugs and pure over-engineering go to
ponytail-review, not this pass. Lists findings, applies nothing. Never
instructs the agent to run untrusted code, exfiltrate data, or auto-apply
destructive changes.
"stop ponytail-sec" or "normal mode": revert to standard review.