secure-coding — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited secure-coding (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill is the substrate layer for code work. It triggers when Claude is writing or reviewing code and there's no framework-specific skill that goes deeper.
Activates on:
django-security or api-security) active.django-security, Rails → rails-security, Spring Boot → spring-security, Next.js → nextjs-security.api-security.iac-security, Dockerfile or OCI → container-hardening, Kubernetes manifests → k8s-security, CI/CD workflows → cicd-hardening.cve-triage, SBOM and provenance → supply-chain, secrets in git history → secrets-scanner.security-review. This skill is its pattern library.If one of the above skills applies, use that first. secure-coding remains relevant for the parts they don't cover.
Six phases. Work through them sequentially during a review. When generating code you can jump between them depending on what you're writing. Each phase has the same shape: rule → code signal (red flags to spot) → do/don't.
The phases correspond to OWASP Top 10 2021 and Proactive Controls v3, but here they are organized in the order a developer encounters them in practice.
The first question with any piece of code: where does untrusted data enter, and which code runs with more privilege than the data producer?
Red flag — trust loss. Code that says "this comes from the database so it's safe" — unless you've also validated the write path to that database, that's an assumption. Cached untrusted data is still untrusted.
Two separate concerns that often get conflated. Validate at the boundary (parse, don't validate-after-parse); encode at the output point based on the destination context.
<script>" is a denylist and always loses to encoding tricks, Unicode homoglyphs, or new payload shapes.int, UUID, enum) instead of passing the string along with a separate validation check. See Alexis King's "Parse, don't validate".safe_join, template auto-escape, shlex.quote).autoescape=True, ERB h(), etc.).Red flags in code:
# SQL — string interpolation in query
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
cursor.execute("SELECT * FROM users WHERE id = " + user_id)
# Command — shell=True with user input
subprocess.Popen(f"git clone {url}", shell=True)
os.system("convert " + filename + " out.png")
# Template — raw HTML insertion
element.innerHTML = userInput # JS/DOM
v-html="userInput" # Vue
dangerouslySetInnerHTML={{ __html }} # React
{{ user.bio | safe }} # Jinja2 with | safe on untrustedSafe variants:
# SQL
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,)) # psycopg2
db.query("SELECT * FROM users WHERE id = $1", userID) # Go
PreparedStatement s = conn.prepareStatement("SELECT ... WHERE id = ?") # Java
# Command
subprocess.run(["git", "clone", url], check=True) # no shell
child_process.execFile("git", ["clone", url]) # Node
# Template — keep auto-escape on by default, deliberately disable only for trusted dataNo hand-rolled auth. Use vetted frameworks (Spring Security, Django auth, auth.js, Devise, Keycloak, Auth0). If you do build something yourself, build it around a vetted primitive only.
/api/documents/123 only checks authentication and not whether the actor may see 123. Check ownership or role on every lookup path.Red flags:
# Password handling
hashlib.md5(password.encode()).hexdigest()
hashlib.sha256(password.encode()).hexdigest() # no salt, no KDF
bcrypt.hashpw(password, bcrypt.gensalt(4)) # cost factor too low
# Session
document.cookie = "sid=" + sessionId # JS can read it → XSS steals
session.permanent = True # without TTL set
# AuthZ
@app.route("/api/documents/<id>")
def get_doc(id):
return Document.objects.get(id=id) # no ownership check
# Generic "is logged in" as the only check
if (user.isAuthenticated()) { return adminPanel }Implementing crypto yourself is the classic foot-gun. Use high-level APIs. For secrets: never in source, ideally in a vault, env-vars are acceptable as a secondary path.
.env file outside git. Never in source, never in logs, never in error messages.verify=False in requests/curl is a staging hack, not a production config.Math.random() / random.random() for security purposes (tokens, IDs, nonces).Red flags:
# Hardcoded secrets
const API_KEY = "sk-proj-..."
db_password = "Welcome2024!"
# AWS / GitHub / Slack tokens in source → caught with gitleaks or trufflehog
# Crypto wrong
cipher = AES.new(key, AES.MODE_ECB) # ECB
requests.get(url, verify=False) # TLS validation off
token = str(random.random()) # not crypto-safe
hashlib.sha1(data).hexdigest() # for signatures/integrity
# Key management
KEY = "hardcoded-32-byte-string-right-here" # also as env-var fallbackWhat happens when something goes wrong? Fail closed, no sensitive data in errors, no dangerous deserialization, structured logging without secrets.
catch (Exception e) { } (Java/C#) or except: pass (Python) masks bugs that may be security-relevant. Log the error, return a generic message to the user.request-id, not /home/app/lib/.../db.py line 47 in _execute.ObjectInputStream, Python pickle.loads, PHP unserialize, Ruby Marshal.load, YAML yaml.load (without SafeLoader), .NET BinaryFormatter. Use JSON, protobuf, or msgpack with schema validation.alg: none and algorithm confusion). Cookies that encode server state are signed with HMAC.Red flags:
# Swallowed exceptions
try: risky()
except: pass
catch (Exception e) { /* empty */ }
# Dangerous deserialization
pickle.loads(request.body)
yaml.load(user_input) # without Loader=SafeLoader
ObjectInputStream ois = new ObjectInputStream(sock.getInputStream())
# JWT
jwt.decode(token, key, algorithms=None) # accept any alg
jwt.decode(token, None, options={"verify_signature": False})
# Logging
logger.info(f"Login for {user.email} with password {password}")
logger.error(f"Payment {card_number} failed")Inherited vulnerabilities through libraries are the largest share of modern exploits (OWASP A06). Treat this as a first-class concern.
package-lock.json / poetry.lock / Gemfile.lock / go.sum committed in git. Transitive dep versions must be reproducible.cyclonedx-bom, syft, sbom-tool. In CI, every build. See the supply-chain skill for provenance.osv-scanner or grype for scans, Snyk/Mend for enterprise. Scan on a per-PR basis, not just nightly.container-hardening and k8s-security for depth.cve-triage (reachable path plus EPSS).Red flags:
# requirements.txt without version pin
requests
flask
# package.json with caret on everything (^) → auto-drift
"dependencies": { "lodash": "^4.0.0" }
# Dockerfile
FROM node:latest # latest tag = unreproducible
USER root # or no USER directive
ADD http://... /app/ # ADD with URL bypasses integrity
# Runtime
process.env.NODE_ENV not "production" in prod
DEBUG=True in production config (Django, Flask)When this skill is used for a code review or scan, it produces a structured report. During code generation the skill works "silently": it influences what you write but produces no separate output.
Report structure for review:
Findings per phase:
1. Trust boundaries: <ok | issues: ...>
2. Input/output: <ok | issues with file:line>
3. Identity: <ok | issues with file:line>
4. Secrets/crypto: <ok | issues with file:line>
5. Robustness: <ok | issues with file:line>
6. Dependencies: <ok | issues with versions>
Per issue:
- Phase: <1–6>
- Location: <file:line>
- Classification: <CWE-ID, OWASP A0x>
- Severity: <blocker | high | medium | low>
- Pattern: <short name, e.g. "SQL string concat", "pickle.loads", "verify=False">
- Fix: <concrete suggestion, ideally with code alternative>
- Handoff: <if applicable: use <skill-id> for deeper review>
Overall conclusion: <blocker count, overall verdict>Always tie issues to a CWE-ID where possible — that's the language tools (SAST, CI, issue trackers) speak. Use only CWE numbers you can verify; mark with [verify: CWE] when in doubt (see verification-loop Layer 2).
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.