Detection surface
What the platform exposes, what it hides, and where to build detection.
- 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 underdetection/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.
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 |
— | — |
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
INFORMATION_SCHEMA views for sub-minute alerting.
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.
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/%';