Fix #2509: Settings-based deny rules now override root user privileges

The root user's permission_resources_sql hook was returning early with a
blanket "allow all" rule, preventing settings-based deny rules from being
considered. This caused /-/allowed and /-/rules endpoints to incorrectly
show resources that were denied via settings.

Changed permission_resources_sql to append root permissions to the rules
list instead of returning early, allowing config-based deny rules to be
evaluated. The SQL cascading logic correctly applies: deny rules at the
same depth beat allow rules, so database-level denies override root's
global-level allow.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Simon Willison 2025-10-24 10:56:51 -07:00
commit 7c6bc0b902
2 changed files with 92 additions and 12 deletions

View file

@ -203,23 +203,22 @@ def permission_allowed_root(datasette, actor, action, resource):
@hookimpl
async def permission_resources_sql(datasette, actor, action):
# Root user with root_enabled gets all permissions
rules: list[PermissionSQL] = []
# Root user with root_enabled gets all permissions at global level
# Config rules at more specific levels (database/table) can still override
if datasette.root_enabled and actor and actor.get("id") == "root":
# Return SQL that grants access to ALL resources for this action
action_obj = datasette.actions.get(action)
if action_obj and action_obj.resource_class:
resources_sql = action_obj.resource_class.resources_sql()
sql = f"""
SELECT parent, child, 1 AS allow, 'root user' AS reason
FROM ({resources_sql})
"""
return PermissionSQL(
# Add a single global-level allow rule (NULL, NULL) for root
# This allows root to access everything by default, but database-level
# and table-level deny rules in config can still block specific resources
sql = "SELECT NULL AS parent, NULL AS child, 1 AS allow, 'root user' AS reason"
rules.append(
PermissionSQL(
source="root_permissions",
sql=sql,
params={},
)
rules: list[PermissionSQL] = []
)
config_rules = await _config_permission_rules(datasette, actor, action)
rules.extend(config_rules)