@zivis/mcp 0.1.0-alpha.23 → 0.1.0-alpha.24
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/inspect-cache.d.ts +32 -0
- package/dist/lib/inspect-cache.js +99 -0
- package/dist/lib/inspect-cache.js.map +1 -0
- package/dist/pattern-packs/zivis-public-0.1.0/manifest.json +1 -1
- package/dist/pattern-packs/zivis-public-0.2.0/capsules/agents/privilege-separation.yaml +224 -0
- package/dist/pattern-packs/zivis-public-0.2.0/capsules/prompting/self-consistency-no-isolation.yaml +237 -0
- package/dist/pattern-packs/zivis-public-0.2.0/capsules/retrieval/context-injection-no-validator.yaml +348 -0
- package/dist/pattern-packs/zivis-public-0.2.0/capsules/security/late-org-filter.yaml +266 -0
- package/dist/pattern-packs/zivis-public-0.2.0/controls/external_validator_on_facts.yaml +37 -0
- package/dist/pattern-packs/zivis-public-0.2.0/controls/fact_validator_present.yaml +68 -0
- package/dist/pattern-packs/zivis-public-0.2.0/controls/human_approval_gate.yaml +44 -0
- package/dist/pattern-packs/zivis-public-0.2.0/controls/langgraph_state_machine_with_per_node_tools.yaml +35 -0
- package/dist/pattern-packs/zivis-public-0.2.0/controls/per_sample_retrieval_diversification.yaml +38 -0
- package/dist/pattern-packs/zivis-public-0.2.0/controls/prisma_extension_or_middleware_attaching_org_filter.yaml +31 -0
- package/dist/pattern-packs/zivis-public-0.2.0/controls/row_level_security_policy.yaml +37 -0
- package/dist/pattern-packs/zivis-public-0.2.0/controls/source_attribution_present.yaml +58 -0
- package/dist/pattern-packs/zivis-public-0.2.0/manifest.json +167 -0
- package/dist/pattern-packs/zivis-public-0.2.0/prompts/agents/privilege-separation/identify.md +54 -0
- package/dist/pattern-packs/zivis-public-0.2.0/prompts/prompting/self-consistency-no-isolation/identify.md +60 -0
- package/dist/pattern-packs/zivis-public-0.2.0/prompts/retrieval/context-injection-no-validator/identify.md +109 -0
- package/dist/pattern-packs/zivis-public-0.2.0/prompts/security/late-org-filter/identify.md +78 -0
- package/dist/tools/check-project.d.ts +1 -1
- package/dist/tools/check-project.js +2 -4
- package/dist/tools/check-project.js.map +1 -1
- package/dist/tools/get-started.d.ts +1 -1
- package/dist/tools/get-started.js +38 -73
- package/dist/tools/get-started.js.map +1 -1
- package/dist/tools/inspect.d.ts +1 -1
- package/dist/tools/inspect.js +2 -0
- package/dist/tools/inspect.js.map +1 -1
- package/dist/tools/threat-guide.d.ts +3 -1
- package/dist/tools/threat-guide.js +101 -9
- package/dist/tools/threat-guide.js.map +1 -1
- package/package.json +14 -13
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* inspect-cache — shared helpers for locating local `zivis inspect` artifacts.
|
|
3
|
+
*
|
|
4
|
+
* The repo-id derivation must match the algorithm `zivis inspect` uses when it
|
|
5
|
+
* writes runs (git remote URL hash, falling back to root-path hash). Both
|
|
6
|
+
* `zivis_get_started` and `zivis_threat_guide` read this cache to avoid
|
|
7
|
+
* nagging the user to re-inspect when a fresh run already exists on disk.
|
|
8
|
+
*/
|
|
9
|
+
export interface LocatedArtifact {
|
|
10
|
+
path: string;
|
|
11
|
+
mtimeMs: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function userCacheRoot(): string;
|
|
14
|
+
export declare function deriveRepoId(cwd: string): Promise<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Locate the most recent inspect artifact for THIS repo.
|
|
17
|
+
*
|
|
18
|
+
* Returns undefined if no runs exist for this repo, even when other repos
|
|
19
|
+
* have artifacts. Returns both the path and mtime so callers can apply
|
|
20
|
+
* freshness logic without re-stat'ing.
|
|
21
|
+
*/
|
|
22
|
+
export declare function locateLatestArtifact(cwd: string): Promise<LocatedArtifact | undefined>;
|
|
23
|
+
/**
|
|
24
|
+
* Default freshness TTL for the inspect cache. After this, callers should
|
|
25
|
+
* treat the artifact as stale and recommend re-running inspect.
|
|
26
|
+
*
|
|
27
|
+
* 15 min matches the typical IDE-session length for "I just inspected; don't
|
|
28
|
+
* make me do it again." Override via ZIVIS_INSPECT_CACHE_TTL_MS.
|
|
29
|
+
*/
|
|
30
|
+
export declare const DEFAULT_INSPECT_CACHE_TTL_MS: number;
|
|
31
|
+
export declare function inspectCacheTtlMs(): number;
|
|
32
|
+
export declare function isFresh(mtimeMs: number, now?: number): boolean;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* inspect-cache — shared helpers for locating local `zivis inspect` artifacts.
|
|
3
|
+
*
|
|
4
|
+
* The repo-id derivation must match the algorithm `zivis inspect` uses when it
|
|
5
|
+
* writes runs (git remote URL hash, falling back to root-path hash). Both
|
|
6
|
+
* `zivis_get_started` and `zivis_threat_guide` read this cache to avoid
|
|
7
|
+
* nagging the user to re-inspect when a fresh run already exists on disk.
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from "node:fs/promises";
|
|
10
|
+
import * as fssync from "node:fs";
|
|
11
|
+
import * as os from "node:os";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import * as crypto from "node:crypto";
|
|
14
|
+
import { exec } from "node:child_process";
|
|
15
|
+
import { promisify } from "node:util";
|
|
16
|
+
const execAsync = promisify(exec);
|
|
17
|
+
export function userCacheRoot() {
|
|
18
|
+
const home = os.homedir();
|
|
19
|
+
if (process.platform === "darwin") {
|
|
20
|
+
return path.join(home, "Library", "Caches", "zivis");
|
|
21
|
+
}
|
|
22
|
+
if (process.platform === "win32") {
|
|
23
|
+
const localAppData = process.env.LOCALAPPDATA ?? path.join(home, "AppData", "Local");
|
|
24
|
+
return path.join(localAppData, "zivis", "Cache");
|
|
25
|
+
}
|
|
26
|
+
const xdg = process.env.XDG_CACHE_HOME;
|
|
27
|
+
return path.join(xdg ?? path.join(home, ".cache"), "zivis");
|
|
28
|
+
}
|
|
29
|
+
export async function deriveRepoId(cwd) {
|
|
30
|
+
const remote = await tryGit(cwd, "remote get-url origin");
|
|
31
|
+
if (remote) {
|
|
32
|
+
return "remote-" + sha256Short(remote);
|
|
33
|
+
}
|
|
34
|
+
return "path-" + sha256Short(cwd);
|
|
35
|
+
}
|
|
36
|
+
async function tryGit(cwd, args) {
|
|
37
|
+
try {
|
|
38
|
+
const { stdout } = await execAsync(`git ${args}`, { cwd });
|
|
39
|
+
const value = stdout.trim();
|
|
40
|
+
return value || undefined;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function sha256Short(value) {
|
|
47
|
+
return crypto.createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Locate the most recent inspect artifact for THIS repo.
|
|
51
|
+
*
|
|
52
|
+
* Returns undefined if no runs exist for this repo, even when other repos
|
|
53
|
+
* have artifacts. Returns both the path and mtime so callers can apply
|
|
54
|
+
* freshness logic without re-stat'ing.
|
|
55
|
+
*/
|
|
56
|
+
export async function locateLatestArtifact(cwd) {
|
|
57
|
+
const repoId = await deriveRepoId(cwd);
|
|
58
|
+
const runsDir = path.join(userCacheRoot(), "repos", repoId, "inspect", "runs");
|
|
59
|
+
if (!fssync.existsSync(runsDir))
|
|
60
|
+
return undefined;
|
|
61
|
+
const candidates = [];
|
|
62
|
+
for (const entry of await fs.readdir(runsDir)) {
|
|
63
|
+
if (!entry.endsWith(".json"))
|
|
64
|
+
continue;
|
|
65
|
+
const full = path.join(runsDir, entry);
|
|
66
|
+
try {
|
|
67
|
+
const stat = await fs.stat(full);
|
|
68
|
+
candidates.push({ path: full, mtimeMs: stat.mtimeMs });
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// skip
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (candidates.length === 0)
|
|
75
|
+
return undefined;
|
|
76
|
+
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
77
|
+
return candidates[0];
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Default freshness TTL for the inspect cache. After this, callers should
|
|
81
|
+
* treat the artifact as stale and recommend re-running inspect.
|
|
82
|
+
*
|
|
83
|
+
* 15 min matches the typical IDE-session length for "I just inspected; don't
|
|
84
|
+
* make me do it again." Override via ZIVIS_INSPECT_CACHE_TTL_MS.
|
|
85
|
+
*/
|
|
86
|
+
export const DEFAULT_INSPECT_CACHE_TTL_MS = 15 * 60 * 1000;
|
|
87
|
+
export function inspectCacheTtlMs() {
|
|
88
|
+
const raw = process.env.ZIVIS_INSPECT_CACHE_TTL_MS;
|
|
89
|
+
if (!raw)
|
|
90
|
+
return DEFAULT_INSPECT_CACHE_TTL_MS;
|
|
91
|
+
const parsed = Number.parseInt(raw, 10);
|
|
92
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
93
|
+
return DEFAULT_INSPECT_CACHE_TTL_MS;
|
|
94
|
+
return parsed;
|
|
95
|
+
}
|
|
96
|
+
export function isFresh(mtimeMs, now = Date.now()) {
|
|
97
|
+
return now - mtimeMs <= inspectCacheTtlMs();
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=inspect-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect-cache.js","sourceRoot":"","sources":["../../src/lib/inspect-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"pack_id": "zivis-public",
|
|
4
4
|
"pack_name": "ZIVIS Public Pattern Pack",
|
|
5
5
|
"version": "0.1.0",
|
|
6
|
-
"built_at": "2026-05-07T12:
|
|
6
|
+
"built_at": "2026-05-07T12:29:17.361Z",
|
|
7
7
|
"tier": "customer_safe",
|
|
8
8
|
"description": "ZIVIS-curated public pattern pack — capsules + inference prompts evaluated locally on the user's machine.",
|
|
9
9
|
"capsules": [
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Detection Card: Privilege Separation (absent)
|
|
2
|
+
# Spec: docs/specs/PATTERN-MATCHING-LIBRARY-V1.md
|
|
3
|
+
# Schema: schemas/pattern-detection-card.schema.json
|
|
4
|
+
|
|
5
|
+
id: agents.privilege-separation
|
|
6
|
+
slug: privilege-separation
|
|
7
|
+
version: 0.1.0
|
|
8
|
+
category: agents
|
|
9
|
+
title: Privilege Separation
|
|
10
|
+
headline: Per-Subtask Capability Scoping
|
|
11
|
+
description: >
|
|
12
|
+
Detects agentic codebases where multiple agents share a single tool/capability
|
|
13
|
+
set without per-agent scoping. Absence of privilege separation means one
|
|
14
|
+
compromised agent inherits the full capability surface of the system.
|
|
15
|
+
detection_tier: public
|
|
16
|
+
scoring_tier: proprietary
|
|
17
|
+
|
|
18
|
+
applicable_languages:
|
|
19
|
+
- typescript
|
|
20
|
+
- javascript
|
|
21
|
+
- python
|
|
22
|
+
|
|
23
|
+
# Don't run this pattern unless an agent framework is actually present.
|
|
24
|
+
# Mirrors nuclei-style relevance gating to keep the noise floor low.
|
|
25
|
+
relevance_filter:
|
|
26
|
+
any_of:
|
|
27
|
+
- dependency: "langgraph"
|
|
28
|
+
direct_only: true
|
|
29
|
+
- dependency: "langchain"
|
|
30
|
+
direct_only: true
|
|
31
|
+
- dependency: "@langchain/langgraph"
|
|
32
|
+
direct_only: true
|
|
33
|
+
- dependency: "crewai"
|
|
34
|
+
direct_only: true
|
|
35
|
+
- dependency: "llama-index"
|
|
36
|
+
direct_only: true
|
|
37
|
+
- dependency: "openai-agents"
|
|
38
|
+
direct_only: true
|
|
39
|
+
- dependency: "autogen-agentchat"
|
|
40
|
+
direct_only: true
|
|
41
|
+
|
|
42
|
+
strategies:
|
|
43
|
+
# 1. AST: Find tool list assignments (Python — LangChain/LangGraph idiom).
|
|
44
|
+
- code: AST
|
|
45
|
+
language: python
|
|
46
|
+
expression: |
|
|
47
|
+
rule:
|
|
48
|
+
kind: assignment
|
|
49
|
+
pattern: $TOOLS = [$$$]
|
|
50
|
+
has:
|
|
51
|
+
stopBy: end
|
|
52
|
+
any:
|
|
53
|
+
- pattern: create_react_agent
|
|
54
|
+
- pattern: bind_tools
|
|
55
|
+
- pattern: AgentExecutor
|
|
56
|
+
evidence_fields: [file_path, line_span, symbol]
|
|
57
|
+
confidence_weight: 0.25
|
|
58
|
+
|
|
59
|
+
# 2. AST: TypeScript/JavaScript equivalent.
|
|
60
|
+
- code: AST
|
|
61
|
+
language: typescript
|
|
62
|
+
expression: |
|
|
63
|
+
rule:
|
|
64
|
+
any:
|
|
65
|
+
- pattern: const $TOOLS = [$$$]
|
|
66
|
+
- pattern: bindTools($$$)
|
|
67
|
+
- pattern: createReactAgent($$$)
|
|
68
|
+
evidence_fields: [file_path, line_span, symbol]
|
|
69
|
+
confidence_weight: 0.25
|
|
70
|
+
|
|
71
|
+
# 3. GRAPH: Two or more agent declarations share the same tool set.
|
|
72
|
+
# Predicate evaluated against extractor output (components/edges).
|
|
73
|
+
- code: GRAPH
|
|
74
|
+
expression: |
|
|
75
|
+
count(distinct agent_declaration) >= 2
|
|
76
|
+
AND any_pair(agents, lambda a, b: a.tool_set == b.tool_set)
|
|
77
|
+
evidence_fields: [graph_path, file_spans, node_ids]
|
|
78
|
+
confidence_weight: 0.35
|
|
79
|
+
|
|
80
|
+
# 4. META: Absence of per-agent capability scope OR explicit authz layer.
|
|
81
|
+
- code: META
|
|
82
|
+
expression:
|
|
83
|
+
none_of_controls:
|
|
84
|
+
- langgraph_state_machine_with_per_node_tools
|
|
85
|
+
evidence_fields: [missing_controls]
|
|
86
|
+
confidence_weight: 0.30
|
|
87
|
+
|
|
88
|
+
# 5. INFER: Last-resort semantic check; never alone proves a finding.
|
|
89
|
+
- code: INFER
|
|
90
|
+
expression: "prompt_id:agents.privilege-separation.identify"
|
|
91
|
+
evidence_fields: [llm_rationale]
|
|
92
|
+
confidence_weight: 0.15
|
|
93
|
+
trusted: false
|
|
94
|
+
|
|
95
|
+
required_evidence_count: 3
|
|
96
|
+
|
|
97
|
+
# Presence of any of these controls downgrades or eliminates the finding.
|
|
98
|
+
control_negations:
|
|
99
|
+
- "presence_of(per_agent_tool_filter)"
|
|
100
|
+
- "presence_of(capability_token_check)"
|
|
101
|
+
- "presence_of(langgraph_state_machine_with_per_node_tools)"
|
|
102
|
+
|
|
103
|
+
threats:
|
|
104
|
+
- title: Compromised planner inherits executor capabilities
|
|
105
|
+
stride: [tampering, elevation_of_privilege]
|
|
106
|
+
description: >
|
|
107
|
+
A prompt-injection compromise of any agent escalates to the full set of
|
|
108
|
+
tools available to all agents in the system, including high-impact tools
|
|
109
|
+
like code execution, email, or file write.
|
|
110
|
+
remediation: >
|
|
111
|
+
Bind only the minimum tools required to each agent. In LangGraph, define
|
|
112
|
+
tools per node rather than globally. Add a capability token or
|
|
113
|
+
authorization layer that validates tool dispatch against agent identity.
|
|
114
|
+
- title: Lateral compromise via shared tool registry
|
|
115
|
+
stride: [elevation_of_privilege, information_disclosure]
|
|
116
|
+
description: >
|
|
117
|
+
Any tool callable from one agent is callable from every agent. There is
|
|
118
|
+
no blast-radius reduction even if one agent is otherwise constrained.
|
|
119
|
+
|
|
120
|
+
fixtures:
|
|
121
|
+
positive:
|
|
122
|
+
- "fixtures/positive/privilege-separation/langgraph_shared_tools_python"
|
|
123
|
+
- "fixtures/positive/privilege-separation/langchain_shared_tools_typescript"
|
|
124
|
+
negative:
|
|
125
|
+
- "fixtures/negative/privilege-separation/langgraph_per_node_tools"
|
|
126
|
+
- "fixtures/negative/privilege-separation/non_agentic_repo"
|
|
127
|
+
|
|
128
|
+
lifecycle: draft
|
|
129
|
+
owner: platform-threat-modeling
|
|
130
|
+
tags:
|
|
131
|
+
- agentic-ai
|
|
132
|
+
- least-privilege
|
|
133
|
+
- blast-radius
|
|
134
|
+
|
|
135
|
+
# Capsule fields — added in Phase 2A so the pack builder can ship this card
|
|
136
|
+
# as a runnable customer-safe capsule (deterministic strategies + inference prompt).
|
|
137
|
+
sensitivity_level: customer_safe
|
|
138
|
+
allowed_execution_modes:
|
|
139
|
+
- local_only
|
|
140
|
+
- local_model
|
|
141
|
+
- hybrid_cloud_eval
|
|
142
|
+
|
|
143
|
+
safe_summary: >
|
|
144
|
+
Multi-agent systems often share a single tool registry across agents. When that
|
|
145
|
+
happens, a prompt-injection compromise of any one agent inherits the full
|
|
146
|
+
capability surface of the system — including high-impact tools like code
|
|
147
|
+
execution, email, or filesystem write. Privilege separation means each agent
|
|
148
|
+
is bound only to the minimum tools required for its role.
|
|
149
|
+
|
|
150
|
+
detect_when: >
|
|
151
|
+
Two or more agent declarations are wired to the same tool list, or a single
|
|
152
|
+
shared tool array is referenced by every agent factory call, with no per-agent
|
|
153
|
+
capability scoping or authorization layer between agent and tool dispatch.
|
|
154
|
+
|
|
155
|
+
risk_hints:
|
|
156
|
+
- Any tool callable from one agent becomes callable from every agent
|
|
157
|
+
- Compromise of the weakest-prompted agent escalates to the most-privileged tool
|
|
158
|
+
- LangGraph's per-node tool binding is the idiomatic fix; AgentExecutor with a flat tool list is the anti-pattern
|
|
159
|
+
|
|
160
|
+
architectural_signals:
|
|
161
|
+
- Shared TOOLS array reused across multiple agent constructors
|
|
162
|
+
- No capability_token / authorization_layer between planner and tool dispatch
|
|
163
|
+
- LangGraph used without per-node tool binding
|
|
164
|
+
|
|
165
|
+
model_task_prompt_ref: prompts/agents/privilege-separation/identify.md
|
|
166
|
+
|
|
167
|
+
expected_output_schema:
|
|
168
|
+
type: object
|
|
169
|
+
required: [verdict, rationale, evidence_spans]
|
|
170
|
+
properties:
|
|
171
|
+
verdict:
|
|
172
|
+
type: string
|
|
173
|
+
enum: [present, absent, unclear]
|
|
174
|
+
description: present = privilege separation IS implemented; absent = the anti-pattern fires; unclear = needs human review
|
|
175
|
+
confidence:
|
|
176
|
+
type: number
|
|
177
|
+
minimum: 0
|
|
178
|
+
maximum: 1
|
|
179
|
+
rationale:
|
|
180
|
+
type: string
|
|
181
|
+
description: One paragraph, cites specific code constructs.
|
|
182
|
+
evidence_spans:
|
|
183
|
+
type: array
|
|
184
|
+
items:
|
|
185
|
+
type: object
|
|
186
|
+
required: [file]
|
|
187
|
+
properties:
|
|
188
|
+
file: { type: string }
|
|
189
|
+
start_line: { type: integer }
|
|
190
|
+
end_line: { type: integer }
|
|
191
|
+
note: { type: string }
|
|
192
|
+
|
|
193
|
+
minimum_context_required:
|
|
194
|
+
lines: 120
|
|
195
|
+
symbols:
|
|
196
|
+
- bind_tools
|
|
197
|
+
- bindTools
|
|
198
|
+
- create_react_agent
|
|
199
|
+
- createReactAgent
|
|
200
|
+
- AgentExecutor
|
|
201
|
+
|
|
202
|
+
related_patterns:
|
|
203
|
+
- agents.goal-decomposition-attack-surface
|
|
204
|
+
- agents.tool-call-without-authorization
|
|
205
|
+
|
|
206
|
+
repair_contract:
|
|
207
|
+
allowed:
|
|
208
|
+
- Bind a per-agent tool subset at construction time
|
|
209
|
+
- Introduce a capability-token or authorization layer between agent and tool dispatch
|
|
210
|
+
- Migrate to LangGraph state machines with per-node tools
|
|
211
|
+
forbidden:
|
|
212
|
+
- Removing tools entirely without functional replacement
|
|
213
|
+
- Centralizing tools further (the opposite direction)
|
|
214
|
+
- Disabling agent execution to "fix" the finding
|
|
215
|
+
|
|
216
|
+
validation_contract:
|
|
217
|
+
requires_tests_pass: false
|
|
218
|
+
requires_static_rules:
|
|
219
|
+
- no_shared_tool_registry_after_patch
|
|
220
|
+
|
|
221
|
+
unsafe_to_expose_fields:
|
|
222
|
+
- internal_rubric
|
|
223
|
+
- golden_transcripts
|
|
224
|
+
- exploit_templates
|
package/dist/pattern-packs/zivis-public-0.2.0/capsules/prompting/self-consistency-no-isolation.yaml
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Capsule: Self-consistency without context isolation
|
|
2
|
+
# Spec: docs/specs/PATTERN-MATCHING-LIBRARY-V1.md
|
|
3
|
+
# Plan: docs/plans/LOCAL-REPO-GRAPH-IMPLEMENTATION.md
|
|
4
|
+
# Schema: schemas/pattern-detection-card.schema.json
|
|
5
|
+
|
|
6
|
+
id: prompting.self-consistency-no-isolation
|
|
7
|
+
slug: self-consistency-no-isolation
|
|
8
|
+
version: 0.1.0
|
|
9
|
+
category: prompting
|
|
10
|
+
title: Self-Consistency Without Context Isolation
|
|
11
|
+
headline: Vote Without Independence
|
|
12
|
+
description: >
|
|
13
|
+
Detects code that aggregates multiple LLM samples (vote, consensus, ranking)
|
|
14
|
+
while every sample shares the same untrusted context. Majority voting only
|
|
15
|
+
filters random noise — when context is shared, correlated errors override
|
|
16
|
+
correctness and the vote count is mis-read as calibrated confidence.
|
|
17
|
+
|
|
18
|
+
detection_tier: public
|
|
19
|
+
scoring_tier: proprietary
|
|
20
|
+
|
|
21
|
+
applicable_languages:
|
|
22
|
+
- typescript
|
|
23
|
+
- javascript
|
|
24
|
+
- python
|
|
25
|
+
|
|
26
|
+
relevance_filter:
|
|
27
|
+
any_of:
|
|
28
|
+
- dependency: openai
|
|
29
|
+
direct_only: true
|
|
30
|
+
- dependency: "@anthropic-ai/sdk"
|
|
31
|
+
direct_only: true
|
|
32
|
+
- dependency: anthropic
|
|
33
|
+
direct_only: true
|
|
34
|
+
- dependency: "ai"
|
|
35
|
+
direct_only: true
|
|
36
|
+
- dependency: langchain
|
|
37
|
+
direct_only: true
|
|
38
|
+
- dependency: llama-index
|
|
39
|
+
direct_only: true
|
|
40
|
+
|
|
41
|
+
strategies:
|
|
42
|
+
- code: AST
|
|
43
|
+
language: python
|
|
44
|
+
expression: |
|
|
45
|
+
rule:
|
|
46
|
+
kind: for_statement
|
|
47
|
+
has:
|
|
48
|
+
kind: call
|
|
49
|
+
any:
|
|
50
|
+
- pattern: $CLIENT.chat.completions.create($$$)
|
|
51
|
+
- pattern: $CLIENT.messages.create($$$)
|
|
52
|
+
- pattern: openai.ChatCompletion.create($$$)
|
|
53
|
+
evidence_fields: [file_path, line_span, symbol]
|
|
54
|
+
confidence_weight: 0.30
|
|
55
|
+
|
|
56
|
+
- code: AST
|
|
57
|
+
language: typescript
|
|
58
|
+
expression: |
|
|
59
|
+
rule:
|
|
60
|
+
any:
|
|
61
|
+
- pattern: $_.chat.completions.create($$$)
|
|
62
|
+
- pattern: generateText($$$)
|
|
63
|
+
- pattern: streamText($$$)
|
|
64
|
+
inside:
|
|
65
|
+
stopBy: end
|
|
66
|
+
any:
|
|
67
|
+
- pattern: Promise.all($$$)
|
|
68
|
+
- pattern: $X.map($$$)
|
|
69
|
+
- kind: for_statement
|
|
70
|
+
- kind: while_statement
|
|
71
|
+
evidence_fields: [file_path, line_span, symbol]
|
|
72
|
+
confidence_weight: 0.30
|
|
73
|
+
|
|
74
|
+
- code: GRAPH
|
|
75
|
+
expression: |
|
|
76
|
+
same_messages_argument_passed_to_n_completion_calls(n >= 2)
|
|
77
|
+
AND aggregator_observed(any_of: Counter, max(set(.), key=.count), majority_vote, np.argmax, statistics.mode)
|
|
78
|
+
evidence_fields: [graph_path, file_spans, node_ids]
|
|
79
|
+
confidence_weight: 0.30
|
|
80
|
+
|
|
81
|
+
- code: META
|
|
82
|
+
expression:
|
|
83
|
+
none_of_controls:
|
|
84
|
+
- per_sample_retrieval_diversification
|
|
85
|
+
- external_validator_on_facts
|
|
86
|
+
- human_approval_gate
|
|
87
|
+
evidence_fields: [missing_controls]
|
|
88
|
+
confidence_weight: 0.25
|
|
89
|
+
|
|
90
|
+
- code: INFER
|
|
91
|
+
expression: prompt_id:prompting.self-consistency-no-isolation.identify
|
|
92
|
+
evidence_fields: [llm_rationale]
|
|
93
|
+
confidence_weight: 0.15
|
|
94
|
+
trusted: false
|
|
95
|
+
|
|
96
|
+
required_evidence_count: 2
|
|
97
|
+
|
|
98
|
+
control_negations:
|
|
99
|
+
- "presence_of(per_sample_retrieval_diversification)"
|
|
100
|
+
- "presence_of(external_validator_on_facts)"
|
|
101
|
+
- "presence_of(human_approval_gate)"
|
|
102
|
+
|
|
103
|
+
threats:
|
|
104
|
+
- title: Vote count mistaken for calibrated confidence
|
|
105
|
+
stride: [tampering, repudiation]
|
|
106
|
+
description: >
|
|
107
|
+
The application treats agreement across samples as independent confirmation,
|
|
108
|
+
when in fact every sample saw the same poisoned retrieval context or the
|
|
109
|
+
same prompt-injection payload. Downstream actions (auto-approve, send,
|
|
110
|
+
execute) fire under false high-confidence signal.
|
|
111
|
+
remediation: >
|
|
112
|
+
Diversify per-sample retrieval queries, temperatures, or seeds.
|
|
113
|
+
Add an external fact-validator. For high-impact outputs, require a human
|
|
114
|
+
approval gate. Never expose vote count as a confidence number to users
|
|
115
|
+
without disclosing the independence assumption.
|
|
116
|
+
- title: Correlated injection amplification
|
|
117
|
+
stride: [tampering]
|
|
118
|
+
description: >
|
|
119
|
+
A single prompt-injection payload in shared retrieved context biases all N
|
|
120
|
+
samples identically; majority vote then ratifies the attack rather than
|
|
121
|
+
filtering it.
|
|
122
|
+
remediation: >
|
|
123
|
+
Per-sample context isolation. Run independent retrieval (different queries
|
|
124
|
+
or different stores) per sample so an injection only contaminates a subset.
|
|
125
|
+
|
|
126
|
+
fixtures:
|
|
127
|
+
positive:
|
|
128
|
+
- "fixtures/positive/self-consistency-no-isolation/openai_loop_vote_python"
|
|
129
|
+
- "fixtures/positive/self-consistency-no-isolation/anthropic_promise_all_typescript"
|
|
130
|
+
negative:
|
|
131
|
+
- "fixtures/negative/self-consistency-no-isolation/per_sample_diverse_retrieval"
|
|
132
|
+
- "fixtures/negative/self-consistency-no-isolation/single_completion_no_aggregation"
|
|
133
|
+
|
|
134
|
+
lifecycle: draft
|
|
135
|
+
owner: platform-threat-modeling
|
|
136
|
+
tags:
|
|
137
|
+
- prompting
|
|
138
|
+
- self-consistency
|
|
139
|
+
- context-isolation
|
|
140
|
+
- prompt-injection-amplification
|
|
141
|
+
|
|
142
|
+
sensitivity_level: customer_safe
|
|
143
|
+
allowed_execution_modes:
|
|
144
|
+
- local_only
|
|
145
|
+
- local_model
|
|
146
|
+
- hybrid_cloud_eval
|
|
147
|
+
|
|
148
|
+
safe_summary: >
|
|
149
|
+
Multiple LLM completions are aggregated by voting or consensus, but every
|
|
150
|
+
sample sees the same retrieved context or the same user message. Independence
|
|
151
|
+
is assumed and not enforced. A single prompt-injection or a single
|
|
152
|
+
hallucinated retrieval contaminates the vote uniformly; majority count is
|
|
153
|
+
mistaken for calibrated confidence and downstream actions fire under
|
|
154
|
+
false certainty.
|
|
155
|
+
|
|
156
|
+
detect_when: >
|
|
157
|
+
Two or more LLM completions for the same user task are merged by voting,
|
|
158
|
+
consensus, or similarity scoring without independent context isolation —
|
|
159
|
+
no per-sample retrieval diversification, no external validator, no human gate.
|
|
160
|
+
|
|
161
|
+
risk_hints:
|
|
162
|
+
- Shared retrieval/injection in the user message affects every sample the same way
|
|
163
|
+
- Majority vote amplifies systematic bias rather than filtering noise
|
|
164
|
+
- Vote count surfaced as a confidence number is a UX lie when context is shared
|
|
165
|
+
- Async Promise.all with the same messages array is the most common JS shape
|
|
166
|
+
|
|
167
|
+
architectural_signals:
|
|
168
|
+
- Same `messages` argument passed to N completion calls inside a loop or Promise.all
|
|
169
|
+
- majority_vote, Counter, np.argmax, statistics.mode applied to the result array
|
|
170
|
+
- Single retrieval call upstream feeding all samples
|
|
171
|
+
|
|
172
|
+
model_task_prompt_ref: prompts/prompting/self-consistency-no-isolation/identify.md
|
|
173
|
+
|
|
174
|
+
expected_output_schema:
|
|
175
|
+
type: object
|
|
176
|
+
required: [independence_assessment, confidence_claim_risk, evidence_spans]
|
|
177
|
+
properties:
|
|
178
|
+
independence_assessment:
|
|
179
|
+
type: string
|
|
180
|
+
enum: [shared_context, isolated_context, unclear]
|
|
181
|
+
confidence_claim_risk:
|
|
182
|
+
type: string
|
|
183
|
+
enum: [high, medium, low]
|
|
184
|
+
description: How dangerous is the confidence claim users see, given the (lack of) independence
|
|
185
|
+
rationale:
|
|
186
|
+
type: string
|
|
187
|
+
recommended_controls:
|
|
188
|
+
type: array
|
|
189
|
+
items:
|
|
190
|
+
type: string
|
|
191
|
+
enum: [per_sample_retrieval_diversification, external_validator, human_approval, temperature_variation, seed_variation]
|
|
192
|
+
evidence_spans:
|
|
193
|
+
type: array
|
|
194
|
+
items:
|
|
195
|
+
type: object
|
|
196
|
+
required: [file]
|
|
197
|
+
properties:
|
|
198
|
+
file: { type: string }
|
|
199
|
+
start_line: { type: integer }
|
|
200
|
+
end_line: { type: integer }
|
|
201
|
+
note: { type: string }
|
|
202
|
+
|
|
203
|
+
minimum_context_required:
|
|
204
|
+
lines: 80
|
|
205
|
+
symbols:
|
|
206
|
+
- chat.completions.create
|
|
207
|
+
- messages.create
|
|
208
|
+
- generateText
|
|
209
|
+
- Counter
|
|
210
|
+
- majority
|
|
211
|
+
- np.argmax
|
|
212
|
+
|
|
213
|
+
related_patterns:
|
|
214
|
+
- prompting.chain-of-thought-leakage
|
|
215
|
+
- retrieval.shared-context-poisoning
|
|
216
|
+
|
|
217
|
+
repair_contract:
|
|
218
|
+
allowed:
|
|
219
|
+
- Diversify retrieval queries per sample
|
|
220
|
+
- Vary temperature or seed across samples
|
|
221
|
+
- Add an external fact validator on aggregated output
|
|
222
|
+
- Add a human approval gate before high-impact actions
|
|
223
|
+
forbidden:
|
|
224
|
+
- Silently treating vote count as calibrated confidence
|
|
225
|
+
- Removing the aggregation entirely for performance reasons (does not address the architectural issue)
|
|
226
|
+
- Adding more samples without addressing context independence
|
|
227
|
+
|
|
228
|
+
validation_contract:
|
|
229
|
+
requires_tests_pass: false
|
|
230
|
+
requires_static_rules:
|
|
231
|
+
- aggregator_remains_present_after_patch
|
|
232
|
+
- independence_mechanism_introduced
|
|
233
|
+
|
|
234
|
+
unsafe_to_expose_fields:
|
|
235
|
+
- internal_rubric
|
|
236
|
+
- golden_transcripts
|
|
237
|
+
- exploit_templates
|