@securityreviewai/securityreview-kit 0.1.22 → 0.1.23
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 +3 -0
- package/src/commands/init.js +86 -7
- package/src/generators/rules/claude.js +11 -3
- package/src/generators/rules/codex.js +11 -3
- package/src/generators/rules/content.js +7 -0
- package/src/generators/rules/content.md +1 -1
- package/src/generators/rules/ctm_sync.md +1 -10
- package/src/generators/rules/cursor.js +8 -0
- package/src/generators/rules/guardrails-init-profile.md +18 -0
- package/src/generators/rules/guardrails-profiler/SKILL.md +163 -0
- package/src/generators/rules/guardrails-profiler/references/signal-registry.json +514 -0
- package/src/utils/guardrails-profiler-bundle.js +33 -0
- package/src/utils/ide-cli-install.js +82 -0
- package/src/utils/profiler-agent.js +64 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -26,6 +26,9 @@ export function run() {
|
|
|
26
26
|
.option('--switch-project', 'Fetch projects and only update mapped workspace rules')
|
|
27
27
|
.option('--skip-mcp', 'Skip MCP server config installation')
|
|
28
28
|
.option('--skip-rules', 'Skip workspace rule installation')
|
|
29
|
+
.option('--skip-ide-cli-install', 'Do not install Cursor / Claude Code / Codex CLIs when those targets are selected')
|
|
30
|
+
.option('--profile-repo', 'After init, run the guardrails profiler agent (non-interactive; needs cursor, claude, or codex target)')
|
|
31
|
+
.option('--no-profile-repo', 'Skip the optional profiler agent step after init')
|
|
29
32
|
.action(async (options) => {
|
|
30
33
|
try {
|
|
31
34
|
if (options.switchProject) {
|
package/src/commands/init.js
CHANGED
|
@@ -2,6 +2,9 @@ import chalk from 'chalk';
|
|
|
2
2
|
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
|
+
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';
|
|
5
8
|
import { fetchVibeReviewProjectNames, getStoredCredentials, normalizeApiUrl } from '../utils/srai.js';
|
|
6
9
|
|
|
7
10
|
// Dynamic imports for generators (avoids loading all at startup)
|
|
@@ -220,18 +223,34 @@ export async function initCommand(options) {
|
|
|
220
223
|
}
|
|
221
224
|
|
|
222
225
|
// Step 1: Resolve targets
|
|
223
|
-
console.log(chalk.bold.white(' Step 1 of
|
|
226
|
+
console.log(chalk.bold.white(' Step 1 of 5: Select Targets'));
|
|
224
227
|
console.log(chalk.dim(' ─────────────────────────────────'));
|
|
225
228
|
const targets = await resolveTargets(options, interactive, cwd);
|
|
226
229
|
console.log(chalk.green(` ✓ Targets: ${targets.map((t) => TARGETS[t].name).join(', ')}`));
|
|
227
230
|
console.log('');
|
|
231
|
+
console.log(chalk.bold.white(' Step 1b: IDE / agent CLIs'));
|
|
232
|
+
console.log(chalk.dim(' ─────────────────────────────────'));
|
|
233
|
+
const cliResults = ensureIdeClisForTargets(targets, { skipInstall: options.skipIdeCliInstall });
|
|
234
|
+
for (const r of cliResults) {
|
|
235
|
+
if (r.skipped) continue;
|
|
236
|
+
const label = TARGETS[r.target]?.name || r.target;
|
|
237
|
+
if (r.already) {
|
|
238
|
+
console.log(chalk.dim(` • ${label} CLI already available`));
|
|
239
|
+
} else if (r.ok) {
|
|
240
|
+
console.log(chalk.green(` \u2713 ${label} CLI install attempted`));
|
|
241
|
+
} else {
|
|
242
|
+
console.log(chalk.yellow(` \u26a0 ${label} CLI: ${r.message || 'install skipped or failed'}`));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
console.log('');
|
|
246
|
+
|
|
228
247
|
|
|
229
248
|
// Step 2: What to install (rules question first, then MCP)
|
|
230
249
|
let installMcp = !options.skipMcp;
|
|
231
250
|
let installRules = !options.skipRules;
|
|
232
251
|
|
|
233
252
|
if (interactive) {
|
|
234
|
-
console.log(chalk.bold.white(' Step 2 of
|
|
253
|
+
console.log(chalk.bold.white(' Step 2 of 5: Installation Options'));
|
|
235
254
|
console.log(chalk.dim(' ─────────────────────────────────'));
|
|
236
255
|
installRules = await confirm({
|
|
237
256
|
message: '📋 Install workspace security rules?',
|
|
@@ -252,19 +271,19 @@ export async function initCommand(options) {
|
|
|
252
271
|
// Step 3: Resolve credentials (only needed when MCP is being installed)
|
|
253
272
|
let envVars = { apiUrl: '', apiToken: '' };
|
|
254
273
|
if (installMcp) {
|
|
255
|
-
console.log(chalk.bold.white(' Step 3 of
|
|
274
|
+
console.log(chalk.bold.white(' Step 3 of 5: SRAI Credentials'));
|
|
256
275
|
console.log(chalk.dim(' ─────────────────────────────────'));
|
|
257
276
|
envVars = await resolveEnvVars(options, interactive, cwd);
|
|
258
277
|
console.log(chalk.green(' ✓ Credentials configured'));
|
|
259
278
|
} else {
|
|
260
|
-
console.log(chalk.dim(' Step 3 of
|
|
279
|
+
console.log(chalk.dim(' Step 3 of 5: Skipping SRAI credentials (MCP not selected).'));
|
|
261
280
|
}
|
|
262
281
|
console.log('');
|
|
263
282
|
|
|
264
283
|
// Step 4: Resolve project name (only needed when MCP is being installed)
|
|
265
284
|
let projectName = options.projectName || process.env.SECURITY_REVIEW_PROJECT_NAME || '';
|
|
266
285
|
if (installMcp) {
|
|
267
|
-
console.log(chalk.bold.white(' Step 4 of
|
|
286
|
+
console.log(chalk.bold.white(' Step 4 of 5: SRAI Project Mapping'));
|
|
268
287
|
console.log(chalk.dim(' ─────────────────────────────────'));
|
|
269
288
|
projectName = await resolveProjectName(options, interactive, envVars.apiUrl, envVars.apiToken);
|
|
270
289
|
if (projectName) {
|
|
@@ -273,11 +292,14 @@ export async function initCommand(options) {
|
|
|
273
292
|
console.log(chalk.dim(' No project name provided. Rules will use a generic project lookup instruction.'));
|
|
274
293
|
}
|
|
275
294
|
} else {
|
|
276
|
-
console.log(chalk.dim(' Step 4 of
|
|
295
|
+
console.log(chalk.dim(' Step 4 of 5: Skipping SRAI project mapping (MCP not selected).'));
|
|
277
296
|
}
|
|
278
297
|
console.log('');
|
|
279
298
|
|
|
280
|
-
|
|
299
|
+
const projectNameForSkill =
|
|
300
|
+
(projectName || options.projectName || process.env.SECURITY_REVIEW_PROJECT_NAME || '').trim();
|
|
301
|
+
|
|
302
|
+
console.log(chalk.bold.white(' Step 5 of 5: Installing workspace files'));
|
|
281
303
|
console.log(chalk.dim(' ─────────────────────────────────'));
|
|
282
304
|
|
|
283
305
|
const results = [];
|
|
@@ -322,6 +344,17 @@ export async function initCommand(options) {
|
|
|
322
344
|
}
|
|
323
345
|
}
|
|
324
346
|
|
|
347
|
+
if (installRules) {
|
|
348
|
+
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 });
|
|
352
|
+
} catch (err) {
|
|
353
|
+
console.log(chalk.red(` \u2717 Guardrails profiler bundle failed: ${err.message}`));
|
|
354
|
+
results.push({ target: 'kit', type: 'bundle', status: 'error', error: err.message });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
325
358
|
// Summary
|
|
326
359
|
const ok = results.filter((r) => r.status === 'ok').length;
|
|
327
360
|
const errors = results.filter((r) => r.status === 'error').length;
|
|
@@ -335,6 +368,52 @@ export async function initCommand(options) {
|
|
|
335
368
|
chalk.bold.yellow(` ⚠ Done with ${errors} error(s). ${ok} configuration(s) installed.`),
|
|
336
369
|
);
|
|
337
370
|
}
|
|
371
|
+
console.log('');
|
|
372
|
+
|
|
373
|
+
const profilerEligible =
|
|
374
|
+
installMcp && installRules && projectNameForSkill && options.profileRepo !== false;
|
|
375
|
+
|
|
376
|
+
let shouldProfile = false;
|
|
377
|
+
if (profilerEligible) {
|
|
378
|
+
if (interactive) {
|
|
379
|
+
shouldProfile = await confirm({
|
|
380
|
+
message:
|
|
381
|
+
'Profile this repository and push the default guardrail pack to SecurityReview.ai (runs your IDE agent CLI)?',
|
|
382
|
+
default: true,
|
|
383
|
+
});
|
|
384
|
+
} else {
|
|
385
|
+
shouldProfile = Boolean(options.profileRepo);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (shouldProfile) {
|
|
390
|
+
const agentTarget = pickProfilerAgentTarget(targets);
|
|
391
|
+
if (!agentTarget) {
|
|
392
|
+
console.log(
|
|
393
|
+
chalk.yellow(
|
|
394
|
+
' \u26a0 Profiling needs Cursor, Claude Code, or Codex in your targets. Add one and re-run, or run the guardrails-init-profile command in your IDE.',
|
|
395
|
+
),
|
|
396
|
+
);
|
|
397
|
+
} else {
|
|
398
|
+
console.log('');
|
|
399
|
+
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 });
|
|
402
|
+
if (pr.ok) {
|
|
403
|
+
console.log(chalk.green(' \u2713 Profiler agent finished.'));
|
|
404
|
+
} else {
|
|
405
|
+
const detail =
|
|
406
|
+
pr.message ||
|
|
407
|
+
(typeof pr.status === 'number' ? `exit status ${pr.status}` : 'unknown error');
|
|
408
|
+
console.log(
|
|
409
|
+
chalk.yellow(
|
|
410
|
+
` \u26a0 Profiler agent exited with an error: ${detail}. You can run the guardrails-init-profile workflow manually.`,
|
|
411
|
+
),
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
338
417
|
console.log('');
|
|
339
418
|
console.log(chalk.dim(' Run `securityreview-kit status` to verify your setup.'));
|
|
340
419
|
console.log('');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { upsertSentinelBlock } from '../../utils/fs-helpers.js';
|
|
3
|
-
import { getRuleContent } from './content.js';
|
|
2
|
+
import { upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
|
|
3
|
+
import { getRuleContent, getGuardrailsInitProfileContent } from './content.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Generate Claude Code workspace rule — appends to CLAUDE.md
|
|
@@ -9,5 +9,13 @@ export function generate(cwd, options = {}) {
|
|
|
9
9
|
const filePath = join(cwd, 'CLAUDE.md');
|
|
10
10
|
const content = getRuleContent(options);
|
|
11
11
|
const action = upsertSentinelBlock(filePath, content);
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
const guardrailsInitPath = join(cwd, '.claude', 'commands', 'guardrails-init-profile.md');
|
|
14
|
+
const guardrailsInitContent = getGuardrailsInitProfileContent(options);
|
|
15
|
+
writeText(guardrailsInitPath, guardrailsInitContent);
|
|
16
|
+
|
|
17
|
+
return [
|
|
18
|
+
{ filePath, action, kind: 'rule' },
|
|
19
|
+
{ filePath: guardrailsInitPath, action: 'created', kind: 'command' },
|
|
20
|
+
];
|
|
13
21
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { upsertSentinelBlock } from '../../utils/fs-helpers.js';
|
|
3
|
-
import { getRuleContent } from './content.js';
|
|
2
|
+
import { upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
|
|
3
|
+
import { getRuleContent, getGuardrailsInitProfileContent } from './content.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Generate Codex workspace rule — appends to AGENTS.md
|
|
@@ -9,5 +9,13 @@ export function generate(cwd, options = {}) {
|
|
|
9
9
|
const filePath = join(cwd, 'AGENTS.md');
|
|
10
10
|
const content = getRuleContent(options);
|
|
11
11
|
const action = upsertSentinelBlock(filePath, content);
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
const guardrailsInitPath = join(cwd, '.codex', 'commands', 'guardrails-init-profile.md');
|
|
14
|
+
const guardrailsInitContent = getGuardrailsInitProfileContent(options);
|
|
15
|
+
writeText(guardrailsInitPath, guardrailsInitContent);
|
|
16
|
+
|
|
17
|
+
return [
|
|
18
|
+
{ filePath, action, kind: 'rule' },
|
|
19
|
+
{ filePath: guardrailsInitPath, action: 'created', kind: 'command' },
|
|
20
|
+
];
|
|
13
21
|
}
|
|
@@ -71,6 +71,13 @@ export function getGuardrailsRuleContent(options = {}) {
|
|
|
71
71
|
return readTemplate('guardrails_rule.md', options);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Guardrails init profile command (repo scan + profile.json + SRAI upload).
|
|
76
|
+
*/
|
|
77
|
+
export function getGuardrailsInitProfileContent(options = {}) {
|
|
78
|
+
return readTemplate('guardrails-init-profile.md', options);
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
/**
|
|
75
82
|
* Returns the hooks.json content for Cursor session hooks.
|
|
76
83
|
*/
|
|
@@ -119,4 +119,4 @@ When running `ctm_sync` (dedicated agent/command where available, or the same st
|
|
|
119
119
|
| **Analysis** | `get_threat_scenarios`, `get_countermeasures`, `get_components`, `get_data_dictionaries`, `get_security_objectives`, `get_findings`, `get_security_test_cases` |
|
|
120
120
|
| **Integrations** | `fetch_jira_issue`, `fetch_confluence_page`, `search_confluence_pages`, `fetch_and_link_to_srai` |
|
|
121
121
|
| **AI IDE CTM** | `create_ai_ide_workflow`, `create_ai_ide_event` (and any `list_*` AI IDE workflow tools exposed by the server) |
|
|
122
|
-
| **
|
|
122
|
+
| **Vibe profile & default packs** | `update_vibe_profile`, `write_default_pack` (used by the guardrails profiler / init flow — not part of `ctm_sync`) |
|
|
@@ -34,14 +34,7 @@ When invoked:
|
|
|
34
34
|
6. **Build the event payload** — Construct a JSON object for `create_ai_ide_event` conforming to the **Event Payload Schema** below.
|
|
35
35
|
7. **Upload the payload** using `security-review-mcp`:
|
|
36
36
|
- Call `create_ai_ide_event` with the JSON payload.
|
|
37
|
-
|
|
38
|
-
- `description`: a concise summary of the system/project derived from the threat model context (what it does, what data it handles, its overall purpose)
|
|
39
|
-
- `architecture_notes`: a list of architecture observations extracted from the threat model — deployment topology, trust boundaries, data flows, integration points
|
|
40
|
-
- `tech_categories`: a list of technology category labels identified during threat modeling (e.g. `"backend"`, `"frontend"`, `"database"`, `"cloud"`, `"mobile"`)
|
|
41
|
-
- `user_groups`: a list of user roles and groups surfaced in the threat model (e.g. `"admin"`, `"end user"`, `"service account"`)
|
|
42
|
-
- `compliance_requirements`: a list of compliance requirements or standards referenced in the threat model or conversation (e.g. `"PCI-DSS"`, `"HIPAA"`, `"SOC 2"`)
|
|
43
|
-
- `language_stacks`: a list of programming languages, runtimes, and major frameworks identified in the codebase or threat model context (e.g. `"Python/FastAPI"`, `"TypeScript/React"`, `"Java/Spring"`)
|
|
44
|
-
- Omit any field for which no data is available in context — do **not** invent values. Pass an empty list `[]` only if the field is reasonably expected but simply unpopulated.
|
|
37
|
+
- **Stop here.** Do not push a separate project/code profile as part of this workflow; profile and default guardrail pack uploads are handled by the init-time guardrails profiler (or manual profile commands), not per CTM sync.
|
|
45
38
|
|
|
46
39
|
---
|
|
47
40
|
|
|
@@ -155,8 +148,6 @@ Use the following IDs and names exactly when populating `owasp_top_10_2025_mappi
|
|
|
155
148
|
- Never skip upload when a threat model exists.
|
|
156
149
|
- Never invent missing values; use empty strings/arrays if data is unavailable.
|
|
157
150
|
- Never omit `chat_session_id` from the payload.
|
|
158
|
-
- Never skip the `update_vibe_project_profile` call when profile-relevant data (architecture, tech, users, compliance, languages, or description) can be derived from context.
|
|
159
151
|
- Return a compact confirmation after upload including:
|
|
160
152
|
- Whether an existing workflow was reused or a new named workflow was created
|
|
161
|
-
- Confirmation that the project profile was updated
|
|
162
153
|
- Count of guardrails applied (existing vs IDE-generated)
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getCreateIdeWorkflowCommandContent,
|
|
10
10
|
getThreatModellingSkillContent,
|
|
11
11
|
getGuardrailsRuleContent,
|
|
12
|
+
getGuardrailsInitProfileContent,
|
|
12
13
|
getHooksContent,
|
|
13
14
|
} from './content.js';
|
|
14
15
|
|
|
@@ -44,6 +45,7 @@ export function generate(cwd, options = {}) {
|
|
|
44
45
|
const ctmSyncAgentPath = join(cwd, '.cursor', 'agents', 'ctm_sync.md');
|
|
45
46
|
const createIdeWorkflowCommandPath = join(cwd, '.cursor', 'commands', 'create-ide-workflow.md');
|
|
46
47
|
const profileCommandPath = join(cwd, '.cursor', 'commands', 'srai-profile.md');
|
|
48
|
+
const guardrailsInitProfileCommandPath = join(cwd, '.cursor', 'commands', 'guardrails-init-profile.md');
|
|
47
49
|
const skillPath = join(cwd, '.cursor', 'skills', 'threat-modelling', 'SKILL.md');
|
|
48
50
|
const hooksPath = join(cwd, '.cursor', 'hooks.json');
|
|
49
51
|
|
|
@@ -53,6 +55,7 @@ export function generate(cwd, options = {}) {
|
|
|
53
55
|
const ctmSyncWorkflowContent = getCtmSyncWorkflowContent(options);
|
|
54
56
|
const createIdeWorkflowCommandContent = getCreateIdeWorkflowCommandContent(options);
|
|
55
57
|
const profileCommandContent = getProfileCommandContent(options);
|
|
58
|
+
const guardrailsInitProfileCommandContent = getGuardrailsInitProfileContent(options);
|
|
56
59
|
const skillContent = getThreatModellingSkillContent(options);
|
|
57
60
|
|
|
58
61
|
const baseRule = writeCursorRule(
|
|
@@ -72,6 +75,10 @@ export function generate(cwd, options = {}) {
|
|
|
72
75
|
const createIdeWorkflowCommand = writeCursorCommand(createIdeWorkflowCommandPath, createIdeWorkflowCommandContent);
|
|
73
76
|
|
|
74
77
|
const profileCommand = writeCursorCommand(profileCommandPath, profileCommandContent);
|
|
78
|
+
const guardrailsInitProfileCommand = writeCursorCommand(
|
|
79
|
+
guardrailsInitProfileCommandPath,
|
|
80
|
+
guardrailsInitProfileCommandContent,
|
|
81
|
+
);
|
|
75
82
|
const skillAction = existsSync(skillPath) ? 'updated' : 'created';
|
|
76
83
|
writeText(skillPath, skillContent);
|
|
77
84
|
|
|
@@ -87,6 +94,7 @@ export function generate(cwd, options = {}) {
|
|
|
87
94
|
{ ...ctmSyncAgent, kind: 'agent' },
|
|
88
95
|
{ ...createIdeWorkflowCommand, kind: 'command' },
|
|
89
96
|
{ ...profileCommand, kind: 'command' },
|
|
97
|
+
{ ...guardrailsInitProfileCommand, kind: 'command' },
|
|
90
98
|
{ filePath: skillPath, action: skillAction, kind: 'skill' },
|
|
91
99
|
{ filePath: hooksPath, action: hooksAction, kind: 'hooks' },
|
|
92
100
|
];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: guardrails-init-profile
|
|
3
|
+
description: Run the Security Review Kit guardrails profiler — scan the repo, write profile.json, push profile and default guardrail pack to SecurityReview.ai via MCP.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Guardrails init profile
|
|
7
|
+
|
|
8
|
+
Execute the workflow defined in **`.securityreview-kit/guardrails-profiler/SKILL.md`** end-to-end in this workspace.
|
|
9
|
+
|
|
10
|
+
Configured SRAI project name: `<SRAI_PROJECT_NAME>`
|
|
11
|
+
|
|
12
|
+
**You must:**
|
|
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`).
|
|
15
|
+
2. Write `.guardrails/profile.json` and **`profile.json`** at the project root as specified.
|
|
16
|
+
3. Call **`update_vibe_profile`** and **`write_default_pack`** on `security-review-mcp` after resolving `project_id` for `<SRAI_PROJECT_NAME>`.
|
|
17
|
+
|
|
18
|
+
Do not skip MCP upload when credentials and MCP are available.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: guardrails-profiler
|
|
3
|
+
description: Profile a codebase to detect its technology stack and generate a guardrails profile for security-aware AI code generation, then publish the profile and default guardrail pack to SecurityReview.ai via security-review-mcp. Use when Security Review Kit init runs profiling, when `.guardrails/profile.json` is missing, or when the developer asks to profile or re-profile the project.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Guardrails Profiler
|
|
7
|
+
|
|
8
|
+
Profile a codebase's technology stack, write `.guardrails/profile.json`, write a combined **`profile.json` in the project root**, and upload to SRAI using `update_vibe_profile` and `write_default_pack`.
|
|
9
|
+
|
|
10
|
+
Configured SRAI project name: `<SRAI_PROJECT_NAME>`
|
|
11
|
+
|
|
12
|
+
## Canonical paths
|
|
13
|
+
|
|
14
|
+
- **Signal registry (read-only):** `.securityreview-kit/guardrails-profiler/references/signal-registry.json`
|
|
15
|
+
- **Local guardrails file:** `.guardrails/profile.json`
|
|
16
|
+
- **Combined manifest (project root):** `profile.json` — includes guardrails profile, vibe profile fields for MCP, and default pack payload
|
|
17
|
+
|
|
18
|
+
## When This Runs
|
|
19
|
+
|
|
20
|
+
1. **Kit init**: User opted in to profile the repo and push the default pack.
|
|
21
|
+
2. **First-run / missing profile**: No `.guardrails/profile.json` and guardrails are needed before threat modeling.
|
|
22
|
+
3. **Explicit re-profile**: Developer asks to refresh the profile.
|
|
23
|
+
|
|
24
|
+
## Quick Check: Should I Profile?
|
|
25
|
+
|
|
26
|
+
Before profiling, check if a profile already exists:
|
|
27
|
+
|
|
28
|
+
- If `.guardrails/profile.json` exists and has a valid `schema_version`: **SKIP** unless the developer asked to re-profile.
|
|
29
|
+
- If missing: **PROCEED**.
|
|
30
|
+
|
|
31
|
+
## Profiling Procedure
|
|
32
|
+
|
|
33
|
+
Follow these steps in order.
|
|
34
|
+
|
|
35
|
+
### Step 1: Locate the Project Root
|
|
36
|
+
|
|
37
|
+
The project root is the current working directory. Confirm with markers such as `.git/`, `package.json`, `go.mod`, etc.
|
|
38
|
+
|
|
39
|
+
### Step 2: Read the Signal Registry
|
|
40
|
+
|
|
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
|
+
|
|
43
|
+
### Step 3: Scan for Signals
|
|
44
|
+
|
|
45
|
+
Same methodology as the upstream guardrails-profiler skill:
|
|
46
|
+
|
|
47
|
+
#### 3a. Manifest and Config File Detection
|
|
48
|
+
|
|
49
|
+
List files in the project root (1–2 levels deep). Detect manifests (`package.json`, `pyproject.toml`, `go.mod`, `pom.xml`, `Dockerfile`, `.github/workflows/`, `next.config.*`, etc.) per the registry.
|
|
50
|
+
|
|
51
|
+
#### 3b. Dependency Parsing
|
|
52
|
+
|
|
53
|
+
For each manifest found, read dependencies and match names against `dependency_signals` in the registry. Prefer manifests over extension-only guesses.
|
|
54
|
+
|
|
55
|
+
#### 3c. Content Signals (targeted only)
|
|
56
|
+
|
|
57
|
+
When needed, grep specific files (e.g. Terraform providers, K8s `apiVersion`, CloudFormation) — do not read the entire repository.
|
|
58
|
+
|
|
59
|
+
#### 3d. File Extension Fallback
|
|
60
|
+
|
|
61
|
+
If no manifest exists for a language, use dominant extensions as a last resort.
|
|
62
|
+
|
|
63
|
+
### Step 4: Assemble the Guardrails Profile Object
|
|
64
|
+
|
|
65
|
+
Build the object for `.guardrails/profile.json`:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"schema_version": "1.0",
|
|
70
|
+
"project_name": "<directory name>",
|
|
71
|
+
"profiled_at": "<ISO 8601 timestamp>",
|
|
72
|
+
"profiled_by": "<ide or cli id, e.g. cursor-agent, claude, codex>",
|
|
73
|
+
"detection_summary": {
|
|
74
|
+
"languages": [],
|
|
75
|
+
"frameworks": [],
|
|
76
|
+
"infrastructure": [],
|
|
77
|
+
"databases": [],
|
|
78
|
+
"auth": [],
|
|
79
|
+
"ai_agent": [],
|
|
80
|
+
"ci_cd": [],
|
|
81
|
+
"cloud_compute": [],
|
|
82
|
+
"messaging": [],
|
|
83
|
+
"api_protocols": [],
|
|
84
|
+
"mobile": false
|
|
85
|
+
},
|
|
86
|
+
"guardrail_packs": [],
|
|
87
|
+
"pack_count": 0
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Rules for `guardrail_packs`:
|
|
92
|
+
|
|
93
|
+
1. Always include `owasp-asvs`.
|
|
94
|
+
2. Include `owasp-masvs` if mobile stacks are detected (flutter, react-native, swift, objective-c, kotlin per registry).
|
|
95
|
+
3. Add language, framework, auth, AI, infra, CI/CD, cloud, DB, messaging, and API packs per registry matches.
|
|
96
|
+
4. Deduplicate and set `pack_count`.
|
|
97
|
+
|
|
98
|
+
Do **not** invent packs or signals; if the repo is empty, use universal baseline only and empty category arrays where appropriate.
|
|
99
|
+
|
|
100
|
+
### Step 5: Write `.guardrails/profile.json`
|
|
101
|
+
|
|
102
|
+
Create `.guardrails/` if needed and write the profile file.
|
|
103
|
+
|
|
104
|
+
### Step 6: Build `profile.json` (project root)
|
|
105
|
+
|
|
106
|
+
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
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"schema_version": "2.0",
|
|
111
|
+
"srai_project_name": "<SRAI_PROJECT_NAME>",
|
|
112
|
+
"guardrails_profile": {},
|
|
113
|
+
"vibe_profile": {
|
|
114
|
+
"description": "<short summary of what the repo appears to be, from detected stack only>",
|
|
115
|
+
"architecture_notes": [],
|
|
116
|
+
"tech_categories": [],
|
|
117
|
+
"user_groups": [],
|
|
118
|
+
"compliance_requirements": [],
|
|
119
|
+
"language_stacks": []
|
|
120
|
+
},
|
|
121
|
+
"default_guardrail_pack": {
|
|
122
|
+
"guardrail_packs": [],
|
|
123
|
+
"pack_count": 0
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Populate `guardrails_profile` with the **same object** written to `.guardrails/profile.json`.
|
|
129
|
+
|
|
130
|
+
Populate `default_guardrail_pack.guardrail_packs` with the deduplicated pack id list (same as `guardrails_profile.guardrail_packs`) and `pack_count`.
|
|
131
|
+
|
|
132
|
+
Derive `vibe_profile` fields **only** from what you observed:
|
|
133
|
+
|
|
134
|
+
- `tech_categories`: e.g. `backend`, `frontend`, `database`, `cloud`, `mobile` when supported by files/deps.
|
|
135
|
+
- `language_stacks`: strings like `TypeScript/Node`, `Python/FastAPI` from detection.
|
|
136
|
+
- `architecture_notes`: short bullets (e.g. "Dockerfile present", "GitHub Actions CI") — not speculative threat narratives.
|
|
137
|
+
- `user_groups` / `compliance_requirements`: only if explicit in repo (e.g. compliance docs); else `[]`.
|
|
138
|
+
|
|
139
|
+
### Step 7: Upload to SecurityReview.ai (security-review-mcp)
|
|
140
|
+
|
|
141
|
+
1. Resolve `project_id`: `find_project_by_name` with `name="<SRAI_PROJECT_NAME>"`. If missing, follow existing kit rules (`list_projects`, `create_project`).
|
|
142
|
+
|
|
143
|
+
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).
|
|
144
|
+
|
|
145
|
+
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).
|
|
146
|
+
|
|
147
|
+
4. Confirm success to the user: paths written (`profile.json`, `.guardrails/profile.json`) and that both MCP calls completed or the exact error.
|
|
148
|
+
|
|
149
|
+
### Step 8: Report
|
|
150
|
+
|
|
151
|
+
Give a concise summary of detected stack, pack count, and upload status.
|
|
152
|
+
|
|
153
|
+
## Empty / New Repository Handling
|
|
154
|
+
|
|
155
|
+
If there are no signals:
|
|
156
|
+
|
|
157
|
+
1. Optionally read `.git/config` for hints.
|
|
158
|
+
2. Emit minimal profile: `owasp-asvs` only, empty summaries where appropriate.
|
|
159
|
+
3. Still write `profile.json` and attempt MCP calls with honest minimal `vibe_profile` data.
|
|
160
|
+
|
|
161
|
+
## IDE-Specific Notes
|
|
162
|
+
|
|
163
|
+
When run from Cursor Agent CLI, Claude Code, or Codex CLI, set `profiled_by` to a stable id (`cursor-agent`, `claude`, `codex`).
|
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_doc": "Maps filesystem signals to guardrail pack IDs. Each entry defines what to look for and which pack to activate.",
|
|
3
|
+
|
|
4
|
+
"universal": {
|
|
5
|
+
"_doc": "Always included regardless of detection",
|
|
6
|
+
"packs": ["owasp-asvs"]
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
"mobile_universal": {
|
|
10
|
+
"_doc": "Included when any mobile platform is detected",
|
|
11
|
+
"trigger_packs": ["flutter", "react-native", "swift", "objective-c", "kotlin"],
|
|
12
|
+
"packs": ["owasp-masvs"]
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
"languages": {
|
|
16
|
+
"typescript": {
|
|
17
|
+
"manifest_files": ["tsconfig.json", "tsconfig.base.json"],
|
|
18
|
+
"file_extensions": [".ts", ".tsx"],
|
|
19
|
+
"pack": "typescript"
|
|
20
|
+
},
|
|
21
|
+
"nodejs": {
|
|
22
|
+
"manifest_files": ["package.json"],
|
|
23
|
+
"file_extensions": [".js", ".mjs", ".cjs"],
|
|
24
|
+
"pack": "nodejs"
|
|
25
|
+
},
|
|
26
|
+
"python": {
|
|
27
|
+
"manifest_files": ["requirements.txt", "pyproject.toml", "setup.py", "setup.cfg", "Pipfile", "poetry.lock"],
|
|
28
|
+
"file_extensions": [".py"],
|
|
29
|
+
"pack": null,
|
|
30
|
+
"_note": "Python has no standalone pack; frameworks (django, flask, fastapi) cover it"
|
|
31
|
+
},
|
|
32
|
+
"go": {
|
|
33
|
+
"manifest_files": ["go.mod", "go.sum"],
|
|
34
|
+
"file_extensions": [".go"],
|
|
35
|
+
"pack": "go"
|
|
36
|
+
},
|
|
37
|
+
"java": {
|
|
38
|
+
"manifest_files": ["pom.xml", "build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts"],
|
|
39
|
+
"file_extensions": [".java"],
|
|
40
|
+
"pack": null,
|
|
41
|
+
"_note": "Java framework packs (spring-boot, java-ee) are more specific"
|
|
42
|
+
},
|
|
43
|
+
"kotlin": {
|
|
44
|
+
"manifest_files": ["build.gradle.kts"],
|
|
45
|
+
"file_extensions": [".kt", ".kts"],
|
|
46
|
+
"dependency_signals": { "build.gradle.kts": ["kotlin"] },
|
|
47
|
+
"pack": "kotlin"
|
|
48
|
+
},
|
|
49
|
+
"php": {
|
|
50
|
+
"manifest_files": ["composer.json", "composer.lock"],
|
|
51
|
+
"file_extensions": [".php"],
|
|
52
|
+
"pack": "php"
|
|
53
|
+
},
|
|
54
|
+
"ruby": {
|
|
55
|
+
"manifest_files": ["Gemfile", "Gemfile.lock"],
|
|
56
|
+
"file_extensions": [".rb"],
|
|
57
|
+
"pack": null,
|
|
58
|
+
"_note": "Ruby framework pack (ruby-on-rails) is more specific"
|
|
59
|
+
},
|
|
60
|
+
"rust": {
|
|
61
|
+
"manifest_files": ["Cargo.toml", "Cargo.lock"],
|
|
62
|
+
"file_extensions": [".rs"],
|
|
63
|
+
"pack": "rust"
|
|
64
|
+
},
|
|
65
|
+
"c": {
|
|
66
|
+
"file_extensions": [".c", ".h"],
|
|
67
|
+
"manifest_files": ["CMakeLists.txt", "Makefile"],
|
|
68
|
+
"pack": "c"
|
|
69
|
+
},
|
|
70
|
+
"cpp": {
|
|
71
|
+
"file_extensions": [".cpp", ".cxx", ".cc", ".hpp", ".hxx"],
|
|
72
|
+
"manifest_files": ["CMakeLists.txt"],
|
|
73
|
+
"pack": "cpp"
|
|
74
|
+
},
|
|
75
|
+
"swift": {
|
|
76
|
+
"manifest_files": ["Package.swift"],
|
|
77
|
+
"file_extensions": [".swift"],
|
|
78
|
+
"config_files": ["*.xcodeproj", "*.xcworkspace"],
|
|
79
|
+
"pack": "swift"
|
|
80
|
+
},
|
|
81
|
+
"objective-c": {
|
|
82
|
+
"file_extensions": [".m", ".mm"],
|
|
83
|
+
"pack": "objective-c"
|
|
84
|
+
},
|
|
85
|
+
"groovy": {
|
|
86
|
+
"file_extensions": [".groovy"],
|
|
87
|
+
"pack": "groovy"
|
|
88
|
+
},
|
|
89
|
+
"csharp": {
|
|
90
|
+
"manifest_files": ["*.csproj", "*.sln"],
|
|
91
|
+
"file_extensions": [".cs"],
|
|
92
|
+
"pack": null,
|
|
93
|
+
"_note": "C# framework pack (dotnet-aspnet-core) is more specific"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
"frameworks": {
|
|
98
|
+
"express": {
|
|
99
|
+
"ecosystem": "nodejs",
|
|
100
|
+
"dependency_signals": { "package.json": ["express"] },
|
|
101
|
+
"pack": "express"
|
|
102
|
+
},
|
|
103
|
+
"fastify": {
|
|
104
|
+
"ecosystem": "nodejs",
|
|
105
|
+
"dependency_signals": { "package.json": ["fastify"] },
|
|
106
|
+
"pack": "fastify"
|
|
107
|
+
},
|
|
108
|
+
"honojs": {
|
|
109
|
+
"ecosystem": "nodejs",
|
|
110
|
+
"dependency_signals": { "package.json": ["hono"] },
|
|
111
|
+
"pack": "honojs"
|
|
112
|
+
},
|
|
113
|
+
"nestjs": {
|
|
114
|
+
"ecosystem": "nodejs",
|
|
115
|
+
"dependency_signals": { "package.json": ["@nestjs/core"] },
|
|
116
|
+
"config_files": ["nest-cli.json"],
|
|
117
|
+
"pack": "nestjs"
|
|
118
|
+
},
|
|
119
|
+
"nextjs": {
|
|
120
|
+
"ecosystem": "nodejs",
|
|
121
|
+
"dependency_signals": { "package.json": ["next"] },
|
|
122
|
+
"config_files": ["next.config.js", "next.config.mjs", "next.config.ts"],
|
|
123
|
+
"pack": "nextjs"
|
|
124
|
+
},
|
|
125
|
+
"react": {
|
|
126
|
+
"ecosystem": "nodejs",
|
|
127
|
+
"dependency_signals": { "package.json": ["react", "react-dom"] },
|
|
128
|
+
"pack": "react"
|
|
129
|
+
},
|
|
130
|
+
"angular": {
|
|
131
|
+
"ecosystem": "nodejs",
|
|
132
|
+
"dependency_signals": { "package.json": ["@angular/core"] },
|
|
133
|
+
"config_files": ["angular.json"],
|
|
134
|
+
"pack": "angular"
|
|
135
|
+
},
|
|
136
|
+
"vuejs": {
|
|
137
|
+
"ecosystem": "nodejs",
|
|
138
|
+
"dependency_signals": { "package.json": ["vue"] },
|
|
139
|
+
"config_files": ["vue.config.js", "vite.config.ts", "nuxt.config.ts"],
|
|
140
|
+
"pack": "vuejs"
|
|
141
|
+
},
|
|
142
|
+
"electron": {
|
|
143
|
+
"ecosystem": "nodejs",
|
|
144
|
+
"dependency_signals": { "package.json": ["electron"] },
|
|
145
|
+
"pack": "electron"
|
|
146
|
+
},
|
|
147
|
+
"react-native": {
|
|
148
|
+
"ecosystem": "nodejs",
|
|
149
|
+
"dependency_signals": { "package.json": ["react-native"] },
|
|
150
|
+
"pack": "react-native"
|
|
151
|
+
},
|
|
152
|
+
"django": {
|
|
153
|
+
"ecosystem": "python",
|
|
154
|
+
"dependency_signals": {
|
|
155
|
+
"requirements.txt": ["django", "Django"],
|
|
156
|
+
"pyproject.toml": ["django", "Django"],
|
|
157
|
+
"Pipfile": ["django", "Django"]
|
|
158
|
+
},
|
|
159
|
+
"config_files": ["manage.py"],
|
|
160
|
+
"directory_signals": ["*/settings.py", "*/wsgi.py"],
|
|
161
|
+
"pack": "django"
|
|
162
|
+
},
|
|
163
|
+
"flask": {
|
|
164
|
+
"ecosystem": "python",
|
|
165
|
+
"dependency_signals": {
|
|
166
|
+
"requirements.txt": ["flask", "Flask"],
|
|
167
|
+
"pyproject.toml": ["flask", "Flask"]
|
|
168
|
+
},
|
|
169
|
+
"pack": "flask"
|
|
170
|
+
},
|
|
171
|
+
"fastapi": {
|
|
172
|
+
"ecosystem": "python",
|
|
173
|
+
"dependency_signals": {
|
|
174
|
+
"requirements.txt": ["fastapi"],
|
|
175
|
+
"pyproject.toml": ["fastapi"]
|
|
176
|
+
},
|
|
177
|
+
"pack": "fastapi"
|
|
178
|
+
},
|
|
179
|
+
"java-spring-boot": {
|
|
180
|
+
"ecosystem": "java",
|
|
181
|
+
"dependency_signals": {
|
|
182
|
+
"pom.xml": ["spring-boot-starter"],
|
|
183
|
+
"build.gradle": ["org.springframework.boot"]
|
|
184
|
+
},
|
|
185
|
+
"pack": "java-spring-boot"
|
|
186
|
+
},
|
|
187
|
+
"spring-security": {
|
|
188
|
+
"ecosystem": "java",
|
|
189
|
+
"dependency_signals": {
|
|
190
|
+
"pom.xml": ["spring-boot-starter-security", "spring-security"],
|
|
191
|
+
"build.gradle": ["spring-boot-starter-security", "spring-security"]
|
|
192
|
+
},
|
|
193
|
+
"pack": "spring-security"
|
|
194
|
+
},
|
|
195
|
+
"java-ee": {
|
|
196
|
+
"ecosystem": "java",
|
|
197
|
+
"dependency_signals": {
|
|
198
|
+
"pom.xml": ["javax.servlet", "jakarta.servlet", "javax.enterprise", "jakarta.enterprise"],
|
|
199
|
+
"build.gradle": ["javax.servlet", "jakarta.servlet"]
|
|
200
|
+
},
|
|
201
|
+
"config_files": ["web.xml"],
|
|
202
|
+
"pack": "java-ee"
|
|
203
|
+
},
|
|
204
|
+
"ruby-on-rails": {
|
|
205
|
+
"ecosystem": "ruby",
|
|
206
|
+
"dependency_signals": { "Gemfile": ["rails"] },
|
|
207
|
+
"config_files": ["config/routes.rb", "config/application.rb"],
|
|
208
|
+
"pack": "ruby-on-rails"
|
|
209
|
+
},
|
|
210
|
+
"laravel": {
|
|
211
|
+
"ecosystem": "php",
|
|
212
|
+
"dependency_signals": { "composer.json": ["laravel/framework"] },
|
|
213
|
+
"config_files": ["artisan"],
|
|
214
|
+
"directory_signals": ["app/Http/Controllers"],
|
|
215
|
+
"pack": "laravel"
|
|
216
|
+
},
|
|
217
|
+
"symfony": {
|
|
218
|
+
"ecosystem": "php",
|
|
219
|
+
"dependency_signals": { "composer.json": ["symfony/framework-bundle"] },
|
|
220
|
+
"config_files": ["config/bundles.php", "symfony.lock"],
|
|
221
|
+
"pack": "symfony"
|
|
222
|
+
},
|
|
223
|
+
"dotnet-aspnet-core": {
|
|
224
|
+
"ecosystem": "csharp",
|
|
225
|
+
"dependency_signals": { "*.csproj": ["Microsoft.AspNetCore"] },
|
|
226
|
+
"config_files": ["Program.cs", "Startup.cs", "appsettings.json"],
|
|
227
|
+
"pack": "dotnet-aspnet-core"
|
|
228
|
+
},
|
|
229
|
+
"flutter": {
|
|
230
|
+
"ecosystem": "dart",
|
|
231
|
+
"manifest_files": ["pubspec.yaml"],
|
|
232
|
+
"dependency_signals": { "pubspec.yaml": ["flutter"] },
|
|
233
|
+
"pack": "flutter"
|
|
234
|
+
},
|
|
235
|
+
"apollo-graphql": {
|
|
236
|
+
"ecosystem": "nodejs",
|
|
237
|
+
"dependency_signals": { "package.json": ["@apollo/server", "apollo-server", "apollo-server-express"] },
|
|
238
|
+
"pack": "apollo-graphql"
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
"auth_identity": {
|
|
243
|
+
"jwt": {
|
|
244
|
+
"dependency_signals": {
|
|
245
|
+
"package.json": ["jsonwebtoken", "jose", "@auth/core"],
|
|
246
|
+
"requirements.txt": ["pyjwt", "python-jose"],
|
|
247
|
+
"pyproject.toml": ["pyjwt", "python-jose"],
|
|
248
|
+
"Gemfile": ["jwt"],
|
|
249
|
+
"pom.xml": ["jjwt", "nimbus-jose-jwt"]
|
|
250
|
+
},
|
|
251
|
+
"pack": "jwt"
|
|
252
|
+
},
|
|
253
|
+
"oauth-oidc": {
|
|
254
|
+
"dependency_signals": {
|
|
255
|
+
"package.json": ["openid-client", "passport-oauth2", "oauth4webapi"],
|
|
256
|
+
"requirements.txt": ["authlib", "oauthlib"],
|
|
257
|
+
"pyproject.toml": ["authlib", "oauthlib"]
|
|
258
|
+
},
|
|
259
|
+
"pack": "oauth-oidc"
|
|
260
|
+
},
|
|
261
|
+
"auth0": {
|
|
262
|
+
"dependency_signals": {
|
|
263
|
+
"package.json": ["@auth0/nextjs-auth0", "auth0", "express-openid-connect"],
|
|
264
|
+
"requirements.txt": ["auth0-python"],
|
|
265
|
+
"pyproject.toml": ["auth0-python"]
|
|
266
|
+
},
|
|
267
|
+
"pack": "auth0"
|
|
268
|
+
},
|
|
269
|
+
"okta": {
|
|
270
|
+
"dependency_signals": {
|
|
271
|
+
"package.json": ["@okta/okta-sdk-nodejs", "@okta/oidc-middleware"],
|
|
272
|
+
"pom.xml": ["okta-spring-boot-starter"]
|
|
273
|
+
},
|
|
274
|
+
"pack": "okta"
|
|
275
|
+
},
|
|
276
|
+
"keycloak": {
|
|
277
|
+
"dependency_signals": {
|
|
278
|
+
"package.json": ["keycloak-connect", "keycloak-js"],
|
|
279
|
+
"pom.xml": ["keycloak-spring-boot-starter"]
|
|
280
|
+
},
|
|
281
|
+
"config_files": ["keycloak.json"],
|
|
282
|
+
"pack": "keycloak"
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
"ai_agent": {
|
|
287
|
+
"langchain": {
|
|
288
|
+
"dependency_signals": {
|
|
289
|
+
"requirements.txt": ["langchain"],
|
|
290
|
+
"pyproject.toml": ["langchain"],
|
|
291
|
+
"package.json": ["langchain"]
|
|
292
|
+
},
|
|
293
|
+
"pack": "langchain"
|
|
294
|
+
},
|
|
295
|
+
"langgraph": {
|
|
296
|
+
"dependency_signals": {
|
|
297
|
+
"requirements.txt": ["langgraph"],
|
|
298
|
+
"pyproject.toml": ["langgraph"]
|
|
299
|
+
},
|
|
300
|
+
"pack": "langgraph"
|
|
301
|
+
},
|
|
302
|
+
"llamaindex": {
|
|
303
|
+
"dependency_signals": {
|
|
304
|
+
"requirements.txt": ["llama-index", "llama_index"],
|
|
305
|
+
"pyproject.toml": ["llama-index", "llama_index"]
|
|
306
|
+
},
|
|
307
|
+
"pack": "llamaindex"
|
|
308
|
+
},
|
|
309
|
+
"crewai": {
|
|
310
|
+
"dependency_signals": {
|
|
311
|
+
"requirements.txt": ["crewai"],
|
|
312
|
+
"pyproject.toml": ["crewai"]
|
|
313
|
+
},
|
|
314
|
+
"pack": "crewai"
|
|
315
|
+
},
|
|
316
|
+
"mastra": {
|
|
317
|
+
"dependency_signals": {
|
|
318
|
+
"package.json": ["@mastra/core", "mastra"]
|
|
319
|
+
},
|
|
320
|
+
"pack": "mastra"
|
|
321
|
+
},
|
|
322
|
+
"ai-sdk": {
|
|
323
|
+
"dependency_signals": {
|
|
324
|
+
"package.json": ["ai", "@ai-sdk/openai", "@ai-sdk/anthropic"]
|
|
325
|
+
},
|
|
326
|
+
"pack": "ai-sdk"
|
|
327
|
+
},
|
|
328
|
+
"claude-agent-sdk": {
|
|
329
|
+
"dependency_signals": {
|
|
330
|
+
"package.json": ["@anthropic-ai/sdk"],
|
|
331
|
+
"requirements.txt": ["anthropic"],
|
|
332
|
+
"pyproject.toml": ["anthropic"]
|
|
333
|
+
},
|
|
334
|
+
"pack": "claude-agent-sdk"
|
|
335
|
+
},
|
|
336
|
+
"openai-agents-sdk": {
|
|
337
|
+
"dependency_signals": {
|
|
338
|
+
"package.json": ["openai"],
|
|
339
|
+
"requirements.txt": ["openai"],
|
|
340
|
+
"pyproject.toml": ["openai"]
|
|
341
|
+
},
|
|
342
|
+
"pack": "openai-agents-sdk"
|
|
343
|
+
},
|
|
344
|
+
"mcp": {
|
|
345
|
+
"dependency_signals": {
|
|
346
|
+
"package.json": ["@modelcontextprotocol/sdk"],
|
|
347
|
+
"requirements.txt": ["mcp"],
|
|
348
|
+
"pyproject.toml": ["mcp"]
|
|
349
|
+
},
|
|
350
|
+
"pack": "mcp"
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
"infrastructure": {
|
|
355
|
+
"docker": {
|
|
356
|
+
"config_files": ["Dockerfile", "docker-compose.yml", "docker-compose.yaml", ".dockerignore"],
|
|
357
|
+
"pack": "docker"
|
|
358
|
+
},
|
|
359
|
+
"kubernetes": {
|
|
360
|
+
"config_files": ["helmfile.yaml", "Chart.yaml", "kustomization.yaml"],
|
|
361
|
+
"directory_signals": ["k8s/", "kubernetes/", "helm/"],
|
|
362
|
+
"content_signals": { "*.yaml": ["apiVersion", "kind: Deployment", "kind: Service", "kind: Pod"] },
|
|
363
|
+
"pack": "kubernetes"
|
|
364
|
+
},
|
|
365
|
+
"terraform-aws": {
|
|
366
|
+
"file_extensions": [".tf"],
|
|
367
|
+
"content_signals": { "*.tf": ["provider \"aws\"", "aws_"] },
|
|
368
|
+
"pack": "terraform-aws"
|
|
369
|
+
},
|
|
370
|
+
"terraform-azure": {
|
|
371
|
+
"file_extensions": [".tf"],
|
|
372
|
+
"content_signals": { "*.tf": ["provider \"azurerm\"", "azurerm_"] },
|
|
373
|
+
"pack": "terraform-azure"
|
|
374
|
+
},
|
|
375
|
+
"terraform-gcp": {
|
|
376
|
+
"file_extensions": [".tf"],
|
|
377
|
+
"content_signals": { "*.tf": ["provider \"google\"", "google_"] },
|
|
378
|
+
"pack": "terraform-gcp"
|
|
379
|
+
},
|
|
380
|
+
"cloudformation": {
|
|
381
|
+
"content_signals": { "*.yaml": ["AWSTemplateFormatVersion"], "*.json": ["AWSTemplateFormatVersion"] },
|
|
382
|
+
"config_files": ["template.yaml", "template.json", "samconfig.toml"],
|
|
383
|
+
"pack": "cloudformation"
|
|
384
|
+
},
|
|
385
|
+
"nginx": {
|
|
386
|
+
"config_files": ["nginx.conf"],
|
|
387
|
+
"directory_signals": ["nginx/"],
|
|
388
|
+
"pack": "nginx"
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
|
|
392
|
+
"ci_cd": {
|
|
393
|
+
"github-actions": {
|
|
394
|
+
"directory_signals": [".github/workflows/"],
|
|
395
|
+
"pack": "github-actions"
|
|
396
|
+
},
|
|
397
|
+
"jenkins-pipeline": {
|
|
398
|
+
"config_files": ["Jenkinsfile"],
|
|
399
|
+
"pack": "jenkins-pipeline"
|
|
400
|
+
},
|
|
401
|
+
"argocd": {
|
|
402
|
+
"config_files": ["argocd-cm.yaml"],
|
|
403
|
+
"content_signals": { "*.yaml": ["argoproj.io"] },
|
|
404
|
+
"pack": "argocd"
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
"cloud_compute": {
|
|
409
|
+
"aws-lambda": {
|
|
410
|
+
"config_files": ["serverless.yml", "serverless.yaml", "serverless.ts", "sam.yaml", "samconfig.toml"],
|
|
411
|
+
"content_signals": {
|
|
412
|
+
"serverless.yml": ["provider:", "aws"],
|
|
413
|
+
"template.yaml": ["AWS::Serverless::Function", "AWS::Lambda::Function"]
|
|
414
|
+
},
|
|
415
|
+
"pack": "aws-lambda"
|
|
416
|
+
},
|
|
417
|
+
"azure-functions": {
|
|
418
|
+
"config_files": ["host.json", "local.settings.json"],
|
|
419
|
+
"directory_signals": ["function_app.py"],
|
|
420
|
+
"pack": "azure-functions"
|
|
421
|
+
},
|
|
422
|
+
"gcp-cloud-run": {
|
|
423
|
+
"config_files": ["app.yaml", "cloudbuild.yaml"],
|
|
424
|
+
"content_signals": { "*.yaml": ["gcr.io", "run.googleapis.com"] },
|
|
425
|
+
"pack": "gcp-cloud-run"
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
|
|
429
|
+
"databases": {
|
|
430
|
+
"mongodb": {
|
|
431
|
+
"dependency_signals": {
|
|
432
|
+
"package.json": ["mongoose", "mongodb"],
|
|
433
|
+
"requirements.txt": ["pymongo", "motor", "mongoengine"],
|
|
434
|
+
"pyproject.toml": ["pymongo", "motor"],
|
|
435
|
+
"Gemfile": ["mongoid"]
|
|
436
|
+
},
|
|
437
|
+
"pack": "mongodb"
|
|
438
|
+
},
|
|
439
|
+
"postgresql": {
|
|
440
|
+
"dependency_signals": {
|
|
441
|
+
"package.json": ["pg", "knex", "prisma", "typeorm", "sequelize"],
|
|
442
|
+
"requirements.txt": ["psycopg2", "asyncpg", "sqlalchemy"],
|
|
443
|
+
"pyproject.toml": ["psycopg2", "asyncpg", "sqlalchemy"],
|
|
444
|
+
"Gemfile": ["pg"],
|
|
445
|
+
"pom.xml": ["postgresql"]
|
|
446
|
+
},
|
|
447
|
+
"pack": "postgresql"
|
|
448
|
+
},
|
|
449
|
+
"mysql-mariadb": {
|
|
450
|
+
"dependency_signals": {
|
|
451
|
+
"package.json": ["mysql2", "mysql"],
|
|
452
|
+
"requirements.txt": ["mysqlclient", "pymysql", "aiomysql"],
|
|
453
|
+
"pyproject.toml": ["mysqlclient", "pymysql"],
|
|
454
|
+
"pom.xml": ["mysql-connector-java"]
|
|
455
|
+
},
|
|
456
|
+
"pack": "mysql-mariadb"
|
|
457
|
+
},
|
|
458
|
+
"redis": {
|
|
459
|
+
"dependency_signals": {
|
|
460
|
+
"package.json": ["redis", "ioredis"],
|
|
461
|
+
"requirements.txt": ["redis", "aioredis"],
|
|
462
|
+
"pyproject.toml": ["redis"],
|
|
463
|
+
"Gemfile": ["redis"]
|
|
464
|
+
},
|
|
465
|
+
"pack": "redis"
|
|
466
|
+
}
|
|
467
|
+
},
|
|
468
|
+
|
|
469
|
+
"messaging": {
|
|
470
|
+
"kafka": {
|
|
471
|
+
"dependency_signals": {
|
|
472
|
+
"package.json": ["kafkajs"],
|
|
473
|
+
"requirements.txt": ["confluent-kafka", "aiokafka"],
|
|
474
|
+
"pyproject.toml": ["confluent-kafka"],
|
|
475
|
+
"pom.xml": ["kafka-clients", "spring-kafka"]
|
|
476
|
+
},
|
|
477
|
+
"pack": "kafka"
|
|
478
|
+
},
|
|
479
|
+
"rabbitmq": {
|
|
480
|
+
"dependency_signals": {
|
|
481
|
+
"package.json": ["amqplib"],
|
|
482
|
+
"requirements.txt": ["pika", "aio-pika"],
|
|
483
|
+
"pyproject.toml": ["pika"],
|
|
484
|
+
"pom.xml": ["spring-boot-starter-amqp"]
|
|
485
|
+
},
|
|
486
|
+
"pack": "rabbitmq"
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
"api_protocols": {
|
|
491
|
+
"grpc": {
|
|
492
|
+
"dependency_signals": {
|
|
493
|
+
"package.json": ["@grpc/grpc-js"],
|
|
494
|
+
"requirements.txt": ["grpcio"],
|
|
495
|
+
"pyproject.toml": ["grpcio"],
|
|
496
|
+
"pom.xml": ["grpc-netty"]
|
|
497
|
+
},
|
|
498
|
+
"file_extensions": [".proto"],
|
|
499
|
+
"pack": "grpc"
|
|
500
|
+
},
|
|
501
|
+
"websockets": {
|
|
502
|
+
"dependency_signals": {
|
|
503
|
+
"package.json": ["ws", "socket.io"],
|
|
504
|
+
"requirements.txt": ["websockets"],
|
|
505
|
+
"pyproject.toml": ["websockets"]
|
|
506
|
+
},
|
|
507
|
+
"pack": "websockets"
|
|
508
|
+
},
|
|
509
|
+
"openapi-rest-api": {
|
|
510
|
+
"config_files": ["openapi.yaml", "openapi.json", "swagger.yaml", "swagger.json"],
|
|
511
|
+
"pack": "openapi-rest-api"
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { copyFileSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { ensureDir, writeText } from './fs-helpers.js';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
function sanitizeProjectName(value) {
|
|
9
|
+
return String(value || '').replace(/[\r\n`]/g, ' ').trim();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function injectProjectName(content, projectName) {
|
|
13
|
+
const resolved = sanitizeProjectName(projectName) || '<SRAI_PROJECT_NAME>';
|
|
14
|
+
return content.replaceAll('{{SRAI_PROJECT_NAME}}', resolved).replaceAll('<SRAI_PROJECT_NAME>', resolved);
|
|
15
|
+
}
|
|
16
|
+
|
|
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);
|
|
26
|
+
|
|
27
|
+
const skillTemplate = readFileSync(join(BUNDLE_ROOT, 'SKILL.md'), 'utf-8');
|
|
28
|
+
writeText(join(destBase, 'SKILL.md'), injectProjectName(skillTemplate, options.projectName));
|
|
29
|
+
|
|
30
|
+
copyFileSync(join(BUNDLE_ROOT, 'references', 'signal-registry.json'), join(destRefs, 'signal-registry.json'));
|
|
31
|
+
|
|
32
|
+
return destBase;
|
|
33
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
const AGENT_CLI_TARGETS = new Set(['cursor', 'claude', 'codex']);
|
|
4
|
+
|
|
5
|
+
function commandOk(cmd, args = ['--version']) {
|
|
6
|
+
const r = spawnSync(cmd, args, { stdio: 'ignore' });
|
|
7
|
+
return r.status === 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function runShell(script) {
|
|
11
|
+
return spawnSync(script, { shell: true, stdio: 'inherit' });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Ensure Cursor / Claude Code / Codex CLIs are present when those targets are selected.
|
|
16
|
+
* Installation uses vendor scripts or npm where appropriate.
|
|
17
|
+
*/
|
|
18
|
+
export function ensureIdeCliForTarget(target, options = {}) {
|
|
19
|
+
const { skipInstall = false } = options;
|
|
20
|
+
|
|
21
|
+
if (!AGENT_CLI_TARGETS.has(target)) {
|
|
22
|
+
return { target, ok: true, skipped: true };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (target === 'cursor') {
|
|
26
|
+
if (commandOk('cursor-agent', ['--version'])) {
|
|
27
|
+
return { target, ok: true, already: true };
|
|
28
|
+
}
|
|
29
|
+
if (skipInstall) {
|
|
30
|
+
return { target, ok: false, message: 'cursor-agent not found on PATH' };
|
|
31
|
+
}
|
|
32
|
+
if (process.platform === 'win32') {
|
|
33
|
+
return {
|
|
34
|
+
target,
|
|
35
|
+
ok: false,
|
|
36
|
+
message: 'Install Cursor CLI manually: https://cursor.com/cli',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const r = runShell('curl -fsSL https://cursor.com/install | bash');
|
|
40
|
+
return r.status === 0
|
|
41
|
+
? { target, ok: true }
|
|
42
|
+
: { target, ok: false, message: 'Cursor CLI install script failed' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (target === 'claude') {
|
|
46
|
+
if (commandOk('claude', ['--version'])) {
|
|
47
|
+
return { target, ok: true, already: true };
|
|
48
|
+
}
|
|
49
|
+
if (skipInstall) {
|
|
50
|
+
return { target, ok: false, message: 'claude not found on PATH' };
|
|
51
|
+
}
|
|
52
|
+
if (process.platform === 'win32') {
|
|
53
|
+
const r = runShell('powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://claude.ai/install.ps1 | iex"');
|
|
54
|
+
return r.status === 0
|
|
55
|
+
? { target, ok: true }
|
|
56
|
+
: { target, ok: false, message: 'Claude Code install failed' };
|
|
57
|
+
}
|
|
58
|
+
const r = runShell('curl -fsSL https://claude.ai/install.sh | bash');
|
|
59
|
+
return r.status === 0
|
|
60
|
+
? { target, ok: true }
|
|
61
|
+
: { target, ok: false, message: 'Claude Code install failed' };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (target === 'codex') {
|
|
65
|
+
if (commandOk('codex', ['--version'])) {
|
|
66
|
+
return { target, ok: true, already: true };
|
|
67
|
+
}
|
|
68
|
+
if (skipInstall) {
|
|
69
|
+
return { target, ok: false, message: 'codex not found on PATH' };
|
|
70
|
+
}
|
|
71
|
+
const r = runShell('npm install -g @openai/codex');
|
|
72
|
+
return r.status === 0
|
|
73
|
+
? { target, ok: true }
|
|
74
|
+
: { target, ok: false, message: 'Codex CLI npm install failed' };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { target, ok: true, skipped: true };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function ensureIdeClisForTargets(targets, options = {}) {
|
|
81
|
+
return targets.map((t) => ensureIdeCliForTarget(t, options));
|
|
82
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
const PREFERRED_ORDER = ['cursor', 'claude', 'codex'];
|
|
4
|
+
|
|
5
|
+
function commandOk(cmd, args = ['--version']) {
|
|
6
|
+
const r = spawnSync(cmd, args, { stdio: 'ignore' });
|
|
7
|
+
return r.status === 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function buildProfilerAgentPrompt(projectName) {
|
|
11
|
+
const p = String(projectName || '').trim() || '<SRAI_PROJECT_NAME>';
|
|
12
|
+
return [
|
|
13
|
+
'Security Review Kit: run guardrails initialization profiling for this workspace.',
|
|
14
|
+
`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.',
|
|
17
|
+
'Write .guardrails/profile.json and profile.json at the repository root as defined in the skill.',
|
|
18
|
+
`Resolve project_id with find_project_by_name for "${p}", then call update_vibe_profile and write_default_pack via security-review-mcp.`,
|
|
19
|
+
'Do not invent stack details, compliance, or user groups; ground everything in the repository.',
|
|
20
|
+
].join('\n');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function pickProfilerAgentTarget(targets) {
|
|
24
|
+
for (const t of PREFERRED_ORDER) {
|
|
25
|
+
if (targets.includes(t)) {
|
|
26
|
+
return t;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Spawn the IDE agent CLI to execute the profiler skill (user must be logged in where required).
|
|
34
|
+
*/
|
|
35
|
+
export function runProfilerAgent(cwd, { target, projectName }) {
|
|
36
|
+
const prompt = buildProfilerAgentPrompt(projectName);
|
|
37
|
+
const opts = { cwd, stdio: 'inherit', env: { ...process.env } };
|
|
38
|
+
|
|
39
|
+
if (target === 'cursor') {
|
|
40
|
+
if (!commandOk('cursor-agent', ['--version'])) {
|
|
41
|
+
return { ok: false, message: 'cursor-agent not on PATH' };
|
|
42
|
+
}
|
|
43
|
+
const r = spawnSync('cursor-agent', ['-p', prompt], opts);
|
|
44
|
+
return { ok: r.status === 0, status: r.status };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (target === 'claude') {
|
|
48
|
+
if (!commandOk('claude', ['--version'])) {
|
|
49
|
+
return { ok: false, message: 'claude not on PATH' };
|
|
50
|
+
}
|
|
51
|
+
const r = spawnSync('claude', ['-p', prompt], opts);
|
|
52
|
+
return { ok: r.status === 0, status: r.status };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (target === 'codex') {
|
|
56
|
+
if (!commandOk('codex', ['--version'])) {
|
|
57
|
+
return { ok: false, message: 'codex not on PATH' };
|
|
58
|
+
}
|
|
59
|
+
const r = spawnSync('codex', ['exec', prompt], opts);
|
|
60
|
+
return { ok: r.status === 0, status: r.status };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { ok: false, message: 'unsupported agent target' };
|
|
64
|
+
}
|