@securityreviewai/securityreview-kit 0.1.25 → 0.1.27

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.25",
3
+ "version": "0.1.27",
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
@@ -37,6 +37,10 @@ export function run() {
37
37
  '--profiler-cursor-login',
38
38
  'Before Cursor profiling, run cursor-agent login in this terminal (then profiling runs in the same init)',
39
39
  )
40
+ .option(
41
+ '--profiler-quiet',
42
+ 'When profiling, use minimal agent output (no streaming JSON / verbose progress)',
43
+ )
40
44
  .action(async (options) => {
41
45
  try {
42
46
  if (options.switchProject) {
@@ -41,7 +41,7 @@ function normalizeRuleResults(rawResult) {
41
41
  }
42
42
 
43
43
  if (entry && typeof entry.filePath === 'string') {
44
- const allowedKinds = new Set(['rule', 'command', 'agent', 'skill']);
44
+ const allowedKinds = new Set(['rule', 'command', 'agent', 'skill', 'hooks', 'config']);
45
45
  const kind = allowedKinds.has(entry.kind) ? entry.kind : 'rule';
46
46
  return { filePath: entry.filePath, action: entry.action || 'created', kind };
47
47
  }
@@ -336,6 +336,8 @@ export async function initCommand(options) {
336
336
  command: 'Workspace command',
337
337
  agent: 'Workspace agent',
338
338
  skill: 'Workspace skill',
339
+ hooks: 'Hooks',
340
+ config: 'CLI config',
339
341
  };
340
342
  const label = labelByKind[rule.kind] || 'Workspace rule';
341
343
  console.log(chalk.green(` ✓ ${label} → ${rule.filePath} (${rule.action})`));
@@ -459,6 +461,7 @@ export async function initCommand(options) {
459
461
  target: agentTarget,
460
462
  projectName: projectNameForSkill,
461
463
  cursorTrust: !options.profilerNoTrust,
464
+ streamProgress: !options.profilerQuiet,
462
465
  });
463
466
  if (pr.ok) {
464
467
  console.log(chalk.green(' \u2713 Profiler agent finished.'));
@@ -63,6 +63,19 @@ export async function statusCommand() {
63
63
  console.log(
64
64
  ` Workspace Rule: ${ruleHasSrai ? chalk.green('✓ Installed') : chalk.dim('✗ Not found')} ${chalk.dim(target.rulePath)}`,
65
65
  );
66
+ if (key === 'cursor') {
67
+ const cliPath = join(cwd, '.cursor', 'cli.json');
68
+ const cliJson = readJson(cliPath);
69
+ const allow = cliJson?.permissions?.allow;
70
+ const hasMcpAllow =
71
+ Array.isArray(allow) &&
72
+ allow.some(
73
+ (e) => typeof e === 'string' && e.toLowerCase().includes('mcp(security-review-mcp'),
74
+ );
75
+ console.log(
76
+ ` CLI MCP allow: ${hasMcpAllow ? chalk.green('\u2713 security-review-mcp') : chalk.dim('\u2717 Missing in .cursor/cli.json')} ${chalk.dim('.cursor/cli.json')}`,
77
+ );
78
+ }
66
79
  console.log('');
67
80
  }
68
81
 
@@ -25,7 +25,7 @@ function normalizeRuleResults(rawResult) {
25
25
  }
26
26
 
27
27
  if (entry && typeof entry.filePath === 'string') {
28
- const allowedKinds = new Set(['rule', 'command', 'agent', 'skill']);
28
+ const allowedKinds = new Set(['rule', 'command', 'agent', 'skill', 'hooks', 'config']);
29
29
  const kind = allowedKinds.has(entry.kind) ? entry.kind : 'rule';
30
30
  return { filePath: entry.filePath, action: entry.action || 'created', kind };
31
31
  }
@@ -180,6 +180,8 @@ export async function switchProjectCommand(options = {}) {
180
180
  command: 'Workspace command',
181
181
  agent: 'Workspace agent',
182
182
  skill: 'Workspace skill',
183
+ hooks: 'Hooks',
184
+ config: 'CLI config',
183
185
  };
184
186
  const label = labelByKind[rule.kind] || 'Workspace rule';
185
187
  console.log(chalk.green(` ✓ ${label} → ${rule.filePath} (${rule.action})`));
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { writeText } from '../../utils/fs-helpers.js';
4
- import { GUARDRAILS_PROFILER_SKILL_REL_DIR } from '../../utils/constants.js';
3
+ import { GUARDRAILS_PROFILER_SKILL_REL_DIR, MCP_SERVER_NAME } from '../../utils/constants.js';
4
+ import { readJson, writeJson, writeText } from '../../utils/fs-helpers.js';
5
5
  import {
6
6
  getRuleContent,
7
7
  getProfileCommandContent,
@@ -34,6 +34,32 @@ function writeCursorCommand(filePath, content) {
34
34
  return { filePath, action };
35
35
  }
36
36
 
37
+ /**
38
+ * Merge `.cursor/cli.json` so Cursor CLI / Agent auto-allows security-review-mcp tools (no MCP approval prompts).
39
+ * @see https://cursor.com/docs/cli/reference/permissions
40
+ */
41
+ function mergeCursorCliMcpAllowlist(cwd) {
42
+ const cliPath = join(cwd, '.cursor', 'cli.json');
43
+ const existed = existsSync(cliPath);
44
+ const existing = readJson(cliPath) || {};
45
+ if (!existing.permissions || typeof existing.permissions !== 'object') {
46
+ existing.permissions = {};
47
+ }
48
+ if (!Array.isArray(existing.permissions.allow)) {
49
+ existing.permissions.allow = [];
50
+ }
51
+ const token = `Mcp(${MCP_SERVER_NAME}:*)`;
52
+ if (!existing.permissions.allow.includes(token)) {
53
+ existing.permissions.allow.push(token);
54
+ }
55
+ if (existing.permissions.deny !== undefined && !Array.isArray(existing.permissions.deny)) {
56
+ existing.permissions.deny = [];
57
+ }
58
+ writeJson(cliPath, existing);
59
+ const action = existed ? 'updated' : 'created';
60
+ return { filePath: cliPath, action };
61
+ }
62
+
37
63
  /**
38
64
  * Generate Cursor workspace rules at .cursor/rules/*.mdc
39
65
  * Cursor uses .mdc format with YAML front matter.
@@ -90,6 +116,8 @@ export function generate(cwd, options = {}) {
90
116
  const hooksAction = existsSync(hooksPath) ? 'updated' : 'created';
91
117
  writeText(hooksPath, hooksContent);
92
118
 
119
+ const cliPermissions = mergeCursorCliMcpAllowlist(cwd);
120
+
93
121
  return [
94
122
  { ...baseRule, kind: 'rule' },
95
123
  { ...ctmSyncTriggerRule, kind: 'rule' },
@@ -101,5 +129,6 @@ export function generate(cwd, options = {}) {
101
129
  { ...guardrailsInitProfileCommand, kind: 'command' },
102
130
  { filePath: skillPath, action: skillAction, kind: 'skill' },
103
131
  { filePath: hooksPath, action: hooksAction, kind: 'hooks' },
132
+ { ...cliPermissions, kind: 'config' },
104
133
  ];
105
134
  }
@@ -17,11 +17,15 @@ Configured SRAI project name: `<SRAI_PROJECT_NAME>`
17
17
 
18
18
  Do not skip MCP upload when credentials and MCP are available.
19
19
 
20
+ Do **not** ask the user to verbally approve MCP for `security-review-mcp`. The repo’s **`.cursor/cli.json`** should list `Mcp(security-review-mcp:*)` under `permissions.allow`; call the MCP tools directly.
21
+
20
22
  ## Cursor CLI (scripted)
21
23
 
22
- From the repo root, non-interactive runs should include workspace trust and MCP approval (matches `securityreview-kit init`):
24
+ From the repo root, non-interactive runs should include workspace trust, MCP approval, and **streaming progress** (matches default `securityreview-kit init`):
25
+
26
+ `cursor-agent -p "<your profiling instructions>" --output-format stream-json --stream-partial-output --trust --approve-mcps`
23
27
 
24
- `cursor-agent -p "<your profiling instructions>" --trust --approve-mcps`
28
+ Omit `--output-format` / `--stream-partial-output` if you want less verbose terminal output (or use `securityreview-kit init` with `--profiler-quiet`).
25
29
 
26
30
  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
31
 
@@ -104,21 +104,13 @@ Create `.guardrails/` if needed and write the profile file.
104
104
 
105
105
  ### Step 6: Build `profile.json` (project root)
106
106
 
107
- Write **`profile.json`** at the project root with this structure (all parts required; use empty strings or `[]` when unknown — never fabricate compliance or user groups):
107
+ Write **`profile.json`** at the project root with **only** these parts:
108
108
 
109
109
  ```json
110
110
  {
111
111
  "schema_version": "2.0",
112
112
  "srai_project_name": "<SRAI_PROJECT_NAME>",
113
113
  "guardrails_profile": {},
114
- "vibe_profile": {
115
- "description": "<short summary of what the repo appears to be, from detected stack only>",
116
- "architecture_notes": [],
117
- "tech_categories": [],
118
- "user_groups": [],
119
- "compliance_requirements": [],
120
- "language_stacks": []
121
- },
122
114
  "default_guardrail_pack": {
123
115
  "guardrail_packs": [],
124
116
  "pack_count": 0
@@ -126,26 +118,22 @@ Write **`profile.json`** at the project root with this structure (all parts requ
126
118
  }
127
119
  ```
128
120
 
129
- Populate `guardrails_profile` with the **same object** written to `.guardrails/profile.json`.
130
-
131
- Populate `default_guardrail_pack.guardrail_packs` with the deduplicated pack id list (same as `guardrails_profile.guardrail_packs`) and `pack_count`.
121
+ - Populate **`guardrails_profile`** with the **same object** written to `.guardrails/profile.json` (detection summary, packs, etc.).
122
+ - Populate **`default_guardrail_pack`** with the deduplicated `guardrail_packs` list (same ids as in `guardrails_profile`) and `pack_count`.
132
123
 
133
- Derive `vibe_profile` fields **only** from what you observed:
134
-
135
- - `tech_categories`: e.g. `backend`, `frontend`, `database`, `cloud`, `mobile` when supported by files/deps.
136
- - `language_stacks`: strings like `TypeScript/Node`, `Python/FastAPI` from detection.
137
- - `architecture_notes`: short bullets (e.g. "Dockerfile present", "GitHub Actions CI") — not speculative threat narratives.
138
- - `user_groups` / `compliance_requirements`: only if explicit in repo (e.g. compliance docs); else `[]`.
124
+ **Do not** add a separate `vibe_profile` block and do **not** populate narrative fields such as long `description`, `architecture_notes`, `tech_categories`, `user_groups`, `compliance_requirements`, or `language_stacks`. The server-facing “vibe” update is driven **only** from the technical **`guardrails_profile`** plus the default pack (see Step 7).
139
125
 
140
126
  ### Step 7: Upload to SecurityReview.ai (security-review-mcp)
141
127
 
142
128
  1. Resolve `project_id`: `find_project_by_name` with `name="<SRAI_PROJECT_NAME>"`. If missing, follow existing kit rules (`list_projects`, `create_project`).
143
129
 
144
- 2. Call **`update_vibe_profile`** with `project_id` and the fields from `profile.json.vibe_profile` (map parameter names to the MCP tool’s expected shape; pass only fields the tool accepts).
130
+ 2. Call **`update_vibe_profile`** with `project_id` and arguments mapped **only** from **`profile.json.guardrails_profile`** (and any required `project_id` fields) per the MCP tool’s documented schema. Treat the guardrails detection object as the profile payload — **not** a separate prose vibe document.
131
+
132
+ 3. Call **`write_default_pack`** with `project_id` and the payload from **`profile.json.default_guardrail_pack`** (match the MCP tool’s schema).
145
133
 
146
- 3. Call **`write_default_pack`** with `project_id` and the default pack payload from `profile.json.default_guardrail_pack` (match the MCP tool’s schema typically the pack id list and metadata the server expects).
134
+ 4. **MCP approval:** Do **not** ask the user to “approve MCP” or “say you approve” for `security-review-mcp`. Security Review Kit installs **`.cursor/cli.json`** with `Mcp(security-review-mcp:*)` in `permissions.allow` so Cursor CLI runs tools without that prompt. Invoke `find_project_by_name`, `update_vibe_profile`, and `write_default_pack` directly. If a call still fails with permissions, report the error and suggest verifying Cursor **auto-run** / CLI permissions — not a conversational approval step.
147
135
 
148
- 4. Confirm success to the user: paths written (`profile.json`, `.guardrails/profile.json`) and that both MCP calls completed or the exact error.
136
+ 5. Confirm success: paths written (`profile.json`, `.guardrails/profile.json`) and whether both MCP calls succeeded, or the exact error.
149
137
 
150
138
  ### Step 8: Report
151
139
 
@@ -157,7 +145,7 @@ If there are no signals:
157
145
 
158
146
  1. Optionally read `.git/config` for hints.
159
147
  2. Emit minimal profile: `owasp-asvs` only, empty summaries where appropriate.
160
- 3. Still write `profile.json` and attempt MCP calls with honest minimal `vibe_profile` data.
148
+ 3. Still write `profile.json` (minimal `guardrails_profile` + `default_guardrail_pack`) and attempt MCP calls.
161
149
 
162
150
  ## IDE-Specific Notes
163
151
 
@@ -52,16 +52,28 @@ export function pickProfilerAgentTarget(targets) {
52
52
  * @param {boolean} [opts.cursorTrust=true] When true, passes `--trust` and `--approve-mcps` so headless init is not blocked by
53
53
  * workspace trust or MCP approval (user confirmed profiling in the kit). Set false with `--profiler-no-trust`
54
54
  * if you need an interactive trust/login/MCP flow in the same terminal.
55
+ * @param {boolean} [opts.streamProgress=true] When true, pass each CLI’s streaming / verbose flags so the terminal shows live progress
56
+ * (JSON event lines on Cursor/Codex; stream-json + partial messages + verbose on Claude). Disable with `--profiler-quiet`.
55
57
  */
56
- export function runProfilerAgent(cwd, { target, projectName, cursorTrust = true }) {
58
+ export function runProfilerAgent(cwd, { target, projectName, cursorTrust = true, streamProgress = true }) {
57
59
  const prompt = buildProfilerAgentPrompt(projectName, target);
58
60
  const opts = { cwd, stdio: 'inherit', env: { ...process.env } };
59
61
 
62
+ if (streamProgress) {
63
+ console.error(
64
+ '\n[securityreview-kit] Profiler live output: you should see streaming progress below ' +
65
+ '(JSON lines are normal). Use --profiler-quiet for minimal output.\n',
66
+ );
67
+ }
68
+
60
69
  if (target === 'cursor') {
61
70
  if (!commandOk('cursor-agent', ['--version'])) {
62
71
  return { ok: false, message: 'cursor-agent not on PATH' };
63
72
  }
64
73
  const args = ['-p', prompt];
74
+ if (streamProgress) {
75
+ args.push('--output-format', 'stream-json', '--stream-partial-output');
76
+ }
65
77
  if (cursorTrust) {
66
78
  args.push('--trust', '--approve-mcps');
67
79
  }
@@ -73,7 +85,10 @@ export function runProfilerAgent(cwd, { target, projectName, cursorTrust = true
73
85
  if (!commandOk('claude', ['--version'])) {
74
86
  return { ok: false, message: 'claude not on PATH' };
75
87
  }
76
- const r = spawnSync('claude', ['-p', prompt], opts);
88
+ const args = streamProgress
89
+ ? ['-p', '--output-format', 'stream-json', '--include-partial-messages', '--verbose', prompt]
90
+ : ['-p', prompt];
91
+ const r = spawnSync('claude', args, opts);
77
92
  return { ok: r.status === 0, status: r.status };
78
93
  }
79
94
 
@@ -81,7 +96,8 @@ export function runProfilerAgent(cwd, { target, projectName, cursorTrust = true
81
96
  if (!commandOk('codex', ['--version'])) {
82
97
  return { ok: false, message: 'codex not on PATH' };
83
98
  }
84
- const r = spawnSync('codex', ['exec', prompt], opts);
99
+ const args = streamProgress ? ['exec', '--json', prompt] : ['exec', prompt];
100
+ const r = spawnSync('codex', args, opts);
85
101
  return { ok: r.status === 0, status: r.status };
86
102
  }
87
103