@securityreviewai/securityreview-kit 0.1.48 → 0.1.49

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.
Files changed (78) hide show
  1. package/dist/api.js +44 -0
  2. package/dist/commands/guardrails.js +13 -0
  3. package/dist/commands/init.js +88 -0
  4. package/dist/commands/profile.js +14 -0
  5. package/dist/commands/status.js +27 -0
  6. package/dist/commands/sync.js +6 -0
  7. package/dist/config.js +18 -0
  8. package/dist/fs.js +43 -0
  9. package/dist/index.js +44 -0
  10. package/dist/profile.js +113 -0
  11. package/dist/scaffold/claude-code.js +37 -0
  12. package/dist/scaffold/codex.js +35 -0
  13. package/dist/scaffold/cursor.js +39 -0
  14. package/dist/scaffold/gemini.js +10 -0
  15. package/dist/scaffold/index.js +22 -0
  16. package/dist/scaffold/mcp.js +15 -0
  17. package/dist/scaffold/rules.js +165 -0
  18. package/dist/scaffold/vibreview.js +24 -0
  19. package/dist/scaffold/vscode.js +22 -0
  20. package/dist/scaffold/windsurf.js +10 -0
  21. package/dist/sync/index.js +34 -0
  22. package/dist/sync/payload.js +23 -0
  23. package/dist/sync/state.js +12 -0
  24. package/dist/types.js +1 -0
  25. package/package.json +24 -30
  26. package/templates/claude/CLAUDE.md +13 -0
  27. package/templates/claude/agents/guardrail_profiler.md +12 -0
  28. package/templates/claude/agents/threat_modeler.md +5 -0
  29. package/templates/claude/skills/vibreview/SKILL.md +21 -0
  30. package/templates/claude/skills/vibreview/guardrail_patterns.md +12 -0
  31. package/templates/cursor/rules/vibreview-security.mdc +8 -0
  32. package/README.md +0 -105
  33. package/bin/securityreview-kit.js +0 -5
  34. package/src/cli.js +0 -109
  35. package/src/commands/init.js +0 -851
  36. package/src/commands/status.js +0 -99
  37. package/src/commands/switch-project.js +0 -207
  38. package/src/generators/mcp/claude.js +0 -85
  39. package/src/generators/mcp/claude.test.js +0 -64
  40. package/src/generators/mcp/codex.js +0 -70
  41. package/src/generators/mcp/codex.test.js +0 -43
  42. package/src/generators/mcp/cursor.js +0 -29
  43. package/src/generators/mcp/cursor.test.js +0 -50
  44. package/src/generators/mcp/gemini.js +0 -28
  45. package/src/generators/mcp/vscode.js +0 -29
  46. package/src/generators/mcp/windsurf.js +0 -27
  47. package/src/generators/rules/antigravity.js +0 -22
  48. package/src/generators/rules/claude.js +0 -87
  49. package/src/generators/rules/claude.test.js +0 -60
  50. package/src/generators/rules/codex.js +0 -141
  51. package/src/generators/rules/codex.test.js +0 -59
  52. package/src/generators/rules/content.js +0 -110
  53. package/src/generators/rules/content.md +0 -57
  54. package/src/generators/rules/cursor.js +0 -128
  55. package/src/generators/rules/gemini.js +0 -13
  56. package/src/generators/rules/guardrails-init-profile.md +0 -56
  57. package/src/generators/rules/guardrails-profiler/SKILL.md +0 -130
  58. package/src/generators/rules/guardrails-profiler/references/signal-registry.json +0 -514
  59. package/src/generators/rules/guardrails-selection/SKILL.md +0 -187
  60. package/src/generators/rules/guardrails-selection/references/category-threat-map.md +0 -232
  61. package/src/generators/rules/guardrails_rule.md +0 -94
  62. package/src/generators/rules/hooks.json +0 -11
  63. package/src/generators/rules/skill.md +0 -256
  64. package/src/generators/rules/srai-profile.md +0 -32
  65. package/src/generators/rules/vibereview-sync/SKILL.md +0 -378
  66. package/src/generators/rules/vscode.js +0 -101
  67. package/src/generators/rules/vscode.test.js +0 -54
  68. package/src/generators/rules/windsurf.js +0 -13
  69. package/src/utils/constants.js +0 -95
  70. package/src/utils/cursor-agent-path.js +0 -67
  71. package/src/utils/cursor-cli-permissions.js +0 -28
  72. package/src/utils/detect.js +0 -27
  73. package/src/utils/fs-helpers.js +0 -82
  74. package/src/utils/guardrails-profiler-bundle.js +0 -84
  75. package/src/utils/ide-cli-install.js +0 -138
  76. package/src/utils/profiler-agent.js +0 -446
  77. package/src/utils/profiler-agent.test.js +0 -81
  78. package/src/utils/srai.js +0 -252
@@ -1,29 +0,0 @@
1
- import { join } from 'node:path';
2
- import { readJson, writeJson } from '../../utils/fs-helpers.js';
3
- import { MCP_SERVER_NAME, MCP_SERVER_PACKAGE } from '../../utils/constants.js';
4
-
5
- /**
6
- * Generate VS Code Copilot MCP config at .vscode/mcp.json
7
- * Uses the VS Code input variable pattern for secure credential prompting.
8
- */
9
- export function generate(cwd, envVars) {
10
- const filePath = join(cwd, '.vscode', 'mcp.json');
11
- const existing = readJson(filePath) || {};
12
-
13
- if (!existing.servers) {
14
- existing.servers = {};
15
- }
16
-
17
- existing.servers[MCP_SERVER_NAME] = {
18
- type: 'stdio',
19
- command: 'npx',
20
- args: ['-y', `${MCP_SERVER_PACKAGE}@latest`],
21
- env: {
22
- SECURITY_REVIEW_API_URL: envVars.apiUrl,
23
- SECURITY_REVIEW_API_TOKEN: envVars.apiToken,
24
- },
25
- };
26
-
27
- writeJson(filePath, existing);
28
- return filePath;
29
- }
@@ -1,27 +0,0 @@
1
- import { join } from 'node:path';
2
- import { readJson, writeJson } from '../../utils/fs-helpers.js';
3
- import { MCP_SERVER_NAME, MCP_SERVER_PACKAGE } from '../../utils/constants.js';
4
-
5
- /**
6
- * Generate Windsurf MCP config at .windsurf/mcp_config.json
7
- */
8
- export function generate(cwd, envVars) {
9
- const filePath = join(cwd, '.windsurf', 'mcp_config.json');
10
- const existing = readJson(filePath) || {};
11
-
12
- if (!existing.mcpServers) {
13
- existing.mcpServers = {};
14
- }
15
-
16
- existing.mcpServers[MCP_SERVER_NAME] = {
17
- command: 'npx',
18
- args: ['-y', `${MCP_SERVER_PACKAGE}@latest`],
19
- env: {
20
- SECURITY_REVIEW_API_URL: envVars.apiUrl,
21
- SECURITY_REVIEW_API_TOKEN: envVars.apiToken,
22
- },
23
- };
24
-
25
- writeJson(filePath, existing);
26
- return filePath;
27
- }
@@ -1,22 +0,0 @@
1
- import { join } from 'node:path';
2
- import { writeText } from '../../utils/fs-helpers.js';
3
- import { getRuleContent } from './content.js';
4
-
5
- /**
6
- * Generate Antigravity workspace rule at .agent/rules/srai-security-review.md
7
- * Antigravity uses .agent/rules/ with YAML frontmatter (trigger: always_on).
8
- */
9
- export function generate(cwd, options = {}) {
10
- const filePath = join(cwd, '.agent', 'rules', 'srai-security-review.md');
11
- const content = getRuleContent(options);
12
-
13
- const rule = `---
14
- trigger: always_on
15
- ---
16
-
17
- ${content}
18
- `;
19
-
20
- writeText(filePath, rule);
21
- return filePath;
22
- }
@@ -1,87 +0,0 @@
1
- import { existsSync, unlinkSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import {
4
- GUARDRAILS_PROFILER_SKILL_REL_DIR,
5
- GUARDRAILS_SELECTION_SKILL_REL_DIR,
6
- SENTINEL_END,
7
- SENTINEL_START,
8
- THREAT_MODELLING_SKILL_REL_DIR,
9
- VIBEREVIEW_SYNC_SKILL_REL_DIR,
10
- } from '../../utils/constants.js';
11
- import { readText, upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
12
- import {
13
- getGuardrailsInitProfileContent,
14
- getRuleContent,
15
- getThreatModellingSkillContent,
16
- getVibeReviewSyncSkillContent,
17
- } from './content.js';
18
-
19
- function writeGeneratedText(filePath, content) {
20
- const action = existsSync(filePath) ? 'updated' : 'created';
21
- writeText(filePath, content);
22
- return { filePath, action };
23
- }
24
-
25
- /**
26
- * Generate Claude Code workspace rule — appends to .claude/CLAUDE.md
27
- */
28
- export function generate(cwd, options = {}) {
29
- const optionsWithSkillDirs = {
30
- ...options,
31
- guardrailsSelectionSkillDir: GUARDRAILS_SELECTION_SKILL_REL_DIR.claude,
32
- threatModellingSkillDir: THREAT_MODELLING_SKILL_REL_DIR.claude,
33
- vibereviewSyncSkillDir: VIBEREVIEW_SYNC_SKILL_REL_DIR.claude,
34
- };
35
- const legacyRootPath = join(cwd, 'CLAUDE.md');
36
- const filePath = join(cwd, '.claude', 'CLAUDE.md');
37
- const content = getRuleContent(optionsWithSkillDirs);
38
- const action = upsertSentinelBlock(filePath, content);
39
-
40
- const legacyContent = readText(legacyRootPath);
41
- if (legacyContent.includes(SENTINEL_START) && legacyContent.includes(SENTINEL_END)) {
42
- const startIdx = legacyContent.indexOf(SENTINEL_START);
43
- const endIdx = legacyContent.indexOf(SENTINEL_END);
44
- const before = legacyContent.substring(0, startIdx);
45
- const after = legacyContent.substring(endIdx + SENTINEL_END.length);
46
- const migrated = `${before}${after}`.trim();
47
- if (migrated) {
48
- writeText(legacyRootPath, migrated + '\n');
49
- } else if (existsSync(legacyRootPath)) {
50
- unlinkSync(legacyRootPath);
51
- }
52
- }
53
-
54
- const threatSkillPath = join(cwd, THREAT_MODELLING_SKILL_REL_DIR.claude, 'SKILL.md');
55
- const threatSkill = writeGeneratedText(
56
- threatSkillPath,
57
- getThreatModellingSkillContent(optionsWithSkillDirs),
58
- );
59
-
60
- const vibereviewSyncSkillPath = join(cwd, VIBEREVIEW_SYNC_SKILL_REL_DIR.claude, 'SKILL.md');
61
- const vibereviewSyncSkill = writeGeneratedText(
62
- vibereviewSyncSkillPath,
63
- getVibeReviewSyncSkillContent(optionsWithSkillDirs),
64
- );
65
-
66
- const deletedLegacyAgentPath = join(cwd, '.claude', 'agents', 'ctm_sync.md');
67
- const deletedLegacyAgent = existsSync(deletedLegacyAgentPath)
68
- ? (unlinkSync(deletedLegacyAgentPath), { filePath: deletedLegacyAgentPath, action: 'deleted' })
69
- : null;
70
-
71
- const guardrailsInitPath = join(cwd, '.claude', 'commands', 'guardrails-init-profile.md');
72
- const guardrailsInit = writeGeneratedText(
73
- guardrailsInitPath,
74
- getGuardrailsInitProfileContent({
75
- ...optionsWithSkillDirs,
76
- guardrailsSkillDir: GUARDRAILS_PROFILER_SKILL_REL_DIR.claude,
77
- }),
78
- );
79
-
80
- return [
81
- { filePath, action, kind: 'rule' },
82
- { ...threatSkill, kind: 'skill' },
83
- { ...vibereviewSyncSkill, kind: 'skill' },
84
- { ...guardrailsInit, kind: 'command' },
85
- ...(deletedLegacyAgent ? [{ ...deletedLegacyAgent, kind: 'cleanup' }] : []),
86
- ];
87
- }
@@ -1,60 +0,0 @@
1
- import { existsSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
4
- import { test } from 'node:test';
5
- import assert from 'node:assert/strict';
6
- import { generate } from './claude.js';
7
-
8
- test('Claude generator writes .claude/CLAUDE.md, threat skill, and profiling command', () => {
9
- const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-claude-'));
10
-
11
- const results = generate(cwd, { projectName: 'SmokeProject' });
12
-
13
- const expectedPaths = [
14
- '.claude/CLAUDE.md',
15
- '.claude/skills/threat-modelling/SKILL.md',
16
- '.claude/skills/vibereview-sync/SKILL.md',
17
- '.claude/commands/guardrails-init-profile.md',
18
- ];
19
-
20
- for (const relPath of expectedPaths) {
21
- assert.equal(existsSync(join(cwd, relPath)), true, `${relPath} should exist`);
22
- }
23
-
24
- assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', 'skill', 'command']);
25
-
26
- const instructions = readFileSync(join(cwd, '.claude/CLAUDE.md'), 'utf8');
27
- assert.match(instructions, /\.claude\/skills\/guardrails-selection\/SKILL\.md/);
28
- assert.match(instructions, /\.claude\/skills\/threat-modelling\/SKILL\.md/);
29
- assert.match(instructions, /\.claude\/skills\/vibereview-sync\/SKILL\.md/);
30
- assert.match(instructions, /sync_ai_ide_markdown/);
31
- assert.match(instructions, /vibereview\//);
32
-
33
- const threatSkill = readFileSync(join(cwd, '.claude/skills/threat-modelling/SKILL.md'), 'utf8');
34
- assert.match(threatSkill, /sync_ai_ide_markdown/);
35
- assert.match(threatSkill, /vibereview\//);
36
- assert.doesNotMatch(threatSkill, /get_project_profile_description/);
37
-
38
- const vibereviewSkill = readFileSync(join(cwd, '.claude/skills/vibereview-sync/SKILL.md'), 'utf8');
39
- assert.match(vibereviewSkill, /Do not read other/i);
40
- assert.match(vibereviewSkill, /sync_ai_ide_markdown/);
41
- assert.match(vibereviewSkill, /Event Identity/);
42
- assert.match(vibereviewSkill, /Practice 1/);
43
- assert.match(vibereviewSkill, /practice_name:/);
44
- });
45
-
46
- test('Claude generator migrates the kit-managed root CLAUDE.md block into .claude/CLAUDE.md', () => {
47
- const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-claude-migrate-'));
48
- const legacyPath = join(cwd, 'CLAUDE.md');
49
-
50
- writeFileSync(
51
- legacyPath,
52
- '<!-- securityreview-kit:start -->\nold managed block\n<!-- securityreview-kit:end -->\n',
53
- 'utf8',
54
- );
55
-
56
- generate(cwd, { projectName: 'SmokeProject' });
57
-
58
- assert.equal(existsSync(join(cwd, '.claude/CLAUDE.md')), true);
59
- assert.equal(existsSync(legacyPath), false);
60
- });
@@ -1,141 +0,0 @@
1
- import { existsSync, unlinkSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import {
4
- GUARDRAILS_PROFILER_SKILL_REL_DIR,
5
- GUARDRAILS_SELECTION_SKILL_REL_DIR,
6
- THREAT_MODELLING_SKILL_REL_DIR,
7
- VIBEREVIEW_SYNC_SKILL_REL_DIR,
8
- } from '../../utils/constants.js';
9
- import { upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
10
- import {
11
- getGuardrailsInitProfileContent,
12
- getRuleContent,
13
- getThreatModellingSkillContent,
14
- getVibeReviewSyncSkillContent,
15
- } from './content.js';
16
-
17
- function writeGeneratedText(filePath, content) {
18
- const action = existsSync(filePath) ? 'updated' : 'created';
19
- writeText(filePath, content);
20
- return { filePath, action };
21
- }
22
-
23
- function removeGeneratedText(filePath) {
24
- if (existsSync(filePath)) {
25
- unlinkSync(filePath);
26
- return { filePath, action: 'deleted' };
27
- }
28
- return null;
29
- }
30
-
31
- function getCodexSessionHookContent() {
32
- const additionalContext = [
33
- '## MANDATORY SECURITY GATE (Session Policy)',
34
- '',
35
- 'For any request with security impact (auth, authorization, crypto, input handling, secrets, network, data storage, dependencies, new APIs/endpoints, infrastructure, or code handling untrusted data), follow this order:',
36
- '',
37
- '1. Fetch Vibe Guardrails first using .codex/skills/guardrails-selection/SKILL.md. Resolve the project, call get_guardrails, shortlist relevant guardrails, then call get_guardrail_by_id for the shortlist.',
38
- '2. Run PWNISMS threat modelling using .codex/skills/threat-modelling/SKILL.md. Explicitly walk Product, Workload, Network, IAM, Secrets, Monitoring, and Supply Chain.',
39
- '3. Implement secure code using both the hydrated guardrails and PWNISMS findings.',
40
- '4. Read .codex/skills/vibereview-sync/SKILL.md. Write a structured .md artifact under vibereview/, do not read sibling markdown files there just to infer format, validate it, and call sync_ai_ide_markdown directly after threat modelling or guardrail enforcement. Reuse the exact guardrail shortlist; do not re-query guardrails during the final sync.',
41
- '',
42
- 'Do not use project-profile exploration tools during normal coding tasks. No blocking and no deferral: guardrails first, PWNISMS second, implementation third, VibeReview sync last.',
43
- ].join('\n');
44
-
45
- const sessionStartPayload = JSON.stringify({
46
- hookSpecificOutput: {
47
- hookEventName: 'SessionStart',
48
- additionalContext,
49
- },
50
- });
51
- const userPromptPayload = JSON.stringify({
52
- hookSpecificOutput: {
53
- hookEventName: 'UserPromptSubmit',
54
- additionalContext,
55
- },
56
- });
57
-
58
- return JSON.stringify(
59
- {
60
- hooks: {
61
- SessionStart: [
62
- {
63
- matcher: 'startup|resume',
64
- hooks: [
65
- {
66
- type: 'command',
67
- command: `printf '%s\\n' '${sessionStartPayload.replaceAll("'", "'\\''")}'`,
68
- timeout: 5,
69
- statusMessage: 'Loading SRAI security session policy',
70
- },
71
- ],
72
- },
73
- ],
74
- UserPromptSubmit: [
75
- {
76
- hooks: [
77
- {
78
- type: 'command',
79
- command: `printf '%s\\n' '${userPromptPayload.replaceAll("'", "'\\''")}'`,
80
- timeout: 5,
81
- statusMessage: 'Refreshing SRAI security session policy',
82
- },
83
- ],
84
- },
85
- ],
86
- },
87
- },
88
- null,
89
- 2,
90
- ) + '\n';
91
- }
92
-
93
- /**
94
- * Generate Codex workspace rule — appends to AGENTS.md
95
- */
96
- export function generate(cwd, options = {}) {
97
- const optionsWithSkillDirs = {
98
- ...options,
99
- guardrailsSelectionSkillDir: GUARDRAILS_SELECTION_SKILL_REL_DIR.codex,
100
- threatModellingSkillDir: THREAT_MODELLING_SKILL_REL_DIR.codex,
101
- vibereviewSyncSkillDir: VIBEREVIEW_SYNC_SKILL_REL_DIR.codex,
102
- };
103
- const filePath = join(cwd, '.codex', 'AGENTS.md');
104
- const content = getRuleContent(optionsWithSkillDirs);
105
- const action = upsertSentinelBlock(filePath, content);
106
-
107
- const threatSkillPath = join(cwd, THREAT_MODELLING_SKILL_REL_DIR.codex, 'SKILL.md');
108
- const threatSkill = writeGeneratedText(
109
- threatSkillPath,
110
- getThreatModellingSkillContent(optionsWithSkillDirs),
111
- );
112
-
113
- const vibereviewSyncSkillPath = join(cwd, VIBEREVIEW_SYNC_SKILL_REL_DIR.codex, 'SKILL.md');
114
- const vibereviewSyncSkill = writeGeneratedText(
115
- vibereviewSyncSkillPath,
116
- getVibeReviewSyncSkillContent(optionsWithSkillDirs),
117
- );
118
-
119
- const deletedLegacyAgent = removeGeneratedText(join(cwd, '.codex', 'agents', 'ctm_sync.toml'));
120
-
121
- const hooksPath = join(cwd, '.codex', 'hooks.json');
122
- const hooks = writeGeneratedText(hooksPath, getCodexSessionHookContent());
123
-
124
- const guardrailsInitPath = join(cwd, '.codex', 'commands', 'guardrails-init-profile.md');
125
- const guardrailsInit = writeGeneratedText(
126
- guardrailsInitPath,
127
- getGuardrailsInitProfileContent({
128
- ...optionsWithSkillDirs,
129
- guardrailsSkillDir: GUARDRAILS_PROFILER_SKILL_REL_DIR.codex,
130
- }),
131
- );
132
-
133
- return [
134
- { filePath, action, kind: 'rule' },
135
- { ...threatSkill, kind: 'skill' },
136
- { ...vibereviewSyncSkill, kind: 'skill' },
137
- { ...hooks, kind: 'hooks' },
138
- { ...guardrailsInit, kind: 'command' },
139
- ...(deletedLegacyAgent ? [{ ...deletedLegacyAgent, kind: 'cleanup' }] : []),
140
- ];
141
- }
@@ -1,59 +0,0 @@
1
- import { existsSync, mkdtempSync, readFileSync } from 'node:fs';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
4
- import { test } from 'node:test';
5
- import assert from 'node:assert/strict';
6
- import { generate } from './codex.js';
7
-
8
- test('Codex generator writes AGENTS, skills, hooks, and profiling command', () => {
9
- const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-codex-'));
10
-
11
- const results = generate(cwd, { projectName: 'SmokeProject' });
12
-
13
- const expectedPaths = [
14
- '.codex/AGENTS.md',
15
- '.codex/skills/threat-modelling/SKILL.md',
16
- '.codex/skills/vibereview-sync/SKILL.md',
17
- '.codex/hooks.json',
18
- '.codex/commands/guardrails-init-profile.md',
19
- ];
20
-
21
- for (const relPath of expectedPaths) {
22
- assert.equal(existsSync(join(cwd, relPath)), true, `${relPath} should exist`);
23
- }
24
-
25
- assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', 'skill', 'hooks', 'command']);
26
-
27
- const instructions = readFileSync(join(cwd, '.codex/AGENTS.md'), 'utf8');
28
- assert.match(instructions, /\.codex\/skills\/guardrails-selection\/SKILL\.md/);
29
- assert.match(instructions, /\.codex\/skills\/threat-modelling\/SKILL\.md/);
30
- assert.match(instructions, /\.codex\/skills\/vibereview-sync\/SKILL\.md/);
31
- assert.match(instructions, /sync_ai_ide_markdown/);
32
- assert.match(instructions, /vibereview\//);
33
- assert.doesNotMatch(instructions, /\.cursor/);
34
- assert.doesNotMatch(instructions, /\.agents\/skills/);
35
-
36
- const threatSkill = readFileSync(join(cwd, '.codex/skills/threat-modelling/SKILL.md'), 'utf8');
37
- for (const heading of ['Product', 'Workload', 'Network', 'IAM', 'Secrets', 'Monitoring', 'Supply Chain']) {
38
- assert.match(threatSkill, new RegExp(heading));
39
- }
40
- assert.match(threatSkill, /sync_ai_ide_markdown/);
41
- assert.match(threatSkill, /vibereview\//);
42
- assert.doesNotMatch(threatSkill, /get_project_profile_description/);
43
-
44
- const vibereviewSkill = readFileSync(join(cwd, '.codex/skills/vibereview-sync/SKILL.md'), 'utf8');
45
- assert.match(vibereviewSkill, /do not read other `?\.md`? files in `?vibereview\/`?/i);
46
- assert.match(vibereviewSkill, /sync_ai_ide_markdown/);
47
- assert.match(vibereviewSkill, /Event Identity/);
48
- assert.match(vibereviewSkill, /OWASP Top 10 2025 Mappings/);
49
- assert.match(vibereviewSkill, /Practice 1/);
50
- assert.match(vibereviewSkill, /practice_name:/);
51
-
52
- const hooks = JSON.parse(readFileSync(join(cwd, '.codex/hooks.json'), 'utf8'));
53
- assert.equal(hooks.hooks.SessionStart[0].hooks[0].type, 'command');
54
- assert.equal(hooks.hooks.UserPromptSubmit[0].hooks[0].type, 'command');
55
- assert.match(hooks.hooks.UserPromptSubmit[0].hooks[0].command, /"hookEventName":"UserPromptSubmit"/);
56
- assert.match(hooks.hooks.UserPromptSubmit[0].hooks[0].command, /vibereview/);
57
- assert.match(hooks.hooks.UserPromptSubmit[0].hooks[0].command, /sync_ai_ide_markdown/);
58
- assert.match(hooks.hooks.UserPromptSubmit[0].hooks[0].command, /vibereview-sync\/SKILL\.md/);
59
- });
@@ -1,110 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { dirname, join } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
-
5
- const __dirname = dirname(fileURLToPath(import.meta.url));
6
-
7
- function sanitizeProjectName(value) {
8
- return value.replace(/[\r\n`]/g, ' ').trim();
9
- }
10
-
11
- function injectPathPlaceholder(content, placeholder, fallbackPath, configuredPath) {
12
- if (!content.includes(placeholder)) {
13
- return content;
14
- }
15
-
16
- const resolvedPath =
17
- typeof configuredPath === 'string' && configuredPath.trim() ? configuredPath.trim() : fallbackPath;
18
-
19
- return content.replaceAll(placeholder, resolvedPath);
20
- }
21
-
22
- /**
23
- * Reads a markdown template and injects the configured project name placeholder.
24
- */
25
- function readTemplate(templateFileName, options = {}) {
26
- const template = readFileSync(join(__dirname, templateFileName), 'utf-8').trim();
27
- const rawProjectName = typeof options.projectName === 'string' ? options.projectName : '';
28
- const projectName = sanitizeProjectName(rawProjectName);
29
- const resolvedProjectName = projectName || '<SRAI_PROJECT_NAME>';
30
-
31
- let out = template
32
- .replaceAll('{{SRAI_PROJECT_NAME}}', resolvedProjectName)
33
- .replaceAll('<SRAI_PROJECT_NAME>', resolvedProjectName);
34
-
35
- out = injectPathPlaceholder(
36
- out,
37
- '{{GUARDRAILS_SKILL_DIR}}',
38
- '.cursor/skills/guardrails-profiler',
39
- options.guardrailsSkillDir,
40
- );
41
- out = injectPathPlaceholder(
42
- out,
43
- '{{GUARDRAILS_SELECTION_SKILL_DIR}}',
44
- '.cursor/skills/guardrails-selection',
45
- options.guardrailsSelectionSkillDir,
46
- );
47
- out = injectPathPlaceholder(
48
- out,
49
- '{{THREAT_MODELLING_SKILL_DIR}}',
50
- '.cursor/skills/threat-modelling',
51
- options.threatModellingSkillDir,
52
- );
53
- out = injectPathPlaceholder(
54
- out,
55
- '{{VIBEREVIEW_SYNC_SKILL_DIR}}',
56
- '.cursor/skills/vibereview-sync',
57
- options.vibereviewSyncSkillDir,
58
- );
59
-
60
- return out;
61
- }
62
-
63
- /**
64
- * Returns the shared rule content markdown.
65
- */
66
- export function getRuleContent(options = {}) {
67
- return readTemplate('content.md', options);
68
- }
69
-
70
- /**
71
- * Returns the Cursor profile uploader command markdown.
72
- */
73
- export function getProfileCommandContent(options = {}) {
74
- return readTemplate('srai-profile.md', options);
75
- }
76
-
77
- /**
78
- * Returns the threat-modelling skill markdown.
79
- */
80
- export function getThreatModellingSkillContent(options = {}) {
81
- return readTemplate('skill.md', options);
82
- }
83
-
84
- /**
85
- * Returns the VibeReview sync skill markdown.
86
- */
87
- export function getVibeReviewSyncSkillContent(options = {}) {
88
- return readTemplate(join('vibereview-sync', 'SKILL.md'), options);
89
- }
90
-
91
- /**
92
- * Returns the vibe guardrails always-applied rule markdown.
93
- */
94
- export function getGuardrailsRuleContent(options = {}) {
95
- return readTemplate('guardrails_rule.md', options);
96
- }
97
-
98
- /**
99
- * Guardrails init profile command (repo scan + .guardrails/profile.json + SRAI upload).
100
- */
101
- export function getGuardrailsInitProfileContent(options = {}) {
102
- return readTemplate('guardrails-init-profile.md', options);
103
- }
104
-
105
- /**
106
- * Returns the hooks.json content for Cursor session hooks.
107
- */
108
- export function getHooksContent() {
109
- return readFileSync(join(__dirname, 'hooks.json'), 'utf-8').trim();
110
- }
@@ -1,57 +0,0 @@
1
- # Security Review Kit Agent Instructions
2
-
3
- Configured SRAI project name: `<SRAI_PROJECT_NAME>`
4
-
5
- These instructions are always active for security-relevant coding work. Keep this file as the routing policy; use the skills and agents below for the detailed workflows.
6
-
7
- ## Core Workflow
8
-
9
- For any task that touches auth, authorization, input handling, secrets, network, data storage, dependencies, new APIs/endpoints, infrastructure, or code handling untrusted data:
10
-
11
- 1. **Fetch Vibe Guardrails first.**
12
- - Read and follow `{{GUARDRAILS_SELECTION_SKILL_DIR}}/SKILL.md`.
13
- - Resolve the SRAI project with `find_project_by_name` using `name="<SRAI_PROJECT_NAME>"`.
14
- - Call `get_guardrails`, shortlist only the relevant project guardrails, then hydrate the exact shortlist with `get_guardrail_by_id`.
15
- - Preserve that exact shortlist in context for implementation and the final VibeReview sync step.
16
-
17
- 2. **Run PWNISMS threat modelling before implementation.**
18
- - Read and follow `{{THREAT_MODELLING_SKILL_DIR}}/SKILL.md`.
19
- - Explicitly walk all seven PWNISMS categories: Product, Workload, Network, IAM, Secrets, Monitoring, and Supply Chain.
20
- - If a category is not applicable, say so briefly and continue.
21
- - Cross-reference each meaningful threat with the shortlisted guardrails.
22
-
23
- 3. **Implement secure code using both inputs.**
24
- - Treat applicable `must` guardrails as mandatory.
25
- - Treat applicable `must_not` guardrails as hard prohibitions.
26
- - Use the PWNISMS findings to guide design, validation, authorization, logging, secrets handling, dependency choices, and abuse controls.
27
- - If PWNISMS reveals a recurring security rule that is not covered by existing guardrails, create and apply an `ide_generated` guardrail in context.
28
-
29
- 4. **Write and sync the VibeReview markdown last.**
30
- - Read and follow `{{VIBEREVIEW_SYNC_SKILL_DIR}}/SKILL.md`.
31
- - After threat modelling is created or updated, or after guardrails are enforced during implementation, the main agent must write or update a structured `.md` artifact under `vibereview/`.
32
- - Do not delegate markdown authoring to a subagent. The same agent that performed the work should author the artifact so the context stays intact.
33
- - Use the skill's structure and validation checklist rather than improvising the markdown format.
34
- - Reuse the exact existing guardrails shortlisted earlier, include any `ide_generated` guardrails, and only include grounded code snippets from actual code.
35
- - Call `sync_ai_ide_markdown` directly with the finished markdown artifact before finalizing the task.
36
- - If sync fails, keep the file in `vibereview/`, report the failure, and do not pretend synchronization succeeded.
37
-
38
- ## When To Skip
39
-
40
- Skip the SRAI security workflow only for tasks with no security surface, such as documentation-only changes, typo fixes, pure formatting, variable renames with no logic change, or general Q&A that produces no code.
41
-
42
- If in doubt, run the workflow. A quick PWNISMS pass is better than silently missing a security boundary.
43
-
44
- ## Do Not Use Project Profile Exploration
45
-
46
- Do not call project-profile exploration tools during normal coding tasks. The old profile walkthrough is no longer part of the agent workflow.
47
-
48
- The normal coding workflow is guardrails selection, PWNISMS threat modelling, secure implementation, then VibeReview markdown sync.
49
-
50
- ## Tool Reference
51
-
52
- | Purpose | Tools |
53
- |---|---|
54
- | Project resolution | `find_project_by_name`, `list_projects`, `create_project`, `get_project` |
55
- | Guardrails | `get_guardrails`, `get_guardrail_by_id` |
56
- | VibeReview sync | `sync_ai_ide_markdown` |
57
- | Profiler only | `update_vibe_profile`, `write_default_pack` are used by init-time profiling, not normal coding tasks |