apache/cassandra · error · UnauthorizedException

User %s has no UNMASK nor SELECT_MASKED permission on table

Error message

User %s has no UNMASK nor SELECT_MASKED permission on table %s.%s, cannot query masked columns %s

What it means

SelectStatement.authorize() enforces dynamic data masking permissions: if the user has neither UNMASK nor SELECT_MASKED on a table and the query restricts (filters on) masked columns, it throws UnauthorizedException listing the offending columns. Masking is bypassed only for users holding one of those permissions or full ALTER/DROP/SELECT-unrestricted grant; this error blocks leaking masked data via WHERE clauses.

Source

Thrown at src/java/org/apache/cassandra/cql3/statements/SelectStatement.java:350

        {
            state.ensureTablePermission(table, Permission.SELECT);
        }

        for (Function function : getFunctions())
            state.ensurePermission(Permission.EXECUTE, function);

        if (table.hasMaskedColumns() &&
            !state.hasTablePermission(table, Permission.UNMASK) &&
            !state.hasTablePermission(table, Permission.SELECT_MASKED))
        {
            List<ColumnMetadata> queriedMaskedColumns = table.columns()
                                                             .stream()
                                                             .filter(ColumnMetadata::isMasked)
                                                             .filter(restrictions::isRestricted)
                                                             .collect(Collectors.toList());

            if (!queriedMaskedColumns.isEmpty())
                throw new UnauthorizedException(format("User %s has no UNMASK nor SELECT_MASKED permission on table %s.%s, " +
                                                       "cannot query masked columns %s",
                                                       state.getUser().getName(), keyspace(), table(), queriedMaskedColumns));
        }
    }

    public void validate(ClientState state) throws InvalidRequestException
    {
        if (parameters.allowFiltering && !SchemaConstants.isSystemKeyspace(table.keyspace))
            Guardrails.allowFilteringEnabled.ensureEnabled(state);
    }

    @Override
    public void validatePrepare(ClientState state)
    {
        Guardrails.preparedStatementsRequireParameters.guard(this, restrictions, state, table.keyspace, table.getTableName());
    }

    @Override

View on GitHub (pinned to 88fd0f6a0e)

Solutions

  1. GRANT SELECT_MASKED ON keyspace.table TO app_role if the app must filter on masked columns
  2. GRANT UNMASK ON keyspace.table TO the role if it should see unmasked values entirely
  3. Restructure the query to not restrict on the masked column (filter on an unmasked column instead)
  4. Ask an administrator to review the masking policy if filtering is a legitimate need

Example fix

// before
SELECT * FROM ks.t WHERE ssn = '123-45-6789'; // Unauthorized
// after (as admin)
GRANT SELECT_MASKED ON ks.t TO reporting_user;
// then run the query as reporting_user
Defensive patterns

Strategy: try-catch

Validate before calling

const perms = await listEffectivePermissions(user, 'ks', 't'); if (!perms.includes('UNMASK') && !perms.includes('SELECT_MASKED') && queryFiltersOnMaskedColumns) throw new Error('Request SELECT_MASKED grant first');

Try / catch

try { return session.execute(query); } catch (e) { if (e instanceof UnauthorizedException || /UNMASK nor SELECT_MASKED/.test(e.message)) { requestMaskedPermissionOrRewriteQuery(); } else throw e; }

Prevention

When it happens

Trigger: Executing SELECT ... WHERE masked_col = x as a user without UNMASK or SELECT_MASKED permission on the table, when masked_col has a masking policy attached (CREATE TABLE ... col int MASKED WITH ...).

Common situations: Non-admin users querying tables where a DBA recently added column masking; applications running under restricted roles after a security hardening rollout; dashboards filtering on columns that are now masked.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/fe018b2d90ae6835. Report an issue: GitHub.