slopscan — AI-aware security scanner that detects vulnerabilities in AI-generated code
Project description
What is this?
AI coding tools ship code fast. They also ship SQL injection, hardcoded API keys, and SSRF at scale — and generic SAST tools were not designed for this.
slopscan detects AI-generated files first using three offline heuristics — comment patterns, structural regularity, and token fingerprints — then applies stricter scanning exactly where the risk is highest. It also detects slopsquatting (hallucinated package names that attackers pre-register), secrets in .env and MCP config files, and prompt injection strings in source code.
pip install slopscan
slopscan scan .
How it works
Your code
│
▼
┌─────────────────────────────┐
│ AI Code Detector │ ← scores each file 0.0–1.0
│ comments · structure │ using offline heuristics
│ tokens │ no network, no LLM calls
└─────────────────────────────┘
│
├── score ≥ 0.6 → FULL scan (all rules)
├── score 0.3–0.6 → MEDIUM scan (Tier 1 rules)
└── score < 0.3 → CRITICAL scan (secrets + SQLi only)
│
▼
┌─────────────────────────────┐
│ 81 security rules │ ← targets AI-specific
│ Semgrep CE + plugins │ vulnerability patterns
└─────────────────────────────┘
│
▼
Terminal · SARIF · JSON
Install
pip install slopscan
Requires Python 3.10+. Semgrep and detect-secrets are installed automatically.
Quick start
Scan your project:
slopscan scan .
Scan only changed files (faster in CI):
slopscan scan . --diff
Get your security score and badge:
slopscan score .
AI-powered fix suggestions (requires free Gemini API key):
export GEMINI_API_KEY=your-key-here
slopscan fix .
Set up pre-commit + GitHub Action in 60 seconds:
slopscan init
GitHub Action
- uses: DARusrus/slopscan@v1
Full configuration:
name: slopscan security scan
on: [push, pull_request]
permissions:
security-events: write
contents: read
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: DARusrus/slopscan@v1
with:
severity: medium # minimum severity to report
fail-on-findings: true # exit 1 if findings exist
ai-threshold: 0.6 # confidence threshold for AI detection
upload-sarif: true # show findings in GitHub Security tab
Findings appear inline on pull requests via the GitHub Security tab.
Pre-commit hook
repos:
- repo: https://github.com/DARusrus/slopscan
rev: v0.1.0
hooks:
- id: slopscan
Security rules
81 rules across 8 languages. Every rule includes the specific reason AI models produce that pattern.
Python — 41 rules
| Rule | What it catches |
|---|---|
sqli-fstring |
SQL injection via f-string or concatenation in execute() calls |
cmdi-shell-true |
Command injection via subprocess.run(shell=True) with user input |
path-traversal |
Path traversal via unsanitized open() or send_file() |
ssrf-user-url |
SSRF via requests.get(user_input) |
hardcoded-secret |
Hardcoded API keys, passwords, tokens in assignments |
insecure-deserialization |
pickle.loads(), yaml.load() without Loader, eval() on untrusted data |
jwt-verify-disabled |
JWT verify=False or verify_signature: False |
timing-unsafe-comparison |
Token comparison with == instead of hmac.compare_digest() |
mass-assignment |
Model(**request.get_json()) without field filtering |
cors-wildcard |
CORS(app, origins="*") |
verbose-error-response |
return jsonify({"error": str(e), "traceback": ...}) |
nosql-injection |
MongoDB .find(request.json) without field extraction |
log-injection |
User input interpolated directly into log statements |
xxe |
Unsafe XML parsing with ElementTree, lxml, minidom |
ssti |
render_template_string(user_input) — Jinja2 RCE |
open-redirect |
redirect(request.args.get('next')) without validation |
graphql-injection |
GraphQL query strings built with f-string interpolation |
weak-password-hash |
hashlib.sha256(password) — use bcrypt or argon2 |
ecb-mode |
AES.new(key, AES.MODE_ECB) — leaks data patterns |
pii-in-logs |
Email, phone, SSN interpolated into log statements |
insecure-random |
random.randint() for tokens, OTP codes, session IDs |
debug-mode |
app.run(debug=True) — Werkzeug RCE in browser |
missing-rate-limit |
Login/auth endpoints without rate limiting decorator |
weak-session-token |
uuid.uuid4() used as a security token |
token-in-url |
Reset tokens, API keys passed as URL query parameters |
zip-slip |
zipfile.extractall() without path validation |
crlf-injection |
User input in response headers without CRLF stripping |
race-condition-balance |
Balance check + deduction without database transaction |
idor |
CRUD routes using URL ID without ownership verification |
default-credentials |
Seed scripts with password='admin123' literals |
plaintext-sensitive-fields |
Column(String) for SSN, credit card, medical data |
missing-security-headers |
Flask/FastAPI without Talisman or CSP middleware |
html-injection |
HTML response built with f-string, missing markupsafe.escape() |
client-side-pricing |
Price calculations in React components sent to payment API |
missing-rls |
Supabase CREATE TABLE without ENABLE ROW LEVEL SECURITY |
insecure-chmod |
os.chmod(file, 0o777) in Python code |
redos |
Regex patterns with catastrophic backtracking |
xxe-lxml |
lxml.etree.parse() without resolve_entities=False |
weak-crypto-md5 |
hashlib.md5() for any security-sensitive operation |
missing-https |
app.run() without SSL context on auth routes |
sensitive-url-param |
Tokens, passwords in URL query strings |
JavaScript — 18 rules
| Rule | What it catches |
|---|---|
sqli-template-literal |
SQL built with template literals in query(), execute() |
xss-inner-html |
element.innerHTML = userInput without sanitization |
xss-dangerous-html |
dangerouslySetInnerHTML with unsanitized content |
hardcoded-secret |
apiKey, password, token string literals in JS |
eval-user-input |
eval(), new Function() on user-controlled strings |
prototype-pollution |
obj["__proto__"] assignment or unsafe recursive merge |
csrf-missing-token |
fetch() POST without CSRF token in headers |
nosql-injection |
Mongoose .find(req.body) without field extraction |
client-side-auth |
isAdmin, isAuthenticated in React state used for access control |
client-side-pricing |
Price calculations in React components |
log-injection |
User input interpolated into console.log() |
supabase-service-role |
createClient(url, SERVICE_ROLE_KEY) in browser-side code |
open-redirect |
res.redirect(req.query.url) without validation |
graphql-injection |
GraphQL query string built with template literals |
crlf-injection |
User input in response headers |
ecb-mode |
createCipheriv('aes-256-ecb', ...) |
pii-in-logs |
PII fields interpolated into console or logger calls |
missing-helmet |
Express app without helmet() middleware |
TypeScript — 4 rules
| Rule | What it catches |
|---|---|
sqli-orm |
Raw SQL in TypeORM .query(), Prisma $executeRawUnsafe(), Drizzle |
type-assertion-bypass |
as any cast passed to security-sensitive functions |
client-side-auth |
Auth state in React/Next.js components controlling access |
missing-server-validation |
use server functions accepting FormData without Zod validation |
Shell — 3 rules
| Rule | What it catches |
|---|---|
curl-pipe-bash |
curl URL | bash — executes remote code without verification |
missing-errexit |
Shell scripts without set -euo pipefail |
chmod-777 |
chmod 777 in scripts and deployment code |
Dockerfile — 2 rules
| Rule | What it catches |
|---|---|
missing-user |
Dockerfile with no USER instruction — runs as root |
curl-pipe-bash |
RUN curl URL | bash in build steps |
GitHub Actions — 2 rules
| Rule | What it catches |
|---|---|
secret-echo |
echo "${{ secrets.* }}" in workflow run steps |
unpinned-action |
uses: action@v3 — use full commit SHA instead |
Kubernetes — 1 rule
| Rule | What it catches |
|---|---|
rbac-cluster-admin |
ClusterRoleBinding granting cluster-admin to service accounts |
SQL — 1 rule
| Rule | What it catches |
|---|---|
missing-rls |
CREATE TABLE in Supabase migrations without ENABLE ROW LEVEL SECURITY |
Supply chain detection
slopscan also runs supply chain checks that go beyond Semgrep rules:
| Plugin | What it detects |
|---|---|
| Slopsquatting | Package names in requirements.txt / package.json that AI models hallucinate — attackers pre-register these names with malicious code |
| CVE scanning | Known CVEs in AI-recommended packages via OSV.dev |
| Unpinned deps | Packages without version pins (flask instead of flask==3.0.0) |
| Missing lock files | requirements.txt present but no poetry.lock or package-lock.json |
| .env secrets | High-entropy credentials in .env files not excluded from git |
| MCP config secrets | API keys in .claude/settings.json, .cursor/mcp.json, and other agent config files |
| Prompt injection strings | Adversarial strings designed to manipulate LLM code reviewers |
AI features
All AI features are optional and require a free Gemini API key (1,500 requests/day free). Everything works without a key — AI features degrade gracefully.
export GEMINI_API_KEY=your-key-here
| Feature | Command | What it does |
|---|---|---|
| Auto-fix | slopscan fix . |
Generates a context-aware secure replacement for each finding, shows a diff, asks for confirmation before applying |
| Explain | slopscan scan . --explain |
Adds a plain-English attack scenario to each finding — what an attacker can actually do with it |
| Smart filter | slopscan scan . --smart-filter |
Uses AI to remove likely false positives from HIGH/CRITICAL findings before surfacing them |
Configuration
Create .Slopscan.toml in your project root:
[slopscan]
min_severity = "MEDIUM" # CRITICAL | HIGH | MEDIUM | LOW
ai_threshold = 0.6 # 0.0–1.0 — files above this get full scan
fail_on_findings = true # exit 1 in CI when findings exist
smart_filter = false # AI false positive reduction (needs GEMINI_API_KEY)
explain = false # plain-English attack explanations
online_sca = false # live registry checks for slopsquatting
min_score = 0 # fail CI if score drops below this
exclude_paths = [
"node_modules", ".git", "__pycache__",
"venv", ".venv", "dist", "tests/fixtures"
]
Commands
slopscan scan [PATH] Scan a directory or file slopscan fix [PATH] AI-powered fix suggestions (needs GEMINI_API_KEY) slopscan score [PATH] Security score + README badge slopscan rules List all 81 built-in rules slopscan init Interactive setup wizard
How slopscan compares
| slopscan | Semgrep CE | Bandit | detect-secrets | |
|---|---|---|---|---|
| AI-aware detection | ✓ | ✗ | ✗ | ✗ |
| Supply chain / slopsquatting | ✓ | ✗ | ✗ | ✗ |
| MCP config scanning | ✓ | ✗ | ✗ | ✗ |
| Pre-commit hook | ✓ | ✓ | ✓ | ✓ |
| GitHub Action | ✓ | ✓ | ✗ | ✗ |
| SARIF + GitHub Security tab | ✓ | ✓ | ✗ | ✗ |
| Security score badge | ✓ | ✗ | ✗ | ✗ |
| AI auto-fix | ✓ | ✗ | ✗ | ✗ |
| License | MIT | LGPL-2.1 | MIT | Apache-2.0 |
Contributing
Writing a YAML rule takes under 30 minutes and has the highest impact per hour of any contribution. See CONTRIBUTING.md.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file slopscan-0.1.0.tar.gz.
File metadata
- Download URL: slopscan-0.1.0.tar.gz
- Upload date:
- Size: 448.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
224e404c5fd57a7974a538b194c708d556223ddd8e4029d9ac1e82d321baaba9
|
|
| MD5 |
5992e001ea3885a30d9f74c7afa5475a
|
|
| BLAKE2b-256 |
01b70a67fe29c36c119e60a695ea3b631d7a39b03192aad43059ed52aa518a42
|
Provenance
The following attestation bundles were made for slopscan-0.1.0.tar.gz:
Publisher:
publish.yml on DARusrus/SlopScan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
slopscan-0.1.0.tar.gz -
Subject digest:
224e404c5fd57a7974a538b194c708d556223ddd8e4029d9ac1e82d321baaba9 - Sigstore transparency entry: 1328605177
- Sigstore integration time:
-
Permalink:
DARusrus/SlopScan@9ee78c3b1451d627133cd48320916a41394bc3fc -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/DARusrus
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9ee78c3b1451d627133cd48320916a41394bc3fc -
Trigger Event:
push
-
Statement type:
File details
Details for the file slopscan-0.1.0-py3-none-any.whl.
File metadata
- Download URL: slopscan-0.1.0-py3-none-any.whl
- Upload date:
- Size: 119.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1674482eacd8042ad9d6f17b3b812d75d9780548203804b824bdc3fff6a3214a
|
|
| MD5 |
ccaa3bae36f8346616eedbd2a9e35273
|
|
| BLAKE2b-256 |
824e9e41c2e6b060584cbf7fb318af75173f32479b45f30b904eaa2ae35227bd
|
Provenance
The following attestation bundles were made for slopscan-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on DARusrus/SlopScan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
slopscan-0.1.0-py3-none-any.whl -
Subject digest:
1674482eacd8042ad9d6f17b3b812d75d9780548203804b824bdc3fff6a3214a - Sigstore transparency entry: 1328605191
- Sigstore integration time:
-
Permalink:
DARusrus/SlopScan@9ee78c3b1451d627133cd48320916a41394bc3fc -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/DARusrus
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9ee78c3b1451d627133cd48320916a41394bc3fc -
Trigger Event:
push
-
Statement type: