appsec-agent 2.8.0 → 3.0.1
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 +0 -18
- package/dist/bin/agent-run.js +14 -17
- package/dist/bin/agent-run.js.map +1 -1
- package/dist/src/__tests__/mocks/codex_sdk.d.ts +53 -0
- package/dist/src/__tests__/mocks/codex_sdk.d.ts.map +1 -0
- package/dist/src/__tests__/mocks/codex_sdk.js +8 -0
- package/dist/src/__tests__/mocks/codex_sdk.js.map +1 -0
- package/dist/src/agent_actions.d.ts +1 -1
- package/dist/src/agent_actions.d.ts.map +1 -1
- package/dist/src/agent_actions.js +41 -27
- package/dist/src/agent_actions.js.map +1 -1
- package/dist/src/agent_options.d.ts +17 -93
- package/dist/src/agent_options.d.ts.map +1 -1
- package/dist/src/agent_options.js +191 -310
- package/dist/src/agent_options.js.map +1 -1
- package/dist/src/llm_query.d.ts +4 -43
- package/dist/src/llm_query.d.ts.map +1 -1
- package/dist/src/llm_query.js +4 -145
- package/dist/src/llm_query.js.map +1 -1
- package/dist/src/main.d.ts.map +1 -1
- package/dist/src/main.js +1 -7
- package/dist/src/main.js.map +1 -1
- package/dist/src/mcp_internal.d.ts +13 -0
- package/dist/src/mcp_internal.d.ts.map +1 -0
- package/dist/src/mcp_internal.js +34 -0
- package/dist/src/mcp_internal.js.map +1 -0
- package/dist/src/providers/claude_provider.d.ts +18 -0
- package/dist/src/providers/claude_provider.d.ts.map +1 -0
- package/dist/src/providers/claude_provider.js +27 -0
- package/dist/src/providers/claude_provider.js.map +1 -0
- package/dist/src/providers/claude_role_spec.d.ts +10 -0
- package/dist/src/providers/claude_role_spec.d.ts.map +1 -0
- package/dist/src/providers/claude_role_spec.js +85 -0
- package/dist/src/providers/claude_role_spec.js.map +1 -0
- package/dist/src/providers/codex_model.d.ts +12 -0
- package/dist/src/providers/codex_model.d.ts.map +1 -0
- package/dist/src/providers/codex_model.js +45 -0
- package/dist/src/providers/codex_model.js.map +1 -0
- package/dist/src/providers/codex_provider.d.ts +30 -0
- package/dist/src/providers/codex_provider.d.ts.map +1 -0
- package/dist/src/providers/codex_provider.js +170 -0
- package/dist/src/providers/codex_provider.js.map +1 -0
- package/dist/src/providers/codex_role_spec.d.ts +16 -0
- package/dist/src/providers/codex_role_spec.d.ts.map +1 -0
- package/dist/src/providers/codex_role_spec.js +63 -0
- package/dist/src/providers/codex_role_spec.js.map +1 -0
- package/dist/src/providers/query_message.d.ts +45 -0
- package/dist/src/providers/query_message.d.ts.map +1 -0
- package/dist/src/providers/query_message.js +8 -0
- package/dist/src/providers/query_message.js.map +1 -0
- package/dist/src/providers/resolve_provider.d.ts +10 -0
- package/dist/src/providers/resolve_provider.d.ts.map +1 -0
- package/dist/src/providers/resolve_provider.js +29 -0
- package/dist/src/providers/resolve_provider.js.map +1 -0
- package/dist/src/providers/role_spec.d.ts +39 -0
- package/dist/src/providers/role_spec.d.ts.map +1 -0
- package/dist/src/providers/role_spec.js +8 -0
- package/dist/src/providers/role_spec.js.map +1 -0
- package/dist/src/providers/structured_output.d.ts +21 -0
- package/dist/src/providers/structured_output.d.ts.map +1 -0
- package/dist/src/providers/structured_output.js +61 -0
- package/dist/src/providers/structured_output.js.map +1 -0
- package/dist/src/providers/types.d.ts +18 -0
- package/dist/src/providers/types.d.ts.map +1 -0
- package/dist/src/providers/types.js +15 -0
- package/dist/src/providers/types.js.map +1 -0
- package/dist/src/utils.js +1 -1
- package/dist/src/utils.js.map +1 -1
- package/package.json +3 -3
- package/dist/src/openai_tools.d.ts +0 -26
- package/dist/src/openai_tools.d.ts.map +0 -1
- package/dist/src/openai_tools.js +0 -194
- package/dist/src/openai_tools.js.map +0 -1
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
* Author: Sam Li
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.AgentOptions = exports.MCP_INTERNAL_TOOL_NAMES = exports.MCP_INTERNAL_SERVER_NAME = exports.DEFAULT_MCP_SERVER_NAME = void 0;
|
|
9
|
-
exports.buildMcpInternalToolNames = buildMcpInternalToolNames;
|
|
8
|
+
exports.AgentOptions = exports.buildMcpInternalToolNames = exports.MCP_INTERNAL_TOOL_NAMES = exports.MCP_INTERNAL_SERVER_NAME = exports.DEFAULT_MCP_SERVER_NAME = void 0;
|
|
10
9
|
exports.buildPrReviewerMcpNudgeSystemPromptSuffix = buildPrReviewerMcpNudgeSystemPromptSuffix;
|
|
11
10
|
const security_report_1 = require("./schemas/security_report");
|
|
12
11
|
const threat_model_report_1 = require("./schemas/threat_model_report");
|
|
@@ -16,6 +15,12 @@ const finding_validator_1 = require("./schemas/finding_validator");
|
|
|
16
15
|
const context_extraction_1 = require("./schemas/context_extraction");
|
|
17
16
|
const learned_guidance_1 = require("./schemas/learned_guidance");
|
|
18
17
|
const fp_adversary_pass_1 = require("./schemas/fp_adversary_pass");
|
|
18
|
+
const claude_role_spec_1 = require("./providers/claude_role_spec");
|
|
19
|
+
const mcp_internal_1 = require("./mcp_internal");
|
|
20
|
+
Object.defineProperty(exports, "DEFAULT_MCP_SERVER_NAME", { enumerable: true, get: function () { return mcp_internal_1.DEFAULT_MCP_SERVER_NAME; } });
|
|
21
|
+
Object.defineProperty(exports, "MCP_INTERNAL_SERVER_NAME", { enumerable: true, get: function () { return mcp_internal_1.MCP_INTERNAL_SERVER_NAME; } });
|
|
22
|
+
Object.defineProperty(exports, "MCP_INTERNAL_TOOL_NAMES", { enumerable: true, get: function () { return mcp_internal_1.MCP_INTERNAL_TOOL_NAMES; } });
|
|
23
|
+
Object.defineProperty(exports, "buildMcpInternalToolNames", { enumerable: true, get: function () { return mcp_internal_1.buildMcpInternalToolNames; } });
|
|
19
24
|
const FIX_CODE_VS_OPTIONS_GUIDANCE = `
|
|
20
25
|
|
|
21
26
|
FIXED CODE vs FIX OPTIONS:
|
|
@@ -24,54 +29,6 @@ FIXED CODE vs FIX OPTIONS:
|
|
|
24
29
|
- Use "fix_options" when the fix requires architectural decisions, domain-specific knowledge,
|
|
25
30
|
or when multiple valid remediation approaches exist. Each option needs an id, title, and description.
|
|
26
31
|
- Provide either fixed_code OR fix_options per finding, not both.`;
|
|
27
|
-
/**
|
|
28
|
-
* Default identifier the agent uses to register a parent-app-managed MCP
|
|
29
|
-
* server with the Claude Agent SDK. The resulting tool names exposed to
|
|
30
|
-
* the LLM follow the SDK convention `mcp__<server>__<tool>`, so this
|
|
31
|
-
* value becomes the literal prefix the model sees on its tool list.
|
|
32
|
-
*
|
|
33
|
-
* The default is intentionally generic (`appsec-internal`) so the
|
|
34
|
-
* `appsec-agent` package is reusable across parent apps. Callers that
|
|
35
|
-
* need a different identifier — e.g. to keep an existing prompt-nudge or
|
|
36
|
-
* counter contract stable — pass `--mcp-server-name <name>` on the CLI
|
|
37
|
-
* (or `mcpServerName` to the role builders directly), and the override
|
|
38
|
-
* threads through to both `Options.mcpServers[<name>]` and the namespaced
|
|
39
|
-
* tool names in the subagent whitelist.
|
|
40
|
-
*/
|
|
41
|
-
exports.DEFAULT_MCP_SERVER_NAME = 'appsec-internal';
|
|
42
|
-
/**
|
|
43
|
-
* @deprecated since v2.4.2 — historical alias for the default server name.
|
|
44
|
-
* Kept exported so existing imports keep type-checking; new code should
|
|
45
|
-
* read `DEFAULT_MCP_SERVER_NAME` (and pass an override via
|
|
46
|
-
* `mcpServerName` when a specific identifier is required).
|
|
47
|
-
*/
|
|
48
|
-
exports.MCP_INTERNAL_SERVER_NAME = exports.DEFAULT_MCP_SERVER_NAME;
|
|
49
|
-
/**
|
|
50
|
-
* Tools exposed by the per-scan in-process MCP server the parent app
|
|
51
|
-
* stands up. Pinned at this set (`queryFindingsHistory`,
|
|
52
|
-
* `queryImportGraph`, `queryRuntimeEnrichment`, `queryCodebaseGraph`) so
|
|
53
|
-
* the agent's tool whitelist is deterministic at the current version;
|
|
54
|
-
* parent apps that expose a different surface should fork or extend this
|
|
55
|
-
* list rather than rely on dynamic discovery (the SDK would otherwise have
|
|
56
|
-
* to round-trip the server before constructing the whitelist).
|
|
57
|
-
*/
|
|
58
|
-
exports.MCP_INTERNAL_TOOL_NAMES = [
|
|
59
|
-
'queryFindingsHistory',
|
|
60
|
-
'queryImportGraph',
|
|
61
|
-
'queryRuntimeEnrichment',
|
|
62
|
-
'queryCodebaseGraph',
|
|
63
|
-
];
|
|
64
|
-
/**
|
|
65
|
-
* SDK-namespaced tool names the LLM sees on its tool list when the server
|
|
66
|
-
* is wired up. Exposed as a helper rather than a constant so callers in
|
|
67
|
-
* `agent_options.ts` and the test suite share one source of truth.
|
|
68
|
-
*
|
|
69
|
-
* @param serverName - Override for the MCP server identifier. Defaults
|
|
70
|
-
* to `DEFAULT_MCP_SERVER_NAME` (`appsec-internal`).
|
|
71
|
-
*/
|
|
72
|
-
function buildMcpInternalToolNames(serverName = exports.DEFAULT_MCP_SERVER_NAME) {
|
|
73
|
-
return exports.MCP_INTERNAL_TOOL_NAMES.map((tool) => `mcp__${serverName}__${tool}`);
|
|
74
|
-
}
|
|
75
32
|
/**
|
|
76
33
|
* System-prompt suffix for `pr_reviewer` / `code_reviewer` when
|
|
77
34
|
* `--mcp-server-url` is set. Steers the model toward all live parent-app
|
|
@@ -88,8 +45,8 @@ function buildMcpInternalToolNames(serverName = exports.DEFAULT_MCP_SERVER_NAME)
|
|
|
88
45
|
* @param mcpServerName - Same override as `attachMcpServerToOptions`
|
|
89
46
|
* (`DEFAULT_MCP_SERVER_NAME` when omitted).
|
|
90
47
|
*/
|
|
91
|
-
function buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName =
|
|
92
|
-
const name = mcpServerName ||
|
|
48
|
+
function buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName = mcp_internal_1.DEFAULT_MCP_SERVER_NAME) {
|
|
49
|
+
const name = mcpServerName || mcp_internal_1.DEFAULT_MCP_SERVER_NAME;
|
|
93
50
|
const findingsTool = `mcp__${name}__queryFindingsHistory`;
|
|
94
51
|
const importGraphTool = `mcp__${name}__queryImportGraph`;
|
|
95
52
|
const runtimeTool = `mcp__${name}__queryRuntimeEnrichment`;
|
|
@@ -101,24 +58,13 @@ function buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName = exports.DEFAU
|
|
|
101
58
|
}
|
|
102
59
|
/**
|
|
103
60
|
* Mutate an already-built `Options` object to attach the MCP server config
|
|
104
|
-
*
|
|
105
|
-
* whitelist with the backend-backed tool surface. No-op when
|
|
106
|
-
* `mcpServerUrl` is falsy — preserves the pre-v2.4.0 behaviour for
|
|
107
|
-
* callers that don't pass the new parameter.
|
|
108
|
-
*
|
|
109
|
-
* Mutating in place (rather than returning a fresh object) keeps the
|
|
110
|
-
* existing role builders' return statements untouched and avoids a deep
|
|
111
|
-
* clone of the (large) `Options` shape.
|
|
112
|
-
*
|
|
113
|
-
* @param mcpServerName - Override for the server identifier. Defaults
|
|
114
|
-
* to `DEFAULT_MCP_SERVER_NAME` (`appsec-internal`); pass the parent
|
|
115
|
-
* app's chosen name to keep tool-name contracts stable.
|
|
61
|
+
* @deprecated Use attachMcpToRoleSpec + roleSpecToClaudeOptions instead.
|
|
116
62
|
*/
|
|
117
|
-
function attachMcpServerToOptions(options, mcpServerUrl, agentKey, mcpServerName =
|
|
63
|
+
function attachMcpServerToOptions(options, mcpServerUrl, agentKey, mcpServerName = mcp_internal_1.DEFAULT_MCP_SERVER_NAME, mcpServerBearer) {
|
|
118
64
|
if (!mcpServerUrl) {
|
|
119
65
|
return;
|
|
120
66
|
}
|
|
121
|
-
const serverName = mcpServerName ||
|
|
67
|
+
const serverName = mcpServerName || mcp_internal_1.DEFAULT_MCP_SERVER_NAME;
|
|
122
68
|
const httpEntry = {
|
|
123
69
|
type: 'http',
|
|
124
70
|
url: mcpServerUrl,
|
|
@@ -133,7 +79,7 @@ function attachMcpServerToOptions(options, mcpServerUrl, agentKey, mcpServerName
|
|
|
133
79
|
const agent = options.agents?.[agentKey];
|
|
134
80
|
if (agent) {
|
|
135
81
|
const existingTools = agent.tools ?? [];
|
|
136
|
-
agent.tools = [...existingTools, ...buildMcpInternalToolNames(serverName)];
|
|
82
|
+
agent.tools = [...existingTools, ...(0, mcp_internal_1.buildMcpInternalToolNames)(serverName)];
|
|
137
83
|
}
|
|
138
84
|
}
|
|
139
85
|
class AgentOptions {
|
|
@@ -181,19 +127,26 @@ class AgentOptions {
|
|
|
181
127
|
/**
|
|
182
128
|
* Get options for simple query agent
|
|
183
129
|
*/
|
|
184
|
-
|
|
130
|
+
getSimpleQueryAgentRoleSpec(role = 'simple_query_agent', srcDir) {
|
|
185
131
|
const roleConfig = this.confDict[this.environment]?.[role];
|
|
186
132
|
let systemPrompt = roleConfig?.options?.system_prompt ||
|
|
187
133
|
'You are an Application Security (AppSec) expert assistant. You are responsible for providing security advice and guidance to the user.';
|
|
188
|
-
// Add source directory context to system prompt if provided
|
|
189
134
|
if (srcDir) {
|
|
190
135
|
systemPrompt += ` You have access to a source code directory at ${srcDir} that you can search and read files from to answer questions.`;
|
|
191
136
|
}
|
|
192
137
|
return {
|
|
193
|
-
|
|
194
|
-
|
|
138
|
+
roleId: 'simple_query_agent',
|
|
139
|
+
systemPrompt,
|
|
140
|
+
maxTurns: roleConfig?.options?.max_turns || 1,
|
|
141
|
+
capabilities: {},
|
|
142
|
+
noTools: true,
|
|
143
|
+
model: this.model,
|
|
144
|
+
workingDirectory: srcDir ?? undefined,
|
|
195
145
|
};
|
|
196
146
|
}
|
|
147
|
+
getSimpleQueryAgentOptions(role = 'simple_query_agent', srcDir) {
|
|
148
|
+
return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getSimpleQueryAgentRoleSpec(role, srcDir));
|
|
149
|
+
}
|
|
197
150
|
/**
|
|
198
151
|
* Get options for security code reviewer
|
|
199
152
|
*
|
|
@@ -213,7 +166,7 @@ class AgentOptions {
|
|
|
213
166
|
* @param mcpServerName - Override for the MCP server identifier
|
|
214
167
|
* @param mcpServerBearer - Bearer token for MCP HTTP requests
|
|
215
168
|
*/
|
|
216
|
-
|
|
169
|
+
getCodeReviewerRoleSpec(role = 'code_reviewer', outputFormat, mcpServerUrl, mcpServerName, mcpServerBearer, workingDirectory) {
|
|
217
170
|
const roleConfig = this.confDict[this.environment]?.[role];
|
|
218
171
|
let systemPrompt = roleConfig?.options?.system_prompt ||
|
|
219
172
|
'You are an Application Security (AppSec) expert assistant. You are responsible for performing a thorough code review. List out all the potential security and privacy issues found in the code.';
|
|
@@ -223,59 +176,60 @@ class AgentOptions {
|
|
|
223
176
|
if (mcpServerUrl) {
|
|
224
177
|
systemPrompt += buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName);
|
|
225
178
|
}
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
},
|
|
237
|
-
permissionMode: 'bypassPermissions'
|
|
179
|
+
const spec = {
|
|
180
|
+
roleId: 'code_reviewer',
|
|
181
|
+
systemPrompt,
|
|
182
|
+
maxTurns: roleConfig?.options?.max_turns ?? 30,
|
|
183
|
+
agentName: 'code-reviewer',
|
|
184
|
+
agentDescription: 'Reviews code for best practices and potential security issues only',
|
|
185
|
+
capabilities: { read: true, grep: true, write: true },
|
|
186
|
+
permissionMode: 'bypassPermissions',
|
|
187
|
+
model: this.model,
|
|
188
|
+
workingDirectory,
|
|
238
189
|
};
|
|
239
|
-
// Add JSON schema enforcement when output format is JSON
|
|
240
190
|
if (outputFormat?.toLowerCase() === 'json') {
|
|
241
|
-
|
|
242
|
-
type: 'json_schema',
|
|
243
|
-
schema: security_report_1.SECURITY_REPORT_SCHEMA
|
|
244
|
-
};
|
|
191
|
+
spec.outputSchema = security_report_1.SECURITY_REPORT_SCHEMA;
|
|
245
192
|
}
|
|
246
|
-
|
|
247
|
-
return
|
|
193
|
+
(0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
|
|
194
|
+
return spec;
|
|
195
|
+
}
|
|
196
|
+
getCodeReviewerOptions(role = 'code_reviewer', outputFormat, mcpServerUrl, mcpServerName, mcpServerBearer) {
|
|
197
|
+
return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getCodeReviewerRoleSpec(role, outputFormat, mcpServerUrl, mcpServerName, mcpServerBearer));
|
|
248
198
|
}
|
|
249
199
|
/**
|
|
250
|
-
*
|
|
251
|
-
* @param role - The role configuration key
|
|
252
|
-
* @param outputFormat - Output format (json, markdown, etc.)
|
|
200
|
+
* Provider-neutral spec for threat modeler (Phase 3 RoleSpec spike).
|
|
253
201
|
*/
|
|
254
|
-
|
|
202
|
+
getThreatModelerRoleSpec(role = 'threat_modeler', outputFormat, workingDirectory) {
|
|
255
203
|
const roleConfig = this.confDict[this.environment]?.[role];
|
|
256
204
|
const systemPrompt = roleConfig?.options?.system_prompt ||
|
|
257
205
|
'You are an Application Security (AppSec) expert assistant. You are responsible for performing risk assessment on the source code repository for SOC2 type 2 compliance audit using the STRIDE methodology.';
|
|
258
206
|
const isJson = outputFormat?.toLowerCase() === 'json';
|
|
259
207
|
const resolvedMaxTurns = roleConfig?.options?.max_turns ?? 20;
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
|
|
208
|
+
const spec = {
|
|
209
|
+
roleId: 'threat_modeler',
|
|
210
|
+
systemPrompt,
|
|
211
|
+
maxTurns: resolvedMaxTurns,
|
|
212
|
+
agentName: 'threat-modeler',
|
|
213
|
+
agentDescription: 'Performs threat modeling and risk assessment using STRIDE methodology',
|
|
214
|
+
capabilities: isJson
|
|
215
|
+
? { read: true, grep: true }
|
|
216
|
+
: { read: true, grep: true, write: true, graphviz: true },
|
|
217
|
+
permissionMode: 'bypassPermissions',
|
|
218
|
+
model: this.model,
|
|
219
|
+
workingDirectory,
|
|
271
220
|
};
|
|
272
221
|
if (isJson) {
|
|
273
|
-
|
|
274
|
-
type: 'json_schema',
|
|
275
|
-
schema: threat_model_report_1.THREAT_MODEL_REPORT_SCHEMA
|
|
276
|
-
};
|
|
222
|
+
spec.outputSchema = threat_model_report_1.THREAT_MODEL_REPORT_SCHEMA;
|
|
277
223
|
}
|
|
278
|
-
return
|
|
224
|
+
return spec;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get options for threat modeler
|
|
228
|
+
* @param role - The role configuration key
|
|
229
|
+
* @param outputFormat - Output format (json, markdown, etc.)
|
|
230
|
+
*/
|
|
231
|
+
getThreatModelerOptions(role = 'threat_modeler', outputFormat) {
|
|
232
|
+
return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getThreatModelerRoleSpec(role, outputFormat));
|
|
279
233
|
}
|
|
280
234
|
/**
|
|
281
235
|
* Get options for PR diff-focused code reviewer
|
|
@@ -285,7 +239,7 @@ class AgentOptions {
|
|
|
285
239
|
* @param srcDir - Optional source directory path
|
|
286
240
|
* @param outputFormat - Output format (json, markdown, etc.)
|
|
287
241
|
*/
|
|
288
|
-
|
|
242
|
+
getDiffReviewerRoleSpec(role = 'code_reviewer', srcDir, outputFormat, maxTurns, noTools, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
|
|
289
243
|
const roleConfig = this.confDict[this.environment]?.[role];
|
|
290
244
|
let systemPrompt;
|
|
291
245
|
if (noTools) {
|
|
@@ -341,7 +295,6 @@ You have access to Read, Grep, and Write tools:
|
|
|
341
295
|
if (srcDir) {
|
|
342
296
|
systemPrompt += `\n\nSource directory available at: ${srcDir}`;
|
|
343
297
|
}
|
|
344
|
-
// Allow role config to override the system prompt
|
|
345
298
|
if (roleConfig?.options?.diff_reviewer_system_prompt) {
|
|
346
299
|
systemPrompt = roleConfig.options.diff_reviewer_system_prompt;
|
|
347
300
|
}
|
|
@@ -356,76 +309,59 @@ You have access to Read, Grep, and Write tools:
|
|
|
356
309
|
if (mcpServerUrl && role === 'pr_reviewer') {
|
|
357
310
|
systemPrompt += buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName);
|
|
358
311
|
}
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
}
|
|
371
|
-
},
|
|
372
|
-
permissionMode: 'bypassPermissions'
|
|
312
|
+
const spec = {
|
|
313
|
+
roleId: role === 'pr_reviewer' ? 'pr_reviewer' : 'code_reviewer',
|
|
314
|
+
systemPrompt,
|
|
315
|
+
maxTurns: maxTurns ?? roleConfig?.options?.max_turns ?? 10,
|
|
316
|
+
agentName: 'diff-reviewer',
|
|
317
|
+
agentDescription: 'Reviews PR diff changes for security vulnerabilities',
|
|
318
|
+
capabilities: noTools ? {} : { read: true, grep: true, write: true },
|
|
319
|
+
allowedTools: noTools ? ['Write'] : undefined,
|
|
320
|
+
permissionMode: 'bypassPermissions',
|
|
321
|
+
model: this.model,
|
|
322
|
+
workingDirectory: srcDir ?? undefined,
|
|
373
323
|
};
|
|
374
|
-
// Add JSON schema enforcement when output format is JSON
|
|
375
324
|
if (outputFormat?.toLowerCase() === 'json') {
|
|
376
|
-
|
|
377
|
-
type: 'json_schema',
|
|
378
|
-
schema: security_report_1.SECURITY_REPORT_SCHEMA
|
|
379
|
-
};
|
|
325
|
+
spec.outputSchema = security_report_1.SECURITY_REPORT_SCHEMA;
|
|
380
326
|
}
|
|
381
|
-
|
|
382
|
-
return
|
|
327
|
+
(0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
|
|
328
|
+
return spec;
|
|
383
329
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
* @param role - The role configuration key
|
|
389
|
-
* @param srcDir - Optional source directory path for additional context
|
|
390
|
-
*/
|
|
391
|
-
getCodeFixerOptions(role = 'code_fixer', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
|
|
330
|
+
getDiffReviewerOptions(role = 'code_reviewer', srcDir, outputFormat, maxTurns, noTools, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
|
|
331
|
+
return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getDiffReviewerRoleSpec(role, srcDir, outputFormat, maxTurns, noTools, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer));
|
|
332
|
+
}
|
|
333
|
+
getCodeFixerRoleSpec(role = 'code_fixer', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
|
|
392
334
|
const roleConfig = this.confDict[this.environment]?.[role];
|
|
393
335
|
let systemPrompt = roleConfig?.options?.system_prompt ||
|
|
394
336
|
'You are an expert security engineer specializing in fixing vulnerabilities in code. ' +
|
|
395
337
|
'You receive a finding with code context and must produce a precise, minimal fix that resolves ' +
|
|
396
|
-
|
|
338
|
+
"the security issue while preserving the original code's functionality and indentation. " +
|
|
397
339
|
'Only modify the affected lines. Always use the recommended secure alternatives when applicable.';
|
|
398
340
|
if (srcDir) {
|
|
399
341
|
systemPrompt += `\n\nSource directory available at: ${srcDir}. You may read files for additional context if needed.`;
|
|
400
342
|
}
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
model: this.model,
|
|
409
|
-
maxTurns: resolvedMaxTurns
|
|
410
|
-
}
|
|
411
|
-
},
|
|
343
|
+
const spec = {
|
|
344
|
+
roleId: 'code_fixer',
|
|
345
|
+
systemPrompt,
|
|
346
|
+
maxTurns: roleConfig?.options?.max_turns ?? 10,
|
|
347
|
+
agentName: 'code-fixer',
|
|
348
|
+
agentDescription: 'Generates precise security fixes for code vulnerabilities',
|
|
349
|
+
capabilities: { read: true, grep: true },
|
|
412
350
|
permissionMode: 'bypassPermissions',
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
351
|
+
model: this.model,
|
|
352
|
+
outputSchema: security_fix_1.FIX_OUTPUT_SCHEMA,
|
|
353
|
+
workingDirectory: srcDir ?? undefined,
|
|
417
354
|
};
|
|
418
|
-
|
|
419
|
-
return
|
|
355
|
+
(0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
|
|
356
|
+
return spec;
|
|
420
357
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
getQaVerifierOptions(role = 'qa_verifier', srcDir) {
|
|
358
|
+
getCodeFixerOptions(role = 'code_fixer', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
|
|
359
|
+
return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getCodeFixerRoleSpec(role, srcDir, mcpServerUrl, mcpServerName, mcpServerBearer));
|
|
360
|
+
}
|
|
361
|
+
getQaVerifierRoleSpec(role = 'qa_verifier', srcDir) {
|
|
426
362
|
const roleConfig = this.confDict[this.environment]?.[role];
|
|
427
363
|
let systemPrompt = roleConfig?.options?.system_prompt ||
|
|
428
|
-
|
|
364
|
+
"You are a QA verification engineer. Your task is to verify security fixes by running the project's test suite " +
|
|
429
365
|
'and analyzing the results. You have access to the project source code and can execute shell commands to run tests. ' +
|
|
430
366
|
'First, set up the environment (install dependencies if needed), then run the test suite. ' +
|
|
431
367
|
'If tests fail, analyze the failures to determine if they are caused by the security fix or are pre-existing issues. ' +
|
|
@@ -433,30 +369,23 @@ You have access to Read, Grep, and Write tools:
|
|
|
433
369
|
if (srcDir) {
|
|
434
370
|
systemPrompt += `\n\nProject source code is available at: ${srcDir}. Use Read and Grep to inspect files, and Bash to execute commands.`;
|
|
435
371
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
model: this.model,
|
|
444
|
-
maxTurns: resolvedMaxTurns
|
|
445
|
-
}
|
|
446
|
-
},
|
|
372
|
+
return {
|
|
373
|
+
roleId: 'qa_verifier',
|
|
374
|
+
systemPrompt,
|
|
375
|
+
maxTurns: roleConfig?.options?.max_turns ?? 15,
|
|
376
|
+
agentName: 'qa-verifier',
|
|
377
|
+
agentDescription: 'Verifies security fixes by running project tests and analyzing results',
|
|
378
|
+
capabilities: { read: true, grep: true, shell: true },
|
|
447
379
|
permissionMode: 'bypassPermissions',
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
}
|
|
380
|
+
model: this.model,
|
|
381
|
+
outputSchema: qa_context_1.QA_VERDICT_SCHEMA,
|
|
382
|
+
workingDirectory: srcDir ?? undefined,
|
|
452
383
|
};
|
|
453
|
-
return options;
|
|
454
384
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
getContextExtractorOptions(role = 'context_extractor') {
|
|
385
|
+
getQaVerifierOptions(role = 'qa_verifier', srcDir) {
|
|
386
|
+
return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getQaVerifierRoleSpec(role, srcDir));
|
|
387
|
+
}
|
|
388
|
+
getContextExtractorRoleSpec(role = 'context_extractor') {
|
|
460
389
|
const roleConfig = this.confDict[this.environment]?.[role];
|
|
461
390
|
const systemPrompt = roleConfig?.options?.system_prompt ||
|
|
462
391
|
'You are a security-aware software analyst. Your task is to analyze repository files and metadata ' +
|
|
@@ -470,25 +399,23 @@ You have access to Read, Grep, and Write tools:
|
|
|
470
399
|
'(logs, uploads, work-dir, data), IDE config (.cursor, .vscode), utility scripts, and documentation. Use ' +
|
|
471
400
|
'specific paths from the tree (e.g., "backend/scripts/**" not just "scripts/**"). Only suggest patterns NOT ' +
|
|
472
401
|
'already in the standard preset. If a field has no relevant information, return an empty string.';
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
},
|
|
482
|
-
},
|
|
402
|
+
return {
|
|
403
|
+
roleId: 'context_extractor',
|
|
404
|
+
systemPrompt,
|
|
405
|
+
maxTurns: 1,
|
|
406
|
+
agentName: 'context-extractor',
|
|
407
|
+
agentDescription: 'Extracts structured project intelligence from repository files',
|
|
408
|
+
capabilities: {},
|
|
409
|
+
allowedTools: [],
|
|
483
410
|
permissionMode: 'bypassPermissions',
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
schema: context_extraction_1.CONTEXT_EXTRACTION_SCHEMA,
|
|
487
|
-
},
|
|
411
|
+
model: this.model,
|
|
412
|
+
outputSchema: context_extraction_1.CONTEXT_EXTRACTION_SCHEMA,
|
|
488
413
|
};
|
|
489
|
-
return options;
|
|
490
414
|
}
|
|
491
|
-
|
|
415
|
+
getContextExtractorOptions(role = 'context_extractor') {
|
|
416
|
+
return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getContextExtractorRoleSpec(role));
|
|
417
|
+
}
|
|
418
|
+
getFindingValidatorRoleSpec(role = 'finding_validator', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
|
|
492
419
|
const roleConfig = this.confDict[this.environment]?.[role];
|
|
493
420
|
let systemPrompt = roleConfig?.options?.system_prompt ||
|
|
494
421
|
'You are a security expert specializing in vulnerability validation. ' +
|
|
@@ -498,45 +425,25 @@ You have access to Read, Grep, and Write tools:
|
|
|
498
425
|
if (srcDir) {
|
|
499
426
|
systemPrompt += `\n\nSource code is available at: ${srcDir}. Use Read and Grep to inspect files for additional context if needed.`;
|
|
500
427
|
}
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
model: this.model,
|
|
509
|
-
maxTurns: resolvedMaxTurns
|
|
510
|
-
}
|
|
511
|
-
},
|
|
428
|
+
const spec = {
|
|
429
|
+
roleId: 'finding_validator',
|
|
430
|
+
systemPrompt,
|
|
431
|
+
maxTurns: roleConfig?.options?.max_turns ?? 5,
|
|
432
|
+
agentName: 'finding-validator',
|
|
433
|
+
agentDescription: 'Validates whether a previously detected security vulnerability is still present in code',
|
|
434
|
+
capabilities: { read: true, grep: true },
|
|
512
435
|
permissionMode: 'bypassPermissions',
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
436
|
+
model: this.model,
|
|
437
|
+
outputSchema: finding_validator_1.RETEST_VERDICT_SCHEMA,
|
|
438
|
+
workingDirectory: srcDir ?? undefined,
|
|
517
439
|
};
|
|
518
|
-
|
|
519
|
-
return
|
|
440
|
+
(0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
|
|
441
|
+
return spec;
|
|
520
442
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
*
|
|
526
|
-
* Tools: NONE — the agent must work from the provided buckets only. No
|
|
527
|
-
* source-tree access, no MCP server. The parent app's `runSynthesizerAgent`
|
|
528
|
-
* spawns this from a temp working directory anyway, so even if a tool were
|
|
529
|
-
* attached it would have nothing to read.
|
|
530
|
-
*
|
|
531
|
-
* Output schema (LEARNED_GUIDANCE_OUTPUT_SCHEMA):
|
|
532
|
-
* { bullets: [{ cwe, bullet (≤300 chars), confidence (0..1) }] }
|
|
533
|
-
*
|
|
534
|
-
* Confidence floor and active-bullet cap are enforced by the parent app
|
|
535
|
-
* (`MIN_CONFIDENCE = 0.6`, `MAX_ACTIVE_BULLETS_PER_PROJECT = 12`); this
|
|
536
|
-
* role is allowed to return up to 50 bullets and the parent ranks +
|
|
537
|
-
* truncates.
|
|
538
|
-
*/
|
|
539
|
-
getLearnedGuidanceSynthesizerOptions(role = 'learned_guidance_synthesizer') {
|
|
443
|
+
getFindingValidatorOptions(role = 'finding_validator', srcDir, mcpServerUrl, mcpServerName, mcpServerBearer) {
|
|
444
|
+
return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getFindingValidatorRoleSpec(role, srcDir, mcpServerUrl, mcpServerName, mcpServerBearer));
|
|
445
|
+
}
|
|
446
|
+
getLearnedGuidanceSynthesizerRoleSpec(role = 'learned_guidance_synthesizer') {
|
|
540
447
|
const roleConfig = this.confDict[this.environment]?.[role];
|
|
541
448
|
const systemPrompt = roleConfig?.options?.system_prompt ||
|
|
542
449
|
'You are a senior application security engineer summarizing patterns from past PR-scan ' +
|
|
@@ -548,30 +455,23 @@ You have access to Read, Grep, and Write tools:
|
|
|
548
455
|
'vague to ground a specific rule, OMIT that bucket entirely — it is better to return zero ' +
|
|
549
456
|
'bullets than a bullet the reviewer cannot act on. Output is constrained to the required ' +
|
|
550
457
|
'JSON schema; emit nothing else.';
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
maxTurns: resolvedMaxTurns,
|
|
560
|
-
},
|
|
561
|
-
},
|
|
458
|
+
return {
|
|
459
|
+
roleId: 'learned_guidance_synthesizer',
|
|
460
|
+
systemPrompt,
|
|
461
|
+
maxTurns: roleConfig?.options?.max_turns ?? 1,
|
|
462
|
+
agentName: 'learned-guidance-synthesizer',
|
|
463
|
+
agentDescription: 'Synthesizes class-level learned-guidance bullets from per-CWE dismissal-signal buckets',
|
|
464
|
+
capabilities: {},
|
|
465
|
+
allowedTools: [],
|
|
562
466
|
permissionMode: 'bypassPermissions',
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
schema: learned_guidance_1.LEARNED_GUIDANCE_OUTPUT_SCHEMA,
|
|
566
|
-
},
|
|
467
|
+
model: this.model,
|
|
468
|
+
outputSchema: learned_guidance_1.LEARNED_GUIDANCE_OUTPUT_SCHEMA,
|
|
567
469
|
};
|
|
568
|
-
return options;
|
|
569
470
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
getPrAdversaryOptions(role = 'pr_adversary', srcDir, maxTurns, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
|
|
471
|
+
getLearnedGuidanceSynthesizerOptions(role = 'learned_guidance_synthesizer') {
|
|
472
|
+
return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getLearnedGuidanceSynthesizerRoleSpec(role));
|
|
473
|
+
}
|
|
474
|
+
getPrAdversaryRoleSpec(role = 'pr_adversary', srcDir, maxTurns, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
|
|
575
475
|
const roleConfig = this.confDict[this.environment]?.[role];
|
|
576
476
|
let systemPrompt = roleConfig?.options?.system_prompt ||
|
|
577
477
|
'You are a senior application security engineer performing an adversarial second pass on security findings. ' +
|
|
@@ -585,42 +485,25 @@ You have access to Read, Grep, and Write tools:
|
|
|
585
485
|
systemPrompt +=
|
|
586
486
|
'\n\n**Experiment (treatment):** Bias toward dropping borderline issues unless the diff plus quick repo checks show a real attack surface.';
|
|
587
487
|
}
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
model: this.model,
|
|
596
|
-
maxTurns: resolvedMaxTurns,
|
|
597
|
-
},
|
|
598
|
-
},
|
|
488
|
+
const spec = {
|
|
489
|
+
roleId: 'pr_adversary',
|
|
490
|
+
systemPrompt,
|
|
491
|
+
maxTurns: maxTurns ?? roleConfig?.options?.max_turns ?? 15,
|
|
492
|
+
agentName: 'pr-adversary',
|
|
493
|
+
agentDescription: 'Adversarial second pass: filters PR scan findings by concrete failure paths',
|
|
494
|
+
capabilities: { read: true, grep: true },
|
|
599
495
|
permissionMode: 'bypassPermissions',
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
},
|
|
496
|
+
model: this.model,
|
|
497
|
+
outputSchema: security_report_1.SECURITY_REPORT_SCHEMA,
|
|
498
|
+
workingDirectory: srcDir ?? undefined,
|
|
604
499
|
};
|
|
605
|
-
|
|
606
|
-
return
|
|
500
|
+
(0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
|
|
501
|
+
return spec;
|
|
607
502
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
* is constrained to FP_ADVERSARY_REPORT_SCHEMA so the verdict contract stays
|
|
613
|
-
* model-independent across Claude upgrades.
|
|
614
|
-
*
|
|
615
|
-
* Differences from `getPrAdversaryOptions`:
|
|
616
|
-
* - Output schema is the dedicated `fp_adversary_report` (not
|
|
617
|
-
* SECURITY_REPORT_SCHEMA) — verdicts round-trip on `fingerprint`, not `id`.
|
|
618
|
-
* - No `experiment_enabled` plumbing (Lane-2 deliberately omits A/B per M4).
|
|
619
|
-
* - System prompt references the four structured posture fields and
|
|
620
|
-
* `similar_dismissed` precedent that the parent app threads in via
|
|
621
|
-
* `--adversarial-context`.
|
|
622
|
-
*/
|
|
623
|
-
getFpAdversaryOptions(role = 'fp_adversary', srcDir, maxTurns, mcpServerUrl, mcpServerName, mcpServerBearer) {
|
|
503
|
+
getPrAdversaryOptions(role = 'pr_adversary', srcDir, maxTurns, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer) {
|
|
504
|
+
return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getPrAdversaryRoleSpec(role, srcDir, maxTurns, experimentEnabled, mcpServerUrl, mcpServerName, mcpServerBearer));
|
|
505
|
+
}
|
|
506
|
+
getFpAdversaryRoleSpec(role = 'fp_adversary', srcDir, maxTurns, mcpServerUrl, mcpServerName, mcpServerBearer) {
|
|
624
507
|
const roleConfig = this.confDict[this.environment]?.[role];
|
|
625
508
|
let systemPrompt = roleConfig?.options?.system_prompt ||
|
|
626
509
|
'You are a senior application security engineer performing an adversarial false-positive review on a full-repository security scan. ' +
|
|
@@ -636,25 +519,23 @@ You have access to Read, Grep, and Write tools:
|
|
|
636
519
|
if (mcpServerUrl) {
|
|
637
520
|
systemPrompt += buildPrReviewerMcpNudgeSystemPromptSuffix(mcpServerName);
|
|
638
521
|
}
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
model: this.model,
|
|
647
|
-
maxTurns: resolvedMaxTurns,
|
|
648
|
-
},
|
|
649
|
-
},
|
|
522
|
+
const spec = {
|
|
523
|
+
roleId: 'fp_adversary',
|
|
524
|
+
systemPrompt,
|
|
525
|
+
maxTurns: maxTurns ?? roleConfig?.options?.max_turns ?? 15,
|
|
526
|
+
agentName: 'fp-adversary',
|
|
527
|
+
agentDescription: 'Adversarial false-positive filter for full-repo scans: emits per-finding (verdict, confidence, rationale) verdicts',
|
|
528
|
+
capabilities: { read: true, grep: true },
|
|
650
529
|
permissionMode: 'bypassPermissions',
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
},
|
|
530
|
+
model: this.model,
|
|
531
|
+
outputSchema: fp_adversary_pass_1.FP_ADVERSARY_REPORT_SCHEMA,
|
|
532
|
+
workingDirectory: srcDir ?? undefined,
|
|
655
533
|
};
|
|
656
|
-
|
|
657
|
-
return
|
|
534
|
+
(0, mcp_internal_1.attachMcpToRoleSpec)(spec, mcpServerUrl, mcpServerName, mcpServerBearer);
|
|
535
|
+
return spec;
|
|
536
|
+
}
|
|
537
|
+
getFpAdversaryOptions(role = 'fp_adversary', srcDir, maxTurns, mcpServerUrl, mcpServerName, mcpServerBearer) {
|
|
538
|
+
return (0, claude_role_spec_1.roleSpecToClaudeOptions)(this.getFpAdversaryRoleSpec(role, srcDir, maxTurns, mcpServerUrl, mcpServerName, mcpServerBearer));
|
|
658
539
|
}
|
|
659
540
|
}
|
|
660
541
|
exports.AgentOptions = AgentOptions;
|