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
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
|