security-vulnerability-scan
OWASP Top 10:2025-aligned vulnerability scanner for any codebase. Static-only (no source files modified); produces a persisted Markdown report under
audit/<YYYY-MM-DD>/report.md
in the project root and echoes the path back to the user.
When I Activate
Activate proactively on any of the following — do not wait for the literal phrase "use the security-vulnerability-scan skill":
- "review this code", "code review", "PR review", "review my app"
- "security review", "security audit", "audit this app", "audit my code"
- "scan for vulnerabilities", "vulnerability scan", "find vulnerabilities", "OWASP scan"
- "look for improvements", "find issues", "what could be better", "what should I fix"
- "harden security", "secure this", "make it more secure", "lock down"
- "check for OWASP", "OWASP Top 10", "CVE check", "CWE", "threat model"
- "find secrets", "leaked credentials", "exposed keys", "secret scan"
- "pentest this", "attack surface", "assess risk", "risk assessment"
- "audit dependencies", "vulnerable packages", "dependency audit", "outdated packages"
When in doubt, trigger. Overtriggering this skill is cheap; missing a real security review is expensive.
Read/Write Contract
- Read the project freely: source files, configs, lockfiles, manifests.
- Write exactly one location:
<project root>/audit/<YYYY-MM-DD>/report.md
(or a timestamped variant if it already exists).
- Never modify source files, configs, dependencies, lockfiles, , , or anything outside .
- No outbound network calls are required after Step 0 (clone) completes. Optional dependency-audit shell calls (, , …) require network and exec — mark them as skipped in sandboxed environments.
Step 0 — Bootstrap Decision
Determine whether the working directory already contains a project, or is blank and needs a clone.
bash
# Inside a git repo?
git rev-parse --is-inside-work-tree 2>/dev/null
Also check for telltale project files:
bash
# Glob '**/*' capped — if nothing meaningful, treat as blank.
Use Glob to look for any of:
,
,
,
,
,
,
,
,
,
,
,
,
.
If populated: skip to Step 1.
If blank: ask the user for a GitHub URL. Then clone:
bash
gh repo clone <url> . # preferred — uses gh auth
# fallback if gh unavailable:
git clone <url> .
Re-run the populated check and proceed to Step 1. If both clone commands fail, stop and report the error verbatim; do not improvise.
Step 1 — Recon
Identify the stack so subsequent checks are framework-aware.
| Signal | Detect via | What it tells you |
|---|
| Read | Node/JS; check for express/fastify/koa/next/nest, react/vue/svelte |
| , , | Read | Python; check for django/flask/fastapi |
| Read | Go; framework is in imports (gin/echo/fiber/chi) |
| , , | Read | Java/Kotlin; spring-boot/quarkus |
| Read | Ruby; rails/sinatra |
| Read | Rust; actix-web/axum/rocket |
| Read | PHP; laravel/symfony |
| , | Glob+Read | .NET; aspnetcore version |
| , | Read | Runtime base images; exposed ports; build steps |
| , , | Glob+Read | CI/CD posture for A03/A08 |
| , , , | Glob | IaC for A02 misconfig |
, config/*.{yml,yaml,json,toml}
| Glob | Configuration surface, secret leak risk |
Capture: language(s), web framework(s), DB driver(s), auth library, runtime image, CI provider, package manager. Use this profile to prioritize Step 4 framework checks.
Step 2 — OWASP Top 10:2025 Static Scan
One short scan section per category. Each section ends with a pointer to the reference file, which you load on demand only when triaging a specific finding in that category.
A01:2025 — Broken Access Control
Grep targets (Node/Python/Go syntaxes shown; adapt to stack):
- Missing auth middleware on routes:
app\.(get|post|put|delete|patch)\s*\(
without an //-style guard nearby.
- IDOR risk: queries by primary key that don't scope to the session user:
findByPk\(req\.(params|body|query)\.id\)
, without a tenant/user filter.
- Mass assignment: spread into a model
User.create({...req.body})
, .
- Path traversal: / / with unsanitized user input. Look for allowed in supplied paths.
- CORS wildcard with credentials:
Access-Control-Allow-Origin:\s*\*
next to Access-Control-Allow-Credentials:\s*true
.
- JWT accepted, JWT decode without verify, JWT secret hard-coded.
- Hidden admin/debug routes: grep for , , , and check guard presence.
For full guidance (description, prevention, attack scenarios, mapped CWEs), read references/A01_2025-Broken_Access_Control.md
before recommending fixes.
A02:2025 — Security Misconfiguration
Grep / file checks:
- Debug/verbose error pages in prod: , , defaulting to development.
- Default credentials in configs: , , .
- Permissive CORS, missing Helmet/, missing security headers (
Strict-Transport-Security
, , , , ).
- Open S3/GCS buckets: ,
BucketAccessControl.PUBLIC_READ
, iam.PublicAccessPrevention: inherited
.
- Container hygiene: , left in final stage, without , missing
rm -rf /var/lib/apt/lists/*
.
- Docker-compose with ports bound to dev-only services.
- Terraform/CDK: ingress rules, public RDS, unencrypted S3.
- Sample apps / , , exposed without auth in non-dev.
For full guidance, read references/A02_2025-Security_Misconfiguration.md
.
A03:2025 — Software Supply Chain Failures
Lockfile + dependency checks:
- Confirm a lockfile exists: , , , , , , , . No lockfile is itself a finding.
- Known-vulnerable packages: run if available — , , , , , . Mark as skipped if exec/network is forbidden.
- GitHub Actions pinning: grep workflows for (tag-pinned) vs
uses:\s*[\w/-]+@[0-9a-f]{40}
(SHA-pinned). Tag-pinned third-party actions are findings.
- Registry trust: // referencing unknown registries.
- Auto-update without signature: code that downloads + runs binaries ( in Dockerfile/CI scripts).
- Direct fetches from
raw.githubusercontent.com
or in build scripts.
- SBOM presence: any , , , . Absence is a finding.
For full guidance, read references/A03_2025-Software_Supply_Chain_Failures.md
.
A04:2025 — Cryptographic Failures
Grep targets:
- Weak hashes for security: ,
crypto.createHash\(['"](md5|sha1)['"]\)
, .
- Weak ciphers: ,
Cipher\.getInstance\(['"](DES|RC4|.*ECB)['"]\)
, (deprecated; no IV).
- Bad randomness for security: , , , — flag near token/password-reset/CSRF generation.
- Cert validation disabled:
rejectUnauthorized:\s*false
, , InsecureSkipVerify:\s*true
, ServicePointManager\.ServerCertificateValidationCallback
.
- Hard-coded keys / IVs / secrets:
(?i)(secret|key|password|token|api[_-]?key)\s*[:=]\s*['"][^'"]{8,}['"]
.
- Plain HTTP: URLs to internal services in non-localhost contexts.
- TLS downgrades: , , ciphersuite strings allowing or .
- Password storage without KDF: /// absent and passwords being hashed with bare SHA-* or stored plain.
- AES-GCM nonce reuse risk:
createCipheriv\('aes-...-gcm', key, iv)
where is constant or counter-derived from a recycled source.
For full guidance, read references/A04_2025-Cryptographic_Failures.md
.
A05:2025 — Injection
Grep targets:
- SQL string concatenation / template-literal interpolation:
(query|execute|raw)\s*\(\s*['"\
][^'"`]${'\s+\sreq.f"SELECT . {String.format(.*SELECT`.
- ORM raw escapes: ,
Sequelize\.QueryTypes\.RAW
, Model\.findAll\(\{\s*where:\s*\[
, .
- NoSQL: passed directly to / without shape validation; patterns in user-controlled input.
- Shell injection: / with concatenation;
spawn\(.*,\s*{\s*shell:\s*true
; , , .
- XSS: , , , , , server templates with / on user data.
- SSTI: , , .
- LDAP injection: filter string concatenation with user input.
- Eval/dynamic code: , , , .
- LLM/agent context (2025): tool outputs / fetched docs / RAG context passed verbatim into prompts without separation.
For full guidance, read references/A05_2025-Injection.md
.
A06:2025 — Insecure Design
Static signals (these often need a brief design read, not just grep):
- Login endpoint without rate limiting middleware (, , , gateway-level limit).
- No CAPTCHA / anti-automation on password reset, registration, MFA challenge.
- Workflow endpoints without server-side state checks (e.g., accepts any order ID without re-checking payment state).
- Server-trusted client-computed values: , , written to DB without recomputation.
- Single-tenant query patterns in a multi-tenant schema: any / without in code that handles tenant data.
- URL fetchers that accept arbitrary destinations: , with no allow-list / metadata-IP block.
- File uploads without type/size/storage-path enforcement.
- Recursive parsers / regex without complexity bounds (ReDoS risk).
- No abuse-case tests — search for tests named , , , . Sparse coverage is a finding.
For full guidance, read references/A06_2025-Insecure_Design.md
.
A07:2025 — Authentication Failures
Grep targets:
- Missing MFA enforcement on admin/privileged routes.
- Hard-coded credentials in source:
password\s*=\s*['"][^'"]+['"]
, Authorization:\s*Basic\s+[A-Za-z0-9+/=]+
in code or configs.
- Session ID in URL: , patterns, in redirect URLs.
- Session not rotated on login: look for session-store / after auth without a prior /.
- JWT without revocation: any JWT issuance code with no corresponding blocklist / refresh-token rotation.
- Weak password policy / no breached-password check: registration handler that accepts any non-empty password.
- Username enumeration: differing responses on login/reset/register for "user exists" vs "user does not exist."
- No rate limit on login/reset/MFA endpoints.
- OAuth/OIDC issues: parsed from request and used without allow-list check; parameter missing; accepted on ID tokens.
- Default/well-known passwords in seed data, fixtures, or examples that may be copy-pasted to prod.
For full guidance, read references/A07_2025-Authentication_Failures.md
.
A08:2025 — Software or Data Integrity Failures
Grep targets:
- Insecure deserialization: , (without ), , , , .
- Auto-update without signature verification: code that downloads + executes/installs without checksum or signature check.
- Signed cookie/token verification missing or weak: JWT decoded without ; without ; HMAC compare with instead of constant-time.
- CI workflows running on untrusted PRs with secrets: + checkout of PR head + access to .
- Plugin/extension loading from disk or URL without signature: , , .
- Mutable third-party actions/images: tags, branch refs in lines.
- Missing SBOM/provenance artifacts in release pipeline.
For full guidance, read references/A08_2025-Software_or_Data_Integrity_Failures.md
.
A09:2025 — Security Logging and Alerting Failures
Static-only signals (this category requires runtime context — flag gaps but defer depth to the agent):
- No structured logger in the project (no winston/pino/bunyan, no python setup, no Serilog) — or only in server code.
- Auth events not logged: search / paths for log calls on failure.
- Sensitive data logged: log calls that include , , , , , full request body, full headers.
- No centralized error handler / no request-ID correlation.
- No audit trail for high-value actions (role change, payment, data export) — look for or equivalent on critical write paths.
- No retention policy / no log shipping config (no , , , no Datadog/Splunk/CloudWatch agent config).
For full guidance, read references/A09_2025-Security_Logging_and_Alerting_Failures.md
.
A10:2025 — Mishandling of Exceptional Conditions
Grep targets:
- Silent exception swallowing:
catch\s*\(\s*\w*\s*\)\s*\{\s*\}
, , catch\s*\(_?\)\s*=>\s*\{\s*\}
, try { ... } catch { /* ignore */ }
.
- Generic catches around security calls: / wrapping , , , calls.
- Fail-open patterns: catch blocks that set a permission/role/allowed flag to or return on error.
- Stack-trace responses: error middleware that sends or to clients in non-dev mode.
- Unhandled async rejections: route handlers in Express 4 without or try/catch — flag any without an error catcher.
- Transaction without rollback on error: / without a matching rollback in the catch.
- Different status codes/messages between "exists" and "not found" branches on auth-adjacent endpoints (enumeration via error).
- TOCTOU patterns:
if (fs.exists) { ... fs.read }
— separated check and use.
For full guidance, read references/A10_2025-Mishandling_of_Exceptional_Conditions.md
.
Step 3 — Secret Scan
Walk
,
,
,
/
,
,
,
, source files. Grep patterns:
| Secret class | Regex (anchor to ensure context) |
|---|
| AWS access key | |
| AWS secret key | near / |
| GCP service-account key | "type":\s*"service_account"
, "private_key":\s*"-----BEGIN PRIVATE KEY-----
|
| GitHub PAT (classic) | |
| GitHub fine-grained PAT | github_pat_[A-Za-z0-9_]{82}
|
| Slack token | xox[abprs]-[A-Za-z0-9-]{10,}
|
| Stripe live key | |
| Stripe restricted | |
| Twilio account SID | |
| Generic JWT | eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+
|
| RSA / EC private key | `-----BEGIN (RSA |
| Connection string | (postgres|mysql|mongodb|redis)://[^@/]+:[^@/]+@
|
| Generic high-entropy assignment | (?i)(secret|password|token|api[_-]?key)\s*[:=]\s*['"][A-Za-z0-9+/=_-]{16,}['"]
|
For each match: record file:line, redact the middle of the value in the report (
), and flag whether the file is committed to git (
git log --all --diff-filter=A -- <path>
). Committed secrets require a separate remediation note ("rotate immediately + purge history").
Recommend
gitleaks detect --no-banner
or
as deeper follow-ups if available.
Step 4 — Framework-Aware Checks
Only run the sub-section(s) matching the recon profile from Step 1.
Express / Node.js
- middleware installed and applied to the app.
express.json({ limit: ... })
has a reasonable cap (not unlimited).
- CSRF middleware on state-changing routes (Express 5 lacks built-in CSRF — look for or a custom token check).
- Cookie flags: , , .
- Body parsers don't accept if is wired up (XXE risk).
- Open redirect:
res.redirect(req.query.url)
without allow-list.
- Prototype pollution surface: / on user-controlled keys;
Object.assign({}, req.body)
with no schema gate.
Django / Flask / FastAPI
- in prod settings.
- not committed; loaded from env.
- not in production settings.
- CSRF middleware enabled; justified per use.
- includes , .
- ORM / / usage flagged.
- Flask / on user input.
- FastAPI for auth on every protected route; no
Depends(get_user, use_cache=True)
masking failed lookups.
- SQLAlchemy with f-string interpolation.
Spring / Java
- / on controllers; method-security enabled.
WebSecurityConfigurerAdapter
/ doesn't broad paths.
- CSRF disabled only with justification.
- Actuator endpoints () not exposed publicly.
- / using validated SSL contexts; no .
- Jackson polymorphic deserialization () without an allow-list (deserialization RCE surface).
- JNDI lookups in user-controlled strings (post-Log4Shell awareness).
Rails / Ruby
- enabled.
- enforced — no on user input.
Rails.application.credentials
used; no committed .
- / with user input.
- / with concatenated input.
- YAML.load (not ) on user data.
Go
- has timeouts set (, , ) — defaults are unlimited.
- patterns covered by authentication middleware.
- config doesn't set .
- SQL:
db.Query(fmt.Sprintf(...))
or concatenation.
exec.Command("sh", "-c", userInput)
patterns.
- Open redirect via .
.NET
- attribute on controllers; audited.
- Antiforgery tokens on POSTs.
JsonSerializerSettings.TypeNameHandling != None
(deserialization RCE).
XmlReaderSettings.DtdProcessing != Prohibit
(XXE).
- Connection strings in committed — should be in user secrets / Key Vault.
- ASP.NET Core data-protection keys persisted and protected.
PHP
- , , on input.
- / with user input.
- on cookies/inputs.
- File upload: to web-served directory without extension allow-list.
mysqli_query(... . $_GET[...])
patterns.
Step 5 — Report Output
Determine project root
Use
git rev-parse --show-toplevel
if inside a repo; otherwise the working directory at scan time. Treat that path as
.
Choose the report path (ISO date; collision-safe)
bash
DATE=$(date +%Y-%m-%d)
DIR="<project_root>/audit/${DATE}"
mkdir -p "$DIR"
FILE="${DIR}/report.md"
if [ -e "$FILE" ]; then
TIME=$(date +%H%M%S)
FILE="${DIR}/report-${TIME}.md"
fi
Windows / PowerShell equivalent:
powershell
$date = Get-Date -Format 'yyyy-MM-dd'
$dir = Join-Path $projectRoot "audit\$date"
New-Item -ItemType Directory -Force -Path $dir | Out-Null
$file = Join-Path $dir 'report.md'
if (Test-Path $file) {
$time = Get-Date -Format 'HHmmss'
$file = Join-Path $dir "report-$time.md"
}
The first scan of the day writes
; subsequent scans the same day write
. History is preserved; nothing is overwritten.
Report structure
Write via the
tool with this exact skeleton:
markdown
# Security Assessment — <project name> — <YYYY-MM-DD>
## Summary
- **Overall risk:** Critical | High | Medium | Low
- **Scan time:** <timestamp>
- **Stack:** <languages, frameworks, runtimes from Step 1>
- **Attack surface:** <public endpoints, authn surface, data classes>
- **Components reviewed:** <directories / files in scope>
- **Findings:** N Critical / N High / N Medium / N Low
## Findings
### Critical
#### SEC-001 — <short title>
- **OWASP:** A0X:2025 <Category>
- **CWE:** CWE-NNN
- **Location:** `path/to/file.ts:42`
- **Description:** <what the issue is, in plain language>
- **Attack scenario:** <how an attacker exploits this in practice>
- **Remediation:** <how to fix; include a code-level diff sketch when possible>
- **References:** [`references/A0X_2025-<Title>.md`](references/A0X_2025-<Title>.md)
### High
### Medium
### Low
## Prioritized Remediation
1. <Critical-1 — one-line action>
2. <Critical-2 — one-line action>
3. <High-1 — one-line action>
…
## Recommended Follow-ups
- **Deep manual review:** spawn the `@security-auditor` agent on the top N findings for adversarial validation.
- **Secret scanning:** run `gitleaks detect --no-banner` and `trufflehog filesystem .` to confirm Step 3 coverage.
- **Dependency CVEs:** run the language-appropriate auditor (`npm audit`, `pip-audit`, `cargo audit`, `govulncheck`, …) — re-include in CI if not already.
- **Container scan:** `trivy fs .` or `grype dir:.` for OS-package + dependency CVEs in built images.
- **DAST:** schedule an OWASP ZAP / Burp scan against a staging deployment.
Severity rubric
Apply consistently — these are the only allowed labels.
- Critical — unauthenticated remote code execution, public exfiltration of secrets/PII at scale, privilege escalation to admin from anonymous, complete authentication bypass.
- High — authenticated RCE, IDOR exposing other users' sensitive data, missing auth on sensitive endpoints, hard-coded credentials in a committed file, weak password hashing in production code.
- Medium — XSS in non-admin contexts, CSRF on state-changing endpoints, missing security headers, weak TLS configuration, dependency CVEs with known PoCs but limited blast radius.
- Low — verbose error pages, missing rate limits without immediate abuse path, defense-in-depth gaps, missing audit logging on non-critical actions.
After writing
- Echo the absolute path of the report file back to the user in chat.
- Surface the top 3 highest-severity findings as a one-line preview each.
- Suggest the user add to if they don't want reports tracked in git. Do not modify — surface the suggestion only.
Relationship with Agent
This skill produces a
fast, broad, automatic first pass. The
agent produces a
deep, adversarial, manual review of specific surfaces.
- Use this skill for: every PR review, periodic full-repo sweeps, "is there anything obvious," initial onboarding to an unfamiliar codebase.
- Escalate to when: a Critical/High finding needs validation; a sensitive surface (auth, payment, crypto, multi-tenant data) needs design-level review; the codebase touches a regulated domain (HIPAA, PCI, GDPR).
The recommended workflow: run this skill → user picks the 3–5 highest-impact findings → spawn
to drill in.
Sandboxing Compatibility
- Step 0 clone requires network + exec — skip in a strict sandbox; ask the user to clone manually or run outside the sandbox.
- Dependency auditors (, , , , , …) require network and/or subprocess. If a call fails with permission or network error, mark the section as "Skipped — sandbox" in the report and continue with static analysis only.
- Static checks (Step 1–5) are pure file reads + greps; they work in any sandbox.
Best Practices
- Be conservative on severity. When in doubt, mark Medium and let the human re-rank. Inflated criticals destroy trust.
- One finding per issue. Don't bundle "weak crypto in , , " into one item — they're separate finds with separate fixes.
- Cite file:line in every finding. No exceptions. A finding without a location is unactionable.
- Don't propose code edits in the report body. The report is read-only context; the user/agent applies fixes deliberately afterward.
- No false-positive bait. If a grep hit is in a test file mocking the unsafe pattern intentionally, omit it (or mark Low with "test file — informational only").
- Re-fetch references on demand. When triaging a specific A0X finding, read the matching to ground the remediation language in OWASP terms.
Related Tools
- agent — deep, manual security review of specific surfaces.
- / — secret-scanning beyond the regexes in Step 3.
- / — pattern-based static analysis with curated rule packs.
- / / — vulnerability scanners for dependencies and container images.
- OWASP ZAP / Burp Suite — DAST against a running staging environment.