@securityreviewai/securityreview-kit 0.1.40 → 0.1.41

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/README.md CHANGED
@@ -28,7 +28,7 @@ npx @securityreviewai/securityreview-kit init --switch-project
28
28
  | Target | Flag | MCP Config | Workspace Rule |
29
29
  |---|---|---|---|
30
30
  | Cursor | `cursor` | `.cursor/mcp.json` | `.cursor/rules/srai-security-review.mdc`, `.cursor/rules/ctm_sync_rule.mdc`, `.cursor/commands/ctm_sync.md`, `.cursor/agents/ctm_sync.md`, `.cursor/commands/create-ide-workflow.md`, `.cursor/commands/srai-profile.md`, `.cursor/skills/threat-modelling/SKILL.md` |
31
- | Claude Code | `claude` | `.claude/settings.json` | `CLAUDE.md`, `.claude/skills/threat-modelling/SKILL.md`, `.claude/skills/guardrails-profiler/SKILL.md`, `.claude/skills/guardrails-selection/SKILL.md`, `.claude/agents/ctm_sync.md`, `.claude/commands/guardrails-init-profile.md` |
31
+ | Claude Code | `claude` | `.mcp.json` | `.claude/CLAUDE.md`, `.claude/settings.json`, `.claude/skills/threat-modelling/SKILL.md`, `.claude/skills/guardrails-profiler/SKILL.md`, `.claude/skills/guardrails-selection/SKILL.md`, `.claude/agents/ctm_sync.md`, `.claude/commands/guardrails-init-profile.md` |
32
32
  | VS Code Copilot | `vscode` | `.vscode/mcp.json` | `.github/copilot-instructions.md`, `.github/skills/threat-modelling/SKILL.md`, `.github/skills/guardrails-profiler/SKILL.md`, `.github/skills/guardrails-selection/SKILL.md`, `.github/agents/ctm_sync.agent.md`, `.github/hooks/srai-session-policy.json` |
33
33
  | Windsurf | `windsurf` | `.windsurf/mcp_config.json` | `.windsurf/rules/srai-security-review.md` |
34
34
  | Codex | `codex` | `.codex/config.toml` | `.codex/AGENTS.md`, `.codex/skills/threat-modelling/SKILL.md`, `.codex/skills/guardrails-profiler/SKILL.md`, `.codex/skills/guardrails-selection/SKILL.md`, `.codex/agents/ctm_sync.toml`, `.codex/hooks.json`, `.codex/commands/guardrails-init-profile.md` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@securityreviewai/securityreview-kit",
3
- "version": "0.1.40",
3
+ "version": "0.1.41",
4
4
  "description": "Bootstrap security-review-mcp for AI IDEs and CLI tools",
5
5
  "author": "Debarshi Das <debarshi.das@we45.com>",
6
6
  "license": "UNLICENSED",
@@ -657,7 +657,7 @@ export async function initCommand(options) {
657
657
  claudeProfilerAuth = await resolveClaudeProfilerAuth(options, interactive, cwd);
658
658
  console.log(
659
659
  chalk.dim(
660
- ` Claude Code: profiling uses \`.claude/settings.json\`, the configured MCP server, and \`${claudeProfilerAuth.model}\` for this run.`,
660
+ ` Claude Code: profiling uses \`.mcp.json\`, \`.claude/settings.json\`, bypassed tool prompts for this profiling pass, and \`${claudeProfilerAuth.model}\` for this run.`,
661
661
  ),
662
662
  );
663
663
  console.log(chalk.dim(` Auth mode: ${claudeProfilerAuth.summary}.`));
@@ -819,7 +819,7 @@ export async function initCommand(options) {
819
819
  );
820
820
  console.log(
821
821
  chalk.dim(
822
- ' • MCP missing: re-run init with Claude Code selected and MCP installation enabled so `.claude/settings.json` is written.',
822
+ ' • MCP missing: re-run init with Claude Code selected and MCP installation enabled so `.mcp.json` is written and `.claude/settings.json` enables `security-review-mcp`.',
823
823
  ),
824
824
  );
825
825
  } else if (agentTarget === 'codex') {
@@ -29,17 +29,17 @@ function getClaudeSessionStartHooks() {
29
29
  }
30
30
 
31
31
  /**
32
- * Generate Claude Code MCP config at .claude/settings.json
32
+ * Generate Claude Code project MCP config at .mcp.json and project settings at .claude/settings.json
33
33
  */
34
34
  export function generate(cwd, envVars) {
35
- const filePath = join(cwd, '.claude', 'settings.json');
36
- const existing = readJson(filePath) || {};
35
+ const mcpPath = join(cwd, '.mcp.json');
36
+ const mcpConfig = readJson(mcpPath) || {};
37
37
 
38
- if (!existing.mcpServers) {
39
- existing.mcpServers = {};
38
+ if (!mcpConfig.mcpServers) {
39
+ mcpConfig.mcpServers = {};
40
40
  }
41
41
 
42
- existing.mcpServers[MCP_SERVER_NAME] = {
42
+ mcpConfig.mcpServers[MCP_SERVER_NAME] = {
43
43
  command: 'npx',
44
44
  args: ['-y', `${MCP_SERVER_PACKAGE}@latest`],
45
45
  env: {
@@ -48,6 +48,17 @@ export function generate(cwd, envVars) {
48
48
  },
49
49
  };
50
50
 
51
+ writeJson(mcpPath, mcpConfig);
52
+
53
+ const settingsPath = join(cwd, '.claude', 'settings.json');
54
+ const existing = readJson(settingsPath) || {};
55
+ const enabledServers = Array.isArray(existing.enabledMcpjsonServers)
56
+ ? existing.enabledMcpjsonServers.filter((name) => typeof name === 'string' && name.trim())
57
+ : [];
58
+ if (!enabledServers.includes(MCP_SERVER_NAME)) {
59
+ existing.enabledMcpjsonServers = [...enabledServers, MCP_SERVER_NAME];
60
+ }
61
+
51
62
  const existingSessionStart = Array.isArray(existing.SessionStart) ? existing.SessionStart : [];
52
63
  const marker = 'MANDATORY SECURITY GATE (Claude Code Session Policy)';
53
64
  const ours = getClaudeSessionStartHooks();
@@ -57,6 +68,6 @@ export function generate(cwd, envVars) {
57
68
  );
58
69
  existing.SessionStart = hasOurs ? existingSessionStart : [...existingSessionStart, ...ours];
59
70
 
60
- writeJson(filePath, existing);
61
- return filePath;
71
+ writeJson(settingsPath, existing);
72
+ return mcpPath;
62
73
  }
@@ -5,7 +5,7 @@ import { test } from 'node:test';
5
5
  import assert from 'node:assert/strict';
6
6
  import { generate } from './claude.js';
7
7
 
8
- test('Claude MCP generator writes mcp server and session hooks', () => {
8
+ test('Claude MCP generator writes .mcp.json, enables the project MCP server, and adds session hooks', () => {
9
9
  const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-claude-mcp-'));
10
10
 
11
11
  generate(cwd, {
@@ -13,10 +13,12 @@ test('Claude MCP generator writes mcp server and session hooks', () => {
13
13
  apiToken: 'secret-token',
14
14
  });
15
15
 
16
- const config = JSON.parse(readFileSync(join(cwd, '.claude', 'settings.json'), 'utf8'));
17
- assert.equal(config.mcpServers['security-review-mcp'].command, 'npx');
18
- assert.equal(Array.isArray(config.SessionStart), true);
19
- assert.match(config.SessionStart[0].hooks[0].prompt, /MANDATORY SECURITY GATE/);
16
+ const mcpConfig = JSON.parse(readFileSync(join(cwd, '.mcp.json'), 'utf8'));
17
+ const settings = JSON.parse(readFileSync(join(cwd, '.claude', 'settings.json'), 'utf8'));
18
+ assert.equal(mcpConfig.mcpServers['security-review-mcp'].command, 'npx');
19
+ assert.deepEqual(settings.enabledMcpjsonServers, ['security-review-mcp']);
20
+ assert.equal(Array.isArray(settings.SessionStart), true);
21
+ assert.match(settings.SessionStart[0].hooks[0].prompt, /MANDATORY SECURITY GATE/);
20
22
  });
21
23
 
22
24
  test('Claude MCP generator preserves existing SessionStart hooks', () => {
@@ -50,4 +52,5 @@ test('Claude MCP generator preserves existing SessionStart hooks', () => {
50
52
  assert.equal(config.SessionStart.length, 2);
51
53
  assert.match(config.SessionStart[0].hooks[0].prompt, /Existing hook/);
52
54
  assert.match(config.SessionStart[1].hooks[0].prompt, /MANDATORY SECURITY GATE/);
55
+ assert.deepEqual(config.enabledMcpjsonServers, ['security-review-mcp']);
53
56
  });
@@ -1,12 +1,14 @@
1
- import { existsSync } from 'node:fs';
1
+ import { existsSync, unlinkSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import {
4
4
  CTM_SYNC_AGENT_REL_PATH,
5
5
  GUARDRAILS_PROFILER_SKILL_REL_DIR,
6
6
  GUARDRAILS_SELECTION_SKILL_REL_DIR,
7
+ SENTINEL_END,
8
+ SENTINEL_START,
7
9
  THREAT_MODELLING_SKILL_REL_DIR,
8
10
  } from '../../utils/constants.js';
9
- import { upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
11
+ import { readText, upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
10
12
  import {
11
13
  getCtmSyncWorkflowContent,
12
14
  getGuardrailsInitProfileContent,
@@ -40,7 +42,7 @@ function getClaudeCtmSyncAgentContent(options = {}) {
40
42
  }
41
43
 
42
44
  /**
43
- * Generate Claude Code workspace rule — appends to CLAUDE.md
45
+ * Generate Claude Code workspace rule — appends to .claude/CLAUDE.md
44
46
  */
45
47
  export function generate(cwd, options = {}) {
46
48
  const optionsWithSkillDirs = {
@@ -49,10 +51,25 @@ export function generate(cwd, options = {}) {
49
51
  threatModellingSkillDir: THREAT_MODELLING_SKILL_REL_DIR.claude,
50
52
  ctmSyncAgentPath: CTM_SYNC_AGENT_REL_PATH.claude,
51
53
  };
52
- const filePath = join(cwd, 'CLAUDE.md');
54
+ const legacyRootPath = join(cwd, 'CLAUDE.md');
55
+ const filePath = join(cwd, '.claude', 'CLAUDE.md');
53
56
  const content = getRuleContent(optionsWithSkillDirs);
54
57
  const action = upsertSentinelBlock(filePath, content);
55
58
 
59
+ const legacyContent = readText(legacyRootPath);
60
+ if (legacyContent.includes(SENTINEL_START) && legacyContent.includes(SENTINEL_END)) {
61
+ const startIdx = legacyContent.indexOf(SENTINEL_START);
62
+ const endIdx = legacyContent.indexOf(SENTINEL_END);
63
+ const before = legacyContent.substring(0, startIdx);
64
+ const after = legacyContent.substring(endIdx + SENTINEL_END.length);
65
+ const migrated = `${before}${after}`.trim();
66
+ if (migrated) {
67
+ writeText(legacyRootPath, migrated + '\n');
68
+ } else if (existsSync(legacyRootPath)) {
69
+ unlinkSync(legacyRootPath);
70
+ }
71
+ }
72
+
56
73
  const threatSkillPath = join(cwd, THREAT_MODELLING_SKILL_REL_DIR.claude, 'SKILL.md');
57
74
  const threatSkill = writeGeneratedText(
58
75
  threatSkillPath,
@@ -1,17 +1,17 @@
1
- import { existsSync, mkdtempSync, readFileSync } from 'node:fs';
1
+ import { existsSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { test } from 'node:test';
5
5
  import assert from 'node:assert/strict';
6
6
  import { generate } from './claude.js';
7
7
 
8
- test('Claude generator writes CLAUDE.md, threat skill, ctm_sync agent, and profiling command', () => {
8
+ test('Claude generator writes .claude/CLAUDE.md, threat skill, ctm_sync agent, and profiling command', () => {
9
9
  const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-claude-'));
10
10
 
11
11
  const results = generate(cwd, { projectName: 'SmokeProject' });
12
12
 
13
13
  const expectedPaths = [
14
- 'CLAUDE.md',
14
+ '.claude/CLAUDE.md',
15
15
  '.claude/skills/threat-modelling/SKILL.md',
16
16
  '.claude/agents/ctm_sync.md',
17
17
  '.claude/commands/guardrails-init-profile.md',
@@ -23,7 +23,7 @@ test('Claude generator writes CLAUDE.md, threat skill, ctm_sync agent, and profi
23
23
 
24
24
  assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', 'agent', 'command']);
25
25
 
26
- const instructions = readFileSync(join(cwd, 'CLAUDE.md'), 'utf8');
26
+ const instructions = readFileSync(join(cwd, '.claude/CLAUDE.md'), 'utf8');
27
27
  assert.match(instructions, /\.claude\/skills\/guardrails-selection\/SKILL\.md/);
28
28
  assert.match(instructions, /\.claude\/skills\/threat-modelling\/SKILL\.md/);
29
29
  assert.match(instructions, /\.claude\/agents\/ctm_sync\.md/);
@@ -37,3 +37,19 @@ test('Claude generator writes CLAUDE.md, threat skill, ctm_sync agent, and profi
37
37
  assert.match(agent, /model: inherit/);
38
38
  assert.match(agent, /Configured SRAI project name: `SmokeProject`/);
39
39
  });
40
+
41
+ test('Claude generator migrates the kit-managed root CLAUDE.md block into .claude/CLAUDE.md', () => {
42
+ const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-claude-migrate-'));
43
+ const legacyPath = join(cwd, 'CLAUDE.md');
44
+
45
+ writeFileSync(
46
+ legacyPath,
47
+ '<!-- securityreview-kit:start -->\nold managed block\n<!-- securityreview-kit:end -->\n',
48
+ 'utf8',
49
+ );
50
+
51
+ generate(cwd, { projectName: 'SmokeProject' });
52
+
53
+ assert.equal(existsSync(join(cwd, '.claude/CLAUDE.md')), true);
54
+ assert.equal(existsSync(legacyPath), false);
55
+ });
@@ -33,9 +33,9 @@ You can still sign in manually with `agent login` (or `cursor-agent login`). To
33
33
 
34
34
  ## Claude Code CLI (scripted)
35
35
 
36
- From the repo root, non-interactive runs should execute with the project settings file and the Haiku model for this profiling pass:
36
+ From the repo root, non-interactive runs should execute with the project settings file, the project `.mcp.json` server config, explicit MCP-only loading, bypassed tool prompts for the profiling pass, and the Haiku model:
37
37
 
38
- `claude -p "<your profiling instructions>" --settings .claude/settings.json --model haiku`
38
+ `claude -p "<your profiling instructions>" --settings .claude/settings.json --mcp-config "$(cat .mcp.json)" --strict-mcp-config --permission-mode bypassPermissions --model haiku`
39
39
 
40
40
  During `securityreview-kit init`, choose **Yes** when asked to run Claude Code login, or pass **`--profiler-claude-login`** with **`--profile-repo`** so `claude auth login` and profiling stay in one run.
41
41
 
@@ -17,8 +17,8 @@ export const TARGETS = {
17
17
  },
18
18
  claude: {
19
19
  name: 'Claude Code',
20
- mcpConfigPath: '.claude/settings.json',
21
- rulePath: 'CLAUDE.md',
20
+ mcpConfigPath: '.mcp.json',
21
+ rulePath: '.claude/CLAUDE.md',
22
22
  ruleMode: 'append',
23
23
  detectDirs: ['.claude'],
24
24
  },
@@ -213,6 +213,21 @@ export function buildCodexConfigOverrides(cwd, overrides = {}) {
213
213
  ];
214
214
  }
215
215
 
216
+ export function buildClaudeAdditionalMcpConfig(cwd) {
217
+ const projectMcp = readJson(join(cwd, '.mcp.json'));
218
+ const sourceServer = projectMcp?.mcpServers?.[MCP_SERVER_NAME];
219
+
220
+ if (!sourceServer || typeof sourceServer !== 'object') {
221
+ return null;
222
+ }
223
+
224
+ return JSON.stringify({
225
+ mcpServers: {
226
+ [MCP_SERVER_NAME]: sourceServer,
227
+ },
228
+ });
229
+ }
230
+
216
231
  export function pickProfilerAgentTarget(targets) {
217
232
  for (const t of PREFERRED_ORDER) {
218
233
  if (targets.includes(t)) {
@@ -288,7 +303,26 @@ export function runProfilerAgent(
288
303
  return { ok: false, message: 'claude not on PATH' };
289
304
  }
290
305
  const settingsPath = join(cwd, '.claude', 'settings.json');
291
- const args = ['-p', '--settings', settingsPath, '--model', modelOverride || 'haiku'];
306
+ const mcpConfig = buildClaudeAdditionalMcpConfig(cwd);
307
+ if (!mcpConfig) {
308
+ return {
309
+ ok: false,
310
+ message:
311
+ 'Claude profiling needs the SRAI MCP server in .mcp.json. Re-run init with MCP installation enabled.',
312
+ };
313
+ }
314
+ const args = [
315
+ '-p',
316
+ '--settings',
317
+ settingsPath,
318
+ '--mcp-config',
319
+ mcpConfig,
320
+ '--strict-mcp-config',
321
+ '--permission-mode',
322
+ 'bypassPermissions',
323
+ '--model',
324
+ modelOverride || 'haiku',
325
+ ];
292
326
  if (streamProgress) {
293
327
  args.push('--output-format', 'stream-json', '--include-partial-messages', '--include-hook-events', '--verbose');
294
328
  }