Summary
PermissionChecker.evaluate() returns allow for any tool in allowed_tools before it checks the user-configured denied_commands patterns and path_rules deny-rules. As a result, allow-listing a broad tool such as bash or write_file silently disables the user's own deny policy.
This contradicts the design used everywhere else in the checker, where deny takes precedence over allow. Built-in sensitive-path protection and the explicit denied_tools list are both evaluated before the allow-list (and there is even a test, test_allowed_tools_does_not_bypass_sensitive_paths, asserting the allow-list cannot bypass sensitive paths). denied_commands and path deny-rules are the inconsistent exceptions.
Affected code
src/openharness/permissions/checker.py — in evaluate(), the allow-list short-circuit at line 105 returns before the path deny-rule loop (lines 108–117) and the denied_commands loop (lines 119–126):
# Explicit tool allow list
if tool_name in self._settings.allowed_tools:
return PermissionDecision(allowed=True, reason=f"{tool_name} is explicitly allowed")
# Check path-level rules
if file_path and self._path_rules:
... # deny rules here are never reached for allow-listed tools
# Check command deny patterns (e.g. deny "rm -rf /")
if command:
... # denied_commands here are never reached for allow-listed tools
Reproduction
from openharness.config.settings import PathRuleConfig, PermissionSettings
from openharness.permissions import PermissionChecker, PermissionMode
# 1) denied_commands bypassed by allow-listing `bash`
s = PermissionSettings.model_construct(
mode=PermissionMode.DEFAULT, allowed_tools=["bash"], denied_tools=[],
denied_commands=["*rm -rf /*"], path_rules=[],
)
print(PermissionChecker(s).evaluate(
"bash", is_read_only=False, command="rm -rf / --no-preserve-root").allowed)
# expected: False (matches deny pattern) -> actual: True
# 2) path deny-rule bypassed by allow-listing `write_file`
s2 = PermissionSettings.model_construct(
mode=PermissionMode.DEFAULT, allowed_tools=["write_file"], denied_tools=[],
denied_commands=[], path_rules=[PathRuleConfig(pattern="/etc/*", allow=False)],
)
print(PermissionChecker(s2).evaluate(
"write_file", is_read_only=False, file_path="/etc/passwd").allowed)
# expected: False (matches deny rule) -> actual: True
Both print True. With allowed_tools empty, the same calls are correctly denied, confirming the allow-list short-circuit is the cause.
Impact
Users commonly allow-list bash (or another mutating tool) for a smoother session while relying on denied_commands / path_rules as guardrails against destructive or sensitive operations — exactly the configuration where this matters most. Because the LLM drives tool calls, this also weakens defense-in-depth against prompt-injection-directed destructive commands. Severity is high: a configured deny rule provides no protection once the tool is allow-listed.
Expected behavior
Deny rules should take precedence over the allow-list (as they already do for denied_tools and built-in sensitive paths). denied_commands and path_rules deny-rules should be evaluated before the allowed_tools short-circuit.
Environment
- OpenHarness
main (v0.1.9)
- Python 3.10 / 3.11
I'll open a PR that reorders the deny checks ahead of the allow-list and adds regression tests.
Summary
PermissionChecker.evaluate()returns allow for any tool inallowed_toolsbefore it checks the user-configureddenied_commandspatterns andpath_rulesdeny-rules. As a result, allow-listing a broad tool such asbashorwrite_filesilently disables the user's own deny policy.This contradicts the design used everywhere else in the checker, where deny takes precedence over allow. Built-in sensitive-path protection and the explicit
denied_toolslist are both evaluated before the allow-list (and there is even a test,test_allowed_tools_does_not_bypass_sensitive_paths, asserting the allow-list cannot bypass sensitive paths).denied_commandsand path deny-rules are the inconsistent exceptions.Affected code
src/openharness/permissions/checker.py— inevaluate(), the allow-list short-circuit at line 105 returns before the path deny-rule loop (lines 108–117) and thedenied_commandsloop (lines 119–126):Reproduction
Both print
True. Withallowed_toolsempty, the same calls are correctly denied, confirming the allow-list short-circuit is the cause.Impact
Users commonly allow-list
bash(or another mutating tool) for a smoother session while relying ondenied_commands/path_rulesas guardrails against destructive or sensitive operations — exactly the configuration where this matters most. Because the LLM drives tool calls, this also weakens defense-in-depth against prompt-injection-directed destructive commands. Severity is high: a configured deny rule provides no protection once the tool is allow-listed.Expected behavior
Deny rules should take precedence over the allow-list (as they already do for
denied_toolsand built-in sensitive paths).denied_commandsandpath_rulesdeny-rules should be evaluated before theallowed_toolsshort-circuit.Environment
main(v0.1.9)I'll open a PR that reorders the deny checks ahead of the allow-list and adds regression tests.