Install
$ agentstack add skill-khasky-awesome-agent-skills-awesome-security-audit ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Security Audit
Review code and config for common security issues so risks are identified and remediated.
When to Activate
- User asks for "security review", "security audit", or "check for vulnerabilities"
- Before a release or after adding auth, payments, or sensitive data handling
- Reviewing new or changed endpoints, file handling, or configuration
- After adding a new dependency or external integration
Work Process
Split the work into a passive phase (reading source, config, and dependency manifests — no gate) and an active phase (running scanners, npm audit/pip audit, dependency resolution that reaches a registry, or any dynamic/network probe — behind the approval gate in step 2). Default to passive.
- Confirm scope, authorization, and mode — Pin down what is in scope and explicitly out of scope (exact origin,
/api, specific modules, test accounts vs real data). Confirm the user owns or is authorized to test the target — required before any active command touches a live system or a package registry. Note the mode: this skill is white-box static review; dynamic and runtime issues are out of its reach (see Scope and limitations). Focus high-risk areas first. - Plan, then gate active steps — Propose the active-scan plan (which scanners, which commands) and wait for approval before running it; passive code and config reading needs no approval. If authorization for an active step is missing, stay passive and say what that leaves unverified. Before recommending or running any scanner, confirm it exists at a known version (
semgrep --version, exit 0) — an assumed-installed tool and an installed one produce different plans; where a tool is absent, mark that gate unavailable rather than skipping it silently. - Detect the stack — Indicator files (
package.json,requirements.txt,go.mod, framework configs) tell you which checks matter and which framework mitigations apply, before scanning. - Work the surface in order — Attack-surface map (enumerate endpoints, inputs, trust boundaries, external integrations) → passive HTTP/config analysis → authentication and authorization model → tenant and object-ownership boundaries → business-logic abuse. Within each phase use the category checklists in
references/checklists.md— load that file first; it is the working checklist. Trace high-value leads as source→sink data flow (taint chains): a secret reaching a network sink, a file read reaching a network sink, or external input reaching a code-exec sink is a finding of its own, distinct from "a secret is present". - Gate findings by confidence — Report a finding only when the vulnerable pattern AND attacker-controlled input are both confirmed by reading the code. Medium-confidence items go to a separate "Needs verification" section with the specific open question. "Potentially" or "theoretically" in a finding means it is not one yet — every reported issue needs a concrete attacker, input, and result. Defense-in-depth suggestions go to a "Hardening notes" section, never into findings. Keep a candidate worklist — every grep hit and scanner line driven to an explicit verdict (confirmed / traced-safe / needs-PoC), never eyeballed-and-forgotten — and run each confirmed candidate through the restate-the-claim, adversarial-revalidation, and already-fixed checks in [references/verification.md](references/verification.md) before it enters the report.
- Design-intent gate — Before flagging a boundary as unhandled, check whether the code explicitly returns/rejects there (
errors.New("cache full"), HTTP 429, buffer-full reject). An explicit designed return is not a bug; apanic/crash at the boundary still is. Deduplicate a repeated pattern into one finding with a count, not N findings. - Escalate criticals immediately — Don't hold a confirmed Critical (RCE, auth bypass, exposed live secret, bulk-PII exposure) for the final report; surface it to the user the moment it's confirmed, with the immediate containment step.
- Sweep for variants — a confirmed finding is a class, not an instance. Before writing it up, search the repo for the same shape: the same sink reached from a different caller, the same missing check on sibling routes, the same pattern copy-pasted into another module. Grep the sink, the vulnerable call, and the fix's absence (
execute(f"after finding one f-string query; every route file after finding one without an ownership check), then read each hit in context. Report the class as one finding listing every location; split it out only where a variant's severity or reachability genuinely differs. Fixing the one caller the report named and leaving four siblings live is the failure this step exists to prevent. - Document findings — Location (file:line or area), issue, impact, and recommended fix. Do not claim "secure"; frame as "no obvious issues in reviewed scope" and suggest further steps (e.g. dependency scan, pentest) if relevant.
- Remediate — Suggest concrete fixes. Do not introduce new secrets or log sensitive data in fixes.
- Stop on impact — If an active step shows signs of affecting the running system or its data (errors, state changes, account lockouts), stop that step and report before continuing.
What not to flag (false-positive control)
- Server-controlled sources are not attacker input:
settings.*, config files, env vars, hardcoded constants, CLI args of admin tools. Attacker-controlled: request params/headers/cookies/body, file uploads, WebSocket messages, third-party webhook payloads, and DB content written by other users. - Framework-mitigated patterns are safe by default: auto-escaping templates (Django, JSX), ORM-parameterized queries. Flag only the escape hatches:
mark_safe,dangerouslySetInnerHTML,.raw(), string-built queries,eval-style templating. - Same API, different verdicts — check context first:
requests.get(request.GET['url'])= flag (SSRF);requests.get(settings.API_URL)= safe;requests.get(f"{settings.BASE}/{path}")= check wherepathcomes from.md5(file_content)for dedup = safe;md5(password)= flag. - Test directories intentionally contain insecure patterns and fake credentials — separate bucket; flag only if the code ships or the credentials are real.
- Centralized auth — Check for framework-level auth middleware (Next.js
middleware.ts, Express middleware chains, Railsbefore_action) before flagging missing per-route auth — this is the #1 auth false positive. - Templated code — A SAST tool that can't fully parse Jinja2/ERB/JSX under-reports there (false negatives), so read templates by hand rather than trusting a clean scan. When citing a scanner rule, use its real ID (e.g.
python.flask.security.injection.sql-injection-with-format-string→ CWE-89) and.semgrepignoregenerated/vendored paths.
Reviewing a diff (PR mode)
When the scope is a diff, triage by change type before reading line-by-line:
| Change type | Primary risk | Look for | |---|---|---| | New endpoint/route | Missing auth/validation/rate-limit | auth check present, input validated, throttled | | DB query change | Injection, over-exposure | parameterized, no SELECT * leaking columns | | Auth/session logic | Privilege escalation, token misuse | token validation, no fail-open, scope checks | | File upload | Path traversal, RCE | MIME+size validation, stored outside web root | | New dependency | Supply chain / CVE | pinned, reputable, npm audit clean | | Env var / config | Hardcoded secret, fail-open default | not committed, no or 'default' | | CI workflow / lockfile / build script | Pipeline compromise | actions pinned by SHA, permissions scoped, no untrusted interpolation, lockfile diff reviewed |
Static analysis and its output
Scanners are an active step (step 2 gate) — they execute rules over the tree and some resolve dependencies. Once approved, treat their output as leads, not findings. The runnable recipe — exact Semgrep/CodeQL commands, --metrics=off, the third-party rulesets (Trail of Bits, 0xdea, Decurity), Pro detection, SARIF merge, and the rule fixture-pair discipline — is in [references/static-analysis.md](references/static-analysis.md); load it before scanning. Then, on the output:
- Every hit passes the same confidence gate as a hand-read finding: vulnerable pattern and attacker-controlled input confirmed by reading the code. A scanner ID in a report with no source trace behind it is a false positive waiting to be argued about in review.
- Cite the real rule ID and its CWE (
python.flask.security.injection.sql-injection-with-format-string→ CWE-89), never a paraphrase — a reader must be able to re-run the exact rule. - When a confirmed class has no rule, write one. A rule ships with two fixtures: the vulnerable snippet it must match and the fixed snippet it must not. A rule with no failing fixture has never been proven to fire; a rule with no passing fixture will flag the fix.
- Port a rule before re-deriving it — the same class in a second language is usually the same rule with different syntax, and the fixture pair carries over.
- SARIF is the interchange format when the output has to reach CI, a code-scanning tab, or another tool. Emit it there; keep the human report separate — a SARIF dump is not an audit report.
- Every suppression carries a reason and an expiry (
# nosec B608 — table name is an enum, not input; revisit 2026-Q4). A bare.semgrepignoreline,# noqa, or baseline file that nobody can date is how a real finding gets inherited as "already triaged". - Know the blind spots. Parsers under-report on templated code (Jinja2, ERB, JSX) and on anything reached through metaprogramming, ORMs, or decorators. A clean scan over those paths is
NOT ASSESSED, not "clean" — read them by hand.
Trust boundaries first
Before the category checklist, map where data crosses a trust boundary (client→server, service→service, user→admin) and run a quick STRIDE pass per boundary — Spoofing, Tampering, Repudiation, Information disclosure, Denial of service, Elevation of privilege. Walk each boundary against the attacker models that can reach it — anonymous, authenticated user, user from another tenant, privileged user, compromised account, malicious integration, insider, automated bot, resource-exhaustion attacker — the cross-tenant, compromised-account, and malicious-integration models surface bugs a per-boundary STRIDE pass alone misses. Prioritize findings where data crosses a boundary; the categories below are the concrete checks. For a design-level review, capture the result as a Threat Register (ID | Component | STRIDE | Threat | Risk | Mitigation); when CVSS doesn't fit, DREAD (Damage, Reproducibility, Exploitability, Affected users, Discoverability) is a quick alternative score.
Checklist by Category
The detailed checklists live in [references/checklists.md](references/checklists.md) — read that file before walking the surface; it is the working checklist of this audit, not optional background. The categories:
| # | Category | Covers | |---|----------|--------| | 1 | Injection | SQL, command/shell, XSS, LDAP/XML, NoSQL operators, deserialization, unicode/control-character smuggling | | 2 | Secrets and credentials | hardcoded secrets, fail-open defaults, storage, leak sinks, scanner layering, git history and burned keys | | 3 | Authentication and authorization | authn vs authz, forgeable client session state, IDOR, multi-tenant, TOCTOU, workflow bypass, replay, step-up, impersonation, refresh tokens | | 4 | Sensitive data | in transit, in responses, in logs, in errors | | 5 | Dependencies | known CVEs and reachability, install-time execution, supply chain | | 6 | Configuration and deployment | safe defaults, fail-open control paths, unsafe-by-default APIs, CORS/headers, uploads, serverless IAM, WAF blind spots, cloud posture | | 7 | LLM / AI integration | output-to-sink flows, prompt assembly, tool surface, cost caps, agentic setups, cross-user memory | | 8 | Business logic and abuse | rate-limit bypass, duplicate/concurrent actions, coupon/refund abuse, denial-of-wallet, privilege laundering, scraping, moderation bypass | | 9 | CI/CD and build pipeline | unpinned actions, privileged triggers, expression injection, token scope, cross-trust caches, runner exposure | | 10 | Cryptographic misuse | broken primitives, weak randomness, nonce reuse, timing oracles and compiler-defeated constant time, secret lifetime in memory, password hashing, JWT, rotation, certificate validation |
Output Format
Open with a findings matrix so the reader sees the whole picture before the detail:
| ID | Title | Severity | Confidence | Location | Status | |----|-------|----------|------------|----------|--------| | F1 | Missing rate limiting on /login | High | High | src/auth/login.py:88 | Open |
Then, for each finding:
**[F#] [file:line or area]** [Short title] — CWE-XXX, OWASP AXX
- **Issue:** [What is wrong.]
- **Prerequisites:** [Attacker preconditions — auth level, tenant, prior compromise, tooling. Sets the real severity.]
- **Source trace:** [The source→sink chain: where attacker input enters and the sink it reaches, as file:line steps.]
- **Impact:** [Concrete attacker + input + result — no "potentially".]
- **Evidence:** [The proving excerpt — vulnerable code lines, or request/response — with live secrets and PII redacted.]
- **Reproduction:** [Minimal steps or PoC that trigger it. Omit only when reading the code is itself the proof.]
- **Existing mitigations:** [What already limits this — partial throttle, framework escaping, a downstream check. Sets residual risk; "none found" is a valid answer.]
- **Recommendation:** [Concrete fix or mitigation.]
- **Regression test:** [When proposing a fix: the test that fails without it and passes with it (write it with awesome-test-writing). Omit for review-only findings.]
- **Severity:** Critical | High | Medium | Low | Informational
- **Confidence:** High (pattern + attacker input confirmed) | Medium (goes to "Needs verification" instead)
- **Compliance:** [optional — the control this maps to, e.g. SOC2 CC6.1, PCI-DSS 3.4, ASVS V2.1.1, WSTG-ATHN-03, alongside the CWE]
Example of a populated finding:
**[F1] src/auth/login.py:88** Missing rate limiting on /login — CWE-307, OWASP A07
- **Issue:** The login endpoint accepts unlimited attempts per account and per IP.
- **Prerequisites:** Anonymous attacker with a list of known emails; no account needed.
- **Source trace:** `request.json['email']`/`['password']` (login.py:80) → `authenticate()` (login.py:88) with no attempt counter in between.
- **Impact:** An attacker runs credential stuffing at thousands of guesses per minute against known emails.
- **Evidence:** `login.py:88` calls `authenticate(email, password)` directly, no throttle, lockout, or counter check on the path.
- **Reproduction:** Fire 1000× POST /login with one email and a password list; every attempt returns 200/401, never 429 or a lockout.
- **Existing mitigations:** None found — no lockout, throttle, or CAPTCHA on the path.
- **Recommendation:** Add per-account lockout with exponential backoff and per-IP throttling at the gateway.
- **Regression test:** Assert the 6th failed attempt within the window returns 429 (fails today, passes after the lockout lands).
- **Severity:** High
- **Confidence:** High
Then these sections after the findings:
- Needs verification — medium-confidence items, each with the specific question that would confirm or kill it.
- Hardening notes — defense-in-depth suggestions that are not vulnerabilities.
- Positive patterns — 1–3 things the code does right (parameterized queries throughout, centralized authz); this calibrates trust in the findings.
- Scope and limitations — what was reviewed and how: white-box
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: khasky
- Source: khasky/awesome-agent-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.