@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 +1 -1
- package/src/cli.js +4 -0
- package/src/commands/init.js +4 -1
- package/src/commands/status.js +13 -0
- package/src/commands/switch-project.js +3 -1
- package/src/generators/rules/cursor.js +31 -2
- package/src/generators/rules/guardrails-init-profile.md +6 -2
- package/src/generators/rules/guardrails-profiler/SKILL.md +10 -22
- package/src/utils/profiler-agent.js +19 -3
package/package.json
CHANGED
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) {
|
package/src/commands/init.js
CHANGED
|
@@ -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.'));
|
package/src/commands/status.js
CHANGED
|
@@ -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 {
|
|
4
|
-
import {
|
|
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
|
|
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
|
-
`
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|