Repository navigation
Description
Description
evaluateInput resolves an empty resources list to allow, discarding the fail-safe default the evaluator itself uses.
evaluate() (packages/core/src/permission.ts:87-95) treats an unmatched rule as ask, never allow:
.findLast((rule) => Wildcard.match(action, rule.action) && Wildcard.match(resource, rule.resource)) ?? {
action, resource: "*", effect: "ask",
}but the aggregation over a list of resources has no such default (packages/core/src/permission.ts:177-178):
const effects = input.resources.map((resource) => evaluate(input.action, resource, all).effect)
const effect: Permission.Effect = effects.includes("ask") ? "ask" : "allow"With resources: [] the map is empty, so nothing can hold the line and the answer is allow. denied() (:165-167) is vacuously false on [] for the same reason. Request.resources is a plain Schema.Array(Schema.String), so nothing rejects an empty list.
Reachable today from the always-registered opencode_list_mcp_resources tool, whose own input description invites the model to omit server ("Omit to list every server"). With no MCP servers configured, mcp-resource.ts:34-35 computes servers as [] and asserts with resources: servers (:36-44) — the comment above it says "Listing every server checks each server name so per-server rules still apply", and with no servers no check happens. A configured deny for that action, or any deny-all agent (missingAgentPermissions, permission.ts:20), does not apply, and no prompt is shown.
The blast radius for this one tool is small: no servers means an empty catalog anyway. What makes it worth fixing is the shape — resources: <filtered list> is the established in-tree pattern, so any tool that builds its list from a filter inherits a silent allow, and nothing in the UI shows that a rule was skipped. No test covers an empty resources (git grep 'resources: \[\]' over the tree only hits e2e mock payloads).
Shape question: should an empty list be ask (fail safe, consistent with evaluate's default), or should the schema require a non-empty list and the call site skip the assert when there is nothing to check? I lean to the first, since a tool that would otherwise be unfiltered should not become unprompted — happy to be argued out of it.
Steps to reproduce
- Configure no MCP servers, and a permission rule that denies the action:
{ "permissions": [{ "action": "opencode_list_mcp_resources", "resource": "*", "effect": "deny" }] }- Ask the model to list MCP resources (it calls the tool with no
serverargument, as the input description suggests). - The call proceeds instead of being blocked, and no prompt appears.
Verified at c33de198e (v2) against the real service: ask({action, resources: []}) under a deny-all ruleset returns {effect: "allow"}, and assert with resources: [] returns without raising BlockedError. permission.ts is identical at that commit and at the local v2 head, so the line numbers hold for both.
OpenCode version
c33de198e (v2)
Operating System
macOS (arm64) — service-level test with bun test, no local TUI session and no model call.
Activity
opencodejr commentedon Oct 28, 2026
Thanks for the detailed report. I reproduced this on the latest
dev(86ce19d96) with a service-level test of the permission service (attached below). I didn't run it atc33de198e, but that commit is an ancestor ofdevand thedenied()/evaluateInputlines are the same there.{ action: "*", resource: "*", effect: "deny" },ask({ action: "opencode_list_mcp_resources", resources: [] })returns{ effect: "allow" }.resources: []returnsallow, whileresources: ["x"]returnsask. So the defaultaskis lost only when the list is empty.denyrule foropencode_list_mcp_resources,assertwithresources: []succeeds instead of raisingBlockedError.The cause matches your analysis:
denied()uses.some()and the aggregation inevaluateInputuses.includes("ask"), and both fall through toallowwhen the list is empty. This is now with the maintainers to decide on the fix (an empty list resolving toask/wildcard vs. a non-empty schema).Reproduction script (permission-empty-resources.test.ts)
Run it:
bun permission-empty-resources.test.ts