hunter-party-py/security-hunter-py/SKILL.md
Audit Python code for security vulnerabilities — hardcoded secrets, injection risks, missing input validation at trust boundaries, insecure defaults, auth gaps, sensitive data exposure, and unsafe patterns like eval, pickle, or shell injection. Use when: reviewing Python code before deployment, auditing trust boundaries, preparing for a security review, onboarding third-party integrations, or hardening an application.
npx skillsauth add skyosev/agent-skills security-hunter-pyInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Audit code for security vulnerabilities — places where untrusted input flows into sensitive operations without validation, secrets are embedded in source, defaults are permissive, or auth checks are missing. The goal: every trust boundary validates its inputs, no secrets live in code, and the principle of least privilege is applied throughout.
Trust boundaries are the perimeter. Every point where external data enters the system — HTTP requests, file uploads, environment variables, database reads, third-party API responses, CLI arguments — is a trust boundary. Input must be validated, sanitized, or escaped before it flows into operations that could be exploited.
Secrets never belong in source control. API keys, passwords, tokens, certificates, and connection strings must come from a secure source — environment variables, secret managers, vault services, or encrypted config — never from source files, comments, or committed configuration.
Default to deny. Defaults should be restrictive: CORS origins explicit not *, auth required not optional,
permissions minimal not broad. Permissive defaults are the most common class of security misconfiguration.
Defense in depth. No single validation layer is sufficient. Validate at the boundary, escape at the output, enforce at the data layer. If one layer fails, another should catch the exploit.
Fail closed. When auth/authz checks fail or raise, the result should be denial, not access. Error paths must not bypass security controls.
API keys, passwords, tokens, or connection strings embedded directly in source code.
Signals:
sk_live_, AKIA, ghp_, password=, secret=postgres://user:pass@host.env files or secret-bearing config committed to the repodef connect(password="admin123")Action: Move to a secure secret source (environment variables, secret manager, vault). Add the pattern to
.gitignore. Rotate any committed secret immediately — it's already compromised if the repo was ever shared.
Untrusted input concatenated into strings that are interpreted as code, queries, or commands.
Signals:
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}"))os.system(), subprocess.call(shell=True), subprocess.Popen(shell=True) with user-controlled
argumentsopen(), pathlib.Path(), or URL construction without sanitization| safe or autoescape disabledre.compile() (ReDoS risk)eval(), exec(), compile() with dynamic inputpickle.loads() / pickle.load() on untrusted data (arbitrary code execution)yaml.load() without Loader=SafeLoader (arbitrary code execution)Action: Use parameterized queries, allowlists, path canonicalization, template auto-escaping, or regex escaping. Never concatenate untrusted input into interpreted strings.
Endpoints, handlers, or integration points that accept external data without schema validation or sanitization.
Signals:
request.json, request.args, request.form without validation
(no Pydantic, marshmallow, attrs validators, etc.)argparse/click used without additional validation for security-sensitive operationsAction: Add schema validation at every trust boundary. Validate type, format, range, and length. Reject invalid input explicitly — don't coerce or default.
Permissive defaults that weaken security posture when not explicitly overridden.
Signals:
Access-Control-Allow-Origin: * or credentials: True with wildcard originSecure, HttpOnly, or SameSite attributesverify=False in requests, ssl._create_unverified_context())DEBUG=True or app.run(debug=True) in production configALLOWED_HOSTS = ['*'], SECRET_KEY hardcoded, DEBUG = Trueapp.secret_key hardcoded in sourceAction: Restrict defaults. Require explicit opt-in for permissive settings with justification.
Missing or inconsistent auth checks that allow unauthorized access or privilege escalation.
Signals:
bcrypt, argon2, or passlib instead)Action: Ensure every endpoint has explicit auth/authz. Check authorization server-side against the session, not client-provided claims. Use bcrypt/argon2/passlib for password hashing.
Personal data, secrets, or internal details leaked through logs, errors, responses, or storage.
Signals:
logging.info(f"User data: {request.json}"))Action: Strip sensitive fields from logs and error responses. Filter API responses to only required fields. Use structured logging with field-level redaction.
Package dependencies with known vulnerabilities, unvetted install scripts, or lockfile integrity issues.
Signals:
requirements.txt dependencies without pinned versions (requests instead of requests==2.31.0)poetry.lock, Pipfile.lock, uv.lock) not committed to the reposetup.py that execute arbitrary code during installpip-audit or safety)pip install from untrusted sources at runtimeAction: Commit the lockfile. Pin dependency versions. Run pip-audit (or safety check) and address
critical/high findings. Review setup scripts in new dependencies before adding them.
Language-level patterns that create exploitable conditions.
Signals:
pickle.loads() / pickle.load() on untrusted data (arbitrary code execution)yaml.load(data) without Loader=yaml.SafeLoader (arbitrary code execution)eval(), exec(), compile() with user-controlled input__import__() or importlib.import_module() with user-controlled argumentssubprocess with shell=True and string arguments derived from user inputtempfile.mktemp() instead of tempfile.mkstemp() (race condition)hashlib.md5() / hashlib.sha1() for security-sensitive hashing (use SHA-256+ or bcrypt)random.random() for security tokens (use secrets module instead)xml.etree.ElementTree without defusing (XXE attacks) — use defusedxmlmarshal.loads() on untrusted dataAction: Use yaml.safe_load() instead of yaml.load(), secrets.token_urlsafe() instead of random, defusedxml
for XML parsing, subprocess.run() with list arguments instead of shell=True. Never deserialize untrusted data with
pickle.
Security patterns specific to popular Python web frameworks that go beyond general best practices.
Signals:
CORSMiddleware with allow_origins=["*"] combined with allow_credentials=True (credential leakage),
routes missing Depends(get_current_user) where peer routes require auth, Pydantic Settings with hardcoded
secret_key or jwt_secret default values, uvicorn.run(host="0.0.0.0") without TLS in production configALLOWED_HOSTS = ['*'] in production settings, SECRET_KEY hardcoded in settings.py (not from env),
DEBUG = True in production settings module, @csrf_exempt on state-changing views without justification,
SECURE_SSL_REDIRECT = False and SESSION_COOKIE_SECURE = False in production,
AUTH_PASSWORD_VALIDATORS = [] (empty password validation)app.secret_key hardcoded in source, app.run(debug=True) in production entrypoint,
no SESSION_COOKIE_SECURE or SESSION_COOKIE_HTTPONLY configured, send_from_directory() or send_file()
with user-controlled paths without sanitizationAction: Move secrets to environment variables or secret managers. Enable framework security features explicitly. Audit route-level auth coverage against a peer-comparison table. Ensure production settings modules disable debug and enforce HTTPS.
main/master)BASE=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo main)
SCOPE=$(git diff --name-only $(git merge-base HEAD $BASE)...HEAD)
Constrain all subsequent scans to the resolved surface.EXCLUDE='--glob !**/venv/** --glob !**/.venv/** --glob !**/dist/** --glob !**/*_test.py --glob !**/test_*.py'
# Hardcoded secrets (common patterns)
rg --pcre2 '(sk_live_|AKIA|ghp_|password\s*=\s*["\x27]|secret\s*=\s*["\x27])' $EXCLUDE
rg --pcre2 '(postgres|mysql|mongodb)://\w+:\w+@' $EXCLUDE
# Injection: eval, exec, compile, pickle, yaml.load
rg '\beval\s*\(|\bexec\s*\(|\bcompile\s*\(' --type py $EXCLUDE
rg 'pickle\.(loads?|load)\s*\(' --type py $EXCLUDE
rg 'yaml\.load\s*\(' --type py $EXCLUDE
# Shell injection: subprocess with shell=True, os.system
rg 'shell\s*=\s*True|os\.system\s*\(' --type py $EXCLUDE
# SQL injection: f-strings or .format() in queries
rg --pcre2 '(execute|query)\s*\(\s*f["\x27]' --type py $EXCLUDE
rg --pcre2 '(execute|query)\s*\([^)]*\.format\(' --type py $EXCLUDE
# Insecure config
rg --pcre2 'verify\s*=\s*False|DEBUG\s*=\s*True|ALLOWED_HOSTS\s*=\s*\[\s*["\x27]\*' $EXCLUDE
# Missing auth (route definitions — project-specific, adapt pattern)
rg '@(app|router)\.(get|post|put|patch|delete)\s*\(' --type py $EXCLUDE
# Logging of sensitive data
rg --pcre2 'log(ger)?\.(info|debug|warning).*\b(password|token|secret|authorization|cookie)\b' -i --type py $EXCLUDE
# Insecure random
rg 'random\.(random|randint|choice|sample)\s*\(' --type py $EXCLUDE
# XML parsing without defusedxml
rg 'xml\.etree|xml\.dom|xml\.sax' --type py $EXCLUDE
# tempfile.mktemp (race condition)
rg 'mktemp\s*\(' --type py $EXCLUDE
For each trust boundary found in Phase 1:
eval, exec, a database query, subprocess, or pickle.loads()?Save as YYYY-MM-DD-security-hunter-audit-{$LLM-name}.md in the project's docs folder (or project root if no docs
folder exists).
# Security Hunter Audit — {date}
## Scope
- Surface: {diff / path / codebase}
- Files: {count or list}
- Exclusions: {list}
## Trust Boundary Map
| # | Boundary | Location | Validation | Auth |
| - | -------- | -------- | ---------- | ---- |
| 1 | POST /api/users | file:line | Pydantic model | JWT middleware |
| 2 | Webhook /hooks/stripe | file:line | None | None |
## Findings
### Hardcoded Secrets
| # | Location | Pattern | Severity | Action |
| - | -------- | ------- | -------- | ------ |
| 1 | file:line | `sk_live_...` Stripe key | Critical | Move to env, rotate immediately |
### Injection Risks
| # | Location | Type | Untrusted Input | Action |
| - | -------- | ---- | --------------- | ------ |
| 1 | file:line | SQL injection | f-string in `cursor.execute()` | Use parameterized query |
| 2 | file:line | Pickle deserialization | Untrusted bytes in `pickle.loads()` | Use JSON or msgpack |
### Missing Input Validation
| # | Location | Boundary | Input | Action |
| - | -------- | -------- | ----- | ------ |
| 1 | file:line | POST /api/orders | `request.json` used without validation | Add Pydantic model |
### Insecure Defaults
| # | Location | Setting | Current | Action |
| - | -------- | ------- | ------- | ------ |
| 1 | file:line | TLS verification | `verify=False` | Enable verification |
### Auth/Authz Gaps
| # | Location | Endpoint | Issue | Action |
| - | -------- | -------- | ----- | ------ |
| 1 | file:line | GET /admin/users | No auth decorator | Add admin auth |
### Sensitive Data Exposure
| # | Location | Data | Channel | Action |
| - | -------- | ---- | ------- | ------ |
| 1 | file:line | Auth token | logging.info() | Remove from logs |
### Unsafe Patterns
| # | Location | Pattern | Risk | Action |
| - | -------- | ------- | ---- | ------ |
| 1 | file:line | `yaml.load(data)` | Arbitrary code execution | Use `yaml.safe_load()` |
### Dependency / Supply-Chain Risks
| # | Location | Issue | Severity | Action |
| - | -------- | ----- | -------- | ------ |
| 1 | requirements.txt | No lockfile committed | High | Commit lockfile |
## Recommendations (Priority Order)
1. **Critical**: {hardcoded secrets to rotate, injection vulnerabilities, pickle on untrusted data, auth bypasses}
2. **Must-fix**: {missing input validation, insecure defaults, auth gaps, dependency CVEs}
3. **Should-fix**: {sensitive data exposure, unsafe patterns, supply-chain hygiene}
file/path.py:line with the exact code.SameSite cookie attribute. Prioritize accordingly.re.compile() with a hardcoded pattern is fine. A re.compile(user_input) is
a ReDoS risk. Grep finds both — judgment separates them.development
Transforms vague feature ideas into precise, codebase-grounded technical requirements. Use when requirements are ambiguous/incomplete, the user struggles to describe behavior, terminology is unclear, or multiple concepts are mixed. Output is a requirements spec—NOT an implementation plan.
tools
Audit TypeScript type definitions for design debt — duplicated shapes, missing derivations, over-engineered generics, under-constrained type parameters, reinvented utility types, and disorganized type architecture. Type structure and maintainability, not type enforcement. Use when: reviewing type definitions for maintainability, reducing type duplication, simplifying over-engineered type-level logic, or reorganizing type architecture after growth.
development
Audit TypeScript test code for quality gaps — missing coverage on critical paths, brittle tests coupled to implementation, over-mocking, assertion-free tests, missing edge cases, and duplicated test setup. Focuses on test effectiveness, not production code structure. Use when: reviewing TypeScript test suites for reliability, reducing false-positive test failures, improving coverage of critical business logic, or cleaning up test debt.
tools
Audit TypeScript class and interface design for SOLID violations — god classes, rigid extension points, broken substitutability, fat interfaces, and concrete dependency chains. Focuses on responsibility assignment and abstraction fitness. Use when: reviewing class hierarchies, preparing for extension with new variants, reducing coupling between services, or improving testability of class-heavy code.