@securityreviewai/securityreview-kit 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { input, checkbox, confirm, select } from '@inquirer/prompts';
|
|
3
3
|
import { TARGETS, TARGET_NAMES } from '../utils/constants.js';
|
|
4
4
|
import { detectTargets } from '../utils/detect.js';
|
|
5
|
-
import {
|
|
5
|
+
import { fetchVibeReviewProjectNames, getStoredCredentials, normalizeApiUrl } from '../utils/srai.js';
|
|
6
6
|
|
|
7
7
|
// Dynamic imports for generators (avoids loading all at startup)
|
|
8
8
|
const mcpGenerators = {
|
|
@@ -111,27 +111,36 @@ async function resolveEnvVars(options, interactive, cwd) {
|
|
|
111
111
|
async function resolveProjectName(options, interactive, apiUrl, apiToken) {
|
|
112
112
|
const pinnedProject = (options.projectName || process.env.SECURITY_REVIEW_PROJECT_NAME || '').trim();
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
return pinnedProject;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const projectNames = await fetchProjectNames(apiUrl, apiToken);
|
|
114
|
+
const projectNames = await fetchVibeReviewProjectNames(apiUrl, apiToken);
|
|
119
115
|
|
|
120
116
|
if (!interactive) {
|
|
117
|
+
if (pinnedProject) {
|
|
118
|
+
if (!projectNames.includes(pinnedProject)) {
|
|
119
|
+
console.log(
|
|
120
|
+
chalk.yellow(
|
|
121
|
+
`⚠ Project "${pinnedProject}" is not in the vibe-review-enabled list. ` +
|
|
122
|
+
'Only projects with is_vibe_review=true can be selected. Pass a valid --project-name or run interactive mode.',
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
return pinnedProject;
|
|
128
|
+
}
|
|
129
|
+
|
|
121
130
|
if (projectNames.length === 1) {
|
|
122
131
|
return projectNames[0];
|
|
123
132
|
}
|
|
124
133
|
|
|
125
134
|
console.log(
|
|
126
135
|
chalk.yellow(
|
|
127
|
-
'⚠ Multiple projects available. Pass --project-name or run interactive mode to choose one.',
|
|
136
|
+
'⚠ Multiple vibe-review projects available. Pass --project-name or run interactive mode to choose one.',
|
|
128
137
|
),
|
|
129
138
|
);
|
|
130
139
|
process.exit(1);
|
|
131
140
|
}
|
|
132
141
|
|
|
133
142
|
const selected = await select({
|
|
134
|
-
message: '🧩 Select SRAI project
|
|
143
|
+
message: '🧩 Select SRAI project (vibe review enabled only):',
|
|
135
144
|
choices: projectNames.map((name) => ({
|
|
136
145
|
name,
|
|
137
146
|
value: name,
|
|
@@ -4,7 +4,7 @@ import chalk from 'chalk';
|
|
|
4
4
|
import { input, select } from '@inquirer/prompts';
|
|
5
5
|
import { SENTINEL_START, TARGET_NAMES, TARGETS } from '../utils/constants.js';
|
|
6
6
|
import { readJson, readText } from '../utils/fs-helpers.js';
|
|
7
|
-
import {
|
|
7
|
+
import { fetchVibeReviewProjectNames, getStoredCredentials, normalizeApiUrl } from '../utils/srai.js';
|
|
8
8
|
|
|
9
9
|
const ruleGenerators = {
|
|
10
10
|
cursor: () => import('../generators/rules/cursor.js'),
|
|
@@ -116,10 +116,10 @@ async function resolveCredentials(options, cwd) {
|
|
|
116
116
|
|
|
117
117
|
async function resolveProjectName(options, apiUrl, apiToken) {
|
|
118
118
|
const pinnedProject = (options.projectName || process.env.SECURITY_REVIEW_PROJECT_NAME || '').trim();
|
|
119
|
-
const projectNames = await
|
|
119
|
+
const projectNames = await fetchVibeReviewProjectNames(apiUrl, apiToken);
|
|
120
120
|
|
|
121
121
|
return select({
|
|
122
|
-
message: '🧩 Select SRAI project:',
|
|
122
|
+
message: '🧩 Select SRAI project (vibe review enabled only):',
|
|
123
123
|
choices: projectNames.map((name) => ({
|
|
124
124
|
name,
|
|
125
125
|
value: name,
|
|
@@ -80,6 +80,14 @@ Rather than fetching the entire profile at once, walk through each reasoning blo
|
|
|
80
80
|
|
|
81
81
|
12. **After threat modeling (ALWAYS / POST threat modelling)** — Invoke `ctm_sync` agent and upload the required CTM data.
|
|
82
82
|
|
|
83
|
+
### CTM sync — chat session and workflows (all IDEs)
|
|
84
|
+
|
|
85
|
+
When running `ctm_sync` (dedicated agent/command where available, or the same steps inline):
|
|
86
|
+
|
|
87
|
+
- Include a stable **`chat_session_id`** on every event payload. Reuse one id for the entire current chat; use a different id for a different chat. Prefer an IDE-provided session/conversation id; if unavailable, generate a UUID once per chat and reuse it.
|
|
88
|
+
- **First time** a `chat_session_id` is seen for a project: create an AI IDE workflow via `create_ai_ide_workflow` with a short name `session1`, `session2`, … (next free index) and put `chat_session_id:<id>` in the workflow **description** so it can be found later.
|
|
89
|
+
- **Same chat later:** find the workflow whose description contains that `chat_session_id` marker and push `create_ai_ide_event` to that `workflow_id` — do **not** create another workflow for the same session.
|
|
90
|
+
|
|
83
91
|
### Tool Reference
|
|
84
92
|
|
|
85
93
|
| Category | Tools |
|
|
@@ -91,3 +99,5 @@ Rather than fetching the entire profile at once, walk through each reasoning blo
|
|
|
91
99
|
| **Workflow** | `start_workflow`, `get_workflow_status`, `start_next_workflow_job`, `start_workflow_job`, `retry_workflow_job` |
|
|
92
100
|
| **Analysis** | `get_threat_scenarios`, `get_countermeasures`, `get_components`, `get_data_dictionaries`, `get_security_objectives`, `get_findings`, `get_security_test_cases` |
|
|
93
101
|
| **Integrations** | `fetch_jira_issue`, `fetch_confluence_page`, `search_confluence_pages`, `fetch_and_link_to_srai` |
|
|
102
|
+
| **AI IDE CTM** | `create_ai_ide_workflow`, `create_ai_ide_event` (and any `list_*` AI IDE workflow tools exposed by the server) |
|
|
103
|
+
| **Profile Update** | `update_vibe_project_profile` |
|
|
@@ -9,25 +9,45 @@ Configured SRAI project name: `<SRAI_PROJECT_NAME>`
|
|
|
9
9
|
|
|
10
10
|
When invoked:
|
|
11
11
|
|
|
12
|
-
0. Verify `create_ai_ide_event`
|
|
12
|
+
0. Verify `create_ai_ide_event` and `create_ai_ide_workflow` exist in `security-review-mcp`. If a `list_*` tool for AI IDE workflows exists, prefer it for workflow discovery.
|
|
13
13
|
1. Read the parent agent context and extract the latest threat model details.
|
|
14
|
-
2.
|
|
14
|
+
2. **Chat session identity (required)** — The event payload 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 (e.g. conversation or session id from the IDE/agent runtime).
|
|
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. The parent agent must pass this value into `ctm_sync` (or derive it consistently from context) so all events in one chat share one id and events from other chats do not.
|
|
17
|
+
3. **Resolve or create the AI IDE workflow** (one workflow per distinct `chat_session_id`):
|
|
18
|
+
- Resolve the SRAI project: `find_project_by_name` with `name="<SRAI_PROJECT_NAME>"`. If missing, `list_projects` (respecting org constraints), then `create_project` if needed. Obtain `project_id` as required by MCP tools.
|
|
19
|
+
- List or search AI IDE workflows for that project (use the MCP tool provided, e.g. listing workflows filtered by project). Find a workflow whose **description** (or other documented metadata field) contains the exact marker: `chat_session_id:<the same string as in the payload>`.
|
|
20
|
+
- **If a matching workflow exists:** use its `workflow_id` for the event.
|
|
21
|
+
- **If none exists:** call `create_ai_ide_workflow` with:
|
|
22
|
+
- `project_id`
|
|
23
|
+
- `name`: a short sequential label `session1`, `session2`, `session3`, … — choose the next unused index (e.g. count existing AI IDE workflows for the project and use `session` + (count + 1), or first gap if your listing allows).
|
|
24
|
+
- `description`: must include `chat_session_id:<chat_session_id>` so future syncs can attach to this workflow. Add a brief human-readable note if helpful.
|
|
25
|
+
- Store the returned `workflow_id` for the upload step.
|
|
26
|
+
4. Build a JSON payload for `create_ai_ide_event` with these exact keys:
|
|
15
27
|
- `workflow_id`
|
|
28
|
+
- `chat_session_id` (same value used for routing above)
|
|
16
29
|
- `title`
|
|
17
30
|
- `summary`
|
|
18
|
-
- `developer_name` = fetch from git config unless
|
|
19
|
-
- `developer_email` = fetch from git config unless
|
|
31
|
+
- `developer_name` = fetch from git config unless explicitly specified by user
|
|
32
|
+
- `developer_email` = fetch from git config unless explicitly specified by user
|
|
20
33
|
- `threats_mitigated`
|
|
21
34
|
- `best_practises_achieved`
|
|
22
35
|
- `secure_code_snippets`
|
|
23
|
-
|
|
24
|
-
- Call `find_project_by_name` with `name="<SRAI_PROJECT_NAME>"`.
|
|
25
|
-
- If missing, call `list_projects`, then `create_project` if needed.
|
|
26
|
-
4. Upload the payload using `security-review-mcp`:
|
|
36
|
+
5. Upload the payload using `security-review-mcp`:
|
|
27
37
|
- Use the tool `create_ai_ide_event`
|
|
38
|
+
6. **Push a project profile update** — After the event is created, derive project profile data from the current threat model context and call `update_vibe_project_profile` with `project_id` and the following fields:
|
|
39
|
+
- `description`: a concise summary of the system/project derived from the threat model context (what it does, what data it handles, its overall purpose)
|
|
40
|
+
- `architecture_notes`: a list of architecture observations extracted from the threat model — deployment topology, trust boundaries, data flows, integration points
|
|
41
|
+
- `tech_categories`: a list of technology category labels identified during threat modeling (e.g. `"backend"`, `"frontend"`, `"database"`, `"cloud"`, `"mobile"`)
|
|
42
|
+
- `user_groups`: a list of user roles and groups surfaced in the threat model (e.g. `"admin"`, `"end user"`, `"service account"`)
|
|
43
|
+
- `compliance_requirements`: a list of compliance requirements or standards referenced in the threat model or conversation (e.g. `"PCI-DSS"`, `"HIPAA"`, `"SOC 2"`)
|
|
44
|
+
- `language_stacks`: a list of programming languages, runtimes, and major frameworks identified in the codebase or threat model context (e.g. `"Python/FastAPI"`, `"TypeScript/React"`, `"Java/Spring"`)
|
|
45
|
+
- Omit any field for which no data is available in context — do **not** invent values. Pass an empty list `[]` only if the field is reasonably expected but simply unpopulated.
|
|
28
46
|
|
|
29
47
|
## Output Contract
|
|
30
48
|
|
|
31
49
|
- Never skip upload when a threat model exists.
|
|
32
50
|
- Never invent missing values; use empty strings/arrays if data is unavailable.
|
|
33
|
-
-
|
|
51
|
+
- Never omit `chat_session_id` from the payload.
|
|
52
|
+
- Never skip the `update_vibe_project_profile` call when profile-relevant data (architecture, tech, users, compliance, languages, or description) can be derived from context.
|
|
53
|
+
- Return a compact confirmation after upload (including whether an existing workflow was reused or a new `sessionN` workflow was created, and confirmation that the project profile was updated).
|
|
@@ -55,10 +55,12 @@ This includes both:
|
|
|
55
55
|
2. **Immediately call the `ctm_sync` agent**
|
|
56
56
|
|
|
57
57
|
- Use the `Task` tool (or equivalent agent launcher) with `subagent_type="ctm_sync"`.
|
|
58
|
+
- **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 (`session1`, `session2`, … for new sessions).
|
|
58
59
|
- Provide a clear, concise description in the `description`/`prompt` fields explaining:
|
|
59
60
|
- What system or component the threat model covers
|
|
60
61
|
- Whether this is a new threat model or an update
|
|
61
62
|
- Where the latest threat content can be found (e.g., “from this conversation” or “from the last threat modeling step”)
|
|
63
|
+
- The `chat_session_id` value to use for this sync
|
|
62
64
|
|
|
63
65
|
**Example invocation (conceptual, not literal code):**
|
|
64
66
|
|
package/src/utils/srai.js
CHANGED
|
@@ -115,7 +115,7 @@ function toProjectName(entry) {
|
|
|
115
115
|
return '';
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
function
|
|
118
|
+
function collectProjectLists(payload) {
|
|
119
119
|
const collections = [];
|
|
120
120
|
|
|
121
121
|
if (Array.isArray(payload)) {
|
|
@@ -131,8 +131,12 @@ function extractProjectNames(payload) {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
return collections;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function extractProjectNames(payload) {
|
|
134
138
|
const names = new Set();
|
|
135
|
-
for (const list of
|
|
139
|
+
for (const list of collectProjectLists(payload)) {
|
|
136
140
|
for (const entry of list) {
|
|
137
141
|
const name = toProjectName(entry);
|
|
138
142
|
if (name) names.add(name);
|
|
@@ -143,9 +147,48 @@ function extractProjectNames(payload) {
|
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
/**
|
|
146
|
-
*
|
|
150
|
+
* Whether a project object is marked for AI / vibe review kit selection.
|
|
151
|
+
* Only entries with an explicit true-like `is_vibe_review` (or common API aliases) qualify.
|
|
147
152
|
*/
|
|
148
|
-
export
|
|
153
|
+
export function isVibeReviewProject(entry) {
|
|
154
|
+
if (!entry || typeof entry !== 'object') return false;
|
|
155
|
+
|
|
156
|
+
const keys = ['is_vibe_review', 'isVibeReview', 'vibe_review', 'vibeReview'];
|
|
157
|
+
for (const key of keys) {
|
|
158
|
+
if (!Object.prototype.hasOwnProperty.call(entry, key)) continue;
|
|
159
|
+
const value = entry[key];
|
|
160
|
+
if (value === true || value === 1) return true;
|
|
161
|
+
if (typeof value === 'string') {
|
|
162
|
+
const v = value.toLowerCase().trim();
|
|
163
|
+
if (v === 'true' || v === '1' || v === 'yes') return true;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Extract { name, entry } pairs from a projects API payload.
|
|
173
|
+
*/
|
|
174
|
+
export function extractProjectEntries(payload) {
|
|
175
|
+
const out = [];
|
|
176
|
+
const seen = new Set();
|
|
177
|
+
|
|
178
|
+
for (const list of collectProjectLists(payload)) {
|
|
179
|
+
for (const entry of list) {
|
|
180
|
+
const name = toProjectName(entry);
|
|
181
|
+
if (!name || seen.has(name)) continue;
|
|
182
|
+
seen.add(name);
|
|
183
|
+
out.push({ name, entry });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
out.sort((a, b) => a.name.localeCompare(b.name));
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function fetchProjectsPayload(apiUrl, apiToken) {
|
|
149
192
|
const endpoint = resolveProjectsEndpoint(apiUrl);
|
|
150
193
|
|
|
151
194
|
let response;
|
|
@@ -174,6 +217,14 @@ export async function fetchProjectNames(apiUrl, apiToken) {
|
|
|
174
217
|
throw new Error(`Project fetch returned non-JSON content at ${endpoint}`);
|
|
175
218
|
}
|
|
176
219
|
|
|
220
|
+
return { payload, endpoint };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Fetch all project names from SRAI (no filtering).
|
|
225
|
+
*/
|
|
226
|
+
export async function fetchProjectNames(apiUrl, apiToken) {
|
|
227
|
+
const { payload, endpoint } = await fetchProjectsPayload(apiUrl, apiToken);
|
|
177
228
|
const names = extractProjectNames(payload);
|
|
178
229
|
if (names.length === 0) {
|
|
179
230
|
throw new Error(`No project names found in API response from ${endpoint}`);
|
|
@@ -181,3 +232,21 @@ export async function fetchProjectNames(apiUrl, apiToken) {
|
|
|
181
232
|
|
|
182
233
|
return names;
|
|
183
234
|
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Fetch project names that have `is_vibe_review` (or API alias) set true — used for kit init / project switch.
|
|
238
|
+
*/
|
|
239
|
+
export async function fetchVibeReviewProjectNames(apiUrl, apiToken) {
|
|
240
|
+
const { payload, endpoint } = await fetchProjectsPayload(apiUrl, apiToken);
|
|
241
|
+
const entries = extractProjectEntries(payload);
|
|
242
|
+
const names = entries.filter(({ entry }) => isVibeReviewProject(entry)).map(({ name }) => name);
|
|
243
|
+
|
|
244
|
+
if (names.length === 0) {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`No projects with vibe review enabled (is_vibe_review=true) were returned from ${endpoint}. ` +
|
|
247
|
+
'Enable vibe review on at least one SRAI project, or check your API token and URL.',
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return names;
|
|
252
|
+
}
|