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/src/index.js ADDED
@@ -0,0 +1,177 @@
1
+ /**
2
+ * secure-rules - OWASP 2025 Security Rules Generator for AI Coding Assistants
3
+ *
4
+ * Generates security coding rules in the format of your preferred AI tool:
5
+ * - CLAUDE.md (Claude Code)
6
+ * - .cursor/rules/*.mdc (Cursor)
7
+ * - .windsurf/rules/*.md (Windsurf)
8
+ * - .github/copilot-instructions.md (GitHub Copilot)
9
+ * - AGENTS.md (vendor-neutral)
10
+ */
11
+
12
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
13
+ import { readFileSync, existsSync } from 'node:fs';
14
+ import { join, dirname } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { promptUser } from './prompts.js';
17
+ import { loadTemplates } from './loader.js';
18
+
19
+ // Adapter imports
20
+ import * as claudeAdapter from './adapters/claude.js';
21
+ import * as cursorAdapter from './adapters/cursor.js';
22
+ import * as windsurfAdapter from './adapters/windsurf.js';
23
+ import * as copilotAdapter from './adapters/copilot.js';
24
+ import * as agentsAdapter from './adapters/agents.js';
25
+
26
+ const __filename = fileURLToPath(import.meta.url);
27
+ const __dirname = dirname(__filename);
28
+
29
+ function getVersion() {
30
+ try {
31
+ const pkg = JSON.parse(
32
+ readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')
33
+ );
34
+ return pkg.version;
35
+ } catch {
36
+ return '2.0.0';
37
+ }
38
+ }
39
+
40
+ const adapters = {
41
+ claude: claudeAdapter,
42
+ cursor: cursorAdapter,
43
+ windsurf: windsurfAdapter,
44
+ copilot: copilotAdapter,
45
+ agents: agentsAdapter,
46
+ };
47
+
48
+ export async function run() {
49
+ const args = process.argv.slice(2);
50
+ const version = getVersion();
51
+
52
+ // Help flag
53
+ if (args.includes('--help') || args.includes('-h')) {
54
+ printHelp(version);
55
+ return;
56
+ }
57
+
58
+ // Version flag
59
+ if (args.includes('--version') || args.includes('-v')) {
60
+ console.log(`secure-coding-rules v${version}`);
61
+ return;
62
+ }
63
+
64
+ const config = await promptUser(args);
65
+
66
+ // --check returns null to signal early exit
67
+ if (config === null) return;
68
+
69
+ const adapter = adapters[config.tool];
70
+
71
+ console.log(`\nLoading security templates...`);
72
+ const templates = await loadTemplates(config.categories);
73
+
74
+ if (templates.size === 0) {
75
+ console.error('No templates found. Please check your installation.');
76
+ process.exit(1);
77
+ }
78
+
79
+ console.log(`Loaded ${templates.size} security rule modules.`);
80
+
81
+ const cwd = process.cwd();
82
+ const options = { framework: config.framework, version };
83
+
84
+ if (config.tool === 'cursor') {
85
+ await generateMultipleFiles(cursorAdapter, templates, options, cwd);
86
+ } else if (config.tool === 'windsurf') {
87
+ await generateMultipleFiles(windsurfAdapter, templates, options, cwd);
88
+ } else {
89
+ await generateSingleFile(adapter, templates, options, cwd);
90
+ }
91
+
92
+ console.log('\nāœ… Security rules generated successfully!');
93
+ console.log('šŸ“– Based on OWASP Top 10 2025 (https://owasp.org/Top10/2025/)');
94
+ console.log('\nRun again anytime to update: npx secure-coding-rules\n');
95
+ }
96
+
97
+ async function generateSingleFile(adapter, templates, options, cwd) {
98
+ const outputPath = join(cwd, adapter.outputPath);
99
+ const dir = dirname(outputPath);
100
+
101
+ if (!existsSync(dir)) {
102
+ await mkdir(dir, { recursive: true });
103
+ }
104
+
105
+ const newContent = adapter.format(templates, options);
106
+
107
+ if (existsSync(outputPath) && adapter.merge) {
108
+ const existing = await readFile(outputPath, 'utf-8');
109
+ const merged = adapter.merge(existing, newContent);
110
+ await writeFile(outputPath, merged, 'utf-8');
111
+ console.log(`\nšŸ“ Updated: ${adapter.outputPath} (merged with existing content)`);
112
+ } else {
113
+ await writeFile(outputPath, newContent, 'utf-8');
114
+ console.log(`\nšŸ“ Created: ${adapter.outputPath}`);
115
+ }
116
+ }
117
+
118
+ async function generateMultipleFiles(adapter, templates, options, cwd) {
119
+ const outputDir = join(cwd, adapter.outputDir);
120
+
121
+ if (!existsSync(outputDir)) {
122
+ await mkdir(outputDir, { recursive: true });
123
+ }
124
+
125
+ const files = adapter.formatMultiple(templates, options);
126
+ let count = 0;
127
+
128
+ for (const [filename, content] of files) {
129
+ const filePath = join(outputDir, filename);
130
+ await writeFile(filePath, content, 'utf-8');
131
+ count++;
132
+ }
133
+
134
+ console.log(`\nšŸ“ Generated ${count} files in ${adapter.outputDir}/`);
135
+ }
136
+
137
+ function printHelp(version) {
138
+ console.log(`
139
+ secure-coding-rules v${version}
140
+
141
+ OWASP 2025 기반 JavaScript ė³“ģ•ˆ 코딩 ė£°ģ„ AI 코딩 ģ–“ģ‹œģŠ¤ķ„“ķŠøģ— ģžė™ 적용
142
+
143
+ Usage:
144
+ npx secure-coding-rules Interactive mode (auto-detects project)
145
+ npx secure-coding-rules --yes Apply all rules with smart defaults
146
+ npx secure-coding-rules --check Show current project security status
147
+
148
+ Options:
149
+ -y, --yes Non-interactive mode (auto-detects tool & framework)
150
+ --check Show which AI tools and frameworks are detected
151
+ -h, --help Show this help
152
+ -v, --version Show version
153
+
154
+ Supported AI Tools:
155
+ - Claude Code → CLAUDE.md
156
+ - Cursor → .cursor/rules/*.mdc
157
+ - Windsurf → .windsurf/rules/*.md
158
+ - GitHub Copilot → .github/copilot-instructions.md
159
+ - AGENTS.md → AGENTS.md (vendor-neutral)
160
+
161
+ Security Categories (OWASP Top 10 2025):
162
+ A01: Broken Access Control
163
+ A02: Security Misconfiguration
164
+ A03: Supply Chain Failures (NEW in 2025)
165
+ A04: Cryptographic Failures
166
+ A05: Injection
167
+ A06: Insecure Design
168
+ A07: Authentication Failures
169
+ A08: Data Integrity Failures
170
+ A09: Logging & Alerting Failures
171
+ A10: Error Handling (NEW in 2025)
172
+
173
+ + Frontend: XSS, CSRF, CSP, Secure State Management
174
+
175
+ Homepage: https://github.com/user/secure-coding-rules
176
+ `);
177
+ }
package/src/loader.js ADDED
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Template loader - reads modular security rule files
3
+ */
4
+
5
+ import { readFile } from 'node:fs/promises';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { dirname, join } from 'node:path';
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = dirname(__filename);
11
+ const TEMPLATES_DIR = join(__dirname, 'templates');
12
+
13
+ const CORE_CATEGORIES = [
14
+ 'access-control',
15
+ 'security-config',
16
+ 'supply-chain',
17
+ 'cryptographic',
18
+ 'injection',
19
+ 'secure-design',
20
+ 'authentication',
21
+ 'data-integrity',
22
+ 'logging-alerting',
23
+ 'error-handling',
24
+ ];
25
+
26
+ const FRONTEND_CATEGORIES = [
27
+ 'xss-prevention',
28
+ 'csrf-protection',
29
+ 'csp',
30
+ 'secure-state',
31
+ ];
32
+
33
+ /**
34
+ * Load a single template file by category name
35
+ */
36
+ export async function loadTemplate(category) {
37
+ const subdir = CORE_CATEGORIES.includes(category) ? 'core' : 'frontend';
38
+ const filePath = join(TEMPLATES_DIR, subdir, `${category}.md`);
39
+ try {
40
+ return await readFile(filePath, 'utf-8');
41
+ } catch {
42
+ console.warn(`Warning: Template not found: ${category}`);
43
+ return null;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Load multiple templates and return as Map<category, content>
49
+ */
50
+ export async function loadTemplates(categories) {
51
+ const templates = new Map();
52
+ const results = await Promise.all(
53
+ categories.map(async (cat) => {
54
+ const content = await loadTemplate(cat);
55
+ return [cat, content];
56
+ })
57
+ );
58
+ for (const [cat, content] of results) {
59
+ if (content) templates.set(cat, content);
60
+ }
61
+ return templates;
62
+ }
63
+
64
+ /**
65
+ * Get category metadata
66
+ */
67
+ export function getCategoryInfo(category) {
68
+ const info = {
69
+ 'access-control': { owasp: 'A01', title: 'Broken Access Control', group: 'core' },
70
+ 'security-config': { owasp: 'A02', title: 'Security Misconfiguration', group: 'core' },
71
+ 'supply-chain': { owasp: 'A03', title: 'Supply Chain Failures', group: 'core' },
72
+ 'cryptographic': { owasp: 'A04', title: 'Cryptographic Failures', group: 'core' },
73
+ 'injection': { owasp: 'A05', title: 'Injection', group: 'core' },
74
+ 'secure-design': { owasp: 'A06', title: 'Insecure Design', group: 'core' },
75
+ 'authentication': { owasp: 'A07', title: 'Authentication Failures', group: 'core' },
76
+ 'data-integrity': { owasp: 'A08', title: 'Data Integrity Failures', group: 'core' },
77
+ 'logging-alerting': { owasp: 'A09', title: 'Logging & Alerting Failures', group: 'core' },
78
+ 'error-handling': { owasp: 'A10', title: 'Error Handling', group: 'core' },
79
+ 'xss-prevention': { owasp: 'FE-01', title: 'XSS Prevention', group: 'frontend' },
80
+ 'csrf-protection': { owasp: 'FE-02', title: 'CSRF Protection', group: 'frontend' },
81
+ 'csp': { owasp: 'FE-03', title: 'Content Security Policy', group: 'frontend' },
82
+ 'secure-state': { owasp: 'FE-04', title: 'Secure State Management', group: 'frontend' },
83
+ };
84
+ return info[category] || { owasp: '??', title: category, group: 'unknown' };
85
+ }
package/src/prompts.js ADDED
@@ -0,0 +1,307 @@
1
+ /**
2
+ * Interactive CLI prompts using Node.js built-in readline
3
+ * Zero dependencies - works with Node.js 18+
4
+ */
5
+
6
+ import { createInterface } from 'node:readline';
7
+ import { existsSync, readFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+
10
+ // ─── Readline helpers ────────────────────────────────────────────
11
+
12
+ const rl = () =>
13
+ createInterface({ input: process.stdin, output: process.stdout });
14
+
15
+ function ask(question) {
16
+ return new Promise((resolve) => {
17
+ const r = rl();
18
+ r.question(question, (answer) => {
19
+ r.close();
20
+ resolve(answer.trim());
21
+ });
22
+ });
23
+ }
24
+
25
+ function printOptions(options) {
26
+ options.forEach((opt, i) => {
27
+ const marker = opt.detected ? ' (detected)' : '';
28
+ console.log(` ${i + 1}) ${opt.label}${marker}`);
29
+ });
30
+ }
31
+
32
+ async function selectOne(question, options) {
33
+ console.log(`\n${question}`);
34
+ printOptions(options);
35
+ const answer = await ask(`\nSelect (1-${options.length}): `);
36
+ const idx = parseInt(answer, 10) - 1;
37
+ if (idx >= 0 && idx < options.length) return options[idx].value;
38
+ console.log('Invalid selection, defaulting to first option.');
39
+ return options[0].value;
40
+ }
41
+
42
+ async function selectMultiple(question, options) {
43
+ console.log(`\n${question}`);
44
+ printOptions(options);
45
+ console.log(` 0) All`);
46
+ const answer = await ask(
47
+ `\nSelect (comma-separated, e.g. 1,3,5 or 0 for all): `
48
+ );
49
+
50
+ if (answer === '0' || answer.toLowerCase() === 'all') {
51
+ return options.map((o) => o.value);
52
+ }
53
+
54
+ const indices = answer
55
+ .split(',')
56
+ .map((s) => parseInt(s.trim(), 10) - 1)
57
+ .filter((i) => i >= 0 && i < options.length);
58
+
59
+ if (indices.length === 0) {
60
+ console.log('No valid selection, selecting all.');
61
+ return options.map((o) => o.value);
62
+ }
63
+
64
+ return indices.map((i) => options[i].value);
65
+ }
66
+
67
+ async function confirm(question) {
68
+ const answer = await ask(`\n${question} (Y/n): `);
69
+ return answer.toLowerCase() !== 'n';
70
+ }
71
+
72
+ // ─── Constants ───────────────────────────────────────────────────
73
+
74
+ export const AI_TOOLS = [
75
+ { label: 'Claude Code (CLAUDE.md)', value: 'claude' },
76
+ { label: 'Cursor (.cursor/rules/)', value: 'cursor' },
77
+ { label: 'Windsurf (.windsurf/rules/)', value: 'windsurf' },
78
+ { label: 'GitHub Copilot (.github/copilot-instructions.md)', value: 'copilot' },
79
+ { label: 'AGENTS.md (vendor-neutral)', value: 'agents' },
80
+ ];
81
+
82
+ export const FRAMEWORKS = [
83
+ { label: 'React / Next.js', value: 'react' },
84
+ { label: 'Vue / Nuxt', value: 'vue' },
85
+ { label: 'Node.js / Express', value: 'node' },
86
+ { label: 'Vanilla JavaScript / TypeScript', value: 'vanilla' },
87
+ ];
88
+
89
+ export const SECURITY_CATEGORIES = [
90
+ { label: 'A01: Broken Access Control', value: 'access-control' },
91
+ { label: 'A02: Security Misconfiguration', value: 'security-config' },
92
+ { label: 'A03: Supply Chain Failures', value: 'supply-chain' },
93
+ { label: 'A04: Cryptographic Failures', value: 'cryptographic' },
94
+ { label: 'A05: Injection', value: 'injection' },
95
+ { label: 'A06: Insecure Design', value: 'secure-design' },
96
+ { label: 'A07: Authentication Failures', value: 'authentication' },
97
+ { label: 'A08: Data Integrity Failures', value: 'data-integrity' },
98
+ { label: 'A09: Logging & Alerting Failures', value: 'logging-alerting' },
99
+ { label: 'A10: Error Handling', value: 'error-handling' },
100
+ { label: 'Frontend: XSS Prevention', value: 'xss-prevention' },
101
+ { label: 'Frontend: CSRF Protection', value: 'csrf-protection' },
102
+ { label: 'Frontend: CSP', value: 'csp' },
103
+ { label: 'Frontend: Secure State Management', value: 'secure-state' },
104
+ ];
105
+
106
+ // ─── Project state detection ─────────────────────────────────────
107
+
108
+ /**
109
+ * Detect the current project environment:
110
+ * - Which AI tool configs already exist
111
+ * - What framework is being used (via package.json)
112
+ * - Whether this is a new or existing project
113
+ */
114
+ export function detectProjectState(cwd) {
115
+ const state = {
116
+ hasPackageJson: existsSync(join(cwd, 'package.json')),
117
+ detectedTools: [],
118
+ detectedFramework: null,
119
+ existingRules: {},
120
+ };
121
+
122
+ // Detect existing AI tool configs
123
+ const toolPaths = {
124
+ claude: 'CLAUDE.md',
125
+ cursor: '.cursor/rules',
126
+ windsurf: '.windsurf/rules',
127
+ copilot: '.github/copilot-instructions.md',
128
+ agents: 'AGENTS.md',
129
+ };
130
+
131
+ for (const [tool, path] of Object.entries(toolPaths)) {
132
+ const fullPath = join(cwd, path);
133
+ if (existsSync(fullPath)) {
134
+ state.detectedTools.push(tool);
135
+ state.existingRules[tool] = fullPath;
136
+ }
137
+ }
138
+
139
+ // Detect framework from package.json dependencies
140
+ if (state.hasPackageJson) {
141
+ try {
142
+ const pkg = JSON.parse(
143
+ readFileSync(join(cwd, 'package.json'), 'utf-8')
144
+ );
145
+ const allDeps = {
146
+ ...pkg.dependencies,
147
+ ...pkg.devDependencies,
148
+ };
149
+ if (allDeps.next || allDeps.react) state.detectedFramework = 'react';
150
+ else if (allDeps.nuxt || allDeps.vue) state.detectedFramework = 'vue';
151
+ else if (allDeps.express || allDeps.fastify || allDeps.koa)
152
+ state.detectedFramework = 'node';
153
+ else state.detectedFramework = 'vanilla';
154
+ } catch {
155
+ // Ignore parse errors
156
+ }
157
+ }
158
+
159
+ return state;
160
+ }
161
+
162
+ /**
163
+ * Print a summary of the detected project state
164
+ */
165
+ export function printProjectStatus(state) {
166
+ console.log('\nšŸ“‹ Project Status:');
167
+
168
+ if (state.detectedTools.length > 0) {
169
+ const toolNames = state.detectedTools
170
+ .map((t) => AI_TOOLS.find((a) => a.value === t)?.label || t)
171
+ .join(', ');
172
+ console.log(` AI tools detected: ${toolNames}`);
173
+ } else {
174
+ console.log(' No AI tool configs found (new setup)');
175
+ }
176
+
177
+ if (state.detectedFramework) {
178
+ const fwName =
179
+ FRAMEWORKS.find((f) => f.value === state.detectedFramework)?.label ||
180
+ state.detectedFramework;
181
+ console.log(` Framework detected: ${fwName}`);
182
+ }
183
+
184
+ if (!state.hasPackageJson) {
185
+ console.log(' No package.json found (works fine - rules will be created in current directory)');
186
+ }
187
+ }
188
+
189
+ // ─── Main prompt flow ────────────────────────────────────────────
190
+
191
+ function isInteractive() {
192
+ return process.stdin.isTTY === true;
193
+ }
194
+
195
+ export async function promptUser(args) {
196
+ const cwd = process.cwd();
197
+ const state = detectProjectState(cwd);
198
+
199
+ // --check flag: just show status and exit (must be before auto-mode check)
200
+ if (args.includes('--check')) {
201
+ printProjectStatus(state);
202
+ return null; // Signal to index.js to exit early
203
+ }
204
+
205
+ // Non-interactive mode: --yes flag or non-TTY environment
206
+ if (args.includes('--yes') || args.includes('-y') || !isInteractive()) {
207
+ if (!isInteractive() && !args.includes('--yes') && !args.includes('-y')) {
208
+ console.log('Non-interactive environment detected, using defaults.');
209
+ }
210
+
211
+ // Smart defaults based on detection
212
+ const tool =
213
+ state.detectedTools.length === 1
214
+ ? state.detectedTools[0]
215
+ : state.detectedTools.includes('claude')
216
+ ? 'claude'
217
+ : 'claude';
218
+
219
+ return {
220
+ tool,
221
+ framework: state.detectedFramework || 'vanilla',
222
+ categories: SECURITY_CATEGORIES.map((c) => c.value),
223
+ includeFrontend: (state.detectedFramework || 'vanilla') !== 'node',
224
+ };
225
+ }
226
+
227
+ // Interactive mode
228
+ console.log('\nšŸ”’ Secure Coding Rules - OWASP 2025 Security Rules Generator\n');
229
+ console.log('─'.repeat(55));
230
+ printProjectStatus(state);
231
+
232
+ // AI tool selection - highlight detected ones
233
+ const toolOptions = AI_TOOLS.map((t) => ({
234
+ ...t,
235
+ detected: state.detectedTools.includes(t.value),
236
+ }));
237
+
238
+ // If only one tool detected, suggest it first
239
+ if (state.detectedTools.length === 1) {
240
+ const detected = state.detectedTools[0];
241
+ const idx = toolOptions.findIndex((t) => t.value === detected);
242
+ if (idx > 0) {
243
+ const [item] = toolOptions.splice(idx, 1);
244
+ toolOptions.unshift(item);
245
+ }
246
+ }
247
+
248
+ const tool = await selectOne('Which AI coding tool do you use?', toolOptions);
249
+
250
+ // Framework selection - auto-suggest detected
251
+ const frameworkOptions = FRAMEWORKS.map((f) => ({
252
+ ...f,
253
+ detected: f.value === state.detectedFramework,
254
+ }));
255
+ if (state.detectedFramework) {
256
+ const idx = frameworkOptions.findIndex(
257
+ (f) => f.value === state.detectedFramework
258
+ );
259
+ if (idx > 0) {
260
+ const [item] = frameworkOptions.splice(idx, 1);
261
+ frameworkOptions.unshift(item);
262
+ }
263
+ }
264
+
265
+ const framework = await selectOne(
266
+ 'What is your primary framework?',
267
+ frameworkOptions
268
+ );
269
+
270
+ // Update or fresh install message
271
+ if (state.existingRules[tool]) {
272
+ console.log(`\n ℹ Existing rules found - will update security section only.`);
273
+ }
274
+
275
+ const allCategories = await confirm(
276
+ 'Include all OWASP 2025 security categories?'
277
+ );
278
+
279
+ let categories;
280
+ if (allCategories) {
281
+ categories = SECURITY_CATEGORIES.map((c) => c.value);
282
+ } else {
283
+ categories = await selectMultiple(
284
+ 'Select security categories:',
285
+ SECURITY_CATEGORIES
286
+ );
287
+ }
288
+
289
+ const includeFrontend =
290
+ framework !== 'node'
291
+ ? await confirm('Include frontend-specific security rules?')
292
+ : false;
293
+
294
+ if (includeFrontend) {
295
+ const frontendCats = [
296
+ 'xss-prevention',
297
+ 'csrf-protection',
298
+ 'csp',
299
+ 'secure-state',
300
+ ];
301
+ frontendCats.forEach((c) => {
302
+ if (!categories.includes(c)) categories.push(c);
303
+ });
304
+ }
305
+
306
+ return { tool, framework, categories, includeFrontend };
307
+ }
@@ -0,0 +1,112 @@
1
+ # Access Control Security Rules
2
+
3
+ > OWASP Top 10 2025 - A01: Broken Access Control
4
+
5
+ ## Rules
6
+
7
+ ### 1. Enforce Server-Side Access Control
8
+ - **DO**: Validate all access control checks on the server side. Never rely solely on client-side checks.
9
+ - **DON'T**: Hide UI elements as the only means of restricting access. Attackers bypass client-side controls trivially.
10
+ - **WHY**: Client-side access control is purely cosmetic and can be bypassed by modifying requests directly.
11
+
12
+ ### 2. Deny by Default
13
+ - **DO**: Implement a default-deny policy. Explicitly grant access only to resources the user is authorized for.
14
+ - **DON'T**: Use a default-allow model where you try to block specific unauthorized access patterns.
15
+ - **WHY**: Default-deny ensures new endpoints and resources are secure by default, reducing the risk of accidental exposure.
16
+
17
+ ### 3. Use Role-Based or Attribute-Based Access Control
18
+ - **DO**: Implement RBAC or ABAC with clearly defined roles and permissions. Check permissions at every access point.
19
+ - **DON'T**: Hardcode user IDs or permission checks scattered throughout business logic.
20
+ - **WHY**: Centralized access control is easier to audit, maintain, and less prone to bypass.
21
+
22
+ ### 4. Validate Object-Level Authorization (IDOR Prevention)
23
+ - **DO**: Verify the authenticated user has permission to access the specific resource identified by the request parameter.
24
+ - **DON'T**: Trust client-supplied IDs (e.g., `/api/users/123/orders`) without verifying the requester owns that resource.
25
+ - **WHY**: Insecure Direct Object Reference (IDOR) is one of the most common access control flaws, allowing users to access others' data.
26
+
27
+ ### 5. Enforce Function-Level Access Control
28
+ - **DO**: Check authorization for every API endpoint and controller action, including admin functions.
29
+ - **DON'T**: Assume that obscure or undocumented endpoints are safe from unauthorized access.
30
+ - **WHY**: Attackers discover hidden endpoints through reconnaissance, API documentation leaks, or brute-force.
31
+
32
+ ### 6. Implement Rate Limiting on Sensitive Operations
33
+ - **DO**: Apply rate limiting to authentication, password reset, and other sensitive endpoints.
34
+ - **DON'T**: Allow unlimited requests to access-controlled endpoints without throttling.
35
+ - **WHY**: Rate limiting mitigates brute-force attacks and automated enumeration of resources.
36
+
37
+ ### 7. Use Secure Session and Token Management
38
+ - **DO**: Invalidate sessions and tokens on logout, password change, and after a configurable idle timeout.
39
+ - **DON'T**: Issue long-lived tokens without refresh mechanisms or allow sessions to persist indefinitely.
40
+ - **WHY**: Stale sessions and tokens expand the attack window for session hijacking.
41
+
42
+ ## Code Examples
43
+
44
+ ### Bad Practice
45
+ ```javascript
46
+ // Trusting client-supplied user ID without authorization check
47
+ app.get("/api/users/:userId/profile", async (req, res) => {
48
+ const profile = await db.getUserProfile(req.params.userId);
49
+ res.json(profile); // No check if requester owns this profile
50
+ });
51
+
52
+ // Client-side only access control
53
+ function AdminPanel() {
54
+ const { user } = useAuth();
55
+ if (user.role !== "admin") return null; // Easily bypassed
56
+ return <SensitiveAdminUI />;
57
+ }
58
+ ```
59
+
60
+ ### Good Practice
61
+ ```javascript
62
+ // Server-side authorization check with IDOR prevention
63
+ app.get("/api/users/:userId/profile", authenticate, async (req, res) => {
64
+ if (req.user.id !== req.params.userId && req.user.role !== "admin") {
65
+ return res.status(403).json({ error: "Forbidden" });
66
+ }
67
+ const profile = await db.getUserProfile(req.params.userId);
68
+ res.json(profile);
69
+ });
70
+
71
+ // Centralized RBAC middleware
72
+ function authorize(...allowedRoles) {
73
+ return (req, res, next) => {
74
+ if (!req.user || !allowedRoles.includes(req.user.role)) {
75
+ return res.status(403).json({ error: "Forbidden" });
76
+ }
77
+ next();
78
+ };
79
+ }
80
+
81
+ app.delete("/api/users/:id", authenticate, authorize("admin"), deleteUser);
82
+
83
+ // Policy-based access control
84
+ class AccessPolicy {
85
+ static canAccess(user, resource) {
86
+ const policies = {
87
+ "order:read": (u, r) => u.id === r.ownerId || u.role === "admin",
88
+ "order:delete": (u, r) => u.role === "admin",
89
+ };
90
+ const check = policies[`${resource.type}:${resource.action}`];
91
+ return check ? check(user, resource) : false; // Default deny
92
+ }
93
+ }
94
+
95
+ app.get("/api/orders/:id", authenticate, async (req, res) => {
96
+ const order = await db.getOrder(req.params.id);
97
+ if (!AccessPolicy.canAccess(req.user, { ...order, type: "order", action: "read" })) {
98
+ return res.status(403).json({ error: "Forbidden" });
99
+ }
100
+ res.json(order);
101
+ });
102
+ ```
103
+
104
+ ## Quick Checklist
105
+ - [ ] All access control is enforced server-side
106
+ - [ ] Default-deny policy is in place for all routes
107
+ - [ ] Every API endpoint checks authorization (not just authentication)
108
+ - [ ] Object-level authorization prevents IDOR attacks
109
+ - [ ] Admin functions require explicit role verification
110
+ - [ ] Sessions and tokens are invalidated on logout/password change
111
+ - [ ] Rate limiting is applied to sensitive endpoints
112
+ - [ ] Access control logic is centralized and reusable