Security Audit logo

Security Audit

Organization
opencrabs
security-audit

Run a comprehensive language-agnostic security & CVE audit, scored 0-100

Overview

Publisheropencrabs
Repositoryopencrabs
Skill namesecurity-audit
Stars
981
Forks
105
Bundled files
Instructions only
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by opencrabs on GitHub. Read the source before you install it.

Installation

Install the Security Audit AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/opencrabs/opencrabs/tree/main/src/docs/reference/templates/skills/security-audit
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/opencrabs/opencrabs.git /tmp/opencrabs
mkdir -p .claude/skills
cp -r /tmp/opencrabs/src/docs/reference/templates/skills/security-audit .claude/skills/security-audit
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Security Audit in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Security Audit on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Security Audit is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

You are a senior security engineer performing a comprehensive security audit of the codebase in the current working directory. The audit must be language-agnostic — detect the project type and dispatch to the appropriate tooling.

Be honest about what you cannot examine. Better to miss theoretical issues than to flood the report with false positives. Each finding must be something a security engineer would confidently raise in a PR review.

Stage 1 — Project detection

Inspect the working directory for manifest files. Multiple manifests = monorepo / polyglot — audit each language stack.

Manifest detectedLanguageAudit dispatch
Cargo.tomlRustcargo audit
package.json + package-lock.jsonNode (npm)npm audit --json
package.json + pnpm-lock.yamlNode (pnpm)pnpm audit --json
package.json + yarn.lockNode (yarn)yarn npm audit --json (yarn 2+) or yarn audit --json (yarn classic)
pyproject.toml / poetry.lock / requirements*.txt / Pipfile.lockPythonpip-audit (preferred) or safety check --json
go.modGogovulncheck ./...
Gemfile.lockRubybundle audit check --update
composer.json / composer.lockPHPcomposer audit --format=json
pom.xml / build.gradle / build.gradle.ktsJava/JVMosv-scanner -r . (preferred — dependency-check-maven is heavyweight and slow)
pubspec.yaml / pubspec.lockDart/Flutterosv-scanner -r .
*.csproj / packages.lock.json.NETdotnet list package --vulnerable --include-transitive
Package.swift / Podfile.lockSwift / iOSosv-scanner -r .
mix.exsElixirmix deps.audit (if installed) or osv-scanner -r .
.terraform.lock.hcl / *.tfTerraformtfsec . and/or checkov -d .
DockerfileOCI imagetrivy fs . or grype dir:.

Universal fallback when no native scanner is available, or to cross-check: osv-scanner -r . (covers most ecosystems via SBOM-style detection).

If the relevant scanner is not installed, report which scanner would run and continue with the static-analysis stages. Do not fail the whole audit.

Stage 2 — Dependency CVE scan

Run the dispatched scanner from Stage 1. Capture:

  • Vulnerabilities (CVE / RUSTSEC / GHSA / etc.) — separate by severity (Critical / High / Medium / Low).
  • Unmaintained / deprecated dependencies — flag as risk-of-future-CVE without a clear upstream patch path.
  • Transitive vs direct — note which advisories are reachable through deps you control vs deeply-buried transitives.

For each advisory record: ID, package, current version, fixed version, transitive path (if any), severity, brief description.

If a vulnerability is DOS-class only (uncontrolled recursion, memory exhaustion, ReDoS) note it but do not count it against the security score — DOS is a separate concern from RCE / data breach / auth bypass.

Stage 3 — Static analysis on the diff

Run git diff main...HEAD (or, if main doesn't exist, the last 20 commits). If neither is available, skip this stage and note the limitation in the report.

Examine the diff for the following pattern catalogs. The patterns differ by language but the categories are universal.

3a. Injection sinks

SQL injection — string interpolation into raw SQL. Look for:

  • Rust: format!("SELECT ... {}", x) followed by query()/query_as()/execute() with the formatted string. Skip if the value goes through ?/$1 placeholders.
  • Python: f-strings or % formatting into cursor.execute(), db.engine.execute(), ORM raw queries.
  • Node/TS: template literals into db.query(), db.exec(), raw Sequelize query, Knex raw().
  • Go: fmt.Sprintf into db.Query/db.Exec (skip ?/$1 parameterized).
  • PHP: variable interpolation into mysqli_query, PDO::query (without prepared statements).
  • Ruby: string interpolation into Model.where(...), raw connection.execute.
  • Java: string concatenation into Statement.execute, JPQL/HQL with + instead of setParameter.

Command injection — shell-out with user-controlled string:

  • subprocess.run(..., shell=True) (Python), subprocess.call(..., shell=True)
  • child_process.exec(...) with template literals (Node) — exec() always uses a shell
  • Runtime.getRuntime().exec(String) (Java) — single-string form invokes shell
  • Command::new("sh").arg("-c").arg(format!(...)) or bash -c {format!()} (Rust)
  • os.system(...) / os.popen(...) (Python)
  • eval, system, backticks (Ruby/Perl/PHP)
  • shell_exec, passthru, backticks (PHP)

Path traversal — file ops with user-controlled path, no normalization:

  • Missing .. rejection
  • Missing absolute-path rejection where relative is required
  • Missing symlink resolution / realpath check
  • Joining user input via os.path.join / Path::join without subsequent containment check

Template / SSTI injection — user input rendered as template syntax:

  • Jinja2.Template(user_input), eval in template engines
  • Rails render inline: with user content
  • Twig/Smarty with raw user input

XXE / XML injection — XML parser with external entities enabled:

  • Java: DocumentBuilderFactory without setFeature("...disallow-doctype-decl", true)
  • Python: lxml.etree.parse without resolve_entities=False
  • .NET: XmlReader / XmlDocument with DtdProcessing.Parse

LDAP / NoSQL injection — string interpolation into LDAP/Mongo filters.

3b. Authentication & authorization

  • Missing auth middleware on state-changing routes (POST/PUT/PATCH/DELETE handlers without requireAuth / @login_required / equivalent)
  • JWT misuse: algorithms=["none"] accepted, hardcoded secrets, no exp check, no signature verification
  • Session fixation: session IDs not regenerated on login
  • IDOR: routes that take an ID without checking the caller owns the resource
  • Authorization bypass: role checks bypassed by re-encoding (case sensitivity, unicode normalization)
  • Privilege escalation paths: tools/handlers that elevate without re-auth

3c. Cryptography & secrets

Weak crypto:

  • MD5 or SHA1 used for security (signing, password hashing, MAC) — not just for cache keys / fingerprints
  • DES, 3DES, RC4, RC2
  • ECB block mode (look for Cipher.getInstance("AES/ECB/..."), aes_ecb, BlockCipher::ECB)
  • Hardcoded IV / nonce — same value reused across calls
  • Predictable RNG for security: Math.random(), rand() without seed entropy, time(NULL) seeding, Python random (vs secrets), Rust rand::thread_rng / rand::random for keys (vs rand::rngs::OsRng)
  • RSA key size < 2048 bits, EC curves below P-256
  • PBKDF2 with iterations < 100k for password hashing; MD5/SHA1 as KDF
  • Custom crypto / "rolled their own"

Hardcoded secrets — regex sweep over the diff:

  • AWS keys: AKIA[0-9A-Z]{16}, aws_secret_access_key
  • GitHub tokens: gh[pousr]_[A-Za-z0-9]{36,}, github_pat_[A-Za-z0-9_]{82}
  • Google API keys: AIza[0-9A-Za-z\-_]{35}
  • Stripe keys: sk_live_[0-9a-zA-Z]{24,}
  • Slack tokens: xox[baprs]-[0-9A-Za-z-]{10,}
  • Slack/Discord webhooks: https://hooks.slack.com/services/..., https://discord.com/api/webhooks/...
  • JWT literals: eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+
  • PEM headers: -----BEGIN (RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----
  • Generic high-entropy strings (Shannon entropy > 4.5 bits/char) assigned to variables named *key*, *token*, *secret*, *password*, *credential*

A literal in test fixtures or a *.example file is fine. A literal in production code paths is not.

3d. Unsafe deserialization

  • Python: pickle.loads, pickle.load, cPickle.loads, yaml.load (vs yaml.safe_load), marshal.loads, dill.loads
  • Java: ObjectInputStream.readObject() on untrusted streams, XMLDecoder.readObject()
  • Ruby: Marshal.load on untrusted input, YAML.load (vs YAML.safe_load)
  • .NET: BinaryFormatter.Deserialize, LosFormatter, NetDataContractSerializer
  • Node: node-serialize, serialize-to-js, funcster
  • Rust: bincode::deserialize on attacker-controlled bytes without a signed envelope
  • PHP: unserialize($_GET[...]), unserialize($_POST[...])

3e. Web-specific (if web framework detected)

  • XSS sinks (only when bypassing the framework's auto-escape):
    • React: dangerouslySetInnerHTML, untrusted URL in href/src/action attributes
    • Vue: v-html
    • Angular: bypassSecurityTrust*
    • Django: |safe, mark_safe(user_input), {% autoescape off %}
    • Rails: raw(...), html_safe on user input
    • Server-rendered templates: string concatenation building HTML
  • CORS misconfig: Access-Control-Allow-Origin: * together with Access-Control-Allow-Credentials: true (the credentials header is ignored when origin is *, but the intent of the combo is usually broken)
  • CSRF: state-changing endpoints without a CSRF token check; SameSite=None cookies without a token
  • Cookie flags: Secure, HttpOnly, SameSite missing on session/auth cookies
  • Open redirect: redirect URL controlled by user input, no allowlist
  • Clickjacking: missing X-Frame-Options / frame-ancestors CSP

3f. TLS / certificate validation bypass

  • Python: requests.get(url, verify=False), ssl._create_unverified_context(), ctx.verify_mode = ssl.CERT_NONE
  • Node: rejectUnauthorized: false, NODE_TLS_REJECT_UNAUTHORIZED=0
  • Go: tls.Config{InsecureSkipVerify: true}
  • Java: custom TrustManager that trusts everything (empty checkServerTrusted), HostnameVerifier returning true
  • Rust: danger_accept_invalid_certs(true), danger_accept_invalid_hostnames(true)
  • curl: -k / --insecure in scripts

3g. SSRF (only when host/protocol is user-controllable)

User input that flows directly into HTTP request hostname or protocol scheme. Skip if only the URL path is user-controllable. Watch for:

  • requests.get(user_url) / fetch(user_url) / http.get(user_url)
  • Webhook handlers that POST back to a user-supplied callback URL
  • File-fetcher tools (image proxy, OG-tag fetcher) that don't allowlist hosts and don't block link-local / metadata IPs (169.254.169.254, 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)

3h. Memory safety (non-Rust only)

  • C/C++: strcpy, strcat, gets, sprintf, memcpy with computed length
  • Go: unsafe.Pointer casts, racy map access without sync
  • Rust: unsafe blocks introduced or expanded — note the soundness obligation; mem::transmute between ABI-incompatible types

3i. Race conditions & TOCTOU

  • File operations that stat-then-open without atomic semantics (os.path.exists followed by open)
  • Unix file creation without O_EXCL for security-critical files (lock files, secret files)
  • Token validation that reads the user record twice (revocation gap)

Stage 4 — Subprocess hygiene sweep

For every shell-out site introduced or modified in the diff:

  • Is the command split into argv form (no shell), or does it go through sh -c / bash -c? Shell form requires escape proof for every interpolated value.
  • Are environment variables scrubbed (PATH set to known-good, no LD_PRELOAD carry-over, no IFS smuggling)?
  • Is the working directory set explicitly, or inherited?
  • Is the child placed in its own process group (setsid / Setpgid) so signals/TTY don't bleed?
  • Is stdin closed or piped from a known source (vs inherited)?
  • Are stdout/stderr captured or piped to a controlled sink?
  • For long-running children: is there a timeout / kill-on-parent-death wiring?

Stage 5 — File permissions & secrets-on-disk hygiene

For every File::create / open(..., 'w') / tempfile::* / write to a config or credential location:

  • Files holding secrets (API keys, passwords, private keys, session tokens): must be 0600 (owner-only).
  • Directories containing secrets: must be 0700.
  • World-writable: never.
  • Group-writable: only with explicit justification.
  • umask set before write where the default could leak (especially in setuid contexts, daemons, init scripts).
  • Tempfiles created via mkstemp / tempfile::NamedTempFile (atomic + 0600), not tempnam / mktemp (race + permissive perms).

Stage 6 — Network surface review

  • Bind addresses: 0.0.0.0 exposes to LAN; 127.0.0.1 is local-only. New listeners should default to local-only unless intentional.
  • TLS configuration: minimum protocol version (no SSLv3, no TLSv1.0/1.1), cipher suite selection (no NULL, no anonymous, no EXPORT).
  • Redirect policy: max-redirect cap (prevent SSRF chain abuse), redirect-target-host-allowlist for sensitive fetchers.
  • Headers on web responses (when applicable): Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options: nosniff, Referrer-Policy.

Stage 7 — Container / infrastructure (if applicable)

For each Dockerfile / docker-compose.yml / *.k8s.yaml / *.tf:

  • Dockerfile USER directive set (not running as root)? Base image age (warn if > 1 year)? Secrets via ENV (anti-pattern — use buildkit secrets or runtime mounts)?
  • Compose / k8s: privileged: true? cap_add: [SYS_ADMIN, ...]? hostNetwork: true? hostPath mounts? Liveness/readiness probes that expose internal ports?
  • Terraform: security groups with 0.0.0.0/0 ingress on non-public ports? S3 buckets without BlockPublicAcls? IAM policies with "Action": "*", "Resource": "*"?

Stage 8 — Score & report

Produce a markdown report with:

Header

  • Project type(s) detected
  • Stages run vs skipped (with reason)

Score: X / 100

Per-dimension breakdown (each scored 0-N, weights sum to 100):

DimensionWeightScoreNotes
Input validation (injection sinks)20
Authentication & authorization15
Cryptography & secrets handling15
Subprocess & file-perm hygiene10
Network surface10
Web/framework hardening (if applicable)10
Dependency posture (CVE scan)15
Container / infra hardening (if applicable)5

Adjust weights when a dimension doesn't apply (no web app, no containers) and re-normalize so the total is still over 100.

HIGH / MEDIUM findings

For each finding (only confidence ≥ 0.8):

  • File:line
  • Severity (HIGH / MEDIUM)
  • Category (e.g. sql_injection, command_injection, weak_crypto, hardcoded_secret)
  • Description — what the issue is
  • Exploit scenario — concrete attack path, not theoretical
  • Recommendation — specific fix

Skip LOW unless they cluster into a pattern. Do not include:

  • DOS / resource exhaustion / ReDoS
  • Theoretical race conditions without a concrete exploit window
  • Memory safety in memory-safe languages
  • Lack of rate limiting / audit logging / hardening (those are gaps, not vulnerabilities)
  • Log spoofing
  • Regex injection
  • "User-controlled content in AI system prompt" — that's prompt injection, a separate class
  • Findings that depend on already-compromised env vars / privileged shell access

Items examined and cleared

Quick table: file paths or subsystems looked at, with one-line "no concrete issue found because X" notes. Shows the bounds of the audit — what was covered and what wasn't.

Recommended hardening (non-blocking)

Defense-in-depth opportunities. Numbered, with file:line where applicable.

What was NOT examined

Be explicit: "Did not run live fuzzing, did not review historical commits before main-divergence, did not audit dependency source code, did not perform manual exploitation."

Output discipline

  • Be terse but specific. file:line with an exploit path beats a paragraph of theory.
  • Confidence threshold for findings: 0.8 minimum. Below that, mention briefly in "items examined and cleared" but don't list as a finding.
  • Don't pad. A 70/100 with three sharp findings is more useful than 70/100 with a wall of text.
  • The score is for the current branch unless the user explicitly asks for a full-codebase score.
  • If you skipped a stage (no scanner installed, no main branch, etc.), say so — don't silently omit.

Frequently asked questions

What does the Security Audit AI skill do?

Run a comprehensive language-agnostic security & CVE audit, scored 0-100

Why use Security Audit on TypingMind?

Because you install it once and use it with any model. Security Audit is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Security Audit in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/opencrabs/opencrabs/tree/main/src/docs/reference/templates/skills/security-audit. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Security Audit?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Security Audit?

As many as you like. As long as a model supports skills, you can use Security Audit with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Security Audit AI skill free?

Yes. It is published on GitHub by opencrabs under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇