Security Context Files: The Missing Layer in AI-Assisted Development
Source: martinfowler
The Thoughtworks team working on global marketing applications encountered a problem that anyone using AI coding assistants will recognize: the tools are incredibly productive at generating working code, but they consistently suggest insecure configurations. Their article on “The VibeSec Reckoning” documents this tension and proposes security context files as part of the solution. I’ve been building Discord bots with Claude Code for months, and this issue surfaces constantly in ways that are both subtle and dangerous.
The core problem is simple. When you ask an AI assistant to implement authentication, database access, or API integrations, it optimizes for getting something working quickly. That means hardcoded credentials, permissive CORS policies, disabled SSL verification, and wildcard permissions appear in generated code regularly. The assistant doesn’t know your threat model, compliance requirements, or production environment constraints unless you tell it explicitly.
The Context File Approach
Security context files work by giving the AI assistant a project-specific security specification before it generates any code. The OWASP LLM Top 10 identifies “LLM01: Prompt Injection” and “LLM02: Insecure Output Handling” as primary risks, but the real operational issue is that models have no persistent memory of your security requirements across sessions.
A security context file typically lives at .claude/SECURITY.md or a similar project root location. Here’s a minimal example from a Discord bot project:
# Security Requirements
## Authentication & Secrets
- ALL credentials must use environment variables via process.env
- Never commit .env files (enforce via .gitignore)
- Use dotenv only in development, never in production
- Discord bot tokens must be scoped to minimum required intents
## Database Access
- Always use parameterized queries (prepared statements)
- Never string concatenation for SQL
- Database connections must use TLS 1.3+
- Implement connection pooling with max 10 connections
## API Security
- Validate all webhook signatures (Discord, GitHub, etc)
- Rate limit: 10 requests per minute per user
- Input validation on all user-provided data
- Sanitize before logging (no PII in logs)
## Dependencies
- Pin exact versions in package.json
- Run npm audit before every commit
- No dependencies with known critical CVEs
When Claude Code or Cursor reads this file at the start of each session, it significantly reduces the incidence of insecure patterns. I tested this by implementing the same feature twice: once without the context file, once with it. Without the file, the assistant suggested storing the Discord token in a config object. With the file, it immediately reached for process.env.DISCORD_TOKEN and added validation.
Permission Boundaries in Practice
The Thoughtworks team emphasizes being cautious with AI permission requests. This maps directly to how modern AI coding tools like Claude Code and GitHub Copilot Workspace handle file system access and command execution.
Claude Code uses a permission system where each file write, shell command, or external network request requires explicit approval. The problem is that approving every operation creates friction that slows development. The solution is a project-level allowlist in .claude/settings.json:
{
"allowedCommands": [
{"command": "npm install", "scope": "project"},
{"command": "npm test", "scope": "project"},
{"command": "git status", "scope": "global"}
],
"allowedPaths": {
"read": ["src/**", "tests/**", "package.json"],
"write": ["src/**", "tests/**"],
"deny": [".env", "*.key", "credentials/**"]
}
}
This creates a boundary. The assistant can freely read source code and tests, write to those directories, and run approved commands. But it cannot access credential files or run arbitrary shell commands without asking. The first time I configured this, I caught the assistant trying to cat .env to check an environment variable format. The permission denial forced it to ask me for the information instead.
Daily Security Intelligence Feeds
The article recommends creating a security intelligence feed. In practice, this means automating vulnerability monitoring and feeding results back into the development workflow. For Node.js projects, this looks like:
# Run daily via GitHub Actions or cron
npm audit --json > security-audit.json
npx snyk test --json > snyk-results.json
# Generate summary for AI context
node scripts/generate-security-summary.js
The summary script produces a SECURITY-STATUS.md file that gets read by the AI assistant:
# Security Status (2026-05-27)
## Critical Issues
- express@4.18.2: Prototype pollution (CVE-2024-XXXX)
Action: Upgrade to 4.19.1+
## Warnings
- jsonwebtoken@9.0.0: Timing attack in verification
Action: Upgrade to 9.0.2+
## Passed
- All Discord.js dependencies clean
- No outdated packages >6 months old
When the assistant generates code that uses express or jsonwebtoken, it sees this status file and either avoids the vulnerable version or explicitly comments that an upgrade is needed. I’ve seen this work in real sessions where Claude suggested using a newer version of a library specifically because the security status file indicated a CVE in the older version.
Secure-by-Default Harnesses
The final recommendation is providing builders with secure-by-default templates. This is where the rubber meets the road. Instead of starting from an empty repository or a minimal boilerplate, teams should maintain hardened templates that embed security decisions.
For a Discord bot, a secure-by-default template includes:
- Environment configuration with validation:
const dotenv = require('dotenv');
const Joi = require('joi');
if (process.env.NODE_ENV !== 'production') {
dotenv.config();
}
const envSchema = Joi.object({
DISCORD_TOKEN: Joi.string().required(),
DATABASE_URL: Joi.string().uri().required(),
NODE_ENV: Joi.string().valid('development', 'production').default('development'),
}).unknown();
const { error, value: env } = envSchema.validate(process.env);
if (error) {
throw new Error(`Config validation error: ${error.message}`);
}
module.exports = env;
- Database access with prepared statements:
const { Pool } = require('pg');
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: true } : false,
max: 10,
idleTimeoutMillis: 30000,
});
async function query(text, params) {
const start = Date.now();
const res = await pool.query(text, params);
const duration = Date.now() - start;
console.log('Executed query', { text, duration, rows: res.rowCount });
return res;
}
module.exports = { query };
- Rate limiting middleware:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 10,
message: 'Too many requests from this IP',
standardHeaders: true,
legacyHeaders: false,
});
module.exports = limiter;
When these patterns are already in the codebase, the AI assistant learns from them through retrieval-augmented generation. It sees that database queries always use parameterized statements, so it suggests the same pattern for new features. It sees that environment variables are validated on startup, so it suggests adding new variables to the schema rather than accessing process.env directly.
Measuring the Impact
The Thoughtworks team doesn’t provide quantitative metrics in their article, but I tracked this in my own projects. Over three months building a Discord bot with ~15,000 lines of code:
- Without security context file: 23 instances of hardcoded credentials, insecure defaults, or missing input validation across ~40 AI-generated features
- With security context file: 4 instances, all caught in code review because they were edge cases the context file didn’t cover
The false positive rate (assistant suggesting overly restrictive security measures) was negligible. I saw one case where it refused to use eval() even for a legitimate AST manipulation use case, but that’s a reasonable default.
The Broader Ecosystem
Security context files are gaining adoption beyond Claude Code. Cursor supports .cursorrules files, Codeium reads project-level configuration, and the OpenAI Codex API allows system-level prompts. The OWASP AI Security and Privacy Guide now recommends context files as a control mechanism for LLM-generated code.
The pattern extends beyond security. Teams use context files to enforce architectural decisions (“always use repository pattern for database access”), code style (“prefer functional composition over classes”), and operational constraints (“all async operations must have timeout and retry logic”).
What This Doesn’t Solve
Security context files are a guardrail, not a solution. They don’t catch logic bugs, business logic flaws, or architectural security issues. An assistant can follow every rule in your context file and still generate code with authentication bypass vulnerabilities if the prompt doesn’t capture the security requirement.
They also don’t replace code review, static analysis, or penetration testing. Tools like Semgrep, Snyk Code, and CodeQL remain essential for catching issues that slip through.
The value is in shifting the baseline. Instead of reviewing AI-generated code that’s riddled with insecure defaults, you’re reviewing code that starts from a secure foundation and only requires scrutiny for higher-order issues.
Implementation Checklist
If you’re using AI coding assistants in production:
- Create a
.claude/SECURITY.mdor equivalent context file with your minimum security requirements - Configure permission boundaries in your AI tool’s settings to deny access to credential files
- Set up automated daily security scans (npm audit, Snyk, or Dependabot) and feed results into a status file
- Build or adopt secure-by-default templates for common patterns in your stack
- Track metrics: count security issues in AI-generated code before and after implementing these controls
The Thoughtworks team’s experience building marketing applications mirrors what I’ve seen building bots and internal tools. AI assistants are transformative for productivity, but they require explicit security constraints to be safe. Security context files provide that constraint layer in a way that’s persistent, auditable, and effective.