secure-coding-rules 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +168 -0
- package/bin/cli.js +8 -0
- package/package.json +64 -0
- package/src/adapters/agents.js +54 -0
- package/src/adapters/claude.js +88 -0
- package/src/adapters/copilot.js +82 -0
- package/src/adapters/cursor.js +68 -0
- package/src/adapters/windsurf.js +57 -0
- package/src/index.js +177 -0
- package/src/loader.js +85 -0
- package/src/prompts.js +307 -0
- package/src/templates/core/access-control.md +112 -0
- package/src/templates/core/authentication.md +138 -0
- package/src/templates/core/cryptographic.md +114 -0
- package/src/templates/core/data-integrity.md +144 -0
- package/src/templates/core/error-handling.md +180 -0
- package/src/templates/core/injection.md +125 -0
- package/src/templates/core/logging-alerting.md +156 -0
- package/src/templates/core/secure-design.md +117 -0
- package/src/templates/core/security-config.md +118 -0
- package/src/templates/core/supply-chain.md +127 -0
- package/src/templates/frontend/csp.md +146 -0
- package/src/templates/frontend/csrf-protection.md +151 -0
- package/src/templates/frontend/secure-state.md +167 -0
- package/src/templates/frontend/xss-prevention.md +125 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Injection Security Rules
|
|
2
|
+
|
|
3
|
+
> OWASP Top 10 2025 - A05: Injection
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
### 1. Use Parameterized Queries for All Database Operations
|
|
8
|
+
- **DO**: Use parameterized queries, prepared statements, or ORM query builders for all SQL and NoSQL operations.
|
|
9
|
+
- **DON'T**: Concatenate or interpolate user input into query strings.
|
|
10
|
+
- **WHY**: SQL/NoSQL injection allows attackers to read, modify, or delete entire databases and potentially execute system commands.
|
|
11
|
+
|
|
12
|
+
### 2. Sanitize and Escape All Output
|
|
13
|
+
- **DO**: Context-encode output before rendering in HTML, JavaScript, CSS, or URL contexts. Use framework auto-escaping.
|
|
14
|
+
- **DON'T**: Insert raw user input into HTML templates or DOM elements using `innerHTML` or `dangerouslySetInnerHTML`.
|
|
15
|
+
- **WHY**: Cross-Site Scripting (XSS) enables session hijacking, credential theft, and defacement.
|
|
16
|
+
|
|
17
|
+
### 3. Validate and Sanitize User Input
|
|
18
|
+
- **DO**: Validate input against strict schemas (type, length, format, allowed characters). Use allowlists over denylists.
|
|
19
|
+
- **DON'T**: Accept any input and try to filter out known-bad patterns.
|
|
20
|
+
- **WHY**: Denylist-based filtering is always incomplete. Allowlisting ensures only expected input is processed.
|
|
21
|
+
|
|
22
|
+
### 4. Prevent Command Injection
|
|
23
|
+
- **DO**: Avoid shell commands. If necessary, use `execFile` with an argument array instead of `exec` with string interpolation.
|
|
24
|
+
- **DON'T**: Pass user input to `child_process.exec()`, `eval()`, `Function()`, or template literals in shell commands.
|
|
25
|
+
- **WHY**: Command injection gives attackers full control over the server operating system.
|
|
26
|
+
|
|
27
|
+
### 5. Guard Against Template Injection
|
|
28
|
+
- **DO**: Use logic-less templates or sandbox template rendering. Never pass user input as template source.
|
|
29
|
+
- **DON'T**: Allow users to control template strings that are compiled or rendered server-side.
|
|
30
|
+
- **WHY**: Server-Side Template Injection (SSTI) can lead to Remote Code Execution (RCE).
|
|
31
|
+
|
|
32
|
+
### 6. Prevent Path Traversal
|
|
33
|
+
- **DO**: Resolve file paths and verify they fall within the intended directory using `path.resolve()` and prefix checking.
|
|
34
|
+
- **DON'T**: Use user input directly in file system operations without path validation.
|
|
35
|
+
- **WHY**: Path traversal allows reading arbitrary files like `/etc/passwd` or application secrets.
|
|
36
|
+
|
|
37
|
+
### 7. Sanitize Regular Expressions
|
|
38
|
+
- **DO**: Escape user input used in regular expressions. Set timeouts or use RE2 for untrusted patterns.
|
|
39
|
+
- **DON'T**: Pass user input directly to `new RegExp()` without escaping.
|
|
40
|
+
- **WHY**: ReDoS (Regular Expression Denial of Service) can hang the event loop with crafted input.
|
|
41
|
+
|
|
42
|
+
## Code Examples
|
|
43
|
+
|
|
44
|
+
### Bad Practice
|
|
45
|
+
```javascript
|
|
46
|
+
// SQL Injection
|
|
47
|
+
const query = `SELECT * FROM users WHERE id = '${req.params.id}'`;
|
|
48
|
+
await db.query(query);
|
|
49
|
+
|
|
50
|
+
// NoSQL Injection
|
|
51
|
+
const user = await User.findOne({ username: req.body.username, password: req.body.password });
|
|
52
|
+
|
|
53
|
+
// Command Injection
|
|
54
|
+
const { exec } = require("child_process");
|
|
55
|
+
exec(`convert ${req.query.filename} output.png`);
|
|
56
|
+
|
|
57
|
+
// Path Traversal
|
|
58
|
+
const filePath = `./uploads/${req.params.filename}`;
|
|
59
|
+
res.sendFile(filePath);
|
|
60
|
+
|
|
61
|
+
// XSS via innerHTML
|
|
62
|
+
element.innerHTML = userInput;
|
|
63
|
+
|
|
64
|
+
// eval with user input
|
|
65
|
+
const result = eval(req.body.expression);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Good Practice
|
|
69
|
+
```javascript
|
|
70
|
+
import { execFile } from "node:child_process";
|
|
71
|
+
import path from "node:path";
|
|
72
|
+
|
|
73
|
+
// Parameterized SQL query
|
|
74
|
+
const [rows] = await db.execute(
|
|
75
|
+
"SELECT * FROM users WHERE id = ?",
|
|
76
|
+
[req.params.id]
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Safe NoSQL query - validate types explicitly
|
|
80
|
+
const username = String(req.body.username);
|
|
81
|
+
const user = await User.findOne({ username });
|
|
82
|
+
const isValid = await verifyPassword(req.body.password, user.passwordHash);
|
|
83
|
+
|
|
84
|
+
// Safe command execution with execFile
|
|
85
|
+
execFile("convert", [validatedFilename, "output.png"], (error, stdout) => {
|
|
86
|
+
if (error) handleError(error);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Path traversal prevention
|
|
90
|
+
function getSafeFilePath(userInput, baseDir) {
|
|
91
|
+
const resolved = path.resolve(baseDir, userInput);
|
|
92
|
+
if (!resolved.startsWith(path.resolve(baseDir) + path.sep)) {
|
|
93
|
+
throw new Error("Path traversal detected");
|
|
94
|
+
}
|
|
95
|
+
return resolved;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Safe DOM manipulation
|
|
99
|
+
element.textContent = userInput; // Auto-escaped, no HTML parsing
|
|
100
|
+
|
|
101
|
+
// Input validation with schema
|
|
102
|
+
import { z } from "zod";
|
|
103
|
+
const UserInput = z.object({
|
|
104
|
+
email: z.string().email().max(254),
|
|
105
|
+
name: z.string().min(1).max(100).regex(/^[a-zA-Z\s'-]+$/),
|
|
106
|
+
age: z.number().int().min(0).max(150),
|
|
107
|
+
});
|
|
108
|
+
const validated = UserInput.parse(req.body);
|
|
109
|
+
|
|
110
|
+
// Safe regex from user input
|
|
111
|
+
function escapeRegex(str) {
|
|
112
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
113
|
+
}
|
|
114
|
+
const safePattern = new RegExp(escapeRegex(userInput), "i");
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Quick Checklist
|
|
118
|
+
- [ ] All database queries use parameterized statements or ORM query builders
|
|
119
|
+
- [ ] User input is never concatenated into SQL, NoSQL, LDAP, or OS commands
|
|
120
|
+
- [ ] Output is context-encoded for the rendering context (HTML, JS, URL, CSS)
|
|
121
|
+
- [ ] `eval()`, `Function()`, and `setTimeout(string)` are never used with user input
|
|
122
|
+
- [ ] File paths from user input are resolved and validated against a base directory
|
|
123
|
+
- [ ] Shell commands use `execFile` with argument arrays, not `exec` with string interpolation
|
|
124
|
+
- [ ] Input is validated with strict schemas (type, length, format, allowlist)
|
|
125
|
+
- [ ] Regular expressions from user input are escaped or use safe regex engines
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Security Logging and Alerting Rules
|
|
2
|
+
|
|
3
|
+
> OWASP Top 10 2025 - A09: Security Logging and Alerting Failures
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
### 1. Log All Security-Relevant Events
|
|
8
|
+
- **DO**: Log authentication attempts (success and failure), access control failures, input validation failures, and administrative actions.
|
|
9
|
+
- **DON'T**: Rely on generic application logs to detect security incidents.
|
|
10
|
+
- **WHY**: Without security event logging, breaches go undetected. Mean time to detect a breach averages 200+ days without proper logging.
|
|
11
|
+
|
|
12
|
+
### 2. Never Log Sensitive Data
|
|
13
|
+
- **DO**: Sanitize logs to exclude passwords, tokens, credit card numbers, PII, and session IDs. Use structured logging with redaction.
|
|
14
|
+
- **DON'T**: Log request bodies, full headers, or raw user input without sanitization.
|
|
15
|
+
- **WHY**: Logs are often stored with weaker access controls than primary data stores. Leaked logs expose credentials and PII.
|
|
16
|
+
|
|
17
|
+
### 3. Use Structured, Consistent Log Formats
|
|
18
|
+
- **DO**: Use JSON-structured logging with consistent fields (timestamp, level, event type, user ID, IP, request ID).
|
|
19
|
+
- **DON'T**: Use unstructured `console.log()` for security events or mix log formats.
|
|
20
|
+
- **WHY**: Structured logs enable automated parsing, correlation, and alerting by SIEM tools.
|
|
21
|
+
|
|
22
|
+
### 4. Implement Tamper-Evident Logging
|
|
23
|
+
- **DO**: Send logs to a centralized, append-only logging service. Implement log integrity checks.
|
|
24
|
+
- **DON'T**: Store security logs only on the application server where an attacker could modify them.
|
|
25
|
+
- **WHY**: Attackers routinely delete or modify local logs to cover their tracks.
|
|
26
|
+
|
|
27
|
+
### 5. Set Up Real-Time Alerting for Critical Events
|
|
28
|
+
- **DO**: Configure alerts for brute-force attempts, privilege escalation, unusual data access patterns, and authentication anomalies.
|
|
29
|
+
- **DON'T**: Only review logs manually or after an incident is reported by users.
|
|
30
|
+
- **WHY**: Real-time alerting reduces breach detection time from months to minutes, limiting damage.
|
|
31
|
+
|
|
32
|
+
### 6. Protect Log Injection
|
|
33
|
+
- **DO**: Sanitize log inputs to prevent log injection (newlines, control characters). Use parameterized logging.
|
|
34
|
+
- **DON'T**: Directly interpolate user input into log messages.
|
|
35
|
+
- **WHY**: Log injection can forge log entries, corrupt log analysis, or exploit log viewer vulnerabilities.
|
|
36
|
+
|
|
37
|
+
### 7. Ensure Adequate Log Retention
|
|
38
|
+
- **DO**: Retain security logs for at least 90 days (hot) and 1 year (cold) per compliance requirements.
|
|
39
|
+
- **DON'T**: Delete logs immediately after processing or keep them indefinitely without retention policies.
|
|
40
|
+
- **WHY**: Incident investigation often requires historical log analysis. Retention policies balance security with storage costs and privacy regulations.
|
|
41
|
+
|
|
42
|
+
## Code Examples
|
|
43
|
+
|
|
44
|
+
### Bad Practice
|
|
45
|
+
```javascript
|
|
46
|
+
// Logging sensitive data
|
|
47
|
+
console.log(`User login: ${email}, password: ${password}`);
|
|
48
|
+
console.log(`Payment processed: card=${cardNumber}, amount=${amount}`);
|
|
49
|
+
|
|
50
|
+
// Log injection vulnerability
|
|
51
|
+
const username = req.body.username; // Could contain "\nAdmin login successful"
|
|
52
|
+
console.log(`Login attempt for user: ${username}`);
|
|
53
|
+
|
|
54
|
+
// No security event logging
|
|
55
|
+
app.post("/api/login", async (req, res) => {
|
|
56
|
+
const user = await authenticate(req.body);
|
|
57
|
+
if (user) res.json({ token: createToken(user) });
|
|
58
|
+
else res.status(401).json({ error: "Invalid" });
|
|
59
|
+
// No logging of success or failure
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Good Practice
|
|
64
|
+
```javascript
|
|
65
|
+
import pino from "pino";
|
|
66
|
+
|
|
67
|
+
// Structured logger with redaction
|
|
68
|
+
const logger = pino({
|
|
69
|
+
level: "info",
|
|
70
|
+
redact: {
|
|
71
|
+
paths: ["password", "token", "authorization", "cookie", "*.password", "*.token"],
|
|
72
|
+
censor: "[REDACTED]",
|
|
73
|
+
},
|
|
74
|
+
serializers: {
|
|
75
|
+
req: pino.stdSerializers.req,
|
|
76
|
+
err: pino.stdSerializers.err,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Security event logger
|
|
81
|
+
function logSecurityEvent(event) {
|
|
82
|
+
logger.info({
|
|
83
|
+
type: "security",
|
|
84
|
+
event: event.action,
|
|
85
|
+
userId: event.userId ?? "anonymous",
|
|
86
|
+
ip: event.ip,
|
|
87
|
+
userAgent: event.userAgent,
|
|
88
|
+
resource: event.resource,
|
|
89
|
+
outcome: event.outcome,
|
|
90
|
+
timestamp: new Date().toISOString(),
|
|
91
|
+
requestId: event.requestId,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Login with comprehensive security logging
|
|
96
|
+
app.post("/api/login", loginLimiter, async (req, res) => {
|
|
97
|
+
const { email, password } = req.body;
|
|
98
|
+
const requestId = crypto.randomUUID();
|
|
99
|
+
const user = await db.findUser(email);
|
|
100
|
+
const isValid = user ? await verifyPassword(password, user.passwordHash) : false;
|
|
101
|
+
|
|
102
|
+
logSecurityEvent({
|
|
103
|
+
action: isValid ? "login_success" : "login_failure",
|
|
104
|
+
userId: user?.id,
|
|
105
|
+
ip: req.ip,
|
|
106
|
+
userAgent: req.get("user-agent"),
|
|
107
|
+
resource: "/api/login",
|
|
108
|
+
outcome: isValid ? "success" : "failure",
|
|
109
|
+
requestId,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (!isValid) {
|
|
113
|
+
return res.status(401).json({ error: "Invalid credentials", requestId });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
setSessionCookie(res, user);
|
|
117
|
+
res.json({ success: true });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Safe log message - prevent log injection
|
|
121
|
+
function sanitizeForLog(input) {
|
|
122
|
+
if (typeof input !== "string") return String(input);
|
|
123
|
+
return input.replace(/[\n\r\t]/g, "").substring(0, 500);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Access control failure logging middleware
|
|
127
|
+
function logAccessDenied(req, res, next) {
|
|
128
|
+
const originalJson = res.json.bind(res);
|
|
129
|
+
res.json = (body) => {
|
|
130
|
+
if (res.statusCode === 403) {
|
|
131
|
+
logSecurityEvent({
|
|
132
|
+
action: "access_denied",
|
|
133
|
+
userId: req.user?.id,
|
|
134
|
+
ip: req.ip,
|
|
135
|
+
userAgent: req.get("user-agent"),
|
|
136
|
+
resource: `${req.method} ${req.originalUrl}`,
|
|
137
|
+
outcome: "denied",
|
|
138
|
+
requestId: req.id,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return originalJson(body);
|
|
142
|
+
};
|
|
143
|
+
next();
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Quick Checklist
|
|
148
|
+
- [ ] Authentication successes and failures are logged
|
|
149
|
+
- [ ] Access control violations are logged
|
|
150
|
+
- [ ] No passwords, tokens, or PII in log output
|
|
151
|
+
- [ ] Structured JSON logging format with consistent fields
|
|
152
|
+
- [ ] Logs sent to centralized, tamper-resistant storage
|
|
153
|
+
- [ ] Real-time alerts configured for critical security events
|
|
154
|
+
- [ ] Log inputs sanitized to prevent log injection
|
|
155
|
+
- [ ] Log retention policy defined and enforced (90 days+)
|
|
156
|
+
- [ ] Each log entry includes timestamp, user ID, IP, and request ID
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Insecure Design Security Rules
|
|
2
|
+
|
|
3
|
+
> OWASP Top 10 2025 - A06: Insecure Design
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
### 1. Apply Threat Modeling During Design
|
|
8
|
+
- **DO**: Identify trust boundaries, data flows, and threat actors before writing code. Use STRIDE or similar frameworks.
|
|
9
|
+
- **DON'T**: Bolt security onto the application after implementation.
|
|
10
|
+
- **WHY**: Security flaws rooted in design cannot be fixed by better implementation alone. Architecture-level vulnerabilities require redesign.
|
|
11
|
+
|
|
12
|
+
### 2. Enforce Business Logic Limits
|
|
13
|
+
- **DO**: Implement server-side limits for business operations (e.g., max transaction amount, order quantity, API calls per user).
|
|
14
|
+
- **DON'T**: Rely on the UI to enforce business rules like purchase limits or booking constraints.
|
|
15
|
+
- **WHY**: Business logic abuse (e.g., buying items at negative prices, mass-redeeming coupons) bypasses client-side validation easily.
|
|
16
|
+
|
|
17
|
+
### 3. Implement Defense in Depth
|
|
18
|
+
- **DO**: Layer multiple security controls so that failure of one does not compromise the system. Combine input validation, access control, encryption, and monitoring.
|
|
19
|
+
- **DON'T**: Depend on a single security mechanism to protect critical assets.
|
|
20
|
+
- **WHY**: No single control is perfect. Layered defenses ensure that an attacker must defeat multiple barriers.
|
|
21
|
+
|
|
22
|
+
### 4. Follow the Principle of Least Privilege
|
|
23
|
+
- **DO**: Grant minimum necessary permissions to users, services, and processes. Use separate service accounts with scoped credentials.
|
|
24
|
+
- **DON'T**: Run services as root, use admin-level database credentials for application queries, or grant blanket permissions.
|
|
25
|
+
- **WHY**: Over-privileged components amplify the impact of any breach, turning a small vulnerability into full system compromise.
|
|
26
|
+
|
|
27
|
+
### 5. Design for Abuse Cases
|
|
28
|
+
- **DO**: For every feature, ask "how could this be abused?" and implement safeguards (rate limiting, CAPTCHA, fraud detection).
|
|
29
|
+
- **DON'T**: Only consider the happy path in feature design. Assume all input can be malicious.
|
|
30
|
+
- **WHY**: Attackers use features in unintended ways. Referral systems get exploited, file uploads deliver malware, search becomes a data exfiltration tool.
|
|
31
|
+
|
|
32
|
+
### 6. Separate Sensitive Operations
|
|
33
|
+
- **DO**: Require re-authentication or multi-factor confirmation for critical operations (password change, fund transfer, account deletion).
|
|
34
|
+
- **DON'T**: Allow destructive or sensitive operations with a single click or without additional verification.
|
|
35
|
+
- **WHY**: Session hijacking or CSRF can trigger sensitive operations. Step-up authentication adds a security boundary.
|
|
36
|
+
|
|
37
|
+
## Code Examples
|
|
38
|
+
|
|
39
|
+
### Bad Practice
|
|
40
|
+
```javascript
|
|
41
|
+
// No business logic validation on the server
|
|
42
|
+
app.post("/api/transfer", authenticate, async (req, res) => {
|
|
43
|
+
const { amount, toAccount } = req.body;
|
|
44
|
+
// Trusting client-sent amount without validation
|
|
45
|
+
await transferFunds(req.user.id, toAccount, amount);
|
|
46
|
+
res.json({ success: true });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Feature without abuse consideration
|
|
50
|
+
app.post("/api/referral", authenticate, async (req, res) => {
|
|
51
|
+
// No limit on referral bonuses - can be exploited with fake accounts
|
|
52
|
+
await addReferralBonus(req.user.id, req.body.referralCode);
|
|
53
|
+
res.json({ success: true });
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Good Practice
|
|
58
|
+
```javascript
|
|
59
|
+
import { z } from "zod";
|
|
60
|
+
import { rateLimit } from "express-rate-limit";
|
|
61
|
+
|
|
62
|
+
// Business logic with server-side validation and limits
|
|
63
|
+
const TransferSchema = z.object({
|
|
64
|
+
amount: z.number().positive().max(10000), // Business limit
|
|
65
|
+
toAccount: z.string().regex(/^\d{10,12}$/),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
app.post("/api/transfer", authenticate, async (req, res) => {
|
|
69
|
+
const { amount, toAccount } = TransferSchema.parse(req.body);
|
|
70
|
+
|
|
71
|
+
// Check daily transfer limit
|
|
72
|
+
const dailyTotal = await getDailyTransferTotal(req.user.id);
|
|
73
|
+
if (dailyTotal + amount > req.user.dailyLimit) {
|
|
74
|
+
return res.status(400).json({ error: "Daily transfer limit exceeded" });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Require step-up authentication for large transfers
|
|
78
|
+
if (amount > 1000) {
|
|
79
|
+
const mfaVerified = await verifyMFA(req.user.id, req.body.mfaToken);
|
|
80
|
+
if (!mfaVerified) {
|
|
81
|
+
return res.status(403).json({ error: "MFA required for large transfers" });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await transferFunds(req.user.id, toAccount, amount);
|
|
86
|
+
await logAuditEvent("transfer", { userId: req.user.id, amount, toAccount });
|
|
87
|
+
res.json({ success: true });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Referral system with abuse prevention
|
|
91
|
+
const referralLimiter = rateLimit({ windowMs: 24 * 60 * 60 * 1000, max: 5 });
|
|
92
|
+
|
|
93
|
+
app.post("/api/referral", authenticate, referralLimiter, async (req, res) => {
|
|
94
|
+
const referrer = await getUserByReferralCode(req.body.referralCode);
|
|
95
|
+
|
|
96
|
+
// Abuse checks
|
|
97
|
+
if (referrer.id === req.user.id) {
|
|
98
|
+
return res.status(400).json({ error: "Cannot refer yourself" });
|
|
99
|
+
}
|
|
100
|
+
const existingReferral = await getReferral(req.user.id);
|
|
101
|
+
if (existingReferral) {
|
|
102
|
+
return res.status(400).json({ error: "Already used a referral" });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
await addReferralBonus(referrer.id, req.user.id);
|
|
106
|
+
res.json({ success: true });
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Quick Checklist
|
|
111
|
+
- [ ] Threat modeling performed for critical features
|
|
112
|
+
- [ ] Business logic limits enforced server-side (not just client-side)
|
|
113
|
+
- [ ] Multiple layers of security controls (defense in depth)
|
|
114
|
+
- [ ] Services run with minimum required privileges
|
|
115
|
+
- [ ] Abuse cases identified and mitigated for each feature
|
|
116
|
+
- [ ] Sensitive operations require step-up authentication or MFA
|
|
117
|
+
- [ ] Data flows and trust boundaries documented
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Security Configuration Rules
|
|
2
|
+
|
|
3
|
+
> OWASP Top 10 2025 - A02: Security Misconfiguration
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
### 1. Disable Verbose Error Messages in Production
|
|
8
|
+
- **DO**: Return generic error responses to clients. Log detailed errors server-side only.
|
|
9
|
+
- **DON'T**: Expose stack traces, database errors, or internal paths in API responses.
|
|
10
|
+
- **WHY**: Verbose errors leak implementation details that attackers use for targeted exploitation.
|
|
11
|
+
|
|
12
|
+
### 2. Remove Default Credentials and Configurations
|
|
13
|
+
- **DO**: Change all default passwords, API keys, and configuration values before deployment.
|
|
14
|
+
- **DON'T**: Ship applications with default admin accounts, sample configurations, or debug settings enabled.
|
|
15
|
+
- **WHY**: Default credentials are the first thing attackers try and are widely documented.
|
|
16
|
+
|
|
17
|
+
### 3. Set Secure HTTP Headers
|
|
18
|
+
- **DO**: Configure security headers: `Strict-Transport-Security`, `X-Content-Type-Options`, `X-Frame-Options`, `Content-Security-Policy`, and `Permissions-Policy`.
|
|
19
|
+
- **DON'T**: Rely on framework defaults without verifying which security headers are actually set.
|
|
20
|
+
- **WHY**: Security headers provide defense-in-depth against XSS, clickjacking, MIME sniffing, and protocol downgrade attacks.
|
|
21
|
+
|
|
22
|
+
### 4. Disable Unnecessary Features and Services
|
|
23
|
+
- **DO**: Remove or disable unused routes, middleware, debug endpoints, and server features (e.g., directory listing, `X-Powered-By`).
|
|
24
|
+
- **DON'T**: Leave development tools, test endpoints, or administrative panels accessible in production.
|
|
25
|
+
- **WHY**: Every unnecessary feature expands the attack surface.
|
|
26
|
+
|
|
27
|
+
### 5. Enforce HTTPS Everywhere
|
|
28
|
+
- **DO**: Redirect all HTTP traffic to HTTPS. Use HSTS with a long `max-age` and `includeSubDomains`.
|
|
29
|
+
- **DON'T**: Allow mixed content or serve any resources over plain HTTP.
|
|
30
|
+
- **WHY**: Unencrypted traffic is vulnerable to interception, modification, and man-in-the-middle attacks.
|
|
31
|
+
|
|
32
|
+
### 6. Configure CORS Restrictively
|
|
33
|
+
- **DO**: Set `Access-Control-Allow-Origin` to specific trusted domains. Validate the `Origin` header server-side.
|
|
34
|
+
- **DON'T**: Use `Access-Control-Allow-Origin: *` with credentials or reflect any origin without validation.
|
|
35
|
+
- **WHY**: Overly permissive CORS allows malicious sites to make authenticated requests on behalf of users.
|
|
36
|
+
|
|
37
|
+
### 7. Harden Environment Configuration
|
|
38
|
+
- **DO**: Use environment variables or secret managers for sensitive configuration. Validate all config values at startup.
|
|
39
|
+
- **DON'T**: Commit secrets to version control or store them in plain-text config files.
|
|
40
|
+
- **WHY**: Leaked secrets in repositories are a leading cause of breaches.
|
|
41
|
+
|
|
42
|
+
## Code Examples
|
|
43
|
+
|
|
44
|
+
### Bad Practice
|
|
45
|
+
```javascript
|
|
46
|
+
// Leaking internal details in error responses
|
|
47
|
+
app.use((err, req, res, next) => {
|
|
48
|
+
res.status(500).json({
|
|
49
|
+
error: err.message,
|
|
50
|
+
stack: err.stack, // Exposes internals
|
|
51
|
+
query: err.sql, // Exposes database queries
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Permissive CORS
|
|
56
|
+
app.use(cors({ origin: "*", credentials: true })); // Dangerous combination
|
|
57
|
+
|
|
58
|
+
// Hardcoded secrets
|
|
59
|
+
const JWT_SECRET = "super-secret-key-123";
|
|
60
|
+
const DB_PASSWORD = "admin123";
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Good Practice
|
|
64
|
+
```javascript
|
|
65
|
+
import helmet from "helmet";
|
|
66
|
+
import cors from "cors";
|
|
67
|
+
|
|
68
|
+
// Security headers with helmet
|
|
69
|
+
app.use(helmet());
|
|
70
|
+
app.use(helmet.hsts({ maxAge: 31536000, includeSubDomains: true, preload: true }));
|
|
71
|
+
|
|
72
|
+
// Remove powered-by header
|
|
73
|
+
app.disable("x-powered-by");
|
|
74
|
+
|
|
75
|
+
// Restrictive CORS
|
|
76
|
+
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS?.split(",") ?? [];
|
|
77
|
+
app.use(cors({
|
|
78
|
+
origin(origin, callback) {
|
|
79
|
+
if (!origin || ALLOWED_ORIGINS.includes(origin)) {
|
|
80
|
+
callback(null, true);
|
|
81
|
+
} else {
|
|
82
|
+
callback(new Error("Not allowed by CORS"));
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
credentials: true,
|
|
86
|
+
methods: ["GET", "POST", "PUT", "DELETE"],
|
|
87
|
+
maxAge: 86400,
|
|
88
|
+
}));
|
|
89
|
+
|
|
90
|
+
// Safe error handler for production
|
|
91
|
+
app.use((err, req, res, next) => {
|
|
92
|
+
const errorId = crypto.randomUUID();
|
|
93
|
+
console.error({ errorId, message: err.message, stack: err.stack });
|
|
94
|
+
res.status(err.status ?? 500).json({
|
|
95
|
+
error: "An internal error occurred",
|
|
96
|
+
errorId, // For support reference only
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Validate required config at startup
|
|
101
|
+
const requiredEnvVars = ["JWT_SECRET", "DATABASE_URL", "ALLOWED_ORIGINS"];
|
|
102
|
+
for (const envVar of requiredEnvVars) {
|
|
103
|
+
if (!process.env[envVar]) {
|
|
104
|
+
throw new Error(`Missing required environment variable: ${envVar}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Quick Checklist
|
|
110
|
+
- [ ] No stack traces or internal details in production error responses
|
|
111
|
+
- [ ] All default credentials removed or changed
|
|
112
|
+
- [ ] Security headers configured (HSTS, CSP, X-Content-Type-Options, etc.)
|
|
113
|
+
- [ ] `X-Powered-By` and other fingerprinting headers disabled
|
|
114
|
+
- [ ] HTTPS enforced with HSTS
|
|
115
|
+
- [ ] CORS configured with specific allowed origins
|
|
116
|
+
- [ ] No secrets in source code or version control
|
|
117
|
+
- [ ] Unused routes, debug endpoints, and dev tools disabled in production
|
|
118
|
+
- [ ] Environment configuration validated at application startup
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Software Supply Chain Security Rules
|
|
2
|
+
|
|
3
|
+
> OWASP Top 10 2025 - A03: Software Supply Chain Failures (NEW)
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
### 1. Audit Dependencies Regularly
|
|
8
|
+
- **DO**: Run `npm audit` in CI/CD pipelines. Use tools like Socket.dev or Snyk to detect malicious or vulnerable packages.
|
|
9
|
+
- **DON'T**: Ignore audit warnings or suppress them without reviewing each finding.
|
|
10
|
+
- **WHY**: Known vulnerabilities in dependencies are a top attack vector. Automated auditing catches issues before deployment.
|
|
11
|
+
|
|
12
|
+
### 2. Pin Dependency Versions
|
|
13
|
+
- **DO**: Use exact versions or lockfiles (`package-lock.json`, `pnpm-lock.yaml`) and commit them to version control.
|
|
14
|
+
- **DON'T**: Use loose version ranges (e.g., `^` or `*`) for production dependencies without lockfile enforcement.
|
|
15
|
+
- **WHY**: Unpinned versions allow silent upgrades that may introduce vulnerabilities or malicious code.
|
|
16
|
+
|
|
17
|
+
### 3. Verify Lockfile Integrity
|
|
18
|
+
- **DO**: Enable lockfile-only installs in CI (`npm ci` or `--frozen-lockfile`). Detect and reject unexpected lockfile changes.
|
|
19
|
+
- **DON'T**: Run `npm install` in CI, which can modify the lockfile and pull in unreviewed versions.
|
|
20
|
+
- **WHY**: Lockfile manipulation is a supply chain attack vector. Strict installs ensure reproducible, verified builds.
|
|
21
|
+
|
|
22
|
+
### 4. Use Subresource Integrity (SRI)
|
|
23
|
+
- **DO**: Add `integrity` attributes to all external `<script>` and `<link>` tags loading from CDNs.
|
|
24
|
+
- **DON'T**: Load external scripts without integrity verification.
|
|
25
|
+
- **WHY**: SRI ensures the browser rejects tampered CDN assets, preventing supply chain attacks via compromised CDNs.
|
|
26
|
+
|
|
27
|
+
### 5. Minimize Dependency Surface
|
|
28
|
+
- **DO**: Evaluate each dependency for necessity, maintenance status, and security posture before adding it.
|
|
29
|
+
- **DON'T**: Add packages for trivial functionality that can be implemented in a few lines of code.
|
|
30
|
+
- **WHY**: Each dependency is a trust relationship. Fewer dependencies mean a smaller attack surface and less risk of transitive vulnerabilities.
|
|
31
|
+
|
|
32
|
+
### 6. Monitor for Typosquatting and Malicious Packages
|
|
33
|
+
- **DO**: Double-check package names before installing. Use scoped packages (`@org/package`) where possible.
|
|
34
|
+
- **DON'T**: Install packages without verifying the publisher, download count, and repository link.
|
|
35
|
+
- **WHY**: Typosquatting attacks publish packages with similar names to popular libraries, injecting malicious code.
|
|
36
|
+
|
|
37
|
+
### 7. Enforce Build Reproducibility
|
|
38
|
+
- **DO**: Use deterministic builds. Pin Node.js versions, use lockfiles, and build in controlled environments.
|
|
39
|
+
- **DON'T**: Allow builds to fetch latest versions at build time or rely on mutable tags like `latest`.
|
|
40
|
+
- **WHY**: Non-reproducible builds make it impossible to verify that deployed code matches reviewed source.
|
|
41
|
+
|
|
42
|
+
### 8. Restrict Install Scripts
|
|
43
|
+
- **DO**: Use `--ignore-scripts` flag when installing untrusted packages. Review `preinstall` and `postinstall` scripts.
|
|
44
|
+
- **DON'T**: Allow arbitrary install scripts to run without review, especially from new or unvetted dependencies.
|
|
45
|
+
- **WHY**: Malicious install scripts can execute arbitrary code during `npm install`, compromising the build environment.
|
|
46
|
+
|
|
47
|
+
## Code Examples
|
|
48
|
+
|
|
49
|
+
### Bad Practice
|
|
50
|
+
```json
|
|
51
|
+
// package.json with loose version ranges
|
|
52
|
+
{
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"lodash": "*",
|
|
55
|
+
"express": "^4",
|
|
56
|
+
"some-unknown-pkg": "latest"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<!-- Loading CDN scripts without integrity check -->
|
|
63
|
+
<script src="https://cdn.example.com/lib/v3/analytics.min.js"></script>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
# CI pipeline using npm install (modifies lockfile)
|
|
68
|
+
steps:
|
|
69
|
+
- run: npm install
|
|
70
|
+
- run: npm run build
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Good Practice
|
|
74
|
+
```json
|
|
75
|
+
// package.json with pinned versions
|
|
76
|
+
{
|
|
77
|
+
"dependencies": {
|
|
78
|
+
"lodash": "4.17.21",
|
|
79
|
+
"express": "4.21.2"
|
|
80
|
+
},
|
|
81
|
+
"overrides": {
|
|
82
|
+
"vulnerable-transitive-dep": ">=2.0.1"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```html
|
|
88
|
+
<!-- CDN scripts with SRI -->
|
|
89
|
+
<script
|
|
90
|
+
src="https://cdn.example.com/lib/v3/analytics.min.js"
|
|
91
|
+
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8w"
|
|
92
|
+
crossorigin="anonymous"
|
|
93
|
+
></script>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```yaml
|
|
97
|
+
# CI pipeline with frozen lockfile and audit
|
|
98
|
+
steps:
|
|
99
|
+
- run: npm ci --ignore-scripts # Frozen lockfile, no scripts
|
|
100
|
+
- run: npm audit --audit-level=high
|
|
101
|
+
- run: npx lockfile-lint --path package-lock.json --type npm --allowed-hosts npm
|
|
102
|
+
- run: npm run build
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
// Runtime dependency verification helper
|
|
107
|
+
import { createHash } from "node:crypto";
|
|
108
|
+
import { readFile } from "node:fs/promises";
|
|
109
|
+
|
|
110
|
+
async function verifyFileIntegrity(filePath, expectedHash) {
|
|
111
|
+
const content = await readFile(filePath);
|
|
112
|
+
const hash = createHash("sha384").update(content).digest("base64");
|
|
113
|
+
if (hash !== expectedHash) {
|
|
114
|
+
throw new Error(`Integrity check failed for ${filePath}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Quick Checklist
|
|
120
|
+
- [ ] `npm audit` (or equivalent) runs in CI on every build
|
|
121
|
+
- [ ] `package-lock.json` is committed and CI uses `npm ci`
|
|
122
|
+
- [ ] No wildcard (`*`) or `latest` version ranges in production dependencies
|
|
123
|
+
- [ ] External CDN scripts use SRI `integrity` attributes
|
|
124
|
+
- [ ] New dependencies are reviewed for security posture before adoption
|
|
125
|
+
- [ ] Install scripts are reviewed or disabled for untrusted packages
|
|
126
|
+
- [ ] Node.js version is pinned in `.nvmrc` or `engines` field
|
|
127
|
+
- [ ] Dependency update PRs are reviewed for unexpected changes
|