@securityreviewai/securityreview-kit 0.1.39 → 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` |
@@ -53,6 +53,14 @@ Options:
53
53
  --skip-rules Skip workspace rule installation
54
54
  --profile-repo Run the guardrails profiler after init
55
55
  --profiler-claude-login Run Claude Code login before profiling
56
+ --claude-auth-mode <mode>
57
+ Claude profiling auth mode: current, claudeai, console, api_key, gateway, bedrock, vertex, or setup_token
58
+ --claude-api-key <key> Anthropic API key for Claude profiling
59
+ --claude-base-url <url> Anthropic-compatible base URL for Claude profiling
60
+ --claude-auth-token <token>
61
+ Auth token for Claude profiling gateway mode
62
+ --claude-provider-model <model>
63
+ Optional Claude provider model override for gateway, Bedrock, or Vertex profiling
56
64
  --profiler-copilot-login
57
65
  Run GitHub Copilot CLI login before VS Code Copilot profiling
58
66
  --profiler-codex-login Run Codex login before Codex profiling
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@securityreviewai/securityreview-kit",
3
- "version": "0.1.39",
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",
package/src/cli.js CHANGED
@@ -41,6 +41,26 @@ export function run() {
41
41
  '--profiler-claude-login',
42
42
  'Before Claude Code profiling, run `claude auth login` in this terminal',
43
43
  )
44
+ .option(
45
+ '--claude-auth-mode <mode>',
46
+ 'Claude profiling auth mode: current, claudeai, console, api_key, gateway, bedrock, vertex, or setup_token',
47
+ )
48
+ .option(
49
+ '--claude-api-key <key>',
50
+ 'Anthropic API key for Claude profiling when using --claude-auth-mode api_key',
51
+ )
52
+ .option(
53
+ '--claude-base-url <url>',
54
+ 'Anthropic-compatible base URL for Claude profiling when using --claude-auth-mode gateway',
55
+ )
56
+ .option(
57
+ '--claude-auth-token <token>',
58
+ 'Auth token for Claude profiling when using --claude-auth-mode gateway',
59
+ )
60
+ .option(
61
+ '--claude-provider-model <model>',
62
+ 'Optional Claude provider model override for gateway, Bedrock, or Vertex profiling',
63
+ )
44
64
  .option(
45
65
  '--profiler-copilot-login',
46
66
  'Before VS Code Copilot profiling, run `copilot login` in this terminal',
@@ -1,12 +1,13 @@
1
1
  import chalk from 'chalk';
2
- import { input, checkbox, confirm, select } from '@inquirer/prompts';
2
+ import { input, checkbox, confirm, password, select } from '@inquirer/prompts';
3
3
  import { TARGETS, TARGET_NAMES } from '../utils/constants.js';
4
4
  import { detectTargets } from '../utils/detect.js';
5
5
  import { ensureIdeClisForTargets } from '../utils/ide-cli-install.js';
6
6
  import { writeGuardrailsSkillBundles } from '../utils/guardrails-profiler-bundle.js';
7
7
  import {
8
8
  pickProfilerAgentTarget,
9
- runClaudeLogin,
9
+ runClaudeAuthLogin,
10
+ runClaudeSetupToken,
10
11
  runCodexLogin,
11
12
  runCopilotLogin,
12
13
  runCursorAgentLogin,
@@ -53,6 +54,170 @@ function normalizeRuleResults(rawResult) {
53
54
  });
54
55
  }
55
56
 
57
+ async function resolveClaudeProfilerAuth(options, interactive, cwd) {
58
+ const providerModel = String(options.claudeProviderModel || process.env.ANTHROPIC_MODEL || '').trim();
59
+ let mode = String(options.claudeAuthMode || process.env.SECURITY_REVIEW_CLAUDE_AUTH_MODE || '').trim();
60
+
61
+ if (!mode && options.profilerClaudeLogin) {
62
+ mode = 'claudeai';
63
+ }
64
+
65
+ if (!mode && interactive) {
66
+ mode = await select({
67
+ message: 'How should Claude Code authenticate for this profiling run?',
68
+ default: 'current',
69
+ choices: [
70
+ { name: 'Use current Claude Code auth/environment', value: 'current' },
71
+ { name: 'Claude subscription login', value: 'claudeai' },
72
+ { name: 'Anthropic Console login', value: 'console' },
73
+ { name: 'Anthropic API key', value: 'api_key' },
74
+ { name: 'Anthropic-compatible gateway / proxy', value: 'gateway' },
75
+ { name: 'AWS Bedrock', value: 'bedrock' },
76
+ { name: 'Google Vertex AI', value: 'vertex' },
77
+ { name: 'Long-lived Claude token', value: 'setup_token' },
78
+ ],
79
+ });
80
+ }
81
+
82
+ if (!mode) {
83
+ mode = 'current';
84
+ }
85
+
86
+ const result = {
87
+ mode,
88
+ model: providerModel || 'haiku',
89
+ envOverrides: {},
90
+ loginRunner: null,
91
+ loginLabel: '',
92
+ summary: '',
93
+ };
94
+
95
+ if (mode === 'claudeai') {
96
+ result.loginRunner = () => runClaudeAuthLogin(cwd, { mode: 'claudeai' });
97
+ result.loginLabel = 'Claude subscription login';
98
+ result.summary = 'Claude subscription auth';
99
+ return result;
100
+ }
101
+
102
+ if (mode === 'console') {
103
+ result.loginRunner = () => runClaudeAuthLogin(cwd, { mode: 'console' });
104
+ result.loginLabel = 'Anthropic Console login';
105
+ result.summary = 'Anthropic Console auth';
106
+ return result;
107
+ }
108
+
109
+ if (mode === 'setup_token') {
110
+ result.loginRunner = () => runClaudeSetupToken(cwd);
111
+ result.loginLabel = 'Claude long-lived token setup';
112
+ result.summary = 'Claude subscription token auth';
113
+ return result;
114
+ }
115
+
116
+ if (mode === 'api_key') {
117
+ let apiKey = String(options.claudeApiKey || process.env.ANTHROPIC_API_KEY || '').trim();
118
+ if (!apiKey && interactive) {
119
+ apiKey = await password({
120
+ message: 'Anthropic API key for this profiling run:',
121
+ validate: (v) => (String(v || '').trim() ? true : 'API key is required'),
122
+ });
123
+ }
124
+ result.envOverrides = {
125
+ ANTHROPIC_API_KEY: apiKey,
126
+ ANTHROPIC_AUTH_TOKEN: null,
127
+ ANTHROPIC_BASE_URL: null,
128
+ CLAUDE_CODE_USE_BEDROCK: null,
129
+ CLAUDE_CODE_USE_VERTEX: null,
130
+ };
131
+ result.summary = 'Anthropic API key auth';
132
+ return result;
133
+ }
134
+
135
+ if (mode === 'gateway') {
136
+ let baseUrl = String(options.claudeBaseUrl || process.env.ANTHROPIC_BASE_URL || '').trim();
137
+ let authToken = String(options.claudeAuthToken || process.env.ANTHROPIC_AUTH_TOKEN || '').trim();
138
+ let model = providerModel;
139
+
140
+ if (interactive) {
141
+ if (!baseUrl) {
142
+ baseUrl = await input({
143
+ message: 'Anthropic-compatible base URL for this profiling run:',
144
+ validate: (v) => (String(v || '').trim() ? true : 'Base URL is required'),
145
+ });
146
+ }
147
+ if (!authToken) {
148
+ authToken = await password({
149
+ message: 'Gateway auth token for this profiling run:',
150
+ validate: (v) => (String(v || '').trim() ? true : 'Auth token is required'),
151
+ });
152
+ }
153
+ if (!providerModel) {
154
+ model = String(
155
+ await input({
156
+ message: 'Provider model override (leave blank to use haiku):',
157
+ default: '',
158
+ }),
159
+ ).trim();
160
+ }
161
+ }
162
+
163
+ result.model = model || 'haiku';
164
+ result.envOverrides = {
165
+ ANTHROPIC_BASE_URL: baseUrl,
166
+ ANTHROPIC_AUTH_TOKEN: authToken,
167
+ ANTHROPIC_API_KEY: null,
168
+ CLAUDE_CODE_USE_BEDROCK: null,
169
+ CLAUDE_CODE_USE_VERTEX: null,
170
+ };
171
+ result.summary = `Anthropic-compatible gateway (${baseUrl || 'configured base URL'})`;
172
+ return result;
173
+ }
174
+
175
+ if (mode === 'bedrock') {
176
+ let model = providerModel;
177
+ if (interactive && !providerModel) {
178
+ model = String(
179
+ await input({
180
+ message: 'Bedrock model override (leave blank to use haiku):',
181
+ default: '',
182
+ }),
183
+ ).trim();
184
+ }
185
+ result.model = model || 'haiku';
186
+ result.envOverrides = {
187
+ CLAUDE_CODE_USE_BEDROCK: 'true',
188
+ CLAUDE_CODE_USE_VERTEX: null,
189
+ ANTHROPIC_API_KEY: null,
190
+ ANTHROPIC_AUTH_TOKEN: null,
191
+ };
192
+ result.summary = 'AWS Bedrock credentials from your shell/environment';
193
+ return result;
194
+ }
195
+
196
+ if (mode === 'vertex') {
197
+ let model = providerModel;
198
+ if (interactive && !providerModel) {
199
+ model = String(
200
+ await input({
201
+ message: 'Vertex model override (leave blank to use haiku):',
202
+ default: '',
203
+ }),
204
+ ).trim();
205
+ }
206
+ result.model = model || 'haiku';
207
+ result.envOverrides = {
208
+ CLAUDE_CODE_USE_VERTEX: 'true',
209
+ CLAUDE_CODE_USE_BEDROCK: null,
210
+ ANTHROPIC_API_KEY: null,
211
+ ANTHROPIC_AUTH_TOKEN: null,
212
+ };
213
+ result.summary = 'Google Vertex AI credentials from your shell/environment';
214
+ return result;
215
+ }
216
+
217
+ result.summary = 'current Claude Code auth/environment';
218
+ return result;
219
+ }
220
+
56
221
  /**
57
222
  * Resolve environment variables from flags, env, or interactive prompt.
58
223
  */
@@ -418,6 +583,7 @@ export async function initCommand(options) {
418
583
  } else {
419
584
  console.log('');
420
585
  console.log(chalk.bold.white(` Starting profiler via ${TARGETS[agentTarget].name} CLI…`));
586
+ let claudeProfilerAuth = null;
421
587
  if (agentTarget === 'cursor') {
422
588
  console.log(
423
589
  chalk.dim(
@@ -488,31 +654,33 @@ export async function initCommand(options) {
488
654
  console.log('');
489
655
  }
490
656
  } else if (agentTarget === 'claude') {
657
+ claudeProfilerAuth = await resolveClaudeProfilerAuth(options, interactive, cwd);
491
658
  console.log(
492
659
  chalk.dim(
493
- ' Claude Code: profiling uses `.claude/settings.json`, the configured MCP server, and the Haiku 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.`,
494
661
  ),
495
662
  );
663
+ console.log(chalk.dim(` Auth mode: ${claudeProfilerAuth.summary}.`));
496
664
 
497
- let runLogin = Boolean(options.profilerClaudeLogin);
498
- if (!runLogin && interactive) {
665
+ const needsSetup = typeof claudeProfilerAuth.loginRunner === 'function';
666
+ let runLogin = needsSetup;
667
+ if (needsSetup && interactive && !options.claudeAuthMode && !options.profilerClaudeLogin) {
499
668
  runLogin = await confirm({
500
- message:
501
- 'Run Claude Code login in this terminal now? (Same init — profiling runs next. Choose No if already signed in.)',
669
+ message: `${claudeProfilerAuth.loginLabel} in this terminal now? (Same init — profiling runs next.)`,
502
670
  default: true,
503
671
  });
504
672
  }
505
- if (runLogin) {
673
+ if (runLogin && claudeProfilerAuth.loginRunner) {
506
674
  console.log('');
507
- console.log(chalk.bold.white(' Claude Code login'));
508
- console.log(chalk.dim(' Complete the browser prompt, then return here.\n'));
509
- const loginResult = runClaudeLogin(cwd);
675
+ console.log(chalk.bold.white(` ${claudeProfilerAuth.loginLabel}`));
676
+ console.log(chalk.dim(' Complete the prompt flow, then return here.\n'));
677
+ const loginResult = claudeProfilerAuth.loginRunner();
510
678
  if (loginResult.ok) {
511
- console.log(chalk.green(' \u2713 Claude Code login step finished.'));
679
+ console.log(chalk.green(` \u2713 ${claudeProfilerAuth.loginLabel} finished.`));
512
680
  } else {
513
681
  console.log(
514
682
  chalk.yellow(
515
- ` \u26a0 Claude login exited with status ${loginResult.status ?? 'unknown'}. Profiling will still be attempted; sign in and re-run init if it fails.`,
683
+ ` \u26a0 ${claudeProfilerAuth.loginLabel} exited with status ${loginResult.status ?? 'unknown'}. Profiling will still be attempted; configure auth and re-run init if it fails.`,
516
684
  ),
517
685
  );
518
686
  }
@@ -567,6 +735,8 @@ export async function initCommand(options) {
567
735
  projectName: projectNameForSkill,
568
736
  apiUrl: envVars?.apiUrl,
569
737
  apiToken: envVars?.apiToken,
738
+ modelOverride: agentTarget === 'claude' ? claudeProfilerAuth?.model : undefined,
739
+ extraEnv: agentTarget === 'claude' ? claudeProfilerAuth?.envOverrides : undefined,
570
740
  cursorTrust: !options.profilerNoTrust,
571
741
  streamProgress: profilerVerbose,
572
742
  showOutput: showProfilerOutput,
@@ -629,7 +799,7 @@ export async function initCommand(options) {
629
799
  console.log(chalk.dim(' Typical fixes:'));
630
800
  console.log(
631
801
  chalk.dim(
632
- ' • Not signed in: re-run `securityreview-kit init` and choose Yes for Claude Code login, or pass `--profiler-claude-login` with `--profile-repo`.',
802
+ ' • Choose the right auth mode: subscription, Console, API key, gateway, Bedrock, or Vertex.',
633
803
  ),
634
804
  );
635
805
  console.log(
@@ -639,7 +809,17 @@ export async function initCommand(options) {
639
809
  );
640
810
  console.log(
641
811
  chalk.dim(
642
- ' • MCP missing: re-run init with Claude Code selected and MCP installation enabled so `.claude/settings.json` is written.',
812
+ ' • API key / gateway: provide `--claude-api-key`, or `--claude-base-url` plus `--claude-auth-token`, or set the matching env vars before re-running init.',
813
+ ),
814
+ );
815
+ console.log(
816
+ chalk.dim(
817
+ ' • Bedrock / Vertex: make sure your cloud credentials are already available in this shell before profiling.',
818
+ ),
819
+ );
820
+ console.log(
821
+ chalk.dim(
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`.',
643
823
  ),
644
824
  );
645
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,12 +33,14 @@ 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
 
42
+ Claude profiling can also run with **Anthropic Console**, **ANTHROPIC_API_KEY**, an **Anthropic-compatible gateway** (`ANTHROPIC_BASE_URL` + `ANTHROPIC_AUTH_TOKEN`), or cloud-provider credentials such as **AWS Bedrock** and **Google Vertex AI**. `securityreview-kit init` can branch into those auth modes before profiling.
43
+
42
44
  ## GitHub Copilot CLI (scripted)
43
45
 
44
46
  From the repo root, non-interactive runs should load the SRAI MCP server and allow the tools needed to scan, write profile files, and call MCP:
@@ -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
  },
@@ -15,8 +15,15 @@ export function getProfilerLogPath(cwd, target) {
15
15
  return join(cwd, '.guardrails', 'logs', `profiler-${target}.log`);
16
16
  }
17
17
 
18
- function buildProfilerEnv(target, streamProgress) {
18
+ function buildProfilerEnv(target, streamProgress, extraEnv = {}) {
19
19
  const env = augmentPathEnv(process.env);
20
+ for (const [key, value] of Object.entries(extraEnv || {})) {
21
+ if (value == null || value === '') {
22
+ delete env[key];
23
+ } else {
24
+ env[key] = value;
25
+ }
26
+ }
20
27
  if (target === 'codex' && streamProgress && !env.RUST_LOG) {
21
28
  env.RUST_LOG = 'info';
22
29
  }
@@ -113,6 +120,34 @@ export function runCopilotLogin(cwd) {
113
120
  * Run Claude Code login in the current terminal.
114
121
  */
115
122
  export function runClaudeLogin(cwd) {
123
+ return runClaudeAuthLogin(cwd, { mode: 'claudeai' });
124
+ }
125
+
126
+ /**
127
+ * Run Claude Code auth login in the current terminal.
128
+ */
129
+ export function runClaudeAuthLogin(cwd, options = {}) {
130
+ const env = augmentPathEnv(process.env);
131
+ if (!commandOk('claude', ['--version'], env)) {
132
+ return {
133
+ ok: false,
134
+ status: null,
135
+ message: 'Claude Code CLI not found (`claude`). Install from https://claude.ai/code.',
136
+ };
137
+ }
138
+ const mode = options.mode === 'console' ? '--console' : '--claudeai';
139
+ const r = spawnSync('claude', ['auth', 'login', mode], { cwd, stdio: 'inherit', env });
140
+ const spawnErr = r.error ? r.error.message : null;
141
+ if (r.status === null && spawnErr) {
142
+ return { ok: false, status: null, message: spawnErr };
143
+ }
144
+ return { ok: r.status === 0, status: r.status, message: r.status !== 0 ? spawnErr : undefined };
145
+ }
146
+
147
+ /**
148
+ * Run Claude Code long-lived token setup in the current terminal.
149
+ */
150
+ export function runClaudeSetupToken(cwd) {
116
151
  const env = augmentPathEnv(process.env);
117
152
  if (!commandOk('claude', ['--version'], env)) {
118
153
  return {
@@ -121,7 +156,7 @@ export function runClaudeLogin(cwd) {
121
156
  message: 'Claude Code CLI not found (`claude`). Install from https://claude.ai/code.',
122
157
  };
123
158
  }
124
- const r = spawnSync('claude', ['auth', 'login', '--claudeai'], { cwd, stdio: 'inherit', env });
159
+ const r = spawnSync('claude', ['setup-token'], { cwd, stdio: 'inherit', env });
125
160
  const spawnErr = r.error ? r.error.message : null;
126
161
  if (r.status === null && spawnErr) {
127
162
  return { ok: false, status: null, message: spawnErr };
@@ -178,6 +213,21 @@ export function buildCodexConfigOverrides(cwd, overrides = {}) {
178
213
  ];
179
214
  }
180
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
+
181
231
  export function pickProfilerAgentTarget(targets) {
182
232
  for (const t of PREFERRED_ORDER) {
183
233
  if (targets.includes(t)) {
@@ -205,13 +255,15 @@ export function runProfilerAgent(
205
255
  projectName,
206
256
  apiUrl,
207
257
  apiToken,
258
+ modelOverride,
259
+ extraEnv,
208
260
  cursorTrust = true,
209
261
  streamProgress = false,
210
262
  showOutput = streamProgress,
211
263
  },
212
264
  ) {
213
265
  const prompt = buildProfilerAgentPrompt(projectName, target);
214
- const env = buildProfilerEnv(target, streamProgress);
266
+ const env = buildProfilerEnv(target, streamProgress, extraEnv);
215
267
  const opts = showOutput
216
268
  ? { cwd, stdio: 'inherit', env }
217
269
  : { cwd, stdio: ['ignore', 'pipe', 'pipe'], env, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 };
@@ -251,7 +303,26 @@ export function runProfilerAgent(
251
303
  return { ok: false, message: 'claude not on PATH' };
252
304
  }
253
305
  const settingsPath = join(cwd, '.claude', 'settings.json');
254
- const args = ['-p', '--settings', settingsPath, '--model', '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
+ ];
255
326
  if (streamProgress) {
256
327
  args.push('--output-format', 'stream-json', '--include-partial-messages', '--include-hook-events', '--verbose');
257
328
  }