Skip to content

fix(sdd): propagate top-level permissions to orchestrator agents#956

Open
decode2 wants to merge 4 commits into
Gentleman-Programming:mainfrom
decode2:fix/issue-787-opencode-permission-bypass
Open

fix(sdd): propagate top-level permissions to orchestrator agents#956
decode2 wants to merge 4 commits into
Gentleman-Programming:mainfrom
decode2:fix/issue-787-opencode-permission-bypass

Conversation

@decode2

@decode2 decode2 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Closes #787

Summary by CodeRabbit

  • Bug Fixes

    • Improves configuration merging by propagating top-level permission settings (read, bash, write) into targeted orchestrator agents, while preserving agent-specific overrides and task replacement entries.
    • Stops the merge/injection process and returns an error if permission propagation fails, instead of writing an unprocessed output.
  • Tests

    • Added unit tests to verify permission propagation behavior for targeted agents, no propagation for non-target agents, and no changes when no relevant top-level permission keys exist.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Propagates root permission settings into selected orchestrator agent permissions during opencode.json merging, and adds regression tests for propagation and no-op cases.

Changes

Orchestrator permission propagation

Layer / File(s) Summary
Merge post-processing
internal/components/sdd/inject.go
mergeJSONFile now runs permission propagation after merging JSON, and the helper copies root read, bash, and write entries into targeted orchestrator agent permission maps before reserializing when needed.
Regression coverage
internal/components/sdd/inject_test.go
TestMergeJSONFile_PropagatesPermissions verifies merged orchestrator agents keep propagated read, bash, and write blocks while retaining overlay-defined task rules, and TestMergeJSONFile_NoPropagatesIfNoKeys verifies the helper leaves non-matching permissions unchanged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main fix: propagating top-level permissions to orchestrator agents.
Linked Issues check ✅ Passed The implementation propagates root permissions into gentle-orchestrator and sdd-orchestrator-* agents, preserving the deny rules the issue requires.
Out of Scope Changes check ✅ Passed The changes stay focused on permission propagation and regression tests, with no obvious unrelated scope added.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/components/sdd/inject_test.go`:
- Around line 4477-4575: The merge test only covers agents without existing
read/bash rules, so it cannot catch overwrite behavior in mergeJSONFile. Extend
TestMergeJSONFile_PropagatesPermissions to include an agent that already defines
its own read and bash entries, and assert those agent-specific rules still exist
after merge alongside the propagated top-level permissions. Use mergeJSONFile
and the permission handling under agent/gentle-orchestrator and
agent/sdd-orchestrator-custom to verify merge-not-replace semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 13af078d-c74b-40f1-85f8-2a2bf749c0d7

📥 Commits

Reviewing files that changed from the base of the PR and between 8a67347 and 1681a33.

📒 Files selected for processing (2)
  • internal/components/sdd/inject.go
  • internal/components/sdd/inject_test.go

Comment thread internal/components/sdd/inject_test.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/components/sdd/inject.go (1)

1494-1499: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Empty permission: {} blocks may be injected when no propagatable keys exist.

changed is set to true as soon as an empty permission map is created, before confirming any of read/bash/write are actually present in topPerms. If the top-level permission contains none of those three keys, every orchestrator agent that lacked a permission block gets an empty "permission": {} written and the file is re-marshaled unnecessarily. Consider creating/attaching the map lazily only when a key is actually propagated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/components/sdd/inject.go` around lines 1494 - 1499, The permission
propagation logic in inject.go is eagerly creating an empty
agentMap["permission"] and marking changed before confirming any propagatable
keys exist. Update the permission handling near the agentPerms initialization so
the map is only created and attached in the loop that copies read/bash/write
from topPerms, and only set changed when at least one of those keys is actually
propagated. This keeps orchestrator agents from getting empty permission blocks
and avoids unnecessary re-marshaling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/components/sdd/inject.go`:
- Around line 1502-1507: The non-map fallback in inject.go’s merge logic is
overwriting an agent’s existing per-pattern map and marking the value changed
even when nothing actually changed. Update the branch around topMap/okTop so a
scalar top-level value like the one merged into agentPerms[permKey] only
replaces the agent value when there is no existing map to preserve, and set
changed = true only if the stored value is actually different. Keep the fix
localized to the merge path that handles permKey and agentPerms.
- Around line 1500-1537: In propagateTopLevelPermissions, exact glob collisions
for read/bash/write must let top-level deny override agent allow instead of only
filling missing keys. Update the merge logic in inject.go so when topPerms and
agentPerms contain the same glob key, a top-level deny replaces any existing
agent rule rather than leaving the agent’s value intact. Use the existing
propagateTopLevelPermissions flow and the read, bash, and write permission map
handling to locate the collision branch.

---

Outside diff comments:
In `@internal/components/sdd/inject.go`:
- Around line 1494-1499: The permission propagation logic in inject.go is
eagerly creating an empty agentMap["permission"] and marking changed before
confirming any propagatable keys exist. Update the permission handling near the
agentPerms initialization so the map is only created and attached in the loop
that copies read/bash/write from topPerms, and only set changed when at least
one of those keys is actually propagated. This keeps orchestrator agents from
getting empty permission blocks and avoids unnecessary re-marshaling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2ddcef9e-636c-4802-9fd9-93562c428d57

📥 Commits

Reviewing files that changed from the base of the PR and between 1681a33 and c14794d.

📒 Files selected for processing (2)
  • internal/components/sdd/inject.go
  • internal/components/sdd/inject_test.go

Comment thread internal/components/sdd/inject.go
Comment thread internal/components/sdd/inject.go
@decode2

decode2 commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

@Alan-TheGentleman I've addressed the CodeRabbit review feedback: merged permissions are now propagated lazily, scalar top-level values won't clobber pre-existing agent permission maps, and top-level denies override agent-level allows for matching globs. I also extended TestMergeJSONFile_PropagatesPermissions and added TestMergeJSONFile_NoPropagatesIfNoKeys to verify these semantics.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/components/sdd/inject.go`:
- Around line 1502-1524: The permission merge logic in the `permKey` loop
currently lets an existing agent-side map override a top-level scalar deny,
which can bypass a global restriction. Update the merge behavior in the
`topPerms`/`agentPerms` handling so that scalar deny values like `"read":
"deny"` always win over an agent map, or explicitly overwrite the agent entry
when the scalar is restrictive. Keep the change localized to the permission
propagation block in `inject.go` and preserve the existing map-vs-scalar
handling for non-restrictive cases if that is intentional.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b1b6dfed-afa5-4a79-a3aa-0e956ce5206f

📥 Commits

Reviewing files that changed from the base of the PR and between c14794d and 5aff735.

📒 Files selected for processing (2)
  • internal/components/sdd/inject.go
  • internal/components/sdd/inject_test.go

Comment thread internal/components/sdd/inject.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/components/sdd/inject.go (1)

1468-1471: 🗄️ Data Integrity & Integration | 🟠 Major

Add explicit backup and restore handling for user config writes.

The mergeJSONFile function in internal/components/sdd/inject.go persists merged configuration using filemerge.WriteFileAtomic (line 1457). While the current implementation ensures atomicity and idempotency, it lacks a backup/restore path for the existing configuration file before overwriting it.

Per the project guidelines: "Any write to user config must have a backup/restore path."

The current WriteFileAtomic implementation (lines 42-111 in internal/components/filemerge/writer.go) writes to a temporary file and renames it directly to the target path. If the merged result is malformed or incorrect, the original user configuration is lost without a recovered path.

Required fix:

  1. Modify the write path to copy the existing file to a backup location (e.g., path.bak) before performing the atomic write.
  2. Ensure the backup is removed only after the new write is confirmed successful, or provide a mechanism for the caller to restore the backup on failure.

Do not rely solely on atomic rename for backup safety; an explicit copy operation is required.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/components/sdd/inject.go` around lines 1468 - 1471, The config write
path in mergeJSONFile/WriteFileAtomic needs an explicit backup and restore flow,
not just atomic rename. Before overwriting the user config, copy the existing
file to a backup path (for example via a helper around filemerge.WriteFileAtomic
or in mergeJSONFile), then only delete that backup after the new write succeeds.
If the merge/write fails, restore from the backup so the original config remains
recoverable, and use the existing mergeJSONFile and filemerge.WriteFileAtomic
symbols to place the change.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/components/sdd/inject.go`:
- Around line 1468-1471: The config write path in mergeJSONFile/WriteFileAtomic
needs an explicit backup and restore flow, not just atomic rename. Before
overwriting the user config, copy the existing file to a backup path (for
example via a helper around filemerge.WriteFileAtomic or in mergeJSONFile), then
only delete that backup after the new write succeeds. If the merge/write fails,
restore from the backup so the original config remains recoverable, and use the
existing mergeJSONFile and filemerge.WriteFileAtomic symbols to place the
change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b72c45e3-f61b-4575-9b2c-fc69e734d9e1

📥 Commits

Reviewing files that changed from the base of the PR and between 5aff735 and ecaac13.

📒 Files selected for processing (2)
  • internal/components/sdd/inject.go
  • internal/components/sdd/inject_test.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(opencode): orchestrator agent permission override bypasses top-level sensitive-file read denies

1 participant