agent-security-scanner-mcp 3.20.0 → 4.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.
Files changed (126) hide show
  1. package/README.md +144 -43
  2. package/code-review-agent/.env.example +8 -0
  3. package/code-review-agent/README.md +142 -0
  4. package/code-review-agent/TODO.md +149 -0
  5. package/code-review-agent/bin/cr-agent.ts +313 -0
  6. package/code-review-agent/dist/bin/cr-agent.d.ts +3 -0
  7. package/code-review-agent/dist/bin/cr-agent.d.ts.map +1 -0
  8. package/code-review-agent/dist/bin/cr-agent.js +299 -0
  9. package/code-review-agent/dist/bin/cr-agent.js.map +1 -0
  10. package/code-review-agent/dist/src/analyzer/engine.d.ts +16 -0
  11. package/code-review-agent/dist/src/analyzer/engine.d.ts.map +1 -0
  12. package/code-review-agent/dist/src/analyzer/engine.js +298 -0
  13. package/code-review-agent/dist/src/analyzer/engine.js.map +1 -0
  14. package/code-review-agent/dist/src/analyzer/intent.d.ts +10 -0
  15. package/code-review-agent/dist/src/analyzer/intent.d.ts.map +1 -0
  16. package/code-review-agent/dist/src/analyzer/intent.js +40 -0
  17. package/code-review-agent/dist/src/analyzer/intent.js.map +1 -0
  18. package/code-review-agent/dist/src/analyzer/semantic.d.ts +19 -0
  19. package/code-review-agent/dist/src/analyzer/semantic.d.ts.map +1 -0
  20. package/code-review-agent/dist/src/analyzer/semantic.js +150 -0
  21. package/code-review-agent/dist/src/analyzer/semantic.js.map +1 -0
  22. package/code-review-agent/dist/src/context/assembler.d.ts +16 -0
  23. package/code-review-agent/dist/src/context/assembler.d.ts.map +1 -0
  24. package/code-review-agent/dist/src/context/assembler.js +135 -0
  25. package/code-review-agent/dist/src/context/assembler.js.map +1 -0
  26. package/code-review-agent/dist/src/context/file.d.ts +6 -0
  27. package/code-review-agent/dist/src/context/file.d.ts.map +1 -0
  28. package/code-review-agent/dist/src/context/file.js +139 -0
  29. package/code-review-agent/dist/src/context/file.js.map +1 -0
  30. package/code-review-agent/dist/src/context/project.d.ts +4 -0
  31. package/code-review-agent/dist/src/context/project.d.ts.map +1 -0
  32. package/code-review-agent/dist/src/context/project.js +252 -0
  33. package/code-review-agent/dist/src/context/project.js.map +1 -0
  34. package/code-review-agent/dist/src/graph/dependency.d.ts +11 -0
  35. package/code-review-agent/dist/src/graph/dependency.d.ts.map +1 -0
  36. package/code-review-agent/dist/src/graph/dependency.js +102 -0
  37. package/code-review-agent/dist/src/graph/dependency.js.map +1 -0
  38. package/code-review-agent/dist/src/graph/resolver.d.ts +9 -0
  39. package/code-review-agent/dist/src/graph/resolver.d.ts.map +1 -0
  40. package/code-review-agent/dist/src/graph/resolver.js +124 -0
  41. package/code-review-agent/dist/src/graph/resolver.js.map +1 -0
  42. package/code-review-agent/dist/src/index.d.ts +21 -0
  43. package/code-review-agent/dist/src/index.d.ts.map +1 -0
  44. package/code-review-agent/dist/src/index.js +21 -0
  45. package/code-review-agent/dist/src/index.js.map +1 -0
  46. package/code-review-agent/dist/src/llm/anthropic.d.ts +13 -0
  47. package/code-review-agent/dist/src/llm/anthropic.d.ts.map +1 -0
  48. package/code-review-agent/dist/src/llm/anthropic.js +83 -0
  49. package/code-review-agent/dist/src/llm/anthropic.js.map +1 -0
  50. package/code-review-agent/dist/src/llm/claude-cli.d.ts +13 -0
  51. package/code-review-agent/dist/src/llm/claude-cli.d.ts.map +1 -0
  52. package/code-review-agent/dist/src/llm/claude-cli.js +142 -0
  53. package/code-review-agent/dist/src/llm/claude-cli.js.map +1 -0
  54. package/code-review-agent/dist/src/llm/openai.d.ts +13 -0
  55. package/code-review-agent/dist/src/llm/openai.d.ts.map +1 -0
  56. package/code-review-agent/dist/src/llm/openai.js +78 -0
  57. package/code-review-agent/dist/src/llm/openai.js.map +1 -0
  58. package/code-review-agent/dist/src/llm/provider.d.ts +18 -0
  59. package/code-review-agent/dist/src/llm/provider.d.ts.map +1 -0
  60. package/code-review-agent/dist/src/llm/provider.js +11 -0
  61. package/code-review-agent/dist/src/llm/provider.js.map +1 -0
  62. package/code-review-agent/dist/src/llm/router.d.ts +14 -0
  63. package/code-review-agent/dist/src/llm/router.d.ts.map +1 -0
  64. package/code-review-agent/dist/src/llm/router.js +67 -0
  65. package/code-review-agent/dist/src/llm/router.js.map +1 -0
  66. package/code-review-agent/dist/src/llm/schemas.d.ts +18 -0
  67. package/code-review-agent/dist/src/llm/schemas.d.ts.map +1 -0
  68. package/code-review-agent/dist/src/llm/schemas.js +91 -0
  69. package/code-review-agent/dist/src/llm/schemas.js.map +1 -0
  70. package/code-review-agent/dist/src/types/analysis.d.ts +56 -0
  71. package/code-review-agent/dist/src/types/analysis.d.ts.map +1 -0
  72. package/code-review-agent/dist/src/types/analysis.js +2 -0
  73. package/code-review-agent/dist/src/types/analysis.js.map +1 -0
  74. package/code-review-agent/dist/src/types/config.d.ts +24 -0
  75. package/code-review-agent/dist/src/types/config.d.ts.map +1 -0
  76. package/code-review-agent/dist/src/types/config.js +42 -0
  77. package/code-review-agent/dist/src/types/config.js.map +1 -0
  78. package/code-review-agent/dist/src/types/findings.d.ts +236 -0
  79. package/code-review-agent/dist/src/types/findings.d.ts.map +1 -0
  80. package/code-review-agent/dist/src/types/findings.js +64 -0
  81. package/code-review-agent/dist/src/types/findings.js.map +1 -0
  82. package/code-review-agent/package.json +36 -0
  83. package/code-review-agent/src/analyzer/engine.ts +374 -0
  84. package/code-review-agent/src/analyzer/intent.ts +49 -0
  85. package/code-review-agent/src/analyzer/semantic.ts +222 -0
  86. package/code-review-agent/src/context/assembler.ts +165 -0
  87. package/code-review-agent/src/context/file.ts +145 -0
  88. package/code-review-agent/src/context/project.ts +253 -0
  89. package/code-review-agent/src/graph/dependency.ts +116 -0
  90. package/code-review-agent/src/graph/resolver.ts +138 -0
  91. package/code-review-agent/src/index.ts +58 -0
  92. package/code-review-agent/src/llm/anthropic.ts +106 -0
  93. package/code-review-agent/src/llm/claude-cli.ts +188 -0
  94. package/code-review-agent/src/llm/openai.ts +95 -0
  95. package/code-review-agent/src/llm/provider.ts +33 -0
  96. package/code-review-agent/src/llm/router.ts +86 -0
  97. package/code-review-agent/src/llm/schemas.ts +125 -0
  98. package/code-review-agent/src/types/analysis.ts +62 -0
  99. package/code-review-agent/src/types/config.ts +72 -0
  100. package/code-review-agent/src/types/findings.ts +81 -0
  101. package/code-review-agent/tests/analyzer/engine.test.ts +194 -0
  102. package/code-review-agent/tests/analyzer/intent.test.ts +76 -0
  103. package/code-review-agent/tests/analyzer/semantic.test.ts +131 -0
  104. package/code-review-agent/tests/context/file.test.ts +21 -0
  105. package/code-review-agent/tests/context/project.test.ts +20 -0
  106. package/code-review-agent/tests/fixtures/safe-build-tool/README.md +19 -0
  107. package/code-review-agent/tests/fixtures/safe-build-tool/builder.js +52 -0
  108. package/code-review-agent/tests/fixtures/safe-file-manager/README.md +16 -0
  109. package/code-review-agent/tests/fixtures/safe-file-manager/organizer.py +70 -0
  110. package/code-review-agent/tests/fixtures/vuln-api-server/README.md +17 -0
  111. package/code-review-agent/tests/fixtures/vuln-api-server/server.js +52 -0
  112. package/code-review-agent/tests/fixtures/vuln-ecommerce/README.md +18 -0
  113. package/code-review-agent/tests/fixtures/vuln-ecommerce/checkout.js +63 -0
  114. package/code-review-agent/tests/graph/dependency.test.ts +136 -0
  115. package/code-review-agent/tests/helpers/mock-provider.ts +48 -0
  116. package/code-review-agent/tests/llm/claude-cli.test.ts +251 -0
  117. package/code-review-agent/tests/llm/router.test.ts +77 -0
  118. package/code-review-agent/tests/llm/schemas.test.ts +142 -0
  119. package/code-review-agent/tsconfig.json +20 -0
  120. package/code-review-agent/vitest.config.ts +11 -0
  121. package/index.js +18 -18
  122. package/openclaw.plugin.json +2 -2
  123. package/package.json +13 -3
  124. package/server.json +3 -3
  125. package/src/cli/init-hooks.js +3 -3
  126. package/src/cli/init.js +1 -1
@@ -0,0 +1,131 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { SemanticAnalyzer } from '../../src/analyzer/semantic.js';
3
+ import { MockLLMProvider } from '../helpers/mock-provider.js';
4
+ import type { FileContext, ProjectContext } from '../../src/types/analysis.js';
5
+ import type { IntentProfile } from '../../src/types/findings.js';
6
+
7
+ const mockIntent: IntentProfile = {
8
+ purpose: 'REST API for user management',
9
+ expectedBehaviors: ['Handle HTTP requests', 'Read/write database'],
10
+ unexpectedBehaviors: ['Execute system commands', 'Eval user input'],
11
+ framework: 'express',
12
+ riskDomain: 'web-api',
13
+ };
14
+
15
+ const mockProject: ProjectContext = {
16
+ readme: '# API Server',
17
+ packageMeta: { name: 'api', dependencies: { express: '^4.0.0' } },
18
+ directoryTree: 'src/\n server.js',
19
+ envVars: [],
20
+ hasDockerfile: false,
21
+ hasCI: false,
22
+ language: 'javascript/typescript',
23
+ framework: 'express',
24
+ };
25
+
26
+ const mockFileContext: FileContext = {
27
+ filePath: 'server.js',
28
+ content: 'const x = eval(req.query.code);',
29
+ language: 'javascript',
30
+ lineCount: 1,
31
+ imports: ['express'],
32
+ importedBy: [],
33
+ siblingFiles: [],
34
+ isTestFile: false,
35
+ isConfigFile: false,
36
+ isGenerated: false,
37
+ };
38
+
39
+ const mockAnalysisResponse = {
40
+ findings: [
41
+ {
42
+ title: 'eval() on user input',
43
+ severity: 'critical' as const,
44
+ category: 'security' as const,
45
+ location: { file: 'server.js', startLine: 1, endLine: 1 },
46
+ reasoning: 'eval() with user-controlled input allows arbitrary code execution',
47
+ intentAlignment: 'violates-intent' as const,
48
+ confidence: 0.95,
49
+ suggestedAction: 'Use a safe parser or whitelist approach instead of eval',
50
+ cwe: 'CWE-94',
51
+ },
52
+ ],
53
+ };
54
+
55
+ const mockTriageResponse = {
56
+ action: 'analyze' as const,
57
+ reason: 'File handles HTTP requests and uses eval',
58
+ areasOfInterest: [{ startLine: 1, endLine: 1, reason: 'eval() call' }],
59
+ };
60
+
61
+ describe('SemanticAnalyzer', () => {
62
+ it('analyzes a file and returns findings', async () => {
63
+ const analysisProvider = new MockLLMProvider({
64
+ file_analysis: mockAnalysisResponse,
65
+ });
66
+ const triageProvider = new MockLLMProvider({
67
+ triage_decision: mockTriageResponse,
68
+ });
69
+
70
+ const analyzer = new SemanticAnalyzer(analysisProvider, triageProvider);
71
+ const result = await analyzer.analyzeFile(mockIntent, mockProject, mockFileContext);
72
+
73
+ expect(result.findings).toHaveLength(1);
74
+ expect(result.findings[0].title).toBe('eval() on user input');
75
+ expect(result.findings[0].severity).toBe('critical');
76
+ expect(result.findings[0].intentAlignment).toBe('violates-intent');
77
+ expect(result.findings[0].confidence).toBe(0.95);
78
+ expect(result.tokensUsed).toBeGreaterThan(0);
79
+ });
80
+
81
+ it('triages a file and returns decision', async () => {
82
+ const analysisProvider = new MockLLMProvider({});
83
+ const triageProvider = new MockLLMProvider({
84
+ triage_decision: mockTriageResponse,
85
+ });
86
+
87
+ const analyzer = new SemanticAnalyzer(analysisProvider, triageProvider);
88
+ const decision = await analyzer.triageFile(mockProject, mockFileContext);
89
+
90
+ expect(decision.action).toBe('analyze');
91
+ expect(decision.areasOfInterest).toHaveLength(1);
92
+ expect(triageProvider.structuredCalls[0]?.messages[0]?.content).toContain('UNTRUSTED INPUT');
93
+ });
94
+
95
+ it('returns skip decision for safe files', async () => {
96
+ const skipResponse = {
97
+ action: 'skip' as const,
98
+ reason: 'Test file with no security-relevant code',
99
+ areasOfInterest: [],
100
+ };
101
+
102
+ const analysisProvider = new MockLLMProvider({});
103
+ const triageProvider = new MockLLMProvider({
104
+ triage_decision: skipResponse,
105
+ });
106
+
107
+ const analyzer = new SemanticAnalyzer(analysisProvider, triageProvider);
108
+ const decision = await analyzer.triageFile(mockProject, {
109
+ ...mockFileContext,
110
+ filePath: 'test.test.js',
111
+ isTestFile: true,
112
+ });
113
+
114
+ expect(decision.action).toBe('skip');
115
+ });
116
+
117
+ it('sets correct file path on findings', async () => {
118
+ const analysisProvider = new MockLLMProvider({
119
+ file_analysis: mockAnalysisResponse,
120
+ });
121
+ const triageProvider = new MockLLMProvider({});
122
+
123
+ const analyzer = new SemanticAnalyzer(analysisProvider, triageProvider);
124
+ const result = await analyzer.analyzeFile(mockIntent, mockProject, {
125
+ ...mockFileContext,
126
+ filePath: 'src/routes/users.js',
127
+ });
128
+
129
+ expect(result.findings[0].location.file).toBe('src/routes/users.js');
130
+ });
131
+ });
@@ -0,0 +1,21 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { isTestFile } from '../../src/context/file.js';
3
+
4
+ describe('isTestFile', () => {
5
+ it('matches top-level test directories on POSIX paths', () => {
6
+ expect(isTestFile('tests/helpers/mock-provider.ts')).toBe(true);
7
+ expect(isTestFile('test/unit/example.js')).toBe(true);
8
+ expect(isTestFile('__tests__/parser.test.ts')).toBe(true);
9
+ });
10
+
11
+ it('matches top-level test directories on Windows paths', () => {
12
+ expect(isTestFile('tests\\helpers\\mock-provider.ts')).toBe(true);
13
+ expect(isTestFile('test\\unit\\example.js')).toBe(true);
14
+ expect(isTestFile('__tests__\\parser.test.ts')).toBe(true);
15
+ });
16
+
17
+ it('does not classify regular source files as tests', () => {
18
+ expect(isTestFile('src/server.js')).toBe(false);
19
+ expect(isTestFile('fixtures/vuln-api-server/server.js')).toBe(false);
20
+ });
21
+ });
@@ -0,0 +1,20 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import { buildProjectContext } from '../../src/context/project.js';
6
+
7
+ describe('buildProjectContext', () => {
8
+ it('detects language from nested source files when manifests are absent', () => {
9
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cr-project-'));
10
+ try {
11
+ fs.mkdirSync(path.join(tmpDir, 'src'));
12
+ fs.writeFileSync(path.join(tmpDir, 'src', 'server.js'), 'console.log(1);');
13
+
14
+ const context = buildProjectContext(tmpDir);
15
+ expect(context.language).toBe('javascript/typescript');
16
+ } finally {
17
+ fs.rmSync(tmpDir, { recursive: true, force: true });
18
+ }
19
+ });
20
+ });
@@ -0,0 +1,19 @@
1
+ # Build Runner
2
+
3
+ A JavaScript build tool that compiles and bundles project assets. Runs predefined build steps like TypeScript compilation, CSS processing, and asset copying.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ node builder.js build
9
+ node builder.js clean
10
+ node builder.js watch
11
+ ```
12
+
13
+ ## Build Steps
14
+
15
+ 1. Clean dist/ directory
16
+ 2. Compile TypeScript
17
+ 3. Process CSS with PostCSS
18
+ 4. Copy static assets
19
+ 5. Generate source maps
@@ -0,0 +1,52 @@
1
+ const { execFile } = require('child_process');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ const DIST = path.join(__dirname, 'dist');
6
+
7
+ const BUILD_STEPS = [
8
+ { name: 'typescript', cmd: 'npx', args: ['tsc', '--outDir', DIST] },
9
+ { name: 'css', cmd: 'npx', args: ['postcss', 'src/styles/*.css', '--dir', path.join(DIST, 'css')] },
10
+ { name: 'assets', cmd: 'cp', args: ['-r', 'public/', path.join(DIST, 'public')] },
11
+ ];
12
+
13
+ function clean() {
14
+ if (fs.existsSync(DIST)) {
15
+ fs.rmSync(DIST, { recursive: true });
16
+ console.log('Cleaned dist/');
17
+ }
18
+ }
19
+
20
+ async function runStep(step) {
21
+ return new Promise((resolve, reject) => {
22
+ console.log(`Running: ${step.name}`);
23
+ execFile(step.cmd, step.args, { cwd: __dirname }, (error, stdout, stderr) => {
24
+ if (error) {
25
+ console.error(`${step.name} failed:`, stderr);
26
+ reject(error);
27
+ return;
28
+ }
29
+ console.log(`${step.name} done:`, stdout.trim());
30
+ resolve(stdout);
31
+ });
32
+ });
33
+ }
34
+
35
+ async function build() {
36
+ fs.mkdirSync(DIST, { recursive: true });
37
+ for (const step of BUILD_STEPS) {
38
+ await runStep(step);
39
+ }
40
+ console.log('Build complete!');
41
+ }
42
+
43
+ const command = process.argv[2] || 'build';
44
+ if (command === 'clean') {
45
+ clean();
46
+ } else if (command === 'build') {
47
+ clean();
48
+ build().catch((err) => {
49
+ console.error('Build failed:', err.message);
50
+ process.exit(1);
51
+ });
52
+ }
@@ -0,0 +1,16 @@
1
+ # File Organizer
2
+
3
+ A Python utility that organizes files in a directory by extension. It moves files into categorized subdirectories (images/, docs/, code/, etc.) based on their file type.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ python organizer.py /path/to/messy/directory
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - Sorts files by extension into categorized folders
14
+ - Handles duplicates by appending a number
15
+ - Supports dry-run mode with --dry-run flag
16
+ - Logs all operations to organizer.log
@@ -0,0 +1,70 @@
1
+ """File organizer - sorts files into categorized directories."""
2
+
3
+ import os
4
+ import sys
5
+ import shutil
6
+ import logging
7
+
8
+ logging.basicConfig(filename='organizer.log', level=logging.INFO)
9
+ logger = logging.getLogger(__name__)
10
+
11
+ CATEGORIES = {
12
+ 'images': ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg'],
13
+ 'docs': ['.pdf', '.doc', '.docx', '.txt', '.md', '.csv'],
14
+ 'code': ['.py', '.js', '.ts', '.java', '.go', '.rs'],
15
+ 'archives': ['.zip', '.tar', '.gz', '.rar', '.7z'],
16
+ 'media': ['.mp3', '.mp4', '.avi', '.mkv', '.wav'],
17
+ }
18
+
19
+
20
+ def get_category(filename):
21
+ ext = os.path.splitext(filename)[1].lower()
22
+ for category, extensions in CATEGORIES.items():
23
+ if ext in extensions:
24
+ return category
25
+ return 'other'
26
+
27
+
28
+ def organize(directory, dry_run=False):
29
+ if not os.path.isdir(directory):
30
+ logger.error(f"Not a directory: {directory}")
31
+ return
32
+
33
+ for filename in os.listdir(directory):
34
+ filepath = os.path.join(directory, filename)
35
+ if not os.path.isfile(filepath):
36
+ continue
37
+
38
+ category = get_category(filename)
39
+ target_dir = os.path.join(directory, category)
40
+
41
+ if not dry_run:
42
+ os.makedirs(target_dir, exist_ok=True)
43
+ target_path = os.path.join(target_dir, filename)
44
+
45
+ # Handle duplicates
46
+ counter = 1
47
+ base, ext = os.path.splitext(filename)
48
+ while os.path.exists(target_path):
49
+ target_path = os.path.join(target_dir, f"{base}_{counter}{ext}")
50
+ counter += 1
51
+
52
+ shutil.move(filepath, target_path)
53
+ logger.info(f"Moved {filename} -> {category}/")
54
+ else:
55
+ logger.info(f"[DRY RUN] Would move {filename} -> {category}/")
56
+
57
+
58
+ def cleanup_empty_dirs(directory):
59
+ for dirpath, dirnames, filenames in os.walk(directory, topdown=False):
60
+ if not filenames and not dirnames:
61
+ os.rmdir(dirpath)
62
+ logger.info(f"Removed empty directory: {dirpath}")
63
+
64
+
65
+ if __name__ == '__main__':
66
+ target = sys.argv[1] if len(sys.argv) > 1 else '.'
67
+ dry_run = '--dry-run' in sys.argv
68
+ organize(target, dry_run)
69
+ if not dry_run:
70
+ cleanup_empty_dirs(target)
@@ -0,0 +1,17 @@
1
+ # User API Server
2
+
3
+ A REST API server for managing user accounts. Handles user registration, authentication, and profile management.
4
+
5
+ ## Endpoints
6
+
7
+ - POST /api/register - Create new account
8
+ - POST /api/login - Authenticate user
9
+ - GET /api/profile/:id - Get user profile
10
+ - PUT /api/profile/:id - Update profile
11
+ - GET /api/search - Search users
12
+
13
+ ## Stack
14
+
15
+ - Node.js + Express
16
+ - SQLite database
17
+ - JWT authentication
@@ -0,0 +1,52 @@
1
+ const express = require('express');
2
+ const sqlite3 = require('sqlite3');
3
+ const fs = require('fs');
4
+ const app = express();
5
+
6
+ app.use(express.json());
7
+
8
+ const db = new sqlite3.Database('./users.db');
9
+
10
+ // User registration
11
+ app.post('/api/register', (req, res) => {
12
+ const { username, email, password } = req.body;
13
+ // SQL injection - string concatenation instead of parameterized query
14
+ const query = `INSERT INTO users (username, email, password) VALUES ('${username}', '${email}', '${password}')`;
15
+ db.run(query, (err) => {
16
+ if (err) return res.status(500).json({ error: err.message });
17
+ res.json({ success: true });
18
+ });
19
+ });
20
+
21
+ // User search - eval injection
22
+ app.get('/api/search', (req, res) => {
23
+ const { filter } = req.query;
24
+ // eval on user input - command injection
25
+ const filterFn = eval(`(user) => ${filter}`);
26
+ db.all('SELECT * FROM users', (err, users) => {
27
+ if (err) return res.status(500).json({ error: err.message });
28
+ const filtered = users.filter(filterFn);
29
+ res.json(filtered);
30
+ });
31
+ });
32
+
33
+ // Profile picture upload - path traversal
34
+ app.post('/api/upload', (req, res) => {
35
+ const { filename, data } = req.body;
36
+ // Arbitrary file write - no path sanitization
37
+ fs.writeFile(`./uploads/${filename}`, Buffer.from(data, 'base64'), (err) => {
38
+ if (err) return res.status(500).json({ error: err.message });
39
+ res.json({ path: `/uploads/${filename}` });
40
+ });
41
+ });
42
+
43
+ // Debug endpoint - exposes server info
44
+ app.get('/api/debug', (req, res) => {
45
+ res.json({
46
+ env: process.env,
47
+ cwd: process.cwd(),
48
+ memory: process.memoryUsage(),
49
+ });
50
+ });
51
+
52
+ app.listen(3000);
@@ -0,0 +1,18 @@
1
+ # E-Commerce Checkout
2
+
3
+ A Node.js e-commerce checkout service. Handles shopping cart management, price calculation, payment processing, and order confirmation.
4
+
5
+ ## Features
6
+
7
+ - Shopping cart with add/remove/update
8
+ - Discount code validation
9
+ - Payment gateway integration (Stripe)
10
+ - Order confirmation emails
11
+ - Inventory management
12
+
13
+ ## API
14
+
15
+ - POST /cart/add - Add item to cart
16
+ - POST /checkout - Process payment
17
+ - GET /order/:id - Get order status
18
+ - POST /discount/validate - Validate discount code
@@ -0,0 +1,63 @@
1
+ const express = require('express');
2
+ const app = express();
3
+
4
+ app.use(express.json());
5
+
6
+ const carts = {};
7
+
8
+ // Add to cart - prototype pollution via merge
9
+ app.post('/cart/add', (req, res) => {
10
+ const { userId, item } = req.body;
11
+ if (!carts[userId]) carts[userId] = { items: [] };
12
+ // Deep merge without prototype pollution protection
13
+ merge(carts[userId], item);
14
+ res.json(carts[userId]);
15
+ });
16
+
17
+ function merge(target, source) {
18
+ for (const key in source) {
19
+ if (typeof source[key] === 'object' && source[key] !== null) {
20
+ if (!target[key]) target[key] = {};
21
+ merge(target[key], source[key]);
22
+ } else {
23
+ target[key] = source[key];
24
+ }
25
+ }
26
+ return target;
27
+ }
28
+
29
+ // Apply discount - no auth check, price manipulation
30
+ app.post('/discount/validate', (req, res) => {
31
+ const { code, cartTotal } = req.body;
32
+ // User controls the cart total - should be server-side calculation
33
+ const discounts = { SAVE10: 0.1, SAVE50: 0.5, EMPLOYEE: 1.0 };
34
+ const discount = discounts[code];
35
+ if (discount) {
36
+ res.json({ newTotal: cartTotal * (1 - discount), valid: true });
37
+ } else {
38
+ res.json({ valid: false });
39
+ }
40
+ });
41
+
42
+ // Checkout - open redirect in success URL
43
+ app.post('/checkout', (req, res) => {
44
+ const { successUrl } = req.body;
45
+ // Process payment...
46
+ // Open redirect - user controls redirect destination
47
+ res.redirect(successUrl);
48
+ });
49
+
50
+ // Order lookup - no authorization, IDOR
51
+ app.get('/order/:id', (req, res) => {
52
+ // No check that requesting user owns this order
53
+ const order = getOrder(req.params.id);
54
+ if (!order) return res.status(404).json({ error: 'Not found' });
55
+ res.json(order);
56
+ });
57
+
58
+ function getOrder(id) {
59
+ // Stub
60
+ return { id, items: [], total: 0, status: 'completed' };
61
+ }
62
+
63
+ app.listen(3000);
@@ -0,0 +1,136 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import * as path from 'node:path';
3
+ import * as fs from 'node:fs';
4
+ import * as os from 'node:os';
5
+ import { DependencyGraphBuilder } from '../../src/graph/dependency.js';
6
+
7
+ describe('DependencyGraphBuilder', () => {
8
+ it('builds a graph from fixture files', () => {
9
+ const fixtureDir = path.resolve(__dirname, '../fixtures/vuln-api-server');
10
+ const builder = new DependencyGraphBuilder(fixtureDir);
11
+ const graph = builder.build(['server.js']);
12
+
13
+ expect(graph.nodes.size).toBeGreaterThan(0);
14
+ expect(graph.entryPoints).toEqual(['server.js']);
15
+
16
+ const serverNode = graph.nodes.get('server.js');
17
+ expect(serverNode).toBeTruthy();
18
+ expect(serverNode!.file).toBe('server.js');
19
+ });
20
+
21
+ it('resolves local imports', () => {
22
+ // Create a temp directory with two files that import each other
23
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cr-test-'));
24
+ try {
25
+ fs.writeFileSync(
26
+ path.join(tmpDir, 'main.js'),
27
+ "import { helper } from './helper.js';\nconsole.log(helper());",
28
+ );
29
+ fs.writeFileSync(
30
+ path.join(tmpDir, 'helper.js'),
31
+ "export function helper() { return 'hello'; }",
32
+ );
33
+
34
+ const builder = new DependencyGraphBuilder(tmpDir);
35
+ const graph = builder.build(['main.js']);
36
+
37
+ expect(graph.nodes.size).toBe(2);
38
+
39
+ const mainNode = graph.nodes.get('main.js');
40
+ expect(mainNode?.imports).toContain('helper.js');
41
+
42
+ const helperNode = graph.nodes.get('helper.js');
43
+ expect(helperNode?.importedBy).toContain('main.js');
44
+ } finally {
45
+ fs.rmSync(tmpDir, { recursive: true });
46
+ }
47
+ });
48
+
49
+ it('handles cycles without infinite loop', () => {
50
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cr-cycle-'));
51
+ try {
52
+ fs.writeFileSync(
53
+ path.join(tmpDir, 'a.js'),
54
+ "import './b.js';",
55
+ );
56
+ fs.writeFileSync(
57
+ path.join(tmpDir, 'b.js'),
58
+ "import './a.js';",
59
+ );
60
+
61
+ const builder = new DependencyGraphBuilder(tmpDir);
62
+ const graph = builder.build(['a.js']);
63
+
64
+ expect(graph.nodes.size).toBe(2);
65
+ // Should complete without hanging
66
+ } finally {
67
+ fs.rmSync(tmpDir, { recursive: true });
68
+ }
69
+ });
70
+
71
+ it('getImporters returns files that import a given file', () => {
72
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cr-importers-'));
73
+ try {
74
+ fs.writeFileSync(path.join(tmpDir, 'index.js'), "import './utils.js';");
75
+ fs.writeFileSync(path.join(tmpDir, 'utils.js'), "export const x = 1;");
76
+
77
+ const builder = new DependencyGraphBuilder(tmpDir);
78
+ builder.build(['index.js']);
79
+
80
+ expect(builder.getImporters('utils.js')).toContain('index.js');
81
+ expect(builder.getImportees('index.js')).toContain('utils.js');
82
+ } finally {
83
+ fs.rmSync(tmpDir, { recursive: true });
84
+ }
85
+ });
86
+
87
+ it('ignores non-local imports', () => {
88
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cr-external-'));
89
+ try {
90
+ fs.writeFileSync(
91
+ path.join(tmpDir, 'app.js'),
92
+ "import express from 'express';\nimport chalk from 'chalk';",
93
+ );
94
+
95
+ const builder = new DependencyGraphBuilder(tmpDir);
96
+ const graph = builder.build(['app.js']);
97
+
98
+ const appNode = graph.nodes.get('app.js');
99
+ expect(appNode?.imports).toHaveLength(0);
100
+ } finally {
101
+ fs.rmSync(tmpDir, { recursive: true });
102
+ }
103
+ });
104
+
105
+ it('includes barrel re-export edges in the graph', () => {
106
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cr-barrel-'));
107
+ try {
108
+ fs.writeFileSync(path.join(tmpDir, 'app.js'), "import { value } from './index.js';");
109
+ fs.writeFileSync(path.join(tmpDir, 'index.js'), "export * from './lib.js';");
110
+ fs.writeFileSync(path.join(tmpDir, 'lib.js'), 'export const value = 1;');
111
+
112
+ const builder = new DependencyGraphBuilder(tmpDir);
113
+ const graph = builder.build(['app.js']);
114
+
115
+ expect(graph.nodes.get('index.js')?.imports).toContain('lib.js');
116
+ expect(graph.nodes.get('lib.js')?.importedBy).toContain('index.js');
117
+ } finally {
118
+ fs.rmSync(tmpDir, { recursive: true });
119
+ }
120
+ });
121
+
122
+ it('keeps supported non-JS entry files in the graph', () => {
123
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cr-java-'));
124
+ try {
125
+ fs.writeFileSync(path.join(tmpDir, 'Main.java'), 'class Main {}');
126
+
127
+ const builder = new DependencyGraphBuilder(tmpDir);
128
+ const graph = builder.build(['Main.java']);
129
+
130
+ expect(graph.entryPoints).toEqual(['Main.java']);
131
+ expect(graph.nodes.get('Main.java')?.file).toBe('Main.java');
132
+ } finally {
133
+ fs.rmSync(tmpDir, { recursive: true });
134
+ }
135
+ });
136
+ });
@@ -0,0 +1,48 @@
1
+ import type { z } from 'zod';
2
+ import type { ChatMessage, LLMProvider } from '../../src/llm/provider.js';
3
+
4
+ export class MockLLMProvider implements LLMProvider {
5
+ readonly modelId: string;
6
+ readonly providerName = 'mock';
7
+
8
+ private responses: Map<string, unknown>;
9
+ public chatCalls: Array<{ messages: ChatMessage[] }> = [];
10
+ public structuredCalls: Array<{ messages: ChatMessage[]; schemaName: string }> = [];
11
+
12
+ constructor(
13
+ responses: Record<string, unknown>,
14
+ modelId = 'mock-model',
15
+ ) {
16
+ this.responses = new Map(Object.entries(responses));
17
+ this.modelId = modelId;
18
+ }
19
+
20
+ async chat(messages: ChatMessage[]): Promise<string> {
21
+ this.chatCalls.push({ messages });
22
+ return 'mock response';
23
+ }
24
+
25
+ async chatStructured<T>(
26
+ messages: ChatMessage[],
27
+ schema: z.ZodType<T>,
28
+ schemaName: string,
29
+ ): Promise<T> {
30
+ this.structuredCalls.push({ messages, schemaName });
31
+
32
+ const response = this.responses.get(schemaName);
33
+ if (!response) {
34
+ throw new Error(`No mock response for schema: ${schemaName}`);
35
+ }
36
+
37
+ const result = schema.safeParse(response);
38
+ if (!result.success) {
39
+ throw new Error(`Mock response failed validation for ${schemaName}: ${result.error.message}`);
40
+ }
41
+
42
+ return result.data;
43
+ }
44
+
45
+ countTokens(text: string): number {
46
+ return Math.ceil(text.length / 4);
47
+ }
48
+ }