secure-coding-rules 2.0.0 β†’ 2.0.1

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 CHANGED
@@ -1,22 +1,15 @@
1
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)
2
+ * secure-coding-rules - OWASP 2025 Security Rules Generator for AI Coding Assistants
10
3
  */
11
4
 
12
5
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
13
6
  import { readFileSync, existsSync } from 'node:fs';
14
7
  import { join, dirname } from 'node:path';
15
8
  import { fileURLToPath } from 'node:url';
9
+ import { initLang, t } from './i18n.js';
16
10
  import { promptUser } from './prompts.js';
17
11
  import { loadTemplates } from './loader.js';
18
12
 
19
- // Adapter imports
20
13
  import * as claudeAdapter from './adapters/claude.js';
21
14
  import * as cursorAdapter from './adapters/cursor.js';
22
15
  import * as windsurfAdapter from './adapters/windsurf.js';
@@ -33,7 +26,7 @@ function getVersion() {
33
26
  );
34
27
  return pkg.version;
35
28
  } catch {
36
- return '2.0.0';
29
+ return '2.1.0';
37
30
  }
38
31
  }
39
32
 
@@ -45,53 +38,88 @@ const adapters = {
45
38
  agents: agentsAdapter,
46
39
  };
47
40
 
41
+ // Tools that natively use directory-based output
42
+ const DIRECTORY_NATIVE_TOOLS = ['cursor', 'windsurf'];
43
+
44
+ // Tools that support optional directory mode
45
+ const DIRECTORY_OPTIONAL_TOOLS = ['claude', 'copilot'];
46
+
48
47
  export async function run() {
49
48
  const args = process.argv.slice(2);
50
49
  const version = getVersion();
51
50
 
52
- // Help flag
51
+ initLang(args);
52
+
53
53
  if (args.includes('--help') || args.includes('-h')) {
54
54
  printHelp(version);
55
55
  return;
56
56
  }
57
57
 
58
- // Version flag
59
58
  if (args.includes('--version') || args.includes('-v')) {
60
59
  console.log(`secure-coding-rules v${version}`);
61
60
  return;
62
61
  }
63
62
 
63
+ // --remove flag
64
+ if (args.includes('--remove')) {
65
+ await removeRules();
66
+ return;
67
+ }
68
+
69
+ const dryRun = args.includes('--dry-run');
64
70
  const config = await promptUser(args);
65
71
 
66
- // --check returns null to signal early exit
67
72
  if (config === null) return;
68
73
 
69
- const adapter = adapters[config.tool];
70
-
71
- console.log(`\nLoading security templates...`);
74
+ console.log(`\n${t('loading')}`);
72
75
  const templates = await loadTemplates(config.categories);
73
76
 
74
77
  if (templates.size === 0) {
75
- console.error('No templates found. Please check your installation.');
78
+ console.error(t('noTemplates'));
76
79
  process.exit(1);
77
80
  }
78
81
 
79
- console.log(`Loaded ${templates.size} security rule modules.`);
82
+ console.log(t('loaded', templates.size));
80
83
 
81
84
  const cwd = process.cwd();
82
85
  const options = { framework: config.framework, version };
83
86
 
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);
87
+ // Process each selected tool
88
+ for (const toolName of config.tools) {
89
+ const adapter = adapters[toolName];
90
+ if (!adapter) continue;
91
+
92
+ if (config.tools.length > 1) {
93
+ console.log(`\n${t('generatingFor', adapter.name)}`);
94
+ }
95
+
96
+ if (dryRun) {
97
+ dryRunPreview(adapter, toolName, config, templates, options, cwd);
98
+ continue;
99
+ }
100
+
101
+ const useDirectory = shouldUseDirectory(toolName, config.outputMode);
102
+
103
+ if (useDirectory) {
104
+ await generateDirectoryMode(adapter, toolName, templates, options, config, cwd);
105
+ } else if (DIRECTORY_NATIVE_TOOLS.includes(toolName)) {
106
+ await generateMultipleFiles(adapter, templates, options, cwd);
107
+ } else {
108
+ await generateSingleFile(adapter, templates, options, cwd);
109
+ }
110
+ }
111
+
112
+ if (!dryRun) {
113
+ console.log(`\nβœ… ${t('success')}`);
114
+ console.log(`πŸ“– ${t('reference')}`);
115
+ console.log(`\n${t('runAgain')}\n`);
90
116
  }
117
+ }
91
118
 
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');
119
+ function shouldUseDirectory(toolName, outputMode) {
120
+ if (DIRECTORY_NATIVE_TOOLS.includes(toolName)) return false; // cursor/windsurf already directory-based
121
+ if (DIRECTORY_OPTIONAL_TOOLS.includes(toolName) && outputMode === 'directory') return true;
122
+ return false;
95
123
  }
96
124
 
97
125
  async function generateSingleFile(adapter, templates, options, cwd) {
@@ -108,10 +136,10 @@ async function generateSingleFile(adapter, templates, options, cwd) {
108
136
  const existing = await readFile(outputPath, 'utf-8');
109
137
  const merged = adapter.merge(existing, newContent);
110
138
  await writeFile(outputPath, merged, 'utf-8');
111
- console.log(`\nπŸ“ Updated: ${adapter.outputPath} (merged with existing content)`);
139
+ console.log(`πŸ“ ${t('updated', adapter.outputPath)}`);
112
140
  } else {
113
141
  await writeFile(outputPath, newContent, 'utf-8');
114
- console.log(`\nπŸ“ Created: ${adapter.outputPath}`);
142
+ console.log(`πŸ“ ${t('created', adapter.outputPath)}`);
115
143
  }
116
144
  }
117
145
 
@@ -131,47 +159,185 @@ async function generateMultipleFiles(adapter, templates, options, cwd) {
131
159
  count++;
132
160
  }
133
161
 
134
- console.log(`\nπŸ“ Generated ${count} files in ${adapter.outputDir}/`);
162
+ console.log(`πŸ“ ${t('generated', count, adapter.outputDir)}`);
163
+ }
164
+
165
+ async function generateDirectoryMode(adapter, toolName, templates, options, config, cwd) {
166
+ // 1. Generate individual rule files in the rules directory
167
+ const rulesDir = join(cwd, adapter.rulesDir);
168
+
169
+ if (!existsSync(rulesDir)) {
170
+ await mkdir(rulesDir, { recursive: true });
171
+ }
172
+
173
+ const files = adapter.formatMultiple(templates, options);
174
+ let count = 0;
175
+
176
+ for (const [filename, content] of files) {
177
+ const filePath = join(rulesDir, filename);
178
+ await writeFile(filePath, content, 'utf-8');
179
+ count++;
180
+ }
181
+
182
+ console.log(`πŸ“ ${t('generated', count, adapter.rulesDir)}`);
183
+
184
+ // 2. Add/update reference in main file
185
+ const mainPath = join(cwd, adapter.outputPath);
186
+ const mainDir = dirname(mainPath);
187
+
188
+ if (!existsSync(mainDir)) {
189
+ await mkdir(mainDir, { recursive: true });
190
+ }
191
+
192
+ const refContent = adapter.formatReference(config.categories, options);
193
+
194
+ if (existsSync(mainPath) && adapter.merge) {
195
+ const existing = await readFile(mainPath, 'utf-8');
196
+ const merged = adapter.merge(existing, refContent);
197
+ await writeFile(mainPath, merged, 'utf-8');
198
+ console.log(`πŸ“ ${t('refUpdated', adapter.outputPath)}`);
199
+ } else {
200
+ await writeFile(mainPath, refContent, 'utf-8');
201
+ console.log(`πŸ“ ${t('refCreated', adapter.outputPath)}`);
202
+ }
203
+ }
204
+
205
+ function dryRunPreview(adapter, toolName, config, templates, options, cwd) {
206
+ console.log(`\n── ${t('dryRunTitle')} (${adapter.name}) ──────────────────`);
207
+ console.log(`${t('dryRunFramework')} ${config.framework}`);
208
+ console.log(`${t('dryRunCategories')} ${config.categories.length}`);
209
+
210
+ const useDirectory = shouldUseDirectory(toolName, config.outputMode);
211
+
212
+ if (useDirectory) {
213
+ const files = adapter.formatMultiple(templates, options);
214
+ console.log(`\n${t('dryRunWouldGenerate', files.size, adapter.rulesDir)}`);
215
+ for (const [filename] of files) {
216
+ console.log(` - ${filename}`);
217
+ }
218
+ console.log(`\n${t('dryRunWouldUpdate', adapter.outputPath)} (reference only)`);
219
+ } else if (DIRECTORY_NATIVE_TOOLS.includes(toolName)) {
220
+ const files = adapter.formatMultiple(templates, options);
221
+ console.log(`\n${t('dryRunWouldGenerate', files.size, adapter.outputDir)}`);
222
+ for (const [filename] of files) {
223
+ console.log(` - ${filename}`);
224
+ }
225
+ } else {
226
+ const outputPath = join(cwd, adapter.outputPath);
227
+ const content = adapter.format(templates, options);
228
+ const exists = existsSync(outputPath);
229
+ console.log(
230
+ `\n${exists ? t('dryRunWouldUpdate', adapter.outputPath) : t('dryRunWouldCreate', adapter.outputPath)}`
231
+ );
232
+ console.log(t('dryRunSize', (content.length / 1024).toFixed(1)));
233
+ }
234
+
235
+ console.log(`── ${t('dryRunApply')} ───────────────────\n`);
236
+ }
237
+
238
+ async function removeRules() {
239
+ const cwd = process.cwd();
240
+ const SECTION_START = '<!-- js-secure-coding:start -->';
241
+ const SECTION_END = '<!-- js-secure-coding:end -->';
242
+ let removed = 0;
243
+
244
+ // 1. Clean markers from single-file tools
245
+ const singleFiles = ['CLAUDE.md', '.github/copilot-instructions.md', 'AGENTS.md'];
246
+ for (const file of singleFiles) {
247
+ const filePath = join(cwd, file);
248
+ if (!existsSync(filePath)) continue;
249
+ const content = await readFile(filePath, 'utf-8');
250
+ if (!content.includes(SECTION_START)) continue;
251
+
252
+ const before = content.substring(0, content.indexOf(SECTION_START));
253
+ const after = content.substring(
254
+ content.indexOf(SECTION_END) + SECTION_END.length
255
+ );
256
+ const cleaned = (before.trimEnd() + '\n' + after.trimStart()).trim();
257
+
258
+ if (cleaned) {
259
+ await writeFile(filePath, cleaned + '\n', 'utf-8');
260
+ console.log(`πŸ—‘οΈ ${t('removedMarkers', file)}`);
261
+ } else {
262
+ const { unlink } = await import('node:fs/promises');
263
+ await unlink(filePath);
264
+ console.log(`πŸ—‘οΈ ${t('removedFile', file)}`);
265
+ }
266
+ removed++;
267
+ }
268
+
269
+ // 2. Remove security-* files from rule directories
270
+ const ruleDirs = [
271
+ { dir: '.cursor/rules', ext: '.mdc' },
272
+ { dir: '.windsurf/rules', ext: '.md' },
273
+ { dir: '.claude/rules', ext: '.md' },
274
+ { dir: '.github/instructions', ext: '.md' },
275
+ ];
276
+
277
+ for (const { dir, ext } of ruleDirs) {
278
+ const dirPath = join(cwd, dir);
279
+ if (!existsSync(dirPath)) continue;
280
+
281
+ const { readdir, unlink } = await import('node:fs/promises');
282
+ const files = await readdir(dirPath);
283
+ const securityFiles = files.filter(
284
+ (f) => f.startsWith('security-') && f.endsWith(ext)
285
+ );
286
+
287
+ for (const file of securityFiles) {
288
+ await unlink(join(dirPath, file));
289
+ removed++;
290
+ }
291
+
292
+ if (securityFiles.length > 0) {
293
+ console.log(`πŸ—‘οΈ ${t('removedDir', securityFiles.length, dir)}`);
294
+ }
295
+ }
296
+
297
+ if (removed === 0) {
298
+ console.log(`\n${t('noRulesFound')}`);
299
+ } else {
300
+ console.log(`\nβœ… ${t('removeSuccess')}`);
301
+ }
135
302
  }
136
303
 
137
304
  function printHelp(version) {
138
305
  console.log(`
139
306
  secure-coding-rules v${version}
140
307
 
141
- OWASP 2025 기반 JavaScript λ³΄μ•ˆ μ½”λ”© 룰을 AI μ½”λ”© μ–΄μ‹œμŠ€ν„΄νŠΈμ— μžλ™ 적용
308
+ ${t('helpDesc')}
142
309
 
143
310
  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
311
+ npx secure-coding-rules Interactive mode (multi-tool select)
312
+ npx secure-coding-rules --yes Smart defaults
313
+ npx secure-coding-rules --check Project status
314
+ npx secure-coding-rules --dry-run Preview
315
+ npx secure-coding-rules --remove Remove all generated rules
316
+ npx secure-coding-rules --lang ko ν•œκ΅­μ–΄λ‘œ μ‹€ν–‰
147
317
 
148
318
  Options:
149
- -y, --yes Non-interactive mode (auto-detects tool & framework)
150
- --check Show which AI tools and frameworks are detected
319
+ -y, --yes Non-interactive mode
320
+ --check Show detected AI tools and frameworks
321
+ --dry-run Preview without writing files
322
+ --remove Remove all security rules (clean uninstall)
323
+ --lang <code> Language: en (default), ko
151
324
  -h, --help Show this help
152
325
  -v, --version Show version
153
326
 
154
- Supported AI Tools:
155
- - Claude Code β†’ CLAUDE.md
327
+ Output Modes:
328
+ inline Embed all rules in main file (e.g. CLAUDE.md)
329
+ directory Separate rule files + reference in main file
330
+ (e.g. .claude/rules/security-*.md + CLAUDE.md reference)
331
+
332
+ Supported AI Tools (select multiple):
333
+ - Claude Code β†’ CLAUDE.md or .claude/rules/
156
334
  - Cursor β†’ .cursor/rules/*.mdc
157
335
  - 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
336
+ - GitHub Copilot β†’ .github/copilot-instructions.md or .github/instructions/
337
+ - AGENTS.md β†’ AGENTS.md
338
+
339
+ OWASP Top 10 2025: A01-A10 + Frontend (XSS, CSRF, CSP, State)
340
+
341
+ Homepage: https://github.com/kwakseongjae/js-secure-coding-prompt-templates
176
342
  `);
177
343
  }
package/src/loader.js CHANGED
@@ -10,31 +10,40 @@ const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = dirname(__filename);
11
11
  const TEMPLATES_DIR = join(__dirname, 'templates');
12
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
- ];
13
+ const CATEGORY_DIRS = {
14
+ // OWASP Top 10 2025
15
+ 'access-control': 'core',
16
+ 'security-config': 'core',
17
+ 'supply-chain': 'core',
18
+ 'cryptographic': 'core',
19
+ 'injection': 'core',
20
+ 'secure-design': 'core',
21
+ 'authentication': 'core',
22
+ 'data-integrity': 'core',
23
+ 'logging-alerting': 'core',
24
+ 'error-handling': 'core',
25
+ // Frontend
26
+ 'xss-prevention': 'frontend',
27
+ 'csrf-protection': 'frontend',
28
+ 'csp': 'frontend',
29
+ 'secure-state': 'frontend',
30
+ // TypeScript
31
+ 'typescript-security': 'typescript',
32
+ // Frameworks
33
+ 'react-security': 'frameworks',
34
+ 'express-security': 'frameworks',
35
+ 'nextjs-security': 'frameworks',
36
+ };
32
37
 
33
38
  /**
34
39
  * Load a single template file by category name
35
40
  */
36
41
  export async function loadTemplate(category) {
37
- const subdir = CORE_CATEGORIES.includes(category) ? 'core' : 'frontend';
42
+ const subdir = CATEGORY_DIRS[category];
43
+ if (!subdir) {
44
+ console.warn(`Warning: Template not found: ${category}`);
45
+ return null;
46
+ }
38
47
  const filePath = join(TEMPLATES_DIR, subdir, `${category}.md`);
39
48
  try {
40
49
  return await readFile(filePath, 'utf-8');
@@ -80,6 +89,10 @@ export function getCategoryInfo(category) {
80
89
  'csrf-protection': { owasp: 'FE-02', title: 'CSRF Protection', group: 'frontend' },
81
90
  'csp': { owasp: 'FE-03', title: 'Content Security Policy', group: 'frontend' },
82
91
  'secure-state': { owasp: 'FE-04', title: 'Secure State Management', group: 'frontend' },
92
+ 'typescript-security': { owasp: 'TS-01', title: 'TypeScript Security', group: 'typescript' },
93
+ 'react-security': { owasp: 'FW-01', title: 'React Security', group: 'frameworks' },
94
+ 'express-security': { owasp: 'FW-02', title: 'Express Security', group: 'frameworks' },
95
+ 'nextjs-security': { owasp: 'FW-03', title: 'Next.js Security', group: 'frameworks' },
83
96
  };
84
97
  return info[category] || { owasp: '??', title: category, group: 'unknown' };
85
98
  }