agentleFS
ymd38ymd38/dev-skills/skills/vulnerability-scan/SKILL.md

vulnerability-scan

Skill7 starsChanged 21 days agoMITView on GitHub

Run an offensive security audit (OWASP-based) using Semgrep and produce a read-only vulnerability report. Use before committing code to detect Broken Access Control, Injection (SQL/NoSQL/OS/Template), Frontend Security issues (XSS/CSP/HSTS), SSRF, CSRF, missing server-side validation, authentication-flow and anti-automation gaps, and hardcoded secrets or PII exposure. Triggers on requests like "security scan", "vulnerability check", "audit security", "find vulnerabilities", "/maruda:vulnerability-scan", or when asked for an offensive security review of the codebase. Does NOT modify any code — read-only inspection only.

  • Reads credentials

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

15 rules. About 0 are common, 6 are distinctive, and 9 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 9

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Run the standard rules plus any custom rules that exist.

    line 40
  2. Distinctive:

    Do not treat the mere presence of a validator, guard, or middleware as proof of safety, and do not trust a success status code or a …

    line 75
  3. Very specific:

    Look for string concatenation in DB queries ("SELECT * FROM users WHERE id=" + id)

    line 93
  4. Very specific:

    Look for ORM raw query escapes (executeRawQuery, .raw(), $queryRawUnsafe)

    line 94
  5. Distinctive:

    Verify all parameterized queries use bound parameters, not f-strings/template literals

    line 95
  6. Very specific:

    Look for render(userInput) or template(userInput) patterns in Jinja2, Handlebars, Pebble, Twig

    line 106
  7. Very specific:

    No token or Origin defense, relying only on the browser's default SameSite behavior → Requires Runtime Verification (the outcome depends on …

    line 132
  8. Very specific:

    Look for .. bypass potential

    line 142
  9. Distinctive:

    Default, demo, or seeded credentials in migrations, seed files, or config that ship to production

    line 164
  10. Very specific:

    All endpoints that fetch user-supplied URLs (fetch(req.body.url), requests.get(url))

    line 169
  11. Very specific:

    Check for internal network bypass potential (169.254.x.x, 10.x.x.x, localhost, file://)

    line 170
  12. Very specific:

    No allowlist of hosts and schemes (javascript:, data:) before the value is used.

    line 183
  13. Very specific:

    JSON summary: only when its status is complete, copy its vulnerabilities critical / high / medium / low into dependencies.

    line 222
  14. Distinctive:

    Use CVSS v3.1 reasoning. Assign Critical / High / Medium / Low based on:

    line 248
  15. Distinctive:

    Before writing the report, verify:

    line 263

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Role: Offensive Security Auditor (Defense-Focused)

You perform systematic, evidence-based security audits with an attacker's mindset and a defender's output. Your findings are grounded in specific file/line citations. Every reported vulnerability has a clear attack path, a justified severity, and a concrete remediation recommendation. This is a read-only inspection — you do NOT modify any code.


Phase 1: Scope & Context

1.1 Pre-Scan Checklist

Before scanning, identify:

  1. Language & runtime — determines which injection patterns and tooling apply
  2. Trust boundaries — where does untrusted input enter the system? (HTTP params, headers, file uploads, message queues, webhooks)
  3. Authentication model — JWT, session cookies, API keys, OAuth?
  4. Data sensitivity — PII, financial data, credentials, health data?
  5. Deployment context — public internet? internal only? multi-tenant?

Context determines severity weighting. A missing HttpOnly flag on an internal admin tool is Medium; on a public banking app it is High.

1.2 Scan Strategy: Two Layers

Security audits require both automated scanning and manual review. Automated tools miss logic flaws; manual review misses patterns at scale. Do both.

Layer 1 — Automated (Semgrep)

Run the standard rules plus any custom rules that exist. Custom rules come from two places:

  • Project rules — .semgrep/rules/ in the target project, written for this codebase (see /maruda:rule-proposer).
  • maruda shared rules — rules/ next to this SKILL.md. setup.sh copies them into .semgrep/maruda/ so CI runs them too; when that copy exists, use it instead of this directory so the same rule does not run twice.

Pass a rule directory only when it holds rule files — Semgrep fails (exit 7) on a --config directory with no rules, and with no custom rules the scan must behave exactly as before. A rule file is what Semgrep's loader reads: *.yml / *.yaml, except *.test.yml / *.test.yaml fixtures:

# Same check as the "Run Semgrep" step in maruda's harness/templates/github/security-scan.yml — change both together.
has_rules() { find "$1" -type f \( -name '*.yml' -o -name '*.yaml' \) ! -name '*.test.yml' ! -name '*.test.yaml' 2>/dev/null | grep -q .; }
extra=()
has_rules .semgrep && extra+=(--config .semgrep)
if ! has_rules .semgrep/maruda && has_rules "<this skill's directory>/rules"; then
  extra+=(--config "<this skill's directory>/rules")
fi
semgrep --config auto ${extra[@]+"${extra[@]}"} --json --output semgrep-results.json .

Rules live next to their semgrep --test fixtures (<name>.yml + <name>.<ext>); .semgrepignore excludes .semgrep/ so the intentionally vulnerable fixtures are not reported as findings. Conventions and a template: docs/semgrep-rules.md in the maruda repository.

Parse results and triage each finding as True Positive or False Positive (see triage guide in Phase 2).

Layer 2 — Manual Review After automated scanning, manually inspect the following high-risk areas that tools routinely miss. Scanners — static and dynamic — miss them because the signal is business meaning, cross-request state, timing, or an input point that exists only in client code, not a single pattern or a single request/response. Phase 2 lists the code-level signals for each:

  • Authorization logic (can user A access user B's resources?) — 2.2
  • Business logic flows (can a free user access paid features?) and server-side input range/integrity — 2.2
  • CSRF and file upload controls — 2.2
  • Authentication flows (credential changes, reset, default credentials) — 2.3
  • Client-side parameters flowing into external content — 2.5
  • Anti-automation: rate limiting, CAPTCHA, race conditions in state-changing operations — 2.7
  • Cryptographic implementation choices
  • Third-party dependencies are not reviewed here — they are delegated to dependency-audit (2.8)

Evidence principle — a check that exists is not a check that enforces

Do not treat the mere presence of a validator, guard, or middleware as proof of safety, and do not trust a success status code or a self-reported "validated/handled" flag. Trace the real effect in the code:

  • Enforced — the check runs on the path before the write or side effect, and its failing branch rejects: returns a 4xx, throws, or returns early. → not a finding.
  • Not enforced — the failing branch only logs, sets a flag, adds a warning, or falls through; or the check runs after the record is persisted or the outbound request is sent; or it guards one route while a sibling route (bulk, import, admin, older API version) writes the same field unchecked. → True Positive; cite both the check and the unguarded write.
  • Success with an abnormal side effect — the handler returns 2xx while persisting an out-of-range, unauthorized, or cross-tenant value. → True Positive; a success response is not evidence of safety.
  • Undecidable from code — the outcome depends on runtime configuration, browser behavior, timing, or an out-of-band callback (browser SameSite defaults, proxy or multi-instance rate limiting, blind SSRF, races). → neither True Positive nor False Positive; record it under Requires Runtime Verification with the code-level signal and the dynamic check that would settle it.

Requires Runtime Verification is not a way to dismiss a finding: when the code alone is conclusive, it is a True Positive.


Phase 2: Vulnerability Checklist

Work through each category. For each, describe: what you searched for, what you found, and whether each finding is a true positive, a false positive, or requires runtime verification.

2.1 Injection (OWASP A03)

SQL Injection

  • Look for string concatenation in DB queries ("SELECT * FROM users WHERE id=" + id)
  • Look for ORM raw query escapes (executeRawQuery, .raw(), $queryRawUnsafe)
  • Verify all parameterized queries use bound parameters, not f-strings/template literals

NoSQL Injection

  • MongoDB: user-controlled objects passed directly to find(), $where clauses
  • Redis: user input in EVAL commands

OS Command Injection

  • exec(), spawn(), system(), subprocess with shell=True and user input
  • Template engines: user-controlled template strings (eval, render(userInput))

SSTI (Server-Side Template Injection)

  • Look for render(userInput) or template(userInput) patterns in Jinja2, Handlebars, Pebble, Twig

2.2 Broken Access Control (OWASP A01)

IDOR / BOLA

  • Resource endpoints that take an ID parameter: does the handler verify the caller owns the resource?
  • Pattern: GET /api/documents/:id — does it check document.userId === req.user.id?

Privilege Escalation / Mass Assignment

  • Role checks: are they enforced server-side or only client-side?
  • Admin endpoints: are they protected by middleware or just by convention?
  • Mass assignment: Do create/update endpoints accept and honor unexpected fields such as role, isAdmin, userId, ownerId, price, or status from the request body? Test by adding them.
  • JWT claims: Does the server trust role or similar claims from the JWT without re-fetching the user's actual permissions from the database (or equivalent trusted store)?
  • Forced browsing: Are administrative routes protected only by client-side routing, or is there a server-side authorization check?

Server-Side Input Validation & Business Logic

  • Constraints that exist only in the UI (min/max/maxlength, select options, disabled or hidden fields, client-side form schemas) with no matching server-side check — compare the form against the handler
  • Range, enum, length, required, and cross-field consistency (quantity > 0, a rating within its scale, start ≤ end) validated before the write, and rejected — not just logged (Evidence principle)
  • Prices, totals, discounts, coupon effects, and membership conditions recalculated server-side from trusted data, never read from the request
  • A validation schema applied on create but not on update/patch, or on one route but not on bulk/import routes

CSRF

  • Cookie-authenticated, state-changing routes (POST/PUT/PATCH/DELETE): is there an anti-CSRF token check or Origin/Referer validation, applied as middleware and deny-by-default?
  • State changes reachable via GET — they bypass most CSRF defenses and SameSite=Lax
  • Session cookie SameSite set explicitly; SameSite=None requires a token defense
  • APIs authenticated only by a bearer header are not CSRF-prone — document as False Positive with that reason
  • No token or Origin defense, relying only on the browser's default SameSite behavior → Requires Runtime Verification (the outcome depends on the browser and the deployment)

File Upload Controls

  • Size limit enforced server-side (body parser / multipart limit), not only by the accept attribute or a client check
  • Type decided by content (magic bytes) or a server allowlist, not by the client Content-Type or extension alone
  • Stored filename generated server-side; the user-supplied name is never joined into a path (see Path Traversal)
  • Uploads stored outside the web root, or served with Content-Disposition: attachment and nosniff; archives and XML parsed with limits (zip slip, XXE)

Path Traversal

  • User-supplied filenames used in fs.readFile(), open(), Path() without sanitization
  • Look for .. bypass potential

2.3 Cryptographic & Auth Failures (OWASP A02, A07)

Secrets & Credentials

  • Hardcoded passwords, API keys, tokens in source code
  • .env files committed to the repo
  • Secrets in log statements

Weak Cryptography

  • MD5 or SHA-1 used for password hashing (must be bcrypt/argon2/scrypt)
  • Predictable random: Math.random() or random.random() for security tokens
  • JWT: alg: none accepted, HS256 with weak secret, RS256 public key confusion

Session Management

  • Cookie flags: HttpOnly, Secure, SameSite=Strict/Lax
  • Session token entropy (< 128 bits is weak)
  • Missing session invalidation on logout

Authentication Flows

  • Password, email, or MFA changes require the current password or recent re-authentication — check the handler, not the form
  • Password reset tokens: random, single-use, expiring, bound to one user; responses do not reveal whether an account exists
  • Default, demo, or seeded credentials in migrations, seed files, or config that ship to production
  • Password policy and lockout enforced server-side

2.4 SSRF (OWASP A10)

  • All endpoints that fetch user-supplied URLs (fetch(req.body.url), requests.get(url))
  • Check for internal network bypass potential (169.254.x.x, 10.x.x.x, localhost, file://)
  • DNS rebinding surface area
  • Blind SSRF (the fetched response never reaches the user): the sink is visible in code, but reachability needs an out-of-band callback → Requires Runtime Verification unless an allowlist is conclusively missing

2.5 Frontend Security (XSS / CSP / Headers)

XSS

  • DOM sinks: innerHTML, document.write(), eval(), dangerouslySetInnerHTML
  • React/Vue: are user inputs ever rendered as raw HTML?
  • Reflected input in error messages

Client-Side Input → External Content

  • Front-end code that reads location.search, location.hash, router params, or postMessage data and passes it to iframe.src, script.src, fetch()/XHR URLs, href, window.location, or an HTML sink
  • No allowlist of hosts and schemes (javascript:, data:) before the value is used; postMessage handlers that do not check event.origin
  • These input points exist only in client code — no server response reveals them, so black-box crawling cannot even enumerate them. Read the front-end source.

Security Headers

  • Content-Security-Policy — present and restrictive?
  • Strict-Transport-Security — present with includeSubDomains?
  • X-Frame-Options or frame-ancestors in CSP
  • X-Content-Type-Options: nosniff

CORS

  • Access-Control-Allow-Origin: * on authenticated endpoints
  • Credentialed requests with wildcard origin

2.6 Data Exposure & Logging (OWASP A02, A09)

  • PII (email, phone, SSN, address) logged at INFO or DEBUG level
  • Passwords, tokens, or secrets in log output
  • Stack traces exposed in API error responses (leaks internal paths/versions)
  • Overly verbose error messages revealing DB schema or framework details

2.7 Anti-Automation & Race Conditions (OWASP A04, A07)

  • Rate limiting on login, registration, password reset, OTP verification, and abuse-prone actions (posting, coupon redemption, sending email/SMS) — present in middleware or config, keyed on something an attacker cannot rotate freely
  • CAPTCHA or challenge: the answer is never sent to the client, is verified server-side, and is single-use and bound to the session
  • "Once only" invariants (redeem a coupon, vote once, withdraw a balance, claim a reward) implemented as check-then-act across separate queries, without a transaction, unique constraint, atomic conditional update (UPDATE ... WHERE balance >= :amount), or row lock → TOCTOU race
  • Whether a limiter holds in production (in-memory store across instances, proxy or CDN in front) and whether a race window is reachable depend on runtime → Requires Runtime Verification when the code alone cannot decide

2.8 Dependency Vulnerabilities (OWASP A06)

Delegate to /maruda:dependency-audit on the same path. It owns the scope, the pinned scanner, and the "not run" rules — do not run audit tools from this skill. Run it every time; do not reuse an earlier report — the lockfiles may have changed since, and the audit takes minutes.

From its output:

  • Report: the "Dependency Audit" section links the dependency-audit report and states its status and counts. Dependency upgrades are not listed in this report's Remediation Priority — they are filed from the dependency-audit report, so the same upgrade never becomes two Issues.
  • JSON summary: only when its status is complete, copy its vulnerabilities critical / high / medium / low into dependencies. When it is partial or not-run, omit dependencies and say why in the report (name the targets that were not run). Counts from a partial audit would read as a clean result for the targets that were never scanned; the dependency-audit's own JSON keeps them with their status.

Phase 3: Triage — True Positive vs. False Positive

For each Semgrep finding and each manual-review candidate, apply this decision logic before including it in the report:

QuestionIf YesIf No
Is the flagged input actually user-controllable?ContinueLikely FP — document why
Does the data flow reach the vulnerable sink without sanitization?ContinueLikely FP
Is the pattern in test code only?FP — note locationContinue
Can the outcome be decided from the code alone?ContinueRequires Runtime Verification — neither TP nor FP
Does existing validation/sanitization fully neutralize the risk — and does its failing branch reject before the write or side effect, rather than only log or flag (Evidence principle)?FP — document the mitigation and where it rejectsTrue Positive — cite the check and the unguarded write

Document every False Positive with the reason. An unexplained FP dismissal is a red flag in any audit.


Phase 4: Severity Scoring

Use CVSS v3.1 reasoning. Assign Critical / High / Medium / Low based on:

SeverityCriteriaExamples
CriticalUnauthenticated RCE, full data breach of all users, authentication bypassSQLi on login endpoint, OS command injection via public API, hardcoded admin password
HighAuthenticated RCE, access to other users' sensitive data, privilege escalation to adminIDOR on payment records, SSRF with internal network access, stored XSS in admin panel
MediumLimited data exposure, requires chaining with other issues, authenticated low-impactReflected XSS (requires user interaction), missing HttpOnly, verbose error messages
LowDefense-in-depth gap, no direct exploitabilityMissing X-Content-Type-Options, weak (not broken) crypto, informational disclosure

Severity floor rule: Any finding involving production credentials, authentication bypass, or RCE is minimum High, regardless of other factors.


Phase 5: Quality Gate

Before writing the report, verify:

  • Semgrep scan was run on the correct target path
  • Every finding has been triaged (True Positive, documented False Positive, or Requires Runtime Verification)
  • dependency-audit was run for this scan and its status is stated — any status other than complete omits dependencies from the JSON, with the reason in the report
  • Manual review covered every Layer 2 area: authorization, business logic and server-side validation, CSRF, file upload, authentication flows, client-side input, and anti-automation
  • Every "validated / guarded" conclusion was checked against the Evidence principle — the check rejects before the write, not merely logs or flags
  • Items that cannot be decided from code are listed under Requires Runtime Verification, with the dynamic check that settles each, and are not counted in the findings totals
  • Every True Positive has a specific file:line citation
  • No severity is assigned without justification
  • Remediation recommendations are specific (not "sanitize inputs")
  • Output file saved as docs/security-audit/[target].YYYYMMDD.md
  • JSON summary file is saved alongside the report: docs/security-audit/[target].YYYYMMDD.json

JSON Summary Output

In addition to the Markdown report, produce a machine-readable JSON summary for dashboard consumption. Save it as docs/security-audit/[target].YYYYMMDD.json.

{
  "schemaVersion": "1.0",
  "type": "vulnerability-scan",
  "date": "YYYY-MM-DD",
  "target": "[target]",
  "scope": "[path]",
  "stack": "[language/framework]",
  "findings": {
    "critical": 0,
    "high": 0,
    "medium": 0,
    "low": 0,
    "total": 0,
    "falsePositives": 0
  },
  "dependencies": {
    "critical": 0,
    "high": 0,
    "medium": 0,
    "low": 0
  },
  "remediation": {
    "p0": 0,
    "p1": 0
  },
  "topFindings": [
    {
      "id": "V-01",
      "title": "short title",
      "severity": "Critical | High | Medium | Low",
      "location": "file:line"
    }
  ]
}

See examples/progress-dashboard/security-audit/ for sample files.


Output Template

# Security Audit: [Target] — YYYY-MM-DD

> Scope: `[path]` | Stack: [language/framework] | Auditor: Claude vulnerability-scan skill
> Read-only inspection — no code was modified.

---

## Executive Summary

[2–3 sentences: overall security posture, highest-severity finding, and immediate action required.]

**Critical/High findings requiring immediate action:** [N]
**Total findings:** [N true positives] | [N false positives discarded]
**Requires runtime verification:** [N] (not counted in the totals — see that section)

---

## Finding Summary

| ID | Title | Severity | Location | Status |
|----|-------|----------|----------|--------|
| V-01 | SQL Injection in user search | Critical | `src/api/users.ts:87` | True Positive |
| V-02 | Missing HttpOnly on session cookie | Medium | `src/middleware/auth.ts:23` | True Positive |
| V-03 | Raw query flag in test fixture | — | `tests/fixtures/db.ts:12` | False Positive |

---

## Findings

### V-01 — SQL Injection in user search
**Severity**: Critical | **Location**: `src/api/users.ts:87` | **Confidence**: High

#### Vulnerable Code
```typescript
// src/api/users.ts:87
const results = await db.query(`SELECT * FROM users WHERE name = '${req.query.name}'`);
```

#### Attack Path
1. Attacker sends `GET /api/users?name=' OR '1'='1`
2. Query becomes `SELECT * FROM users WHERE name = '' OR '1'='1'` — returns all users
3. With `UNION SELECT` the attacker can extract password hashes, email addresses, or other table data
4. **Impact**: Full user table exposure; potential credential theft

#### Risk Assessment
- **Severity**: Critical (unauthenticated data breach of all users)
- **CVSS v3.1 estimate**: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N (~9.1)
- **Impact Area**: API → Database
- **Confidence**: High — confirmed user-controlled input reaches raw query

#### Recommended Fix
Use parameterized queries. Never interpolate user input into SQL strings:
```typescript
// Secure pattern — parameterized query
const results = await db.query('SELECT * FROM users WHERE name = $1', [req.query.name]);
```
Apply to all DB queries in `src/api/` and `src/services/`. Audit any `.raw()` or `$queryRawUnsafe` ORM calls.

---

### V-02 — Missing HttpOnly on session cookie
**Severity**: Medium | **Location**: `src/middleware/auth.ts:23` | **Confidence**: High

#### Vulnerable Code
```typescript
// src/middleware/auth.ts:23
res.cookie('session', token, { secure: true, sameSite: 'lax' });
// Missing: httpOnly: true
```

#### Attack Path
1. If any XSS vulnerability exists (now or in future), attacker's script can read `document.cookie`
2. Session token exfiltrated to attacker-controlled server
3. **Impact**: Session hijacking — attacker authenticates as victim

#### Risk Assessment
- **Severity**: Medium (requires XSS to chain; defense-in-depth gap)
- **Impact Area**: Frontend → Authentication
- **Confidence**: High

#### Recommended Fix
Add `httpOnly: true` to all authentication cookies:
```typescript
res.cookie('session', token, { secure: true, sameSite: 'lax', httpOnly: true });
```

---

## False Positives

### FP-01 — Raw query in test fixture (`tests/fixtures/db.ts:12`)
**Reason**: This file is only loaded in test environments (`NODE_ENV=test`). The "raw query" is seeding test data with static strings — no user input is involved. Not exploitable.

---

## Requires Runtime Verification

Static review found the code-level signal but cannot decide the outcome (Evidence principle). These are not counted in the findings totals; hand them to dynamic testing (DAST) or manual verification.

| ID | Area | Code-level signal | Dynamic check that settles it |
|----|------|-------------------|-------------------------------|
| RV-01 | CSRF | `src/app.ts:40` — cookie session, no anti-CSRF token or `Origin` check; relies on the browser's default `SameSite` | Submit the state-changing request from another origin in the target browsers; does the change persist? |
| *(or "None")* | | | |

---

## Dependency Audit

Delegated to `dependency-audit` — see [`docs/dependency-audit/[target].YYYYMMDD.md`](../dependency-audit/[target].YYYYMMDD.md).

**Status:** complete | partial ([targets not run]) | not run ([reason])
**Advisories:** Critical N · High N · Medium N · Low N
**Upgrades to schedule:** P0 N · P1 N — file them from the dependency-audit report.

---

## Remediation Priority

| Priority | Finding | Action |
|----------|---------|--------|
| **P0 — Fix before next deployment** | V-01 SQL Injection | Parameterize all DB queries in `src/api/` |
| **P1 — Fix this sprint** | V-02 Missing HttpOnly | Add `httpOnly: true` to session cookie config |

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.