PACT Security Patterns
Security guidance for PACT development phases. This skill provides essential security patterns and links to detailed references for comprehensive implementation.
SACROSANCT Rules (Non-Negotiable)
These rules are ABSOLUTE and must NEVER be violated.
Rule 1: Credential Protection
NEVER ALLOW in version control:
- •Actual API keys, tokens, passwords, or secrets
- •Credentials in frontend code (VITE_, REACT_APP_, NEXT_PUBLIC_ prefixes)
- •Real credential values in documentation or code examples
- •Hardcoded secrets in any file committed to git
ONLY acceptable locations for actual credentials:
| Location | Example | Security Level |
|---|---|---|
.env files in .gitignore | API_KEY=sk-xxx | Development |
Server-side process.env | process.env.API_KEY | Runtime |
| Deployment platform secrets | Railway, Vercel, AWS | Production |
| Secrets managers | Vault, AWS Secrets Manager | Enterprise |
In Documentation - Always Use Placeholders:
# Configuration Set your API key in `.env`: API_KEY=your_api_key_here
Rule 2: Backend Proxy Pattern
WRONG: Frontend --> External API (credentials in frontend) CORRECT: Frontend --> Backend Proxy --> External API
Architecture Requirements:
- •Frontend MUST NEVER have direct access to API credentials
- •ALL API credentials MUST exist exclusively on server-side
- •Frontend calls backend endpoints (
/api/resource) without credentials - •Backend handles ALL authentication with external APIs
- •Backend validates and sanitizes ALL requests from frontend
Verification Checklist:
# Build the application npm run build # Search for exposed credentials in bundle grep -r "sk-" dist/assets/*.js grep -r "api_key" dist/assets/*.js grep -r "VITE_" dist/assets/*.js # All above should return NO results
Quick Security Reference
Input Validation
Always validate on the server side:
// Express.js example
const { body, validationResult } = require('express-validator');
app.post('/api/user',
body('email').isEmail().normalizeEmail(),
body('name').trim().escape().isLength({ min: 1, max: 100 }),
body('age').isInt({ min: 0, max: 150 }),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
// Process validated input
}
);
Output Encoding
Prevent XSS by encoding output:
// React (automatic encoding)
return <div>{userInput}</div>; // Safe - React escapes
// Dangerous - avoid unless absolutely necessary
return <div dangerouslySetInnerHTML={{__html: userInput}} />; // UNSAFE
// Node.js HTML response
const escapeHtml = (str) => str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
SQL Injection Prevention
Always use parameterized queries:
// WRONG - SQL Injection vulnerable
const query = `SELECT * FROM users WHERE id = ${userId}`;
// CORRECT - Parameterized query
const query = 'SELECT * FROM users WHERE id = $1';
const result = await db.query(query, [userId]);
// ORM example (Prisma)
const user = await prisma.user.findUnique({
where: { id: userId } // Safe - Prisma handles escaping
});
Authentication Security
Password Storage:
const bcrypt = require('bcrypt');
// Hashing password
const saltRounds = 12; // Minimum recommended
const hashedPassword = await bcrypt.hash(password, saltRounds);
// Verifying password
const isValid = await bcrypt.compare(password, hashedPassword);
Session Configuration:
app.use(session({
secret: process.env.SESSION_SECRET, // Strong, random secret
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // No JavaScript access
sameSite: 'strict', // CSRF protection
maxAge: 3600000 // 1 hour
}
}));
Security Headers
Essential HTTP headers:
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'"],
frameSrc: ["'none'"],
objectSrc: ["'none'"]
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true
}
}));
Rate Limiting
Protect against abuse:
const rateLimit = require('express-rate-limit');
// General API rate limit
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
message: { error: 'Too many requests, please try again later' }
});
// Stricter limit for auth endpoints
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5,
message: { error: 'Too many login attempts' }
});
app.use('/api/', apiLimiter);
app.use('/api/auth/', authLimiter);
Security Checklist
Before any commit or deployment, verify:
Credential Protection
- • No credentials in staged files (
git diff --staged | grep -i "key\|secret\|password") - •
.envfiles listed in.gitignore - • Placeholders used in all documentation
- • No hardcoded API keys in source code
Architecture
- • Frontend makes NO direct external API calls with credentials
- • Backend proxy pattern implemented for all external integrations
- • All credentials loaded from environment variables
Input/Output
- • All user inputs validated server-side
- • SQL queries use parameterized statements
- • HTML output properly encoded
- • File uploads validated for type and size
Authentication
- • Passwords hashed with bcrypt (12+ rounds)
- • Sessions configured with secure flags
- • Authentication endpoints rate-limited
- • JWT tokens have short expiration
Headers and Transport
- • Security headers configured (use Helmet.js or equivalent)
- • HTTPS enforced in production
- • CORS configured restrictively
Detailed References
For comprehensive security guidance, see:
- •
OWASP Top 10 Mitigations: references/owasp-top-10.md
- •Detailed vulnerability descriptions
- •Code examples for each mitigation
- •Testing approaches
- •
Authentication Patterns: references/authentication-patterns.md
- •JWT implementation
- •Session management
- •OAuth 2.0 flows
- •Multi-factor authentication
- •
Data Protection: references/data-protection.md
- •Encryption at rest and in transit
- •PII handling requirements
- •GDPR compliance patterns
- •Key management