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