@zivis/mcp 0.1.8 → 0.1.11
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/LICENSE +201 -0
- package/README.md +19 -0
- package/dist/api-client.d.ts +2 -24
- package/dist/api-client.js +32 -52
- package/dist/auth/index.d.ts +1 -22
- package/dist/auth/index.js +17 -38
- package/dist/auth/jwt.d.ts +0 -16
- package/dist/auth/jwt.js +0 -13
- package/dist/auth/keychain.d.ts +6 -32
- package/dist/auth/keychain.js +83 -114
- package/dist/auth/oauth.d.ts +3 -32
- package/dist/auth/oauth.js +67 -61
- package/dist/auth/sessions.d.ts +5 -25
- package/dist/auth/sessions.js +20 -39
- package/dist/auth/token-refresh.d.ts +2 -22
- package/dist/auth/token-refresh.js +33 -36
- package/dist/cli.d.ts +1 -18
- package/dist/cli.js +30 -908
- package/dist/http-probe/capture.d.ts +51 -0
- package/dist/http-probe/capture.js +96 -0
- package/dist/http-probe/compare.d.ts +43 -0
- package/dist/http-probe/compare.js +117 -0
- package/dist/http-probe/index.d.ts +2 -0
- package/dist/http-probe/index.js +2 -0
- package/dist/index.d.ts +0 -6
- package/dist/index.js +0 -7
- package/dist/lib/inspect-cache.d.ts +10 -0
- package/dist/lib/inspect-cache.js +75 -0
- package/dist/lib/next-steps.d.ts +27 -0
- package/dist/lib/next-steps.js +21 -0
- package/dist/matcher/ast-eval.d.ts +8 -0
- package/dist/matcher/ast-eval.js +95 -0
- package/dist/matcher/finding.d.ts +55 -0
- package/dist/matcher/finding.js +1 -0
- package/dist/matcher/index.d.ts +8 -0
- package/dist/matcher/index.js +202 -0
- package/dist/matcher/inference-candidates.d.ts +26 -0
- package/dist/matcher/inference-candidates.js +81 -0
- package/dist/matcher/walker.d.ts +7 -0
- package/dist/matcher/walker.js +71 -0
- package/dist/pattern-pack/index.d.ts +125 -0
- package/dist/pattern-pack/index.js +189 -0
- 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/project-binding.d.ts +2 -19
- package/dist/project-binding.js +38 -21
- package/dist/prompts/audit-dependencies.d.ts +2 -0
- package/dist/prompts/audit-dependencies.js +47 -0
- package/dist/prompts/getting-started.d.ts +2 -0
- package/dist/prompts/getting-started.js +37 -0
- package/dist/prompts/index.d.ts +2 -0
- package/dist/prompts/index.js +6 -0
- package/dist/redact.d.ts +30 -0
- package/dist/redact.js +164 -0
- package/dist/resolve-application-id.d.ts +0 -4
- package/dist/resolve-application-id.js +0 -5
- package/dist/sanitize.d.ts +1 -21
- package/dist/sanitize.js +7 -41
- package/dist/scanners/index.d.ts +3 -0
- package/dist/scanners/index.js +2 -0
- package/dist/scanners/normalize.d.ts +6 -0
- package/dist/scanners/normalize.js +181 -0
- package/dist/scanners/run.d.ts +27 -0
- package/dist/scanners/run.js +142 -0
- package/dist/scanners/types.d.ts +40 -0
- package/dist/scanners/types.js +1 -0
- package/dist/server.d.ts +1 -13
- package/dist/server.js +142 -265
- package/dist/tools/artifacts.d.ts +76 -0
- package/dist/tools/artifacts.js +178 -0
- package/dist/tools/check-project.d.ts +5 -18
- package/dist/tools/check-project.js +122 -110
- package/dist/tools/check-repo-trust.d.ts +4 -19
- package/dist/tools/check-repo-trust.js +56 -47
- package/dist/tools/create-diagram.d.ts +0 -5
- package/dist/tools/create-diagram.js +0 -10
- package/dist/tools/create-document.d.ts +0 -5
- package/dist/tools/create-document.js +3 -9
- package/dist/tools/create-finding.d.ts +85 -0
- package/dist/tools/create-finding.js +136 -0
- package/dist/tools/delete-finding.d.ts +19 -0
- package/dist/tools/delete-finding.js +36 -0
- package/dist/tools/devx-run.d.ts +88 -0
- package/dist/tools/devx-run.js +175 -0
- package/dist/tools/discover-local-infra.d.ts +0 -10
- package/dist/tools/discover-local-infra.js +3 -73
- package/dist/tools/enterprise/get-org-zat.d.ts +0 -5
- package/dist/tools/enterprise/get-org-zat.js +0 -6
- package/dist/tools/explain-signal-for-diff.d.ts +34 -0
- package/dist/tools/explain-signal-for-diff.js +75 -0
- package/dist/tools/generate-diagram.d.ts +0 -6
- package/dist/tools/generate-diagram.js +21 -13
- package/dist/tools/get-application-overview.d.ts +0 -5
- package/dist/tools/get-application-overview.js +0 -6
- package/dist/tools/get-application.d.ts +0 -5
- package/dist/tools/get-application.js +0 -6
- package/dist/tools/get-diagram.d.ts +0 -5
- package/dist/tools/get-diagram.js +1 -11
- package/dist/tools/get-document.d.ts +0 -5
- package/dist/tools/get-document.js +0 -6
- package/dist/tools/get-oss-zat.d.ts +0 -5
- package/dist/tools/get-oss-zat.js +0 -7
- package/dist/tools/get-signal.d.ts +24 -0
- package/dist/tools/get-signal.js +75 -0
- package/dist/tools/get-started.d.ts +18 -0
- package/dist/tools/get-started.js +470 -0
- package/dist/tools/get-trust-keys.d.ts +0 -5
- package/dist/tools/get-trust-keys.js +0 -6
- package/dist/tools/import-openapi-endpoints.d.ts +0 -5
- package/dist/tools/import-openapi-endpoints.js +0 -6
- package/dist/tools/inspect-zat.d.ts +0 -5
- package/dist/tools/inspect-zat.js +0 -19
- package/dist/tools/inspect.d.ts +21 -0
- package/dist/tools/inspect.js +222 -0
- package/dist/tools/list-applications.d.ts +1 -6
- package/dist/tools/list-applications.js +0 -6
- package/dist/tools/list-diagrams.d.ts +0 -5
- package/dist/tools/list-diagrams.js +0 -6
- package/dist/tools/list-documents.d.ts +0 -5
- package/dist/tools/list-documents.js +0 -6
- package/dist/tools/list-endpoints.d.ts +1 -6
- package/dist/tools/list-endpoints.js +0 -6
- package/dist/tools/list-signals.d.ts +37 -0
- package/dist/tools/list-signals.js +83 -0
- package/dist/tools/manage-application.d.ts +64 -0
- package/dist/tools/manage-application.js +129 -0
- package/dist/tools/manage-diagram.d.ts +0 -5
- package/dist/tools/manage-diagram.js +0 -16
- package/dist/tools/manage-endpoint-lifecycle.d.ts +80 -0
- package/dist/tools/manage-endpoint-lifecycle.js +180 -0
- package/dist/tools/open-in-ide.d.ts +2 -14
- package/dist/tools/open-in-ide.js +0 -13
- package/dist/tools/review-change.d.ts +34 -0
- package/dist/tools/review-change.js +93 -0
- package/dist/tools/security-memory.d.ts +76 -0
- package/dist/tools/security-memory.js +202 -0
- package/dist/tools/security-review.d.ts +1 -21
- package/dist/tools/security-review.js +8 -108
- package/dist/tools/threat-get-capsule.d.ts +15 -0
- package/dist/tools/threat-get-capsule.js +53 -0
- package/dist/tools/threat-get-inference-prompt.d.ts +15 -0
- package/dist/tools/threat-get-inference-prompt.js +73 -0
- package/dist/tools/threat-list-relevant-capsules.d.ts +17 -0
- package/dist/tools/threat-list-relevant-capsules.js +158 -0
- package/dist/tools/threat-run-matcher.d.ts +17 -0
- package/dist/tools/threat-run-matcher.js +145 -0
- package/dist/tools/update-document.d.ts +0 -5
- package/dist/tools/update-document.js +3 -9
- package/dist/tools/update-endpoint.d.ts +1 -6
- package/dist/tools/update-endpoint.js +0 -7
- package/dist/tools/update-finding.d.ts +61 -0
- package/dist/tools/update-finding.js +80 -0
- package/dist/tools/update-mermaid-source.d.ts +0 -13
- package/dist/tools/update-mermaid-source.js +0 -14
- package/dist/tools/verify-trust-mark.d.ts +0 -5
- package/dist/tools/verify-trust-mark.js +0 -6
- package/dist/types.d.ts +14 -14
- package/dist/types.js +22 -28
- package/package.json +41 -15
- package/dist/api-client.js.map +0 -1
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/jwt.js.map +0 -1
- package/dist/auth/keychain.js.map +0 -1
- package/dist/auth/oauth.js.map +0 -1
- package/dist/auth/sessions.js.map +0 -1
- package/dist/auth/token-refresh.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/project-binding.js.map +0 -1
- package/dist/resolve-application-id.js.map +0 -1
- package/dist/sanitize.js.map +0 -1
- package/dist/server.js.map +0 -1
- package/dist/tools/check-project.js.map +0 -1
- package/dist/tools/check-repo-trust.js.map +0 -1
- package/dist/tools/create-diagram.js.map +0 -1
- package/dist/tools/create-document.js.map +0 -1
- package/dist/tools/discover-local-infra.js.map +0 -1
- package/dist/tools/enterprise/capture-evidence.d.ts +0 -54
- package/dist/tools/enterprise/capture-evidence.js +0 -72
- package/dist/tools/enterprise/capture-evidence.js.map +0 -1
- package/dist/tools/enterprise/create-report-ticket.d.ts +0 -34
- package/dist/tools/enterprise/create-report-ticket.js +0 -44
- package/dist/tools/enterprise/create-report-ticket.js.map +0 -1
- package/dist/tools/enterprise/end-pentest-session.d.ts +0 -33
- package/dist/tools/enterprise/end-pentest-session.js +0 -42
- package/dist/tools/enterprise/end-pentest-session.js.map +0 -1
- package/dist/tools/enterprise/generate-report.d.ts +0 -55
- package/dist/tools/enterprise/generate-report.js +0 -111
- package/dist/tools/enterprise/generate-report.js.map +0 -1
- package/dist/tools/enterprise/get-org-zat.js.map +0 -1
- package/dist/tools/enterprise/get-report-status.d.ts +0 -27
- package/dist/tools/enterprise/get-report-status.js +0 -38
- package/dist/tools/enterprise/get-report-status.js.map +0 -1
- package/dist/tools/enterprise/get-threat-model.d.ts +0 -29
- package/dist/tools/enterprise/get-threat-model.js +0 -83
- package/dist/tools/enterprise/get-threat-model.js.map +0 -1
- package/dist/tools/enterprise/list-agents.d.ts +0 -42
- package/dist/tools/enterprise/list-agents.js +0 -95
- package/dist/tools/enterprise/list-agents.js.map +0 -1
- package/dist/tools/enterprise/list-report-issues.d.ts +0 -36
- package/dist/tools/enterprise/list-report-issues.js +0 -50
- package/dist/tools/enterprise/list-report-issues.js.map +0 -1
- package/dist/tools/enterprise/list-threat-models.d.ts +0 -36
- package/dist/tools/enterprise/list-threat-models.js +0 -82
- package/dist/tools/enterprise/list-threat-models.js.map +0 -1
- package/dist/tools/enterprise/manage-actor.d.ts +0 -58
- package/dist/tools/enterprise/manage-actor.js +0 -99
- package/dist/tools/enterprise/manage-actor.js.map +0 -1
- package/dist/tools/enterprise/manage-attack-scenario.d.ts +0 -70
- package/dist/tools/enterprise/manage-attack-scenario.js +0 -89
- package/dist/tools/enterprise/manage-attack-scenario.js.map +0 -1
- package/dist/tools/enterprise/manage-component.d.ts +0 -58
- package/dist/tools/enterprise/manage-component.js +0 -90
- package/dist/tools/enterprise/manage-component.js.map +0 -1
- package/dist/tools/enterprise/manage-data-asset.d.ts +0 -53
- package/dist/tools/enterprise/manage-data-asset.js +0 -90
- package/dist/tools/enterprise/manage-data-asset.js.map +0 -1
- package/dist/tools/enterprise/manage-data-flow.d.ts +0 -57
- package/dist/tools/enterprise/manage-data-flow.js +0 -98
- package/dist/tools/enterprise/manage-data-flow.js.map +0 -1
- package/dist/tools/enterprise/manage-finding.d.ts +0 -69
- package/dist/tools/enterprise/manage-finding.js +0 -106
- package/dist/tools/enterprise/manage-finding.js.map +0 -1
- package/dist/tools/enterprise/manage-security-control.d.ts +0 -59
- package/dist/tools/enterprise/manage-security-control.js +0 -96
- package/dist/tools/enterprise/manage-security-control.js.map +0 -1
- package/dist/tools/enterprise/manage-test-case.d.ts +0 -88
- package/dist/tools/enterprise/manage-test-case.js +0 -119
- package/dist/tools/enterprise/manage-test-case.js.map +0 -1
- package/dist/tools/enterprise/promote-finding-to-issue.d.ts +0 -36
- package/dist/tools/enterprise/promote-finding-to-issue.js +0 -65
- package/dist/tools/enterprise/promote-finding-to-issue.js.map +0 -1
- package/dist/tools/enterprise/publish-report-version.d.ts +0 -34
- package/dist/tools/enterprise/publish-report-version.js +0 -75
- package/dist/tools/enterprise/publish-report-version.js.map +0 -1
- package/dist/tools/enterprise/publish-test-to-library.d.ts +0 -31
- package/dist/tools/enterprise/publish-test-to-library.js +0 -40
- package/dist/tools/enterprise/publish-test-to-library.js.map +0 -1
- package/dist/tools/enterprise/record-test-result.d.ts +0 -67
- package/dist/tools/enterprise/record-test-result.js +0 -75
- package/dist/tools/enterprise/record-test-result.js.map +0 -1
- package/dist/tools/enterprise/start-pentest-session.d.ts +0 -35
- package/dist/tools/enterprise/start-pentest-session.js +0 -50
- package/dist/tools/enterprise/start-pentest-session.js.map +0 -1
- package/dist/tools/enterprise/sync-report-section.d.ts +0 -41
- package/dist/tools/enterprise/sync-report-section.js +0 -79
- package/dist/tools/enterprise/sync-report-section.js.map +0 -1
- package/dist/tools/enterprise/update-report-issue.d.ts +0 -55
- package/dist/tools/enterprise/update-report-issue.js +0 -69
- package/dist/tools/enterprise/update-report-issue.js.map +0 -1
- package/dist/tools/generate-diagram.js.map +0 -1
- package/dist/tools/get-agent-test-results.d.ts +0 -41
- package/dist/tools/get-agent-test-results.js +0 -86
- package/dist/tools/get-agent-test-results.js.map +0 -1
- package/dist/tools/get-application-overview.js.map +0 -1
- package/dist/tools/get-application.js.map +0 -1
- package/dist/tools/get-artifact-content.d.ts +0 -29
- package/dist/tools/get-artifact-content.js +0 -86
- package/dist/tools/get-artifact-content.js.map +0 -1
- package/dist/tools/get-diagram.js.map +0 -1
- package/dist/tools/get-document.js.map +0 -1
- package/dist/tools/get-finding-details.d.ts +0 -28
- package/dist/tools/get-finding-details.js +0 -43
- package/dist/tools/get-finding-details.js.map +0 -1
- package/dist/tools/get-findings.d.ts +0 -57
- package/dist/tools/get-findings.js +0 -103
- package/dist/tools/get-findings.js.map +0 -1
- package/dist/tools/get-oss-zat.js.map +0 -1
- package/dist/tools/get-recon-scan.d.ts +0 -27
- package/dist/tools/get-recon-scan.js +0 -46
- package/dist/tools/get-recon-scan.js.map +0 -1
- package/dist/tools/get-scan-output.d.ts +0 -27
- package/dist/tools/get-scan-output.js +0 -64
- package/dist/tools/get-scan-output.js.map +0 -1
- package/dist/tools/get-scenario-details.d.ts +0 -27
- package/dist/tools/get-scenario-details.js +0 -46
- package/dist/tools/get-scenario-details.js.map +0 -1
- package/dist/tools/get-test-case-results.d.ts +0 -37
- package/dist/tools/get-test-case-results.js +0 -66
- package/dist/tools/get-test-case-results.js.map +0 -1
- package/dist/tools/get-test-details.d.ts +0 -29
- package/dist/tools/get-test-details.js +0 -49
- package/dist/tools/get-test-details.js.map +0 -1
- package/dist/tools/get-trust-keys.js.map +0 -1
- package/dist/tools/import-openapi-endpoints.js.map +0 -1
- package/dist/tools/inspect-zat.js.map +0 -1
- package/dist/tools/list-agent-test-cases.d.ts +0 -41
- package/dist/tools/list-agent-test-cases.js +0 -73
- package/dist/tools/list-agent-test-cases.js.map +0 -1
- package/dist/tools/list-applications.js.map +0 -1
- package/dist/tools/list-diagrams.js.map +0 -1
- package/dist/tools/list-documents.js.map +0 -1
- package/dist/tools/list-endpoints.js.map +0 -1
- package/dist/tools/list-recon-scans.d.ts +0 -36
- package/dist/tools/list-recon-scans.js +0 -74
- package/dist/tools/list-recon-scans.js.map +0 -1
- package/dist/tools/list-scan-artifacts.d.ts +0 -41
- package/dist/tools/list-scan-artifacts.js +0 -63
- package/dist/tools/list-scan-artifacts.js.map +0 -1
- package/dist/tools/list-scans.d.ts +0 -44
- package/dist/tools/list-scans.js +0 -91
- package/dist/tools/list-scans.js.map +0 -1
- package/dist/tools/list-test-library.d.ts +0 -44
- package/dist/tools/list-test-library.js +0 -83
- package/dist/tools/list-test-library.js.map +0 -1
- package/dist/tools/list-test-scenarios.d.ts +0 -31
- package/dist/tools/list-test-scenarios.js +0 -66
- package/dist/tools/list-test-scenarios.js.map +0 -1
- package/dist/tools/local-scan.d.ts +0 -40
- package/dist/tools/local-scan.js +0 -261
- package/dist/tools/local-scan.js.map +0 -1
- package/dist/tools/manage-diagram.js.map +0 -1
- package/dist/tools/open-in-ide.js.map +0 -1
- package/dist/tools/run-test-scenario.d.ts +0 -37
- package/dist/tools/run-test-scenario.js +0 -70
- package/dist/tools/run-test-scenario.js.map +0 -1
- package/dist/tools/scan-pr.d.ts +0 -27
- package/dist/tools/scan-pr.js +0 -51
- package/dist/tools/scan-pr.js.map +0 -1
- package/dist/tools/security-review.js.map +0 -1
- package/dist/tools/setup-red-team-target.d.ts +0 -79
- package/dist/tools/setup-red-team-target.js +0 -430
- package/dist/tools/setup-red-team-target.js.map +0 -1
- package/dist/tools/triage-finding.d.ts +0 -47
- package/dist/tools/triage-finding.js +0 -116
- package/dist/tools/triage-finding.js.map +0 -1
- package/dist/tools/update-document.js.map +0 -1
- package/dist/tools/update-endpoint.js.map +0 -1
- package/dist/tools/update-mermaid-source.js.map +0 -1
- package/dist/tools/verify-trust-mark.js.map +0 -1
- package/dist/types.js.map +0 -1
package/dist/pattern-packs/zivis-public-0.2.0/capsules/retrieval/context-injection-no-validator.yaml
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# Capsule: RAG context injection — retrieved content reaches the model without validation
|
|
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: retrieval.context-injection-no-validator
|
|
7
|
+
slug: context-injection-no-validator
|
|
8
|
+
version: 0.1.0
|
|
9
|
+
category: retrieval
|
|
10
|
+
title: Retrieval Context Injection — No Validator Between Retriever and Model
|
|
11
|
+
headline: Retrieved Content Flows Into the LLM With Nothing Inspecting It First
|
|
12
|
+
|
|
13
|
+
description: >
|
|
14
|
+
Detects retrieval-augmented generation (RAG) flows where the output of a
|
|
15
|
+
retriever (vector store, search index, document loader) is passed directly
|
|
16
|
+
into the model call with no fact-validator, source-attribution check, or
|
|
17
|
+
content sanitizer in between. Retrieval becomes a transparent injection
|
|
18
|
+
channel: anyone who can write to the knowledge base — directly, via a
|
|
19
|
+
scraping job, or via a tool the agent itself calls — can steer the model.
|
|
20
|
+
|
|
21
|
+
detection_tier: public
|
|
22
|
+
scoring_tier: proprietary
|
|
23
|
+
|
|
24
|
+
applicable_languages:
|
|
25
|
+
- typescript
|
|
26
|
+
- javascript
|
|
27
|
+
- python
|
|
28
|
+
|
|
29
|
+
relevance_filter:
|
|
30
|
+
any_of:
|
|
31
|
+
# LangChain / LlamaIndex RAG stacks
|
|
32
|
+
- dependency: langchain
|
|
33
|
+
direct_only: true
|
|
34
|
+
- dependency: "@langchain/core"
|
|
35
|
+
direct_only: true
|
|
36
|
+
- dependency: "@langchain/openai"
|
|
37
|
+
direct_only: true
|
|
38
|
+
- dependency: "@langchain/anthropic"
|
|
39
|
+
direct_only: true
|
|
40
|
+
- dependency: "@langchain/community"
|
|
41
|
+
direct_only: true
|
|
42
|
+
- dependency: llamaindex
|
|
43
|
+
direct_only: true
|
|
44
|
+
- dependency: "llama-index"
|
|
45
|
+
direct_only: true
|
|
46
|
+
# Manual RAG: any LLM SDK + a vector store / retrieval client
|
|
47
|
+
- dependency: openai
|
|
48
|
+
direct_only: true
|
|
49
|
+
- dependency: "@anthropic-ai/sdk"
|
|
50
|
+
direct_only: true
|
|
51
|
+
- dependency: anthropic
|
|
52
|
+
direct_only: true
|
|
53
|
+
- dependency: "@pinecone-database/pinecone"
|
|
54
|
+
direct_only: true
|
|
55
|
+
- dependency: pinecone-client
|
|
56
|
+
direct_only: true
|
|
57
|
+
- dependency: "@qdrant/js-client-rest"
|
|
58
|
+
direct_only: true
|
|
59
|
+
- dependency: qdrant-client
|
|
60
|
+
direct_only: true
|
|
61
|
+
- dependency: weaviate-ts-client
|
|
62
|
+
direct_only: true
|
|
63
|
+
- dependency: weaviate-client
|
|
64
|
+
direct_only: true
|
|
65
|
+
- dependency: chromadb
|
|
66
|
+
direct_only: true
|
|
67
|
+
- dependency: "@elastic/elasticsearch"
|
|
68
|
+
direct_only: true
|
|
69
|
+
- dependency: elasticsearch
|
|
70
|
+
|
|
71
|
+
strategies:
|
|
72
|
+
# AST as architectural evidence (NOT a vulnerability claim) — does this
|
|
73
|
+
# codebase even have a retrieval→LLM call shape? If it doesn't, the
|
|
74
|
+
# pattern can't apply.
|
|
75
|
+
- code: AST
|
|
76
|
+
language: typescript
|
|
77
|
+
expression: |
|
|
78
|
+
rule:
|
|
79
|
+
any:
|
|
80
|
+
# LangChain idioms
|
|
81
|
+
- pattern: $RETRIEVER.invoke($$$)
|
|
82
|
+
- pattern: $RETRIEVER.getRelevantDocuments($$$)
|
|
83
|
+
- pattern: new RetrievalQAChain($$$)
|
|
84
|
+
- pattern: RetrievalQAChain.fromLLM($$$)
|
|
85
|
+
- pattern: createRetrievalChain($$$)
|
|
86
|
+
# LlamaIndex
|
|
87
|
+
- pattern: $INDEX.asQueryEngine($$$)
|
|
88
|
+
- pattern: $QUERY_ENGINE.query($$$)
|
|
89
|
+
# Manual RAG: vector-store similarity search feeding a model call
|
|
90
|
+
- pattern: $STORE.similaritySearch($$$)
|
|
91
|
+
- pattern: $STORE.similaritySearchWithScore($$$)
|
|
92
|
+
evidence_fields: [file_path, line_span, symbol]
|
|
93
|
+
confidence_weight: 0.40
|
|
94
|
+
|
|
95
|
+
- code: AST
|
|
96
|
+
language: python
|
|
97
|
+
expression: |
|
|
98
|
+
rule:
|
|
99
|
+
any:
|
|
100
|
+
# LangChain
|
|
101
|
+
- pattern: $RETRIEVER.invoke($$$)
|
|
102
|
+
- pattern: $RETRIEVER.get_relevant_documents($$$)
|
|
103
|
+
- pattern: RetrievalQA.from_chain_type($$$)
|
|
104
|
+
- pattern: create_retrieval_chain($$$)
|
|
105
|
+
# LlamaIndex
|
|
106
|
+
- pattern: $INDEX.as_query_engine($$$)
|
|
107
|
+
- pattern: $QUERY_ENGINE.query($$$)
|
|
108
|
+
# Manual RAG: vector-store similarity search
|
|
109
|
+
- pattern: $STORE.similarity_search($$$)
|
|
110
|
+
- pattern: $STORE.similarity_search_with_score($$$)
|
|
111
|
+
evidence_fields: [file_path, line_span, symbol]
|
|
112
|
+
confidence_weight: 0.40
|
|
113
|
+
|
|
114
|
+
# GRAPH strategy — forward-compatible. Matcher v1 doesn't evaluate
|
|
115
|
+
# graph predicates yet (per Phase 3 v1 in LOCAL-REPO-GRAPH-IMPLEMENTATION),
|
|
116
|
+
# but writing it now means the card "lights up" when the matcher catches
|
|
117
|
+
# up, without re-authoring.
|
|
118
|
+
- code: GRAPH
|
|
119
|
+
expression: |
|
|
120
|
+
retrieval_node_returns_documents
|
|
121
|
+
AND llm_call_node_consumes_documents
|
|
122
|
+
AND NO_NODE_BETWEEN(retrieval_node, llm_call_node) matches
|
|
123
|
+
(validator | fact_checker | source_attribution | content_sanitizer)
|
|
124
|
+
evidence_fields: [graph_path, file_spans, node_ids]
|
|
125
|
+
confidence_weight: 0.30
|
|
126
|
+
|
|
127
|
+
# META — does the codebase declare any of the controls that would make
|
|
128
|
+
# this pattern safe? Absence is what we care about.
|
|
129
|
+
- code: META
|
|
130
|
+
expression:
|
|
131
|
+
none_of_controls:
|
|
132
|
+
- fact_validator_present
|
|
133
|
+
- source_attribution_present
|
|
134
|
+
evidence_fields: [missing_controls]
|
|
135
|
+
confidence_weight: 0.30
|
|
136
|
+
|
|
137
|
+
# INFER — the host LLM (Claude in the IDE) gets the actual source spans
|
|
138
|
+
# and decides whether what looks like RAG truly has no validator. This is
|
|
139
|
+
# the lane we want most matches to land in: ZIVIS surfaces the candidate,
|
|
140
|
+
# the frontier model validates against the code.
|
|
141
|
+
- code: INFER
|
|
142
|
+
expression: prompt_id:retrieval.context-injection-no-validator.identify
|
|
143
|
+
evidence_fields: [llm_rationale]
|
|
144
|
+
confidence_weight: 0.20
|
|
145
|
+
trusted: false
|
|
146
|
+
|
|
147
|
+
# Two deterministic strategies must fire for a trusted-lane match. AST
|
|
148
|
+
# alone won't promote — it's just "there is RAG-shaped code." AST + META
|
|
149
|
+
# (controls absent) WILL promote. GRAPH lights up later for free.
|
|
150
|
+
required_evidence_count: 2
|
|
151
|
+
|
|
152
|
+
control_negations:
|
|
153
|
+
- "presence_of(fact_validator_present)"
|
|
154
|
+
- "presence_of(source_attribution_present)"
|
|
155
|
+
|
|
156
|
+
threats:
|
|
157
|
+
- title: Indirect prompt injection via retrieved corpus
|
|
158
|
+
stride: [tampering, elevation_of_privilege]
|
|
159
|
+
description: >
|
|
160
|
+
An attacker who can write to the retrieval corpus — a public web page
|
|
161
|
+
indexed by a scraper, a Confluence space synced into the vector store,
|
|
162
|
+
a customer-uploaded document, or a tool the agent uses to fetch
|
|
163
|
+
content — embeds instructions that the model treats as authoritative
|
|
164
|
+
because they arrive inside the "trusted" retrieval channel. The model
|
|
165
|
+
executes those instructions or leaks data based on them. With no
|
|
166
|
+
validator inspecting the retrieved text, the system has no defense
|
|
167
|
+
between the corpus and the model.
|
|
168
|
+
remediation: >
|
|
169
|
+
Insert a validation step between retriever and model that (a) checks
|
|
170
|
+
retrieved content for prompt-injection patterns, (b) requires
|
|
171
|
+
source-attribution metadata before content can influence answers, and
|
|
172
|
+
(c) rate-limits or content-flags low-trust sources. Patterns like
|
|
173
|
+
guardrails-ai, NeMo Guardrails, Lakera Guard, or a custom
|
|
174
|
+
`validateRetrievedContent()` boundary are all valid; the requirement
|
|
175
|
+
is that *some* boundary exists between corpus and model.
|
|
176
|
+
|
|
177
|
+
- title: Sensitive-data exfiltration via crafted retrieval queries
|
|
178
|
+
stride: [information_disclosure]
|
|
179
|
+
description: >
|
|
180
|
+
An attacker who can influence the retrieval query — directly or via a
|
|
181
|
+
preceding LLM step that re-writes the query — pulls documents the
|
|
182
|
+
requesting user should not see (other tenants' data, internal-only
|
|
183
|
+
pages indexed by mistake, PII-laden support tickets). The model then
|
|
184
|
+
surfaces that content in the response. Without source-attribution and
|
|
185
|
+
access checks at the retrieval boundary, the model becomes an
|
|
186
|
+
authorization bypass.
|
|
187
|
+
remediation: >
|
|
188
|
+
Enforce access control at the retrieval layer, not just at the API
|
|
189
|
+
layer. Tag every indexed document with the access scope it inherits
|
|
190
|
+
from its source system, and filter retrieval by the requesting
|
|
191
|
+
user's scope before content reaches the model. Treat the retriever
|
|
192
|
+
as a privileged data path — same scrutiny as any other query.
|
|
193
|
+
|
|
194
|
+
- title: Long-running drift from poisoned corpus
|
|
195
|
+
stride: [tampering, repudiation]
|
|
196
|
+
description: >
|
|
197
|
+
A single poisoned document indexed months ago continues to surface in
|
|
198
|
+
relevant queries indefinitely. The model's behavior drifts in ways
|
|
199
|
+
that are hard to attribute to a specific bug because the cause is in
|
|
200
|
+
the corpus, not the code. With no audit log of which retrieved
|
|
201
|
+
documents influenced which responses, post-incident investigation
|
|
202
|
+
cannot trace bad behavior back to its source.
|
|
203
|
+
remediation: >
|
|
204
|
+
Log every retrieval — query, returned document IDs, document hashes,
|
|
205
|
+
source attribution — alongside the LLM call it fed. Periodically
|
|
206
|
+
revalidate corpus contents against current trust assumptions
|
|
207
|
+
(re-classify documents from sources that have been compromised or
|
|
208
|
+
whose access policy changed).
|
|
209
|
+
|
|
210
|
+
fixtures:
|
|
211
|
+
positive:
|
|
212
|
+
- "fixtures/positive/context-injection-no-validator/langchain_rag_chain_no_validator_typescript"
|
|
213
|
+
- "fixtures/positive/context-injection-no-validator/llamaindex_query_engine_no_validator_python"
|
|
214
|
+
- "fixtures/positive/context-injection-no-validator/manual_pinecone_to_openai_typescript"
|
|
215
|
+
negative:
|
|
216
|
+
- "fixtures/negative/context-injection-no-validator/guardrails_validator_present_python"
|
|
217
|
+
- "fixtures/negative/context-injection-no-validator/source_attribution_check_typescript"
|
|
218
|
+
|
|
219
|
+
lifecycle: draft
|
|
220
|
+
owner: platform-threat-modeling
|
|
221
|
+
tags:
|
|
222
|
+
- retrieval
|
|
223
|
+
- rag
|
|
224
|
+
- prompt-injection
|
|
225
|
+
- context-injection
|
|
226
|
+
- corpus-poisoning
|
|
227
|
+
- agentic-ai
|
|
228
|
+
|
|
229
|
+
sensitivity_level: customer_safe
|
|
230
|
+
allowed_execution_modes:
|
|
231
|
+
- local_only
|
|
232
|
+
- local_model
|
|
233
|
+
- hybrid_cloud_eval
|
|
234
|
+
|
|
235
|
+
safe_summary: >
|
|
236
|
+
The codebase has a retrieval-augmented generation (RAG) shape — content
|
|
237
|
+
is pulled from a vector store, search index, or document loader and
|
|
238
|
+
passed to the LLM — but there's no validator, sanitizer, or
|
|
239
|
+
source-attribution check between the retriever and the model. Anyone
|
|
240
|
+
who can write to the retrieval corpus can steer the model: an indexed
|
|
241
|
+
web page, a synced Confluence space, a customer-uploaded document, or
|
|
242
|
+
a tool the agent itself fetches from. The fix is to put a boundary
|
|
243
|
+
between corpus and model — a guardrails library, a custom validator,
|
|
244
|
+
or per-document source-attribution that the model is required to
|
|
245
|
+
surface in its answers.
|
|
246
|
+
|
|
247
|
+
detect_when: >
|
|
248
|
+
A retriever call (`retriever.invoke`, `similaritySearch`, query engine,
|
|
249
|
+
retrieval chain) is followed by an LLM call within the same handler or
|
|
250
|
+
function, and the path between them contains no recognizable validator
|
|
251
|
+
or source-attribution step. Most common in chat / Q&A / "ask the docs"
|
|
252
|
+
applications.
|
|
253
|
+
|
|
254
|
+
risk_hints:
|
|
255
|
+
- "Retrieval is a privileged data path — anything in the corpus is implicitly trusted by the model"
|
|
256
|
+
- "Prompt injection embedded in a web page or shared doc survives indefinitely until the document is re-indexed"
|
|
257
|
+
- "Query rewriting (HyDE, multi-query) compounds the problem — the rewritten query is itself an LLM output"
|
|
258
|
+
- "Access control at the API layer doesn't help if the retriever pulls from a corpus indexed under different rules"
|
|
259
|
+
- "Logging the LLM input/output without logging the retrieved documents leaves the actual injection invisible"
|
|
260
|
+
|
|
261
|
+
architectural_signals:
|
|
262
|
+
- "Retriever / vector store / search index call returns documents"
|
|
263
|
+
- "Documents are concatenated into a system or user prompt slot"
|
|
264
|
+
- "No intermediate function whose name suggests validation, sanitization, or fact-checking"
|
|
265
|
+
- "No source-attribution requirement in the prompt template"
|
|
266
|
+
|
|
267
|
+
model_task_prompt_ref: prompts/retrieval/context-injection-no-validator/identify.md
|
|
268
|
+
|
|
269
|
+
expected_output_schema:
|
|
270
|
+
type: object
|
|
271
|
+
required: [verdict, validator_location, evidence_spans]
|
|
272
|
+
properties:
|
|
273
|
+
verdict:
|
|
274
|
+
type: string
|
|
275
|
+
enum: [no_validator_present, validator_present, partial_validator, unclear]
|
|
276
|
+
validator_location:
|
|
277
|
+
type: string
|
|
278
|
+
enum: [pre_retrieval, between_retriever_and_model, post_model, none, multiple]
|
|
279
|
+
rationale:
|
|
280
|
+
type: string
|
|
281
|
+
corpus_sources:
|
|
282
|
+
type: array
|
|
283
|
+
description: "Best-effort enumeration of where the retrieved content comes from"
|
|
284
|
+
items:
|
|
285
|
+
type: string
|
|
286
|
+
blast_radius:
|
|
287
|
+
type: string
|
|
288
|
+
enum: [single_endpoint, route_family, service_wide, agent_wide, unclear]
|
|
289
|
+
recommended_controls:
|
|
290
|
+
type: array
|
|
291
|
+
items:
|
|
292
|
+
type: string
|
|
293
|
+
enum:
|
|
294
|
+
- guardrails_library
|
|
295
|
+
- custom_content_validator
|
|
296
|
+
- source_attribution_in_prompt
|
|
297
|
+
- per_document_access_control
|
|
298
|
+
- retrieval_audit_log
|
|
299
|
+
- none
|
|
300
|
+
evidence_spans:
|
|
301
|
+
type: array
|
|
302
|
+
items:
|
|
303
|
+
type: object
|
|
304
|
+
required: [file]
|
|
305
|
+
properties:
|
|
306
|
+
file: { type: string }
|
|
307
|
+
start_line: { type: integer }
|
|
308
|
+
end_line: { type: integer }
|
|
309
|
+
note: { type: string }
|
|
310
|
+
|
|
311
|
+
minimum_context_required:
|
|
312
|
+
lines: 120
|
|
313
|
+
symbols:
|
|
314
|
+
- retriever
|
|
315
|
+
- invoke
|
|
316
|
+
- similarity_search
|
|
317
|
+
- similaritySearch
|
|
318
|
+
- query_engine
|
|
319
|
+
- asQueryEngine
|
|
320
|
+
- RetrievalQA
|
|
321
|
+
- create_retrieval_chain
|
|
322
|
+
|
|
323
|
+
related_patterns:
|
|
324
|
+
- prompting.user-input-in-system-prompt
|
|
325
|
+
- agents.tool-dispatch-from-llm-output
|
|
326
|
+
|
|
327
|
+
repair_contract:
|
|
328
|
+
allowed:
|
|
329
|
+
- "Insert a validator step between retriever and model call"
|
|
330
|
+
- "Add source-attribution requirement to the prompt template"
|
|
331
|
+
- "Adopt guardrails-ai / NeMo Guardrails / Lakera Guard or equivalent"
|
|
332
|
+
- "Implement per-document access control at the retrieval boundary"
|
|
333
|
+
- "Add structured logging of retrieved-document IDs alongside LLM calls"
|
|
334
|
+
forbidden:
|
|
335
|
+
- "Removing retrieval entirely without first replacing the answering path"
|
|
336
|
+
- "Validating only LLM output instead of retrieved content (treats the wrong stage)"
|
|
337
|
+
- "Hand-rolling regex injection filters as the sole defense (well-known to be bypassable)"
|
|
338
|
+
|
|
339
|
+
validation_contract:
|
|
340
|
+
requires_tests_pass: false
|
|
341
|
+
requires_static_rules:
|
|
342
|
+
- validator_node_present_between_retrieval_and_llm
|
|
343
|
+
- retrieval_audit_log_present
|
|
344
|
+
|
|
345
|
+
unsafe_to_expose_fields:
|
|
346
|
+
- internal_rubric
|
|
347
|
+
- golden_transcripts
|
|
348
|
+
- exploit_templates
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# Capsule: Late org-filter — cross-tenant leak via presentation-time scoping
|
|
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: security.late-org-filter
|
|
7
|
+
slug: late-org-filter
|
|
8
|
+
version: 0.1.0
|
|
9
|
+
category: security
|
|
10
|
+
title: Late Org Filter
|
|
11
|
+
headline: Tenant Scoping Applied After the Query, Not Inside It
|
|
12
|
+
description: >
|
|
13
|
+
Detects multi-tenant code paths where org/tenant filtering happens at the
|
|
14
|
+
presentation layer (response loop, stream emit, response shaping) instead
|
|
15
|
+
of inside the data query. Any race, error path, or partial response that
|
|
16
|
+
reaches the wire before the filter fires leaks rows across tenants.
|
|
17
|
+
|
|
18
|
+
detection_tier: public
|
|
19
|
+
scoring_tier: proprietary
|
|
20
|
+
|
|
21
|
+
applicable_languages:
|
|
22
|
+
- typescript
|
|
23
|
+
- javascript
|
|
24
|
+
- python
|
|
25
|
+
- go
|
|
26
|
+
|
|
27
|
+
relevance_filter:
|
|
28
|
+
any_of:
|
|
29
|
+
- dependency: express
|
|
30
|
+
direct_only: true
|
|
31
|
+
- dependency: fastify
|
|
32
|
+
direct_only: true
|
|
33
|
+
- dependency: hono
|
|
34
|
+
direct_only: true
|
|
35
|
+
- dependency: "@nestjs/core"
|
|
36
|
+
direct_only: true
|
|
37
|
+
- dependency: fastapi
|
|
38
|
+
direct_only: true
|
|
39
|
+
- dependency: flask
|
|
40
|
+
direct_only: true
|
|
41
|
+
- dependency: "django"
|
|
42
|
+
direct_only: true
|
|
43
|
+
- dependency: "github.com/gin-gonic/gin"
|
|
44
|
+
direct_only: true
|
|
45
|
+
- dependency: "github.com/labstack/echo/v4"
|
|
46
|
+
direct_only: true
|
|
47
|
+
none_of:
|
|
48
|
+
# If the project clearly isn't multi-tenant, skip — no benefit, lots of noise.
|
|
49
|
+
- evidence: "single_tenant_only_marker"
|
|
50
|
+
|
|
51
|
+
strategies:
|
|
52
|
+
- code: AST
|
|
53
|
+
language: typescript
|
|
54
|
+
expression: |
|
|
55
|
+
rule:
|
|
56
|
+
any:
|
|
57
|
+
# Filter applied to a query result then sent — classic shape.
|
|
58
|
+
- pattern: $ROWS.filter($_ => $_.orgId === $$$)
|
|
59
|
+
- pattern: $ROWS.filter($_ => $_.org_id === $$$)
|
|
60
|
+
- pattern: $ROWS.filter($_ => $_.tenantId === $$$)
|
|
61
|
+
# Per-row tenant guard inside a loop — the SSE / iterator variant.
|
|
62
|
+
- pattern: |
|
|
63
|
+
if ($R.orgId === $$$) {
|
|
64
|
+
$$$
|
|
65
|
+
}
|
|
66
|
+
- pattern: |
|
|
67
|
+
if ($R.org_id === $$$) {
|
|
68
|
+
$$$
|
|
69
|
+
}
|
|
70
|
+
evidence_fields: [file_path, line_span, symbol]
|
|
71
|
+
confidence_weight: 0.35
|
|
72
|
+
|
|
73
|
+
- code: AST
|
|
74
|
+
language: python
|
|
75
|
+
expression: |
|
|
76
|
+
rule:
|
|
77
|
+
any:
|
|
78
|
+
- pattern: |
|
|
79
|
+
rows = $QUERY($$$)
|
|
80
|
+
filtered = [r for r in rows if r.org_id == $REQ.org_id]
|
|
81
|
+
- pattern: |
|
|
82
|
+
for row in $QUERY($$$):
|
|
83
|
+
if row.org_id == request.org_id:
|
|
84
|
+
yield row
|
|
85
|
+
evidence_fields: [file_path, line_span, symbol]
|
|
86
|
+
confidence_weight: 0.35
|
|
87
|
+
|
|
88
|
+
- code: GRAPH
|
|
89
|
+
expression: |
|
|
90
|
+
query_call_returns_rows
|
|
91
|
+
AND filter_predicate_references(req_org_id OR session_org_id)
|
|
92
|
+
AND filter_executes_AFTER(query_call) IN response_handler_or_stream_emit
|
|
93
|
+
AND query_call.where_clause DOES_NOT include org_id
|
|
94
|
+
evidence_fields: [graph_path, file_spans, node_ids]
|
|
95
|
+
confidence_weight: 0.30
|
|
96
|
+
|
|
97
|
+
- code: META
|
|
98
|
+
expression:
|
|
99
|
+
none_of_controls:
|
|
100
|
+
- row_level_security_policy
|
|
101
|
+
- prisma_extension_or_middleware_attaching_org_filter
|
|
102
|
+
evidence_fields: [missing_controls]
|
|
103
|
+
confidence_weight: 0.20
|
|
104
|
+
|
|
105
|
+
- code: INFER
|
|
106
|
+
expression: prompt_id:security.late-org-filter.identify
|
|
107
|
+
evidence_fields: [llm_rationale]
|
|
108
|
+
confidence_weight: 0.15
|
|
109
|
+
trusted: false
|
|
110
|
+
|
|
111
|
+
required_evidence_count: 2
|
|
112
|
+
|
|
113
|
+
control_negations:
|
|
114
|
+
- "presence_of(row_level_security_policy)"
|
|
115
|
+
- "presence_of(query_helper_with_implicit_org_scope)"
|
|
116
|
+
- "presence_of(prisma_extension_or_middleware_attaching_org_filter)"
|
|
117
|
+
|
|
118
|
+
threats:
|
|
119
|
+
- title: Cross-tenant data leak via SSE / stream pre-filter window
|
|
120
|
+
stride: [information_disclosure, elevation_of_privilege]
|
|
121
|
+
description: >
|
|
122
|
+
In a streaming or async-iterator response, the underlying query returns
|
|
123
|
+
rows from all tenants and the per-emit filter rejects mismatches. A
|
|
124
|
+
brief window where rows are buffered, logged, traced, or partially
|
|
125
|
+
rendered before the filter executes can leak data across tenants. This
|
|
126
|
+
is the SSE / queue/stream variant of the pattern.
|
|
127
|
+
remediation: >
|
|
128
|
+
Push the org/tenant predicate into the data layer — RLS, query helpers,
|
|
129
|
+
ORM middleware. Make every query mandatorily org-scoped at the boundary;
|
|
130
|
+
treat presentation-layer filters as defense in depth, never the primary
|
|
131
|
+
barrier.
|
|
132
|
+
- title: Cross-tenant leak via error path or partial response
|
|
133
|
+
stride: [information_disclosure]
|
|
134
|
+
description: >
|
|
135
|
+
An exception inside the post-query filter loop returns the unfiltered
|
|
136
|
+
buffer or a serialization mid-state. Other tenants' rows reach the
|
|
137
|
+
wire because the only org check sits past the failure point.
|
|
138
|
+
remediation: >
|
|
139
|
+
Same control — RLS or query-time scoping. Plus: serialize the response
|
|
140
|
+
body only after filtering completes; on filter error, return a
|
|
141
|
+
structured error with no payload.
|
|
142
|
+
- title: Logged or traced rows expose other tenants
|
|
143
|
+
stride: [information_disclosure, repudiation]
|
|
144
|
+
description: >
|
|
145
|
+
Observability hooks (request logger, tracer, metrics) often capture the
|
|
146
|
+
raw query result before the presentation-layer filter runs. Other
|
|
147
|
+
tenants' identifiers, names, or PII land in logs the requesting tenant
|
|
148
|
+
should never have seen.
|
|
149
|
+
remediation: >
|
|
150
|
+
Either log only post-filter results, or — better — make the query
|
|
151
|
+
itself org-scoped so the unfiltered result never exists.
|
|
152
|
+
|
|
153
|
+
fixtures:
|
|
154
|
+
positive:
|
|
155
|
+
- "fixtures/positive/late-org-filter/express_sse_post_filter_typescript"
|
|
156
|
+
- "fixtures/positive/late-org-filter/fastapi_iterator_post_filter_python"
|
|
157
|
+
negative:
|
|
158
|
+
- "fixtures/negative/late-org-filter/prisma_middleware_org_scope"
|
|
159
|
+
- "fixtures/negative/late-org-filter/postgres_rls_enabled"
|
|
160
|
+
|
|
161
|
+
lifecycle: draft
|
|
162
|
+
owner: platform-threat-modeling
|
|
163
|
+
tags:
|
|
164
|
+
- multi-tenant
|
|
165
|
+
- cross-tenant-leak
|
|
166
|
+
- data-scoping
|
|
167
|
+
- sse
|
|
168
|
+
- stream
|
|
169
|
+
|
|
170
|
+
sensitivity_level: customer_safe
|
|
171
|
+
allowed_execution_modes:
|
|
172
|
+
- local_only
|
|
173
|
+
- local_model
|
|
174
|
+
- hybrid_cloud_eval
|
|
175
|
+
|
|
176
|
+
safe_summary: >
|
|
177
|
+
Tenant filtering applied after the query — at the response loop, the SSE
|
|
178
|
+
emit, or the stream iterator — instead of inside the query. Any error,
|
|
179
|
+
race, observability hook, or partial response that fires before the filter
|
|
180
|
+
reaches the wire leaks rows across tenants. The fix is to push org/tenant
|
|
181
|
+
scoping down to the data layer (RLS, query helpers, ORM middleware) so the
|
|
182
|
+
unfiltered set never exists in the first place.
|
|
183
|
+
|
|
184
|
+
detect_when: >
|
|
185
|
+
A handler, stream, or async iterator runs a data query, then filters the
|
|
186
|
+
result by `request.orgId` (or a session org) before responding — and the
|
|
187
|
+
query itself doesn't include the org predicate. Multi-tenant routes only.
|
|
188
|
+
|
|
189
|
+
risk_hints:
|
|
190
|
+
- The bug class behind a meaningful share of cross-tenant SaaS incidents
|
|
191
|
+
- SSE / queue / WebSocket variants are the hardest to spot because the filter looks "inside" the loop
|
|
192
|
+
- Logs and traces often capture the unfiltered set even when the response itself is clean
|
|
193
|
+
- Tests often pass because they only run with a single tenant in the test database
|
|
194
|
+
|
|
195
|
+
architectural_signals:
|
|
196
|
+
- Query call followed by an explicit filter referencing a request/session org id
|
|
197
|
+
- SSE or async iterator that yields events conditional on a tenant match
|
|
198
|
+
- No row-level security policy, no implicit-org-scope helper, no ORM middleware
|
|
199
|
+
|
|
200
|
+
model_task_prompt_ref: prompts/security/late-org-filter/identify.md
|
|
201
|
+
|
|
202
|
+
expected_output_schema:
|
|
203
|
+
type: object
|
|
204
|
+
required: [verdict, scope_location, evidence_spans]
|
|
205
|
+
properties:
|
|
206
|
+
verdict:
|
|
207
|
+
type: string
|
|
208
|
+
enum: [late_filter_present, scoped_at_query, partial_scope, unclear]
|
|
209
|
+
scope_location:
|
|
210
|
+
type: string
|
|
211
|
+
enum: [data_layer, query_layer, presentation_layer, multiple, none]
|
|
212
|
+
rationale:
|
|
213
|
+
type: string
|
|
214
|
+
blast_radius:
|
|
215
|
+
type: string
|
|
216
|
+
enum: [single_endpoint, route_family, service_wide, unclear]
|
|
217
|
+
recommended_controls:
|
|
218
|
+
type: array
|
|
219
|
+
items:
|
|
220
|
+
type: string
|
|
221
|
+
enum: [row_level_security, query_helper, orm_middleware, repository_pattern_with_org_scope, none]
|
|
222
|
+
evidence_spans:
|
|
223
|
+
type: array
|
|
224
|
+
items:
|
|
225
|
+
type: object
|
|
226
|
+
required: [file]
|
|
227
|
+
properties:
|
|
228
|
+
file: { type: string }
|
|
229
|
+
start_line: { type: integer }
|
|
230
|
+
end_line: { type: integer }
|
|
231
|
+
note: { type: string }
|
|
232
|
+
|
|
233
|
+
minimum_context_required:
|
|
234
|
+
lines: 100
|
|
235
|
+
symbols:
|
|
236
|
+
- orgId
|
|
237
|
+
- org_id
|
|
238
|
+
- tenant_id
|
|
239
|
+
- filter
|
|
240
|
+
- SSE
|
|
241
|
+
- stream
|
|
242
|
+
|
|
243
|
+
related_patterns:
|
|
244
|
+
- security.idor-via-direct-id
|
|
245
|
+
- security.broken-object-level-authz
|
|
246
|
+
|
|
247
|
+
repair_contract:
|
|
248
|
+
allowed:
|
|
249
|
+
- Push org/tenant scoping into the query (where clause, RLS, ORM middleware)
|
|
250
|
+
- Introduce a repository wrapper that mandates org_id on every method
|
|
251
|
+
- Add a Postgres row-level security policy
|
|
252
|
+
- Keep the presentation-layer filter as defense in depth IN ADDITION TO the data-layer scope
|
|
253
|
+
forbidden:
|
|
254
|
+
- Removing the presentation-layer filter without first introducing a data-layer scope (regresses safety)
|
|
255
|
+
- Treating the patch as complete after only fixing the immediate route — the bug class is structural
|
|
256
|
+
|
|
257
|
+
validation_contract:
|
|
258
|
+
requires_tests_pass: false
|
|
259
|
+
requires_static_rules:
|
|
260
|
+
- data_layer_org_scope_present_after_patch
|
|
261
|
+
- no_unscoped_query_remains_in_route_handler
|
|
262
|
+
|
|
263
|
+
unsafe_to_expose_fields:
|
|
264
|
+
- internal_rubric
|
|
265
|
+
- golden_transcripts
|
|
266
|
+
- exploit_templates
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Control: external_validator_on_facts
|
|
2
|
+
# Referenced by:
|
|
3
|
+
# - prompting.self-consistency-no-isolation (META: none_of_controls)
|
|
4
|
+
#
|
|
5
|
+
# An external (non-LLM) check that the aggregated answer is factually
|
|
6
|
+
# consistent — a SQL query, an API call, a regex against ground truth, etc.
|
|
7
|
+
|
|
8
|
+
id: external_validator_on_facts
|
|
9
|
+
description: >
|
|
10
|
+
An external, non-LLM check applied to the aggregated answer before it's
|
|
11
|
+
acted on. Common shapes: factCheck()/validateFacts() helpers, schema
|
|
12
|
+
validation, lookup against a trusted store.
|
|
13
|
+
|
|
14
|
+
applicable_languages:
|
|
15
|
+
- typescript
|
|
16
|
+
- javascript
|
|
17
|
+
- python
|
|
18
|
+
|
|
19
|
+
detection:
|
|
20
|
+
- language: typescript
|
|
21
|
+
rule:
|
|
22
|
+
any:
|
|
23
|
+
- pattern: factCheck($$$)
|
|
24
|
+
- pattern: validateFacts($$$)
|
|
25
|
+
- pattern: validateAnswer($$$)
|
|
26
|
+
- pattern: $_.factCheck($$$)
|
|
27
|
+
- pattern: $_.validate($$$)
|
|
28
|
+
- language: python
|
|
29
|
+
rule:
|
|
30
|
+
any:
|
|
31
|
+
- pattern: fact_check($$$)
|
|
32
|
+
- pattern: validate_facts($$$)
|
|
33
|
+
- pattern: validate_answer($$$)
|
|
34
|
+
- pattern: $_.validate_against_ground_truth($$$)
|
|
35
|
+
|
|
36
|
+
owner: platform-threat-modeling
|
|
37
|
+
lifecycle: draft
|