{"record":{"id":"fe018b2d90ae6835","repo":"apache/cassandra","slug":"user-s-has-no-unmask-nor-select-masked-permission","errorCode":null,"errorMessage":"User %s has no UNMASK nor SELECT_MASKED permission on table %s.%s, cannot query masked columns %s","messagePattern":"User (.+?) has no UNMASK nor SELECT_MASKED permission on table (.+?)\\.(.+?), cannot query masked columns (.+?)","errorType":"validation","errorClass":"UnauthorizedException","httpStatus":null,"severity":"error","filePath":"src/java/org/apache/cassandra/cql3/statements/SelectStatement.java","lineNumber":350,"sourceCode":"        {\n            state.ensureTablePermission(table, Permission.SELECT);\n        }\n\n        for (Function function : getFunctions())\n            state.ensurePermission(Permission.EXECUTE, function);\n\n        if (table.hasMaskedColumns() &&\n            !state.hasTablePermission(table, Permission.UNMASK) &&\n            !state.hasTablePermission(table, Permission.SELECT_MASKED))\n        {\n            List<ColumnMetadata> queriedMaskedColumns = table.columns()\n                                                             .stream()\n                                                             .filter(ColumnMetadata::isMasked)\n                                                             .filter(restrictions::isRestricted)\n                                                             .collect(Collectors.toList());\n\n            if (!queriedMaskedColumns.isEmpty())\n                throw new UnauthorizedException(format(\"User %s has no UNMASK nor SELECT_MASKED permission on table %s.%s, \" +\n                                                       \"cannot query masked columns %s\",\n                                                       state.getUser().getName(), keyspace(), table(), queriedMaskedColumns));\n        }\n    }\n\n    public void validate(ClientState state) throws InvalidRequestException\n    {\n        if (parameters.allowFiltering && !SchemaConstants.isSystemKeyspace(table.keyspace))\n            Guardrails.allowFilteringEnabled.ensureEnabled(state);\n    }\n\n    @Override\n    public void validatePrepare(ClientState state)\n    {\n        Guardrails.preparedStatementsRequireParameters.guard(this, restrictions, state, table.keyspace, table.getTableName());\n    }\n\n    @Override","sourceCodeStart":332,"sourceCodeEnd":368,"githubUrl":"https://github.com/apache/cassandra/blob/88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java#L332-L368","documentation":"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.","triggerScenarios":"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 ...).","commonSituations":"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.","solutions":["GRANT SELECT_MASKED ON keyspace.table TO app_role if the app must filter on masked columns","GRANT UNMASK ON keyspace.table TO the role if it should see unmasked values entirely","Restructure the query to not restrict on the masked column (filter on an unmasked column instead)","Ask an administrator to review the masking policy if filtering is a legitimate need"],"exampleFix":"// before\nSELECT * FROM ks.t WHERE ssn = '123-45-6789'; // Unauthorized\n// after (as admin)\nGRANT SELECT_MASKED ON ks.t TO reporting_user;\n// then run the query as reporting_user","handlingStrategy":"try-catch","validationCode":"const perms = await listEffectivePermissions(user, 'ks', 't'); if (!perms.includes('UNMASK') && !perms.includes('SELECT_MASKED') && queryFiltersOnMaskedColumns) throw new Error('Request SELECT_MASKED grant first');","typeGuard":null,"tryCatchPattern":"try { return session.execute(query); } catch (e) { if (e instanceof UnauthorizedException || /UNMASK nor SELECT_MASKED/.test(e.message)) { requestMaskedPermissionOrRewriteQuery(); } else throw e; }","preventionTips":["Grant SELECT_MASKED to application roles that legitimately filter on masked columns","Track which columns carry MASKED WITH policies and avoid filtering on them from restricted roles","Review queries after security hardening changes that introduce masking"],"tags":["cql","permissions","data-masking","security"],"backgroundTag":"permission-denied","analyzedSha":"88fd0f6a0eaed8943f05ac9e8f947882b8ddc8f1","analyzedAt":"2026-09-10T07:29:22.284Z","contentChangedAt":"2026-09-10T07:29:22.284Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}