| name | pact-security-patterns |
| description | Security best practices and threat mitigation patterns for PACT framework development.
Use when: implementing authentication or authorization, handling API credentials,
integrating external APIs, processing sensitive data (PII, financial, health),
reviewing code for vulnerabilities, or enforcing SACROSANCT security rules.
Triggers on: security audit, credential handling, OWASP, auth flows, encryption,
data protection, backend proxy pattern, frontend credential exposure.
|
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:
npm run build
grep -r "sk-" dist/assets/*.js
grep -r "api_key" dist/assets/*.js
grep -r "VITE_" dist/assets/*.js
Quick Security Reference
Input Validation
Always validate on the server side:
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() });
}
}
);
Output Encoding
Prevent XSS by encoding output:
return <div>{userInput}</div>;
return <div dangerouslySetInnerHTML={{__html: userInput}} />;
const escapeHtml = (str) => str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
SQL Injection Prevention
Always use parameterized queries:
const query = `SELECT * FROM users WHERE id = ${userId}`;
const query = 'SELECT * FROM users WHERE id = $1';
const result = await db.query(query, [userId]);
const user = await prisma.user.findUnique({
where: { id: userId }
});
Authentication Security
Password Storage:
const bcrypt = require('bcrypt');
const saltRounds = 12;
const hashedPassword = await bcrypt.hash(password, saltRounds);
const isValid = await bcrypt.compare(password, hashedPassword);
Session Configuration:
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
httpOnly: true,
sameSite: 'strict',
maxAge: 3600000
}
}));
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');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: { error: 'Too many requests, please try again later' }
});
const authLimiter = rateLimit({
windowMs: 60 * 60 * 1000,
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
Architecture
Input/Output
Authentication
Headers and Transport
Detailed References
For comprehensive security guidance, see: