Skip to content

feat(core): allow injected ACL scope matching - #226

Merged
khaliqgant merged 1 commit into
mainfrom
codex/core-path-scoped-acl
May 31, 2026
Merged

khaliqgant merged 1 commit into
mainfrom
codex/core-path-scoped-acl

Conversation

@khaliqgant

Copy link
Copy Markdown
Member

Summary

  • add an optional generic scopeMatches callback to core ACL evaluation
  • thread the callback through tree, query, and export reads with per-row read path context
  • preserve default exact scope matching when no callback is provided

Tests

  • npm run build --workspace=packages/core
  • npm run test --workspace=packages/core

@gemini-code-assist

Copy link
Copy Markdown

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@coderabbitai

coderabbitai Bot commented May 31, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces pluggable scope matching to ACL evaluation, allowing callers to inject custom path-aware scope matchers. It adds ScopeMatchContext and PermissionEvaluationOptions types, updates filePermissionAllows to use custom matchers when provided, and threads aclOptions through listTree, queryFiles, and workspace export functions, with full test coverage.

Changes

ACL Scope Matching and Integration

Layer / File(s) Summary
ACL scope matching contract and implementation
packages/core/src/acl.ts
Introduces exported ScopeMatchContext and PermissionEvaluationOptions types defining optional requestedPath, action, and pluggable scopeMatches predicate. Updates filePermissionAllows to accept an options parameter and uses options.scopeMatches (when provided) to evaluate scope: rules instead of simple membership checks against claims.
File filtering and tree APIs with ACL options
packages/core/src/tree.ts, packages/core/src/query.ts
listTree and queryFiles each accept an optional aclOptions parameter and pass it through to filePermissionAllows, constructing an options object with the current file path as requestedPath and defaulting action to "read", enabling path-aware scope matching during file traversal and filtering.
Workspace export functions with ACL options
packages/core/src/export.ts
exportWorkspaceJson accepts aclOptions and applies per-file permission evaluation with the current file path as requestedPath to filter returned files; exportWorkspacePatch and exportWorkspaceTarGzip accept and forward aclOptions to the JSON export, applying the same filtering to patch and tar-compressed outputs.
ACL scope matching test coverage
packages/core/src/acl.test.ts
Defines test helpers to construct token claims, file rows, and mock storage, then verifies default exact-scope matching behavior and validates that callers can inject a custom scopeMatches function enabling path-aware filtering across listTree, queryFiles, and exportWorkspaceJson, confirming expected paths are returned and matchers are invoked.

Sequence Diagram

sequenceDiagram
  participant Caller
  participant TreeAPI as listTree()
  participant QueryAPI as queryFiles()
  participant ExportAPI as exportWorkspaceJson()
  participant PermCheck as filePermissionAllows()
  participant CustomMatcher as options.scopeMatches

  Caller->>TreeAPI: listTree(..., aclOptions)
  TreeAPI->>PermCheck: filePermissionAllows(..., {aclOptions, requestedPath, action: "read"})
  PermCheck->>CustomMatcher: scopeMatches(scope, claims, context)
  CustomMatcher-->>PermCheck: boolean
  PermCheck-->>TreeAPI: boolean
  
  Caller->>QueryAPI: queryFiles(..., aclOptions)
  QueryAPI->>PermCheck: filePermissionAllows(..., {aclOptions, requestedPath, action: "read"})
  PermCheck->>CustomMatcher: scopeMatches(scope, claims, context)
  CustomMatcher-->>PermCheck: boolean
  PermCheck-->>QueryAPI: boolean

  Caller->>ExportAPI: exportWorkspaceJson(..., aclOptions)
  ExportAPI->>PermCheck: filePermissionAllows(..., {aclOptions, requestedPath, action: "read"})
  PermCheck->>CustomMatcher: scopeMatches(scope, claims, context)
  CustomMatcher-->>PermCheck: boolean
  PermCheck-->>ExportAPI: boolean
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A clever scheme takes shape today,
Where scopes can match in custom way,
From trees to files to exports flowing,
Through options passed, the matchers knowing,
Path-aware gates now open wide! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.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
Title check ✅ Passed Title is concise and directly summarizes the primary change: enabling injected/custom ACL scope matching in core.
Description check ✅ Passed Description accurately summarizes the changes (adds scopeMatches and threads it through tree/query/export) and lists test commands.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/core-path-scoped-acl

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 and usage tips.

@github-actions

Copy link
Copy Markdown

Relayfile Eval Review

Run: .relayfile/evals/runs/2026-05-31T19-38-45-106Z-HEAD-provider
Mode: provider
Git SHA: 6ca5bfc

Passed: 4 | Needs human: 0 | Reviewable: 0 | Missing output: 0 | Failed: 0 | Skipped: 0

Human Review Cases

No reviewable human-review cases captured Relayfile output.

@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: 3

🧹 Nitpick comments (1)
packages/core/src/acl.test.ts (1)

65-99: ⚡ Quick win

Add a regression test for caller-supplied action.

Please cover the case where aclOptions.action is passed as "write" or "manage" and assert that tree/query/export still invoke scope matching with context.action === "read". That would lock in the PR’s stated read-path behavior.

🤖 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 `@packages/core/src/acl.test.ts` around lines 65 - 99, Add a regression test
variant that passes aclOptions.action set to "write" and another with "manage"
and asserts that listTree, queryFiles, and exportWorkspaceJson still call the
injected scopeMatches with context.action === "read"; specifically, reuse the
existing test setup (claims, rows, repo, and scopeMatches mock) but pass
aclOptions = { scopeMatches, action: "write" } and then again with action:
"manage", and assert the returned file lists remain ["/github/LAYOUT.md"] and
that scopeMatches was called and its received context.action equals "read" for
each invocation of listTree, queryFiles, and exportWorkspaceJson.
🤖 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 `@packages/core/src/export.ts`:
- Around line 32-36: In exportWorkspaceJson(), ensure ACL checks always use
action: "read" instead of honoring caller-provided aclOptions.action; locate
where aclOptions is spread into the ACL check object (the snippet with
"...aclOptions, action: aclOptions.action ?? 'read', requestedPath: row.path")
and change it to force action: "read" (keep requestedPath: row.path and preserve
other aclOptions fields).

In `@packages/core/src/query.ts`:
- Around line 89-94: The authorization check in queryFiles() currently lets
aclOptions.action override the intended "read" semantics when calling
filePermissionAllows, which can wrongly evaluate visibility using non-read
actions; change the call site so the action passed is always "read" (ignore or
override aclOptions.action) while still forwarding other aclOptions, i.e. call
filePermissionAllows(effectivePermissions, workspaceId, claims, { ...aclOptions,
action: "read", requestedPath: row.path }), ensuring file visibility is always
checked as a read operation.

In `@packages/core/src/tree.ts`:
- Around line 64-68: listTree() currently passes through aclOptions.action
allowing callers to change the ACL check; change it to always use action: "read"
so authorization is based on read scope only, i.e., replace the spread that
includes action: aclOptions.action ?? "read" with a literal action: "read" while
still passing requestedPath (filePath) per row; update any references where
aclOptions is merged for ACL evaluation (e.g., the object created near
listTree() that includes requestedPath) to ensure callers cannot override
action.

---

Nitpick comments:
In `@packages/core/src/acl.test.ts`:
- Around line 65-99: Add a regression test variant that passes aclOptions.action
set to "write" and another with "manage" and asserts that listTree, queryFiles,
and exportWorkspaceJson still call the injected scopeMatches with context.action
=== "read"; specifically, reuse the existing test setup (claims, rows, repo, and
scopeMatches mock) but pass aclOptions = { scopeMatches, action: "write" } and
then again with action: "manage", and assert the returned file lists remain
["/github/LAYOUT.md"] and that scopeMatches was called and its received
context.action equals "read" for each invocation of listTree, queryFiles, and
exportWorkspaceJson.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d2631df1-9f1f-4c1e-a37c-63ad6fb427df

📥 Commits

Reviewing files that changed from the base of the PR and between 8932c2a and 4b8e1b1.

📒 Files selected for processing (5)
  • packages/core/src/acl.test.ts
  • packages/core/src/acl.ts
  • packages/core/src/export.ts
  • packages/core/src/query.ts
  • packages/core/src/tree.ts

Comment on lines +32 to +36
{
...aclOptions,
action: aclOptions.action ?? "read",
requestedPath: row.path,
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Export ACL checks should always run as "read".

This is the content-export path, so honoring a caller-provided aclOptions.action can authorize exports against the wrong action. exportWorkspaceJson() should force action: "read" and only vary requestedPath.

Suggested fix
         {
           ...aclOptions,
-          action: aclOptions.action ?? "read",
+          action: "read",
           requestedPath: row.path,
         },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
...aclOptions,
action: aclOptions.action ?? "read",
requestedPath: row.path,
},
{
...aclOptions,
action: "read",
requestedPath: row.path,
},
🤖 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 `@packages/core/src/export.ts` around lines 32 - 36, In exportWorkspaceJson(),
ensure ACL checks always use action: "read" instead of honoring caller-provided
aclOptions.action; locate where aclOptions is spread into the ACL check object
(the snippet with "...aclOptions, action: aclOptions.action ?? 'read',
requestedPath: row.path") and change it to force action: "read" (keep
requestedPath: row.path and preserve other aclOptions fields).

Comment on lines +89 to +94
if (
!filePermissionAllows(effectivePermissions, workspaceId, claims, {
...aclOptions,
action: aclOptions.action ?? "read",
requestedPath: row.path,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not let query authorization switch off "read".

queryFiles() is also a read surface. Allowing aclOptions.action to override the action here means a caller can evaluate file visibility with non-read semantics, which is the wrong contract for this API.

Suggested fix
       {
         ...aclOptions,
-        action: aclOptions.action ?? "read",
+        action: "read",
         requestedPath: row.path,
       })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (
!filePermissionAllows(effectivePermissions, workspaceId, claims, {
...aclOptions,
action: aclOptions.action ?? "read",
requestedPath: row.path,
})
if (
!filePermissionAllows(effectivePermissions, workspaceId, claims, {
...aclOptions,
action: "read",
requestedPath: row.path,
})
🤖 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 `@packages/core/src/query.ts` around lines 89 - 94, The authorization check in
queryFiles() currently lets aclOptions.action override the intended "read"
semantics when calling filePermissionAllows, which can wrongly evaluate
visibility using non-read actions; change the call site so the action passed is
always "read" (ignore or override aclOptions.action) while still forwarding
other aclOptions, i.e. call filePermissionAllows(effectivePermissions,
workspaceId, claims, { ...aclOptions, action: "read", requestedPath: row.path
}), ensuring file visibility is always checked as a read operation.

Comment thread packages/core/src/tree.ts
Comment on lines +64 to +68
{
...aclOptions,
action: aclOptions.action ?? "read",
requestedPath: filePath,
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Force "read" for tree ACL evaluation.

listTree() is a read API, so letting callers override action here changes authorization semantics and can make entry visibility depend on "write"/"manage" scopes instead of read scopes. Hardcode action: "read" and keep only requestedPath injected per row.

Suggested fix
         {
           ...aclOptions,
-          action: aclOptions.action ?? "read",
+          action: "read",
           requestedPath: filePath,
         },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
...aclOptions,
action: aclOptions.action ?? "read",
requestedPath: filePath,
},
{
...aclOptions,
action: "read",
requestedPath: filePath,
},
🤖 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 `@packages/core/src/tree.ts` around lines 64 - 68, listTree() currently passes
through aclOptions.action allowing callers to change the ACL check; change it to
always use action: "read" so authorization is based on read scope only, i.e.,
replace the spread that includes action: aclOptions.action ?? "read" with a
literal action: "read" while still passing requestedPath (filePath) per row;
update any references where aclOptions is merged for ACL evaluation (e.g., the
object created near listTree() that includes requestedPath) to ensure callers
cannot override action.

@agent-relay-code

Copy link
Copy Markdown
Contributor

⚠️ pr-reviewer did not push — the PR branch advanced during the review, so fixes were withheld to avoid overwriting newer commits. Re-trigger the review once the branch settles. The notes below are advisory and were not pushed.

Reviewed the checked-out PR artifacts, which identify AgentWorkforce/relay PR #1019 rather than relayfile PR #226.

Fixed the remaining actionable review issues:

  • Added legacy .agentworkforce/workspaces.json fallback and reserved-key sanitization in workspace key storage.
  • Validated workspace create names before remote workspace creation.
  • Kept drive and passthrough signal handlers installed until async delivery-mode restore completes.
  • Added OG ellipsis font coverage and removed nonzero OG letter spacing.
  • Added regression tests and updated CHANGELOG.md.

Validation:

  • Prettier check passed on edited files.
  • Narrow TypeScript check passed for workspace-store.ts.
  • Full typecheck/tests could not run because npm install was killed and the local node_modules tree is incomplete/missing type packages and Vitest binaries.

@agent-relay-code agent-relay-code Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ pr-reviewer did not push — the PR branch advanced during the review, so fixes were withheld to avoid overwriting newer commits. Re-trigger the review once the branch settles. The notes below are advisory and were not pushed.

Reviewed the checked-out PR artifacts, which identify AgentWorkforce/relay PR #1019 rather than relayfile PR #226.

Fixed the remaining actionable review issues:

  • Added legacy .agentworkforce/workspaces.json fallback and reserved-key sanitization in workspace key storage.
  • Validated workspace create names before remote workspace creation.
  • Kept drive and passthrough signal handlers installed until async delivery-mode restore completes.
  • Added OG ellipsis font coverage and removed nonzero OG letter spacing.
  • Added regression tests and updated CHANGELOG.md.

Validation:

  • Prettier check passed on edited files.
  • Narrow TypeScript check passed for workspace-store.ts.
  • Full typecheck/tests could not run because npm install was killed and the local node_modules tree is incomplete/missing type packages and Vitest binaries.

@khaliqgant
khaliqgant merged commit 9f333ed into main May 31, 2026
9 checks passed
@khaliqgant
khaliqgant deleted the codex/core-path-scoped-acl branch May 31, 2026 19:43
@agent-relay-code

Copy link
Copy Markdown
Contributor

Fixed the PR issue in acl.test.ts: the new scopeMatches callback test now types context as ScopeMatchContext, so strict TypeScript checks the public ACL option contract instead of producing an implicit any.

Verification run locally:

  • npm run test --workspace=packages/core passed
  • npm run build --workspace=packages/core passed
  • scripts/check-contract-surface.sh passed
  • Root JS/TS tests passed; root commands only failed at Go steps because go is not installed in this environment

@agent-relay-code

Copy link
Copy Markdown
Contributor

✅ pr-reviewer applied fixes — committed and pushed c46e903 to this PR. The notes below describe what changed.

Fixed the PR issue in acl.test.ts: the new scopeMatches callback test now types context as ScopeMatchContext, so strict TypeScript checks the public ACL option contract instead of producing an implicit any.

Verification run locally:

  • npm run test --workspace=packages/core passed
  • npm run build --workspace=packages/core passed
  • scripts/check-contract-surface.sh passed
  • Root JS/TS tests passed; root commands only failed at Go steps because go is not installed in this environment

@agent-relay-code agent-relay-code Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ pr-reviewer applied fixes — committed and pushed c46e903 to this PR. The notes below describe what changed.

Fixed the PR issue in acl.test.ts: the new scopeMatches callback test now types context as ScopeMatchContext, so strict TypeScript checks the public ACL option contract instead of producing an implicit any.

Verification run locally:

  • npm run test --workspace=packages/core passed
  • npm run build --workspace=packages/core passed
  • scripts/check-contract-surface.sh passed
  • Root JS/TS tests passed; root commands only failed at Go steps because go is not installed in this environment

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.

1 participant