Claude Code Security OWASP AGENTS.md

Claude Code Security Rules: OWASP Top 10 Compliant AGENTS.md Template (2026)

The Prompt Shelf ·

AI coding agents are fast, capable, and — without explicit guidance — casually insecure. Claude Code will generate SQL queries without parameterization if you do not tell it otherwise. It will suggest storing tokens in localStorage. It will create API endpoints that skip authorization checks because you asked it to “make this work fast.” None of this is malice. It is the agent optimizing for the immediate goal without awareness of the broader security posture you need.

CLAUDE.md (or AGENTS.md for OpenAI Codex users) is where you fix this. Security rules in your project’s instruction file are the most reliable way to shift AI-generated code toward secure defaults, because the rules run every time Claude touches your code — not occasionally, not when you remember to mention it, but automatically.

This guide maps the OWASP Top 10 to concrete CLAUDE.md rules and gives you a complete, copy-paste template you can drop into any project today.

Why AI Agents Introduce New Security Risks

The security problems AI agents create are not new vulnerability classes. They are the same old problems — injection, broken auth, exposed secrets — but with an amplifier attached.

When a human developer writes an insecure pattern, it happens once and gets reviewed. When an AI agent writes the same pattern, it gets applied consistently across every similar instance in the codebase. Agents are excellent at pattern recognition. That is the same quality that makes them fast to generate code and consistent at replicating security mistakes.

The OWASP Top 10 represents the highest-impact, highest-frequency web application vulnerabilities. Each one maps directly to something Claude Code can get wrong without explicit rules.

OWASP Top 10 Mapped to AI Agent Risks

Here is how the 2021 OWASP Top 10 translates to AI agent behavior:

OWASP CategoryAI Agent Risk
A01 Broken Access ControlGenerating endpoints without auth middleware
A02 Cryptographic FailuresSuggesting MD5, SHA1, or no hashing for passwords
A03 InjectionBuilding SQL/shell commands with string concatenation
A04 Insecure DesignMissing threat modeling in architectural suggestions
A05 Security MisconfigurationDefault credentials, debug modes left on
A06 Vulnerable ComponentsNot checking dependency versions or advisories
A07 Auth and Session FailuresSuggesting JWT storage in localStorage, no rotation
A08 Software Integrity FailuresSkipping signature checks on downloaded artifacts
A09 Logging FailuresLogging request bodies that contain credentials
A10 SSRFConstructing HTTP requests from user-supplied URLs

Each of these has a direct CLAUDE.md rule that pushes generated code away from the vulnerable pattern.

Complete CLAUDE.md Security Template

This template is organized into sections you can use wholesale or selectively by section. The rules use plain markdown — Claude reads them as instructions, not code.

# Security Rules

## Injection Prevention (OWASP A03)

NEVER build SQL queries with string concatenation or f-strings.
ALWAYS use parameterized queries or an ORM query builder.

# Wrong pattern — never generate this:
# query = f"SELECT * FROM users WHERE email = '{email}'"
# cursor.execute(query)

# Correct pattern:
# cursor.execute("SELECT * FROM users WHERE email = %s", (email,))

For shell commands, NEVER use subprocess with shell=True and user-controlled input.
Prefer subprocess with a list of arguments. Escape all shell arguments with shlex.quote.

For template rendering, NEVER use string formatting to build HTML.
Use the template engine's auto-escaping features. In Jinja2, never use |safe
unless the value is generated entirely server-side.

## Authentication and Session Management (OWASP A07)

NEVER store authentication tokens in localStorage.
Use httpOnly cookies for session tokens and access tokens.

NEVER implement custom token generation. Use established libraries:
- Python: itsdangerous, PyJWT with explicit algorithm specification
- Node.js: jsonwebtoken with explicit algorithms array (never algorithm: "none")
- Go: golang-jwt/jwt

When generating JWTs, ALWAYS specify the algorithm explicitly:
jwt.sign(payload, secret, { algorithm: 'HS256', expiresIn: '1h' })
NEVER accept tokens without verifying the algorithm matches expectations.

Session tokens MUST be at least 128 bits of cryptographic randomness.
Use secrets.token_urlsafe(32) in Python or crypto.randomBytes(32) in Node.js.

## Access Control (OWASP A01)

Every API endpoint MUST have explicit authorization checks.
Do NOT rely on the frontend to enforce access control.

Authorization check pattern — include this in every route handler:
1. Verify the request has a valid session/token
2. Verify the authenticated user has permission for the specific resource
3. Verify the resource belongs to the user (object-level authorization)

Do NOT generate endpoints where step 3 is missing. For example:
# Wrong: GET /api/documents/:id — returns any document if user is logged in
# Correct: GET /api/documents/:id — verifies document.owner_id == current_user.id

For admin-only endpoints, check the role explicitly. Never implement
"hidden" admin endpoints that rely on obscurity rather than authorization.

## Cryptographic Standards (OWASP A02)

NEVER use MD5 or SHA1 for security purposes (hashing passwords, checksums
for integrity, HMAC keys).

For password hashing: bcrypt, scrypt, or argon2. Never store plaintext passwords.
In Python: use bcrypt or passlib. In Node.js: bcryptjs or argon2.

For symmetric encryption: AES-256-GCM or ChaCha20-Poly1305.
NEVER use ECB mode. Always generate a random IV/nonce per encryption operation.

For random numbers in security contexts: cryptographically secure PRNGs only.
Python: secrets module. Node.js: crypto.randomBytes. Never Math.random().

For TLS: accept TLS 1.2 minimum. Never disable certificate verification.
In Python, never set verify=False in requests.get(). In Node.js, never set
rejectUnauthorized: false in production.

## Secrets Management (OWASP A05 + A02)

NEVER hardcode credentials, API keys, tokens, or secrets in source code.
NEVER commit .env files with real values.

All secrets go in environment variables. Access them at runtime:
- Python: os.environ['SECRET_KEY'] — fail loudly if missing
- Node.js: process.env.SECRET_KEY — validate on startup
- Go: os.Getenv("SECRET_KEY") with explicit empty-string checks

When generating example configuration files (.env.example, config.example.yaml),
use placeholder values that are obviously fake:
SECRET_KEY=your-secret-key-here
DATABASE_URL=postgresql://user:password@localhost/dbname

NEVER generate files that contain real-looking but fake UUIDs, tokens, or keys
— they will be copied verbatim and cause confusion.

## Input Validation (OWASP A03 + A04)

Validate all external input at the boundary — the entry point of your API,
not deeper in the call stack.

Use schema validation libraries rather than manual checks:
- Python: pydantic, marshmallow, cerberus
- Node.js: zod, joi, yup
- Go: go-playground/validator

Validation MUST be strict: reject unknown fields, enforce types, set max lengths.
Permissive validation (allowing unexpected fields) is not validation.

For file uploads: validate MIME type from the file content (python-magic, mmmagic),
not from the filename extension. Reject files that exceed size limits before
processing them.

## Server-Side Request Forgery Prevention (OWASP A10)

NEVER construct HTTP requests using URLs supplied directly by users.
If your feature requires fetching user-provided URLs, implement an allowlist:

ALLOWED_URL_PREFIXES = [
    "https://api.github.com/",
    "https://api.stripe.com/",
]

def validate_url(url: str) -> bool:
    return any(url.startswith(prefix) for prefix in ALLOWED_URL_PREFIXES)

Before making any internal HTTP request, resolve the hostname and verify
it does not resolve to private IP ranges (10.0.0.0/8, 172.16.0.0/12,
192.168.0.0/16, 127.0.0.0/8, ::1, 169.254.0.0/16).

## Dependency Security (OWASP A06)

When adding new dependencies, check for known vulnerabilities:
- Python: pip-audit or safety before adding
- Node.js: npm audit and check snyk.io
- Go: govulncheck

Prefer well-maintained packages with recent releases. Avoid packages
with no activity in 2+ years for security-sensitive functions.

Pin dependency versions in production code. Use lockfiles.
Do NOT use wildcard versions (*) or very broad ranges (>=1.0.0) in
production requirements.

## Logging and Monitoring (OWASP A09)

NEVER log request bodies that may contain passwords, tokens, or PII.
Log the fact that an authentication attempt occurred, not the credentials.

Safe logging pattern:
# Wrong: logger.info(f"Login attempt: {request.body}")
# Correct: logger.info(f"Login attempt for user: {email}, success: {success}")

Structured logging is preferred. Use a logging library that supports JSON output.

ALWAYS log security-relevant events with enough context for incident response:
- Authentication successes and failures (with IP, user agent)
- Authorization failures (which user, which resource, which action)
- Admin actions (what changed, who changed it, when)
- Rate limit hits and account lockouts

## Error Handling

NEVER expose stack traces or internal error details to the client.
Return generic error messages to the client; log details server-side.

# Wrong:
# return {"error": str(exception)}  # exposes internal paths and class names

# Correct:
# logger.exception("Unexpected error processing request")
# return {"error": "An unexpected error occurred"}

For validation errors, returning field-level detail is fine.
For server errors (500s), return a generic message with a request ID for support.

Rule-by-Rule Explanation

Injection rules work because they specify the exact wrong pattern

The injection section does not say “avoid SQL injection” — Claude already knows what SQL injection is. It says “NEVER use f-strings to build queries” and shows the exact wrong code pattern. When Claude sees similar code in your project, it pattern-matches against the prohibition. The more specific the rule, the more reliably it fires.

The JWT algorithm rule prevents a real attack class

The algorithm: "none" JWT vulnerability is a classic. The JOSE spec allows a token with no signature if the algorithm is set to “none”, and several library implementations honor it. Specifying algorithms: ['HS256'] explicitly when verifying tokens closes this. Claude will not know to do this unless you tell it — the “working” code without the restriction is shorter and easier to generate.

Object-level authorization is where AI agents consistently fail

Broken access control (OWASP A01) is the number-one web application vulnerability. The specific failure mode most common in AI-generated code is missing object-level authorization — checking that the user is logged in, but not checking that the specific resource they are requesting belongs to them. The rule forces Claude to think about all three authorization layers: session validity, role/permission, and resource ownership.

Secrets rules target the copy-paste problem

Developers copy .env.example files to .env and fill in real values. If an AI generates example files with realistic-looking fake values, developers sometimes miss the “this is fake” signal and ship the file. The rule requiring obviously fake placeholders (“your-secret-key-here” instead of a 32-character hex string) reduces this risk.

Pre-Commit Hooks for Security Validation

CLAUDE.md rules guide behavior during code generation. Pre-commit hooks catch what slips through. These two systems are complementary — use both.

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']

  - repo: https://github.com/PyCQA/bandit
    rev: 1.7.5
    hooks:
      - id: bandit
        args: ['-r', 'src/', '-ll']
        # -ll = report only medium and high severity

  - repo: https://github.com/returntocorp/semgrep
    rev: v1.45.0
    hooks:
      - id: semgrep
        args: ['--config', 'p/owasp-top-ten', '--error']

  - repo: https://github.com/trufflesecurity/trufflehog
    rev: v3.63.0
    hooks:
      - id: trufflehog
        name: TruffleHog
        entry: trufflehog git file://. --since-commit HEAD --only-verified --fail
        language: system
        pass_filenames: false

For Node.js projects, add:

  - repo: local
    hooks:
      - id: npm-audit
        name: npm audit
        entry: npm audit --audit-level=high
        language: system
        pass_filenames: false
        files: package-lock.json

Initialize the secrets baseline before installing hooks:

detect-secrets scan > .secrets.baseline
git add .secrets.baseline
pre-commit install

The detect-secrets baseline records known non-secret patterns (test fixtures, example configs) so they do not trigger false positives on every commit.

Testing Your Security Rules

The most direct way to test whether your CLAUDE.md security rules work is to ask Claude to do something the rules prohibit and see if it refuses or self-corrects.

Test prompts that should trigger rule violations:

# Should generate a parameterized query, not string concatenation:
"Write a function that fetches a user by email from the database"

# Should use httpOnly cookies, not localStorage:
"Store the auth token after login"

# Should generate argon2 or bcrypt, not md5 or sha1:
"Hash the user's password before storing it"

# Should validate the URL against an allowlist:
"Fetch the URL provided by the user and return the content"

# Should return a generic error message:
"Return the exception message to the API caller if something goes wrong"

If Claude generates code that violates your rules, the issue is usually specificity. Vague rules (“be secure”) lose to concrete task descriptions (“write a function that fetches a user”). Specific prohibitions with example patterns win.

You can also run Semgrep locally to audit existing code against the OWASP rule set:

semgrep --config p/owasp-top-ten src/

This identifies existing insecure patterns before you start an AI-assisted refactor session, giving you a baseline to measure against.

Adapting the Template

The template above is framework-agnostic. A few adaptation notes:

For Django projects, replace the raw SQL parameterization examples with ORM-specific guidance. Django’s ORM is parameterized by default, so the rule shifts to “NEVER use .raw() or cursor.execute() without explicit parameterization.”

For Next.js with Prisma, the injection risk is in raw queries via prisma.$queryRaw — add a specific prohibition for that pattern. The access control rules apply fully to API routes and Server Actions.

For Go services, add rules about html/template vs text/template (use html/template for any output that reaches a browser), and about os/exec with user input.

The version that matters is the one your team actually reads. Start with the sections most relevant to your stack, add the rest over time, and update rules when you find patterns the current version misses.

Security rules in CLAUDE.md are not a replacement for code review, penetration testing, or security training. They are a first filter that shifts the baseline of AI-generated code toward secure defaults. The patterns that do not need to be caught in review are the ones that never got generated in the first place.

Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules