@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.
Files changed (34) hide show
  1. package/dist/lib/inspect-cache.d.ts +32 -0
  2. package/dist/lib/inspect-cache.js +99 -0
  3. package/dist/lib/inspect-cache.js.map +1 -0
  4. package/dist/pattern-packs/zivis-public-0.1.0/manifest.json +1 -1
  5. package/dist/pattern-packs/zivis-public-0.2.0/capsules/agents/privilege-separation.yaml +224 -0
  6. package/dist/pattern-packs/zivis-public-0.2.0/capsules/prompting/self-consistency-no-isolation.yaml +237 -0
  7. package/dist/pattern-packs/zivis-public-0.2.0/capsules/retrieval/context-injection-no-validator.yaml +348 -0
  8. package/dist/pattern-packs/zivis-public-0.2.0/capsules/security/late-org-filter.yaml +266 -0
  9. package/dist/pattern-packs/zivis-public-0.2.0/controls/external_validator_on_facts.yaml +37 -0
  10. package/dist/pattern-packs/zivis-public-0.2.0/controls/fact_validator_present.yaml +68 -0
  11. package/dist/pattern-packs/zivis-public-0.2.0/controls/human_approval_gate.yaml +44 -0
  12. package/dist/pattern-packs/zivis-public-0.2.0/controls/langgraph_state_machine_with_per_node_tools.yaml +35 -0
  13. package/dist/pattern-packs/zivis-public-0.2.0/controls/per_sample_retrieval_diversification.yaml +38 -0
  14. package/dist/pattern-packs/zivis-public-0.2.0/controls/prisma_extension_or_middleware_attaching_org_filter.yaml +31 -0
  15. package/dist/pattern-packs/zivis-public-0.2.0/controls/row_level_security_policy.yaml +37 -0
  16. package/dist/pattern-packs/zivis-public-0.2.0/controls/source_attribution_present.yaml +58 -0
  17. package/dist/pattern-packs/zivis-public-0.2.0/manifest.json +167 -0
  18. package/dist/pattern-packs/zivis-public-0.2.0/prompts/agents/privilege-separation/identify.md +54 -0
  19. package/dist/pattern-packs/zivis-public-0.2.0/prompts/prompting/self-consistency-no-isolation/identify.md +60 -0
  20. package/dist/pattern-packs/zivis-public-0.2.0/prompts/retrieval/context-injection-no-validator/identify.md +109 -0
  21. package/dist/pattern-packs/zivis-public-0.2.0/prompts/security/late-org-filter/identify.md +78 -0
  22. package/dist/tools/check-project.d.ts +1 -1
  23. package/dist/tools/check-project.js +2 -4
  24. package/dist/tools/check-project.js.map +1 -1
  25. package/dist/tools/get-started.d.ts +1 -1
  26. package/dist/tools/get-started.js +38 -73
  27. package/dist/tools/get-started.js.map +1 -1
  28. package/dist/tools/inspect.d.ts +1 -1
  29. package/dist/tools/inspect.js +2 -0
  30. package/dist/tools/inspect.js.map +1 -1
  31. package/dist/tools/threat-guide.d.ts +3 -1
  32. package/dist/tools/threat-guide.js +101 -9
  33. package/dist/tools/threat-guide.js.map +1 -1
  34. 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:24:59.867Z",
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
@@ -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