Repository navigation
feat(core): enforce Console-managed policies - #49729
Merged
Merged
Conversation
The Console returns experimental.policies from /api/v2/config, but the client decoded only providers and websearch, so nothing was enforced. - Decode the statements and hold them in a process-global ManagedPolicy service the Console plugin writes and the policy plugin reads. - Evaluate organization statements after every reversed authored document so they have final authority; name the organization in the denial message. - Keep the last config for the same connection when a fetch or token refresh fails, so an outage cannot lift organization policy while personal credentials keep working. Switch, disconnect, and 404 still replace it. - Ignore plugin removals for opencode.config.policy and opencode.provider.opencode so a repository cannot disable enforcement. - Cover the permission action in tests and the spec, regenerate the OpenAPI enum, and add the V2 policies docs page.
adamdotdevin
force-pushed
the
managed-policy
branch
from
September 19, 2026 09:34
27a69a9 to
e7d040b
Compare
1 task done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
OpenCode Console compiles per-member Providers and Tools policies into
experimental.policiesstatements and returns them fromGET /api/v2/config. The client fetched that response but decoded onlyprovidersandwebsearch, so the statements were dropped and nothing was enforced. This PR makes V2 read them and evaluate them with organization authority.Changes
Decode and hold the statements
RemoteResponseinpackages/core/src/plugin/provider/opencode.tsnow decodesexperimental.policies(ConfigPolicy.Info[]). Omittedexperimentaland an empty array are equivalent; unknown fields are ignored.ManagedPolicyservice (packages/core/src/managed-policy.ts) holding{ statements, organization }. The Console plugin writes it on every snapshot apply; the policy plugin reads it synchronously. Registered inPluginInternalservices/requirements.Evaluate with final authority
opencode.config.policybuilds one list: reversed authored documents, then organization statements. SamefindLast, both actions. Orgdeny *cannot be lifted by a repo/userallow; orgallowlifts a lower-authoritydeny.Blocked by <org>'s policy(orBlocked by your organization's policy); authored statements keepBlocked by configuration policy.Failure semantics (extends #49873 — please confirm)
/api/v2/configfetch fails for the same connection. This PR routes the OAuth token refresh failure through the same rule: a Console outage that happens to coincide with a near-expiry access token otherwise still clears the snapshot, which fails closed for managed providers but open for policy. Disconnect, credential switch, and 404 still replace the snapshot. No durable offline cache.load()is restructured so both failure points share oneEffect.catch; the upstream retention test passes unchanged through it.Protect the enforcement path (decision — please confirm)
PluginSupervisor.resolveignoresremoveoperations matchingopencode.config.policyoropencode.provider.opencode, whatever the selector (-*,-opencode.*, exact). Otherwise a repository"plugins": ["-opencode.config.policy"]switches off enforcement. The IDs live inPluginInternal.guarded. This narrows-pluginsemantics for exactly these two internal IDs.Housekeeping owed by the
permissionactionactionenum inpackages/protocol/openapi.jsonand the www copies was regenerated upstream by refactor(schema): separate provider and model settings #49850 (client types by chore(client): regenerate policy types #49613), so no generated-file changes remain in this PR.specs/v2/provider-policy.md:permissionaction, Console delivery contract, authority order, failure rule, supervisor guard.services/www/src/docs/content/policies.mdx; linked fromconfig.mdx,permissions.mdx,plugins.mdx,migrate-v1.mdx, andconsole/index.mdx; added to sidebar navigation.Tests
test/config/policy.test.ts:permissiondeny/allow/ask semantics,action:resourcematching incl. trailing*and compound resources, bare*, org statements evaluated last (deny-all + allow, allow restoring a user deny, org deny beating a project allow with the org-named message), catalog rebuild after replacing statements.test/plugin/provider-opencode.test.ts: end-to-end through the plugin host (Consoledeny *+allow opencoderemoves a localanthropic, keepsopencode, unknown fields ignored); statements bound to the connection across poll, unchanged skip, 503 retention, credential switch to another org, 404 clear, and disconnect clear.test/plugin/supervisor.test.ts:-opencode.config.policy/-opencode.provider.opencode/-opencode.config.agentand-*leave the guarded plugins active while other removals still apply.bun run checkpasses.test/config+test/pluginpass except 7 pre-existing failures insupervisor-reload.test.tsandmodule.test.tsthat fail identically on a cleanorigin/v2checkout in this environment (module watcher timeouts).Verification
Rebased onto
origin/v2at5848ee0d24(only conflict:load()inopencode.tsagainst #49873, resolved as described above). The manual check against a dev Console running the Console repo'spolicy-typesbranch (§6 of the handoff) has not been performed in this PR — I did not have a dev Console available. Before merging, someone with one should confirm: a denied local provider disappears from the model list,sudo lsis refused with the organization message whilelsstill works, a project with"plugins": ["-opencode.config.policy"]changes nothing, and removing the Console policy restores both within a minute.