agent-security-scanner-mcp 4.0.0 → 4.1.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 (71) hide show
  1. package/README.md +47 -58
  2. package/code-review-agent/README.md +25 -4
  3. package/code-review-agent/TODO.md +1 -1
  4. package/code-review-agent/bin/cr-agent.ts +7 -1
  5. package/code-review-agent/dist/bin/cr-agent.js +7 -1
  6. package/code-review-agent/dist/bin/cr-agent.js.map +1 -1
  7. package/code-review-agent/dist/src/analyzer/engine.d.ts +5 -0
  8. package/code-review-agent/dist/src/analyzer/engine.d.ts.map +1 -1
  9. package/code-review-agent/dist/src/analyzer/engine.js +30 -3
  10. package/code-review-agent/dist/src/analyzer/engine.js.map +1 -1
  11. package/code-review-agent/dist/src/analyzer/postprocess.d.ts +15 -0
  12. package/code-review-agent/dist/src/analyzer/postprocess.d.ts.map +1 -0
  13. package/code-review-agent/dist/src/analyzer/postprocess.js +275 -0
  14. package/code-review-agent/dist/src/analyzer/postprocess.js.map +1 -0
  15. package/code-review-agent/dist/src/analyzer/semantic.d.ts +5 -1
  16. package/code-review-agent/dist/src/analyzer/semantic.d.ts.map +1 -1
  17. package/code-review-agent/dist/src/analyzer/semantic.js +80 -20
  18. package/code-review-agent/dist/src/analyzer/semantic.js.map +1 -1
  19. package/code-review-agent/dist/src/context/assembler.d.ts +8 -2
  20. package/code-review-agent/dist/src/context/assembler.d.ts.map +1 -1
  21. package/code-review-agent/dist/src/context/assembler.js +33 -1
  22. package/code-review-agent/dist/src/context/assembler.js.map +1 -1
  23. package/code-review-agent/dist/src/context/file.d.ts.map +1 -1
  24. package/code-review-agent/dist/src/context/file.js +11 -23
  25. package/code-review-agent/dist/src/context/file.js.map +1 -1
  26. package/code-review-agent/dist/src/context/security-summary.d.ts +19 -0
  27. package/code-review-agent/dist/src/context/security-summary.d.ts.map +1 -0
  28. package/code-review-agent/dist/src/context/security-summary.js +199 -0
  29. package/code-review-agent/dist/src/context/security-summary.js.map +1 -0
  30. package/code-review-agent/dist/src/graph/dependency.d.ts.map +1 -1
  31. package/code-review-agent/dist/src/graph/dependency.js +8 -1
  32. package/code-review-agent/dist/src/graph/dependency.js.map +1 -1
  33. package/code-review-agent/dist/src/graph/resolver.d.ts.map +1 -1
  34. package/code-review-agent/dist/src/graph/resolver.js +14 -5
  35. package/code-review-agent/dist/src/graph/resolver.js.map +1 -1
  36. package/code-review-agent/dist/src/index.d.ts +4 -1
  37. package/code-review-agent/dist/src/index.d.ts.map +1 -1
  38. package/code-review-agent/dist/src/index.js +2 -0
  39. package/code-review-agent/dist/src/index.js.map +1 -1
  40. package/code-review-agent/dist/src/types/config.d.ts +3 -0
  41. package/code-review-agent/dist/src/types/config.d.ts.map +1 -1
  42. package/code-review-agent/dist/src/types/config.js +9 -0
  43. package/code-review-agent/dist/src/types/config.js.map +1 -1
  44. package/code-review-agent/src/analyzer/engine.ts +36 -2
  45. package/code-review-agent/src/analyzer/postprocess.ts +311 -0
  46. package/code-review-agent/src/analyzer/semantic.ts +87 -18
  47. package/code-review-agent/src/context/assembler.ts +44 -2
  48. package/code-review-agent/src/context/file.ts +13 -18
  49. package/code-review-agent/src/context/security-summary.ts +225 -0
  50. package/code-review-agent/src/graph/dependency.ts +8 -1
  51. package/code-review-agent/src/graph/resolver.ts +14 -5
  52. package/code-review-agent/src/index.ts +4 -0
  53. package/code-review-agent/src/types/config.ts +16 -0
  54. package/code-review-agent/tests/analyzer/engine.test.ts +5 -0
  55. package/code-review-agent/tests/analyzer/postprocess.test.ts +450 -0
  56. package/code-review-agent/tests/analyzer/prompt-routing.test.ts +137 -0
  57. package/code-review-agent/tests/config-mode.test.ts +71 -0
  58. package/code-review-agent/tests/context/file.test.ts +16 -1
  59. package/code-review-agent/tests/context/security-summary.test.ts +181 -0
  60. package/code-review-agent/tests/fixtures/guarded-agent/router.py +6 -0
  61. package/code-review-agent/tests/fixtures/guarded-agent/tools/executor.py +10 -0
  62. package/code-review-agent/tests/fixtures/guarded-agent/tools/guard.py +4 -0
  63. package/code-review-agent/tests/fixtures/guarded-agent/vuln-tool.py +6 -0
  64. package/code-review-agent/tests/graph/dependency.test.ts +76 -0
  65. package/index.js +18 -18
  66. package/openclaw.plugin.json +1 -1
  67. package/package.json +3 -2
  68. package/scripts/postinstall.js +43 -4
  69. package/server.json +1 -1
  70. package/src/cli/init-hooks.js +3 -3
  71. package/src/cli/init.js +1 -1
@@ -0,0 +1,181 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import * as path from 'node:path';
3
+ import {
4
+ buildRelatedFileSummaries,
5
+ formatRelatedFileSummaries,
6
+ } from '../../src/context/security-summary.js';
7
+ import type { FileContext } from '../../src/types/analysis.js';
8
+
9
+ const FIXTURES_DIR = path.resolve(__dirname, '..', 'fixtures', 'guarded-agent');
10
+
11
+ function makeFileContext(overrides: Partial<FileContext> = {}): FileContext {
12
+ return {
13
+ filePath: 'router.py',
14
+ content: 'from tools.executor import execute_tool\n',
15
+ language: 'python',
16
+ lineCount: 5,
17
+ imports: ['./tools/executor'],
18
+ importedBy: [],
19
+ siblingFiles: ['vuln-tool.py', 'tools'],
20
+ isTestFile: false,
21
+ isConfigFile: false,
22
+ isGenerated: false,
23
+ ...overrides,
24
+ };
25
+ }
26
+
27
+ describe('buildRelatedFileSummaries', () => {
28
+ it('extracts security-relevant lines from imported files', () => {
29
+ const file = makeFileContext({
30
+ filePath: 'router.py',
31
+ imports: ['./tools/executor'],
32
+ });
33
+
34
+ const summaries = buildRelatedFileSummaries(file, FIXTURES_DIR);
35
+
36
+ expect(summaries.length).toBeGreaterThan(0);
37
+ const executorSummary = summaries.find((s) => s.filePath.includes('executor'));
38
+ expect(executorSummary).toBeDefined();
39
+ expect(executorSummary!.relationship).toBe('imports');
40
+ // Should capture subprocess.run line
41
+ expect(executorSummary!.relevantLines.some((l) => l.includes('subprocess'))).toBe(true);
42
+ });
43
+
44
+ it('includes security-relevant sibling files', () => {
45
+ const file = makeFileContext({
46
+ filePath: 'router.py',
47
+ imports: [],
48
+ siblingFiles: ['vuln-tool.py', 'guard.py', 'readme.txt'],
49
+ });
50
+
51
+ // guard.py is a security-relevant sibling
52
+ const summaries = buildRelatedFileSummaries(file, FIXTURES_DIR);
53
+
54
+ // May or may not find guard.py depending on whether it exists at the right path
55
+ // The point is the function doesn't crash and returns valid summaries
56
+ expect(Array.isArray(summaries)).toBe(true);
57
+ });
58
+
59
+ it('returns empty array when no related files have security-relevant content', () => {
60
+ const file = makeFileContext({
61
+ filePath: 'empty.py',
62
+ imports: [],
63
+ importedBy: [],
64
+ siblingFiles: [],
65
+ });
66
+
67
+ const summaries = buildRelatedFileSummaries(file, FIXTURES_DIR);
68
+ expect(summaries).toEqual([]);
69
+ });
70
+
71
+ it('limits to MAX_RELATED_FILES (4)', () => {
72
+ const file = makeFileContext({
73
+ imports: [
74
+ './tools/executor',
75
+ './tools/guard',
76
+ './vuln-tool',
77
+ './router',
78
+ './extra1',
79
+ './extra2',
80
+ ],
81
+ });
82
+
83
+ const summaries = buildRelatedFileSummaries(file, FIXTURES_DIR);
84
+ expect(summaries.length).toBeLessThanOrEqual(4);
85
+ });
86
+
87
+ it('resolves Python bare module imports (tools.executor style)', () => {
88
+ const file = makeFileContext({
89
+ filePath: 'router.py',
90
+ imports: ['tools.executor'], // as buildFileContext would actually produce
91
+ });
92
+
93
+ const summaries = buildRelatedFileSummaries(file, FIXTURES_DIR);
94
+
95
+ const executorSummary = summaries.find((s) => s.filePath.includes('executor'));
96
+ expect(executorSummary).toBeDefined();
97
+ expect(executorSummary!.relationship).toBe('imports');
98
+ expect(executorSummary!.relevantLines.some((l) => l.includes('subprocess'))).toBe(true);
99
+ });
100
+
101
+ it('resolves Python single-token imports (import guard style)', () => {
102
+ const file = makeFileContext({
103
+ filePath: 'router.py',
104
+ imports: ['tools.guard'], // from tools.guard import is_allowed_command
105
+ });
106
+
107
+ const summaries = buildRelatedFileSummaries(file, FIXTURES_DIR);
108
+
109
+ const guardSummary = summaries.find((s) => s.filePath.includes('guard'));
110
+ expect(guardSummary).toBeDefined();
111
+ expect(guardSummary!.relevantLines.some((l) => /allow/i.test(l))).toBe(true);
112
+ });
113
+
114
+ it('resolves submodule import from file.ts output (from tools import executor)', () => {
115
+ // file.ts now emits both 'tools' and 'tools.executor' for `from tools import executor`
116
+ // The resolver should find tools/executor.py via the dotted form
117
+ const file = makeFileContext({
118
+ filePath: 'router.py',
119
+ imports: ['tools', 'tools.executor'], // as file.ts now produces
120
+ });
121
+
122
+ const summaries = buildRelatedFileSummaries(file, FIXTURES_DIR);
123
+
124
+ const executorSummary = summaries.find((s) => s.filePath.includes('executor'));
125
+ expect(executorSummary).toBeDefined();
126
+ expect(executorSummary!.relevantLines.some((l) => l.includes('subprocess'))).toBe(true);
127
+ });
128
+
129
+ it('does not inject unrelated package children for bare import', () => {
130
+ // `from tools import safe_helper` should NOT pull in unrelated executor.py
131
+ // file.ts emits ['tools', 'tools.safe_helper'] — neither resolves to executor.py
132
+ const file = makeFileContext({
133
+ filePath: 'router.py',
134
+ imports: ['tools', 'tools.safe_helper'], // safe_helper doesn't exist in fixture
135
+ });
136
+
137
+ const summaries = buildRelatedFileSummaries(file, FIXTURES_DIR);
138
+
139
+ // 'tools' alone resolves to nothing (no tools.py, no tools/__init__.py)
140
+ // 'tools.safe_helper' resolves to nothing (no tools/safe_helper.py)
141
+ // So no executor.py should appear from the import path
142
+ const executorFromImport = summaries.find((s) =>
143
+ s.filePath.includes('executor') && s.relationship === 'imports');
144
+ expect(executorFromImport).toBeUndefined();
145
+ });
146
+
147
+ it('does not summarize the same file twice across relationships', () => {
148
+ // If a file is both imported and a sibling, it should appear only once
149
+ const file = makeFileContext({
150
+ filePath: 'router.py',
151
+ imports: ['./tools/executor'],
152
+ importedBy: [],
153
+ siblingFiles: ['tools'], // tools dir is a sibling
154
+ });
155
+
156
+ const summaries = buildRelatedFileSummaries(file, FIXTURES_DIR);
157
+ const paths = summaries.map((s) => s.filePath);
158
+ const unique = new Set(paths);
159
+ expect(paths.length).toBe(unique.size);
160
+ });
161
+ });
162
+
163
+ describe('formatRelatedFileSummaries', () => {
164
+ it('returns empty string for no summaries', () => {
165
+ expect(formatRelatedFileSummaries([])).toBe('');
166
+ });
167
+
168
+ it('formats summaries with file path, relationship, and lines', () => {
169
+ const summaries = [
170
+ {
171
+ filePath: 'tools/executor.py',
172
+ relationship: 'imports' as const,
173
+ relevantLines: ['L9: result = subprocess.run([command, *args], capture_output=True, text=True, shell=False)'],
174
+ },
175
+ ];
176
+
177
+ const formatted = formatRelatedFileSummaries(summaries);
178
+ expect(formatted).toContain('tools/executor.py (imports)');
179
+ expect(formatted).toContain('subprocess.run');
180
+ });
181
+ });
@@ -0,0 +1,6 @@
1
+ from tools.executor import execute_tool
2
+
3
+ def handle_request(action: str, args: list[str]) -> str:
4
+ if action == "run_command":
5
+ return execute_tool(args[0], args[1:])
6
+ return "unknown action"
@@ -0,0 +1,10 @@
1
+ import subprocess
2
+ from .guard import is_allowed_command
3
+
4
+ ALLOWED_COMMANDS = {"ls", "cat", "echo", "date", "whoami"}
5
+
6
+ def execute_tool(command: str, args: list[str]) -> str:
7
+ if command not in ALLOWED_COMMANDS:
8
+ raise ValueError(f"Command not allowed: {command}")
9
+ result = subprocess.run([command, *args], capture_output=True, text=True, shell=False)
10
+ return result.stdout
@@ -0,0 +1,4 @@
1
+ ALLOWED_COMMANDS = frozenset({"ls", "cat", "echo", "date", "whoami"})
2
+
3
+ def is_allowed_command(command: str) -> bool:
4
+ return command in ALLOWED_COMMANDS
@@ -0,0 +1,6 @@
1
+ import requests
2
+
3
+ def fetch_url(url: str) -> str:
4
+ """Fetches a URL without any validation - SSRF vulnerability."""
5
+ response = requests.get(url)
6
+ return response.text
@@ -119,6 +119,82 @@ describe('DependencyGraphBuilder', () => {
119
119
  }
120
120
  });
121
121
 
122
+ it('resolves Python bare module imports into the graph', () => {
123
+ const fixtureDir = path.resolve(__dirname, '../fixtures/guarded-agent');
124
+ const builder = new DependencyGraphBuilder(fixtureDir);
125
+ const graph = builder.build(['router.py']);
126
+
127
+ // router.py has `from tools.executor import execute_tool`
128
+ // which should resolve to tools/executor.py
129
+ const routerNode = graph.nodes.get('router.py');
130
+ expect(routerNode).toBeTruthy();
131
+
132
+ // Check that tools/executor.py is in the graph with a reverse edge
133
+ const executorKey = Array.from(graph.nodes.keys()).find((k) => k.includes('executor'));
134
+ expect(executorKey).toBeDefined();
135
+ const executorNode = graph.nodes.get(executorKey!);
136
+ expect(executorNode?.importedBy).toContain('router.py');
137
+ });
138
+
139
+ it('resolves Python bare imports from project root for nested files', () => {
140
+ // Simulate app/router.py importing tools.executor where tools/ is at project root
141
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cr-nested-py-'));
142
+ try {
143
+ fs.mkdirSync(path.join(tmpDir, 'app'));
144
+ fs.mkdirSync(path.join(tmpDir, 'tools'));
145
+ fs.writeFileSync(
146
+ path.join(tmpDir, 'app', 'router.py'),
147
+ 'from tools.executor import run\n',
148
+ );
149
+ fs.writeFileSync(
150
+ path.join(tmpDir, 'tools', 'executor.py'),
151
+ 'def run(): pass\n',
152
+ );
153
+
154
+ const builder = new DependencyGraphBuilder(tmpDir);
155
+ const graph = builder.build(['app/router.py']);
156
+
157
+ const normalizedKey = Array.from(graph.nodes.keys()).find((k) =>
158
+ k.replace(/\\/g, '/').includes('tools/executor'));
159
+ expect(normalizedKey).toBeDefined();
160
+ const executorNode = graph.nodes.get(normalizedKey!);
161
+ // Path separators vary by OS — normalize for comparison
162
+ const importedByNorm = executorNode?.importedBy.map((p) => p.replace(/\\/g, '/'));
163
+ expect(importedByNorm).toContain('app/router.py');
164
+ } finally {
165
+ fs.rmSync(tmpDir, { recursive: true });
166
+ }
167
+ });
168
+
169
+ it('resolves "from package import submodule" form into the graph', () => {
170
+ // `from tools import executor` — the graph resolver must emit tools.executor
171
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cr-from-import-'));
172
+ try {
173
+ fs.mkdirSync(path.join(tmpDir, 'app'));
174
+ fs.mkdirSync(path.join(tmpDir, 'tools'));
175
+ fs.writeFileSync(
176
+ path.join(tmpDir, 'app', 'router.py'),
177
+ 'from tools import executor\n',
178
+ );
179
+ fs.writeFileSync(
180
+ path.join(tmpDir, 'tools', 'executor.py'),
181
+ 'def run(): pass\n',
182
+ );
183
+
184
+ const builder = new DependencyGraphBuilder(tmpDir);
185
+ const graph = builder.build(['app/router.py']);
186
+
187
+ const normalizedKey = Array.from(graph.nodes.keys()).find((k) =>
188
+ k.replace(/\\/g, '/').includes('tools/executor'));
189
+ expect(normalizedKey).toBeDefined();
190
+ const executorNode = graph.nodes.get(normalizedKey!);
191
+ const importedByNorm = executorNode?.importedBy.map((p) => p.replace(/\\/g, '/'));
192
+ expect(importedByNorm).toContain('app/router.py');
193
+ } finally {
194
+ fs.rmSync(tmpDir, { recursive: true });
195
+ }
196
+ });
197
+
122
198
  it('keeps supported non-JS entry files in the graph', () => {
123
199
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cr-java-'));
124
200
  try {
package/index.js CHANGED
@@ -47,7 +47,7 @@ try {
47
47
  // Create MCP Server
48
48
  const server = new McpServer(
49
49
  {
50
- name: "prooflayer-agent-security",
50
+ name: "agent-security-scanner-mcp",
51
51
  version: _pkgVersion,
52
52
  },
53
53
  {
@@ -306,7 +306,7 @@ const cliArgs = process.argv.slice(2);
306
306
  // CLI mode: scan-prompt <text> [--verbosity minimal|compact|full]
307
307
  const text = cliArgs[1];
308
308
  if (!text) {
309
- console.error('Usage: prooflayer-agent-security scan-prompt <text> [--verbosity minimal|compact|full]');
309
+ console.error('Usage: agent-security-scanner-mcp scan-prompt <text> [--verbosity minimal|compact|full]');
310
310
  process.exit(1);
311
311
  }
312
312
  const verbosityIdx = cliArgs.indexOf('--verbosity');
@@ -326,7 +326,7 @@ const cliArgs = process.argv.slice(2);
326
326
  // CLI mode: scan-security <file> [--verbosity minimal|compact|full] [--format json|sarif]
327
327
  const filePath = cliArgs[1];
328
328
  if (!filePath) {
329
- console.error('Usage: prooflayer-agent-security scan-security <file> [--verbosity minimal|compact|full] [--format json|sarif]');
329
+ console.error('Usage: agent-security-scanner-mcp scan-security <file> [--verbosity minimal|compact|full] [--format json|sarif]');
330
330
  process.exit(1);
331
331
  }
332
332
  const verbosityIdx = cliArgs.indexOf('--verbosity');
@@ -348,7 +348,7 @@ const cliArgs = process.argv.slice(2);
348
348
  const packageName = cliArgs[1];
349
349
  const ecosystem = cliArgs[2];
350
350
  if (!packageName || !ecosystem) {
351
- console.error('Usage: prooflayer-agent-security check-package <name> <ecosystem>');
351
+ console.error('Usage: agent-security-scanner-mcp check-package <name> <ecosystem>');
352
352
  console.error('Ecosystems: npm, pypi, rubygems, crates, dart, perl, raku');
353
353
  process.exit(1);
354
354
  }
@@ -367,7 +367,7 @@ const cliArgs = process.argv.slice(2);
367
367
  const filePath = cliArgs[1];
368
368
  const ecosystem = cliArgs[2];
369
369
  if (!filePath || !ecosystem) {
370
- console.error('Usage: prooflayer-agent-security scan-packages <file> <ecosystem> [--verbosity minimal|compact|full]');
370
+ console.error('Usage: agent-security-scanner-mcp scan-packages <file> <ecosystem> [--verbosity minimal|compact|full]');
371
371
  console.error('Ecosystems: npm, pypi, rubygems, crates, dart, perl, raku');
372
372
  process.exit(1);
373
373
  }
@@ -387,7 +387,7 @@ const cliArgs = process.argv.slice(2);
387
387
  // CLI mode: scan-project <dir> [--recursive] [--diff-only] [--cross-file] [--include '*.py'] [--exclude '*.test.js'] [--verbosity minimal|compact|full]
388
388
  const dirPath = cliArgs[1];
389
389
  if (!dirPath || dirPath.startsWith('--')) {
390
- console.error('Usage: prooflayer-agent-security scan-project <directory> [--recursive] [--diff-only] [--cross-file] [--include <pattern>] [--exclude <pattern>] [--verbosity minimal|compact|full]');
390
+ console.error('Usage: agent-security-scanner-mcp scan-project <directory> [--recursive] [--diff-only] [--cross-file] [--include <pattern>] [--exclude <pattern>] [--verbosity minimal|compact|full]');
391
391
  process.exit(1);
392
392
  }
393
393
  const verbosityIdx = cliArgs.indexOf('--verbosity');
@@ -455,7 +455,7 @@ const cliArgs = process.argv.slice(2);
455
455
  // CLI mode: scan-mcp <path> [--verbosity minimal|compact|full]
456
456
  const serverPath = cliArgs[1];
457
457
  if (!serverPath) {
458
- console.error('Usage: prooflayer-agent-security scan-mcp <server-path> [--verbosity minimal|compact|full]');
458
+ console.error('Usage: agent-security-scanner-mcp scan-mcp <server-path> [--verbosity minimal|compact|full]');
459
459
  process.exit(1);
460
460
  }
461
461
  const verbosityIdx = cliArgs.indexOf('--verbosity');
@@ -474,7 +474,7 @@ const cliArgs = process.argv.slice(2);
474
474
  const actionType = cliArgs[1];
475
475
  const actionValue = cliArgs[2];
476
476
  if (!actionType || !actionValue) {
477
- console.error('Usage: prooflayer-agent-security scan-action <type> <value> [--verbosity minimal|compact|full]');
477
+ console.error('Usage: agent-security-scanner-mcp scan-action <type> <value> [--verbosity minimal|compact|full]');
478
478
  console.error('Types: bash, file_write, file_read, http_request, file_delete, cron, process_spawn, git, docker');
479
479
  process.exit(1);
480
480
  }
@@ -492,7 +492,7 @@ const cliArgs = process.argv.slice(2);
492
492
  } else if (cliArgs[0] === 'scan-skill') {
493
493
  const skillPath = cliArgs[1];
494
494
  if (!skillPath) {
495
- console.error('Usage: prooflayer-agent-security scan-skill <skill-path> [--verbosity minimal|compact|full] [--baseline]');
495
+ console.error('Usage: agent-security-scanner-mcp scan-skill <skill-path> [--verbosity minimal|compact|full] [--baseline]');
496
496
  process.exit(1);
497
497
  }
498
498
  const verbosityIdx = cliArgs.indexOf('--verbosity');
@@ -529,7 +529,7 @@ const cliArgs = process.argv.slice(2);
529
529
  await import('./src/cli/scan-clawhub-safe.js');
530
530
  // Exit is handled by scan-clawhub-safe.js
531
531
  } else if (cliArgs[0] === '--help' || cliArgs[0] === '-h' || cliArgs[0] === 'help') {
532
- console.log('\n prooflayer-agent-security\n');
532
+ console.log('\n agent-security-scanner-mcp\n');
533
533
  console.log(' Commands:');
534
534
  console.log(' init [client] Set up MCP config for a client');
535
535
  console.log(' init-hooks Install Claude Code hooks for auto-scanning');
@@ -557,14 +557,14 @@ const cliArgs = process.argv.slice(2);
557
557
  console.log(' --include <pattern> Include only matching files (scan-project)');
558
558
  console.log(' --exclude <pattern> Exclude matching files (scan-project)\n');
559
559
  console.log(' Examples:');
560
- console.log(' npx prooflayer-agent-security init');
561
- console.log(' npx prooflayer-agent-security scan-prompt "ignore previous instructions"');
562
- console.log(' npx prooflayer-agent-security scan-security ./app.py --verbosity minimal');
563
- console.log(' npx prooflayer-agent-security check-package flask pypi');
564
- console.log(' npx prooflayer-agent-security scan-project ./src --verbosity minimal');
565
- console.log(' npx prooflayer-agent-security scan-diff HEAD~1');
566
- console.log(' npx prooflayer-agent-security report ./src --json');
567
- console.log(' npx prooflayer-agent-security benchmark --save --compare-latest\n');
560
+ console.log(' npx agent-security-scanner-mcp init');
561
+ console.log(' npx agent-security-scanner-mcp scan-prompt "ignore previous instructions"');
562
+ console.log(' npx agent-security-scanner-mcp scan-security ./app.py --verbosity minimal');
563
+ console.log(' npx agent-security-scanner-mcp check-package flask pypi');
564
+ console.log(' npx agent-security-scanner-mcp scan-project ./src --verbosity minimal');
565
+ console.log(' npx agent-security-scanner-mcp scan-diff HEAD~1');
566
+ console.log(' npx agent-security-scanner-mcp report ./src --json');
567
+ console.log(' npx agent-security-scanner-mcp benchmark --save --compare-latest\n');
568
568
  process.exit(0);
569
569
  } else {
570
570
  // Normal MCP server mode
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "prooflayer-agent-security",
2
+ "name": "agent-security-scanner-mcp",
3
3
  "version": "4.0.0",
4
4
  "description": "Security scanner for OpenClaw: prompt injection firewall, package hallucination detection, code vulnerability scanning, auto-fix",
5
5
  "author": "Sinewave AI",
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "agent-security-scanner-mcp",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "mcpName": "io.github.sinewaveai/agent-security-scanner-mcp",
5
5
  "description": "Security scanner MCP server for AI coding agents. Prompt injection firewall, package hallucination detection (4.3M+ packages), 1700+ vulnerability rules with AST & taint analysis, LLM-powered semantic code review, auto-fix. For Claude Code, Cursor, Windsurf, Cline, OpenClaw.",
6
6
  "main": "index.js",
7
7
  "type": "module",
8
8
  "bin": {
9
- "agent-security-scanner-mcp": "index.js"
9
+ "agent-security-scanner-mcp": "index.js",
10
+ "cr-agent": "code-review-agent/dist/bin/cr-agent.js"
10
11
  },
11
12
  "scripts": {
12
13
  "start": "node index.js",
@@ -1,14 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * postinstall.js - Attempt to install Python dependencies for tree-sitter AST engine.
4
- * If installation fails, the scanner gracefully falls back to regex-only mode.
3
+ * postinstall.js - Setup script for agent-security-scanner-mcp
4
+ * 1. Install Python dependencies for tree-sitter AST engine (optional)
5
+ * 2. Install and build code-review-agent dependencies (optional)
5
6
  */
6
- import { execFileSync } from "child_process";
7
+ import { execFileSync, execSync } from "child_process";
7
8
  import { join, dirname } from "path";
8
9
  import { fileURLToPath } from "url";
10
+ import { existsSync } from "fs";
9
11
 
10
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
11
- const requirementsPath = join(__dirname, "..", "requirements.txt");
13
+ const rootDir = join(__dirname, "..");
14
+ const requirementsPath = join(rootDir, "requirements.txt");
15
+ const codeReviewAgentDir = join(rootDir, "code-review-agent");
12
16
 
13
17
  // Check if Python 3 is available
14
18
  function findPython() {
@@ -33,6 +37,7 @@ function isTreeSitterInstalled(pythonCmd) {
33
37
  }
34
38
  }
35
39
 
40
+ // Setup Python dependencies
36
41
  const pythonCmd = findPython();
37
42
 
38
43
  if (!pythonCmd) {
@@ -58,3 +63,37 @@ if (!pythonCmd) {
58
63
  );
59
64
  }
60
65
  }
66
+
67
+ // Setup code-review-agent (LLM-powered semantic analysis)
68
+ if (existsSync(codeReviewAgentDir)) {
69
+ const distExists = existsSync(join(codeReviewAgentDir, "dist", "bin", "cr-agent.js"));
70
+
71
+ if (distExists) {
72
+ console.log("[postinstall] code-review-agent already built — cr-agent CLI available.");
73
+ } else {
74
+ console.log("[postinstall] Setting up code-review-agent (LLM-powered code review)...");
75
+ try {
76
+ // Install dependencies
77
+ execSync("npm install --omit=dev", {
78
+ cwd: codeReviewAgentDir,
79
+ timeout: 180000,
80
+ stdio: ["pipe", "pipe", "pipe"]
81
+ });
82
+
83
+ // Build TypeScript
84
+ execSync("npm run build", {
85
+ cwd: codeReviewAgentDir,
86
+ timeout: 60000,
87
+ stdio: ["pipe", "pipe", "pipe"]
88
+ });
89
+
90
+ console.log("[postinstall] code-review-agent installed — run: npx cr-agent --help");
91
+ } catch (err) {
92
+ console.log(
93
+ "[postinstall] Could not set up code-review-agent (optional LLM-powered review).\n" +
94
+ " The main scanner still works. To set up manually:\n" +
95
+ " cd node_modules/agent-security-scanner-mcp/code-review-agent && npm install && npm run build"
96
+ );
97
+ }
98
+ }
99
+ }
package/server.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
- "name": "io.github.sinewaveai/prooflayer-agent-security",
3
+ "name": "io.github.sinewaveai/agent-security-scanner-mcp",
4
4
  "description": "MCP security scanner with prompt injection firewall, package hallucination detection, LLM-powered code review, and auto-fix.",
5
5
  "version": "4.0.0",
6
6
  "transport": "stdio",
@@ -5,14 +5,14 @@
5
5
  import { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync } from 'fs';
6
6
  import { join } from 'path';
7
7
 
8
- const SCANNER_HOOK_MARKER = 'prooflayer-agent-security';
8
+ const SCANNER_HOOK_MARKER = 'agent-security-scanner-mcp';
9
9
 
10
10
  function buildHooksConfig(withPromptGuard) {
11
11
  const hooks = {
12
12
  'post-tool-use': [
13
13
  {
14
14
  matcher: 'Write|Edit|MultiEdit',
15
- command: `npx prooflayer-agent-security scan-security "$TOOL_INPUT_FILE_PATH" --verbosity minimal`,
15
+ command: `npx agent-security-scanner-mcp scan-security "$TOOL_INPUT_FILE_PATH" --verbosity minimal`,
16
16
  },
17
17
  ],
18
18
  };
@@ -21,7 +21,7 @@ function buildHooksConfig(withPromptGuard) {
21
21
  hooks['pre-tool-use'] = [
22
22
  {
23
23
  matcher: 'Bash',
24
- command: `npx prooflayer-agent-security scan-prompt "$TOOL_INPUT_COMMAND" --verbosity minimal`,
24
+ command: `npx agent-security-scanner-mcp scan-prompt "$TOOL_INPUT_COMMAND" --verbosity minimal`,
25
25
  },
26
26
  ];
27
27
  }
package/src/cli/init.js CHANGED
@@ -7,7 +7,7 @@ import { createInterface } from "readline";
7
7
 
8
8
  const MCP_SERVER_ENTRY = {
9
9
  command: "npx",
10
- args: ["-y", "prooflayer-agent-security"]
10
+ args: ["-y", "agent-security-scanner-mcp"]
11
11
  };
12
12
 
13
13
  function vscodeBase() {