Security Auditor
eigent-ai/eigent
Audits source code, dependencies and config files for vulnerabilities and hardcoded secrets, using two bundled Python scanners and an OWASP Top 10 checklist.
Audit application source code against the OWASP Top 10 (2021) vulnerability categories — broken access control, cryptographic failures, injection, insecure design, security misconfiguration…
$ npx skills add briiirussell/cybersecurity-skills --skill owasp-audit -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install briiirussell/cybersecurity-skills owasp-audit --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/briiirussell/cybersecurity-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/owasp-audit .claude/skills/owasp-audit && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "owasp-audit" agent skill from https://github.com/briiirussell/cybersecurity-skills/tree/main/skills/owasp-audit into .claude/skills/owasp-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "owasp-audit", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/briiirussell/cybersecurity-skills/tree/main/skills/owasp-auditType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add briiirussell/cybersecurity-skills --skill owasp-audit -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install briiirussell/cybersecurity-skills owasp-audit --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/briiirussell/cybersecurity-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/owasp-audit .agents/skills/owasp-audit && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "owasp-audit" agent skill from https://github.com/briiirussell/cybersecurity-skills/tree/main/skills/owasp-audit into .agents/skills/owasp-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "owasp-audit", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add briiirussell/cybersecurity-skills --skill owasp-audit -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install briiirussell/cybersecurity-skills owasp-audit --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/briiirussell/cybersecurity-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/owasp-audit .cursor/skills/owasp-audit && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "owasp-audit" agent skill from https://github.com/briiirussell/cybersecurity-skills/tree/main/skills/owasp-audit into .cursor/skills/owasp-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "owasp-audit", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/briiirussell/cybersecurity-skills.git --path skills/owasp-audit--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add briiirussell/cybersecurity-skills --skill owasp-audit -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install briiirussell/cybersecurity-skills owasp-audit --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/briiirussell/cybersecurity-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/owasp-audit .gemini/skills/owasp-audit && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "owasp-audit" agent skill from https://github.com/briiirussell/cybersecurity-skills/tree/main/skills/owasp-audit into .gemini/skills/owasp-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "owasp-audit", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install briiirussell/cybersecurity-skills owasp-auditInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add briiirussell/cybersecurity-skills --skill owasp-audit -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/briiirussell/cybersecurity-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/owasp-audit .github/skills/owasp-audit && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "owasp-audit" agent skill from https://github.com/briiirussell/cybersecurity-skills/tree/main/skills/owasp-audit into .github/skills/owasp-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "owasp-audit", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add briiirussell/cybersecurity-skills --skill owasp-audit -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install briiirussell/cybersecurity-skills owasp-audit --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/briiirussell/cybersecurity-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/owasp-audit .opencode/skills/owasp-audit && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "owasp-audit" agent skill from https://github.com/briiirussell/cybersecurity-skills/tree/main/skills/owasp-audit into .opencode/skills/owasp-audit/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "owasp-audit", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
owasp-auditAudit application source code against the OWASP Top 10 (2021) vulnerability categories — broken access control, cryptographic failures, injection, insecure design, security misconfiguration…
Owasp Audit is an agent skill from briiirussell/cybersecurity-skills. Audit application source code against the OWASP Top 10 (2021) vulnerability categories — broken access control, cryptographic failures, injection, insecure design, security misconfiguration, vulnerable components, authentication failures, data integrity, logging failures, SSRF. Use when the user mentions 'OWASP,' 'OWASP Top 10,' 'security audit,' 'security review,' 'secure code review,' 'code security review,' 'vulnerability audit,' 'find vulnerabilities,' 'appsec review,' 'application security audit,' 'check for…
Its SKILL.md is about 8.6k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Security, covering Web application vulnerabilities and Security review. The repository describes itself as: Cybersecurity skills for AI coding agents (Claude Code, Cursor, Codex). The licence is MIT.
4 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit c9ade03. It shows what the files ask for, not the result of running them.
Pre-approves these tools, so the agent can use them without asking each time:
ReadGrepGlobBashWriteFrom allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
npmpiprggitnodebundlecurltscFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
allowed.comschema.orgok.comFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
AUTH_SECRETWEBHOOK_TOKENADMIN_PASSWORDFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Owasp Audit loads about 8.6k tokens when it runs. Until then it costs about 170 tokens; SKILL.md has 3,956 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
allowed-tools: Read, Grep, Glob, Bash, WriteAutomated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from briiirussell/cybersecurity-skills at commit c9ade03, republished under its MIT licence (© briiirussell). 3,956 words, ~8,589 tokens.
.claude/skills/owasp-audit/SKILL.md (or your agent's skills folder).Perform a systematic security audit of application source code against the OWASP Top 10 (2021).
Work through each category systematically. For each, grep for known vulnerability patterns, then read flagged files for deeper analysis.
'use server'action / loader exportcategoryId, projectId, teamId, organizationId) → server validates ownership of the primary record but blindly accepts the FK → ORM relation join later surfaces another tenant's data. Look for formData.get("<id>") / body.<id> passed straight to insert/update without a preceding findFirst({ where: { id, userId } }). For ORM relation joins (Drizzle with:, Prisma include, ActiveRecord includes), trace whether the join target is filtered by the same tenant/ownership predicate as the parent query.?from=, ?next=, ?returnTo=, ?continue=, ?redirect= passed unsanitized to redirect() / Response.redirect(). Restrict to same-origin paths under the expected scope, normalize (new URL(target, "http://localhost").pathname) to defeat traversal like /admin/../foo. Also reject control bytes in the path before redirect: tab/newline/null (\t, \n, \0) — URL parsers strip these and collapse /\tevil into protocol-relative //evil; null bytes can turn the redirect into a 500. Reject any byte in [\x00-\x1F\x7F], any backslash, and any percent-encoded slash/backslash (%2f, %5c).redirect(.*from, redirect(.*next, redirect(.*returnToHardcoded secrets, API keys, or passwords in source
Weak hashing (MD5, SHA1 for passwords instead of bcrypt/argon2/scrypt)
For bcrypt, also check the cost factor. OWASP 2024 guidance is ≥ 12 (cost 10 ≈ 10ms / 100 hashes/sec/core for an attacker)
Type coercion in cryptographic-verification paths. Numeric parsing (parseInt, Number, parseFloat) silently produces NaN for garbage input, and NaN compares as false for both < and >. A timestamp-freshness check if (Math.abs(now - parsed) > tolerance) return false fails to reject NaN — because NaN > tolerance is false. Grep for: parseInt|parseFloat|Number\(.*\) inside verifySignature / validateToken / signed-cookie / JWT-claim code. Each numeric extraction must be followed by if (!Number.isFinite(parsed)) return false before any inequality. Same family: parseInt('0x123', 10) === 0, parseInt('1e10', 10) === 1, parseFloat('Infinity') === Infinity.
Sensitive data in logs, URLs, or localStorage
Missing encryption at rest or in transit
Before recommending VERIFY_PEER for a TLS connection, identify the cert issuer at the deployment target. Many managed services ship self-signed cert chains at lower tiers (Heroku Redis Mini/Hobby, some ElastiCache configurations, Supabase legacy) — VERIFY_PEER fails there without an explicit ca_file: pin. When VERIFY_PEER is genuinely infeasible, present three remediation options in priority order:
VERIFY_NONE with (a) an in-line comment at every call site, (b) a documented compensating control (private network, internal-only routing), (c) a follow-up issue tracking re-verification conditionsNever quietly recommend VERIFY_PEER without checking that the cert chain at the deployment target is verifiable.
Grep for generic secret names AND known provider key prefixes:
password, secret, api_key, private_key, MD5, SHA1, base64sk_live_, sk_test_, rk_live_, whsec_ghp_, gho_, ghu_, ghs_, ghr_AKIA[0-9A-Z]{16}, ASIA[0-9A-Z]{16}AIza[0-9A-Za-z\-_]{35}, service-account JSON ("type": "service_account")xox[baprs]-, xoxe.xoxp-sk-, sk-ant-vercel_blob_rw_git ls-files | xargs grep -lE 'sk_live|ghp_|AKIA[0-9A-Z]{16}|sk-ant-' 2>/dev/null so binaries and gitignored files don't pollute output.Include non-source file extensions in the sweep. Rails cable.yml / database.yml / storage.yml, Kubernetes manifests, and Vercel / Netlify deploy configs routinely contain TLS or cert config that a source-only sweep misses. Concrete sweep for VERIFY_NONE / VERIFY_PEER:
grep -rn "VERIFY_NONE\|verify_mode" \
--include="*.rb" --include="*.yml" --include="*.yaml" \
--include="*.toml" --include="*.json" \
.SQL injection: raw queries with string concatenation, missing parameterized queries
NoSQL injection: unsanitized user input in MongoDB/Convex queries
Command injection: exec(), spawn(), system() with user input
XSS: unescaped user input in HTML, dangerouslySetInnerHTML, v-html.
Inline-script breakout via JSON.stringify. Any <script type="application/ld+json"> or <script>window.__DATA__ = ...</script> that interpolates server data through JSON.stringify is vulnerable — JSON.stringify does NOT escape <, >, &, U+2028, or U+2029. A stored title containing </script><script>alert(1)</script> will break out. The "internal-only object" framing only saves you when every field is guaranteed never to come from user-editable input.
application/ld+json, __html: JSON.stringify, window.__ + JSON.stringify<>&\u2028\u2029 with their \uXXXX Unicode escapes before injecting.Rails ERB sinks: raw(), .html_safe, <%==, sanitize with a permissive allowlist, and simple_format on user input. Grep for these alongside dangerouslySetInnerHTML / v-html.
Sanitizer choice. When remediating an HTML/SVG XSS sink, the fix MUST use a vetted parser-based sanitizer (DOMPurify / isomorphic-dompurify / sanitize-html for JS; bleach for Python). Reject regex-based sanitizers in code review. If unavoidable, a regex sanitizer must:
[/\s] (not just \s) as the attribute-name separator — HTML accepts / between tag name and first attribute: <img/onerror=…><img>, <body>, <video>, <iframe>) — HTML elements instantiate even in SVG-rendering contextson*= regardless of surrounding contextContent-Security-Policy: script-src-attr 'none' as a browser-level backstopSVG uploads as stored XSS. SVG files can carry <script> / onload. Most blob / object storage serves uploads with the declared content-type. Reject image/svg+xml in upload allow-lists unless you have a sanitizer (e.g. DOMPurify SVG profile) and serve with Content-Disposition: attachment.
Sanitize on write AND on render. For stored XSS / injection, sanitize at the trust boundary (write to DB) AND at the render boundary (defense in depth). On finding a stored-XSS bug, plan a one-time backfill migration to sanitize existing data — render-only fixes leave poisoned rows that any new render path will re-expose.
Rails JSON-LD breakout: inside <script type="application/ld+json">, do NOT use j / escape_javascript for field values — j emits \' and \$ (valid JS, invalid JSON), so JSON.parse fails on any field containing an apostrophe or $. Use this idiom instead:
<% schema = { "@context" => "https://schema.org",
"@type" => "Article",
"headline" => @post.title } %>
<script type="application/ld+json">
<%= json_escape(schema.to_json).html_safe %>
</script>to_json handles JSON escaping; json_escape covers <>&\u2028\u2029 against </script> breakout. Verify with round-trip: JSON.parse(json_escape(article.to_json)) equals the source hash.
Template injection: user input in template literals
Grep for: exec(, eval(, innerHTML, dangerouslySetInnerHTML, $where, raw SQL strings
Authentication flows with logic flaws
Missing rate limiting on sensitive endpoints (login, password reset, API)
Business logic constraints only enforced client-side
Background / fire-and-forget jobs inherit the caller's auth context but lose the request-scoped guards. Re-check authorization inside the job, not just at enqueue. Grep for: Promise.all(...).catch(, void someAsync(, .catch(noop), queue enqueue( without re-auth in the worker.
Sister-route audit. When you find a state-machine or immutability guard on one handler (e.g., WHERE … AND signedAt IS NULL on PUT /api/foo/[id]), grep for every other handler that writes the same table:
rg 'update\(\s*tableName\b|\.update\(tableName' --type ts -B1 -A8Each call site needs the same guard, the same userId predicate, and the same conflict-handling (returning() + 0-rows check). Common offender: a POST /:id/send or POST /:id/convert route that ships after the PUT was hardened and was never re-audited.
External-resource-create TOCTOU with billing implications. Any handler that does "SELECT to check, then provider.create(), then INSERT to record the new resource ID" can create orphan resources on the provider side under concurrency. Stripe accounts, Auth0 / Clerk users, SendGrid templates, S3 buckets — all bill or count toward quota whether you stored the ID or not. Fix pattern:
INSERT … ON CONFLICT DO NOTHING (DB UNIQUE constraint is the lock)UPDATE … SET externalId = ? WHERE externalId IS NULL and check 0-rowsprovider.delete(id) best-effort; log on cleanup failureWorker-queue state transitions need atomic claim. Any cron / worker polling pending rows must atomically claim each row before processing. SELECT + process() + UPDATE is a race — two workers (or two overlapping cron invocations) both see the same pending row and both call out, causing duplicate delivery. Fix: UPDATE … SET status='processing' WHERE id=? AND status='pending' RETURNING … — Postgres RETURNING lets you claim and read in one round-trip. If the UPDATE returns 0 rows, someone else got it. Alternative: SELECT … FOR UPDATE SKIP LOCKED (Postgres / Cockroach) for higher-throughput queues.
Multi-tenant webhook signature matching. When an unauthenticated webhook endpoint identifies its tenant by trying each tenant's secret in turn, every request — including garbage — does O(N) DB lookups + O(N) HMAC computations. Attackers flood with random signatures and amplify CPU/DB load without ever passing auth. Defences (compose them):
/^[a-f0-9]{64}$/i for HMAC-SHA256 hex)LIMIT 200)/api/webhooks/foo/<connection_id>) so lookup is O(1)Rate-limit key fallback. If your rate-limit key includes an attacker-controllable or potentially-missing identifier (IP, user-id, session-id), do NOT fall back to a shared constant string when it's absent. Either (a) refuse the request, (b) fall back to a per-resource identifier the attacker can't share (per-email for signup, per-Stripe-customer for billing), or (c) explicitly fail-open and log. A shared 'unknown'/'anon' bucket is a lockout vector — one attacker pinning the bucket locks out every user behind that proxy path.
Configured-but-not-loaded check. Before declaring a security middleware (rate-limit, auth, CSRF, throttle) as "already configured," verify the gem/package is actually installed — not just that the initializer file exists. Initializers wrapped in if defined? Foo / if PACKAGE in sys.modules silently no-op when the package isn't bundled.
| Stack | Check |
|---|---|
| Ruby/Rails | grep -E "^ GEM_NAME " Gemfile.lock (4-space indent = top-level gems) |
| Node | grep "\"PACKAGE_NAME\":" package-lock.json or node -e "require('PACKAGE_NAME')" |
| Python | pip show PACKAGE_NAME |
| Go | grep PACKAGE_PATH go.sum |
For Rails: also verify the middleware is in the runtime stack — bundle exec rails middleware | grep -i FOO.
Debug mode enabled in production configs
Overly permissive CORS policies (Access-Control-Allow-Origin: *)
Missing HTTP security headers (CSP, HSTS, X-Frame-Options, X-Content-Type-Options)
Default credentials or configurations shipped
Verbose error messages exposing stack traces or internals — including validation libraries echoing schema details (e.g. Zod err.issues, Joi error trees) to clients
Baseline header starter values (paste-and-tune):
| Header | Value |
|---|---|
Strict-Transport-Security | max-age=63072000; includeSubDomains; preload — preload requires verifying every *.example.com serves HTTPS first |
X-Content-Type-Options | nosniff |
X-Frame-Options | DENY (defence-in-depth alongside CSP frame-ancestors) |
Referrer-Policy | strict-origin-when-cross-origin |
Permissions-Policy | camera=(), microphone=(), geolocation=(), browsing-topics=() — note interest-cohort is the legacy FLoC name (Chrome ≤ 100); current Chrome uses browsing-topics |
Content-Security-Policy | start with frame-ancestors 'self'; full CSP needs per-site script audit (inline <style>, JSON-LD, analytics) |
HSTS preload submission is sticky — removal takes months. Verify before submitting.
Where security headers live, by framework:
next.config.{js,ts} headers() block; vercel.json headersconfig/initializers/secure_headers.rb, config/application.rbapp.use(helmet())SECURE_* settings in settings.pyRuntime-API mismatch. Code running in Edge / Workers / V8-isolate runtimes can't load Node-only modules. Imports of node:crypto, node:fs, node:buffer, node:net, etc. inside Next.js middleware.ts / Cloudflare Workers / Vercel Edge functions compile cleanly and fail at first request with Failed to load external module. Audit middleware and edge-marked routes for Node-only imports; prefer Web Crypto (crypto.subtle) for portable code
Rails admin-engine mounts. Grep config/routes.rb for engine and dashboard mounts (PgHero, Sidekiq::Web, Flipper UI, Mission Control, Audit1984) and verify the auth middleware in the corresponding initializer applies in every environment reachable from the internet — not just production:
grep -E "mount .+::(Engine|Web|UI)|mount .+::App" config/routes.rb
grep -rn "Rails.env.production?" config/initializers/ | \
grep -B1 -A5 "Auth::Basic\|authenticate\|secure_compare"The README examples for PgHero, Sidekiq::Web, Flipper, and Mission Control all wrap auth in if Rails.env.production?, which leaves staging, review apps, and preview deploys serving the admin UI anonymously. Fix shape: switch the guard to unless Rails.env.local? (Rails 7.1+ helper for development || test), and add a fail-closed check that refuses access when the auth env vars are unset.
Concurrent-execution races on paid endpoints. When an endpoint triggers paid downstream work (LLM calls, third-party APIs, scrape jobs), look for SELECT followed by a runX() call without an intervening atomic claim, and unconditional UPDATE … SET status='processing' writes. Two concurrent requests can both pass the read-side check and both run. Fix: conditional UPDATE … WHERE id = ? AND status = 'pending' RETURNING …, or a Postgres advisory lock. Charge rate-limit budget only on a successful claim so polling and retries don't burn quota.
(Note: This bullet lives under A05 because the failure manifests as a misconfigured invariant guard. The race pattern itself spans A04 / A05 / A08 depending on framing.)
Source-tree hygiene. Grep for sync-conflict duplicates and dead code that could let a reviewer fix the canonical file while leaving a vulnerable copy in place:
find . \( -name '* 2.*' -o -name '* 3.*' -o -name '*.orig' \
-o -name '*~' -o -name '*.bak' \) -not -path '*/node_modules/*'Treat findings as A05 — the canonical file may be patched while the duplicate retains the vulnerability.
Next.js headers() rule merging. Rules in next.config.ts headers() match per route and merge — a more-specific rule does not override headers it doesn't redeclare. Shipping frame-ancestors 'none' + X-Frame-Options: DENY in a /:path* default plus frame-ancestors * in a /embed override results in both frame-ancestors * and X-Frame-Options: DENY on the embed route (contradicting; older browsers may break framing). Verify with curl -I against the deployed origin — config inspection alone misses the merge. Either set XFO in every rule or drop it entirely (CSP frame-ancestors supersedes on modern browsers).
Auth middleware that doesn't exempt bearer/HMAC routes. Cron jobs, Stripe / GitHub webhooks, and any route that authenticates via bearer token or HMAC need to be excluded from session-cookie middleware. Symptom: those routes silently 302 to /login on every deploy or scheduled invocation, the route-level signature check never runs, and the integration appears to "work" until it doesn't.
Bearer-token compare with unset env interpolation. Comparisons that interpolate process.env.X without a presence check — \Bearer ${process.env.WEBHOOK_TOKEN}`— resolve to a literal"Bearer undefined"when the env var is missing. An attacker who guesses the env-not-set condition can replay that literal string. Assert presence at module load:if (!process.env.WEBHOOK_TOKEN) throw new Error(...)`.
API routes returning HTML 302 redirects instead of JSON 401. Auth middleware that 302s every unauthenticated request to /login breaks fetch clients (they follow the redirect and consume HTML), obscures auth state in monitoring, and lets attackers learn endpoint existence by 302 vs 404. API routes should return 401 application/json with a machine-readable body.
npm audit (Node), pip audit (Python), or equivalentnpm audit --omit=dev alongside npm audit and triage by reachability:dependencies) — must fixdependency-audit. A06 here is a one-line sanity check.cookies.set("admin_token", process.env.ADMIN_PASSWORD) and equality-checked on read). Even with httpOnly and secure, this is plaintext-credential storage (CWE-522) and lacks rotation/revocation. Replace with an HMAC-signed expiring token verified via crypto.subtle.verify / crypto.timingSafeEqualsubmitted === expected for passwords, API keys, or signatures leaks length and prefix-match via timing. Use crypto.timingSafeEqual (Node) or crypto.subtle.verify (Web Crypto)password_length requires :validatable. config.password_length in config/initializers/devise.rb is only enforced when the User model includes :validatable in its devise :... declaration. A model with devise :database_authenticatable, :registerable, :recoverable, :rememberable (no :validatable) accepts passwords of any length and any email format, regardless of what the initializer says. Grep: ^\s*devise\s+: in app/models/; flag any line where :validatable is absent. Adding it on an existing app validates on create+update but does not retro-invalidate existing weak passwords.AUTH_SECRET unset silently derives a weak dev value — assert presence at module load: if (!process.env.AUTH_SECRET) throw ...authorize() has no built-in rate limit — wrap or add upstream limiter; otherwise credential stuffing is trivialcookies.set\(.*process\.env, === .*PASSWORD, !== .*SECRET, !== .*Bearer.*\${clientSecret / token may still be held by the client; on completion, the webhook handler may not find the row because the ID has changed (money lands, DB sits at processing forever). Fix: before creating a new resource, retrieve the existing one. If status is non-terminal (processing, requires_payment_method, requires_action) and parameters haven't changed, REUSE it — return the existing clientSecret. Only create a new resource when the prior one is canceled / succeeded or the parameters changed.WHERE status='draft'). If 0 rows, refuse. If 1 row, call the provider. On provider failure, the row already reflects intent — alert and retry. Bonus: this also makes the handler idempotent.catch \{\}, catch (_) \{\}, catch (_e) \{\}, .catch(() => {}), .catch(() => null), try { ... } catch { return null }error.name, status code) without PIIto: address, especially with attacker-influenced subject/body, is a phishing vector against your verified sender's deliverability reputation. Defences (pick one):noreply@ sender with subject/body the attacker can't influencehttp://allowed.com/ (when only https: is expected)https://user:pass@allowed.com/@-host trick: https://allowed.com@evil.com/ (hostname resolves to evil.com)https://allowed.com:8443/https://аllowed.com/ (Cyrillic а) or xn--llowed-pdc.comhttps://allowed.com./ (DNS-equivalent, often missed by string compare)https://allowed.com.evil.com/https://[::1]/https://127.0.0.1/http://2130706433/ → 127.0.0.1http://0x7f000001/http://0177.0.0.1/; zero-padded 0010.0.0.1/ → 8.0.0.1 (octal!)http://[::ffff:127.0.0.1]/ → block the whole ::ffff:* rangehttp://localhost./, http://metadata.google.internal./169.254.169.254, GCP metadata.google.internal, ECS 169.254.170.2100.64.0.0 – 100.127.255.255fe80::/10; unique-local IPv6: fc00::/7redirect: "error" (don't follow attacker-controlled redirects), explicit timeout, no following 3xx into the metadata serviceHost: header, or use a vetted proxynext.config.{ts,js} with images.remotePatterns: [{ hostname: '**' }], domains: ['*'], or any wildcard entry lets attackers route arbitrary URLs through your CPU/bandwidth.remotePatterns, domains: in image configfetch(, axios(, http.get(, urllib, requests.get( with user inputAfter applying a fix, exercise the affected code path — do not stop at typecheck or build. Modern frameworks have runtime-only failure modes that compile cleanly:
node:crypto, node:fs) build successfully but throw on first request.import() or runtime DI surface only when the codepath runs.Bearer ${process.env.X} with X unset becomes a literal that the tests never hit because the test env defines X.For each shipped fix, run the affected route or job and capture the response. tsc --noEmit + build success ≠ fix verified.
For XSS / sanitizer-config fixes, run the canonical payload set through the configured policy and confirm each is neutralized:
<img src=x onerror=alert(1)>
<a href="javascript:alert(1)">x</a>
<a href="data:text/html,<svg onload=alert(1)>">x</a>
<img srcset="javascript:alert(1) 1x,https://ok.com/a.png 2x">
<svg><script>alert(1)</script></svg>
<math><mtext></style><img src=x onerror=alert(1)></math>
<a href="//evil.com">protocol-relative</a>
<svg></svg><img/onerror=alert(1) src=x>A single-pass audit reliably catches the categories on the checklist but misses the specific bypasses that aren't in the checklist (localhost. vs localhost, IPv4-mapped IPv6, status-enumeration via ordered 404/400/401, callback-URL control chars, concurrent-execution races on paid endpoints).
After producing the first report AND after applying fixes, run a second pass with explicit adversarial framing ("assume the author is overconfident; find what they missed") — ideally with a different model or agent entirely, to break correlated blind spots. Treat any disagreement with the first pass as the higher-value finding.
Common things to find in the second pass:
rateLimit doesn't help if you call auth.api.signInEmail(...) programmatically — that bypasses the HTTP router where the limiter attaches.For every OWASP category, document one of three states:
Include an "Items checked and found clean" section in the executive summary. Audit credibility comes from proving you looked, not just from the findings list.
Findings have three possible dispositions:
An "Accepted Risk" entry without all three fields is a real finding being silently dropped under a different label.
For each finding, document:
#### [SEVERITY] A0X: [Title]
**File:** `path/to/file.ts:42`
**CWE:** CWE-XXX
**Description:** [What the vulnerability is and why it matters]
**Vulnerable Code:**
[code snippet]
**Remediation:**
[Fixed code snippet with explanation]
**Verification:** Concrete adversarial input + the command or code path that proves the fix holds. For XSS: the script-tag breakout payload that no longer breaks out. For open redirects: the off-host URL that now rejects. For password-length: the 1-char password that now fails to save. "The linter says it's fine" is not verification — static analysis has known blind spots for correctness bugs that happen to also be security fixes.Produce an executive summary:
# Security Audit Report
## Project: [name]
## Stack: [technologies]
## Date: [date]
### Summary
- Total findings: X
- Critical: X | High: X | Medium: X | Low: X | Info: X
### Findings
[Individual findings as above]
### Prioritized Remediation Plan
1. [Critical fixes — immediate]
2. [High fixes — this week]
3. [Medium/Low — scheduled]© briiirussell, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/owasp-audit of briiirussell/cybersecurity-skills.
Open the folder on GitHubat commit c9ade03
Owasp Audit next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Owasp Audit this skillbriiirussell/cybersecurity-skills | 413 | — | ~8.6k | Automated safety check: Notes | MIT | |
| Security Auditoreigent-ai/eigent | 15k | — | ~1.8k | Automated safety check: Notes | Apache-2.0 | |
| Security Reviewjewbetcha/opentrace | 116 | 18 repos | ~3.1k | Automated safety check: Notes | MIT | |
| Strix Code Vulnerability Scanusestrix/strix | 68k | — | ~1.1k | Automated safety check: Pass | Apache-2.0 | |
| Code Audit3stoneBrother/code-audit | 892 | 1 repos | ~2.7k | Automated safety check: Pass | None | |
| Wooyun Legacytanweai/wooyun-legacy | 1.8k | — | ~1.9k | Automated safety check: Pass | Custom licence |
eigent-ai/eigent
Audits source code, dependencies and config files for vulnerabilities and hardcoded secrets, using two bundled Python scanners and an OWASP Top 10 checklist.
jewbetcha/opentrace
A skill your agent uses when adding authentication, handling user input, working with secrets, creating API endpoints, or implementing payment/sensitive features.
usestrix/strix
Runs a Strix white-box security review that reads the source, then exploits what it finds in a sandbox so each reported issue has a proof-of-concept.
3stoneBrother/code-audit
Professional code security audit skill covering 55+ vulnerability types.
tanweai/wooyun-legacy
WooYun business logic vulnerability methodology — 22,132 real cases across 6 domains (authentication bypass, authorization bypass, payment tampering, information disclosure, logic flaws…
getsentry/skills
Security code review for vulnerabilities. An agent skill from getsentry/skills.
briiirussell/cybersecurity-skills
Apply the NIST AI Risk Management Framework (AI RMF 1.0) and adjacent guidance to AI / ML systems — model lifecycle governance, fairness and bias evaluation, robustness, transparency…
briiirussell/cybersecurity-skills
Audit REST, GraphQL, and RPC APIs against the OWASP API Security Top 10 (2023).
briiirussell/cybersecurity-skills
Learn from public breach disclosures — extract the audit question each one implies and check your own stack.
briiirussell/cybersecurity-skills
Audit cloud infrastructure (AWS, GCP, Azure) for misconfigurations, excessive permissions, and security gaps.
briiirussell/cybersecurity-skills
Audit container images, Dockerfiles, and Kubernetes manifests for misconfigurations, excessive privileges, exposed secrets, and runtime risks.
briiirussell/cybersecurity-skills
Audit cryptography implementation — algorithm choice, key sizes, KDF parameters, IV/nonce handling, signature verification, randomness, TLS configuration, and key rotation.
Categories
Audit application source code against the OWASP Top 10 (2021) vulnerability categories — broken access control, cryptographic failures, injection, insecure design, security misconfiguration…. Owasp Audit is an agent skill from briiirussell/cybersecurity-skills. Audit application source code against the OWASP Top 10 (2021) vulnerability categories — broken access control, cryptographic failures, injection, insecure design, security misconfiguration, vulnerable components, authentication failures, data integrity, logging failures, SSRF.
Owasp Audit fits situations like: the user mentions OWASP; security review; secure code review; code security review.
Run `npx skills add briiirussell/cybersecurity-skills --skill owasp-audit -a claude-code`. Or copy the skill folder (skills/owasp-audit in briiirussell/cybersecurity-skills) into .claude/skills/owasp-audit in your project. Claude Code loads it when a task matches its description.
Run `npx skills add briiirussell/cybersecurity-skills --skill owasp-audit -a codex`. Or copy the skill folder (skills/owasp-audit in briiirussell/cybersecurity-skills) into .agents/skills/owasp-audit in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add briiirussell/cybersecurity-skills --skill owasp-audit -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/owasp-audit, .gemini/skills/owasp-audit, .github/skills/owasp-audit and .opencode/skills/owasp-audit in your project.
Going by SKILL.md and its folder, Owasp Audit needs the command-line tools its instructions call (npm, pip, rg, git, node and bundle) and credentials named AUTH_SECRET, WEBHOOK_TOKEN and ADMIN_PASSWORD. Its frontmatter pre-approves these tools: Read, Grep, Glob, Bash, Write.
SKILL.md names 3 domains. In commands or code: allowed.com, schema.org and ok.com; the agent is likely to contact these when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (pre-approves every shell command (allowed-tools: bash)), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
Owasp Audit is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 8.6k tokens (SKILL.md is roughly 34k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Owasp Audit: Security Auditor (eigent-ai/eigent, 15k stars), Security Review (jewbetcha/opentrace, 116 stars), Strix Code Vulnerability Scan (usestrix/strix, 68k stars) and Code Audit (3stoneBrother/code-audit, 892 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
briiirussell (a GitHub user) maintains it in briiirussell/cybersecurity-skills, which has 413 GitHub stars. The repository holds 25 skills in this directory. The repository was last updated on May 27, 2026.
Source: briiirussell/cybersecurity-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.