@securityreviewai/securityreview-kit 0.1.39 → 0.1.40

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
@@ -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.40",
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 \`.claude/settings.json\`, the configured MCP server, 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(
@@ -637,6 +807,16 @@ export async function initCommand(options) {
637
807
  ' • CLI missing: install Claude Code and verify `claude --version`.',
638
808
  ),
639
809
  );
810
+ console.log(
811
+ chalk.dim(
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
+ );
640
820
  console.log(
641
821
  chalk.dim(
642
822
  ' • MCP missing: re-run init with Claude Code selected and MCP installation enabled so `.claude/settings.json` is written.',
@@ -39,6 +39,8 @@ From the repo root, non-interactive runs should execute with the project setting
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:
@@ -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 };
@@ -205,13 +240,15 @@ export function runProfilerAgent(
205
240
  projectName,
206
241
  apiUrl,
207
242
  apiToken,
243
+ modelOverride,
244
+ extraEnv,
208
245
  cursorTrust = true,
209
246
  streamProgress = false,
210
247
  showOutput = streamProgress,
211
248
  },
212
249
  ) {
213
250
  const prompt = buildProfilerAgentPrompt(projectName, target);
214
- const env = buildProfilerEnv(target, streamProgress);
251
+ const env = buildProfilerEnv(target, streamProgress, extraEnv);
215
252
  const opts = showOutput
216
253
  ? { cwd, stdio: 'inherit', env }
217
254
  : { cwd, stdio: ['ignore', 'pipe', 'pipe'], env, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 };
@@ -251,7 +288,7 @@ export function runProfilerAgent(
251
288
  return { ok: false, message: 'claude not on PATH' };
252
289
  }
253
290
  const settingsPath = join(cwd, '.claude', 'settings.json');
254
- const args = ['-p', '--settings', settingsPath, '--model', 'haiku'];
291
+ const args = ['-p', '--settings', settingsPath, '--model', modelOverride || 'haiku'];
255
292
  if (streamProgress) {
256
293
  args.push('--output-format', 'stream-json', '--include-partial-messages', '--include-hook-events', '--verbose');
257
294
  }