@zivis/mcp 0.1.19 → 0.2.5
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/dist/lib/harness-bootstrap.d.ts +9 -0
- package/dist/lib/harness-bootstrap.js +78 -0
- package/dist/matcher/index.js +1 -1
- package/dist/matcher/inference-candidates.js +1 -1
- package/dist/pattern-packs/zivis-public-0.2.0/manifest.json +1 -1
- package/dist/prompts/getting-started.js +26 -2
- package/dist/server.js +24 -143
- package/dist/tools/application.d.ts +53 -0
- package/dist/tools/application.js +232 -0
- package/dist/tools/artifacts.d.ts +21 -57
- package/dist/tools/artifacts.js +116 -150
- package/dist/tools/devx-run.d.ts +15 -37
- package/dist/tools/devx-run.js +66 -77
- package/dist/tools/diagram.d.ts +55 -0
- package/dist/tools/diagram.js +209 -0
- package/dist/tools/document.d.ts +48 -0
- package/dist/tools/document.js +137 -0
- package/dist/tools/finding.d.ts +133 -0
- package/dist/tools/finding.js +211 -0
- package/dist/tools/get-started.d.ts +1 -1
- package/dist/tools/get-started.js +93 -33
- package/dist/tools/security-memory.d.ts +21 -55
- package/dist/tools/security-memory.js +93 -138
- package/dist/tools/signal.d.ts +50 -0
- package/dist/tools/signal.js +139 -0
- package/dist/tools/threat-library.d.ts +27 -0
- package/dist/tools/threat-library.js +266 -0
- package/package.json +1 -1
- package/dist/tools/create-diagram.d.ts +0 -39
- package/dist/tools/create-diagram.js +0 -74
- package/dist/tools/create-document.d.ts +0 -23
- package/dist/tools/create-document.js +0 -47
- package/dist/tools/create-finding.d.ts +0 -85
- package/dist/tools/create-finding.js +0 -136
- package/dist/tools/delete-finding.d.ts +0 -19
- package/dist/tools/delete-finding.js +0 -36
- package/dist/tools/discover-local-infra.d.ts +0 -14
- package/dist/tools/discover-local-infra.js +0 -686
- package/dist/tools/explain-signal-for-diff.d.ts +0 -34
- package/dist/tools/explain-signal-for-diff.js +0 -75
- package/dist/tools/get-application-overview.d.ts +0 -22
- package/dist/tools/get-application-overview.js +0 -137
- package/dist/tools/get-application.d.ts +0 -22
- package/dist/tools/get-application.js +0 -77
- package/dist/tools/get-diagram.d.ts +0 -22
- package/dist/tools/get-diagram.js +0 -49
- package/dist/tools/get-document.d.ts +0 -17
- package/dist/tools/get-document.js +0 -37
- package/dist/tools/get-signal.d.ts +0 -24
- package/dist/tools/get-signal.js +0 -75
- package/dist/tools/import-openapi-endpoints.d.ts +0 -28
- package/dist/tools/import-openapi-endpoints.js +0 -87
- package/dist/tools/inspect.d.ts +0 -21
- package/dist/tools/inspect.js +0 -222
- package/dist/tools/list-applications.d.ts +0 -29
- package/dist/tools/list-applications.js +0 -68
- package/dist/tools/list-diagrams.d.ts +0 -37
- package/dist/tools/list-diagrams.js +0 -74
- package/dist/tools/list-documents.d.ts +0 -19
- package/dist/tools/list-documents.js +0 -44
- package/dist/tools/list-endpoints.d.ts +0 -42
- package/dist/tools/list-endpoints.js +0 -92
- package/dist/tools/list-signals.d.ts +0 -37
- package/dist/tools/list-signals.js +0 -83
- package/dist/tools/manage-application.d.ts +0 -64
- package/dist/tools/manage-application.js +0 -129
- package/dist/tools/manage-diagram.d.ts +0 -41
- package/dist/tools/manage-diagram.js +0 -91
- package/dist/tools/manage-endpoint-lifecycle.d.ts +0 -80
- package/dist/tools/manage-endpoint-lifecycle.js +0 -180
- package/dist/tools/security-review.d.ts +0 -40
- package/dist/tools/security-review.js +0 -199
- package/dist/tools/threat-get-capsule.d.ts +0 -15
- package/dist/tools/threat-get-capsule.js +0 -53
- package/dist/tools/threat-get-inference-prompt.d.ts +0 -15
- package/dist/tools/threat-get-inference-prompt.js +0 -73
- package/dist/tools/threat-list-relevant-capsules.d.ts +0 -17
- package/dist/tools/threat-list-relevant-capsules.js +0 -158
- package/dist/tools/threat-run-matcher.d.ts +0 -17
- package/dist/tools/threat-run-matcher.js +0 -145
- package/dist/tools/update-document.d.ts +0 -23
- package/dist/tools/update-document.js +0 -57
- package/dist/tools/update-endpoint.d.ts +0 -60
- package/dist/tools/update-endpoint.js +0 -138
- package/dist/tools/update-finding.d.ts +0 -61
- package/dist/tools/update-finding.js +0 -80
- package/dist/tools/update-mermaid-source.d.ts +0 -21
- package/dist/tools/update-mermaid-source.js +0 -60
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sanitizeResponse, sanitizeInboundText } from "../sanitize.js";
|
|
3
|
+
import { requireApplicationId } from "../resolve-application-id.js";
|
|
4
|
+
export const DOCUMENT_NAME = "zivis_document";
|
|
5
|
+
export const DOCUMENT_DESCRIPTION = `CRUD for narrative markdown documents under a ThreatModel (exec summaries, remediation plans, architecture narratives). Can embed Diagrams (see zivis_diagram) via \`\`\`mermaid or \`\`\`zivis:diagram (UUID) fenced blocks.
|
|
6
|
+
|
|
7
|
+
action:
|
|
8
|
+
- create: requires title, exactly one of threat_model_id or entity_type+entity_id — or omit both threat_model_id and entity_type/entity_id entirely to attach to the current workspace's bound Application automatically. entity_type "application" with no entity_id also auto-resolves from that binding. Optional content, tags.
|
|
9
|
+
- get: requires document_id. Full markdown + metadata.
|
|
10
|
+
- list: optional threat_model_id or entity_type+entity_id, search filters.
|
|
11
|
+
- update: requires document_id + at least one of title/content/tags. content REPLACES the whole document — get first, then send the complete edited markdown, preserving unrelated structure.`;
|
|
12
|
+
const ENTITY_TYPE_ENUM = [
|
|
13
|
+
"application",
|
|
14
|
+
"threat_model",
|
|
15
|
+
"agent_instance",
|
|
16
|
+
"scan",
|
|
17
|
+
"finding",
|
|
18
|
+
"attack_scenario",
|
|
19
|
+
"component",
|
|
20
|
+
"trust_room",
|
|
21
|
+
"campaign",
|
|
22
|
+
];
|
|
23
|
+
export const DOCUMENT_SCHEMA = {
|
|
24
|
+
action: z.enum(["create", "get", "list", "update"]).describe("Which document operation to perform"),
|
|
25
|
+
threat_model_id: z.string().optional().describe("UUID of the parent threat model. For create, alternative to entity_type+entity_id — provide exactly one, not both. Optional filter for list."),
|
|
26
|
+
title: z.string().optional().describe("Document title. Required for create."),
|
|
27
|
+
content: z.string().optional().describe("Markdown content. For update, REPLACES the entire document."),
|
|
28
|
+
tags: z.array(z.string()).optional().describe("Tags. For update, replaces existing tags."),
|
|
29
|
+
document_id: z.string().optional().describe("UUID of the document. Required for get and update."),
|
|
30
|
+
entity_type: z.enum(ENTITY_TYPE_ENUM).optional().describe("Polymorphic parent entity type, for attaching a document directly to something other than a ThreatModel (e.g. an Application with no ThreatModel). For create, alternative to threat_model_id — provide exactly one, not both. Must be paired with entity_id. Optional filter for list (also paired with entity_id)."),
|
|
31
|
+
entity_id: z.string().optional().describe("UUID of the entity_type target. Must be paired with entity_type."),
|
|
32
|
+
search: z.string().optional().describe("Filter by title (case-insensitive substring) or tag. Used by list."),
|
|
33
|
+
};
|
|
34
|
+
function errorResult(message) {
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
37
|
+
isError: true,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function successResult(data) {
|
|
41
|
+
return {
|
|
42
|
+
content: [{ type: "text", text: JSON.stringify(sanitizeResponse(data), null, 2) }],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function createDocumentHandler(apiClient) {
|
|
46
|
+
return async (params) => {
|
|
47
|
+
try {
|
|
48
|
+
switch (params.action) {
|
|
49
|
+
case "create": {
|
|
50
|
+
const { threat_model_id, title, content, tags, entity_type, entity_id } = params;
|
|
51
|
+
if (!title || !title.trim())
|
|
52
|
+
return errorResult("title is required for create");
|
|
53
|
+
const hasTm = !!threat_model_id;
|
|
54
|
+
let hasEntityType = !!entity_type;
|
|
55
|
+
let hasEntityId = !!entity_id;
|
|
56
|
+
let resolvedEntityType = entity_type;
|
|
57
|
+
let resolvedEntityId = entity_id;
|
|
58
|
+
if (!hasTm && !hasEntityType && !hasEntityId) {
|
|
59
|
+
const req = requireApplicationId(undefined);
|
|
60
|
+
if (!req.ok)
|
|
61
|
+
return errorResult(req.message);
|
|
62
|
+
resolvedEntityType = "application";
|
|
63
|
+
resolvedEntityId = req.id;
|
|
64
|
+
hasEntityType = true;
|
|
65
|
+
hasEntityId = true;
|
|
66
|
+
}
|
|
67
|
+
else if (!hasTm && entity_type === "application" && !hasEntityId) {
|
|
68
|
+
const req = requireApplicationId(undefined);
|
|
69
|
+
if (!req.ok)
|
|
70
|
+
return errorResult(req.message);
|
|
71
|
+
resolvedEntityId = req.id;
|
|
72
|
+
hasEntityId = true;
|
|
73
|
+
}
|
|
74
|
+
if (hasEntityType !== hasEntityId) {
|
|
75
|
+
return errorResult("entity_type and entity_id must be provided together");
|
|
76
|
+
}
|
|
77
|
+
const hasEntity = hasEntityType && hasEntityId;
|
|
78
|
+
if (hasTm && hasEntity) {
|
|
79
|
+
return errorResult("Provide either threat_model_id or entity_type+entity_id, not both");
|
|
80
|
+
}
|
|
81
|
+
if (!hasTm && !hasEntity) {
|
|
82
|
+
return errorResult("Provide either threat_model_id or entity_type+entity_id");
|
|
83
|
+
}
|
|
84
|
+
const data = await apiClient.post(`/api/documents`, {
|
|
85
|
+
...(hasTm ? { threatModelId: threat_model_id } : { entityType: resolvedEntityType, entityId: resolvedEntityId }),
|
|
86
|
+
title: sanitizeInboundText(title, 500),
|
|
87
|
+
content: sanitizeInboundText(content ?? "", 200_000),
|
|
88
|
+
tags: tags ?? [],
|
|
89
|
+
});
|
|
90
|
+
return successResult(data);
|
|
91
|
+
}
|
|
92
|
+
case "get": {
|
|
93
|
+
if (!params.document_id)
|
|
94
|
+
return errorResult("document_id is required for get");
|
|
95
|
+
const data = await apiClient.get(`/api/documents/${params.document_id}`);
|
|
96
|
+
return successResult(data);
|
|
97
|
+
}
|
|
98
|
+
case "list": {
|
|
99
|
+
const qs = new URLSearchParams();
|
|
100
|
+
if (params.threat_model_id)
|
|
101
|
+
qs.set("threatModelId", params.threat_model_id);
|
|
102
|
+
if (params.entity_type && params.entity_id) {
|
|
103
|
+
qs.set("entityType", params.entity_type);
|
|
104
|
+
qs.set("entityId", params.entity_id);
|
|
105
|
+
}
|
|
106
|
+
if (params.search)
|
|
107
|
+
qs.set("search", params.search);
|
|
108
|
+
const path = `/api/documents${qs.toString() ? `?${qs.toString()}` : ""}`;
|
|
109
|
+
const data = await apiClient.get(path);
|
|
110
|
+
return successResult(data);
|
|
111
|
+
}
|
|
112
|
+
case "update": {
|
|
113
|
+
const { document_id, title, content, tags } = params;
|
|
114
|
+
if (!document_id)
|
|
115
|
+
return errorResult("document_id is required for update");
|
|
116
|
+
if (title === undefined && content === undefined && tags === undefined) {
|
|
117
|
+
return errorResult("At least one of title, content, or tags must be provided for update");
|
|
118
|
+
}
|
|
119
|
+
const body = {};
|
|
120
|
+
if (title !== undefined)
|
|
121
|
+
body.title = sanitizeInboundText(title, 500);
|
|
122
|
+
if (content !== undefined)
|
|
123
|
+
body.content = sanitizeInboundText(content, 200_000);
|
|
124
|
+
if (tags !== undefined)
|
|
125
|
+
body.tags = tags;
|
|
126
|
+
const data = await apiClient.patch(`/api/documents/${document_id}`, body);
|
|
127
|
+
return successResult({ ok: true, document_id, updated: data });
|
|
128
|
+
}
|
|
129
|
+
default:
|
|
130
|
+
return errorResult(`Unknown action: ${params.action}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
return errorResult(err?.message ?? `Failed to ${params.action} document`);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ApiClient } from "../api-client.js";
|
|
3
|
+
export declare const FINDING_NAME = "zivis_finding";
|
|
4
|
+
export declare const FINDING_DESCRIPTION = "Create, update, or delete a tracked security finding authored by hand (not from a scanner). Every change is audited.\n\naction:\n- create: requires title, severity. For service-delivered results (pen test/red team/ZRT), pass source_type + producer (requires run_agents permission).\n- update: requires finding_id. Two kinds of change, never mixed in one call:\n 1. Direct edit: title/severity/description/category/remediation_steps/cwe_id/owasp_id, and non-closing status (open|triaged|in_progress).\n 2. Disposition (`disposition`: open|fixed|accepted_by_design|accepted_risk_based|planned|not_applicable, + rationale/ref/owner_name/target_date/review_date/compensating_controls/residual_severity/note) \u2014 records the customer's claim/decision. disposition=fixed does NOT close the finding: status is only ever derived from (disposition, independent verification) \u2014 a claim never closes a finding, only an independent ZIVIS verdict does. Closing values (resolved/dismissed/suppressed) are REJECTED on the direct-edit path; use `disposition` instead.\n- delete: requires finding_id. Soft-delete, manual findings only.";
|
|
5
|
+
export declare const FINDING_SCHEMA: {
|
|
6
|
+
action: z.ZodEnum<{
|
|
7
|
+
create: "create";
|
|
8
|
+
delete: "delete";
|
|
9
|
+
update: "update";
|
|
10
|
+
}>;
|
|
11
|
+
title: z.ZodOptional<z.ZodString>;
|
|
12
|
+
severity: z.ZodOptional<z.ZodEnum<{
|
|
13
|
+
critical: "critical";
|
|
14
|
+
high: "high";
|
|
15
|
+
medium: "medium";
|
|
16
|
+
low: "low";
|
|
17
|
+
info: "info";
|
|
18
|
+
}>>;
|
|
19
|
+
description: z.ZodOptional<z.ZodString>;
|
|
20
|
+
category: z.ZodOptional<z.ZodEnum<{
|
|
21
|
+
vulnerability: "vulnerability";
|
|
22
|
+
misconfiguration: "misconfiguration";
|
|
23
|
+
secret: "secret";
|
|
24
|
+
anomaly: "anomaly";
|
|
25
|
+
other: "other";
|
|
26
|
+
}>>;
|
|
27
|
+
confidence: z.ZodOptional<z.ZodEnum<{
|
|
28
|
+
high: "high";
|
|
29
|
+
medium: "medium";
|
|
30
|
+
low: "low";
|
|
31
|
+
}>>;
|
|
32
|
+
application_id: z.ZodOptional<z.ZodString>;
|
|
33
|
+
source_document_id: z.ZodOptional<z.ZodString>;
|
|
34
|
+
excerpt: z.ZodOptional<z.ZodString>;
|
|
35
|
+
evidence_location: z.ZodOptional<z.ZodString>;
|
|
36
|
+
cwe_id: z.ZodOptional<z.ZodString>;
|
|
37
|
+
owasp_id: z.ZodOptional<z.ZodString>;
|
|
38
|
+
remediation_steps: z.ZodOptional<z.ZodString>;
|
|
39
|
+
source_type: z.ZodOptional<z.ZodEnum<{
|
|
40
|
+
threat_model: "threat_model";
|
|
41
|
+
manual: "manual";
|
|
42
|
+
red_team: "red_team";
|
|
43
|
+
pen_test: "pen_test";
|
|
44
|
+
}>>;
|
|
45
|
+
producer: z.ZodOptional<z.ZodObject<{
|
|
46
|
+
kind: z.ZodEnum<{
|
|
47
|
+
human: "human";
|
|
48
|
+
zrt: "zrt";
|
|
49
|
+
frontier_tool: "frontier_tool";
|
|
50
|
+
third_party: "third_party";
|
|
51
|
+
}>;
|
|
52
|
+
id: z.ZodOptional<z.ZodString>;
|
|
53
|
+
name: z.ZodOptional<z.ZodString>;
|
|
54
|
+
toolVersion: z.ZodOptional<z.ZodString>;
|
|
55
|
+
engagementId: z.ZodOptional<z.ZodString>;
|
|
56
|
+
}, z.core.$strip>>;
|
|
57
|
+
scenario_id: z.ZodOptional<z.ZodString>;
|
|
58
|
+
attack_step_id: z.ZodOptional<z.ZodString>;
|
|
59
|
+
finding_id: z.ZodOptional<z.ZodString>;
|
|
60
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
61
|
+
open: "open";
|
|
62
|
+
in_progress: "in_progress";
|
|
63
|
+
triaged: "triaged";
|
|
64
|
+
}>>;
|
|
65
|
+
note: z.ZodOptional<z.ZodString>;
|
|
66
|
+
disposition: z.ZodOptional<z.ZodEnum<{
|
|
67
|
+
fixed: "fixed";
|
|
68
|
+
open: "open";
|
|
69
|
+
accepted_by_design: "accepted_by_design";
|
|
70
|
+
accepted_risk_based: "accepted_risk_based";
|
|
71
|
+
planned: "planned";
|
|
72
|
+
not_applicable: "not_applicable";
|
|
73
|
+
}>>;
|
|
74
|
+
rationale: z.ZodOptional<z.ZodString>;
|
|
75
|
+
ref: z.ZodOptional<z.ZodString>;
|
|
76
|
+
owner_name: z.ZodOptional<z.ZodString>;
|
|
77
|
+
target_date: z.ZodOptional<z.ZodString>;
|
|
78
|
+
review_date: z.ZodOptional<z.ZodString>;
|
|
79
|
+
compensating_controls: z.ZodOptional<z.ZodString>;
|
|
80
|
+
residual_severity: z.ZodOptional<z.ZodEnum<{
|
|
81
|
+
critical: "critical";
|
|
82
|
+
high: "high";
|
|
83
|
+
medium: "medium";
|
|
84
|
+
low: "low";
|
|
85
|
+
info: "info";
|
|
86
|
+
}>>;
|
|
87
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
88
|
+
};
|
|
89
|
+
type Producer = {
|
|
90
|
+
kind: "human" | "zrt" | "frontier_tool" | "third_party";
|
|
91
|
+
id?: string;
|
|
92
|
+
name?: string;
|
|
93
|
+
toolVersion?: string;
|
|
94
|
+
engagementId?: string;
|
|
95
|
+
};
|
|
96
|
+
type FindingParams = {
|
|
97
|
+
action: "create" | "update" | "delete";
|
|
98
|
+
title?: string;
|
|
99
|
+
severity?: "critical" | "high" | "medium" | "low" | "info";
|
|
100
|
+
description?: string;
|
|
101
|
+
category?: "vulnerability" | "misconfiguration" | "secret" | "anomaly" | "other";
|
|
102
|
+
confidence?: "high" | "medium" | "low";
|
|
103
|
+
application_id?: string;
|
|
104
|
+
source_document_id?: string;
|
|
105
|
+
excerpt?: string;
|
|
106
|
+
evidence_location?: string;
|
|
107
|
+
cwe_id?: string;
|
|
108
|
+
owasp_id?: string;
|
|
109
|
+
remediation_steps?: string;
|
|
110
|
+
source_type?: "manual" | "pen_test" | "red_team" | "threat_model";
|
|
111
|
+
producer?: Producer;
|
|
112
|
+
scenario_id?: string;
|
|
113
|
+
attack_step_id?: string;
|
|
114
|
+
finding_id?: string;
|
|
115
|
+
status?: "open" | "triaged" | "in_progress";
|
|
116
|
+
note?: string;
|
|
117
|
+
disposition?: "open" | "fixed" | "accepted_by_design" | "accepted_risk_based" | "planned" | "not_applicable";
|
|
118
|
+
rationale?: string;
|
|
119
|
+
ref?: string;
|
|
120
|
+
owner_name?: string;
|
|
121
|
+
target_date?: string;
|
|
122
|
+
review_date?: string;
|
|
123
|
+
compensating_controls?: string;
|
|
124
|
+
residual_severity?: "critical" | "high" | "medium" | "low" | "info";
|
|
125
|
+
reason?: string;
|
|
126
|
+
};
|
|
127
|
+
export declare function createFindingHandler(apiClient: ApiClient): (params: FindingParams) => Promise<{
|
|
128
|
+
content: {
|
|
129
|
+
type: "text";
|
|
130
|
+
text: string;
|
|
131
|
+
}[];
|
|
132
|
+
}>;
|
|
133
|
+
export {};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sanitizeResponse, sanitizeInboundText } from "../sanitize.js";
|
|
3
|
+
import { requireApplicationId } from "../resolve-application-id.js";
|
|
4
|
+
export const FINDING_NAME = "zivis_finding";
|
|
5
|
+
const CLOSING_STATUSES = new Set(["resolved", "dismissed", "suppressed"]);
|
|
6
|
+
export const FINDING_DESCRIPTION = `Create, update, or delete a tracked security finding authored by hand (not from a scanner). Every change is audited.
|
|
7
|
+
|
|
8
|
+
action:
|
|
9
|
+
- create: requires title, severity. For service-delivered results (pen test/red team/ZRT), pass source_type + producer (requires run_agents permission).
|
|
10
|
+
- update: requires finding_id. Two kinds of change, never mixed in one call:
|
|
11
|
+
1. Direct edit: title/severity/description/category/remediation_steps/cwe_id/owasp_id, and non-closing status (open|triaged|in_progress).
|
|
12
|
+
2. Disposition (\`disposition\`: open|fixed|accepted_by_design|accepted_risk_based|planned|not_applicable, + rationale/ref/owner_name/target_date/review_date/compensating_controls/residual_severity/note) — records the customer's claim/decision. disposition=fixed does NOT close the finding: status is only ever derived from (disposition, independent verification) — a claim never closes a finding, only an independent ZIVIS verdict does. Closing values (resolved/dismissed/suppressed) are REJECTED on the direct-edit path; use \`disposition\` instead.
|
|
13
|
+
- delete: requires finding_id. Soft-delete, manual findings only.`;
|
|
14
|
+
export const FINDING_SCHEMA = {
|
|
15
|
+
action: z.enum(["create", "update", "delete"]).describe("Which finding operation to perform"),
|
|
16
|
+
title: z.string().optional().describe("Short summary of the finding. Required for create; optional new value for update."),
|
|
17
|
+
severity: z
|
|
18
|
+
.enum(["critical", "high", "medium", "low", "info"])
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Required for create. Be honest — reserve critical/high for genuinely exploitable issues."),
|
|
21
|
+
description: z.string().optional().describe("Details / new description (markdown allowed)."),
|
|
22
|
+
category: z.enum(["vulnerability", "misconfiguration", "secret", "anomaly", "other"]).optional().describe("Finding category (default on create: vulnerability)."),
|
|
23
|
+
confidence: z.enum(["high", "medium", "low"]).optional().describe("create only: how confident you are this is real (default: medium)."),
|
|
24
|
+
application_id: z.string().optional().describe("Application UUID. create only (update/delete resolve the application from the finding itself). Defaults to applicationId in .zivis/project.json if omitted."),
|
|
25
|
+
source_document_id: z.string().optional().describe("create only: UUID of a ZIVIS Document this finding came from. Links the finding back to it."),
|
|
26
|
+
excerpt: z.string().optional().describe("create only: the exact source text the finding was drawn from (e.g. the README snippet)."),
|
|
27
|
+
evidence_location: z.string().optional().describe("create only: human-readable locator, e.g. 'README.md:L40-L51' or a file path."),
|
|
28
|
+
cwe_id: z.string().optional().describe("Optional CWE id, e.g. 'CWE-89'. create/update."),
|
|
29
|
+
owasp_id: z.string().optional().describe("Optional OWASP id, e.g. 'A01'. create/update."),
|
|
30
|
+
remediation_steps: z.string().optional().describe("Remediation guidance. create/update."),
|
|
31
|
+
source_type: z
|
|
32
|
+
.enum(["manual", "pen_test", "red_team", "threat_model"])
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("create only (default: manual). Non-manual marks the finding as a service-delivered result that flows to the Signal API; requires the run_agents org permission."),
|
|
35
|
+
producer: z
|
|
36
|
+
.object({
|
|
37
|
+
kind: z.enum(["human", "zrt", "frontier_tool", "third_party"]).describe("Who/what produced the finding"),
|
|
38
|
+
id: z.string().optional().describe("Stable producer identifier (e.g. analyst email)"),
|
|
39
|
+
name: z.string().optional().describe("Display name (e.g. 'Jake Miller', 'ZRT')"),
|
|
40
|
+
toolVersion: z.string().optional().describe("Tool version, if applicable"),
|
|
41
|
+
engagementId: z.string().optional().describe("Engagement/campaign this run belongs to"),
|
|
42
|
+
})
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("create only: producer provenance, stored on the finding as metadata.producer"),
|
|
45
|
+
scenario_id: z.string().optional().describe("create only: GraphNode logicalId (UUID) of the ATTACK_SCENARIO this finding proves out"),
|
|
46
|
+
attack_step_id: z.string().optional().describe("create only: GraphNode logicalId (UUID) of the ATTACK_STEP this finding corresponds to"),
|
|
47
|
+
finding_id: z.string().optional().describe("UUID of the finding. Required for update and delete."),
|
|
48
|
+
status: z
|
|
49
|
+
.enum(["open", "triaged", "in_progress"])
|
|
50
|
+
.optional()
|
|
51
|
+
.describe("update only: new NON-CLOSING lifecycle status. resolved/dismissed/suppressed are not accepted here — pass `disposition` instead; this tool rejects a closing status client-side rather than round-tripping to the API."),
|
|
52
|
+
note: z.string().optional().describe("Optional note recorded in the audit history. update only."),
|
|
53
|
+
disposition: z
|
|
54
|
+
.enum(["open", "fixed", "accepted_by_design", "accepted_risk_based", "planned", "not_applicable"])
|
|
55
|
+
.optional()
|
|
56
|
+
.describe("update only: record the CUSTOMER's claim/decision about this finding. Routes through PATCH /unified/:id/disposition (deriveStatus()) — never closes the finding by itself. Rationale is REQUIRED when disposition is accepted_by_design or accepted_risk_based."),
|
|
57
|
+
rationale: z.string().optional().describe("update+disposition only: why this disposition — required for accepted_by_design/accepted_risk_based."),
|
|
58
|
+
ref: z.string().optional().describe("update+disposition only: reference (ticket id, PR link, etc.) backing the disposition."),
|
|
59
|
+
owner_name: z.string().optional().describe("update+disposition only: who owns this disposition going forward."),
|
|
60
|
+
target_date: z.string().optional().describe("update+disposition only: ISO date this disposition targets (e.g. a planned fix date)."),
|
|
61
|
+
review_date: z.string().optional().describe("update+disposition only: ISO date this disposition should be re-reviewed by."),
|
|
62
|
+
compensating_controls: z.string().optional().describe("update+disposition only: mitigations already in place while this risk is carried."),
|
|
63
|
+
residual_severity: z.enum(["critical", "high", "medium", "low", "info"]).optional().describe("update+disposition only: severity that remains after compensating controls."),
|
|
64
|
+
reason: z.string().optional().describe("delete only: short reason for deleting (recorded in audit history)."),
|
|
65
|
+
};
|
|
66
|
+
function errorResult(message) {
|
|
67
|
+
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
|
68
|
+
}
|
|
69
|
+
function successResult(data) {
|
|
70
|
+
return { content: [{ type: "text", text: JSON.stringify(sanitizeResponse(data), null, 2) }] };
|
|
71
|
+
}
|
|
72
|
+
function resolveApplicationIdSafe(explicit) {
|
|
73
|
+
const r = requireApplicationId(explicit);
|
|
74
|
+
return r.ok ? r.id : undefined;
|
|
75
|
+
}
|
|
76
|
+
const ACCEPTED_DISPOSITIONS = new Set(["accepted_by_design", "accepted_risk_based"]);
|
|
77
|
+
export function createFindingHandler(apiClient) {
|
|
78
|
+
return async (params) => {
|
|
79
|
+
try {
|
|
80
|
+
switch (params.action) {
|
|
81
|
+
case "create": {
|
|
82
|
+
if (!params.title || !params.title.trim())
|
|
83
|
+
return errorResult("title is required for create");
|
|
84
|
+
if (!params.severity)
|
|
85
|
+
return errorResult("severity is required for create");
|
|
86
|
+
let applicationId;
|
|
87
|
+
if (!params.source_document_id) {
|
|
88
|
+
const resolved = requireApplicationId(params.application_id);
|
|
89
|
+
if (!resolved.ok)
|
|
90
|
+
return errorResult(resolved.message);
|
|
91
|
+
applicationId = resolved.id;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
applicationId = resolveApplicationIdSafe(params.application_id);
|
|
95
|
+
}
|
|
96
|
+
const data = await apiClient.post(`/api/findings/unified`, {
|
|
97
|
+
title: sanitizeInboundText(params.title, 500),
|
|
98
|
+
severity: params.severity,
|
|
99
|
+
description: sanitizeInboundText(params.description, 8000),
|
|
100
|
+
category: params.category,
|
|
101
|
+
confidence: params.confidence,
|
|
102
|
+
origin: "mcp",
|
|
103
|
+
applicationId,
|
|
104
|
+
sourceDocumentId: params.source_document_id,
|
|
105
|
+
excerpt: sanitizeInboundText(params.excerpt, 8000),
|
|
106
|
+
evidenceLocation: sanitizeInboundText(params.evidence_location, 500),
|
|
107
|
+
cweId: params.cwe_id,
|
|
108
|
+
owaspId: params.owasp_id,
|
|
109
|
+
remediationSteps: sanitizeInboundText(params.remediation_steps, 4000),
|
|
110
|
+
sourceType: params.source_type,
|
|
111
|
+
producer: params.producer
|
|
112
|
+
? {
|
|
113
|
+
kind: params.producer.kind,
|
|
114
|
+
id: sanitizeInboundText(params.producer.id, 300),
|
|
115
|
+
name: sanitizeInboundText(params.producer.name, 300),
|
|
116
|
+
toolVersion: sanitizeInboundText(params.producer.toolVersion, 300),
|
|
117
|
+
engagementId: sanitizeInboundText(params.producer.engagementId, 300),
|
|
118
|
+
}
|
|
119
|
+
: undefined,
|
|
120
|
+
scenarioId: params.scenario_id,
|
|
121
|
+
attackStepId: params.attack_step_id,
|
|
122
|
+
});
|
|
123
|
+
return successResult(data);
|
|
124
|
+
}
|
|
125
|
+
case "update": {
|
|
126
|
+
if (!params.finding_id || !params.finding_id.trim()) {
|
|
127
|
+
return errorResult("finding_id is required for update");
|
|
128
|
+
}
|
|
129
|
+
const hasDisposition = params.disposition !== undefined;
|
|
130
|
+
const hasDirectFields = params.title !== undefined ||
|
|
131
|
+
params.severity !== undefined ||
|
|
132
|
+
params.description !== undefined ||
|
|
133
|
+
params.category !== undefined ||
|
|
134
|
+
params.remediation_steps !== undefined ||
|
|
135
|
+
params.cwe_id !== undefined ||
|
|
136
|
+
params.owasp_id !== undefined ||
|
|
137
|
+
params.status !== undefined;
|
|
138
|
+
if (hasDisposition && hasDirectFields) {
|
|
139
|
+
return errorResult("Provide either `disposition` or direct field/status edits in one call, not both — the disposition write and the content-field write are two different endpoints. Make two calls if you need both.");
|
|
140
|
+
}
|
|
141
|
+
if (hasDisposition) {
|
|
142
|
+
if (ACCEPTED_DISPOSITIONS.has(params.disposition) && !String(params.rationale ?? "").trim()) {
|
|
143
|
+
return errorResult("rationale is required when disposition is accepted_by_design or accepted_risk_based");
|
|
144
|
+
}
|
|
145
|
+
const body = { disposition: params.disposition };
|
|
146
|
+
if (params.rationale !== undefined)
|
|
147
|
+
body.rationale = sanitizeInboundText(params.rationale, 4000);
|
|
148
|
+
if (params.ref !== undefined)
|
|
149
|
+
body.ref = sanitizeInboundText(params.ref, 500);
|
|
150
|
+
if (params.owner_name !== undefined)
|
|
151
|
+
body.ownerName = sanitizeInboundText(params.owner_name, 300);
|
|
152
|
+
if (params.target_date !== undefined)
|
|
153
|
+
body.targetDate = params.target_date;
|
|
154
|
+
if (params.review_date !== undefined)
|
|
155
|
+
body.reviewDate = params.review_date;
|
|
156
|
+
if (params.compensating_controls !== undefined)
|
|
157
|
+
body.compensatingControls = sanitizeInboundText(params.compensating_controls, 2000);
|
|
158
|
+
if (params.residual_severity !== undefined)
|
|
159
|
+
body.residualSeverity = params.residual_severity;
|
|
160
|
+
if (params.note !== undefined)
|
|
161
|
+
body.note = sanitizeInboundText(params.note, 4000);
|
|
162
|
+
const data = await apiClient.patch(`/api/findings/unified/${encodeURIComponent(params.finding_id)}/disposition`, body);
|
|
163
|
+
return successResult(data);
|
|
164
|
+
}
|
|
165
|
+
if (params.status !== undefined && CLOSING_STATUSES.has(params.status)) {
|
|
166
|
+
return errorResult(`"${params.status}" cannot be set directly — it must be derived from disposition + independent verification. ` +
|
|
167
|
+
`Pass \`disposition\` instead (fixed, accepted_by_design, accepted_risk_based, not_applicable, planned) to record what you believe/decided; ` +
|
|
168
|
+
`only an independent ZIVIS verification can move the finding to a closed status.`);
|
|
169
|
+
}
|
|
170
|
+
const body = {};
|
|
171
|
+
if (params.title !== undefined)
|
|
172
|
+
body.title = sanitizeInboundText(params.title, 500);
|
|
173
|
+
if (params.severity !== undefined)
|
|
174
|
+
body.severity = params.severity;
|
|
175
|
+
if (params.description !== undefined)
|
|
176
|
+
body.description = sanitizeInboundText(params.description, 8000);
|
|
177
|
+
if (params.category !== undefined)
|
|
178
|
+
body.category = params.category;
|
|
179
|
+
if (params.remediation_steps !== undefined)
|
|
180
|
+
body.remediationSteps = sanitizeInboundText(params.remediation_steps, 4000);
|
|
181
|
+
if (params.cwe_id !== undefined)
|
|
182
|
+
body.cweId = params.cwe_id;
|
|
183
|
+
if (params.owasp_id !== undefined)
|
|
184
|
+
body.owaspId = params.owasp_id;
|
|
185
|
+
if (params.status !== undefined)
|
|
186
|
+
body.status = params.status;
|
|
187
|
+
if (params.note !== undefined)
|
|
188
|
+
body.note = sanitizeInboundText(params.note, 4000);
|
|
189
|
+
if (Object.keys(body).length === 0) {
|
|
190
|
+
return errorResult("Provide at least one field to update (or `disposition` to record a customer decision)");
|
|
191
|
+
}
|
|
192
|
+
const data = await apiClient.patch(`/api/findings/unified/${encodeURIComponent(params.finding_id)}`, body);
|
|
193
|
+
return successResult(data);
|
|
194
|
+
}
|
|
195
|
+
case "delete": {
|
|
196
|
+
if (!params.finding_id || !params.finding_id.trim()) {
|
|
197
|
+
return errorResult("finding_id is required for delete");
|
|
198
|
+
}
|
|
199
|
+
const query = params.reason ? `?reason=${encodeURIComponent(params.reason)}` : "";
|
|
200
|
+
const data = await apiClient.del(`/api/findings/unified/${encodeURIComponent(params.finding_id)}${query}`);
|
|
201
|
+
return successResult(data);
|
|
202
|
+
}
|
|
203
|
+
default:
|
|
204
|
+
return errorResult(`Unknown action: ${params.action}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
return errorResult(err?.message ?? `Failed to ${params.action} finding`);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import type { ApiClient } from "../api-client.js";
|
|
3
3
|
import type { ZivisConfig } from "../types.js";
|
|
4
4
|
export declare const GET_STARTED_NAME = "zivis_get_started";
|
|
5
|
-
export declare const GET_STARTED_DESCRIPTION = "Call this FIRST on any turn where the user asks about security, vulnerabilities, code review, deployment readiness, threat modeling, dependencies, or \"what should I do next.\" Idempotent
|
|
5
|
+
export declare const GET_STARTED_DESCRIPTION = "Call this FIRST on any turn where the user asks about security, vulnerabilities, code review, deployment readiness, threat modeling, dependencies, or \"what should I do next.\" Idempotent \u2014 detects prior inspect runs and never re-inspects. Prefer this over running `zivis inspect`/`zivis check` (CLI) directly.\n\nTriggers: \"where do I start\", \"is my app/chatbot safe\", \"security stuff before launch\", \"audit/pen test coming up\", \"check my dependencies\", \"how do I use ZIVIS\", \"what can ZIVIS do\".\n\nInspects project state (binding, application, scans, findings) and, if a recent 'zivis inspect' artifact exists, evaluates the local threat library against it to surface architectural patterns (self-consistency without isolation, late org filter, privilege separation, etc.) in `relevant_threats` \u2014 reading the actual codebase, not a generic menu.\n\nPlain developer language by default \u2014 no jargon (actor, STRIDE, kill chain, TTPs) unless the user used it first. Product nouns (\"your threat model\", `zivis threatmodel`) are fine to name directly.\n\nOutput: present recommended_next_steps as a numbered list; lead with relevant_threats if non-empty. Use label/why verbatim \u2014 do not invent steps. If unsure, recommend if_user_unsure.";
|
|
6
6
|
export declare const GET_STARTED_SCHEMA: {
|
|
7
7
|
concern: z.ZodOptional<z.ZodString>;
|
|
8
8
|
cwd: z.ZodOptional<z.ZodString>;
|