← All posts
Data Engineering10 min read

Snowflake ACCOUNTADMIN Audit: Who Has It and Who Uses It?

Too many people, and too many dbt jobs, run as ACCOUNTADMIN. Use these Snowflake queries to find them and fix it safely.

To run a Snowflake ACCOUNTADMIN audit, query SNOWFLAKE.ACCOUNT_USAGE.GRANTS_TO_USERS to list every user holding the role, join it to the USERS view to see MFA status and default role, then check QUERY_HISTORY to see what is actually being run as ACCOUNTADMIN. Anything that is a routine SELECT, a dbt run or a BI query does not belong on that role. The whole audit takes about five minutes, and the queries are below.

Most Snowflake accounts we open for the first time have the same history. The founder or first data hire set the account up as ACCOUNTADMIN, then created a dbt user, a BI user and an ingestion user. Nobody changed the default role, so everything has kept running as the most powerful role in the account ever since. Nothing breaks, so nobody looks. This post shows you how to look, how to fix it without taking down your pipelines, and how to stop it coming back.

Why Is Snowflake ACCOUNTADMIN Overuse Actually Dangerous?

ACCOUNTADMIN sits at the top of the role hierarchy. It can read every table, change billing and resource monitors, and create or drop users. Snowflake's own guidance is to assign this role only to a select, limited number of people, to require MFA for everyone who holds it, and to give it to at least two users so a lost password does not lock you out for days (Snowflake's reset procedure for this role can take up to two business days). So the target is a small number, not one and not twelve.

The real-world cost of ignoring this is documented. In 2024, as many as 165 organisations were potentially exposed in data exfiltration attacks on Snowflake customers, according to Mandiant. Mandiant's findings were that attackers used credentials harvested by infostealer malware against accounts that had no MFA and no restrictions on untrusted locations. Snowflake's platform itself was not compromised. The attackers walked in through ordinary user logins, and what they could do afterwards depended on the roles those users held.

The platform is now closing the gap from its side. Snowflake's documented rollout of mandatory strong authentication has a final phase scheduled between August and October 2026, and it covers human users on passwords (second factor required) and legacy service users (must migrate to a stronger method). If your dbt or ingestion user is a password-based service user running as ACCOUNTADMIN, you now have a security problem and a likely outage in the same place.

Data engineer running a Snowflake ACCOUNTADMIN audit SQL query on monitor


📺 Watch: TRACE: The AML alerts that never fired | Snowflake Cortex + CoCo CLI

TRACE: The AML alerts that never fired | Snowflake Cortex + CoCo CLI


How Do You Run a Snowflake ACCOUNTADMIN Audit?

Run these as ACCOUNTADMIN once, ideally in a worksheet you save. Note that ACCOUNT_USAGE views lag real time by up to a couple of hours, so a grant you made five minutes ago will not appear yet.

Step 1: Who holds the role, and how are they protected?

SELECT
    g.grantee_name,
    u.type,
    u.default_role,
    u.has_password,
    u.has_mfa,
    u.has_rsa_public_key,
    u.last_success_login,
    g.granted_by,
    g.created_on AS granted_on
FROM snowflake.account_usage.grants_to_users g
JOIN snowflake.account_usage.users u
    ON u.name = g.grantee_name
    AND u.deleted_on IS NULL
WHERE g.role = 'ACCOUNTADMIN'
    AND g.deleted_on IS NULL
ORDER BY u.last_success_login ASC NULLS FIRST;

Read the output for four red flags:

  • default_role is ACCOUNTADMIN. Every session and every integration that does not specify a role lands on it.
  • has_password is true and has_mfa is false on a human user. This is the exact profile Mandiant described.
  • type is SERVICE or LEGACY_SERVICE. Automation should never hold this role.
  • last_success_login is NULL or older than 90 days. This is a leaver or a dormant account holding the keys.

Step 2: Are admins really logging in with a second factor?

SELECT
    user_name,
    first_authentication_factor,
    second_authentication_factor,
    COUNT(*) AS successful_logins
FROM snowflake.account_usage.login_history
WHERE is_success = 'YES'
    AND event_timestamp >= DATEADD('day', -30, CURRENT_TIMESTAMP())
    AND user_name IN (
        SELECT grantee_name
        FROM snowflake.account_usage.grants_to_users
        WHERE role = 'ACCOUNTADMIN'
            AND deleted_on IS NULL
    )
GROUP BY 1, 2, 3
ORDER BY successful_logins DESC;

Any row where the first factor is PASSWORD and the second factor is NULL is a successful single-factor login to an account that can administer your whole warehouse.

What Is ACCOUNTADMIN Actually Being Used For?

Holding the role is one question. Using it for daily work is a different one, and it is the one that tells you how hard the clean-up will be.

SELECT
    user_name,
    CASE
        WHEN query_type IN ('SELECT', 'INSERT', 'UPDATE', 'DELETE', 'MERGE', 'CREATE_TABLE_AS_SELECT')
            THEN 'data workload'
        ELSE 'admin or DDL'
    END AS workload_type,
    COUNT(*) AS queries,
    COUNT(DISTINCT warehouse_name) AS warehouses_used,
    MAX(start_time) AS last_seen
FROM snowflake.account_usage.query_history
WHERE role_name = 'ACCOUNTADMIN'
    AND start_time >= DATEADD('day', -30, CURRENT_TIMESTAMP())
GROUP BY 1, 2
ORDER BY queries DESC;

A healthy result is a handful of admin or DDL rows from named humans. An unhealthy result is thousands of data workload queries from a user such as DBT_PROD or LOOKER_SVC. That means your production pipeline depends on the most powerful role in the account, and you now know which jobs you must re-point before you revoke anything.

Then check what ACCOUNTADMIN owns, because ownership is what makes the clean-up awkward:

SELECT granted_on, COUNT(*) AS objects_owned
FROM snowflake.account_usage.grants_to_roles
WHERE privilege = 'OWNERSHIP'
    AND grantee_name = 'ACCOUNTADMIN'
    AND deleted_on IS NULL
GROUP BY 1
ORDER BY objects_owned DESC;

If your analytics database, its schemas and the tables dbt built are all in this list, any role you create later cannot manage them until ownership moves. This is why teams that try to simply take the role away from dbt find their next run failing on permissions.

Running those queries for one account is fine. Checking every dbt project's repo for the credential and lineage problems that tend to travel with over-privileged roles is not. fintel-scan is a free, open-source (MIT) CLI that runs fifteen static checks on a dbt project locally, with no warehouse connection: uvx fintel-scan. If you would rather have a specialist run the whole review, you can explore our services at https://fintel-analytics.com/#services.

How Do You Fix ACCOUNTADMIN Overuse Without Breaking Pipelines?

The order matters. Revoke last, not first.

1. Find where the role is hardcoded. Before touching Snowflake, search your repos for places where automation requests the role explicitly:

grep -rniE "role:.*accountadmin" . --include=*.yml --include=*.yaml

Check profiles.yml, orchestrator configs and BI connection settings too. If a service credential is also sitting in the repository, treat that as a separate incident; we cover the equivalent problem for another warehouse in BigQuery Service Account Key in dbt Repo: Find & Fix It.

2. Create a purpose-built role for each workload. At minimum, a transformer role for dbt, a loader role for ingestion and a reader role for BI. Grant each one only the warehouse usage and object privileges it needs. Both Snowflake guidance and practitioner write-ups agree ACCOUNTADMIN should be reserved for account-level configuration and emergency fixes.

3. Move ownership while the pipeline is idle.

CREATE ROLE IF NOT EXISTS transformer;
GRANT USAGE ON WAREHOUSE transform_wh TO ROLE transformer;
GRANT OWNERSHIP ON DATABASE analytics TO ROLE transformer COPY CURRENT GRANTS;
GRANT OWNERSHIP ON ALL SCHEMAS IN DATABASE analytics TO ROLE transformer COPY CURRENT GRANTS;
GRANT OWNERSHIP ON ALL TABLES IN DATABASE analytics TO ROLE transformer COPY CURRENT GRANTS;
GRANT OWNERSHIP ON ALL VIEWS IN DATABASE analytics TO ROLE transformer COPY CURRENT GRANTS;

COPY CURRENT GRANTS is the important clause. Without it, every downstream grant to BI roles is dropped and your dashboards start returning permission errors. Ownership transfers also fail on objects with in-flight queries, so schedule this between runs.

4. Re-point the user, then revoke.

ALTER USER dbt_prod SET DEFAULT_ROLE = 'TRANSFORMER';
-- update profiles.yml to role: transformer, run a full dbt build, then:
REVOKE ROLE ACCOUNTADMIN FROM USER dbt_prod;

Run one complete dbt build under the new role before you revoke. A pattern we see repeatedly is that the first run reveals one or two forgotten privileges, such as a missing grant on a source schema, and fixing them with the old role still in place costs five minutes instead of a failed 6am refresh.

5. Fix the humans. Remove ACCOUNTADMIN from anyone who has not used it in 90 days. Set every remaining holder's default role to something low-privilege so that elevating is a deliberate act. For service users, move to key-pair or another supported non-password method, which the authentication changes will force you to do anyway.

Startup data team reviewing Snowflake role access matrix during ACCOUNTADMIN audit

How Do You Stop Snowflake ACCOUNTADMIN Overuse Coming Back?

A one-off clean-up decays. New hires get the role because it is quick, and a new integration gets built on a personal login. The fix that lasts is to make the check automatic and make it fail loudly.

Since you already run dbt, write the audit as a singular test. Put this in tests/assert_accountadmin_holders_limit.sql:

SELECT COUNT(*) AS holders
FROM snowflake.account_usage.grants_to_users
WHERE role = 'ACCOUNTADMIN'
    AND deleted_on IS NULL
HAVING COUNT(*) > 3

A dbt singular test fails if it returns any rows, so this goes red the moment a fourth person is granted the role. The threshold of three is our rule of thumb for a 10-to-150-person company, not a Snowflake rule. Pick your own number, document why, and keep it above Snowflake's recommended minimum of two. Run it under a dedicated auditor role that has been granted the SNOWFLAKE.SECURITY_VIEWER and SNOWFLAKE.USAGE_VIEWER database roles rather than under ACCOUNTADMIN itself; check the Snowflake documentation for exactly which views each role exposes.

A second test on query_history that fails when any SERVICE-type user has run queries as ACCOUNTADMIN in the last day catches the more dangerous drift. Pair both with a network policy and an authentication policy on the privileged users, so a stolen password alone is not enough, which is exactly the gap Mandiant's investigation exposed.

Finally, document the break-glass path. Name the two people who hold ACCOUNTADMIN, say where their MFA devices live and write down when it is acceptable to use the role. Undocumented access models rot quickly, much like undocumented dbt models.

Frequently Asked Questions

Q: How do I see who has the ACCOUNTADMIN role in Snowflake?

A: Query SNOWFLAKE.ACCOUNT_USAGE.GRANTS_TO_USERS where role equals ACCOUNTADMIN and deleted_on is null. Join it to the USERS view to see each holder's MFA status, default role, user type and last login. Note that ACCOUNT_USAGE data can lag by up to a couple of hours.

Q: How many users should have ACCOUNTADMIN in Snowflake?

A: Snowflake recommends a limited number of people, with a minimum of two so that a lost credential does not leave you locked out. For most startups of 10 to 150 people, two or three named humans is a sensible ceiling, and no service users at all.

Q: Should dbt run as ACCOUNTADMIN?

A: No. dbt should run under a dedicated transformer role that owns the objects it builds and has usage on the warehouse it needs. Running dbt as ACCOUNTADMIN means a leaked profile or a bad macro has full control over your account.

Q: Can I remove ACCOUNTADMIN from a user without breaking things?

A: Yes, if you move ownership of the objects they created to a lower role first, using COPY CURRENT GRANTS so downstream access survives. Re-point their default role, run a full dbt build under the new role, and only then revoke ACCOUNTADMIN.

Q: How often should I run a Snowflake ACCOUNTADMIN audit?

A: Automate it so it runs on every dbt build or at least daily, and do a manual review quarterly. The automated check catches new grants within a day, and the quarterly review catches dormant users and drifting service accounts.

Conclusion

The challenge most growth-stage teams face is not that they chose to over-privilege their Snowflake account. It is that the setup-day defaults were never revisited while the team, the integrations and the sensitivity of the data all grew. At Fintel Analytics, we run exactly this kind of Snowflake ACCOUNTADMIN audit as part of hands-on data platform reviews for fintech, payments and e-commerce companies, from the first grants query through to role redesign, dbt re-pointing and automated guardrails in production. With password-only sign-ins now being phased out, doing this deliberately in a planned window is far cheaper than doing it during an incident or after a pipeline outage.

← Back to all posts