@securityreviewai/securityreview-kit 0.1.23 → 0.1.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@securityreviewai/securityreview-kit",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
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
@@ -29,6 +29,14 @@ export function run() {
29
29
  .option('--skip-ide-cli-install', 'Do not install Cursor / Claude Code / Codex CLIs when those targets are selected')
30
30
  .option('--profile-repo', 'After init, run the guardrails profiler agent (non-interactive; needs cursor, claude, or codex target)')
31
31
  .option('--no-profile-repo', 'Skip the optional profiler agent step after init')
32
+ .option(
33
+ '--profiler-no-trust',
34
+ 'When profiling with Cursor, do not pass --trust (use if you need interactive workspace trust or login in the terminal)',
35
+ )
36
+ .option(
37
+ '--profiler-cursor-login',
38
+ 'Before Cursor profiling, run cursor-agent login in this terminal (then profiling runs in the same init)',
39
+ )
32
40
  .action(async (options) => {
33
41
  try {
34
42
  if (options.switchProject) {
@@ -3,8 +3,12 @@ import { input, checkbox, confirm, 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
- import { writeGuardrailsProfilerBundle } from '../utils/guardrails-profiler-bundle.js';
7
- import { pickProfilerAgentTarget, runProfilerAgent } from '../utils/profiler-agent.js';
6
+ import { writeGuardrailsProfilerBundles } from '../utils/guardrails-profiler-bundle.js';
7
+ import {
8
+ pickProfilerAgentTarget,
9
+ runCursorAgentLogin,
10
+ runProfilerAgent,
11
+ } from '../utils/profiler-agent.js';
8
12
  import { fetchVibeReviewProjectNames, getStoredCredentials, normalizeApiUrl } from '../utils/srai.js';
9
13
 
10
14
  // Dynamic imports for generators (avoids loading all at startup)
@@ -346,11 +350,23 @@ export async function initCommand(options) {
346
350
 
347
351
  if (installRules) {
348
352
  try {
349
- const bundlePath = writeGuardrailsProfilerBundle(cwd, { projectName: projectNameForSkill });
350
- console.log(chalk.green(` \u2713 Guardrails profiler bundle → ${bundlePath}`));
351
- results.push({ target: 'kit', type: 'bundle', status: 'ok', path: bundlePath });
353
+ const bundlePaths = writeGuardrailsProfilerBundles(cwd, {
354
+ projectName: projectNameForSkill,
355
+ targets,
356
+ });
357
+ if (bundlePaths.length === 0) {
358
+ console.log(
359
+ chalk.dim(
360
+ ' (Guardrails profiler skill is only written for Cursor, Claude Code, and Codex targets.)',
361
+ ),
362
+ );
363
+ }
364
+ for (const bundlePath of bundlePaths) {
365
+ console.log(chalk.green(` \u2713 Guardrails profiler skill \u2192 ${bundlePath}`));
366
+ results.push({ target: 'kit', type: 'bundle', status: 'ok', path: bundlePath });
367
+ }
352
368
  } catch (err) {
353
- console.log(chalk.red(` \u2717 Guardrails profiler bundle failed: ${err.message}`));
369
+ console.log(chalk.red(` \u2717 Guardrails profiler skill failed: ${err.message}`));
354
370
  results.push({ target: 'kit', type: 'bundle', status: 'error', error: err.message });
355
371
  }
356
372
  }
@@ -397,8 +413,53 @@ export async function initCommand(options) {
397
413
  } else {
398
414
  console.log('');
399
415
  console.log(chalk.bold.white(` Starting profiler via ${TARGETS[agentTarget].name} CLI…`));
400
- console.log(chalk.dim(' (Sign-in or approvals may be required in your terminal.)\n'));
401
- const pr = runProfilerAgent(cwd, { target: agentTarget, projectName: projectNameForSkill });
416
+ if (agentTarget === 'cursor') {
417
+ console.log(
418
+ chalk.dim(
419
+ ' Cursor: headless profiling uses `--trust` and `--approve-mcps` on this folder (you confirmed above).',
420
+ ),
421
+ );
422
+ if (options.profilerNoTrust) {
423
+ console.log(
424
+ chalk.dim(
425
+ ' You passed `--profiler-no-trust`: complete any login or workspace-trust prompts in this terminal.',
426
+ ),
427
+ );
428
+ }
429
+
430
+ let runLogin = Boolean(options.profilerCursorLogin);
431
+ if (!runLogin && interactive) {
432
+ runLogin = await confirm({
433
+ message:
434
+ 'Run Cursor Agent login in this terminal now? (Same init — profiling runs next. Choose No if already signed in.)',
435
+ default: true,
436
+ });
437
+ }
438
+ if (runLogin) {
439
+ console.log('');
440
+ console.log(chalk.bold.white(' Cursor Agent login'));
441
+ console.log(chalk.dim(' Complete the browser or code prompt, then return here.\n'));
442
+ const loginResult = runCursorAgentLogin(cwd);
443
+ if (loginResult.ok) {
444
+ console.log(chalk.green(' \u2713 Cursor Agent login step finished.'));
445
+ } else {
446
+ console.log(
447
+ chalk.yellow(
448
+ ` \u26a0 Login exited with status ${loginResult.status ?? 'unknown'}. Profiling will still be attempted; sign in and re-run init if it fails.`,
449
+ ),
450
+ );
451
+ }
452
+ console.log('');
453
+ }
454
+ } else {
455
+ console.log(chalk.dim(' (Sign-in or approvals may be required in your terminal.)'));
456
+ }
457
+ console.log('');
458
+ const pr = runProfilerAgent(cwd, {
459
+ target: agentTarget,
460
+ projectName: projectNameForSkill,
461
+ cursorTrust: !options.profilerNoTrust,
462
+ });
402
463
  if (pr.ok) {
403
464
  console.log(chalk.green(' \u2713 Profiler agent finished.'));
404
465
  } else {
@@ -410,6 +471,20 @@ export async function initCommand(options) {
410
471
  ` \u26a0 Profiler agent exited with an error: ${detail}. You can run the guardrails-init-profile workflow manually.`,
411
472
  ),
412
473
  );
474
+ if (agentTarget === 'cursor') {
475
+ console.log('');
476
+ console.log(chalk.dim(' Typical fixes:'));
477
+ console.log(
478
+ chalk.dim(
479
+ ' • Not signed in: re-run `securityreview-kit init` and choose Yes for “Run Cursor Agent login”, or pass `--profiler-cursor-login` with `--profile-repo`.',
480
+ ),
481
+ );
482
+ console.log(
483
+ chalk.dim(
484
+ ' • Want interactive trust instead of `--trust`: run `securityreview-kit init ... --profiler-no-trust` and answer the prompts, then profile again.',
485
+ ),
486
+ );
487
+ }
413
488
  }
414
489
  }
415
490
  }
@@ -1,4 +1,5 @@
1
1
  import { join } from 'node:path';
2
+ import { GUARDRAILS_PROFILER_SKILL_REL_DIR } from '../../utils/constants.js';
2
3
  import { upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
3
4
  import { getRuleContent, getGuardrailsInitProfileContent } from './content.js';
4
5
 
@@ -11,7 +12,10 @@ export function generate(cwd, options = {}) {
11
12
  const action = upsertSentinelBlock(filePath, content);
12
13
 
13
14
  const guardrailsInitPath = join(cwd, '.claude', 'commands', 'guardrails-init-profile.md');
14
- const guardrailsInitContent = getGuardrailsInitProfileContent(options);
15
+ const guardrailsInitContent = getGuardrailsInitProfileContent({
16
+ ...options,
17
+ guardrailsSkillDir: GUARDRAILS_PROFILER_SKILL_REL_DIR.claude,
18
+ });
15
19
  writeText(guardrailsInitPath, guardrailsInitContent);
16
20
 
17
21
  return [
@@ -1,4 +1,5 @@
1
1
  import { join } from 'node:path';
2
+ import { GUARDRAILS_PROFILER_SKILL_REL_DIR } from '../../utils/constants.js';
2
3
  import { upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
3
4
  import { getRuleContent, getGuardrailsInitProfileContent } from './content.js';
4
5
 
@@ -11,7 +12,10 @@ export function generate(cwd, options = {}) {
11
12
  const action = upsertSentinelBlock(filePath, content);
12
13
 
13
14
  const guardrailsInitPath = join(cwd, '.codex', 'commands', 'guardrails-init-profile.md');
14
- const guardrailsInitContent = getGuardrailsInitProfileContent(options);
15
+ const guardrailsInitContent = getGuardrailsInitProfileContent({
16
+ ...options,
17
+ guardrailsSkillDir: GUARDRAILS_PROFILER_SKILL_REL_DIR.codex,
18
+ });
15
19
  writeText(guardrailsInitPath, guardrailsInitContent);
16
20
 
17
21
  return [
@@ -17,9 +17,19 @@ function readTemplate(templateFileName, options = {}) {
17
17
  const projectName = sanitizeProjectName(rawProjectName);
18
18
  const resolvedProjectName = projectName || '<SRAI_PROJECT_NAME>';
19
19
 
20
- return template
20
+ let out = template
21
21
  .replaceAll('{{SRAI_PROJECT_NAME}}', resolvedProjectName)
22
22
  .replaceAll('<SRAI_PROJECT_NAME>', resolvedProjectName);
23
+
24
+ if (out.includes('{{GUARDRAILS_SKILL_DIR}}')) {
25
+ const skillDir =
26
+ typeof options.guardrailsSkillDir === 'string' && options.guardrailsSkillDir.trim()
27
+ ? options.guardrailsSkillDir.trim()
28
+ : '.cursor/skills/guardrails-profiler';
29
+ out = out.replaceAll('{{GUARDRAILS_SKILL_DIR}}', skillDir);
30
+ }
31
+
32
+ return out;
23
33
  }
24
34
 
25
35
  /**
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { writeText } from '../../utils/fs-helpers.js';
4
+ import { GUARDRAILS_PROFILER_SKILL_REL_DIR } from '../../utils/constants.js';
4
5
  import {
5
6
  getRuleContent,
6
7
  getProfileCommandContent,
@@ -55,7 +56,10 @@ export function generate(cwd, options = {}) {
55
56
  const ctmSyncWorkflowContent = getCtmSyncWorkflowContent(options);
56
57
  const createIdeWorkflowCommandContent = getCreateIdeWorkflowCommandContent(options);
57
58
  const profileCommandContent = getProfileCommandContent(options);
58
- const guardrailsInitProfileCommandContent = getGuardrailsInitProfileContent(options);
59
+ const guardrailsInitProfileCommandContent = getGuardrailsInitProfileContent({
60
+ ...options,
61
+ guardrailsSkillDir: GUARDRAILS_PROFILER_SKILL_REL_DIR.cursor,
62
+ });
59
63
  const skillContent = getThreatModellingSkillContent(options);
60
64
 
61
65
  const baseRule = writeCursorRule(
@@ -5,14 +5,24 @@ description: Run the Security Review Kit guardrails profiler — scan the repo,
5
5
 
6
6
  # Guardrails init profile
7
7
 
8
- Execute the workflow defined in **`.securityreview-kit/guardrails-profiler/SKILL.md`** end-to-end in this workspace.
8
+ Execute the workflow defined in **`{{GUARDRAILS_SKILL_DIR}}/SKILL.md`** end-to-end in this workspace (this IDE’s copy of the guardrails-profiler skill).
9
9
 
10
10
  Configured SRAI project name: `<SRAI_PROJECT_NAME>`
11
11
 
12
12
  **You must:**
13
13
 
14
- 1. Read `.securityreview-kit/guardrails-profiler/SKILL.md` and follow every step (use the signal registry at `.securityreview-kit/guardrails-profiler/references/signal-registry.json`).
14
+ 1. Read `{{GUARDRAILS_SKILL_DIR}}/SKILL.md` and follow every step (use the signal registry at `{{GUARDRAILS_SKILL_DIR}}/references/signal-registry.json`).
15
15
  2. Write `.guardrails/profile.json` and **`profile.json`** at the project root as specified.
16
16
  3. Call **`update_vibe_profile`** and **`write_default_pack`** on `security-review-mcp` after resolving `project_id` for `<SRAI_PROJECT_NAME>`.
17
17
 
18
18
  Do not skip MCP upload when credentials and MCP are available.
19
+
20
+ ## Cursor CLI (scripted)
21
+
22
+ From the repo root, non-interactive runs should include workspace trust and MCP approval (matches `securityreview-kit init`):
23
+
24
+ `cursor-agent -p "<your profiling instructions>" --trust --approve-mcps`
25
+
26
+ During `securityreview-kit init`, choose **Yes** when asked to run Cursor login in-terminal, or pass **`--profiler-cursor-login`** with **`--profile-repo`** so login and profiling stay in one run.
27
+
28
+ You can still sign in manually with `cursor-agent login`. To handle trust/login interactively in the terminal, omit `--trust` and `--approve-mcps`.
@@ -11,7 +11,8 @@ Configured SRAI project name: `<SRAI_PROJECT_NAME>`
11
11
 
12
12
  ## Canonical paths
13
13
 
14
- - **Signal registry (read-only):** `.securityreview-kit/guardrails-profiler/references/signal-registry.json`
14
+ - **This skill & signal registry (read-only):** `<GUARDRAILS_SKILL_DIR>/` — e.g. `.cursor/skills/guardrails-profiler`, `.claude/skills/guardrails-profiler`, or `.codex/skills/guardrails-profiler` depending on where this file was installed.
15
+ - **Signal registry file:** `<GUARDRAILS_SKILL_DIR>/references/signal-registry.json`
15
16
  - **Local guardrails file:** `.guardrails/profile.json`
16
17
  - **Combined manifest (project root):** `profile.json` — includes guardrails profile, vibe profile fields for MCP, and default pack payload
17
18
 
@@ -38,7 +39,7 @@ The project root is the current working directory. Confirm with markers such as
38
39
 
39
40
  ### Step 2: Read the Signal Registry
40
41
 
41
- Read `.securityreview-kit/guardrails-profiler/references/signal-registry.json`. Use its categories (`universal`, `languages`, `frameworks`, `auth_identity`, `ai_agent`, `infrastructure`, `ci_cd`, `cloud_compute`, `databases`, `messaging`, `api_protocols`, etc.) to map detected signals to guardrail pack IDs.
42
+ Read `<GUARDRAILS_SKILL_DIR>/references/signal-registry.json` (the copy next to this `SKILL.md`). Use its categories (`universal`, `languages`, `frameworks`, `auth_identity`, `ai_agent`, `infrastructure`, `ci_cd`, `cloud_compute`, `databases`, `messaging`, `api_protocols`, etc.) to map detected signals to guardrail pack IDs.
42
43
 
43
44
  ### Step 3: Scan for Signals
44
45
 
@@ -59,5 +59,12 @@ export const TARGETS = {
59
59
 
60
60
  export const TARGET_NAMES = Object.keys(TARGETS);
61
61
 
62
+ /** Relative workspace dirs for the guardrails-profiler skill (per IDE / CLI). */
63
+ export const GUARDRAILS_PROFILER_SKILL_REL_DIR = {
64
+ cursor: '.cursor/skills/guardrails-profiler',
65
+ claude: '.claude/skills/guardrails-profiler',
66
+ codex: '.codex/skills/guardrails-profiler',
67
+ };
68
+
62
69
  export const SENTINEL_START = '<!-- securityreview-kit:start -->';
63
70
  export const SENTINEL_END = '<!-- securityreview-kit:end -->';
@@ -1,6 +1,7 @@
1
1
  import { copyFileSync, readFileSync } from 'node:fs';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
+ import { GUARDRAILS_PROFILER_SKILL_REL_DIR } from './constants.js';
4
5
  import { ensureDir, writeText } from './fs-helpers.js';
5
6
 
6
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -14,20 +15,43 @@ function injectProjectName(content, projectName) {
14
15
  return content.replaceAll('{{SRAI_PROJECT_NAME}}', resolved).replaceAll('<SRAI_PROJECT_NAME>', resolved);
15
16
  }
16
17
 
17
- const BUNDLE_ROOT = join(__dirname, '..', 'generators', 'rules', 'guardrails-profiler');
18
-
19
- /**
20
- * Writes `.securityreview-kit/guardrails-profiler/` (SKILL + signal registry) into the target workspace.
21
- */
22
- export function writeGuardrailsProfilerBundle(cwd, options = {}) {
23
- const destBase = join(cwd, '.securityreview-kit', 'guardrails-profiler');
24
- const destRefs = join(destBase, 'references');
25
- ensureDir(destRefs);
18
+ function injectSkillDir(content, skillDirRel) {
19
+ return content.replaceAll('<GUARDRAILS_SKILL_DIR>', skillDirRel);
20
+ }
26
21
 
27
- const skillTemplate = readFileSync(join(BUNDLE_ROOT, 'SKILL.md'), 'utf-8');
28
- writeText(join(destBase, 'SKILL.md'), injectProjectName(skillTemplate, options.projectName));
22
+ function injectSkillTemplate(content, projectName, skillDirRel) {
23
+ return injectSkillDir(injectProjectName(content, projectName), skillDirRel);
24
+ }
29
25
 
30
- copyFileSync(join(BUNDLE_ROOT, 'references', 'signal-registry.json'), join(destRefs, 'signal-registry.json'));
26
+ const BUNDLE_ROOT = join(__dirname, '..', 'generators', 'rules', 'guardrails-profiler');
31
27
 
32
- return destBase;
28
+ /**
29
+ * Writes `guardrails-profiler` (SKILL.md + references/signal-registry.json) under each selected
30
+ * IDE skills directory (e.g. `.cursor/skills/guardrails-profiler`, `.claude/skills/...`).
31
+ *
32
+ * @returns {string[]} Absolute paths to each skill root written */
33
+ export function writeGuardrailsProfilerBundles(cwd, options = {}) {
34
+ const targets = Array.isArray(options.targets) ? options.targets : [];
35
+ const written = [];
36
+
37
+ for (const target of targets) {
38
+ const rel = GUARDRAILS_PROFILER_SKILL_REL_DIR[target];
39
+ if (!rel) continue;
40
+
41
+ const destBase = join(cwd, rel);
42
+ const destRefs = join(destBase, 'references');
43
+ ensureDir(destRefs);
44
+
45
+ const skillTemplate = readFileSync(join(BUNDLE_ROOT, 'SKILL.md'), 'utf-8');
46
+ writeText(join(destBase, 'SKILL.md'), injectSkillTemplate(skillTemplate, options.projectName, rel));
47
+
48
+ copyFileSync(
49
+ join(BUNDLE_ROOT, 'references', 'signal-registry.json'),
50
+ join(destRefs, 'signal-registry.json'),
51
+ );
52
+
53
+ written.push(destBase);
54
+ }
55
+
56
+ return written;
33
57
  }
@@ -1,4 +1,5 @@
1
1
  import { spawnSync } from 'node:child_process';
2
+ import { GUARDRAILS_PROFILER_SKILL_REL_DIR } from './constants.js';
2
3
 
3
4
  const PREFERRED_ORDER = ['cursor', 'claude', 'codex'];
4
5
 
@@ -7,19 +8,34 @@ function commandOk(cmd, args = ['--version']) {
7
8
  return r.status === 0;
8
9
  }
9
10
 
10
- export function buildProfilerAgentPrompt(projectName) {
11
+ export function buildProfilerAgentPrompt(projectName, agentTarget = 'cursor') {
11
12
  const p = String(projectName || '').trim() || '<SRAI_PROJECT_NAME>';
13
+ const skillRoot =
14
+ GUARDRAILS_PROFILER_SKILL_REL_DIR[agentTarget] ||
15
+ GUARDRAILS_PROFILER_SKILL_REL_DIR.cursor;
12
16
  return [
13
17
  'Security Review Kit: run guardrails initialization profiling for this workspace.',
14
18
  `SRAI project name: "${p}".`,
15
- 'Open and follow every step in .securityreview-kit/guardrails-profiler/SKILL.md.',
16
- 'Use .securityreview-kit/guardrails-profiler/references/signal-registry.json as the signal registry.',
19
+ `Open and follow every step in ${skillRoot}/SKILL.md.`,
20
+ `Use ${skillRoot}/references/signal-registry.json as the signal registry.`,
17
21
  'Write .guardrails/profile.json and profile.json at the repository root as defined in the skill.',
18
22
  `Resolve project_id with find_project_by_name for "${p}", then call update_vibe_profile and write_default_pack via security-review-mcp.`,
19
23
  'Do not invent stack details, compliance, or user groups; ground everything in the repository.',
20
24
  ].join('\n');
21
25
  }
22
26
 
27
+ /**
28
+ * Run Cursor Agent OAuth/login in the current terminal (stdio inherited).
29
+ * Call this from init before profiling so the user does not leave the kit flow.
30
+ */
31
+ export function runCursorAgentLogin(cwd) {
32
+ if (!commandOk('cursor-agent', ['--version'])) {
33
+ return { ok: false, status: null, message: 'cursor-agent not on PATH' };
34
+ }
35
+ const r = spawnSync('cursor-agent', ['login'], { cwd, stdio: 'inherit', env: { ...process.env } });
36
+ return { ok: r.status === 0, status: r.status };
37
+ }
38
+
23
39
  export function pickProfilerAgentTarget(targets) {
24
40
  for (const t of PREFERRED_ORDER) {
25
41
  if (targets.includes(t)) {
@@ -31,16 +47,25 @@ export function pickProfilerAgentTarget(targets) {
31
47
 
32
48
  /**
33
49
  * Spawn the IDE agent CLI to execute the profiler skill (user must be logged in where required).
50
+ *
51
+ * @param {object} opts
52
+ * @param {boolean} [opts.cursorTrust=true] When true, passes `--trust` and `--approve-mcps` so headless init is not blocked by
53
+ * workspace trust or MCP approval (user confirmed profiling in the kit). Set false with `--profiler-no-trust`
54
+ * if you need an interactive trust/login/MCP flow in the same terminal.
34
55
  */
35
- export function runProfilerAgent(cwd, { target, projectName }) {
36
- const prompt = buildProfilerAgentPrompt(projectName);
56
+ export function runProfilerAgent(cwd, { target, projectName, cursorTrust = true }) {
57
+ const prompt = buildProfilerAgentPrompt(projectName, target);
37
58
  const opts = { cwd, stdio: 'inherit', env: { ...process.env } };
38
59
 
39
60
  if (target === 'cursor') {
40
61
  if (!commandOk('cursor-agent', ['--version'])) {
41
62
  return { ok: false, message: 'cursor-agent not on PATH' };
42
63
  }
43
- const r = spawnSync('cursor-agent', ['-p', prompt], opts);
64
+ const args = ['-p', prompt];
65
+ if (cursorTrust) {
66
+ args.push('--trust', '--approve-mcps');
67
+ }
68
+ const r = spawnSync('cursor-agent', args, opts);
44
69
  return { ok: r.status === 0, status: r.status };
45
70
  }
46
71