@securityreviewai/securityreview-kit 0.1.42 → 0.1.43
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/README.md +4 -4
- package/package.json +1 -1
- package/src/generators/mcp/claude.js +2 -2
- package/src/generators/mcp/claude.test.js +2 -0
- package/src/generators/rules/claude.js +5 -28
- package/src/generators/rules/claude.test.js +6 -16
- package/src/generators/rules/codex.js +13 -28
- package/src/generators/rules/codex.test.js +8 -16
- package/src/generators/rules/content.js +0 -27
- package/src/generators/rules/content.md +13 -8
- package/src/generators/rules/cursor.js +19 -21
- package/src/generators/rules/guardrails-selection/SKILL.md +6 -6
- package/src/generators/rules/guardrails-selection/references/category-threat-map.md +1 -1
- package/src/generators/rules/guardrails_rule.md +5 -5
- package/src/generators/rules/hooks.json +1 -1
- package/src/generators/rules/skill.md +26 -17
- package/src/generators/rules/vscode.js +14 -12
- package/src/generators/rules/vscode.test.js +8 -13
- package/src/utils/constants.js +0 -8
- package/src/generators/rules/create-ide-workflow.md +0 -34
- package/src/generators/rules/ctm_sync.md +0 -215
- package/src/generators/rules/ctm_sync_rule.md +0 -88
package/README.md
CHANGED
|
@@ -27,11 +27,11 @@ npx @securityreviewai/securityreview-kit init --switch-project
|
|
|
27
27
|
|
|
28
28
|
| Target | Flag | MCP Config | Workspace Rule |
|
|
29
29
|
|---|---|---|---|
|
|
30
|
-
| Cursor | `cursor` | `.cursor/mcp.json` | `.cursor/rules/srai-security-review.mdc`, `.cursor/rules/
|
|
31
|
-
| Claude Code | `claude` | `.mcp.json` | `.claude/CLAUDE.md`, `.claude/settings.json`, `.claude/skills/threat-modelling/SKILL.md`, `.claude/skills/guardrails-profiler/SKILL.md`, `.claude/skills/guardrails-selection/SKILL.md`, `.claude/
|
|
32
|
-
| VS Code Copilot | `vscode` | `.vscode/mcp.json` | `.github/copilot-instructions.md`, `.github/skills/threat-modelling/SKILL.md`, `.github/skills/guardrails-profiler/SKILL.md`, `.github/skills/guardrails-selection/SKILL.md`, `.github/
|
|
30
|
+
| Cursor | `cursor` | `.cursor/mcp.json` | `.cursor/rules/srai-security-review.mdc`, `.cursor/rules/guardrails_rule.mdc`, `.cursor/commands/srai-profile.md`, `.cursor/commands/guardrails-init-profile.md`, `.cursor/skills/threat-modelling/SKILL.md`, `.cursor/hooks.json` |
|
|
31
|
+
| Claude Code | `claude` | `.mcp.json` | `.claude/CLAUDE.md`, `.claude/settings.json`, `.claude/skills/threat-modelling/SKILL.md`, `.claude/skills/guardrails-profiler/SKILL.md`, `.claude/skills/guardrails-selection/SKILL.md`, `.claude/commands/guardrails-init-profile.md` |
|
|
32
|
+
| VS Code Copilot | `vscode` | `.vscode/mcp.json` | `.github/copilot-instructions.md`, `.github/skills/threat-modelling/SKILL.md`, `.github/skills/guardrails-profiler/SKILL.md`, `.github/skills/guardrails-selection/SKILL.md`, `.github/hooks/srai-session-policy.json` |
|
|
33
33
|
| Windsurf | `windsurf` | `.windsurf/mcp_config.json` | `.windsurf/rules/srai-security-review.md` |
|
|
34
|
-
| Codex | `codex` | `.codex/config.toml` | `.codex/AGENTS.md`, `.codex/skills/threat-modelling/SKILL.md`, `.codex/skills/guardrails-profiler/SKILL.md`, `.codex/skills/guardrails-selection/SKILL.md`, `.codex/
|
|
34
|
+
| Codex | `codex` | `.codex/config.toml` | `.codex/AGENTS.md`, `.codex/skills/threat-modelling/SKILL.md`, `.codex/skills/guardrails-profiler/SKILL.md`, `.codex/skills/guardrails-selection/SKILL.md`, `.codex/hooks.json`, `.codex/commands/guardrails-init-profile.md` |
|
|
35
35
|
| Gemini CLI | `gemini` | `.gemini/settings.json` | `GEMINI.md` |
|
|
36
36
|
| Antigravity | `antigravity` | `.gemini/settings.json` | `.agents/rules/srai-security-review.md` |
|
|
37
37
|
|
package/package.json
CHANGED
|
@@ -10,9 +10,9 @@ function getClaudeSessionStartHooks() {
|
|
|
10
10
|
'1. Fetch Vibe Guardrails first using .claude/skills/guardrails-selection/SKILL.md.',
|
|
11
11
|
'2. Run PWNISMS threat modelling using .claude/skills/threat-modelling/SKILL.md.',
|
|
12
12
|
'3. Implement secure code using the hydrated guardrails and threat findings.',
|
|
13
|
-
'4.
|
|
13
|
+
'4. Write a structured .md artifact under vibereview/, validate it, and call sync_ai_ide_markdown directly after implementation or threat-model updates.',
|
|
14
14
|
'',
|
|
15
|
-
'Do not use project-profile exploration tools during normal coding tasks. No blocking and no deferral: guardrails first, PWNISMS second, implementation third,
|
|
15
|
+
'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.',
|
|
16
16
|
].join('\n');
|
|
17
17
|
|
|
18
18
|
return [
|
|
@@ -19,6 +19,8 @@ test('Claude MCP generator writes .mcp.json, enables the project MCP server, and
|
|
|
19
19
|
assert.deepEqual(settings.enabledMcpjsonServers, ['security-review-mcp']);
|
|
20
20
|
assert.equal(Array.isArray(settings.SessionStart), true);
|
|
21
21
|
assert.match(settings.SessionStart[0].hooks[0].prompt, /MANDATORY SECURITY GATE/);
|
|
22
|
+
assert.match(settings.SessionStart[0].hooks[0].prompt, /vibereview\//);
|
|
23
|
+
assert.match(settings.SessionStart[0].hooks[0].prompt, /sync_ai_ide_markdown/);
|
|
22
24
|
});
|
|
23
25
|
|
|
24
26
|
test('Claude MCP generator preserves existing SessionStart hooks', () => {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { existsSync, unlinkSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import {
|
|
4
|
-
CTM_SYNC_AGENT_REL_PATH,
|
|
5
4
|
GUARDRAILS_PROFILER_SKILL_REL_DIR,
|
|
6
5
|
GUARDRAILS_SELECTION_SKILL_REL_DIR,
|
|
7
6
|
SENTINEL_END,
|
|
@@ -10,7 +9,6 @@ import {
|
|
|
10
9
|
} from '../../utils/constants.js';
|
|
11
10
|
import { readText, upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
|
|
12
11
|
import {
|
|
13
|
-
getCtmSyncWorkflowContent,
|
|
14
12
|
getGuardrailsInitProfileContent,
|
|
15
13
|
getRuleContent,
|
|
16
14
|
getThreatModellingSkillContent,
|
|
@@ -22,25 +20,6 @@ function writeGeneratedText(filePath, content) {
|
|
|
22
20
|
return { filePath, action };
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
function getAgentBody(content) {
|
|
26
|
-
return content.replace(/^---\n[\s\S]*?\n---\n*/, '').trim();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function getClaudeCtmSyncAgentContent(options = {}) {
|
|
30
|
-
const body = getAgentBody(getCtmSyncWorkflowContent(options));
|
|
31
|
-
return [
|
|
32
|
-
'---',
|
|
33
|
-
'name: ctm_sync',
|
|
34
|
-
'description: Use this agent after security-relevant implementation or threat modelling work to synchronize the latest threat model, mitigations, and guardrails to SRAI.',
|
|
35
|
-
'model: inherit',
|
|
36
|
-
'color: blue',
|
|
37
|
-
'---',
|
|
38
|
-
'',
|
|
39
|
-
body,
|
|
40
|
-
'',
|
|
41
|
-
].join('\n');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
23
|
/**
|
|
45
24
|
* Generate Claude Code workspace rule — appends to .claude/CLAUDE.md
|
|
46
25
|
*/
|
|
@@ -49,7 +28,6 @@ export function generate(cwd, options = {}) {
|
|
|
49
28
|
...options,
|
|
50
29
|
guardrailsSelectionSkillDir: GUARDRAILS_SELECTION_SKILL_REL_DIR.claude,
|
|
51
30
|
threatModellingSkillDir: THREAT_MODELLING_SKILL_REL_DIR.claude,
|
|
52
|
-
ctmSyncAgentPath: CTM_SYNC_AGENT_REL_PATH.claude,
|
|
53
31
|
};
|
|
54
32
|
const legacyRootPath = join(cwd, 'CLAUDE.md');
|
|
55
33
|
const filePath = join(cwd, '.claude', 'CLAUDE.md');
|
|
@@ -76,11 +54,10 @@ export function generate(cwd, options = {}) {
|
|
|
76
54
|
getThreatModellingSkillContent(optionsWithSkillDirs),
|
|
77
55
|
);
|
|
78
56
|
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
);
|
|
57
|
+
const deletedLegacyAgentPath = join(cwd, '.claude', 'agents', 'ctm_sync.md');
|
|
58
|
+
const deletedLegacyAgent = existsSync(deletedLegacyAgentPath)
|
|
59
|
+
? (unlinkSync(deletedLegacyAgentPath), { filePath: deletedLegacyAgentPath, action: 'deleted' })
|
|
60
|
+
: null;
|
|
84
61
|
|
|
85
62
|
const guardrailsInitPath = join(cwd, '.claude', 'commands', 'guardrails-init-profile.md');
|
|
86
63
|
const guardrailsInit = writeGeneratedText(
|
|
@@ -94,7 +71,7 @@ export function generate(cwd, options = {}) {
|
|
|
94
71
|
return [
|
|
95
72
|
{ filePath, action, kind: 'rule' },
|
|
96
73
|
{ ...threatSkill, kind: 'skill' },
|
|
97
|
-
{ ...ctmSyncAgent, kind: 'agent' },
|
|
98
74
|
{ ...guardrailsInit, kind: 'command' },
|
|
75
|
+
...(deletedLegacyAgent ? [{ ...deletedLegacyAgent, kind: 'cleanup' }] : []),
|
|
99
76
|
];
|
|
100
77
|
}
|
|
@@ -5,7 +5,7 @@ import { test } from 'node:test';
|
|
|
5
5
|
import assert from 'node:assert/strict';
|
|
6
6
|
import { generate } from './claude.js';
|
|
7
7
|
|
|
8
|
-
test('Claude generator writes .claude/CLAUDE.md, threat skill,
|
|
8
|
+
test('Claude generator writes .claude/CLAUDE.md, threat skill, and profiling command', () => {
|
|
9
9
|
const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-claude-'));
|
|
10
10
|
|
|
11
11
|
const results = generate(cwd, { projectName: 'SmokeProject' });
|
|
@@ -13,7 +13,6 @@ test('Claude generator writes .claude/CLAUDE.md, threat skill, ctm_sync agent, a
|
|
|
13
13
|
const expectedPaths = [
|
|
14
14
|
'.claude/CLAUDE.md',
|
|
15
15
|
'.claude/skills/threat-modelling/SKILL.md',
|
|
16
|
-
'.claude/agents/ctm_sync.md',
|
|
17
16
|
'.claude/commands/guardrails-init-profile.md',
|
|
18
17
|
];
|
|
19
18
|
|
|
@@ -21,27 +20,18 @@ test('Claude generator writes .claude/CLAUDE.md, threat skill, ctm_sync agent, a
|
|
|
21
20
|
assert.equal(existsSync(join(cwd, relPath)), true, `${relPath} should exist`);
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', '
|
|
23
|
+
assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', 'command']);
|
|
25
24
|
|
|
26
25
|
const instructions = readFileSync(join(cwd, '.claude/CLAUDE.md'), 'utf8');
|
|
27
26
|
assert.match(instructions, /\.claude\/skills\/guardrails-selection\/SKILL\.md/);
|
|
28
27
|
assert.match(instructions, /\.claude\/skills\/threat-modelling\/SKILL\.md/);
|
|
29
|
-
assert.match(instructions,
|
|
28
|
+
assert.match(instructions, /sync_ai_ide_markdown/);
|
|
29
|
+
assert.match(instructions, /vibereview\//);
|
|
30
30
|
|
|
31
31
|
const threatSkill = readFileSync(join(cwd, '.claude/skills/threat-modelling/SKILL.md'), 'utf8');
|
|
32
|
-
assert.match(threatSkill,
|
|
32
|
+
assert.match(threatSkill, /sync_ai_ide_markdown/);
|
|
33
|
+
assert.match(threatSkill, /vibereview\//);
|
|
33
34
|
assert.doesNotMatch(threatSkill, /get_project_profile_description/);
|
|
34
|
-
|
|
35
|
-
const agent = readFileSync(join(cwd, '.claude/agents/ctm_sync.md'), 'utf8');
|
|
36
|
-
assert.match(agent, /^---\nname: ctm_sync/m);
|
|
37
|
-
assert.match(agent, /model: inherit/);
|
|
38
|
-
assert.match(agent, /Configured SRAI project name: `SmokeProject`/);
|
|
39
|
-
assert.match(agent, /sync_ai_ide_markdown/);
|
|
40
|
-
assert.match(agent, /structured markdown artifact/i);
|
|
41
|
-
assert.match(agent, /chat_session_id/);
|
|
42
|
-
assert.match(agent, /Guardrails Applied/);
|
|
43
|
-
assert.match(agent, /vibereview\//);
|
|
44
|
-
assert.doesNotMatch(agent, /Call `create_ai_ide_event` with/i);
|
|
45
35
|
});
|
|
46
36
|
|
|
47
37
|
test('Claude generator migrates the kit-managed root CLAUDE.md block into .claude/CLAUDE.md', () => {
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { existsSync, unlinkSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import {
|
|
4
|
-
CTM_SYNC_AGENT_REL_PATH,
|
|
5
4
|
GUARDRAILS_PROFILER_SKILL_REL_DIR,
|
|
6
5
|
GUARDRAILS_SELECTION_SKILL_REL_DIR,
|
|
7
6
|
THREAT_MODELLING_SKILL_REL_DIR,
|
|
8
7
|
} from '../../utils/constants.js';
|
|
9
8
|
import { upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
|
|
10
9
|
import {
|
|
11
|
-
getCtmSyncWorkflowContent,
|
|
12
10
|
getGuardrailsInitProfileContent,
|
|
13
11
|
getRuleContent,
|
|
14
12
|
getThreatModellingSkillContent,
|
|
@@ -20,6 +18,14 @@ function writeGeneratedText(filePath, content) {
|
|
|
20
18
|
return { filePath, action };
|
|
21
19
|
}
|
|
22
20
|
|
|
21
|
+
function removeGeneratedText(filePath) {
|
|
22
|
+
if (existsSync(filePath)) {
|
|
23
|
+
unlinkSync(filePath);
|
|
24
|
+
return { filePath, action: 'deleted' };
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
function getCodexSessionHookContent() {
|
|
24
30
|
const additionalContext = [
|
|
25
31
|
'## MANDATORY SECURITY GATE (Session Policy)',
|
|
@@ -29,9 +35,9 @@ function getCodexSessionHookContent() {
|
|
|
29
35
|
'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.',
|
|
30
36
|
'2. Run PWNISMS threat modelling using .codex/skills/threat-modelling/SKILL.md. Explicitly walk Product, Workload, Network, IAM, Secrets, Monitoring, and Supply Chain.',
|
|
31
37
|
'3. Implement secure code using both the hydrated guardrails and PWNISMS findings.',
|
|
32
|
-
'4.
|
|
38
|
+
'4. Write a structured .md artifact under vibereview/, 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.',
|
|
33
39
|
'',
|
|
34
|
-
'Do not use project-profile exploration tools during normal coding tasks. No blocking and no deferral: guardrails first, PWNISMS second, implementation third,
|
|
40
|
+
'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.',
|
|
35
41
|
].join('\n');
|
|
36
42
|
|
|
37
43
|
const sessionStartPayload = JSON.stringify({
|
|
@@ -82,22 +88,6 @@ function getCodexSessionHookContent() {
|
|
|
82
88
|
) + '\n';
|
|
83
89
|
}
|
|
84
90
|
|
|
85
|
-
function getAgentBody(content) {
|
|
86
|
-
return content.replace(/^---\n[\s\S]*?\n---\n*/, '').trim();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function getCodexCtmSyncAgentContent(options = {}) {
|
|
90
|
-
const developerInstructions = getAgentBody(getCtmSyncWorkflowContent(options));
|
|
91
|
-
return [
|
|
92
|
-
'name = "ctm_sync"',
|
|
93
|
-
'description = "Synchronize threat-model and guardrail data to SRAI after security-relevant work."',
|
|
94
|
-
"developer_instructions = '''",
|
|
95
|
-
developerInstructions,
|
|
96
|
-
"'''",
|
|
97
|
-
'',
|
|
98
|
-
].join('\n');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
91
|
/**
|
|
102
92
|
* Generate Codex workspace rule — appends to AGENTS.md
|
|
103
93
|
*/
|
|
@@ -106,7 +96,6 @@ export function generate(cwd, options = {}) {
|
|
|
106
96
|
...options,
|
|
107
97
|
guardrailsSelectionSkillDir: GUARDRAILS_SELECTION_SKILL_REL_DIR.codex,
|
|
108
98
|
threatModellingSkillDir: THREAT_MODELLING_SKILL_REL_DIR.codex,
|
|
109
|
-
ctmSyncAgentPath: CTM_SYNC_AGENT_REL_PATH.codex,
|
|
110
99
|
};
|
|
111
100
|
const filePath = join(cwd, '.codex', 'AGENTS.md');
|
|
112
101
|
const content = getRuleContent(optionsWithSkillDirs);
|
|
@@ -118,11 +107,7 @@ export function generate(cwd, options = {}) {
|
|
|
118
107
|
getThreatModellingSkillContent(optionsWithSkillDirs),
|
|
119
108
|
);
|
|
120
109
|
|
|
121
|
-
const
|
|
122
|
-
const ctmSyncAgent = writeGeneratedText(
|
|
123
|
-
ctmSyncAgentPath,
|
|
124
|
-
getCodexCtmSyncAgentContent(optionsWithSkillDirs),
|
|
125
|
-
);
|
|
110
|
+
const deletedLegacyAgent = removeGeneratedText(join(cwd, '.codex', 'agents', 'ctm_sync.toml'));
|
|
126
111
|
|
|
127
112
|
const hooksPath = join(cwd, '.codex', 'hooks.json');
|
|
128
113
|
const hooks = writeGeneratedText(hooksPath, getCodexSessionHookContent());
|
|
@@ -139,8 +124,8 @@ export function generate(cwd, options = {}) {
|
|
|
139
124
|
return [
|
|
140
125
|
{ filePath, action, kind: 'rule' },
|
|
141
126
|
{ ...threatSkill, kind: 'skill' },
|
|
142
|
-
{ ...ctmSyncAgent, kind: 'agent' },
|
|
143
127
|
{ ...hooks, kind: 'hooks' },
|
|
144
128
|
{ ...guardrailsInit, kind: 'command' },
|
|
129
|
+
...(deletedLegacyAgent ? [{ ...deletedLegacyAgent, kind: 'cleanup' }] : []),
|
|
145
130
|
];
|
|
146
131
|
}
|
|
@@ -5,7 +5,7 @@ import { test } from 'node:test';
|
|
|
5
5
|
import assert from 'node:assert/strict';
|
|
6
6
|
import { generate } from './codex.js';
|
|
7
7
|
|
|
8
|
-
test('Codex generator writes AGENTS, skills,
|
|
8
|
+
test('Codex generator writes AGENTS, skills, hooks, and profiling command', () => {
|
|
9
9
|
const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-codex-'));
|
|
10
10
|
|
|
11
11
|
const results = generate(cwd, { projectName: 'SmokeProject' });
|
|
@@ -13,7 +13,6 @@ test('Codex generator writes AGENTS, skills, subagent, hooks, and profiling comm
|
|
|
13
13
|
const expectedPaths = [
|
|
14
14
|
'.codex/AGENTS.md',
|
|
15
15
|
'.codex/skills/threat-modelling/SKILL.md',
|
|
16
|
-
'.codex/agents/ctm_sync.toml',
|
|
17
16
|
'.codex/hooks.json',
|
|
18
17
|
'.codex/commands/guardrails-init-profile.md',
|
|
19
18
|
];
|
|
@@ -22,12 +21,13 @@ test('Codex generator writes AGENTS, skills, subagent, hooks, and profiling comm
|
|
|
22
21
|
assert.equal(existsSync(join(cwd, relPath)), true, `${relPath} should exist`);
|
|
23
22
|
}
|
|
24
23
|
|
|
25
|
-
assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', '
|
|
24
|
+
assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', 'hooks', 'command']);
|
|
26
25
|
|
|
27
26
|
const instructions = readFileSync(join(cwd, '.codex/AGENTS.md'), 'utf8');
|
|
28
27
|
assert.match(instructions, /\.codex\/skills\/guardrails-selection\/SKILL\.md/);
|
|
29
28
|
assert.match(instructions, /\.codex\/skills\/threat-modelling\/SKILL\.md/);
|
|
30
|
-
assert.match(instructions,
|
|
29
|
+
assert.match(instructions, /sync_ai_ide_markdown/);
|
|
30
|
+
assert.match(instructions, /vibereview\//);
|
|
31
31
|
assert.doesNotMatch(instructions, /\.cursor/);
|
|
32
32
|
assert.doesNotMatch(instructions, /\.agents\/skills/);
|
|
33
33
|
|
|
@@ -35,22 +35,14 @@ test('Codex generator writes AGENTS, skills, subagent, hooks, and profiling comm
|
|
|
35
35
|
for (const heading of ['Product', 'Workload', 'Network', 'IAM', 'Secrets', 'Monitoring', 'Supply Chain']) {
|
|
36
36
|
assert.match(threatSkill, new RegExp(heading));
|
|
37
37
|
}
|
|
38
|
-
assert.match(threatSkill,
|
|
38
|
+
assert.match(threatSkill, /sync_ai_ide_markdown/);
|
|
39
|
+
assert.match(threatSkill, /vibereview\//);
|
|
39
40
|
assert.doesNotMatch(threatSkill, /get_project_profile_description/);
|
|
40
41
|
|
|
41
|
-
const agent = readFileSync(join(cwd, '.codex/agents/ctm_sync.toml'), 'utf8');
|
|
42
|
-
assert.match(agent, /name = "ctm_sync"/);
|
|
43
|
-
assert.match(agent, /developer_instructions = '''/);
|
|
44
|
-
assert.match(agent, /Configured SRAI project name: `SmokeProject`/);
|
|
45
|
-
assert.match(agent, /sync_ai_ide_markdown/);
|
|
46
|
-
assert.match(agent, /structured markdown artifact/i);
|
|
47
|
-
assert.match(agent, /chat_session_id/);
|
|
48
|
-
assert.match(agent, /Best Practices Achieved/);
|
|
49
|
-
assert.match(agent, /vibereview\//);
|
|
50
|
-
assert.doesNotMatch(agent, /Call `create_ai_ide_event` with/i);
|
|
51
|
-
|
|
52
42
|
const hooks = JSON.parse(readFileSync(join(cwd, '.codex/hooks.json'), 'utf8'));
|
|
53
43
|
assert.equal(hooks.hooks.SessionStart[0].hooks[0].type, 'command');
|
|
54
44
|
assert.equal(hooks.hooks.UserPromptSubmit[0].hooks[0].type, 'command');
|
|
55
45
|
assert.match(hooks.hooks.UserPromptSubmit[0].hooks[0].command, /"hookEventName":"UserPromptSubmit"/);
|
|
46
|
+
assert.match(hooks.hooks.UserPromptSubmit[0].hooks[0].command, /vibereview/);
|
|
47
|
+
assert.match(hooks.hooks.UserPromptSubmit[0].hooks[0].command, /sync_ai_ide_markdown/);
|
|
56
48
|
});
|
|
@@ -50,12 +50,6 @@ function readTemplate(templateFileName, options = {}) {
|
|
|
50
50
|
'.cursor/skills/threat-modelling',
|
|
51
51
|
options.threatModellingSkillDir,
|
|
52
52
|
);
|
|
53
|
-
out = injectPathPlaceholder(
|
|
54
|
-
out,
|
|
55
|
-
'{{CTM_SYNC_AGENT_PATH}}',
|
|
56
|
-
'.cursor/agents/ctm_sync.md',
|
|
57
|
-
options.ctmSyncAgentPath,
|
|
58
|
-
);
|
|
59
53
|
|
|
60
54
|
return out;
|
|
61
55
|
}
|
|
@@ -74,27 +68,6 @@ export function getProfileCommandContent(options = {}) {
|
|
|
74
68
|
return readTemplate('srai-profile.md', options);
|
|
75
69
|
}
|
|
76
70
|
|
|
77
|
-
/**
|
|
78
|
-
* Returns the CTM sync workflow content markdown.
|
|
79
|
-
*/
|
|
80
|
-
export function getCtmSyncWorkflowContent(options = {}) {
|
|
81
|
-
return readTemplate('ctm_sync.md', options);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Returns the Cursor CTM sync trigger rule markdown.
|
|
86
|
-
*/
|
|
87
|
-
export function getCtmSyncTriggerRuleContent(options = {}) {
|
|
88
|
-
return readTemplate('ctm_sync_rule.md', options);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Returns the Cursor create-ide-workflow command markdown.
|
|
93
|
-
*/
|
|
94
|
-
export function getCreateIdeWorkflowCommandContent(options = {}) {
|
|
95
|
-
return readTemplate('create-ide-workflow.md', options);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
71
|
/**
|
|
99
72
|
* Returns the threat-modelling skill markdown.
|
|
100
73
|
*/
|
|
@@ -12,7 +12,7 @@ For any task that touches auth, authorization, input handling, secrets, network,
|
|
|
12
12
|
- Read and follow `{{GUARDRAILS_SELECTION_SKILL_DIR}}/SKILL.md`.
|
|
13
13
|
- Resolve the SRAI project with `find_project_by_name` using `name="<SRAI_PROJECT_NAME>"`.
|
|
14
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
|
|
15
|
+
- Preserve that exact shortlist in context for implementation and the final VibeReview sync step.
|
|
16
16
|
|
|
17
17
|
2. **Run PWNISMS threat modelling before implementation.**
|
|
18
18
|
- Read and follow `{{THREAT_MODELLING_SKILL_DIR}}/SKILL.md`.
|
|
@@ -26,11 +26,16 @@ For any task that touches auth, authorization, input handling, secrets, network,
|
|
|
26
26
|
- Use the PWNISMS findings to guide design, validation, authorization, logging, secrets handling, dependency choices, and abuse controls.
|
|
27
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
28
|
|
|
29
|
-
4. **
|
|
30
|
-
- After threat modelling is created or updated, or after guardrails are enforced during implementation,
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
29
|
+
4. **Write and sync the VibeReview markdown last.**
|
|
30
|
+
- 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/`.
|
|
31
|
+
- Do not delegate markdown authoring to a subagent. The same agent that performed the work should author the artifact so the context stays intact.
|
|
32
|
+
- Use a stable path such as `vibereview/<chat_session_id>-<slugified-title-or-event-name>.md`.
|
|
33
|
+
- Required frontmatter: `chat_session_id`, `summary`, and either `title` or `event_name`. Optional frontmatter: `workflow_name`, `workflow_description`.
|
|
34
|
+
- Required sections: `Best Practices Achieved`, `Threats Mitigated`, `Secure Code Snippets`, `Guardrails Applied`, and `OWASP Top 10 2025 Mappings`.
|
|
35
|
+
- Reuse the exact existing guardrails shortlisted earlier, include any `ide_generated` guardrails, and only include grounded code snippets from actual code.
|
|
36
|
+
- Validate the markdown before sync: do not leave out `chat_session_id`, `summary`, threat metadata, guardrail satisfaction, or exact OWASP IDs/names.
|
|
37
|
+
- Call `sync_ai_ide_markdown` directly with the finished markdown artifact before finalizing the task.
|
|
38
|
+
- If sync fails, keep the file in `vibereview/`, report the failure, and do not pretend synchronization succeeded.
|
|
34
39
|
|
|
35
40
|
## When To Skip
|
|
36
41
|
|
|
@@ -42,7 +47,7 @@ If in doubt, run the workflow. A quick PWNISMS pass is better than silently miss
|
|
|
42
47
|
|
|
43
48
|
Do not call project-profile exploration tools during normal coding tasks. The old profile walkthrough is no longer part of the agent workflow.
|
|
44
49
|
|
|
45
|
-
The normal coding workflow is guardrails selection, PWNISMS threat modelling, secure implementation, then
|
|
50
|
+
The normal coding workflow is guardrails selection, PWNISMS threat modelling, secure implementation, then VibeReview markdown sync.
|
|
46
51
|
|
|
47
52
|
## Tool Reference
|
|
48
53
|
|
|
@@ -50,5 +55,5 @@ The normal coding workflow is guardrails selection, PWNISMS threat modelling, se
|
|
|
50
55
|
|---|---|
|
|
51
56
|
| Project resolution | `find_project_by_name`, `list_projects`, `create_project`, `get_project` |
|
|
52
57
|
| Guardrails | `get_guardrails`, `get_guardrail_by_id` |
|
|
53
|
-
|
|
|
58
|
+
| VibeReview sync | `sync_ai_ide_markdown` |
|
|
54
59
|
| Profiler only | `update_vibe_profile`, `write_default_pack` are used by init-time profiling, not normal coding tasks |
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { existsSync, unlinkSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import {
|
|
4
4
|
GUARDRAILS_PROFILER_SKILL_REL_DIR,
|
|
5
5
|
GUARDRAILS_SELECTION_SKILL_REL_DIR,
|
|
6
|
-
CTM_SYNC_AGENT_REL_PATH,
|
|
7
6
|
MCP_SERVER_NAME,
|
|
8
7
|
THREAT_MODELLING_SKILL_REL_DIR,
|
|
9
8
|
} from '../../utils/constants.js';
|
|
@@ -11,9 +10,6 @@ import { readJson, writeJson, writeText } from '../../utils/fs-helpers.js';
|
|
|
11
10
|
import {
|
|
12
11
|
getRuleContent,
|
|
13
12
|
getProfileCommandContent,
|
|
14
|
-
getCtmSyncWorkflowContent,
|
|
15
|
-
getCtmSyncTriggerRuleContent,
|
|
16
|
-
getCreateIdeWorkflowCommandContent,
|
|
17
13
|
getThreatModellingSkillContent,
|
|
18
14
|
getGuardrailsRuleContent,
|
|
19
15
|
getGuardrailsInitProfileContent,
|
|
@@ -40,6 +36,14 @@ function writeCursorCommand(filePath, content) {
|
|
|
40
36
|
return { filePath, action };
|
|
41
37
|
}
|
|
42
38
|
|
|
39
|
+
function removeGeneratedText(filePath) {
|
|
40
|
+
if (existsSync(filePath)) {
|
|
41
|
+
unlinkSync(filePath);
|
|
42
|
+
return { filePath, action: 'deleted' };
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
/**
|
|
44
48
|
* Merge `.cursor/cli.json` so Cursor CLI / Agent auto-allows security-review-mcp tools (no MCP approval prompts).
|
|
45
49
|
* @see https://cursor.com/docs/cli/reference/permissions
|
|
@@ -77,24 +81,16 @@ export function generate(cwd, options = {}) {
|
|
|
77
81
|
...options,
|
|
78
82
|
guardrailsSelectionSkillDir: GUARDRAILS_SELECTION_SKILL_REL_DIR.cursor,
|
|
79
83
|
threatModellingSkillDir: THREAT_MODELLING_SKILL_REL_DIR.cursor,
|
|
80
|
-
ctmSyncAgentPath: CTM_SYNC_AGENT_REL_PATH.cursor,
|
|
81
84
|
};
|
|
82
85
|
const baseRulePath = join(cwd, '.cursor', 'rules', 'srai-security-review.mdc');
|
|
83
|
-
const ctmSyncTriggerRulePath = join(cwd, '.cursor', 'rules', 'ctm_sync_rule.mdc');
|
|
84
86
|
const guardrailsRulePath = join(cwd, '.cursor', 'rules', 'guardrails_rule.mdc');
|
|
85
|
-
const ctmSyncWorkflowPath = join(cwd, '.cursor', 'commands', 'ctm_sync.md');
|
|
86
|
-
const ctmSyncAgentPath = join(cwd, '.cursor', 'agents', 'ctm_sync.md');
|
|
87
|
-
const createIdeWorkflowCommandPath = join(cwd, '.cursor', 'commands', 'create-ide-workflow.md');
|
|
88
87
|
const profileCommandPath = join(cwd, '.cursor', 'commands', 'srai-profile.md');
|
|
89
88
|
const guardrailsInitProfileCommandPath = join(cwd, '.cursor', 'commands', 'guardrails-init-profile.md');
|
|
90
89
|
const skillPath = join(cwd, '.cursor', 'skills', 'threat-modelling', 'SKILL.md');
|
|
91
90
|
const hooksPath = join(cwd, '.cursor', 'hooks.json');
|
|
92
91
|
|
|
93
92
|
const baseRuleContent = getRuleContent(optionsWithSkillDirs);
|
|
94
|
-
const ctmSyncTriggerRuleContent = getCtmSyncTriggerRuleContent(optionsWithSkillDirs);
|
|
95
93
|
const guardrailsRuleContent = getGuardrailsRuleContent(optionsWithSkillDirs);
|
|
96
|
-
const ctmSyncWorkflowContent = getCtmSyncWorkflowContent(optionsWithSkillDirs);
|
|
97
|
-
const createIdeWorkflowCommandContent = getCreateIdeWorkflowCommandContent(optionsWithSkillDirs);
|
|
98
94
|
const profileCommandContent = getProfileCommandContent(optionsWithSkillDirs);
|
|
99
95
|
const guardrailsInitProfileCommandContent = getGuardrailsInitProfileContent({
|
|
100
96
|
...optionsWithSkillDirs,
|
|
@@ -108,15 +104,17 @@ export function generate(cwd, options = {}) {
|
|
|
108
104
|
baseRuleContent,
|
|
109
105
|
);
|
|
110
106
|
|
|
111
|
-
const ctmSyncTriggerRule = writeCursorCommand(ctmSyncTriggerRulePath, ctmSyncTriggerRuleContent);
|
|
112
107
|
const guardrailsRule = writeCursorRule(
|
|
113
108
|
guardrailsRulePath,
|
|
114
109
|
'Vibe Guardrails — fetch and enforce project-specific secure coding guardrails from SRAI',
|
|
115
110
|
guardrailsRuleContent,
|
|
116
111
|
);
|
|
117
|
-
const
|
|
118
|
-
const
|
|
119
|
-
const
|
|
112
|
+
const deletedLegacyRule = removeGeneratedText(join(cwd, '.cursor', 'rules', 'ctm_sync_rule.mdc'));
|
|
113
|
+
const deletedLegacyCommand = removeGeneratedText(join(cwd, '.cursor', 'commands', 'ctm_sync.md'));
|
|
114
|
+
const deletedLegacyAgent = removeGeneratedText(join(cwd, '.cursor', 'agents', 'ctm_sync.md'));
|
|
115
|
+
const deletedLegacyWorkflowCommand = removeGeneratedText(
|
|
116
|
+
join(cwd, '.cursor', 'commands', 'create-ide-workflow.md'),
|
|
117
|
+
);
|
|
120
118
|
|
|
121
119
|
const profileCommand = writeCursorCommand(profileCommandPath, profileCommandContent);
|
|
122
120
|
const guardrailsInitProfileCommand = writeCursorCommand(
|
|
@@ -134,15 +132,15 @@ export function generate(cwd, options = {}) {
|
|
|
134
132
|
|
|
135
133
|
return [
|
|
136
134
|
{ ...baseRule, kind: 'rule' },
|
|
137
|
-
{ ...ctmSyncTriggerRule, kind: 'rule' },
|
|
138
135
|
{ ...guardrailsRule, kind: 'rule' },
|
|
139
|
-
{ ...ctmSyncWorkflow, kind: 'command' },
|
|
140
|
-
{ ...ctmSyncAgent, kind: 'agent' },
|
|
141
|
-
{ ...createIdeWorkflowCommand, kind: 'command' },
|
|
142
136
|
{ ...profileCommand, kind: 'command' },
|
|
143
137
|
{ ...guardrailsInitProfileCommand, kind: 'command' },
|
|
144
138
|
{ filePath: skillPath, action: skillAction, kind: 'skill' },
|
|
145
139
|
{ filePath: hooksPath, action: hooksAction, kind: 'hooks' },
|
|
146
140
|
{ ...cliPermissions, kind: 'config' },
|
|
141
|
+
...(deletedLegacyRule ? [{ ...deletedLegacyRule, kind: 'cleanup' }] : []),
|
|
142
|
+
...(deletedLegacyCommand ? [{ ...deletedLegacyCommand, kind: 'cleanup' }] : []),
|
|
143
|
+
...(deletedLegacyAgent ? [{ ...deletedLegacyAgent, kind: 'cleanup' }] : []),
|
|
144
|
+
...(deletedLegacyWorkflowCommand ? [{ ...deletedLegacyWorkflowCommand, kind: 'cleanup' }] : []),
|
|
147
145
|
];
|
|
148
146
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: guardrails-selection
|
|
3
|
-
description: Analyze the developer request, infer the security categories and likely threats involved, shortlist the most relevant project guardrails, then hydrate the exact guardrails with get_guardrail_by_id before implementation. Use for every security-relevant code task before code is written and preserve the shortlist for
|
|
3
|
+
description: Analyze the developer request, infer the security categories and likely threats involved, shortlist the most relevant project guardrails, then hydrate the exact guardrails with get_guardrail_by_id before implementation. Use for every security-relevant code task before code is written and preserve the shortlist for the final VibeReview sync.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Guardrails Selection
|
|
@@ -16,7 +16,7 @@ This skill exists to stop the IDE from treating the full `get_guardrails` result
|
|
|
16
16
|
3. Predict the threats that might occur for this exact task.
|
|
17
17
|
4. Shortlist only the guardrails that mitigate those threats.
|
|
18
18
|
5. Fetch the exact shortlisted guardrails with `get_guardrail_by_id`.
|
|
19
|
-
6. Carry that same shortlist forward into implementation and
|
|
19
|
+
6. Carry that same shortlist forward into implementation and the final VibeReview markdown sync.
|
|
20
20
|
|
|
21
21
|
Do not skip the analysis step. Do not rely on title-matching alone. Do not dump every guardrail into the final answer.
|
|
22
22
|
|
|
@@ -160,11 +160,11 @@ Once the shortlist is hydrated:
|
|
|
160
160
|
|
|
161
161
|
When deciding whether a guardrail applies, prefer security-preserving inclusion over risky omission. If it plausibly mitigates a realistic path to abuse for the current task, keep it in scope.
|
|
162
162
|
|
|
163
|
-
##
|
|
163
|
+
## VibeReview Sync Contract
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
The final sync step must reuse the shortlist from this skill. It must not call `get_guardrails` or `get_guardrail_by_id` again.
|
|
166
166
|
|
|
167
|
-
Before `
|
|
167
|
+
Before `sync_ai_ide_markdown` is called, ensure the main agent context clearly contains:
|
|
168
168
|
|
|
169
169
|
- the exact existing guardrails shortlisted earlier
|
|
170
170
|
- which of them were applied
|
|
@@ -182,6 +182,6 @@ A good selection does all of the following:
|
|
|
182
182
|
- captures adjacent controls that stop bypass chains
|
|
183
183
|
- avoids irrelevant noise
|
|
184
184
|
- produces a small, defensible set of guardrails that can actually guide implementation
|
|
185
|
-
- leaves
|
|
185
|
+
- leaves the final VibeReview markdown with an exact list of what the IDE selected and enforced
|
|
186
186
|
|
|
187
187
|
If your shortlist feels generic, it is probably incomplete or over-broad. Re-check the prompt, the code patterns, and the threat map.
|
|
@@ -229,4 +229,4 @@ Likely threat families:
|
|
|
229
229
|
- Shortlist for exploit chains, not isolated weaknesses.
|
|
230
230
|
- Logging often matters because poor auditability can turn spoofing, tampering, or privilege abuse into repudiation.
|
|
231
231
|
- Client-side logic often needs server-side guardrails even if the visible change is in the UI.
|
|
232
|
-
- If no existing guardrail covers a realistic recurring threat, create an `ide_generated` guardrail and carry it into
|
|
232
|
+
- If no existing guardrail covers a realistic recurring threat, create an `ide_generated` guardrail and carry it into the final VibeReview markdown sync.
|
|
@@ -34,7 +34,7 @@ That skill is mandatory because the IDE must not leave guardrail selection to ch
|
|
|
34
34
|
- **Load the broad catalog** — call `get_guardrails` with `project_id`.
|
|
35
35
|
- **Shortlist intentionally** — choose only the guardrails that mitigate the categories and likely threats for the current task.
|
|
36
36
|
- **Hydrate the exact shortlist** — call `get_guardrail_by_id` for the shortlisted guardrail ids so implementation uses the exact selected guardrails, not a vague reading of the full catalog.
|
|
37
|
-
- **Preserve the shortlist** — keep the selected existing guardrails in context so
|
|
37
|
+
- **Preserve the shortlist** — keep the selected existing guardrails in context so the final VibeReview markdown can include the same guardrails later without re-querying.
|
|
38
38
|
|
|
39
39
|
The broad `get_guardrails` result is only the candidate catalog. The active implementation guardrails must come from the shortlisted `get_guardrail_by_id` result.
|
|
40
40
|
|
|
@@ -63,7 +63,7 @@ Guardrails and PWNISMS are complementary:
|
|
|
63
63
|
|
|
64
64
|
- Guardrails provide **project-specific, concrete rules** derived from prior threat reviews, compliance requirements, and team decisions.
|
|
65
65
|
- PWNISMS provides **universal threat-category coverage** to catch gaps that guardrails may not yet cover.
|
|
66
|
-
- When PWNISMS analysis reveals a gap not covered by any existing guardrail, **create a new guardrail on the fly** and apply it immediately (marked `source: "ide_generated"` in
|
|
66
|
+
- When PWNISMS analysis reveals a gap not covered by any existing guardrail, **create a new guardrail on the fly** and apply it immediately (marked `source: "ide_generated"` in the VibeReview markdown).
|
|
67
67
|
|
|
68
68
|
### 4. Report guardrail compliance
|
|
69
69
|
|
|
@@ -71,7 +71,7 @@ After code generation, include a brief guardrails compliance summary:
|
|
|
71
71
|
|
|
72
72
|
- List which guardrails were applied (by title), distinguishing existing vs IDE-generated.
|
|
73
73
|
- Flag any guardrails that could not be fully satisfied and explain why.
|
|
74
|
-
- Do not drop shortlisted existing guardrails from session context.
|
|
74
|
+
- Do not drop shortlisted existing guardrails from session context. The final VibeReview markdown must include the same shortlist, including any unsatisfied items with notes.
|
|
75
75
|
|
|
76
76
|
---
|
|
77
77
|
|
|
@@ -79,9 +79,9 @@ After code generation, include a brief guardrails compliance summary:
|
|
|
79
79
|
|
|
80
80
|
Guardrails are living artifacts. The IDE agent can create, apply, and update them:
|
|
81
81
|
|
|
82
|
-
- **New guardrail**: When threat modeling or code review reveals a recurring security pattern not yet captured, the IDE agent creates and applies a new guardrail on the fly. These are reported to SRAI
|
|
82
|
+
- **New guardrail**: When threat modeling or code review reveals a recurring security pattern not yet captured, the IDE agent creates and applies a new guardrail on the fly. These are reported to SRAI in the final VibeReview markdown with `source: "ide_generated"`.
|
|
83
83
|
- **Edit suggestion**: When an existing guardrail is too broad, too narrow, or outdated, suggest an update to the user.
|
|
84
|
-
- All guardrails — existing and IDE-generated — must be preserved in the
|
|
84
|
+
- All guardrails — existing and IDE-generated — must be preserved in the VibeReview markdown under a single `Guardrails Applied` section that the server can normalize into `guardrails_applied`.
|
|
85
85
|
|
|
86
86
|
---
|
|
87
87
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"hooks": {
|
|
4
4
|
"sessionStart": [
|
|
5
5
|
{
|
|
6
|
-
"command": "printf '%s\\n' '{\"additional_context\":\"## MANDATORY SECURITY GATE (Session Policy)\\n\\nFor ANY user request that has security impact (auth, crypto, input handling, secrets, network, data storage, dependencies, new APIs/endpoints, or any code handling untrusted data):\\n\\n**Plan order:**\\n1. **Fetch Vibe Guardrails (first)** - Use .cursor/skills/guardrails-selection/SKILL.md. Analyze the task, call get_guardrails from security-review-mcp to load the broad catalog, shortlist the relevant guardrails, then call get_guardrail_by_id for the shortlist. These exact shortlisted guardrails are the hard constraints on generated code.\\n2. **PWNISMS threat modeling (second)** - Run threat modeling as a guide before writing code. Use .cursor/skills/threat-modelling/SKILL.md. Cross-reference guardrails with PWNISMS findings to identify gaps. Document findings to inform secure implementation.\\n3. **Implement secure code (third)** - Use the threat model findings AND shortlisted guardrails as guides to create secure code. Every must guardrail must be satisfied; no must_not guardrail may be violated.\\n4. **
|
|
6
|
+
"command": "printf '%s\\n' '{\"additional_context\":\"## MANDATORY SECURITY GATE (Session Policy)\\n\\nFor ANY user request that has security impact (auth, crypto, input handling, secrets, network, data storage, dependencies, new APIs/endpoints, or any code handling untrusted data):\\n\\n**Plan order:**\\n1. **Fetch Vibe Guardrails (first)** - Use .cursor/skills/guardrails-selection/SKILL.md. Analyze the task, call get_guardrails from security-review-mcp to load the broad catalog, shortlist the relevant guardrails, then call get_guardrail_by_id for the shortlist. These exact shortlisted guardrails are the hard constraints on generated code.\\n2. **PWNISMS threat modeling (second)** - Run threat modeling as a guide before writing code. Use .cursor/skills/threat-modelling/SKILL.md. Cross-reference guardrails with PWNISMS findings to identify gaps. Document findings to inform secure implementation.\\n3. **Implement secure code (third)** - Use the threat model findings AND shortlisted guardrails as guides to create secure code. Every must guardrail must be satisfied; no must_not guardrail may be violated.\\n4. **VibeReview sync (last)** - The main agent must write a structured `.md` artifact under `vibereview/`, validate that all required metadata and sections are present, and call `sync_ai_ide_markdown` directly after implementation is done. Include the exact shortlisted guardrails from earlier plus any IDE-generated guardrails in the markdown; do not query guardrails again during sync.\\n\\nNo blocking. No deferral. Guardrails enforce secure code; threat modeling guides the design; the main agent owns the final markdown sync to SRAI. Both sync and guardrail compliance are mandatory and should be part of your to-do.\"}'",
|
|
7
7
|
"timeout": 5
|
|
8
8
|
}
|
|
9
9
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: PWNISMS Threat Modelling
|
|
3
|
-
description: Security-first threat modelling workflow for code and architecture tasks. Walks all 7 PWNISMS categories, enforces vibe guardrails (secure by code), and synchronizes findings via
|
|
3
|
+
description: Security-first threat modelling workflow for code and architecture tasks. Walks all 7 PWNISMS categories, enforces vibe guardrails (secure by code), and synchronizes findings via a direct VibeReview markdown sync. Use before, during, and after implementation.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# PWNISMS — Security-First Threat Modelling
|
|
@@ -21,7 +21,7 @@ Before deep analysis, ensure the project-specific guardrail shortlist exists:
|
|
|
21
21
|
1. Use `{{GUARDRAILS_SELECTION_SKILL_DIR}}/SKILL.md`.
|
|
22
22
|
2. Resolve the project with `find_project_by_name` using `name="<SRAI_PROJECT_NAME>"`.
|
|
23
23
|
3. Call `get_guardrails`, shortlist intentionally for this task, then hydrate the exact shortlist with `get_guardrail_by_id`.
|
|
24
|
-
4. Keep the shortlisted existing guardrails in context for implementation and
|
|
24
|
+
4. Keep the shortlisted existing guardrails in context for implementation and the final VibeReview markdown sync.
|
|
25
25
|
|
|
26
26
|
Do not perform project-profile exploration as part of PWNISMS. The old profile tools are not part of this workflow. Ground the threat model in the user request, repository code, diffs, architecture docs the user provides, and the shortlisted guardrails.
|
|
27
27
|
|
|
@@ -160,7 +160,7 @@ After completing the PWNISMS analysis and before writing code:
|
|
|
160
160
|
- `must` rules → mandatory implementation requirements. Every applicable `must` guardrail must be satisfied.
|
|
161
161
|
- `must_not` rules → hard prohibitions. Code must never violate an applicable `must_not` guardrail.
|
|
162
162
|
4. **Flag conflicts** — If a guardrail conflicts with the user's explicit instruction, flag it and ask for confirmation.
|
|
163
|
-
5. **Create new guardrails on the fly** — When PWNISMS analysis or code review reveals a recurring security pattern not captured by existing guardrails, create and apply it as a new guardrail (marked `source: "ide_generated"` in
|
|
163
|
+
5. **Create new guardrails on the fly** — When PWNISMS analysis or code review reveals a recurring security pattern not captured by existing guardrails, create and apply it as a new guardrail (marked `source: "ide_generated"` in the VibeReview markdown). Include `title`, `rule_type` (must/must_not), `category`, `instruction`, and rationale in the notes.
|
|
164
164
|
|
|
165
165
|
---
|
|
166
166
|
|
|
@@ -197,19 +197,19 @@ When discussing designs before code exists:
|
|
|
197
197
|
|
|
198
198
|
---
|
|
199
199
|
|
|
200
|
-
## Phase 5 —
|
|
200
|
+
## Phase 5 — VibeReview Sync (Post Threat Modelling)
|
|
201
201
|
|
|
202
|
-
**MANDATORY:** After every threat modeling step that produces or modifies threat content,
|
|
202
|
+
**MANDATORY:** After every threat modeling step that produces or modifies threat content, the main agent must update the `vibereview/*.md` artifact and call `sync_ai_ide_markdown` directly.
|
|
203
203
|
|
|
204
|
-
### What triggers
|
|
204
|
+
### What triggers the VibeReview sync
|
|
205
205
|
|
|
206
206
|
- New threat model generated (any form: scenarios, data flows, attack trees, PWNISMS analysis)
|
|
207
207
|
- Existing threat model updated or extended (new threats, refined mitigations, additional components)
|
|
208
208
|
- Guardrails applied during a code-generation task (existing or IDE-generated)
|
|
209
209
|
|
|
210
|
-
### What
|
|
210
|
+
### What the VibeReview markdown must contain
|
|
211
211
|
|
|
212
|
-
The
|
|
212
|
+
The main agent writes a structured `.md` artifact under `vibereview/` and uploads it through `sync_ai_ide_markdown`. That markdown should contain:
|
|
213
213
|
|
|
214
214
|
- **Threat model findings**: threats mitigated, PWNISMS categories, severities, mitigations applied
|
|
215
215
|
- **Best practices achieved**: security patterns followed during implementation
|
|
@@ -217,14 +217,23 @@ The `ctm_sync` agent writes a structured `.md` artifact and uploads it through `
|
|
|
217
217
|
- **Guardrails applied**: all guardrails enforced during this session — both existing ones shortlisted earlier via `get_guardrails` + `get_guardrail_by_id` (`source: "existing"`) and new ones the IDE agent created on the fly (`source: "ide_generated"`), each with satisfaction status
|
|
218
218
|
- **Workflow metadata**: `chat_session_id`, `event_name` or `title`, required `summary`, and optional `workflow_name` / `workflow_description`
|
|
219
219
|
|
|
220
|
-
### How to
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
-
|
|
226
|
-
|
|
227
|
-
|
|
220
|
+
### How to sync
|
|
221
|
+
|
|
222
|
+
1. Write or update a file under `vibereview/`, ideally `vibereview/<chat_session_id>-<slugified-title-or-event-name>.md`.
|
|
223
|
+
2. Put `chat_session_id`, `summary`, and either `title` or `event_name` in frontmatter.
|
|
224
|
+
3. Include the required sections:
|
|
225
|
+
- `Best Practices Achieved`
|
|
226
|
+
- `Threats Mitigated`
|
|
227
|
+
- `Secure Code Snippets`
|
|
228
|
+
- `Guardrails Applied`
|
|
229
|
+
- `OWASP Top 10 2025 Mappings`
|
|
230
|
+
4. Validate that:
|
|
231
|
+
- every threat entry includes `threat_name`, `pwnisms_category`, `severity`, and `mitigation_applied`
|
|
232
|
+
- every guardrail includes `title`, `rule_type`, `source`, and `satisfied`
|
|
233
|
+
- OWASP mappings use exact IDs and names
|
|
234
|
+
- snippets are grounded in actual code, not invented text
|
|
235
|
+
5. Call `sync_ai_ide_markdown` directly with the finished markdown artifact.
|
|
236
|
+
6. If sync fails, leave the artifact in `vibereview/` and report the failure clearly.
|
|
228
237
|
|
|
229
238
|
---
|
|
230
239
|
|
|
@@ -239,6 +248,6 @@ Before finalizing output, confirm:
|
|
|
239
248
|
- [ ] Residual risk and follow-up actions are stated.
|
|
240
249
|
- [ ] Vibe guardrails were fetched and enforced (all applicable `must`/`must_not` rules satisfied).
|
|
241
250
|
- [ ] Guardrail compliance summary is included in the response (existing + IDE-generated).
|
|
242
|
-
- [ ]
|
|
251
|
+
- [ ] The VibeReview markdown was written under `vibereview/` and `sync_ai_ide_markdown` was called successfully.
|
|
243
252
|
|
|
244
253
|
If ANY box cannot be checked, you MUST flag the gap to the user with a specific remediation recommendation before finalizing the code.
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { existsSync, unlinkSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import {
|
|
4
|
-
CTM_SYNC_AGENT_REL_PATH,
|
|
5
4
|
GUARDRAILS_SELECTION_SKILL_REL_DIR,
|
|
6
5
|
THREAT_MODELLING_SKILL_REL_DIR,
|
|
7
6
|
} from '../../utils/constants.js';
|
|
8
7
|
import { upsertSentinelBlock, writeText } from '../../utils/fs-helpers.js';
|
|
9
|
-
import {
|
|
8
|
+
import { getRuleContent, getThreatModellingSkillContent } from './content.js';
|
|
10
9
|
|
|
11
10
|
function writeGeneratedText(filePath, content) {
|
|
12
11
|
const action = existsSync(filePath) ? 'updated' : 'created';
|
|
@@ -14,6 +13,14 @@ function writeGeneratedText(filePath, content) {
|
|
|
14
13
|
return { filePath, action };
|
|
15
14
|
}
|
|
16
15
|
|
|
16
|
+
function removeGeneratedText(filePath) {
|
|
17
|
+
if (existsSync(filePath)) {
|
|
18
|
+
unlinkSync(filePath);
|
|
19
|
+
return { filePath, action: 'deleted' };
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
function getCopilotSessionHookContent() {
|
|
18
25
|
const additionalContext = [
|
|
19
26
|
'## MANDATORY SECURITY GATE (Session Policy)',
|
|
@@ -23,9 +30,9 @@ function getCopilotSessionHookContent() {
|
|
|
23
30
|
'1. Fetch Vibe Guardrails first using .github/skills/guardrails-selection/SKILL.md. Resolve the project, call get_guardrails, shortlist relevant guardrails, then call get_guardrail_by_id for the shortlist.',
|
|
24
31
|
'2. Run PWNISMS threat modelling using .github/skills/threat-modelling/SKILL.md. Explicitly walk Product, Workload, Network, IAM, Secrets, Monitoring, and Supply Chain.',
|
|
25
32
|
'3. Implement secure code using both the hydrated guardrails and PWNISMS findings.',
|
|
26
|
-
'4.
|
|
33
|
+
'4. Write a structured .md artifact under vibereview/, 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.',
|
|
27
34
|
'',
|
|
28
|
-
'Do not use project-profile exploration tools during normal coding tasks. No blocking and no deferral: guardrails first, PWNISMS second, implementation third,
|
|
35
|
+
'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.',
|
|
29
36
|
].join('\n');
|
|
30
37
|
|
|
31
38
|
const commandPayload = JSON.stringify({
|
|
@@ -60,7 +67,6 @@ export function generate(cwd, options = {}) {
|
|
|
60
67
|
...options,
|
|
61
68
|
guardrailsSelectionSkillDir: GUARDRAILS_SELECTION_SKILL_REL_DIR.vscode,
|
|
62
69
|
threatModellingSkillDir: THREAT_MODELLING_SKILL_REL_DIR.vscode,
|
|
63
|
-
ctmSyncAgentPath: CTM_SYNC_AGENT_REL_PATH.vscode,
|
|
64
70
|
};
|
|
65
71
|
const filePath = join(cwd, '.github', 'copilot-instructions.md');
|
|
66
72
|
const content = getRuleContent(optionsWithSkillDirs);
|
|
@@ -72,11 +78,7 @@ export function generate(cwd, options = {}) {
|
|
|
72
78
|
getThreatModellingSkillContent(optionsWithSkillDirs),
|
|
73
79
|
);
|
|
74
80
|
|
|
75
|
-
const
|
|
76
|
-
const ctmSyncAgent = writeGeneratedText(
|
|
77
|
-
ctmSyncAgentPath,
|
|
78
|
-
getCtmSyncWorkflowContent(optionsWithSkillDirs),
|
|
79
|
-
);
|
|
81
|
+
const deletedLegacyAgent = removeGeneratedText(join(cwd, '.github', 'agents', 'ctm_sync.agent.md'));
|
|
80
82
|
|
|
81
83
|
const hooksPath = join(cwd, '.github', 'hooks', 'srai-session-policy.json');
|
|
82
84
|
const hooks = writeGeneratedText(hooksPath, getCopilotSessionHookContent());
|
|
@@ -84,7 +86,7 @@ export function generate(cwd, options = {}) {
|
|
|
84
86
|
return [
|
|
85
87
|
{ filePath, action, kind: 'rule' },
|
|
86
88
|
{ ...threatSkill, kind: 'skill' },
|
|
87
|
-
{ ...ctmSyncAgent, kind: 'agent' },
|
|
88
89
|
{ ...hooks, kind: 'hooks' },
|
|
90
|
+
...(deletedLegacyAgent ? [{ ...deletedLegacyAgent, kind: 'cleanup' }] : []),
|
|
89
91
|
];
|
|
90
92
|
}
|
|
@@ -5,7 +5,7 @@ import { test } from 'node:test';
|
|
|
5
5
|
import assert from 'node:assert/strict';
|
|
6
6
|
import { generate } from './vscode.js';
|
|
7
7
|
|
|
8
|
-
test('VS Code Copilot generator writes instructions, skills,
|
|
8
|
+
test('VS Code Copilot generator writes instructions, skills, and hooks', () => {
|
|
9
9
|
const cwd = mkdtempSync(join(tmpdir(), 'securityreview-kit-vscode-'));
|
|
10
10
|
|
|
11
11
|
const results = generate(cwd, { projectName: 'SmokeProject' });
|
|
@@ -13,7 +13,6 @@ test('VS Code Copilot generator writes instructions, skills, agent, and hooks',
|
|
|
13
13
|
const expectedPaths = [
|
|
14
14
|
'.github/copilot-instructions.md',
|
|
15
15
|
'.github/skills/threat-modelling/SKILL.md',
|
|
16
|
-
'.github/agents/ctm_sync.agent.md',
|
|
17
16
|
'.github/hooks/srai-session-policy.json',
|
|
18
17
|
];
|
|
19
18
|
|
|
@@ -21,29 +20,25 @@ test('VS Code Copilot generator writes instructions, skills, agent, and hooks',
|
|
|
21
20
|
assert.equal(existsSync(join(cwd, relPath)), true, `${relPath} should exist`);
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', '
|
|
23
|
+
assert.deepEqual(results.map((entry) => entry.kind), ['rule', 'skill', 'hooks']);
|
|
25
24
|
|
|
26
25
|
const instructions = readFileSync(join(cwd, '.github/copilot-instructions.md'), 'utf8');
|
|
27
26
|
assert.match(instructions, /\.github\/skills\/guardrails-selection\/SKILL\.md/);
|
|
28
27
|
assert.match(instructions, /\.github\/skills\/threat-modelling\/SKILL\.md/);
|
|
29
|
-
assert.match(instructions,
|
|
28
|
+
assert.match(instructions, /sync_ai_ide_markdown/);
|
|
29
|
+
assert.match(instructions, /vibereview\//);
|
|
30
30
|
assert.doesNotMatch(instructions, /\.cursor/);
|
|
31
31
|
|
|
32
32
|
const threatSkill = readFileSync(join(cwd, '.github/skills/threat-modelling/SKILL.md'), 'utf8');
|
|
33
33
|
for (const heading of ['Product', 'Workload', 'Network', 'IAM', 'Secrets', 'Monitoring', 'Supply Chain']) {
|
|
34
34
|
assert.match(threatSkill, new RegExp(heading));
|
|
35
35
|
}
|
|
36
|
-
assert.match(threatSkill,
|
|
36
|
+
assert.match(threatSkill, /sync_ai_ide_markdown/);
|
|
37
|
+
assert.match(threatSkill, /vibereview\//);
|
|
37
38
|
assert.doesNotMatch(threatSkill, /get_project_profile_description/);
|
|
38
39
|
|
|
39
|
-
const agent = readFileSync(join(cwd, '.github/agents/ctm_sync.agent.md'), 'utf8');
|
|
40
|
-
assert.match(agent, /Configured SRAI project name: `SmokeProject`/);
|
|
41
|
-
assert.match(agent, /sync_ai_ide_markdown/);
|
|
42
|
-
assert.match(agent, /structured markdown artifact/i);
|
|
43
|
-
assert.match(agent, /OWASP Top 10 2025 Mappings/);
|
|
44
|
-
assert.match(agent, /vibereview\//);
|
|
45
|
-
assert.doesNotMatch(agent, /Call `create_ai_ide_event` with/i);
|
|
46
|
-
|
|
47
40
|
const hooks = JSON.parse(readFileSync(join(cwd, '.github/hooks/srai-session-policy.json'), 'utf8'));
|
|
48
41
|
assert.equal(hooks.hooks.SessionStart[0].type, 'command');
|
|
42
|
+
assert.match(hooks.hooks.SessionStart[0].command, /vibereview/);
|
|
43
|
+
assert.match(hooks.hooks.SessionStart[0].command, /sync_ai_ide_markdown/);
|
|
49
44
|
});
|
package/src/utils/constants.js
CHANGED
|
@@ -83,13 +83,5 @@ export const THREAT_MODELLING_SKILL_REL_DIR = {
|
|
|
83
83
|
codex: '.codex/skills/threat-modelling',
|
|
84
84
|
};
|
|
85
85
|
|
|
86
|
-
/** Relative workspace paths for the CTM sync agent/workflow (per IDE / CLI). */
|
|
87
|
-
export const CTM_SYNC_AGENT_REL_PATH = {
|
|
88
|
-
cursor: '.cursor/agents/ctm_sync.md',
|
|
89
|
-
claude: '.claude/agents/ctm_sync.md',
|
|
90
|
-
vscode: '.github/agents/ctm_sync.agent.md',
|
|
91
|
-
codex: '.codex/agents/ctm_sync.toml',
|
|
92
|
-
};
|
|
93
|
-
|
|
94
86
|
export const SENTINEL_START = '<!-- securityreview-kit:start -->';
|
|
95
87
|
export const SENTINEL_END = '<!-- securityreview-kit:end -->';
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: create-ide-workflow
|
|
3
|
-
description: Create an AI IDE workflow in SRAI via security-review-mcp.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Create IDE Workflow
|
|
7
|
-
|
|
8
|
-
Use `security-review-mcp` to create a workflow by calling:
|
|
9
|
-
|
|
10
|
-
- `create_ai_ide_workflow`
|
|
11
|
-
|
|
12
|
-
Required payload fields:
|
|
13
|
-
|
|
14
|
-
- `project_id`
|
|
15
|
-
- `name`
|
|
16
|
-
- `description`
|
|
17
|
-
|
|
18
|
-
## Steps
|
|
19
|
-
|
|
20
|
-
1. Resolve `project_id`.
|
|
21
|
-
- Use configured project name `<SRAI_PROJECT_NAME>` by default.
|
|
22
|
-
- Call `find_project_by_name` with `name="<SRAI_PROJECT_NAME>"`.
|
|
23
|
-
- If not found, call `list_projects` and select the right project.
|
|
24
|
-
2. Build `name` and `description` from the user request.
|
|
25
|
-
- Keep `name` short and action-oriented.
|
|
26
|
-
- Keep `description` specific about trigger and output.
|
|
27
|
-
3. Call `create_ai_ide_workflow` with:
|
|
28
|
-
- `project_id`
|
|
29
|
-
- `name`
|
|
30
|
-
- `description`
|
|
31
|
-
4. Return a concise confirmation including:
|
|
32
|
-
- project id
|
|
33
|
-
- workflow name
|
|
34
|
-
- workflow id from MCP response (if returned)
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: ctm_sync
|
|
3
|
-
description: Command/workflow triggered whenever a threat model is generated or updated, or guardrails are proposed/modified. Writes a grounded AI IDE sync markdown file and uploads it through security-review-mcp.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# CTM Sync Workflow
|
|
7
|
-
|
|
8
|
-
Configured SRAI project name: `<SRAI_PROJECT_NAME>`
|
|
9
|
-
|
|
10
|
-
When invoked:
|
|
11
|
-
|
|
12
|
-
0. Verify `sync_ai_ide_markdown` exists in `security-review-mcp`.
|
|
13
|
-
1. Read the parent agent context and extract the latest threat model details, guardrail outcomes, and grounded code snippets from the actual work completed in this session.
|
|
14
|
-
2. **Chat session identity (required)** — The markdown MUST include a stable `chat_session_id` for the current IDE chat session:
|
|
15
|
-
- Use a session identifier supplied by the host environment when one exists (for example, the IDE conversation or session id).
|
|
16
|
-
- If none exists, use a single UUID (or equivalent) generated **once** at the start of this chat and reused for every `ctm_sync` in the same session.
|
|
17
|
-
- Put `chat_session_id` in frontmatter whenever possible. The value must exist somewhere in the markdown even if frontmatter is unavailable.
|
|
18
|
-
3. **Create a structured markdown artifact** — `ctm_sync` is now a thin sync shim. Do not build a JSON payload and do not perform workflow lookup or creation client-side. Instead, write a `.md` file under `vibereview/` so all sync artifacts stay in one predictable place. Create the directory if it does not already exist. The markdown file should give the server everything it needs to extract the structured event.
|
|
19
|
-
4. **Populate the markdown with grounded content**:
|
|
20
|
-
- Include the exact shortlisted existing guardrails selected earlier plus any `ide_generated` guardrails created during the session.
|
|
21
|
-
- Use real code snippets from the files changed or inspected in this session. Never invent code or mitigation text.
|
|
22
|
-
- Keep threat names, severities, PWNISMS categories, guardrail satisfaction, and OWASP mappings aligned with the actual work performed.
|
|
23
|
-
5. **Upload the markdown**:
|
|
24
|
-
- Call `sync_ai_ide_markdown` with the markdown artifact.
|
|
25
|
-
- The server is responsible for normalizing author identity, deriving a workflow name from the title when needed, and extracting structured output from the markdown.
|
|
26
|
-
6. **Stop there** — Do not call `create_ai_ide_event`, `create_ai_ide_workflow`, or client-side user identity tools from `ctm_sync`.
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## Markdown Contract
|
|
31
|
-
|
|
32
|
-
The markdown file must be a `.md` document written under `vibereview/`, with frontmatter when available and clear, structured sections underneath. Keep the structure deterministic so the server can parse it reliably.
|
|
33
|
-
|
|
34
|
-
Recommended path pattern:
|
|
35
|
-
|
|
36
|
-
```text
|
|
37
|
-
vibereview/<chat_session_id>-<slugified-title-or-event-name>.md
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
If a safe filename cannot be derived, use any stable `.md` filename inside `vibereview/`.
|
|
41
|
-
|
|
42
|
-
### Frontmatter
|
|
43
|
-
|
|
44
|
-
Use YAML frontmatter at the top of the file whenever possible:
|
|
45
|
-
|
|
46
|
-
```yaml
|
|
47
|
-
---
|
|
48
|
-
chat_session_id: "<required stable session id>"
|
|
49
|
-
workflow_name: "<optional short workflow name>"
|
|
50
|
-
workflow_description: "<optional workflow description>"
|
|
51
|
-
title: "<required event title if event_name is omitted>"
|
|
52
|
-
event_name: "<required event name if title is omitted>"
|
|
53
|
-
summary: "<required concise summary>"
|
|
54
|
-
---
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Rules:
|
|
58
|
-
|
|
59
|
-
- `chat_session_id` must exist somewhere in the markdown, but frontmatter is preferred.
|
|
60
|
-
- `workflow_name` is optional. If omitted, the server derives it from the title.
|
|
61
|
-
- `workflow_description` is optional.
|
|
62
|
-
- Either `event_name` or `title` must exist. Including both is allowed.
|
|
63
|
-
- `summary` must exist.
|
|
64
|
-
- `developer_name` and `developer_email` in the markdown are ignored. The server always uses the authenticated API user.
|
|
65
|
-
|
|
66
|
-
### Body sections
|
|
67
|
-
|
|
68
|
-
Use clear Markdown headings. The exact wording below is preferred because it keeps parsing simple and consistent.
|
|
69
|
-
|
|
70
|
-
#### Best Practices Achieved
|
|
71
|
-
|
|
72
|
-
Use plain bullet strings:
|
|
73
|
-
|
|
74
|
-
```md
|
|
75
|
-
## Best Practices Achieved
|
|
76
|
-
|
|
77
|
-
- Enforced server-side authorization before updating workflow state.
|
|
78
|
-
- Used parameterized queries for threat-model persistence.
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Internally the server accepts both `best_practises_achieved` and `best_practices_achieved`, but authored markdown should prefer a normal `Best Practices Achieved` section with bullets.
|
|
82
|
-
|
|
83
|
-
#### Threats Mitigated
|
|
84
|
-
|
|
85
|
-
Each threat entry should include all of the following:
|
|
86
|
-
|
|
87
|
-
- `threat_name`
|
|
88
|
-
- `pwnisms_category`
|
|
89
|
-
- `severity`
|
|
90
|
-
- `mitigation_applied`
|
|
91
|
-
- grounded code snippet info if available
|
|
92
|
-
|
|
93
|
-
Preferred structure:
|
|
94
|
-
|
|
95
|
-
```md
|
|
96
|
-
## Threats Mitigated
|
|
97
|
-
|
|
98
|
-
### Threat 1
|
|
99
|
-
- threat_name: Missing authorization check on workflow sync endpoint
|
|
100
|
-
- pwnisms_category: IAM
|
|
101
|
-
- severity: High
|
|
102
|
-
- mitigation_applied: Added server-side authorization before allowing markdown sync writes.
|
|
103
|
-
- file_path: src/routes/sync.js
|
|
104
|
-
- language: javascript
|
|
105
|
-
- snippet: |
|
|
106
|
-
if (!user || !user.canSyncWorkflows) {
|
|
107
|
-
throw new ForbiddenError('Not allowed to sync workflow markdown');
|
|
108
|
-
}
|
|
109
|
-
- explanation: Prevents unauthorized users from creating or updating AI IDE sync events.
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Notes:
|
|
113
|
-
|
|
114
|
-
- Snippets must be real code from the repo or actual generated output, not invented text.
|
|
115
|
-
- If no code snippet exists for a threat, omit the snippet block and explain the mitigation in prose.
|
|
116
|
-
|
|
117
|
-
#### Secure Code Snippets
|
|
118
|
-
|
|
119
|
-
Include security-relevant snippets that may or may not map 1:1 with a threat:
|
|
120
|
-
|
|
121
|
-
```md
|
|
122
|
-
## Secure Code Snippets
|
|
123
|
-
|
|
124
|
-
### Snippet 1
|
|
125
|
-
- file_path: src/server/markdown-sync.ts
|
|
126
|
-
- language: typescript
|
|
127
|
-
- snippet: |
|
|
128
|
-
const markdown = await fs.readFile(inputPath, 'utf8');
|
|
129
|
-
await securityReview.syncAiIdeMarkdown({ markdown });
|
|
130
|
-
- explanation: Sends the structured markdown artifact directly to the server-side sync pipeline.
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
#### Guardrails Applied
|
|
134
|
-
|
|
135
|
-
Each guardrail should include:
|
|
136
|
-
|
|
137
|
-
- `title`
|
|
138
|
-
- `rule_type` as `must` or `must_not`
|
|
139
|
-
- `source` as `existing` or `ide_generated`
|
|
140
|
-
- `satisfied` as `true` or `false`
|
|
141
|
-
|
|
142
|
-
Preferred structure:
|
|
143
|
-
|
|
144
|
-
```md
|
|
145
|
-
## Guardrails Applied
|
|
146
|
-
|
|
147
|
-
### Guardrail 1
|
|
148
|
-
- title: Enforce authorization on workflow updates
|
|
149
|
-
- rule_type: must
|
|
150
|
-
- category: authorization
|
|
151
|
-
- instruction: Require an authenticated, authorized user before mutating workflow records.
|
|
152
|
-
- source: existing
|
|
153
|
-
- satisfied: true
|
|
154
|
-
- notes: Applied in the server-side markdown sync handler.
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
The exact shortlist from earlier in the session must be carried forward. Do not call `get_guardrails` or `get_guardrail_by_id` inside `ctm_sync`.
|
|
158
|
-
|
|
159
|
-
#### OWASP Top 10 2025 Mappings
|
|
160
|
-
|
|
161
|
-
Use exact IDs and names:
|
|
162
|
-
|
|
163
|
-
```md
|
|
164
|
-
## OWASP Top 10 2025 Mappings
|
|
165
|
-
|
|
166
|
-
- A01: Broken Access Control
|
|
167
|
-
- A04: Cryptographic Failures
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
Allowed values:
|
|
171
|
-
|
|
172
|
-
| ID | Name |
|
|
173
|
-
|---|---|
|
|
174
|
-
| `A01` | Broken Access Control |
|
|
175
|
-
| `A02` | Security Misconfiguration |
|
|
176
|
-
| `A03` | Software Supply Chain Failures |
|
|
177
|
-
| `A04` | Cryptographic Failures |
|
|
178
|
-
| `A05` | Injection |
|
|
179
|
-
| `A06` | Insecure Design |
|
|
180
|
-
| `A07` | Authentication Failures |
|
|
181
|
-
| `A08` | Software or Data Integrity Failures |
|
|
182
|
-
| `A09` | Security Logging and Alerting Failures |
|
|
183
|
-
| `A10` | Mishandling of Exceptional Conditions |
|
|
184
|
-
|
|
185
|
-
---
|
|
186
|
-
|
|
187
|
-
## Constraints
|
|
188
|
-
|
|
189
|
-
- The artifact written by `ctm_sync` must be a `.md` file under `vibereview/`.
|
|
190
|
-
- `chat_session_id` must never be omitted.
|
|
191
|
-
- `summary` must never be omitted.
|
|
192
|
-
- `event_name` or `title` must exist.
|
|
193
|
-
- `workflow_name` and `workflow_description` are optional.
|
|
194
|
-
- `developer_name` and `developer_email` should not influence sync behavior; the server ignores them.
|
|
195
|
-
- Every threat entry must include `threat_name`, `pwnisms_category`, `severity`, and `mitigation_applied`.
|
|
196
|
-
- PWNISMS categories must match the supported set: `Product`, `Workload`, `Network`, `IAM`, `Secrets`, `Monitoring`, `Supply Chain`.
|
|
197
|
-
- `severity` should be one of `Critical`, `High`, `Medium`, or `Low`.
|
|
198
|
-
- Guardrails must include `title`, `rule_type`, `source`, and `satisfied`.
|
|
199
|
-
- `rule_type` must be `must` or `must_not`.
|
|
200
|
-
- `source` must be `existing` or `ide_generated`.
|
|
201
|
-
- `satisfied` must be `true` or `false`.
|
|
202
|
-
- OWASP mappings must use the exact IDs and names listed above.
|
|
203
|
-
- Snippets must be grounded in real code. Never fabricate examples just to fill the markdown.
|
|
204
|
-
- Do not call client-side workflow creation, event creation, or developer identity tools from `ctm_sync`.
|
|
205
|
-
|
|
206
|
-
---
|
|
207
|
-
|
|
208
|
-
## Output Contract
|
|
209
|
-
|
|
210
|
-
- Never skip sync when a threat model or guardrail-compliance update exists.
|
|
211
|
-
- Never invent missing values; omit optional fields or use concise prose when exact structured data is unavailable.
|
|
212
|
-
- Return a compact confirmation after upload including:
|
|
213
|
-
- the markdown file path used for sync, which should point inside `vibereview/`
|
|
214
|
-
- whether `workflow_name` was explicit or left for server-side derivation
|
|
215
|
-
- count of guardrails included (existing vs IDE-generated)
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: ctm_sync agent trigger rule
|
|
3
|
-
alwaysApply: true
|
|
4
|
-
---
|
|
5
|
-
# Continuous Threat Modeling Sync (`ctm_sync`)
|
|
6
|
-
|
|
7
|
-
**Always synchronize threat models and guardrail compliance after they are (re)generated.**
|
|
8
|
-
|
|
9
|
-
Any time an AI agent:
|
|
10
|
-
- generates a **new threat model**, or
|
|
11
|
-
- **updates** an existing threat model, or
|
|
12
|
-
- performs a **non-trivial extension** of an existing threat model (e.g., adds new threat scenarios, countermeasures, data flows, or components), or
|
|
13
|
-
- **applies vibe guardrails** during a code-generation task (existing or IDE-generated) and has guardrail compliance data to report,
|
|
14
|
-
|
|
15
|
-
it **must immediately call** the `ctm_sync` agent to synchronize the latest threat modeling state and guardrail data.
|
|
16
|
-
|
|
17
|
-
This rule applies regardless of:
|
|
18
|
-
- which methodology is used (PWNISMS, STRIDE, OWASP, etc.),
|
|
19
|
-
- which skill or MCP was used to produce the threat model, and
|
|
20
|
-
- whether the threat model is partial, incremental, or "draft" form.
|
|
21
|
-
|
|
22
|
-
If in doubt — **assume this rule applies**.
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## When this rule applies
|
|
27
|
-
|
|
28
|
-
This rule is **triggered** whenever the assistant:
|
|
29
|
-
|
|
30
|
-
- **Produces a threat model** in any form, such as:
|
|
31
|
-
- Threat scenarios (even a short list)
|
|
32
|
-
- Data flow diagrams or component diagrams described in text
|
|
33
|
-
- Security objectives or misuse cases
|
|
34
|
-
- Attack trees or abuse cases
|
|
35
|
-
- Structured PWNISMS analysis
|
|
36
|
-
- **Extends or refines** an earlier threat model, e.g.:
|
|
37
|
-
- "Add more threats for the Payments service"
|
|
38
|
-
- "Update the data flow for the auth system"
|
|
39
|
-
- "Refine the mitigations for threat X"
|
|
40
|
-
- **Enforces guardrails** during code generation and has compliance data:
|
|
41
|
-
- Guardrails were shortlisted via the guardrails-selection workflow (`get_guardrails` + `get_guardrail_by_id`) and applied to generated code
|
|
42
|
-
- New guardrails were created by the IDE agent based on gaps found
|
|
43
|
-
- Existing guardrails were flagged as unsatisfiable or conflicting
|
|
44
|
-
|
|
45
|
-
This includes both:
|
|
46
|
-
- Threat modeling done **directly in this conversation**, and
|
|
47
|
-
- Threat modeling done by calling **MCP tools or skills** that output threat scenarios, countermeasures, data dictionaries, or other CTM artifacts.
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## Required behavior
|
|
52
|
-
|
|
53
|
-
**After completing any threat modeling step that produces or modifies threat content, or any code-generation step where guardrails were enforced, the assistant must:**
|
|
54
|
-
|
|
55
|
-
1. **Ensure threat modeling output is finalized for this step**
|
|
56
|
-
|
|
57
|
-
- The assistant should finish producing the current threat modeling content (threat scenarios, components, data dictionaries, etc.) in the user-visible message or via tools.
|
|
58
|
-
- The assistant should not call `ctm_sync` *before* the threat model content exists; it must be called **after** threat data for this step is available.
|
|
59
|
-
|
|
60
|
-
2. **Immediately call the `ctm_sync` agent**
|
|
61
|
-
|
|
62
|
-
- Use the `Task` tool (or equivalent agent launcher) with `subagent_type="ctm_sync"`.
|
|
63
|
-
- **Session routing:** In the prompt (or structured handoff), always include the **current chat's stable `chat_session_id`** — the same id for every `ctm_sync` in this conversation, and a new id for a different chat. Use the IDE/session id when exposed; otherwise generate one UUID at the start of the chat and reuse it. This ensures SRAI attaches events to the correct per-session workflow.
|
|
64
|
-
- Provide a clear, concise description in the `description`/`prompt` fields explaining:
|
|
65
|
-
- What system or component the threat model covers
|
|
66
|
-
- Whether this is a new threat model or an update
|
|
67
|
-
- Where the latest threat content can be found (e.g., "from this conversation" or "from the last threat modeling step")
|
|
68
|
-
- The `chat_session_id` value to use for this sync
|
|
69
|
-
- Whether guardrails were applied (existing and/or IDE-generated)
|
|
70
|
-
- That `ctm_sync` should write a structured `.md` artifact and upload it via `sync_ai_ide_markdown`
|
|
71
|
-
- **Do NOT include developer name or email in the handoff prompt.** Any identity fields in the markdown are ignored server-side; the authenticated API user is authoritative.
|
|
72
|
-
|
|
73
|
-
**Example invocation (conceptual, not literal code):**
|
|
74
|
-
|
|
75
|
-
Use Task tool with:
|
|
76
|
-
- subagent_type: "ctm_sync"
|
|
77
|
-
- description: "Sync latest threat model for <system/component>"
|
|
78
|
-
- prompt: Brief summary of what was threat-modeled, what artifacts were produced (threat scenarios, countermeasures, components, data dictionary, guardrails applied/proposed, etc.), and that the agent should write the sync markdown and upload it accordingly.
|
|
79
|
-
- readonly: false (it must be allowed to write/update CTM data)
|
|
80
|
-
|
|
81
|
-
3. Let `ctm_sync` complete its work
|
|
82
|
-
The assistant should wait for the ctm_sync agent to complete (or reach a healthy steady state) before declaring the threat-modeling step fully done.
|
|
83
|
-
If ctm_sync returns errors that can be reasonably fixed (e.g., missing project name), the assistant should fix the issue and retry once, if possible.
|
|
84
|
-
|
|
85
|
-
Acknowledge completion to the user
|
|
86
|
-
After ctm_sync completes, the assistant should briefly tell the user that:
|
|
87
|
-
Threat modeling is finished for this step, and
|
|
88
|
-
The results have been synchronized via ctm_sync (without leaking internal tooling details beyond what is appropriate for the user's context).
|