diff --git a/detection/snowflake/ENRICHMENT.md b/detection/snowflake/ENRICHMENT.md index a931bde..85d42e1 100644 --- a/detection/snowflake/ENRICHMENT.md +++ b/detection/snowflake/ENRICHMENT.md @@ -180,17 +180,88 @@ to avoid false positives during the slower system's ingestion delay. - **Rules:** [`federated_login_anomaly.yml`](sigma/federated_login_anomaly.yml) (and Trail-paired variants where present). -- **Computation:** `lag_tolerant` is a boolean ingestion-pipeline - parameter (default `true`). When true, the rule defers final firing - until *both* sides of the correlation have known-ingested timestamps - newer than the event time, surfaced as `both_sources_caught_up = true`. - The per-source watermarks (`idp_audit_watermark_ingested_at`, - `snowflake_login_ingested_at`) come from the SIEM pipeline's - per-source ingestion tracker. -- **Input data location:** Computed by the SIEM ingestion pipeline - from per-source ingestion timestamps. Most modern SIEMs (Sentinel, - Splunk SC4S, Elastic Fleet) expose ingestion watermarks per source; - the pipeline only needs to materialize them as enrichment columns. +- **Why these fields exist:** the federated-login rule fires on the + *absence* of a corresponding IdP sign-in event. Without a known + watermark per source, "absent" cannot be distinguished from "delayed." + A rule that fires on "absent so far" produces a false-positive storm + during routine IdP audit ingestion lag and is the first detection a + SOC will disable. The watermark machinery is what makes the rule + deployable. + +**Definition.** A *per-source watermark* is the latest event-time of +any record successfully ingested from that source — measured in the +event's own timestamp domain, not the SIEM's wall clock. The rule +treats an event as "comparable against the other source" only when +both sources' watermarks are at or after the event's timestamp plus the +configured `idp_correlation_window_minutes`. Until then, the rule +suppresses firing and re-evaluates on the next ingestion tick. + +**Computation by SIEM.** The watermark is a max-aggregation per source +over a recent window. Concrete forms: + + - **Microsoft Sentinel (KQL):** + ```kusto + let idp_audit_watermark = toscalar( + OktaSystemLog + | where TimeGenerated > ago(2h) + | summarize max(EventTime) + ); + let snowflake_login_watermark = toscalar( + SnowflakeLoginHistory + | where TimeGenerated > ago(2h) + | summarize max(EventTimestamp) + ); + ``` + Materialize as enrichment columns on each event being evaluated; + `both_sources_caught_up = (event_time + window_minutes) <= least_of_both_watermarks`. + - **Splunk (SPL):** Use a `tstats latest(_time)` lookup per + sourcetype, indexed every 60s into a `summary` kvstore; join the + summary into each event at search time. `_indextime` (not `_time`) + is the canonical ingest-time field if event-time is unreliable. + - **Elastic / Logstash:** Use the `ingest_time` field added by the + Fleet pipeline; aggregate via a transform that computes + `max(ingest_time)` per `data_stream.dataset`. Surface as a + `runtime_field` on the detection index. + +**Where input data lives.** The watermarks are computed from the +ingestion pipeline's own state, not from external watchlists. Concretely +the inputs are: (a) for the IdP side, the Okta System Log connector's +`EventTime` column or the Entra Sign-In Logs connector's +`SignInActivityTimestamp` column; (b) for the Snowflake side, the +`LOGIN_HISTORY` event timestamp on the ACCOUNT_USAGE path, or the +Trail `auth.snowflake.login` event time on the Trail path. The +materialized watermarks should be reachable as fields on each event +the SIEM evaluates — either via a per-event lookup join or via a +runtime field that re-computes on each search. + +**Fallback when watermarks are not yet wired up.** A SIEM without the +watermark machinery has two safe operating modes: + + - **Conservative (recommended).** Set `lag_tolerant: false` on the + rule body and tune `idp_correlation_window_minutes` to absorb the + worst-case combined ingestion SLA of both sides (e.g., 60 minutes + if Okta SLA is 10m, ACCOUNT_USAGE latency is 45m, and the IdP-to- + Snowflake correlation window is 5m). The rule degrades to a fixed- + window correlation that fires when no IdP event has arrived after + the window has elapsed. False-positive risk is bounded by the SLA. + - **Permissive (not recommended without compensating controls).** + Pin `both_sources_caught_up: true` unconditionally. The rule fires + on apparent absence regardless of ingestion state and will produce + spurious alerts during routine ingestion lag. Use this only as a + short-term posture while the watermark pipeline is being built, + and only with explicit SOC sign-off; the noise will train operators + to ignore the rule. + +**Validation before promoting the rule to alert.** Submit a synthetic +test event with `has_corresponding_idp_event: false` and an +event-timestamp 24 hours in the future. The rule MUST NOT fire — if it +does, the watermark logic is not wired up (the rule is evaluating the +future event as if both sources had ingested up to it, which they have +not). A separate synthetic with an event-timestamp two minutes in the +past, no matching IdP event, and `lag_tolerant: true` should fire only +after both real ingestion watermarks have advanced past the event time; +observe the firing delay equals the slower source's lag, not the rule +evaluator's cadence. ### `has_cortex_code_session_within_window`, `cortex_code_session_host_id` @@ -433,7 +504,7 @@ trace once captured: ## 11. Chain-M (UDF EAI) Derived Fields -### `udf_owner`, `udf_eai_list`, `eai_network_rule_value_list`, `eai_rule_is_overbroad`, `invocation_role_eq_owner` +### `udf_owner`, `udf_has_eai`, `udf_eai_list`, `eai_network_rule_value_list`, `eai_rule_is_overbroad`, `invocation_role_eq_owner` - **Rule:** [`udf_with_eai_invocation.yml`](../../tools/lateral-movement/snowflake-pivot/detection/sigma/udf_with_eai_invocation.yml). - **Native source:** `ACCOUNT_USAGE.FUNCTIONS` (function owner + EAI @@ -442,8 +513,14 @@ trace once captured: user/role). - **Computation:** Join at ingest. `eai_rule_is_overbroad` is `true` when the referenced NETWORK RULE's `value_list` contains a wildcard - (`*`, `OPEN_ANY`). -- **Input data location:** All four joins are pure Snowflake views. + (`*`, `OPEN_ANY`). `udf_has_eai` is the explicit boolean cardinality + flag — `true` iff `FUNCTIONS.EXTERNAL_ACCESS_INTEGRATIONS` is non-null + AND parses to a non-empty list. The rule keys on `udf_has_eai: true` + rather than `udf_eai_list|exists: true` because Sigma's `|exists` + modifier checks field presence, not list cardinality — an empty list + serialized as `[]` would pass `|exists` and fire the rule on UDFs + that declare *no* EAIs. +- **Input data location:** All five joins are pure Snowflake views. ## 12. SPCS Image Posture Derived Fields @@ -469,6 +546,12 @@ confirm: `idp_correlation_window_minutes` is tuned to cover the worst-case ingestion lag of either side, and `lag_tolerant` is enabled by default. +- [ ] Per-source ingestion watermarks (`idp_audit_watermark_ingested_at`, + `snowflake_login_ingested_at`) are materialized as enrichment columns + using the SIEM-specific recipe in §3 above; the synthetic-event + validation (future-dated event MUST NOT fire) has been run and passed. + Without this, `federated_login_anomaly.yml` silently never fires and + the customer has a Chain D detection gap they will not notice. - [ ] If Snowflake Trail is enabled for the account, the Cortex Trail event families are emitting; if not, a Cortex telemetry sidecar is deployed (Snowpark wrapper) and the rule registry reflects diff --git a/detection/snowflake/README.md b/detection/snowflake/README.md index 1c55d31..d93a411 100644 --- a/detection/snowflake/README.md +++ b/detection/snowflake/README.md @@ -16,13 +16,13 @@ customer needs in place before the rule will fire. Honest accounting: | Tag | Count | What it means for deployment | |-----|------:|------------------------------| | `production_ready` | 4 | Fires on raw audit / log surfaces a customer already ingests. No enrichment, correlation, or sidecar required. Drop in. | -| `requires_enrichment` | 19 | Fires only when a SIEM-side enrichment pipeline computes the derived fields listed under each rule's `enrichment.required`. See [`ENRICHMENT.md`](ENRICHMENT.md) for the full field contract; templates under [`enrichment-templates/`](enrichment-templates/). | +| `requires_enrichment` | 20 | Fires only when a SIEM-side enrichment pipeline computes the derived fields listed under each rule's `enrichment.required`. See [`ENRICHMENT.md`](ENRICHMENT.md) for the full field contract; templates under [`enrichment-templates/`](enrichment-templates/). | | `requires_correlation` | 4 | Fires only when an external audit stream — IdP sign-in events for `federated_login_anomaly` / `oauth_integration_scope_drift`, Cortex Code CLI session logs for `cortex_code_session_to_unknown_session` — is correlated with the Snowflake-side event. | | `requires_cortex_sidecar` | 5 | Fires only when a Cortex Agents per-step trace is surfaced by a sidecar. Snowflake's first-party `ACCOUNT_USAGE` views do not surface the depth these rules require. | | `requires_endpoint_telemetry` | 1 | Fires on host-side process / file telemetry, not Snowflake audit (Cortex Code CLI version-string detection). | -**Rule of thumb**: of the 33 Sigma rules in this pack, 4 work out of the -box. The remaining 29 land an alert only after the relevant enrichment, +**Rule of thumb**: of the 34 Sigma rules in this pack, 4 work out of the +box. The remaining 30 land an alert only after the relevant enrichment, correlation, or sidecar is operational. The `requires_enrichment` tier is the biggest deployment lift; the [`enrichment-templates/`](enrichment-templates/) directory has the SQL and SIEM lookup definitions to compute the derived @@ -66,7 +66,7 @@ ingestion surface available on the customer's side. | Chain | What it does | ACCOUNT_USAGE Sigma | Trail Sigma | |-------|--------------|---------------------|-------------| -| A — Credential theft to bulk exfil | UNC5537 replay; bulk `COPY INTO @stage` from a non-MFA / no-network-policy user. | [`bulk_exfil_baseline.yml`](sigma/bulk_exfil_baseline.yml) + bind-param coverage: [`snowflake_bind_param_audit_gap.yml`](../../tools/lateral-movement/snowflake-pivot/detection/sigma/snowflake_bind_param_audit_gap.yml) | — (folded into bulk_exfil_baseline via the streaming-ingest pipeline) | +| A — Credential theft to bulk exfil | UNC5537 replay; bulk `COPY INTO @stage` from a non-MFA / no-network-policy user. | [`bulk_exfil_baseline.yml`](sigma/bulk_exfil_baseline.yml) + bind-param coverage: [`snowflake_bind_param_audit_gap.yml`](../../tools/lateral-movement/snowflake-pivot/detection/sigma/snowflake_bind_param_audit_gap.yml) | [`bulk_exfil_baseline_trail.yml`](sigma/bulk_exfil_baseline_trail.yml) — mirrors the ACCOUNT_USAGE rule's four-signal contract on `query.snowflake.completed`; where Trail is not yet wired up, the streaming-ingest sidecar under [`streaming-ingest/`](streaming-ingest/) is the interim minute-scale coverage | | B — Cortex Code indirect injection | Pre-1.0.25 Cortex Code CLI executes shell-pipe-sh under indirect prompt injection. | [`cortex_code_pre_1_0_25.yml`](sigma/cortex_code_pre_1_0_25.yml) (version-string, endpoint-side) + behavioral pair: [`cortex_code_session_to_unknown_session.yml`](sigma/cortex_code_session_to_unknown_session.yml) | covered by the behavioral pair (does not depend on Trail event names) | | C — Native App Marketplace supply-chain | Installed Native App auto-updates to a manifest with new external integrations, new privileges, or new/mutated dependencies (incl. deferred-loader shape). | [`native_app_unexpected_version_bump.yml`](sigma/native_app_unexpected_version_bump.yml) + [`native_app_privilege_bump.yml`](../../tools/supply-chain/snowflake-native-app/detection/sigma/native_app_privilege_bump.yml) + [`native_app_dependency_drift.yml`](../../tools/supply-chain/snowflake-native-app/detection/sigma/native_app_dependency_drift.yml) | [`native_app_privilege_bump_trail.yml`](../../tools/supply-chain/snowflake-native-app/detection/sigma/native_app_privilege_bump_trail.yml) | | D — Federated-IdP compromise | Forged SAML/OAuth assertion authenticates a high-privileged Snowflake user. | [`federated_login_anomaly.yml`](sigma/federated_login_anomaly.yml) | — (use the Chain F Trail variant; same login_history shape) | diff --git a/detection/snowflake/sigma/bulk_exfil_baseline_trail.yml b/detection/snowflake/sigma/bulk_exfil_baseline_trail.yml new file mode 100644 index 0000000..7853554 --- /dev/null +++ b/detection/snowflake/sigma/bulk_exfil_baseline_trail.yml @@ -0,0 +1,118 @@ +title: Snowflake Trail — Bulk COPY INTO External Stage (Chain A, role-aware) +id: 9a1c3e5f-7b2d-4e6a-9c0e-1f2a3b4c5d6e +maturity: requires_enrichment # fires only when a SIEM-side enrichment pipeline computes the derived fields listed under enrichment.required +status: experimental +description: | + Trail-event-shaped pair to `bulk_exfil_baseline.yml`. Consumes the + `query.snowflake.completed` Trail event for any `COPY INTO @` + whose combination of signals separates an attacker's first-and-only + bulk exfil from a legitimate role's recurring data motion. + + The detection contract mirrors the ACCOUNT_USAGE-shaped rule exactly — + same four-signal gating, same false-positive guidance — but consumes + the real-time Trail event stream instead of polling + `SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY`. Latency advantage: seconds + rather than the up-to-45-minute ACCOUNT_USAGE projection lag. This is + the difference between catching an UNC5537-class exfil during the + attacker's session vs. after it has already completed. + + The rule fires when **all four** of the following hold: + + 1. The query is a `COPY INTO @` (external-stage form, + not internal stage). + 2. The external stage is **not** on the customer-maintained + approved-exfil-stage watchlist. + 3. **At least one** of: + - the user's role is not in the approved bulk-exporter set, + - the volume exceeds the role's 90th-percentile baseline, + - the event falls outside business hours for the user's + time zone / tenant policy. + 4. Volume above 10 MB. + + Pair with `snowflake_bind_param_audit_gap.yml` for sessions where bind + parameters degrade the audit signal — the Trail event preserves the + same bind-parameter blindness on `QUERY_TEXT` that ACCOUNT_USAGE does, + so the pair coverage is needed regardless of the ingestion surface. + + **Known sensitivity gap** (identical to the ACCOUNT_USAGE rule): an + attacker who has stolen credentials for an approved bulk-exporter role + and exfils inside that role's documented business-hours window at a + volume below the role's p90 baseline is invisible to this rule unless + the destination stage is flagged at a higher signal level than the + current outer-OR gating. The `fp_fn_harness/bulk_exfil_baseline.py` + harness measures this gap; the recommended remediation is documented + there (promote `external_stage_in_watchlist` to a fire signal, or add + a `stage_outside_corp_namespace` enrichment field). + + **Deployment posture.** This rule activates only where Snowflake Trail + is enabled and the `query_events` event family is subscribed. Where + Trail is not yet wired up, the customer's interim coverage is the + streaming-ingest sidecar documented under + `detection/snowflake/streaming-ingest/`, which polls + `INFORMATION_SCHEMA.QUERY_HISTORY()` on a 60-second cadence and + produces equivalent signal at ~minute-scale latency (rather than the + Trail rule's seconds-scale). +references: + - https://cloud.google.com/blog/topics/threat-intelligence/unc5537-snowflake-data-theft-extortion + - https://docs.snowflake.com/en/user-guide/snowflake-trail + - https://docs.snowflake.com/en/sql-reference/sql/copy-into-location +author: security-research +date: 2026-05-15 +tags: + - attack.exfiltration + - attack.t1567.002 +enrichment: + required: + - external_stage_in_watchlist + - role_in_approved_bulk_exporter_set + - volume_above_role_baseline + - outside_business_hours + doc: ../ENRICHMENT.md +logsource: + product: snowflake_trail + service: query_events +detection: + copy_to_external: + event_type: 'query.snowflake.completed' + query_type|startswith: 'COPY' + query_text|contains: '@' + external_stage_not_in_watchlist: + external_stage_in_watchlist: false + role_off_baseline: + role_in_approved_bulk_exporter_set: false + volume_above_baseline: + volume_above_role_baseline: true + off_hours: + outside_business_hours: true + size_floor: + bytes_written_to_result|gte: 10485760 # 10 MB lower floor + condition: > + copy_to_external + and external_stage_not_in_watchlist + and size_floor + and (role_off_baseline or volume_above_baseline or off_hours) +fields: + - event_timestamp + - user_name + - role_name + - session_id + - query_text + - bytes_written_to_result + - rows_produced + - external_stage_url + - role_in_approved_bulk_exporter_set + - volume_above_role_baseline + - outside_business_hours +falsepositives: + - Legitimate first-run of a new pipeline that loads from / unloads to + a freshly-created external stage. Maintain a 24h grace + + on-call notification — the rule should warn (not page) until the + new stage is added to the watchlist. + - Genuinely novel ad-hoc exports from approved bulk-exporter roles + during declared incidents (DR, data-migration, etc.). Tag the + incident window so the rule's `off_hours` signal does not stack + with operational urgency. + - Roles that have legitimate after-hours export windows (overnight + EHR refreshes). Define the `outside_business_hours` calculation + per role, not per tenant — the enrichment doc names this pattern. +level: high diff --git a/detection/snowflake/sigma/native_app_unexpected_version_bump.yml b/detection/snowflake/sigma/native_app_unexpected_version_bump.yml index 7f4abf0..d4b7cb4 100644 --- a/detection/snowflake/sigma/native_app_unexpected_version_bump.yml +++ b/detection/snowflake/sigma/native_app_unexpected_version_bump.yml @@ -11,6 +11,14 @@ description: | provider account pushes a new manifest; consumers with auto-update enabled receive it without re-consent. The NAAAPS scan is the upstream control; the consumer-side detection is the version-bump diff. + + Structural contract: `manifest_diff_added` is a list of prefixed + tokens emitted by the application_history projection + (`PRIVILEGE:`, `EXTERNAL ACCESS INTEGRATION:`, + `EXTERNAL FUNCTION:`, `CONTAINER:`). The rule uses + `|startswith` against the prefix list so the detection binds to the + structural contract; free-text occurrences of these tokens in other + fields will not fire the rule. references: - https://docs.snowflake.com/en/developer-guide/native-apps/security-overview - https://docs.snowflake.com/en/developer-guide/native-apps/security-cve @@ -34,9 +42,9 @@ detection: event_type: APP_VERSION_INSTALLED auto_upgrade: true new_eai_or_extfn: - manifest_diff_added|contains: - - 'EXTERNAL ACCESS INTEGRATION' - - 'EXTERNAL FUNCTION' + manifest_diff_added|startswith: + - 'EXTERNAL ACCESS INTEGRATION:' + - 'EXTERNAL FUNCTION:' condition: app_upgraded and new_eai_or_extfn fields: - event_timestamp diff --git a/docs/analysis/chain-reference-table.md b/docs/analysis/chain-reference-table.md index fabe162..31c4eca 100644 --- a/docs/analysis/chain-reference-table.md +++ b/docs/analysis/chain-reference-table.md @@ -27,7 +27,7 @@ public-artifact-to-credential-replay path stacked on top is hypothesis. | Chain | Maturity | Tools | Sigma rules (ACCOUNT_USAGE → Trail pair) | CVE / Incident anchor | Healthcare impact (PHI surface) | |-------|----------|-------|------------------------------------------|----------------------|-------------------------------| -| **A** — Credential theft → bulk exfil | `EMPIRICAL` | Any bulk-COPY producer; setup uses `jwt_keypair_signer.py` for service users | `bulk_exfil_baseline.yml` (8e7d2c1f), `snowflake_bind_param_audit_gap.yml` (f3a8c2d7) | UNC5537 (May–Jun 2024 cohort) | Primary patient + claims marts; the volume PHI surface. | +| **A** — Credential theft → bulk exfil | `EMPIRICAL` | Any bulk-COPY producer; setup uses `jwt_keypair_signer.py` for service users | `bulk_exfil_baseline.yml` (8e7d2c1f) → `bulk_exfil_baseline_trail.yml` (9a1c3e5f), `snowflake_bind_param_audit_gap.yml` (f3a8c2d7) | UNC5537 (May–Jun 2024 cohort) | Primary patient + claims marts; the volume PHI surface. | | **B** — Cortex Code indirect injection → cred theft | `HYPOTHESIS` (CVE empirical[^b1]) | Tooling pending; lab mock supports the Cortex Code session surface | `cortex_code_pre_1_0_25.yml` (1f2a3b4c), `cortex_code_session_to_unknown_session.yml` (4e6f8091) | CVE-2026-6442 (pre-1.0.25 Cortex Code CLI; PromptArmor disclosure; patched 2026-02-28) | Engineer credentials → indirect PHI exfil via Chain A; depends on the developer's role grants. | | **C** — Native App marketplace supply-chain | `MODELED` | `version_bump_sim.py`, `manifest_builder.py`, `naaaps_bypass_probe.py` | `native_app_unexpected_version_bump.yml` (2a3b4c5d), `native_app_privilege_bump.yml` (3a5c7d9e) → `native_app_privilege_bump_trail.yml` (4b6d8e0f), `native_app_dependency_drift.yml` (7e1b3c5d) | Shai-Hulud (npm worm) class — analog only; no public Snowflake-CVE'd or Marketplace-attributed incident as of 2026-05. Validated against lab mock's Marketplace + NAAAPS surfaces. | Any consumer's PHI accessible to the Native App's authorized scopes. | | **D** — Federated-IdP compromise → Snowflake | `EMPIRICAL` | Reused: `tools/cloud-identity/golden-saml/`, `tools/lateral-movement/exchange-hybrid/` (evoSTS) | `federated_login_anomaly.yml` (3b4c5d6e) | Golden SAML class (Solorigate-class, multiple campaigns) | ACCOUNTADMIN/SECURITYADMIN role compromise → full PHI access if granted. | @@ -50,6 +50,53 @@ public-artifact-to-credential-replay path stacked on top is hypothesis. [^k1]: **Chain K caveat.** Modelled against the Polaris REST catalog spec as of 2026-05. Snowflake's Open Catalog API is still evolving (Polaris graduated from Apache incubation late 2025); operators should validate the tool against their deployment's actual Polaris version before relying on the tool's enumeration semantics. The `iceberg_table_outside_catalog_base.yml` rule is keyed on the catalog-base prefix and is robust across spec revisions; the tool's REST enumeration paths are not. +## Residual-risk profile + +The maturity badge above grades *evidence strength*, not *post-mitigation +residual risk*. A reader asking "if we apply this report's P0 +recommendations, which chains remain a real concern?" reads this table. + +Two columns, both ordinal (`Low / Medium / High`): + +- **Default residual.** Risk assuming Snowflake's post-UNC5537 defaults + are on (mandatory MFA on humans, network policies on service users, + default Trust Center scanners enabled) but no additional customer- + side hardening. Mirrors the per-chain "Default residual" line in the + healthcare overlay. +- **Post-P0 residual.** Risk after the report's P0 recommendations for + this chain are implemented. Always lower than or equal to default; a + delta of zero is a structural gap the platform itself does not close + (e.g., Chain G's source-side audit blindness). + +| Chain | Default residual | Post-P0 residual | What still bypasses Post-P0 (the structural residual) | +|-------|------------------|------------------|--------------------------------------------------------| +| **A** | High | Medium | Approved-role misuse — attacker uses stolen credentials for an approved bulk-exporter role, exfils within that role's documented business-hours window at a volume below the role's p90 baseline. Invisible to the four-signal rule. | +| **B** | High | Low | Closed by pinning Cortex Code CLI ≥ 1.0.25 across all developer endpoints. The broader class (future Cortex-Code-class RCE, other agent CLIs caching Snowflake tokens) is the residual concern. | +| **C** | Medium-high | Medium | Multi-stage deferred-loader where each individual version is scan-clean at publish time but the cumulative effect arrives via a transitive resolution after install. NAAAPS does not catch this shape; the consumer-side `native_app_dependency_drift.yml` is the compensating signal but depends on consumer-side install-time observation. | +| **D** | High | Medium-high | IdP-side compromise surface is outside Snowflake's controls. Even with FIDO/passkey on admins + IdP audit ingest + correlation rules, a Golden-SAML-class attack against a hardened IdP remains the chain's residual surface. Detection is partial mitigation, not prevention. | +| **E** | Medium-high | Low | Configuration-level fix: enumerate every Storage Integration, replace wildcard `storage_allowed_locations` with bucket prefixes, bound IAM role with bucket-policy-side controls. Chain depends on over-broad config; once tightened, the chain no longer applies. | +| **F** | High | Medium | Service-user key material on CI/orchestration hosts remains the structural residual. Network policies, passphrases, rotation cadence, and managed secret stores reduce the surface but do not eliminate the CI-runner-compromise path. Orchestrator hardening is the dependent control and it lives outside Snowflake. | +| **G** | Medium-high | Medium-high | Source-side audit gap is structural — `QUERY_HISTORY` does not project byte motion for share / replication primitives. Detection rules catch the share-creation DDL but the read events themselves remain invisible. Consumer-side audit acquisition (pre-arranged in the BAA) is the only complete close; absent that, residual stays at Medium-high. | +| **H** | Medium | Low | Configuration-level fix: enumerate every EAI, replace wildcard `ALLOWED_NETWORK_RULES` with explicit hostnames, deploy `snowflake_spcs_eai_overbroad`. Wildcards are the chain's necessary condition. | +| **I** | High | Medium-high | Behavioral planner attacks (semantic injection, authority spoof, multi-turn poisoning) bypass keyword-based Guardrails detection. Row-access and masking policies at the table layer are the load-bearing control; the chain's residual after Guardrails alone is the agent's effective RBAC scope, which is the harder half of the problem. | +| **J** | Medium-high | Medium | Vendors that cannot publish stable egress CIDRs cannot be locked down with network policies. Architectural move to scoped Direct Share (partner-as-consumer rather than partner-holding-our-credential) closes the chain entirely but is a multi-quarter effort. | +| **K** | Medium | Medium | Polaris API spec is still evolving; the tool's enumeration semantics may not align with future Polaris versions. The `iceberg_table_outside_catalog_base.yml` rule keyed on the catalog-base prefix is robust across spec revisions, but the offensive tool's enumeration paths are not. Residual is bounded by spec stability. | +| **L** | Medium | Medium | Chain depends on IdP-side consent expansion that can happen without any Snowflake-side configuration change. Daily consent snapshot + diff is the detection beat; consent expansion within the 24-hour snapshot window remains a residual. OAuth integration default-role tightening is the prevention. | +| **M** | Medium | Low | Configuration-level fix: deploy `udf_with_eai_invocation`, audit every EAI-bound UDF for `PUBLIC` invoke grants, lock EAI network rules to specific hostnames. The UDF + over-broad EAI + `PUBLIC` grant is the chain's necessary condition; remove any one and the chain doesn't apply. | +| **H+** | Medium | Low | Configuration-level fix: pin all SPCS images by `@sha256:` digest, populate `OPS.SECURITY.APPROVED_CONTAINER_REGISTRIES`, deploy `spcs_image_unpinned_or_external`. Tag-based pinning is the chain's necessary condition. | + +**Reading guidance.** A Post-P0 residual of `Low` means the chain is a +posture management problem — keep the control in place and the chain +does not apply. A residual of `Medium` means the chain has partial +mitigation through detection but the underlying surface is not closed. +A residual of `Medium-high` (Chains D, G, I) means the chain depends on +controls or systems outside Snowflake's boundary, or on a structural +audit gap; detection is the load-bearing layer and the platform itself +will not close the gap. There is no `High` post-P0 residual in this +catalog — chains with that profile are out of scope of a 90-day +remediation program and are flagged as architectural changes in the +recommendations page. + ## Cross-cutting detection content The following rules pair with multiple chains rather than a single chain: diff --git a/reports/snowflake-platform-assessment/appendix.html b/reports/snowflake-platform-assessment/appendix.html index 8889402..86d7c26 100644 --- a/reports/snowflake-platform-assessment/appendix.html +++ b/reports/snowflake-platform-assessment/appendix.html @@ -17,6 +17,8 @@
  • Attack chains
  • Detection surface
  • Recommendations
  • +
  • Incident response
  • +
  • Healthcare overlay
  • Appendix
  • @@ -25,7 +27,16 @@

    Appendix

    Glossary, scope and assumptions, and references.

    -

    Glossary

    + + +

    Glossary

    @@ -113,7 +124,7 @@

    Glossary

    -

    Scope and threat-model assumptions

    +

    Scope and threat-model assumptions

    In scope

    • Snowflake authentication and identity (password, MFA, key-pair, OAuth, SAML, SCIM, PATs).
    • @@ -144,7 +155,7 @@

      Threat-model assumptions

    • Snowflake's platform-default controls as of 2026 are in place but customer adoption of opt-in hardening (network policies on key-pair users, MFA-verified federation, audit replication across regions) is uneven.
    -

    Companion material in this repository

    +

    Companion material in this repository

    • docs/analysis/snowflake-platform-attack-surface-2026.md — the analytical companion to this report.
    • docs/analysis/entra-2026-state-of-play.md — relevant for chain D (federated-IdP compromise).
    • diff --git a/reports/snowflake-platform-assessment/assets/style.css b/reports/snowflake-platform-assessment/assets/style.css index 97062aa..d69854f 100644 --- a/reports/snowflake-platform-assessment/assets/style.css +++ b/reports/snowflake-platform-assessment/assets/style.css @@ -120,6 +120,37 @@ p { margin: 8px 0; } margin-bottom: 16px; } +.page-toc { + background: var(--bg-soft); + border: 1px solid var(--border); + border-radius: 6px; + padding: 10px 14px; + font-size: 13px; + line-height: 1.7; + margin: 16px 0 24px; +} +.page-toc .page-toc-title { + color: var(--muted); + font-weight: 600; + text-transform: uppercase; + font-size: 11px; + letter-spacing: 0.05em; + margin-bottom: 6px; +} +.page-toc ol { + list-style: decimal inside; + padding: 0; + margin: 0; + columns: 2; + column-gap: 24px; +} +.page-toc li { break-inside: avoid; } +.page-toc a { text-decoration: none; } +.page-toc a:hover { text-decoration: underline; } +@media (max-width: 700px) { + .page-toc ol { columns: 1; } +} + .callout { padding: 12px 16px; border-radius: 6px; diff --git a/reports/snowflake-platform-assessment/attack-chains.html b/reports/snowflake-platform-assessment/attack-chains.html index a34da99..eddc3fb 100644 --- a/reports/snowflake-platform-assessment/attack-chains.html +++ b/reports/snowflake-platform-assessment/attack-chains.html @@ -17,6 +17,8 @@
    • Attack chains
    • Detection surface
    • Recommendations
    • +
    • Incident response
    • +
    • Healthcare overlay
    • Appendix
    • @@ -32,6 +34,61 @@

      Attack chains

      Hypothesisreachable from documented primitives; not yet exercised end-to-end. Treat as a research direction, not a finding. + + +

      Residual-risk profile

      +

      Maturity grades evidence strength; residual risk grades post-mitigation exposure. The two are different axes — a chain can be Empirical but Low-residual after the P0 fix, or Modeled but Medium-high-residual because the structural surface is not closable by configuration alone.

      + +
      + How to read the residual columns. + Default residual assumes Snowflake's post-UNC5537 defaults + are on (mandatory MFA on humans, network policies on service + users, default Trust Center scanners) but no additional + customer-side hardening. Post-P0 residual is what remains + after the + recommendations page's P0 + controls for this chain are implemented. Chains with a Post-P0 + residual of Medium-high (D, G, I) + depend on controls outside Snowflake's boundary or on a + structural audit gap; detection is the load-bearing layer and + the platform itself will not close the gap. +
      + + + + + + + + + + + + + + + + + + + + + + + + + + +
      ChainDefault residualPost-P0 residualWhat still bypasses Post-P0 (the structural residual)
      AHighMediumApproved-role misuse — attacker uses stolen credentials for an approved bulk-exporter role, exfils within that role's documented business-hours window at a volume below the role's p90 baseline. Invisible to the four-signal rule.
      BHighLowClosed by pinning Cortex Code CLI ≥ 1.0.25 across all developer endpoints. The broader class (future Cortex-Code-class RCE, other agent CLIs caching Snowflake tokens) is the residual concern.
      CMedium-highMediumMulti-stage deferred-loader where each individual version is scan-clean at publish time but the cumulative effect arrives via transitive resolution after install. NAAAPS does not catch this shape; native_app_dependency_drift.yml is the compensating signal but depends on consumer-side install-time observation.
      DHighMedium-highIdP-side compromise surface is outside Snowflake's controls. Even with FIDO/passkey on admins + IdP audit ingest + correlation rules, a Golden-SAML-class attack against a hardened IdP remains the residual surface. Detection is partial mitigation, not prevention.
      EMedium-highLowConfiguration-level fix: enumerate every Storage Integration, replace wildcard storage_allowed_locations with bucket prefixes, bound IAM role with bucket-policy-side controls. Chain depends on over-broad config; once tightened, it no longer applies.
      FHighMediumService-user key material on CI/orchestration hosts remains the structural residual. Network policies, passphrases, rotation cadence, and managed secret stores reduce the surface but do not eliminate the CI-runner-compromise path. Orchestrator hardening is the dependent control and lives outside Snowflake.
      GMedium-highMedium-highSource-side audit gap is structural — QUERY_HISTORY does not project byte motion for share / replication primitives. Detection rules catch the share-creation DDL but the read events themselves remain invisible. Consumer-side audit acquisition (pre-arranged in the BAA) is the only complete close.
      HMediumLowConfiguration-level fix: enumerate every EAI, replace wildcard ALLOWED_NETWORK_RULES with explicit hostnames, deploy snowflake_spcs_eai_overbroad. Wildcards are the chain's necessary condition.
      IHighMedium-highBehavioral planner attacks (semantic injection, authority spoof, multi-turn poisoning) bypass keyword-based Guardrails detection. Row-access and masking policies at the table layer are the load-bearing control; the chain's residual after Guardrails alone is the agent's effective RBAC scope, which is the harder half of the problem.
      JMedium-highMediumVendors that cannot publish stable egress CIDRs cannot be locked down with network policies. Architectural move to scoped Direct Share (partner-as-consumer rather than partner-holding-our-credential) closes the chain entirely but is a multi-quarter effort.
      KMediumMediumPolaris API spec is still evolving; the tool's enumeration semantics may not align with future versions. The iceberg_table_outside_catalog_base.yml rule keyed on the catalog-base prefix is robust across spec revisions, but the offensive tool's enumeration paths are not. Residual is bounded by spec stability.
      LMediumMediumChain depends on IdP-side consent expansion that can happen without any Snowflake-side configuration change. Daily consent snapshot + diff is the detection beat; consent expansion within the 24-hour snapshot window remains a residual. OAuth integration default-role tightening is the prevention.
      MMediumLowConfiguration-level fix: deploy udf_with_eai_invocation, audit every EAI-bound UDF for PUBLIC invoke grants, lock EAI network rules to specific hostnames. The UDF + over-broad EAI + PUBLIC grant is the chain's necessary condition; remove any one and the chain doesn't apply.
      H+MediumLowConfiguration-level fix: pin all SPCS images by @sha256: digest, populate OPS.SECURITY.APPROVED_CONTAINER_REGISTRIES, deploy spcs_image_unpinned_or_external. Tag-based pinning is the chain's necessary condition.
      + +

      Chain detail

      +
      EmpiricalChain A — Credential theft to bulk exfil (replays UNC5537)
      diff --git a/reports/snowflake-platform-assessment/cve-inventory.html b/reports/snowflake-platform-assessment/cve-inventory.html index 7574ec8..1c22c9f 100644 --- a/reports/snowflake-platform-assessment/cve-inventory.html +++ b/reports/snowflake-platform-assessment/cve-inventory.html @@ -17,6 +17,8 @@
    • Attack chains
    • Detection surface
    • Recommendations
    • +
    • Incident response
    • +
    • Healthcare overlay
    • Appendix
    • @@ -30,14 +32,42 @@

      CVE inventory

      and any Snowflake-attributed entries posted to the OpenCVE feed.

      -

      High and medium severity — Snowflake-owned components

      + + +

      High and medium severity — Snowflake-owned components

      + +
      + Applicability detail. Each CVE below carries + four operational fields: Affected (component and version + range), Trigger (the exposure condition that must be + true for the bug to fire — log level, OS, configuration flag), + Artifact (where the exploit's residue appears — the + log file, audit view, or on-host artifact a detection rule + reads), and Detection (the specific Sigma rule in this + pack that depends on the CVE's artifact). The applicability + matrix at docs/analysis/snowflake-cve-applicability-matrix-2026.md + is the source-of-truth for entries marked [REQUIRES_TENANT] + — fields the vendor advisory does not enumerate. +
      + - + @@ -48,9 +78,12 @@

      High and medium severity — Snowflake-owned components

      @@ -59,9 +92,12 @@

      High and medium severity — Snowflake-owned components

      @@ -71,7 +107,11 @@

      High and medium severity — Snowflake-owned components

      @@ -80,7 +120,10 @@

      High and medium severity — Snowflake-owned components

      @@ -90,7 +133,11 @@

      High and medium severity — Snowflake-owned components

      @@ -99,7 +146,11 @@

      High and medium severity — Snowflake-owned components

      @@ -108,7 +159,11 @@

      High and medium severity — Snowflake-owned components

      @@ -117,7 +172,11 @@

      High and medium severity — Snowflake-owned components

      @@ -126,19 +185,30 @@

      High and medium severity — Snowflake-owned components

      - +
      CVE Component CVSSSummary and red-team relevanceSummary, applicability, and detection
      Improper validation of shell commands — the agentic CLI executed a wget | sh-style command issued via indirect prompt injection (malicious README in a reviewed repository). The executed script - read cached Snowflake tokens from disk and used them to issue arbitrary SQL. Fixed in Cortex Code CLI - 1.0.25 (2026-02-28). Disclosed by PromptArmor. -
      Relevance: canonical chain for AI-agent exploitation; token theft from developer host without endpoint compromise. + read cached Snowflake tokens from disk and used them to issue arbitrary SQL. Disclosed by PromptArmor. +

      + Affected: Cortex Code CLI all versions ≤ 1.0.24. Fixed in 1.0.25 (2026-02-28).
      + Trigger: user runs Cortex Code against a prompt-injection-bearing input (malicious README.md, poisoned MCP tool output, hostile commit description). No special log level required.
      + Artifact: (a) Cortex Code CLI session log at ~/.cortex/sessions/*.log records the tool-call name and the executed shell — observable via EDR file telemetry or shell history; (b) Snowflake LOGIN_HISTORY records a follow-on KEY_PAIR login from the attacker's source IP if cached tokens were exfiltrated.
      + Detection: cortex_code_pre_1_0_25.yml (endpoint version-string) paired with cortex_code_session_to_unknown_session.yml (correlates the developer's Cortex Code session against a subsequent Snowflake login from a new source). Status: [VENDOR_PATCHED].
      7.8 H Untrusted search-path / writeable PATH directory on Windows. A local attacker who can plant a binary - earlier in PATH gains privilege escalation from the JDBC process. Affects shared developer workstations - and CI agents. -
      Relevance: local privilege escalation from any host running an unpatched JDBC driver. + earlier in PATH gains privilege escalation from the JDBC process. +

      + Affected: Snowflake JDBC on Windows only. Affected version range [REQUIRES_TENANT] — not enumerated in the public NVD record; check the JDBC release-notes page for the patched build. Linux/macOS hosts are not in scope.
      + Trigger: attacker can place an executable in a directory present in PATH before the legitimate binary's directory. Requires local write access to a PATH directory or PATH being writable by a non-admin user.
      + Artifact: Windows Event Log 4688 process-creation event (or EDR equivalent) showing the JDBC process spawning an unexpected binary from a non-system directory.
      + Detection: None in this pack; endpoint-side rule. Closest pairing is generic Windows path-precedence detection content outside this assessment. Status: [VENDOR_PATCHED], Windows-only.
      Command injection via a malicious SSO server response. The JDBC driver passed an SSO redirect URL to a local browser launcher without sanitization. -
      Relevance: the SSO integration path is an RCE surface, not just an auth surface. +

      + Affected: JDBC; fixed in 2023. Exact pre-fix minor version [REQUIRES_TENANT]. Risk reintroduces if a customer pins to a pre-fix JDBC version — common in long-lived warehouse pipelines.
      + Trigger: user initiates an SSO login flow against an attacker-controlled IdP host (or against the legitimate IdP through an attacker-controlled network path). The driver passes the malicious redirect URL containing shell metacharacters to the local browser launcher.
      + Artifact: on-host process-creation event (browser launcher invoked with an attacker-supplied URL); Snowflake LOGIN_HISTORY shows the resulting session if the chain succeeds.
      + Detection: None platform-side; endpoint telemetry is the detection surface. SSO-redirect MITM is generally a host-side concern, not an ACCOUNT_USAGE one. Status: [VENDOR_PATCHED].
      7.3 H Same class as CVE-2023-30535 — browser-launch command injection through the SSO redirect in the Node.js connector. -
      Relevance: the SSO command-injection class affects multiple connectors; check that all connectors in the stack are patched. +

      + Affected: Node.js Connector; fixed in 2023.
      + Trigger / Artifact / Detection: identical to CVE-2023-30535; check that all connectors in the stack (not just JDBC) are patched.
      + Status: [VENDOR_PATCHED].
      Client-side encryption silently disabled on PUT uploads under a specific configuration combination. Data uploaded to external stages was sent unencrypted despite configuration intent. -
      Relevance: data-in-transit confidentiality failure; relevant to compliance posture and to attackers with network visibility. +

      + Affected: Snowflake JDBC; specific affected range [REQUIRES_TENANT] — the vendor advisory names a configuration combination rather than a version.
      + Trigger: customer has client_encryption_key_size = 0 or a related disabling config and uses PUT to an external stage. Data is uploaded unencrypted client-side despite the customer expecting encryption.
      + Artifact: none in Snowflake audit (the data motion records as a normal PUT). Misconfiguration is observable in driver session logs at DEBUG level, in network capture (TLS to S3/Blob, but no client-side encryption envelope), and at the destination object's encryption metadata.
      + Detection: none — this is a silent fail, not an active-exploit signal. Pair with periodic audits of client_encryption_key_size on every connection config. Status: [VENDOR_PATCHED].
      6.5 M Directory traversal via custom components. Affects both standalone Streamlit and the runtime bundled with Snowflake. -
      Relevance: Streamlit-in-Snowflake is the same codebase; confirm runtime version matches the patched release. +

      + Affected: Streamlit custom components; version range [REQUIRES_TENANT] — verify SiS runtime version matches the patched release.
      + Trigger: app loads a custom component whose path argument is user-controlled.
      + Artifact: the traversed file's contents are surfaced to the attacker via the custom component's render output; Snowflake STREAMLIT app logs would surface the path argument app-side.
      + Detection: none platform-side. App-side audit on the path argument. Status: [VENDOR_PATCHED].
      5.9 M Reflected XSS via URL parameter handling. Attackers can craft links that execute JavaScript in the context of a Streamlit app. -
      Relevance: XSS in Streamlit-in-Snowflake can steal session tokens or issue SQL via the app's warehouse connection. +

      + Affected: Streamlit URL-parameter handling; version range [REQUIRES_TENANT].
      + Trigger: user clicks an attacker-crafted URL into a vulnerable Streamlit app.
      + Artifact: browser-side execution in the user's session context. For Streamlit-in-Snowflake this gives the attacker the user's Snowflake session token.
      + Detection: none platform-side. Status: [VENDOR_PATCHED].
      4.7 M SSRF on Windows via malicious UNC paths. -
      Relevance: SSRF from Streamlit-in-Snowflake reaches the Snowflake worker network. +

      + Affected: Streamlit (SiS and standalone), Windows-only. Affected version range [REQUIRES_TENANT].
      + Trigger: app accepts a user-provided path that resolves to a UNC path on a Windows host. The Streamlit runtime fetches the UNC target, which can be on attacker-controlled SMB.
      + Artifact: network outbound to attacker SMB; for SiS this is constrained by the SPCS egress policy (Chain H — an over-broad EAI makes this exploitable, an empty/scoped EAI does not).
      + Detection: indirectly via Chain H content (snowflake_spcs_eai_overbroad.yml + the Trail pair) — the SSRF would not reach the attacker without an EAI that permits it. Status: [VENDOR_PATCHED], Windows-only.
      4.4 M Temporary credential cache written world-readable on Linux. A local user on a shared host can read another user's cached token. -
      Relevance: high-value on shared CI runners or multi-tenant Airflow hosts. +

      + Affected: Snowflake Connector for Python; affected versions ≤ 3.x prior to fix, exact minor [REQUIRES_TENANT]. Linux only — file-permission semantics differ on Windows.
      + Trigger: Linux host has multiple local users; an attacker is one of the non-privileged users; the target user has previously authenticated and has a cached token in ~/.snowflake/. The attacker reads the cached token.
      + Artifact: the cached credential file (~/.snowflake/credentials*) has world-readable mode. Audit-observable as a sudden Snowflake login from a user different from the owner of the credential cache. LOGIN_HISTORY shows the second login with the same auth_method but a different source-IP or session signature.
      + Detection: indirectly via snowflake_keypair_auth_abuse.yml — the resulting login from an unexpected source is the actionable signal. The cred-cache theft itself is endpoint-side. Status: [VENDOR_PATCHED], Linux-only.
      CVE-2024-28851 Hive MetaStore Connector 4.0 MElevation of privilege via content replacement in a helper script. Relevant to Iceberg-era Snowflake-Hive MetaStore deployments. + Elevation of privilege via content replacement in a helper script. Relevant to Iceberg-era Snowflake-Hive MetaStore deployments. +

      + Affected: Snowflake Hive MetaStore Connector; affected versions [REQUIRES_TENANT].
      + Trigger: attacker can write to a helper-script location the connector reads at install or first-run time. Local-host EoP.
      + Artifact: on-host filesystem; process-creation events from the privileged install path running the modified helper.
      + Detection: none platform-side. Status: [VENDOR_PATCHED]. +
      -

      Lower severity — connector stack secret-leakage cohort (2025)

      +

      Lower severity — connector stack secret-leakage cohort (2025)

      These CVEs share a class: secrets written to debug logs, or race conditions on temporary files containing credentials. Individually low CVSS, but any Snowflake connector host with debug logging enabled leaks credentials @@ -164,7 +234,7 @@

      Lower severity — connector stack secret-leakage cohort (2025)

      -

      Transitive driver-stack CVEs (JDBC 4.0.0 – 4.2.0)

      +

      Transitive driver-stack CVEs (JDBC 4.0.0 – 4.2.0)

      Driver releases bundle dependency-CVE rollups that are not Snowflake-owned vulnerabilities but are surfaced via the connector stack and detectable through SBOM scanning. The 2026 @@ -200,7 +270,7 @@

      Transitive driver-stack CVEs (JDBC 4.0.0 – 4.2.0)

      exposed cohort.

      -

      Class-level chain: connector debug logs to SIEM-mediated credential theft

      +

      Class-level chain: connector debug logs to SIEM-mediated credential theft

      The "Lower severity" cohort above all share a low individual CVSS score, but a defender should model them as a single class with a credible exfil path. The class is "secrets land in a debug @@ -240,7 +310,7 @@

      Class-level chain: connector debug logs to SIEM-mediated credential theft

      -

      Vendor-fix pending CVE assignment

      +

      Vendor-fix pending CVE assignment

      Tracked here so SBOM-aware programs can flag the affected client versions before formal CVE assignment lands. Both ship in the Snowflake Connector for Python @@ -277,7 +347,7 @@

      Vendor-fix pending CVE assignment

      -

      Ecosystem context — third-party SaaS token theft

      +

      Ecosystem context — third-party SaaS token theft

      Not a Snowflake CVE; recorded because the attack-surface picture is incomplete without it. A public incident in April 2026 saw a third-party analytics-SaaS provider's Snowflake-access tokens @@ -290,7 +360,7 @@

      Ecosystem context — third-party SaaS token theft

      be replayed from an attacker-controlled host.
      -

      A note on server-side CVE coverage

      +

      A note on server-side CVE coverage

      The CVE record is dominated by client-side components (drivers, connectors, the Cortex Code CLI). Snowflake's multi-tenant service resolves server-side issues without CVEs. An assessment cannot rely on the diff --git a/reports/snowflake-platform-assessment/detection.html b/reports/snowflake-platform-assessment/detection.html index ba3adc4..c0cb8d0 100644 --- a/reports/snowflake-platform-assessment/detection.html +++ b/reports/snowflake-platform-assessment/detection.html @@ -17,6 +17,8 @@
    • Attack chains
    • Detection surface
    • Recommendations
    • +
    • Incident response
    • +
    • Healthcare overlay
    • Appendix
    • @@ -31,15 +33,167 @@

      Detection surface

      every rule. Plan deployment accordingly:
      • 4 rules are production_ready — fire on raw audit / log surfaces the customer already ingests. Drop in.
      • -
      • 19 rules are requires_enrichment — need a SIEM-side enrichment pipeline to compute derived fields (role baselines, stage watchlists, business-hours windows). Templates ship under detection/snowflake/enrichment-templates/.
      • +
      • 20 rules are requires_enrichment — need a SIEM-side enrichment pipeline to compute derived fields (role baselines, stage watchlists, business-hours windows, ingestion watermarks). Templates ship under detection/snowflake/enrichment-templates/.
      • 4 rules are requires_correlation — need an external audit stream (IdP sign-in events, Cortex Code session logs) joined to the Snowflake side.
      • 5 rules are requires_cortex_sidecar — need a Cortex Agents per-step trace surfaced by a sidecar; Snowflake's first-party views do not surface this depth.
      • 1 rule is requires_endpoint_telemetry — fires on host-side process / file telemetry, not Snowflake audit.
      - Of 33 Sigma rules, 4 work out of the box; the remaining 29 land an alert only after the relevant enrichment, correlation, or sidecar is operational. Treat the ENRICHMENT.md + enrichment-templates/ bundle as the deployment checklist, not optional reading. + Of 34 Sigma rules, 4 work out of the box; the remaining 30 land an alert only after the relevant enrichment, correlation, or sidecar is operational. Treat the ENRICHMENT.md + enrichment-templates/ bundle as the deployment checklist, not optional reading.
      -

      Rules that need work before they fire

      + + +

      Detection coverage matrix — chain × maturity tier

      +

      + The matrix below maps each attack chain to the rules that detect + it, sliced by deployment-maturity tier. A row dense in + Production-ready rules is a chain a SOC can cover on + day one. A row whose only rules are sidecar-blocked is a + chain where detection is gated on a separate engineering effort + — the + recommendations page names + the policy-layer compensating control to use while the gate is + being unlocked. +

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      ChainProduction-ready (fires today)Requires enrichmentRequires correlationRequires sidecar / endpoint telemetry
      Asnowflake_bind_param_audit_gap.yml (pair); cross-cutting: connector_secret_leak_in_logs.ymlbulk_exfil_baseline.yml, bulk_exfil_baseline_trail.yml
      Bcortex_code_session_to_unknown_session.ymlcortex_code_pre_1_0_25.yml (endpoint EDR command_line)
      Cnative_app_unexpected_version_bump.yml, native_app_privilege_bump.yml, native_app_privilege_bump_trail.yml, native_app_dependency_drift.yml
      Dfederated_login_anomaly.yml; cross-cutting: snowflake_scim_role_race.yml
      Esnowflake_storage_integration_misuse.yml, snowflake_storage_integration_misuse_trail.yml
      Fcross-cutting: connector_secret_leak_in_logs.ymlsnowflake_keypair_auth_abuse.yml, snowflake_keypair_auth_abuse_trail.yml, snowflake_pat_anomaly.yml
      Gsnowflake_share_creation_unknown_consumer.yml, snowflake_share_creation_unknown_consumer_trail.yml, snowflake_replication_group_unknown_target.yml, snowflake_replication_group_unknown_target_trail.yml
      Hsnowflake_spcs_eai_overbroad.yml, snowflake_spcs_eai_overbroad_trail.yml
      Icortex_agent_directive_followup.yml, cortex_agent_directive_followup_trail.yml, cortex_agent_followup_without_user_intent.yml, cortex_agent_sql_from_tool_output.yml, cortex_search_rank_anomaly.yml (all sidecar-blocked)
      Jpartner_integration_credential_replay.yml, partner_integration_credential_replay_trail.ymlcross-cutting: federated_login_anomaly.yml
      Kiceberg_table_outside_catalog_base.yml
      Loauth_integration_scope_drift.yml; cross-cutting: snowflake_scim_role_race.yml
      Mudf_with_eai_invocation.yml
      H+spcs_image_unpinned_or_external.yml
      + +
      + Reading the matrix. Chains H + and A (with the bind-param pair) have day-one + coverage. Chain I's full rule set is + sidecar-blocked — deploy the Snowpark wrapper at + tools/llm-attacks/cortex/lab-validation/observe_cortex_agent_trace.sql + or enable Trail's cortex_agent.* event family to + unblock. Chains C, E, + F, G, J, + K, M, H+ + all gate on the enrichment pipeline at + detection/snowflake/enrichment-templates/ — these + are the largest single deployment lift but cover the volume + chains for an enterprise tenant. +
      + +

      Rules that need work before they fire

      The table below lists every rule whose default deployment posture is blocked until the customer wires up the dependency. A SOC that @@ -127,7 +281,7 @@

      Rules that need work before they fire

      under detection/snowflake/enrichment-templates/.

      -

      Primary audit sources

      +

      Primary audit sources

      @@ -185,7 +339,7 @@

      Primary audit sources

      -

      Blind spots

      +

      Blind spots

      ACCOUNT_USAGE latency — the ~45-minute lag makes Account Usage unsuitable for real-time response. Pair with Snowflake Trail or a streaming ingest of INFORMATION_SCHEMA views for sub-minute alerting.
      @@ -210,7 +364,7 @@

      Blind spots

      Cortex AI context visibility — Cortex Analyst and Agents pass data through Anthropic or Azure-OpenAI models. The payload leaving the Snowflake boundary during inference is not fully documented. Customers treating Snowflake as an in-boundary data store should understand what flows to third-party LLM endpoints.
      -

      High-value detection queries

      +

      High-value detection queries

      -- New external stages created in the last 24 hours
       SELECT stage_name, stage_url, created
       FROM SNOWFLAKE.ACCOUNT_USAGE.STAGES
      @@ -269,7 +423,7 @@ 

      High-value detection queries

      OR query_text ILIKE 'CREATE%MANAGED%ACCOUNT%') ORDER BY start_time DESC;
      -

      Incident-response queries

      +

      Incident-response queries

      Queries oriented toward containment and forensic enumeration after a credential or session has been flagged. These complement the proactive detection rules above. diff --git a/reports/snowflake-platform-assessment/healthcare-overlay.html b/reports/snowflake-platform-assessment/healthcare-overlay.html new file mode 100644 index 0000000..06a0665 --- /dev/null +++ b/reports/snowflake-platform-assessment/healthcare-overlay.html @@ -0,0 +1,240 @@ + + + + + + Healthcare Overlay — Snowflake Assessment + + + +

      + +
      +
      +

      Healthcare Overlay

      +

      Re-framing the platform-generic chain catalog for organizations that hold PHI, payor data, or research datasets on Snowflake — and that operate under HIPAA, HITECH, the HHS-OCR breach-reporting regime, and (where applicable) 42 CFR Part 2 and state-level health-privacy laws.

      + +
      + Not legal advice, not a compliance attestation. This page is a red-team companion that names where the platform chains intersect with the controls and reporting obligations a healthcare security program is already responsible for. The HIPAA citations are paraphrased; the authoritative source is the HHS Security Rule Guidance. The analytical companion at docs/analysis/snowflake-healthcare-overlay-2026.md is the source-of-truth document, including four copy-paste-ready risk-register entries (SNOW-A, SNOW-F, SNOW-G, SNOW-J). +
      + + + +

      Why Snowflake is a healthcare crown jewel

      +

      Snowflake sits at the intersection of three healthcare data flows that were historically siloed: clinical data (Epic Clarity / Caboodle, Cerner HealtheIntent, HL7 v2 / FHIR feeds, lab and imaging metadata), claims and financial data (X12 837 / 835, eligibility, formulary, denial workflow), and operational / research data (analytics marts, research cohorts, value-based-care, SDOH, prior-authorization). A typical 2026 healthcare data platform has all three flowing into a small number of curated databases, with Cortex Analyst and Cortex Search sitting on top. The blast radius of an account compromise is every patient the organization has ever treated, not a single table or system.

      + +

      Three implications for the threat model

      +
      + Re-identification of “de-identified” marts. Cortex Search and Cortex Analyst, fed with auxiliary tables (zip → census, lab values → cohort tags), make re-identification of limited-data-set or de-identified marts materially easier. A red-team assessment must treat any dataset with residual quasi-identifiers (DOB, 5-digit ZIP, race, rare diagnosis codes) as PHI-equivalent for chain-impact scoring. +
      +
      + Payor-provider sharing through Snowflake. Multi-tenant payor / provider data sharing is increasingly implemented through Snowflake Data Sharing / Replication (Chain G), not nightly SFTP. This makes Chain G's source-side audit gap a primary §164.312(b) audit-control issue. +
      +
      + AI agents acting on patient data. Cortex Agents wrapping Cortex Analyst and stored procedures can read, summarize, and (where wrapped with a DML procedure) modify patient records. The “minimum necessary” requirement of §164.502(b) is not naturally enforced by Cortex unless row-access and masking policies are correctly applied at the table layer. +
      + +

      MFA enforcement boundary — human vs. service users

      +

      A recurring source of confusion in 2026 healthcare Snowflake reviews: where exactly does Snowflake's April 2025 MFA enforcement bind? The answer determines which chains in the catalog are easy-credential-replay surface and which are not.

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      User classAuth methodMFA enforcement
      Human usersPassword + MFAMandatory at Snowflake. The April 2025 single-factor-password block is enforced server-side; users without an enrolled MFA factor cannot complete login.
      Human usersSAML / OAuth (federated)Enforced at the IdP, not Snowflake. Snowflake trusts the IdP's authentication; if the IdP allows password-only sign-in, Snowflake honors the resulting assertion. The customer's IdP owns this control.
      Service usersKey-pair (JWT)Not applicable. Key-pair authentication is, by design, single-factor — the credential is the RSA private key. The compensating control is the bound network policy. October 2024 mandatory MFA default and April 2025 enforcement explicitly scope to human users.
      Service usersPATNot applicable. A PAT is itself a bearer credential. Compensating controls are scope-limitation and short TTLs.
      Service usersOAuth client credentialsNot applicable. Client-credentials flow is service-to-service; MFA is meaningless on it.
      +

      Chain A's “human users are largely covered by the April 2025 enforcement” should be read in this exact sense: humans were the primary 2024 UNC5537 vector and they are now out of the easy-credential-replay surface. Service users (Chain F, Chain J) are the post-2025 successor surface and remain credential-bearer-only under the platform's own design.

      + +

      HIPAA Security Rule control mapping

      +

      The chain-by-chain map below cites HIPAA Security Rule subsections (e.g., §164.312(b)). Each citation is a deliberate hedge — the chain challenges a control's design intent; it is not a legal finding that the control is violated. This section grounds each cited control in its actual regulatory text and names what the platform-side gap means for the control's design.

      + + + + + + + + + + + + + + + + + + + + + + +
      SubsectionControl intent (paraphrased)Platform-side gap
      §164.308(a)(1)(ii)(A)Risk Analysis — accurate and thorough assessment of risks and vulnerabilities to PHI.Platform misconfiguration (over-broad EAI, wildcard storage integration) is a risk the program must surface in its analysis; the platform does not produce it as a finding.
      §164.308(a)(5)(ii)(B)Protection from Malicious Software — guard against, detect, and report malicious software.Cortex Code on developer endpoints is “software the workforce uses”; the CVE-2026-6442 class is the platform's contribution to this surface.
      §164.308(a)(5)(ii)(D)Password Management — creating, changing, and safeguarding passwords.Service-user key-pair material on CI runners / orchestration hosts is the modern “password” under the rule's text. The cite covers the credential's lifecycle, not just human passwords.
      §164.308(b)Business Associate Contracts — written contracts with each BA creating, receiving, maintaining, or transmitting PHI.Chain J: a partner SaaS holding the customer's Snowflake credentials is a sub-BA. Compromise is a §164.308(b) gap unless the BAA covers credential-storage practice.
      §164.312(a)(1)Access Control — technical policies granting access only to authorized persons or programs.Chains A / D / F: any credential abuse granting access beyond the role's intended scope. Least-privilege RBAC design is the customer's responsibility; the platform enforces what is configured.
      §164.312(a)(2)(i)Unique User Identification — assign a unique name/number for identifying user identity.Chains B / M: where the audit trail attributes the action to the user but the action was taken by an agent (Cortex Code, an EAI-bound UDF owned by another user), unique-identification is challenged.
      §164.312(b)Audit Controls — mechanisms that record and examine activity in information systems that contain or use PHI.Chain G: source-side audit gap on direct shares / replication means the customer cannot examine “who read which patient records via the share.” The most direct platform-side audit-controls gap in the chain catalog.
      §164.312(c)(1)Integrity — protect PHI from improper alteration or destruction.Chain K (Polaris metadata-pointer poisoning): the table name is unchanged, the data behind it is replaced. The integrity control on the underlying PHI is bypassed without the customer's audit surfacing the swap.
      §164.312(d)Person or Entity Authentication — verify that a person seeking access is the one claimed.Chain D: a Golden-SAML-class forged assertion satisfies Snowflake's authentication path; the verification step the rule mandates is the IdP's, and the gap is in cross-system audit.
      §164.312(e)(1)Transmission Security — protect against unauthorized access to PHI transmitted over a network.Chains E / H: cross-cloud pivot via storage integration or SPCS EAI is a transmission-security event the customer must inspect at the cloud-network layer. Snowflake audit captures the grant, not the bytes.
      §164.314(a)Business Associate Contracts (technical safeguards) — BA contracts must include specific provisions covering technical safeguards.Chain C: Native App providers receiving PHI via consumer grants must have BAAs covering the technical safeguards they implement. Auto-update changing data-receipt scope is a BAA-scope event, not just a technical-config event.
      §164.502(b)Minimum Necessary — use, disclose, or request only the minimum PHI necessary for the intended purpose.Chain I: a Cortex Agent steered by tool-output injection into over-fetching patient records exceeds minimum-necessary scope. The technical control is row-access / masking policies at the table layer.
      + +

      Chain-by-chain PHI impact map

      +

      The “default residual” column assumes Snowflake's post-UNC5537 defaults are turned on at the customer side (mandatory MFA on humans, network policies on service users, default Trust Center scanners enabled). It is not a measure of platform security with all hardening turned on — it is a measure of what an average 2026 healthcare Snowflake account actually looks like.

      + + + + + + + + + + + + + + + + + + + + + + + + +
      ChainPHI surface reachedHIPAA control challengedDefault residual (post-UNC5537 defaults)
      AWhatever the compromised user can SELECT — analyst patient mart, claims fact tables, EHR-Clarity export. A single role often grants read on millions of patient records.§164.312(a)(1), §164.308(a)(5)(ii)(D), §164.312(b)High. Service users (dbt, Airflow, BI connectors) on key-pair auth without network policies remain the most common gap. Human users are largely covered by the April 2025 enforcement.
      BCached Snowflake token in ~/.snowsql/ or ~/.snowflake/ plus whatever the developer can SELECT. For a healthcare data engineer this is typically the full warehouse.§164.308(a)(5)(ii)(B), §164.312(a)(2)(i)High until the Cortex Code CLI version pin is enforced across all developer endpoints. Detection is endpoint-side, not Snowflake-side.
      CTables exposed to an installed Native App via consumer grants — commonly the curated patient mart for population-health, payor-quality reporting, and ML inference apps.§164.314(a)Medium-high. Many Healthcare-and-Life-Sciences listings request broad grants and consumers accept the auto-update default.
      DWhatever role(s) the targeted user holds in the IdP-to-Snowflake mapping — frequently ACCOUNTADMIN-class for the data platform team.§164.312(d)High where Golden-SAML-class attacks succeed against the IdP. Snowflake has no visibility into IdP-side compromise except via cross-system correlation that requires both surfaces ingested.
      EAny cloud-storage location the integration's storage_allowed_locations reaches — EHR archive buckets, claims-data lakes, imaging repositories.§164.312(e)(1)Medium-high. Wildcard storage_allowed_locations is a documented anti-pattern; legacy integrations still exhibit it.
      FIdentical to Chain A but with no MFA-replay defense — the JWT is signed offline. Service users on key-pair auth are explicitly out of scope of the April 2025 enforcement by design.§164.308(a)(5)(ii)(D), §164.312(c)(1)High where the key-pair user has no bound network policy. Snowflake's own top callout: the platform documents this configuration as the highest-risk shape.
      GThe full content of a database designated as a share's secure object. Healthcare orgs frequently share patient cohorts with research collaborators or downstream payors using this feature.§164.312(b) — the most consequential audit-trail gap on the platform for healthcare reporting.Medium-high. Once an attacker reaches ACCOUNTADMIN or a role with OWNERSHIP on the share, data motion is silent on the source audit log.
      HAny data the SPCS service handles. Healthcare Cortex / ML workloads in SPCS often handle PHI directly (model inference on patient records, NLP on clinical notes).§164.312(e)(1), §164.308(a)(1)(ii)(A)Medium. New SPCS deployments increasingly use narrower EAI scopes; legacy ones often have wildcard rules.
      IWhatever the agent is allowed to query. In a population-health flow, this is the full curated patient mart. Cortex Search indexes over clinical free text are both data-leak and injection-payload-delivery surfaces.§164.502(b) Minimum NecessaryHigh. Cortex Guardrails was GA only in early 2026; adoption is uneven. The chain assumes a correct RBAC model underneath the agent, which is the harder half of the problem in any real healthcare deployment.
      JWhatever the partner-held credential can read. Common healthcare partners (Fivetran, Matillion, dbt Cloud, BI vendors) often hold ACCOUNTADMIN-adjacent service users.§164.308(b), §164.312(a)(1)Medium-high. The partner-side compromise surface is outside the customer's network policy. Many healthcare-vertical SaaS providers do not publish stable egress CIDRs.
      KIceberg-warehoused PHI tables (de-identified extracts, research cohorts) potentially re-identified via pointer poisoning — the table name is unchanged while the data behind it is replaced.§164.312(c)(1) IntegrityMedium. Modeled against the Polaris REST catalog spec as of May 2026; the API is evolving and the tool should be validated against each deployment's actual Polaris version.
      LRole mapping drift via External OAuth consent expansion grants a federated user broader PHI access than originally intended.§164.312(a)(1)Medium. The drift happens at the IdP layer; Snowflake side has no configuration change to detect it.
      MPer-row PHI sent to an attacker endpoint via a UDF invoked over a patient table — an analyst's SELECT triggers exfil through the EAI-bound function the analyst did not author.§164.312(e)(1), §164.312(a)(2)(i)Medium. Detection depends on joining FUNCTIONS against INTEGRATIONS and noticing analyst-role invocations of UDFs owned by service roles.
      + +

      Cortex over patient data — specific questions

      +

      These questions belong on the table for any healthcare org running Cortex over patient data, regardless of which chains are observed exercising them.

      +
      + Boundary leakage. Cortex final-response generation reaches out of the Snowflake boundary to a third-party model provider (Anthropic, Azure OpenAI). What payload is sent? What is the provider's retention? Is the BAA with the third-party model provider in place, and does it cover the type of PHI that flows (clinical notes vs. claims codes vs. structured demographics)? +
      +
      + Cortex Search over clinical free text. Any indexed corpus of clinical notes, denial letters, appeal documents, or patient-portal messages is both a data-leak surface and an injection-payload delivery surface. Treat the index itself as PHI-bearing. +
      +
      + Cortex Analyst's semantic model is policy. The semantic model defines what an analyst-facing agent can query. In healthcare it is effectively a minimum-necessary policy expressed as YAML/JSON. Review it as a security artifact, not just an analytics one. +
      +
      + Cortex Agents with DML tools. An agent wrapping Cortex Analyst (read) with a stored procedure that does DML (write) breaks the “Analyst is SELECT-only” guarantee. Inventory every agent's tool set; flag any that combine a read tool over PHI with a write tool anywhere. +
      +
      + Guardrails policy applicability. The default Cortex Guardrails policy is not tuned for healthcare-specific abuse (PHI extraction prompts, cohort fishing, re-identification attempts). The repository's Guardrails harness carries a family=Healthcare corpus tier covering PHI extraction, cohort fishing, Sweeney-class re-identification, Safe Harbor de-identification bypass, minimum-necessary violation, and BAA-scope-violation shapes. Run both tiers and report the per-family residual-risk delta. Treat guardrails as one layer of a defense-in-depth stack — load-bearing controls are row-access policies on PHI tables, minimum-necessary scoped views, share-target allowlists, and audit on ALTER SHARE … ADD ACCOUNTS. +
      + +

      Audit-retention sufficiency for OCR reconstruction

      +

      HHS-OCR can request audit reconstruction up to six years post-event (§164.530(j) documentation retention period). Snowflake-side retentions that matter:

      + + + + + + + + + + + + + + +
      SurfaceRetentionNotes
      SNOWFLAKE.ACCOUNT_USAGE.LOGIN_HISTORY365 daysInsufficient for the six-year OCR window.
      SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY365 daysInsufficient for the six-year OCR window.
      Snowflake Trail (event stream)Customer-controlled (sink)Becomes whatever retention the SIEM / data lake gives it.
      Streaming-ingest polling of INFORMATION_SCHEMA.QUERY_HISTORY()Customer-controlled (sink)Same — retention is the downstream's, not Snowflake's.
      +
      + Practical implication. A healthcare Snowflake deployment cannot rely on Snowflake's first-party retention for OCR-grade reconstruction of a breach older than a year. The org's SIEM, data lake, or dedicated audit warehouse must hold a copy. Two specific gaps to plan around: +
        +
      • Chain G's source-side blind spot. Replication / Direct Share data motion that does not appear in source-side QUERY_HISTORY also does not appear in the streamed projection of it. The consumer-side audit (which the consumer owns) is the only place the read shows up.
      • +
      • Cortex Agent step traces. Where CORTEX_AGENT_HISTORY-style views or Trail are not enabled, treat Cortex Agent activity as audit-thin and gate the agent's PHI access at the row-access-policy layer instead.
      • +
      +
      + +

      What to add to the engagement runbook

      +

      Items a healthcare-specific Snowflake engagement should add over a generic platform assessment, regardless of which chains are in scope:

      +
        +
      • Inventory of PHI-bearing surfaces. Per-database, per-schema, a classification (PHI / LDS / De-id / Non-PHI) signed off by the privacy office. Without this, chain impact scoring is guesswork.
      • +
      • Per-role minimum-necessary review. For every role with SELECT on a PHI-bearing schema, confirm the role's user population, IdP-group mapping, and use case align with minimum-necessary.
      • +
      • BAA inventory cross-referenced against installed Native Apps and partner integrations. Every share consumer, every Native App receiving grants on PHI-bearing schemas, every partner SaaS holding a Snowflake credential should have a corresponding BAA.
      • +
      • Cortex agent semantic-model and tool-set review. The semantic model is policy; the tool set is the action surface.
      • +
      • OCR reconstruction tabletop. Pick a date 18 months back; can the org produce a full audit trail of who accessed PHI table X between dates Y and Z? If the answer requires data the org does not have, that gap is a §164.312(b) finding regardless of whether any chain has been exercised.
      • +
      • Incident-response runbook addition: cross-account share acquisition. For Chain G the consumer-side audit is the only source. Pre-build the legal and technical path to acquire it before it is needed.
      • +
      + +

      Risk register templates

      +

      HIPAA §164.308(a)(1)(ii)(A) requires a documented risk analysis. The chains in this overlay end up in the covered entity's risk register; four copy-paste-ready entries (SNOW-A service-user credential replay; SNOW-F service-user key material on CI; SNOW-G server-side data motion bypassing query-level audit; SNOW-J third-party SaaS holding Snowflake credentials) are maintained in the analytical companion at docs/analysis/snowflake-healthcare-overlay-2026.md. Each entry follows a consistent shape: Threat / Vulnerability / Likelihood / Impact / Existing Controls / Residual / Owner / Review Cadence. Tenant-specific values (population size, service-user inventory size, partner count) are [REQUIRES_TENANT] placeholders; substituting invented numbers for measurement is worse than no entry.

      + +

      Cross-references

      +
        +
      • Attack chains — the platform-generic chain catalog this page re-frames.
      • +
      • Detection surface — Sigma rules, enrichment requirements, and the streaming-ingest pipeline that closes the ACCOUNT_USAGE latency gap.
      • +
      • Recommendations — the controls a covered entity implements; this page names why each one matters for HIPAA.
      • +
      • Analytical companion: docs/analysis/snowflake-healthcare-overlay-2026.md — risk-register templates, source-of-truth HIPAA mapping.
      • +
      • Cortex Guardrails harness: tools/llm-attacks/cortex/guardrails-harness/ — the healthcare corpus tier referenced above.
      • +
      +
      + +
      +

      + Source: github.com/AndrewAltimit/exploits +  ·  HIPAA Security Rule: HHS guidance +

      +

      All research targets publicly disclosed, patched CVEs. No vendor systems were tested without authorization. This page is not legal advice; HIPAA citations are paraphrased for threat-modeling purposes.

      +
      +
      +
      + + + diff --git a/reports/snowflake-platform-assessment/incident-response.html b/reports/snowflake-platform-assessment/incident-response.html new file mode 100644 index 0000000..050ae00 --- /dev/null +++ b/reports/snowflake-platform-assessment/incident-response.html @@ -0,0 +1,502 @@ + + + + + + Incident Response — Snowflake Assessment + + + +
      + +
      +
      +

      Incident response playbook

      +

      Detect → contain → remediate → validate — one playbook per chain, keyed to the SQL the SOC actually runs.

      + +
      + Scope. Each playbook below covers the + Snowflake-side actions a SOC takes in the first 60 minutes of + an active incident on the named chain. Cloud-side (S3/Azure/GCS + bucket-policy actions), IdP-side (Okta/Entra token revocation), + and endpoint-side (EDR isolation) steps are referenced but not + spelled out — those belong in the parallel cloud / IdP / + endpoint IR runbooks. The Snowflake-specific containment SQL + is what this page contributes. +
      + +
      + Before running containment SQL. Take the + forensic snapshot first. SHOW and + DESCRIBE commands are non-destructive; capture + their output to evidence storage before running any + ALTER, REVOKE, or DROP. + Once a user is disabled or a share is dropped, the audit + record of what the attacker reached may become harder + to reconstruct. Snowflake's SNOWFLAKE.ACCOUNT_USAGE + retention is 365 days; for OCR-grade reconstruction (six + years), forensic capture goes to the long-retention SIEM sink + documented in the + recommendations timeline. +
      + + + +

      Chain A — Credential theft → bulk exfil

      +

      Trigger: bulk_exfil_baseline.yml or bulk_exfil_baseline_trail.yml fires; or a COPY INTO @external_stage to an unrecognised destination shows up in QUERY_HISTORY.

      + +
      +
      Detect
      +
      +
        +
      • Pull the offending QUERY_HISTORY rows for the firing session — query text, bytes written, external stage URL, user/role/client IP/session ID.
      • +
      • Pull LOGIN_HISTORY for the user across the last 30 days — identify the session's FIRST_AUTHENTICATION_FACTOR, source IP, and any prior anomalous logins from the same IP.
      • +
      • Identify every CREATE STAGE by the user in the last 30 days; the attacker's exfil destination may have been created earlier as a staging step.
      • +
      +
      -- Forensic snapshot (run first, capture output)
      +SELECT user_name, role_name, query_id, query_text, bytes_written_to_result,
      +       external_stage_url, client_ip, session_id, start_time
      +  FROM snowflake.account_usage.query_history
      + WHERE user_name = '<user>'
      +   AND start_time > dateadd(hour, -24, current_timestamp());
      +
      +SELECT login_history.* FROM snowflake.account_usage.login_history
      + WHERE user_name = '<user>'
      +   AND event_timestamp > dateadd(day, -30, current_timestamp())
      + ORDER BY event_timestamp DESC;
      +
      +
      + +
      +
      Contain
      +
      +
      -- Disable the user immediately; preserve the audit attribution.
      +ALTER USER <user> SET DISABLED = TRUE;
      +
      +-- Suspend any task the user owns (the attacker may have scheduled persistence).
      +-- Enumerate first, then suspend each named task.
      +SHOW TASKS IN ACCOUNT;
      +ALTER TASK <db>.<schema>.<task_name> SUSPEND;
      +
      +-- Tighten network policy to lock to a single placeholder address so any
      +-- token still active (key-pair, PAT) cannot reach Snowflake until cleared.
      +CREATE OR REPLACE NETWORK POLICY ir_lockdown_<user>
      +  ALLOWED_IP_LIST = ('127.0.0.1/32');
      +ALTER USER <user> SET NETWORK_POLICY = ir_lockdown_<user>;
      +

      If the user was authenticating by key-pair, also clear the public key so the cached private key cannot re-authenticate even if the network policy is later relaxed:

      +
      ALTER USER <user> SET RSA_PUBLIC_KEY = NULL, RSA_PUBLIC_KEY_2 = NULL;
      +

      If the user has active PATs, revoke them:

      +
      SHOW USER PROGRAMMATIC ACCESS TOKENS FOR USER <user>;
      +ALTER USER <user> REMOVE PROGRAMMATIC ACCESS TOKEN <pat_name>;
      +
      +
      + +
      +
      Remediate
      +
      +
        +
      • Rotate the credential: password reset (with MFA re-enrollment) for human users; new key-pair (with passphrase) for service users; new PAT with reduced scope.
      • +
      • Drop attacker-controlled stages: DROP STAGE <db>.<schema>.<stage>;
      • +
      • Review and tighten the user's role grants to enforce minimum-necessary for the post-incident posture.
      • +
      • If the user is a service identity, audit every CI / orchestration host that holds the credential and rotate on each host.
      • +
      +
      +
      + +
      +
      Validate
      +
      +
      -- Confirm no recent COPY INTO external_stage by the user.
      +SELECT count(*) FROM snowflake.account_usage.query_history
      + WHERE user_name = '<user>'
      +   AND query_type = 'COPY'
      +   AND query_text ILIKE '%@%'
      +   AND start_time > dateadd(hour, -1, current_timestamp());
      +
      +-- Confirm no logins from non-allowlisted IPs.
      +SELECT client_ip, count(*) FROM snowflake.account_usage.login_history
      + WHERE user_name = '<user>'
      +   AND event_timestamp > dateadd(hour, -1, current_timestamp())
      + GROUP BY 1;
      +

      Both queries should return 0 / empty. Once verified, re-enable the user with the new credential and the production network policy.

      +
      +
      + +

      Chain B — Cortex Code IPI → cred theft (CVE-2026-6442)

      +

      Trigger: cortex_code_pre_1_0_25.yml fires (endpoint EDR detected an unpatched CLI), or cortex_code_session_to_unknown_session.yml fires (developer host had a Cortex Code session immediately before a Snowflake login from a new IP).

      + +
      +
      Detect
      +
      +

      The host is the primary forensic surface; pull the developer endpoint's Cortex Code session log at ~/.cortex/sessions/*.log and shell history. Snowflake side: pull LOGIN_HISTORY for the developer across the post-Cortex-session window and check for KEY_PAIR logins from IPs outside the developer's historic range.

      +
      +
      + +
      +
      Contain
      +
      +

      Endpoint side: EDR isolation of the developer host (parallel runbook). Snowflake side:

      +
      -- Disable the developer user and clear cached-key authentication.
      +ALTER USER <developer> SET DISABLED = TRUE;
      +ALTER USER <developer> SET RSA_PUBLIC_KEY = NULL, RSA_PUBLIC_KEY_2 = NULL;
      +
      +-- Revoke any active PAT for the developer (Cortex Code may have cached one).
      +SHOW USER PROGRAMMATIC ACCESS TOKENS FOR USER <developer>;
      +ALTER USER <developer> REMOVE PROGRAMMATIC ACCESS TOKEN <pat_name>;
      +
      +
      + +
      +
      Remediate
      +
      +
        +
      • Force-upgrade Cortex Code CLI to ≥ 1.0.25 across all developer endpoints (package-manager rollout + EDR check).
      • +
      • Rotate every cached credential on the affected host: Snowflake tokens, GitHub tokens, cloud SDK credentials — assume anything Cortex Code could read is compromised.
      • +
      • Run the + retrospective hunt + against the pre-patch exposure window for every developer who ran Cortex Code before 2026-02-28.
      • +
      +
      +
      + +
      +
      Validate
      +
      +

      EDR confirms Cortex Code CLI version ≥ 1.0.25 across all in-scope endpoints. LOGIN_HISTORY shows no KEY_PAIR or OAUTH logins for the developer from non-allowlisted IPs in the post-rotation window.

      +
      +
      + +

      Chain C — Native App supply-chain

      +

      Trigger: native_app_unexpected_version_bump.yml, native_app_privilege_bump.yml, or native_app_dependency_drift.yml fires on an app installed in the consumer account.

      + +
      +
      Detect & Contain
      +
      +
      -- Identify the upgraded app + its current privilege scope.
      +SELECT application_name, current_version, previous_version, manifest_diff_added,
      +       auto_upgrade
      +  FROM snowflake.account_usage.application_history
      + WHERE event_type = 'APP_VERSION_INSTALLED'
      +   AND application_name = '<app>'
      + ORDER BY event_timestamp DESC LIMIT 5;
      +
      +-- Suspend the application immediately (preserves grants for forensics).
      +ALTER APPLICATION <app> SUSPEND;
      +
      +-- If suspension is not enough (e.g., the app's grants are actively being
      +-- exercised by a stored procedure on a Task), drop the application:
      +DROP APPLICATION <app>;
      +
      +
      + +
      +
      Remediate
      +
      +
        +
      • Audit every other consumer-side grant the app received; revoke any wide-scope grants that were not in the consumer's original approval.
      • +
      • If the app integrates with EAIs, drop any EAI the app introduced via the new version.
      • +
      • Notify the provider through their published security contact; gather forensic evidence to support the report.
      • +
      • Add the app to the OPS.SECURITY.NATIVE_APP_QUARANTINE watchlist so re-install attempts are caught.
      • +
      +
      +
      + +
      +
      Validate
      +
      +

      Confirm the application no longer appears in SHOW APPLICATIONS IN ACCOUNT; confirm no Tasks or stored procedures from the app's database are still scheduled. Run native_app_dependency_drift.yml against the rollback to confirm no orphaned dependencies remain.

      +
      +
      + +

      Chain D — Federated IdP compromise

      +

      Trigger: federated_login_anomaly.yml fires — Snowflake login with no corresponding IdP sign-in event after the watermark window has elapsed.

      + +
      +
      Detect
      +
      +

      The Snowflake side is downstream of the compromise. Coordinate with the IdP IR team immediately; the IdP-side actions (revoke the user's sessions, force re-auth with FIDO/passkey, rotate the IdP signing key if Golden SAML suspected) are the load-bearing containment.

      +

      Snowflake-side forensic snapshot:

      +
      SELECT user_name, authentication_method, client_ip, client_app_id, event_timestamp
      +  FROM snowflake.account_usage.login_history
      + WHERE user_name = '<user>'
      +   AND event_timestamp > dateadd(day, -7, current_timestamp())
      + ORDER BY event_timestamp DESC;
      +
      +
      + +
      +
      Contain (Snowflake side)
      +
      +
      -- Disable the user at Snowflake regardless of IdP-side status; the user's
      +-- forged assertion is what reached Snowflake, and disabling the user
      +-- locks out further federated logins until the IdP side is cleaned.
      +ALTER USER <user> SET DISABLED = TRUE;
      +
      +-- If the user holds ACCOUNTADMIN/SECURITYADMIN, immediately revoke and
      +-- reassign to a known-trusted operator.
      +REVOKE ROLE ACCOUNTADMIN FROM USER <user>;
      +REVOKE ROLE SECURITYADMIN FROM USER <user>;
      +

      If a Golden-SAML-class attack is suspected, the SAML integration itself may be untrusted until the IdP signing key is rotated. Optionally suspend the integration:

      +
      SHOW SECURITY INTEGRATIONS;
      +ALTER SECURITY INTEGRATION <saml_integration> SET ENABLED = FALSE;
      +
      +
      + +
      +
      Remediate
      +
      +
        +
      • Coordinate with the IdP IR team to confirm IdP-side compromise scope and rotate the IdP signing key.
      • +
      • Re-enable the SAML integration only after the IdP confirms the signing key has been rotated and the affected sessions revoked.
      • +
      • Audit every other user the IdP-side compromise could have authenticated as — particularly high-privileged roles.
      • +
      • Increase the rule's correlation strictness for the post-incident window (lower idp_correlation_window_minutes so any further anomalies fire faster).
      • +
      +
      +
      + +

      Chain F — Key-pair JWT auth abuse

      +

      Trigger: snowflake_keypair_auth_abuse.yml or snowflake_keypair_auth_abuse_trail.yml fires — a key-pair user signs in from a source outside the documented orchestration egress range.

      + +
      +
      Detect & Contain
      +
      +
      -- Forensic snapshot of the service user's login history.
      +SELECT login_history.*, network_policy.network_policy_name
      +  FROM snowflake.account_usage.login_history
      +  LEFT JOIN snowflake.account_usage.network_policies network_policy
      +    ON login_history.user_name = network_policy.user_name
      + WHERE login_history.user_name = '<service_user>'
      +   AND event_timestamp > dateadd(day, -30, current_timestamp())
      + ORDER BY event_timestamp DESC;
      +
      +-- Clear the public key so the leaked private key cannot sign new JWTs.
      +ALTER USER <service_user> SET RSA_PUBLIC_KEY = NULL, RSA_PUBLIC_KEY_2 = NULL;
      +
      +-- Disable the user as a belt-and-suspenders step.
      +ALTER USER <service_user> SET DISABLED = TRUE;
      +
      +
      + +
      +
      Remediate
      +
      +
        +
      • Identify every CI runner, dbt orchestration host, Airflow worker, and developer laptop that holds the leaked private key; rotate on each host with a new passphrase-protected key.
      • +
      • Bind a network policy to the user with allowed_ip_list matching the documented orchestration egress range.
      • +
      • Run pat_discovery.py against the lab fixture-root equivalents on the affected hosts to confirm no other credentials live in plain text at the same paths.
      • +
      • Migrate the credential to a managed secret store (Vault, AWS Secrets Manager, Azure Key Vault) so future rotation is centralized.
      • +
      +
      +
      + +

      Chain G — Direct Share / Replication exfil

      +

      Trigger: snowflake_share_creation_unknown_consumer.yml or snowflake_replication_group_unknown_target.yml fires — an ALTER SHARE … ADD ACCOUNTS or replication-group event targets an account not on the approved-consumer watchlist.

      + +
      +
      Detect
      +
      +
      SELECT share_name, accounts_added, owner_role, event_timestamp
      +  FROM snowflake.account_usage.shares
      + WHERE event_timestamp > dateadd(day, -7, current_timestamp())
      + ORDER BY event_timestamp DESC;
      +
      +SELECT replication_group_name, target_account_name, event_timestamp
      +  FROM snowflake.account_usage.replication_group_usage_history
      + WHERE event_timestamp > dateadd(day, -7, current_timestamp())
      + ORDER BY event_timestamp DESC;
      +
      +
      + +
      +
      Contain
      +
      +
      -- Remove the unauthorized consumer from the share.
      +ALTER SHARE <share_name> REMOVE ACCOUNTS = ('<attacker_org>.<attacker_account>');
      +
      +-- If the entire share is suspect, drop it.
      +DROP SHARE <share_name>;
      +
      +-- For replication groups, suspend before dropping.
      +ALTER REPLICATION GROUP <group_name> SUSPEND;
      +DROP REPLICATION GROUP <group_name>;
      +
      +-- Revoke ROLE OWNERSHIP and the share-create privilege from the actor.
      +REVOKE CREATE SHARE ON ACCOUNT FROM ROLE <actor_role>;
      +REVOKE OWNERSHIP ON SHARE <share_name> FROM ROLE <actor_role>;
      +
      +
      + +
      +
      Remediate & Validate
      +
      +

      Critical: source-side QUERY_HISTORY does not log the byte motion through the share. Forensic reconstruction of what the consumer read requires the consumer-side ACCESS_HISTORY — which the consumer owns. Invoke the pre-arranged BAA acquisition path (see the recommendations timeline 91–180-day section) to obtain that audit.

      +
        +
      • Disable or rotate any service account that held CREATE SHARE or REPLICATIONADMIN grants and is implicated.
      • +
      • Audit OPS.SECURITY.APPROVED_SHARE_CONSUMERS and confirm the watchlist accurately reflects the post-incident state.
      • +
      • For OCR reporting, treat the entire shared dataset as breached unless the consumer-side audit can definitively bound the reads.
      • +
      +
      +
      + +

      Chain I — Cortex Agent MCP poisoning

      +

      Trigger: any cortex_agent_* rule fires — a tool-call follow-up, SQL execution from tool output, or rank anomaly indicates planner-steering. Rule fires require the Cortex Agent per-step trace sidecar; without it the trigger comes from manual review of agent outputs.

      + +
      +
      Detect & Contain
      +
      +
      -- Disable the affected Cortex Agent immediately.
      +ALTER CORTEX AGENT <agent_name> SUSPEND;
      +
      +-- Or, if the agent is one of several, revoke the role-level grant that
      +-- allows users to invoke it.
      +REVOKE USAGE ON CORTEX AGENT <agent_name> FROM ROLE <invoker_role>;
      +
      +-- Identify any SQL executed by the agent that reads PHI tables.
      +SELECT user_name, role_name, query_id, query_text, bytes_written_to_result, start_time
      +  FROM snowflake.account_usage.query_history
      + WHERE warehouse_name = '<agent_warehouse>'
      +   AND start_time > dateadd(hour, -24, current_timestamp())
      +   AND query_text ILIKE '%FROM <phi_table>%'
      + ORDER BY start_time DESC;
      +
      +
      + +
      +
      Remediate
      +
      +
        +
      • Identify the poisoning source: which Cortex Search index, which MCP tool output, which document contained the injection.
      • +
      • Remove the poisoning artifact (un-index the document, remove the MCP server from the agent's registry, etc.).
      • +
      • Confirm row-access policies on PHI tables are in force — the agent's effective RBAC is the load-bearing control, not the agent-side guardrails.
      • +
      • Enable Cortex Guardrails healthcare-tier policy if not already in force.
      • +
      • For the affected agent, review the semantic model and tool set; remove any DML-capable tool from an agent that also reads PHI.
      • +
      +
      +
      + +

      Chain J — Partner-integration credential replay

      +

      Trigger: partner_integration_credential_replay.yml or partner_integration_credential_replay_trail.yml fires — a partner user signs in from a source outside the partner's documented egress range. Or: the partner notifies the customer of a compromise.

      + +
      +
      Detect & Contain
      +
      +
      -- Forensic snapshot of partner-user activity.
      +SELECT user_name, client_ip, client_app_id, authentication_method, event_timestamp
      +  FROM snowflake.account_usage.login_history
      + WHERE user_name = '<partner_user>'
      +   AND event_timestamp > dateadd(day, -30, current_timestamp())
      + ORDER BY event_timestamp DESC;
      +
      +-- Disable the partner user immediately and revoke the credential.
      +ALTER USER <partner_user> SET DISABLED = TRUE;
      +ALTER USER <partner_user> SET RSA_PUBLIC_KEY = NULL, RSA_PUBLIC_KEY_2 = NULL;
      +
      +-- Revoke any PATs the partner held.
      +SHOW USER PROGRAMMATIC ACCESS TOKENS FOR USER <partner_user>;
      +ALTER USER <partner_user> REMOVE PROGRAMMATIC ACCESS TOKEN <pat_name>;
      +
      +
      + +
      +
      Remediate
      +
      +
        +
      • Coordinate with the partner's IR team; the partner's BAA establishes the customer's right to audit.
      • +
      • Rotate the partner credential and re-issue with a bound network policy if the partner can publish stable egress CIDRs.
      • +
      • If the partner cannot publish stable egress CIDRs, escalate to the architectural remediation in the recommendations timeline 91–180-day section: move to a scoped Direct Share with the partner as consumer rather than the partner holding the customer's credential.
      • +
      • For OCR reporting: the partner's discovery date starts the 60-day notification clock; track the chain carefully because the customer is the OCR-side reporting party even if the compromise is at the partner.
      • +
      +
      +
      + +

      Cross-cutting actions

      + +

      Forensic-capture template

      +

      Run before any containment SQL on any chain. Captures the user, role, session, query, and login state at the moment of detection.

      +
      CREATE OR REPLACE TABLE OPS.IR.SNAPSHOT_<incident_id> AS
      +SELECT current_timestamp() AS snapshot_at, *
      +  FROM snowflake.account_usage.query_history
      + WHERE user_name = '<user>'
      +   AND start_time > dateadd(day, -30, current_timestamp());
      +
      +CREATE OR REPLACE TABLE OPS.IR.LOGIN_SNAPSHOT_<incident_id> AS
      +SELECT current_timestamp() AS snapshot_at, *
      +  FROM snowflake.account_usage.login_history
      + WHERE user_name = '<user>'
      +   AND event_timestamp > dateadd(day, -30, current_timestamp());
      + +

      Account-wide enumeration

      +

      For incidents whose scope is unclear at trigger time, enumerate the actor's reachable surface.

      +
      -- All roles granted to the user.
      +SHOW GRANTS TO USER <user>;
      +
      +-- All grants those roles received.
      +SELECT grantee_name, role_name, granted_on, name, privilege
      +  FROM snowflake.account_usage.grants_to_roles
      + WHERE grantee_name IN (SELECT role FROM table(result_scan(last_query_id())));
      +
      +-- All tasks the user owns.
      +SELECT name, database_name, schema_name, state, owner
      +  FROM snowflake.information_schema.task_history
      + WHERE owner IN (SELECT role FROM table(result_scan(last_query_id())));
      + +

      Watchlist hygiene during IR

      +

      During an active incident, the relevant watchlists should be quarantined rather than updated:

      +
        +
      • OPS.SECURITY.APPROVED_EXFIL_STAGES — do not add the attacker's stage to the watchlist as a noise-suppression measure; treat the alert as load-bearing.
      • +
      • OPS.SECURITY.APPROVED_SHARE_CONSUMERS — same. Do not pre-approve the unknown consumer to silence the rule.
      • +
      • OPS.SECURITY.PARTNER_REGISTRY — review the partner's entry; the entry's bound_network_policy field may need tightening post-incident.
      • +
      + +

      Recovery and lessons learned

      +

      After containment and remediation, run the + recommendations timeline + gap analysis: which control in the 30 / 60 / 90 / 180 day plan + was missing or partially deployed at the time of the incident? + Use the gap as the prioritization input for the next sprint — + retrospectives that produce just an "improve detection" item are + not actionable; the timeline gives the missing artifact a name.

      +
      + + +
      +
      + + + diff --git a/reports/snowflake-platform-assessment/index.html b/reports/snowflake-platform-assessment/index.html index f6f1776..dc8f8a2 100644 --- a/reports/snowflake-platform-assessment/index.html +++ b/reports/snowflake-platform-assessment/index.html @@ -17,6 +17,8 @@
    • Attack chains
    • Detection surface
    • Recommendations
    • +
    • Incident response
    • +
    • Healthcare overlay
    • Appendix
    • @@ -52,6 +54,26 @@

      Snowflake Platform — Security Assessment

      implemented using Snowflake's native tooling. +
      + For covered entities and business associates: + the platform-generic chains in this report re-frame into + PHI-specific impact, HIPAA Security Rule citations, and + BAA-scope considerations on the + healthcare overlay + page. Service users on key-pair auth (Chain F) and partner + SaaS holding Snowflake credentials (Chain J) are the chains + where healthcare residual risk diverges most sharply from + the platform-generic baseline; the overlay names why. +
      + + +
      Scope & assumptions: the assessment covers Snowflake on AWS / Azure / GCP across the Standard, Enterprise, @@ -69,7 +91,7 @@

      Snowflake Platform — Security Assessment

      docs/analysis/snowflake-platform-attack-surface-2026.md.
      -

      Key findings

      +

      Key findings

      Maturity, in this table. Each row carries an inline maturity tag matching the attack-chains page. Rows tagged @@ -156,7 +178,7 @@

      Key findings

      -

      Scope

      +

      Scope

      Platform Snowflake (multi-cloud: AWS, Azure, GCP) Features in scope Authentication, Cortex AI (Code, Analyst, Search, Agents), Native Apps, SPCS, Streamlit-in-Snowflake, External Functions, Storage Integrations, Data Sharing, Tasks/UDFs/Procedures diff --git a/reports/snowflake-platform-assessment/recommendations.html b/reports/snowflake-platform-assessment/recommendations.html index b392779..a5aa960 100644 --- a/reports/snowflake-platform-assessment/recommendations.html +++ b/reports/snowflake-platform-assessment/recommendations.html @@ -17,6 +17,8 @@
    • Attack chains
    • Detection surface
    • Recommendations
    • +
    • Incident response
    • +
    • Healthcare overlay
    • Appendix
    • @@ -35,7 +37,17 @@

      Recommendations

      Governance/Legal); adjust to local org structure.
      -

      Immediate — close the UNC5537 gap pattern and the post-patch window

      + + +

      Immediate — close the UNC5537 gap pattern and the post-patch window

      @@ -96,7 +108,7 @@

      Immediate — close the UNC5537 gap pattern and the post-patch window

      -

      Short-term — reduce blast radius and cross-cloud exposure

      +

      Short-term — reduce blast radius and cross-cloud exposure

      @@ -164,7 +176,7 @@

      Short-term — reduce blast radius and cross-cloud exposure

      -

      Ongoing — AI, agentic, and audit-replication governance

      +

      Ongoing — AI, agentic, and audit-replication governance

      @@ -217,6 +229,270 @@

      Ongoing — AI, agentic, and audit-replication governance

      + +

      Remediation timeline — 30 / 60 / 90 / 180 day phasing

      +

      + Controls grouped by realistic execution window for a + mid-size enterprise (single platform team, single SOC, single + identity team). Effort estimates are wall-clock weeks of + focused engineering work, not calendar duration — a control + listed as “1 week” expects one engineer's + focused attention for that week. Parallel execution across + teams collapses the calendar; sequential execution within a + team extends it. The phasing assumes parallel execution + within each window. +

      + +
      + Reading the timeline. Each row names the + control, its + chain + (or "cross-cutting"), the team that owns the change, and the + estimated effort. The “Why this window” column + explains the sequencing: some P0s genuinely cannot wait + (mandatory MFA gating the human-credential surface); some P1s + sequence after P0s because the detection rules require the + controls to be in place first (the enrichment pipeline depends + on the watchlists the inventory steps populate). +

      + +

      Days 0–30 — close the highest-velocity gaps

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      ControlChainOwnerEffortWhy this window
      Enforce MFA on all human users (AUTHENTICATION POLICY with MFA_ENROLLMENT = REQUIRED)A, DIdentity1–2 daysSingle account-level setting; gates the human-credential surface that drove UNC5537. No upstream dependency.
      Inventory all service and key-pair users without a bound network policy (Trust Center "missing network policy" scanner)A, F, JSOC + Identity1 weekInventory must come before the lock-down step so the team knows the population. Trust Center surfaces this directly.
      Bind network policy to every service / key-pair user (allow-list of documented CI / orchestration egress)A, FIdentity1–2 weeksThe single largest residual-risk reduction post-UNC5537. Per-user policy enables phased rollout (start with the highest-grant users).
      Pin Cortex Code CLI ≥ 1.0.25 across all developer endpoints (package-manager rollout + EDR check)BPlatform + Endpoint1–2 weeksCloses CVE-2026-6442. Retrospective hunt across the pre-patch exposure window is a parallel SOC effort, not a blocker.
      Enumerate every SPCS EXTERNAL ACCESS INTEGRATION; remove any wildcard ALLOWED_NETWORK_RULESHPlatform + Data Eng1–2 weeksChain H residual drops to Low once wildcards are gone. The chain's necessary condition is the wildcard; once removed, the chain no longer applies.
      Deploy the 4 production-ready Sigma rules (no enrichment dependency)A (pair), F, HSOC1 weekDay-one detection coverage for the chains whose rules are production_ready. No upstream dependency.
      + +

      Days 31–60 — close blast-radius and inventory gaps

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      ControlChainOwnerEffortWhy this window
      Enumerate every Storage Integration; remove wildcard storage_allowed_locations; bind IAM role with bucket-policy-side controlsEPlatform + Cloud Sec2–3 weeksConfiguration-level fix; cross-cloud blast-radius reduction. Sequencing after the day-30 inventory because the customer must know which integrations exist before scoping them.
      Disable auto-update on every Native App with grants on PHI-bearing schemas; require manual review per version bumpCPlatform + Governance1 weekCloses the Chain C auto-upgrade vector. Manual version pinning is the compensating control while the manifest-diff detection rules are wired up.
      Deploy the streaming-ingest sidecar (detection/snowflake/streaming-ingest/) to bypass ACCOUNT_USAGE 45-min latencyA, F, JPlatform + SOC2 weeksThe streaming pipeline turns minute-scale latency into seconds-scale for the chains that depend on real-time detection. Terraform + Function App ship as concrete config.
      IdP audit ingestion + per-source ingestion watermark pipeline (Sentinel KQL / Splunk SPL recipe in ENRICHMENT.md §3)D, LSOC + Identity2–3 weeksUnblocks federated_login_anomaly.yml and oauth_integration_scope_drift.yml. The synthetic-event validation step in ENRICHMENT.md is the gate before promoting to alert.
      Populate OPS.SECURITY.APPROVED_SHARE_CONSUMERS and OPS.SECURITY.APPROVED_REPLICATION_TARGETS watchlists; switch the Chain G rules to alert modeGPlatform + Governance1–2 weeksThe Chain G rules are watchlist-gated; populating the watchlist is the unblock. Joint Platform + Governance ownership because legitimate consumers come from the BAA inventory.
      + +

      Days 61–90 — close detection gaps via enrichment

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      ControlChainOwnerEffortWhy this window
      Build the SIEM-side enrichment pipeline (role-baseline p90, business-hours, stage watchlist, integration allowlist, manifest-diff)A, C, E, F, G, J, K, M, H+SOC + Data Eng4–6 weeks (parallelizable)The largest single deployment lift in the program. Unblocks 20 rules across 9 chains. The enrichment-templates directory ships SQL + lookup definitions; the work is per-tenant adaptation.
      Deploy the Cortex Agent per-step trace sidecar (Snowpark wrapper or Trail cortex_agent.* event family)IPlatform + SOC2–3 weeksUnblocks all 5 Chain I rules. Until deployed, Chain I detection coverage is zero; the policy-layer compensating control (row-access policies on PHI tables) is load-bearing.
      Inventory partner integrations; cross-reference against BAA registry; bind network policy where partner publishes stable egress CIDRsJVendor Risk + Identity2–3 weeksChain J residual drops to Medium with this work. Vendors without stable egress CIDRs are themselves a finding — escalate to the architectural move (scoped Direct Share with partner-as-consumer).
      Connector-stack SBOM scan + driver-version rollout (JDBC, Python, Node.js, .NET, Go, C/C++ on every CI / orchestration host)cross-cuttingPlatform + Endpoint2–3 weeksCloses the secret-leakage cohort (CVE-2025-27496, CVE-2025-46329, etc.) and the transitive-dependency CVEs that surface via SBOM scans.
      + +

      Days 91–180 — close structural gaps (covered entity-specific)

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      ControlChainOwnerEffortWhy this window
      Cortex Guardrails healthcare-tier policy deployment + Sweeney-class re-identification detectionIPlatform + Privacy2–3 weeksThe default Guardrails policy is not tuned for healthcare-specific abuse. The repository's harness family=Healthcare tier characterizes residual delta; the customer-side action is enabling and tuning.
      Row-access and masking policies on every PHI-bearing table (per-role minimum-necessary scope)A, I (and any Cortex agent reading PHI)Data Eng + Privacy4–6 weeksThe load-bearing technical control for HIPAA §164.502(b) Minimum Necessary. Sequencing late because it requires the privacy office's per-role classification (an upstream governance prerequisite, not a technical one).
      OCR-grade audit retention pipeline (6-year retention sink for ACCOUNT_USAGE projections and Trail events)cross-cuttingPlatform + SOC3–4 weeksSnowflake's first-party retention (365 days) is insufficient for the OCR reconstruction window. The streaming-ingest sidecar from Day 31–60 is the producer side; the customer owns the long-retention sink.
      Cross-account share acquisition runbook + BAA contractual pre-arrangement for consumer-side ACCESS_HISTORYGGovernance + Legal2–3 weeksChain G's source-side audit blind spot is structural — only the consumer's audit shows the reads. Pre-arranging the acquisition path in the BAA closes the IR gap before it is needed.
      Architectural move: scoped Direct Share with partner-as-consumer (replacing partner-held-credentials where feasible)JPlatform + Data Eng + Vendor Riskmulti-quarterThe complete close for Chain J. Multi-quarter because each partner integration is an independent contract + technical change. Sequence by partner-held credential risk — partners with ACCOUNTADMIN-adjacent users first.
      + +

      Continuous — sustained governance

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      ControlOwnerCadenceNotes
      Review APPROVED_SHARE_CONSUMERS / APPROVED_REPLICATION_TARGETS / APPROVED_EXFIL_STAGES / APPROVED_CONTAINER_REGISTRIES watchlistsSOC + GovernanceQuarterlyStale watchlists are silent detection gaps. A quarterly cadence balances thoroughness against operational overhead.
      SBOM scan of connector stack pinned versions across CI / orchestration hostsPlatform + EndpointQuarterly + on CVE disclosureDriver releases bundle dependency-CVE rollups; quarterly catches the routine cohort, on-disclosure catches the urgent ones.
      OCR reconstruction tabletop — can the org produce a full audit trail of who accessed PHI table X between dates Y and Z (Y ≥ 18 months back)?Privacy + SOC + LegalAnnualThe tabletop surfaces audit-retention gaps before OCR asks. Any gap is a §164.312(b) finding regardless of whether any chain has been exercised.
      FP/FN harness rerun on the bulk-exfil baseline (and any rule whose enrichment changes)SOCSemi-annualThe fp_fn_harness/ directory ships the corpus. Re-measure sensitivity / specificity when baselines shift (new pipelines, new approved roles, new business-hours windows).
      -

      Post-2024 platform hardening — what changed

      +

      Post-2024 platform hardening — what changed

      @@ -107,7 +120,7 @@

      Post-2024 platform hardening — what changed

      DateChangeAssessment implication
      -

      Threat actor profile — 2026 Snowflake assessment

      +

      Threat actor profile — 2026 Snowflake assessment

      The realistic 2026 attacker against an enterprise Snowflake deployment is one or more of:

      • Credential-access operator: purchases infostealer logs; validates credentials at scale against Snowflake login endpoints; operates opportunistically against any tenant missing MFA or network controls. UNC5537 is the prototype.
      • @@ -117,7 +130,7 @@

        Threat actor profile — 2026 Snowflake assessment

      • Insider / misconfigured developer: exfiltrates through over-privileged Storage Integrations or External Functions that were correctly authorized for a narrow use case but bound to an over-broad IAM role.
      -

      Authentication surface

      +

      Authentication surface

      Post-April-2025, single-factor password sign-ins are blocked for human users — which moves the practical attack surface onto non-human credentials and federated sessions. The table below @@ -175,7 +188,7 @@

      Authentication surface

      -

      Cortex inference egress

      +

      Cortex inference egress

      Cortex Analyst and Cortex Agents pass prompts and grounding context to large language models for final-response generation. For most Cortex surfaces this means inference reaches diff --git a/tools/lateral-movement/snowflake-pivot/detection/sigma/udf_with_eai_invocation.yml b/tools/lateral-movement/snowflake-pivot/detection/sigma/udf_with_eai_invocation.yml index 44c04dd..deeff77 100644 --- a/tools/lateral-movement/snowflake-pivot/detection/sigma/udf_with_eai_invocation.yml +++ b/tools/lateral-movement/snowflake-pivot/detection/sigma/udf_with_eai_invocation.yml @@ -32,6 +32,7 @@ tags: enrichment: required: - udf_owner + - udf_has_eai - udf_eai_list - eai_network_rule_value_list - eai_rule_is_overbroad @@ -45,7 +46,7 @@ detection: query_type|in: - 'SELECT' - 'CALL' - udf_eai_list|exists: true + udf_has_eai: true rule_is_overbroad: eai_rule_is_overbroad: true non_owner_invocation: diff --git a/tools/supply-chain/snowflake-native-app/detection/sigma/native_app_privilege_bump.yml b/tools/supply-chain/snowflake-native-app/detection/sigma/native_app_privilege_bump.yml index f0dffa9..c7d7cb8 100644 --- a/tools/supply-chain/snowflake-native-app/detection/sigma/native_app_privilege_bump.yml +++ b/tools/supply-chain/snowflake-native-app/detection/sigma/native_app_privilege_bump.yml @@ -13,6 +13,16 @@ description: | App can request a wide-scope data read on auto-update, and the consent UI is the only control between the installer and that read. + Structural contract: `manifest_diff_added` is a list of prefixed + tokens (`PRIVILEGE:`, `EXTERNAL ACCESS INTEGRATION:`, + `EXTERNAL FUNCTION:`, `CONTAINER:`) emitted by the + consumer-side application_history projection. The rule uses + `|startswith` rather than `|contains` to bind to the prefix + contract — a free-text mention of `PRIVILEGE:` in any other field + the SIEM normalises into `manifest_diff_added` will not fire the + rule. If your SIEM serialises the list field into a single string, + re-tokenise on the prefix list before applying the rule. + Source: the consumer-side application_history audit projection. references: - https://docs.snowflake.com/en/developer-guide/native-apps/security-overview @@ -38,7 +48,7 @@ detection: event_type: APP_VERSION_INSTALLED auto_upgrade: true new_privilege: - manifest_diff_added|contains: 'PRIVILEGE:' + manifest_diff_added|startswith: 'PRIVILEGE:' condition: app_upgraded and new_privilege fields: - event_timestamp diff --git a/tools/supply-chain/snowflake-native-app/detection/sigma/native_app_privilege_bump_trail.yml b/tools/supply-chain/snowflake-native-app/detection/sigma/native_app_privilege_bump_trail.yml index 60869e9..000c685 100644 --- a/tools/supply-chain/snowflake-native-app/detection/sigma/native_app_privilege_bump_trail.yml +++ b/tools/supply-chain/snowflake-native-app/detection/sigma/native_app_privilege_bump_trail.yml @@ -5,11 +5,17 @@ status: experimental description: | Trail-event-shaped pair to `native_app_privilege_bump.yml`. Consumes the `app.snowflake.version_installed` Trail event with - `manifest_diff_added` carrying a `PRIVILEGE:` token. + `manifest_diff_added` carrying a `PRIVILEGE:`-prefixed token. Real-time variant — relevant because auto-upgrade with a wide-scope read privilege can be exploited within minutes of the upgrade firing, inside the ACCOUNT_USAGE ≤45m audit window. + + Structural contract: see the ACCOUNT_USAGE-shaped pair rule. The + `|startswith` binding to `PRIVILEGE:` enforces the prefixed-token + contract emitted by the application_events Trail projection; free- + text occurrences of `PRIVILEGE:` in other fields will not fire the + rule. references: - https://docs.snowflake.com/en/user-guide/snowflake-trail - https://docs.snowflake.com/en/developer-guide/native-apps/security-overview @@ -34,7 +40,7 @@ detection: event_type: 'app.snowflake.version_installed' auto_upgrade: true new_privilege: - manifest_diff_added|contains: 'PRIVILEGE:' + manifest_diff_added|startswith: 'PRIVILEGE:' condition: app_upgraded and new_privilege fields: - event_timestamp