Detection surface

What the platform exposes, what it hides, and where to build detection.

Deployment readiness — not every rule fires out of the box. The Sigma rule pack in this assessment carries explicit maturity tags on every rule. Plan deployment accordingly:
  • 4 rules are production_ready — fire on raw audit / log surfaces the customer already ingests. Drop in.
  • 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 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.

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.

Chain Production-ready (fires today) Requires enrichment Requires correlation Requires sidecar / endpoint telemetry
A snowflake_bind_param_audit_gap.yml (pair); cross-cutting: connector_secret_leak_in_logs.yml bulk_exfil_baseline.yml, bulk_exfil_baseline_trail.yml
B cortex_code_session_to_unknown_session.yml cortex_code_pre_1_0_25.yml (endpoint EDR command_line)
C native_app_unexpected_version_bump.yml, native_app_privilege_bump.yml, native_app_privilege_bump_trail.yml, native_app_dependency_drift.yml
D federated_login_anomaly.yml; cross-cutting: snowflake_scim_role_race.yml
E snowflake_storage_integration_misuse.yml, snowflake_storage_integration_misuse_trail.yml
F cross-cutting: connector_secret_leak_in_logs.yml snowflake_keypair_auth_abuse.yml, snowflake_keypair_auth_abuse_trail.yml, snowflake_pat_anomaly.yml
G snowflake_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
H snowflake_spcs_eai_overbroad.yml, snowflake_spcs_eai_overbroad_trail.yml
I cortex_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)
J partner_integration_credential_replay.yml, partner_integration_credential_replay_trail.yml cross-cutting: federated_login_anomaly.yml
K iceberg_table_outside_catalog_base.yml
L oauth_integration_scope_drift.yml; cross-cutting: snowflake_scim_role_race.yml
M udf_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 loads the rule pack without this work will not get alerts for the listed rules — and the silence will look like the rules are working, not like the dependency is missing.

Rule Requires How to unblock Interim workaround
cortex_agent_directive_followup.yml cortex_sidecar Snowflake Trail with cortex_agent.* event family emitting or install the Snowpark wrapper at tools/llm-attacks/cortex/lab-validation/observe_cortex_agent_trace.sql to materialise per-step traces into CORTEX_AGENT_TRACE. Constrain Cortex Agent role grants to read-only on PHI-bearing tables; gate write paths behind row-access policies. The rule's PHI coverage is then handled at policy time, not at detection time.
cortex_agent_followup_without_user_intent.yml cortex_sidecar Same as above — needs the per-step trace plus the OPS.SECURITY.AGENT_TOOL_CHAIN_ALLOWLIST lookup. Same — policy-layer mitigation.
cortex_agent_sql_from_tool_output.yml cortex_sidecar Same as above — the wrapper must tag each executed query with sql_origin (user_prompt / tool_output / planner). Same — policy-layer mitigation.
cortex_search_rank_anomaly.yml cortex_sidecar Same as above — the wrapper must record per-query top-N rankings at search time. Restrict Cortex Search indexing to a closed set of OPS.SECURITY.APPROVED_INDEXING_ROLES; index-time control replaces search-time anomaly detection.
cortex_agent_directive_followup_trail.yml cortex_sidecar (Trail variant) Trail event family cortex_agent.step_completed must be enabled in the Trail subscription. Same as the ACCOUNT_USAGE variant — wrapper or policy-layer.
federated_login_anomaly.yml correlation — IdP audit Ingest Okta System Log or Entra Sign-In Logs into the SIEM with a documented latency SLA. Templates at detection/snowflake/enrichment-templates/idp-okta-system-log/ and detection/snowflake/enrichment-templates/idp-entra-signin/. Bind a network policy to every federated user with allowed_ip_list matching the IdP's documented egress; closes the source-IP escape valve regardless of IdP audit coverage.
oauth_integration_scope_drift.yml correlation — IdP consent snapshot Same IdP ingest as above, plus a daily snapshot of OPS.SECURITY.IDP_CONSENT_SNAPSHOT_DAILY via Okta /api/v1/apps/{id}/grants or Entra oauth2PermissionGrants. Pin OAuth integration default_role to a non-admin-class role; the drift becomes a Snowflake-side change visible in INTEGRATIONS.
snowflake_scim_role_race.yml correlation — SCIM-side audit Ingest Okta SCIM logs or Entra Provisioning Logs; correlate against the Snowflake SCIM PATCH stream. Lock SCIM-managed role membership at the IdP — disable Snowflake-role attribute mutability for non-break-glass identities.
cortex_code_session_to_unknown_session.yml correlation — EDR + host-egress map Ingest EDR process-creation events for the Cortex Code CLI binary; maintain OPS.SECURITY.HOST_EGRESS_RANGES from VPN or device-posture data. Bind a network policy to every developer-tagged Snowflake user; constrains the post-injection login leg even without endpoint telemetry.
cortex_code_pre_1_0_25.yml endpoint_telemetry Ingest EDR process-creation events with command_line capture for the Cortex Code CLI binary. Roll out a managed-package upgrade gate: block Cortex Code CLI < 1.0.25 at the package-manager layer.

For the 19 requires_enrichment rules, the dependency is a SIEM-side enrichment job rather than an external audit feed; the full derived-field contract is documented at detection/snowflake/ENRICHMENT.md and the templates ship under detection/snowflake/enrichment-templates/.

Primary audit sources

Source Latency Key signals
ACCOUNT_USAGE.LOGIN_HISTORY ~45 min Authentication outcomes, client app, source IP, MFA factor. Watch for FIRST_AUTHENTICATION_FACTOR = 'PASSWORD' and logins from new IPs.
ACCOUNT_USAGE.QUERY_HISTORY ~45 min Full SQL text for every statement. Alert on: COPY INTO @<external_stage>, CREATE SHARE, ALTER USER, CREATE NETWORK POLICY, CREATE TASK, CREATE PROCEDURE … EXECUTE AS OWNER.
ACCOUNT_USAGE.STAGES ~45 min Object inventory for all stages. Diff against baseline to detect new external stages.
ACCOUNT_USAGE.APPLICATIONS ~45 min Installed Native Apps with version history. Monitor for unexpected version bumps.
ACCOUNT_USAGE.PROGRAMMATIC_ACCESS_TOKENS ~45 min PAT issuance, last-use, expiration, owner role. Detect long-lived PATs past the rotation policy and PATs that have never been used (over-provisioned, unrotated).
ACCOUNT_USAGE.SHARES / REPLICATION_GROUPS / MANAGED_ACCOUNTS ~45 min The audit surface for Chain G (Direct Share / Replication / Reader Account exfil). Diff against a baseline of documented partner accounts.
Snowflake Trail Near real-time Lower latency than Account Usage; covers some events not visible there. Prefer for real-time alerting pipelines.
Trust Center scanner On-demand Built-in misconfiguration detection (missing MFA, no network policy, over-privileged service users). Output should feed the SOC, not just admin dashboards.
Cortex AI audit events Varies Usage logs for Cortex Code, Analyst, Search, Agents. Essential for prompt-injection detection — visibility into inbound context content, not just outbound actions.

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.
Bind parameters in QUERY_HISTORY — platform limitation, not rule deficiency. Query text shows the template, not bound parameter values. An attacker using parameterized DML has lower-fidelity audit than one using inline SQL — detection rules keyed on table names or data patterns in query text can not close this gap because the field is missing upstream. The paired Sigma rule snowflake_bind_param_audit_gap.yml is a heuristic on the template shape (parameterised COPY INTO @stage), not a substitute for the missing values. Compensating controls live at the policy / volume / stage-DDL layers — see the bind-parameter callout on the attack-chains page (Chain A) for the full mitigation set.
Cross-region audit gap — replication of Account Usage and Trail across regions is not automatic. An attacker targeting a secondary region may leave forensics in a region the customer doesn't routinely query. Confirm audit replication is configured.
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

-- New external stages created in the last 24 hours
SELECT stage_name, stage_url, created
FROM SNOWFLAKE.ACCOUNT_USAGE.STAGES
WHERE stage_type = 'External'
  AND created > DATEADD(hour, -24, CURRENT_TIMESTAMP)
ORDER BY created DESC;

-- Logins without MFA (human users, last 7 days)
SELECT user_name, client_ip, login_time, first_authentication_factor
FROM SNOWFLAKE.ACCOUNT_USAGE.LOGIN_HISTORY
WHERE login_time > DATEADD(day, -7, CURRENT_TIMESTAMP)
  AND first_authentication_factor = 'PASSWORD'
  AND second_factors IS NULL
ORDER BY login_time DESC;

-- COPY INTO external stage (potential bulk exfil)
SELECT user_name, query_text, start_time, bytes_written_to_result
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE start_time > DATEADD(day, -1, CURRENT_TIMESTAMP)
  AND (query_text ILIKE '%COPY INTO @%s3://%'
       OR query_text ILIKE '%COPY INTO @%azure://%')
ORDER BY bytes_written_to_result DESC;

-- New OWNER-rights stored procedures
SELECT procedure_name, procedure_language, created, execute_as
FROM SNOWFLAKE.ACCOUNT_USAGE.PROCEDURES
WHERE created > DATEADD(day, -7, CURRENT_TIMESTAMP)
  AND execute_as = 'OWNER';

-- Native App version changes
SELECT application_name, source_location, version, patch, created
FROM SNOWFLAKE.ACCOUNT_USAGE.APPLICATIONS
ORDER BY created DESC
LIMIT 50;

-- Long-lived or unused Programmatic Access Tokens
SELECT name, user_name, role_name, created_on, last_used_on, days_to_expiry
FROM SNOWFLAKE.ACCOUNT_USAGE.PROGRAMMATIC_ACCESS_TOKENS
WHERE (last_used_on IS NULL AND created_on < DATEADD(day, -30, CURRENT_TIMESTAMP))
   OR DATEDIFF(day, created_on, CURRENT_TIMESTAMP) > 90
ORDER BY created_on;

-- New or modified Direct Shares (Chain G — bypasses QUERY_HISTORY at data motion)
SELECT share_name, share_type, kind, created, last_altered, listing_global_name
FROM SNOWFLAKE.ACCOUNT_USAGE.SHARES
WHERE last_altered > DATEADD(day, -7, CURRENT_TIMESTAMP)
ORDER BY last_altered DESC;

-- DDL events for share / replication primitives (only catch at query time)
SELECT user_name, role_name, query_text, start_time
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE start_time > DATEADD(day, -7, CURRENT_TIMESTAMP)
  AND (query_text ILIKE 'CREATE%SHARE%'
       OR query_text ILIKE 'ALTER%SHARE%ADD%ACCOUNTS%'
       OR query_text ILIKE 'CREATE%REPLICATION%GROUP%'
       OR query_text ILIKE 'CREATE%MANAGED%ACCOUNT%')
ORDER BY start_time DESC;

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.

-- Containment: kill a specific session (account admin only)
SELECT SYSTEM$ABORT_SESSION(<session_id>);

-- Containment: kill every running query for a flagged user
SELECT SYSTEM$CANCEL_ALL_QUERIES('<USER_NAME>');

-- Forensics: every session for a flagged user, last 14 days
SELECT session_id, login_event_id, client_application_id, client_ip, created_on
FROM SNOWFLAKE.ACCOUNT_USAGE.SESSIONS
WHERE user_name = '<USER_NAME>'
  AND created_on > DATEADD(day, -14, CURRENT_TIMESTAMP)
ORDER BY created_on DESC;

-- Forensics: every query in a flagged session
SELECT query_id, query_text, start_time, bytes_written_to_result, rows_produced
FROM SNOWFLAKE.ACCOUNT_USAGE.QUERY_HISTORY
WHERE session_id = <SESSION_ID>
ORDER BY start_time;

-- Key-pair rotation age (Chain F target population)
SELECT name, has_rsa_public_key, has_rsa_public_key_2,
       rsa_public_key_fp, rsa_public_key_2_fp,
       last_success_login,
       DATEDIFF(day, created_on, CURRENT_TIMESTAMP) AS key_age_days
FROM SNOWFLAKE.ACCOUNT_USAGE.USERS
WHERE has_rsa_public_key = 'true'
  AND deleted_on IS NULL
ORDER BY key_age_days DESC;

-- Service users without a network policy attached
SELECT u.name, u.type, u.has_rsa_public_key, u.last_success_login
FROM SNOWFLAKE.ACCOUNT_USAGE.USERS u
LEFT JOIN SNOWFLAKE.ACCOUNT_USAGE.POLICY_REFERENCES p
  ON p.ref_entity_name = u.name AND p.policy_kind = 'NETWORK_POLICY'
WHERE u.deleted_on IS NULL
  AND (u.type = 'SERVICE' OR u.has_rsa_public_key = 'true')
  AND p.policy_id IS NULL;

-- OWNER-rights procedures (Snowflake-native persistence candidates)
SELECT procedure_name, procedure_owner, procedure_language, execute_as, created, last_altered
FROM SNOWFLAKE.ACCOUNT_USAGE.PROCEDURES
WHERE execute_as = 'OWNER'
  AND deleted IS NULL
ORDER BY last_altered DESC;

-- Scheduled Tasks and their owners (persistence audit)
SELECT name, database_name, schema_name, owner, owner_role_type,
       definition, schedule, state, last_altered
FROM SNOWFLAKE.ACCOUNT_USAGE.TASKS
WHERE deleted IS NULL
  AND state = 'started'
ORDER BY last_altered DESC;

-- Audit replication health (cross-region forensics depends on this)
SHOW REPLICATION DATABASES;
SELECT database_name, is_primary, replication_schedule, last_refresh_status_modified_at
FROM TABLE(INFORMATION_SCHEMA.DATABASE_REPLICATION_USAGE_HISTORY(
  DATE_RANGE_START => DATEADD(day, -7, CURRENT_DATE)));

-- Partner-integration users without a network policy (Chain J target)
-- These are the highest-priority remediation: a stolen credential from
-- a compromised partner SaaS replays from anywhere if no policy is bound.
SELECT u.name, u.type, u.has_rsa_public_key,
       u.last_success_login, tr.tag_value AS partner_id
FROM SNOWFLAKE.ACCOUNT_USAGE.USERS u
JOIN SNOWFLAKE.ACCOUNT_USAGE.TAG_REFERENCES tr
  ON tr.object_name = u.name AND tr.tag_name = 'PARTNER_ID'
LEFT JOIN SNOWFLAKE.ACCOUNT_USAGE.POLICY_REFERENCES p
  ON p.ref_entity_name = u.name AND p.policy_kind = 'NETWORK_POLICY'
WHERE u.deleted_on IS NULL
  AND p.policy_id IS NULL;

-- Iceberg external tables with metadata outside the documented catalog
-- base (Chain K — Polaris / Iceberg catalog abuse). Replace
-- 's3://lab-iceberg-warehouse/' with the customer's approved base.
SELECT table_catalog, table_schema, table_name,
       iceberg_metadata_file_path, table_storage_location, last_altered
FROM SNOWFLAKE.ACCOUNT_USAGE.TABLES
WHERE is_iceberg = 'YES'
  AND NOT (iceberg_metadata_file_path LIKE 's3://lab-iceberg-warehouse/%'
           OR table_storage_location LIKE 's3://lab-iceberg-warehouse/%')
ORDER BY last_altered DESC;

-- External OAuth integrations mapping to admin-class roles (Chain L)
-- Join against IdP-side consent export at ingest to detect silent
-- IdP-only scope widening.
SELECT integration_name, integration_type, created, last_altered,
       integration_parameters
FROM SNOWFLAKE.ACCOUNT_USAGE.INTEGRATIONS
WHERE integration_type = 'EXTERNAL_OAUTH'
  AND integration_parameters:DEFAULT_ROLE::STRING IN
      ('ACCOUNTADMIN', 'SECURITYADMIN', 'USERADMIN')
  AND deleted IS NULL;

-- UDFs declared with EAI callable by PUBLIC (Chain M) — egress is
-- attributed to the function owner; any invoker can trigger it.
SELECT f.function_owner, f.function_name, f.function_signature,
       f.external_access_integrations, f.privileged_users
FROM SNOWFLAKE.ACCOUNT_USAGE.FUNCTIONS f
WHERE f.external_access_integrations IS NOT NULL
  AND f.privileged_users ILIKE '%PUBLIC%'
  AND f.deleted IS NULL;

-- SPCS services with tag-pinned or off-registry images (Chain H
-- supply-chain extension). Replace 'internal.harbor.corp' with the
-- customer's approved-registry prefix list.
SELECT service_name, service_specification, last_updated_on
FROM SNOWFLAKE.ACCOUNT_USAGE.SERVICES
WHERE service_specification NOT LIKE '%@sha256:%'
   OR NOT service_specification LIKE '%internal.harbor.corp/%';