Fix absolute glob expansion in sandbox#268
Conversation
|
@codex review this PR Please use the following severity format for all findings:
Prefix each finding title with its priority label, e.g. "P0: ...", "P1: ...", etc. |
|
Codex Review: Didn't find any major issues. 👍 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review this PR Please use the following severity format for all findings:
Prefix each finding title with its priority label, e.g. "P0: ...", "P1: ...", etc. |
AlexandreYang
left a comment
There was a problem hiding this comment.
Code Review — PR #268 (alex/glob_bug)
Assessment: safe to merge. The fix is correct and secure. One P3 (dead code) noted below.
What this PR does
Adds isAncestorOfRoot to the sandbox and uses it in readDirN to return (nil, nil) when the glob expander calls ReadDirForGlob on a directory that is an ancestor of an allowed root (rather than inside one). This is needed because mvdan.cc/sh's glob algorithm calls ReadDir2 on every path component — including non-meta ones — to verify they exist. Without the fix, a permission error on the ancestor component caused the entire absolute glob to stay unexpanded.
Security analysis
isAncestorOfRoot only returns true when the queried path is a genuine parent of an allowed root (not a sibling, not an unrelated path). Returning (nil, nil) exposes zero directory contents — the glob expander only uses the error value for non-meta components; the entries are discarded. The ancestor shortcut is gated behind maxEntries > 0, so it is exclusive to ReadDirForGlob and never reached by ReadDir.
All the path arithmetic goes through filepath.Clean / filepath.Rel, which neutralises .. traversal and cross-drive attacks on Windows.
Findings summary
| # | Priority | File | Finding |
|---|---|---|---|
| 1 | allowedpaths/sandbox.go:145 |
Dead code: rel == "." unreachable after the equality guard |
Positive observations
- The
maxEntries > 0gate is exactly the right scoping — ancestor permissiveness is limited to glob expansion, not generalReadDir. - New scenario tests cover bracket ranges, negated classes, intermediate wildcards, nested patterns, and the outside-allowed-paths case. The outside case correctly sets
skip_assert_against_bash: true. filepath.Cleanis applied to both sides beforefilepath.Rel, preventing any..pollution in ancestor detection.
|
Iteration 1 self-review result: 0 P0, 0 P1, 0 P2, 1 P3 finding. The fix is correct and secure. The single P3 finding is dead code ( check in is unreachable after the equality guard). No blocking issues. |
|
@codex review this PR Please use the following severity format for all findings:
Prefix each finding title with its priority label, e.g. "P0: ...", "P1: ...", etc. |
AlexandreYang
left a comment
There was a problem hiding this comment.
Review Summary
Reviewed the PR diff for absolute glob expansion under AllowedPaths, including allowedpaths/sandbox.go, the new Go regression tests, and the new scenario coverage.
Overall assessment: safe to merge.
| # | Priority | File | Finding |
|---|---|---|---|
| — | — | — | No findings |
Coverage Summary
| Code path | Scenario test | Go test | Status |
|---|---|---|---|
| Absolute glob with non-meta ancestors and matches inside the allowed root | tests/scenarios/shell/allowed_paths/absolute_glob_inside_allowed.yaml |
interp/allowed_paths_test.go |
Covered |
| Absolute glob outside allowed roots stays literal without disclosing filenames | tests/scenarios/shell/allowed_paths/absolute_glob_outside_allowed.yaml |
interp/allowed_paths_test.go |
Covered |
| Bracket/range/negation absolute globs inside allowed roots | tests/scenarios/shell/allowed_paths/absolute_glob_bracket_negation.yaml |
— | Covered |
| Multiple independent absolute glob patterns inside allowed roots | tests/scenarios/shell/allowed_paths/absolute_glob_multiple_complex_patterns.yaml |
— | Covered |
| Nested mixed metacharacter absolute glob patterns inside allowed roots | tests/scenarios/shell/allowed_paths/absolute_glob_nested_mixed_patterns.yaml |
— | Covered |
Positive Observations
- The new
ReadDirForGlobbehavior is scoped to glob expansion only (maxEntries > 0), leaving normal directory reads denied outsideAllowedPaths. - Ancestor traversal returns no directory entries, so glob patterns cannot enumerate out-of-sandbox parent directories.
- The test coverage checks both expansion inside the allowed tree and literal preservation outside it.
P0/P1/P2 findings: 0. P3 findings: 0.
|
Iteration 1 self-review result: P0=0, P1=0, P2=0, P3=0. Summary: reviewed the absolute glob |
|
Codex Review: Didn't find any major issues. You're on a roll. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@codex review this PR Please use the following severity format for all findings:
Prefix each finding title with its priority label, e.g. "P0: ...", "P1: ...", etc. |
AlexandreYang
left a comment
There was a problem hiding this comment.
Review Summary
Iteration 2 self-review of PR #268. The head SHA remains 82d1045ad10c10b4c2443307cd77efdbdaca3b13; the reviewed files are unchanged from iteration 1. I rechecked the sandbox ancestor traversal, glob expansion call path, and the absolute-glob scenario coverage.
Overall assessment: safe to merge.
| # | Priority | File | Finding |
|---|---|---|---|
| — | — | — | No findings |
Coverage Summary
| Code path | Scenario test | Go test | Status |
|---|---|---|---|
| Absolute glob expansion inside an allowed nested root | yes | yes | Covered |
| Outside-allowed absolute glob remains literal | yes | yes | Covered |
| Bracket/range/negation and nested mixed absolute globs | yes | — | Covered |
Verification run during this pass:
go test ./interp -run 'TestAllowedPaths(AbsoluteGlobInside|AbsoluteGlobOutsideStaysLiteral|GlobInside|GlobOutside)$'\n-go test ./tests -run 'TestShellScenarios/shell/allowed_paths/absolute_glob' -count=1\n\nP0/P1/P2 findings: 0. P3 findings: 0.
|
Iteration 2 self-review result: P0=0, P1=0, P2=0, P3=0. Summary: re-reviewed the unchanged PR head and reran the focused absolute-glob Go/scenario tests. No blocking findings were identified. |
|
@codex review this PR Please use the following severity format for all findings:
Prefix each finding title with its priority label, e.g. "P0: ...", "P1: ...", etc. |
AlexandreYang
left a comment
There was a problem hiding this comment.
Review Summary
Iteration 3 self-review of PR #268. The PR head is still 82d1045ad10c10b4c2443307cd77efdbdaca3b13. I rechecked the same seven-file diff, with focus on the ReadDirForGlob ancestor shortcut, lack of directory-entry disclosure, bash-compatible sorting/expansion behavior, and coverage for inside/outside AllowedPaths.
Overall assessment: safe to merge.
| # | Priority | File | Finding |
|---|---|---|---|
| — | — | — | No findings |
Verification run during this pass:
go test ./interp -run 'TestAllowedPaths(AbsoluteGlobInside|AbsoluteGlobOutsideStaysLiteral|GlobInside|GlobOutside)$'\n-go test ./tests -run 'TestShellScenarios/shell/allowed_paths/absolute_glob' -count=1\n\nP0/P1/P2 findings: 0. P3 findings: 0.
|
Iteration 3 self-review result: P0=0, P1=0, P2=0, P3=0. Summary: re-reviewed the unchanged seven-file diff and reran focused absolute-glob tests. No blocking findings were identified. |
|
@codex review this PR Please use the following severity format for all findings:
Prefix each finding title with its priority label, e.g. "P0: ...", "P1: ...", etc. |
AlexandreYang
left a comment
There was a problem hiding this comment.
Review Summary
Iteration 4 self-review of PR #268. The PR head remains 82d1045ad10c10b4c2443307cd77efdbdaca3b13, and the changed file set is unchanged. I rechecked the sandbox/glob interaction and the regression coverage again.
Overall assessment: safe to merge.
| # | Priority | File | Finding |
|---|---|---|---|
| — | — | — | No findings |
Verification run during this pass:
go test ./interp -run 'TestAllowedPaths(AbsoluteGlobInside|AbsoluteGlobOutsideStaysLiteral|GlobInside|GlobOutside)$'\n-go test ./tests -run 'TestShellScenarios/shell/allowed_paths/absolute_glob' -count=1\n\nP0/P1/P2 findings: 0. P3 findings: 0.
|
Iteration 4 self-review result: P0=0, P1=0, P2=0, P3=0. Summary: re-reviewed the unchanged PR head and reran focused absolute-glob tests. No blocking findings were identified. |
|
@codex review this PR Please use the following severity format for all findings:
Prefix each finding title with its priority label, e.g. "P0: ...", "P1: ...", etc. |
AlexandreYang
left a comment
There was a problem hiding this comment.
Review Summary
Iteration 5 self-review of PR #268. The PR head remains 82d1045ad10c10b4c2443307cd77efdbdaca3b13. I rechecked the seven-file diff, the sandbox/glob call path, and the coverage for absolute globs inside and outside AllowedPaths.
Overall assessment: safe to merge.
| # | Priority | File | Finding |
|---|---|---|---|
| — | — | — | No findings |
Verification run during this pass:
go test ./interp -run 'TestAllowedPaths(AbsoluteGlobInside|AbsoluteGlobOutsideStaysLiteral|GlobInside|GlobOutside)$'\n-go test ./tests -run 'TestShellScenarios/shell/allowed_paths/absolute_glob' -count=1\n\nP0/P1/P2 findings: 0. P3 findings: 0.
|
Iteration 5 self-review result: P0=0, P1=0, P2=0, P3=0. Summary: final self-review pass on the unchanged PR head, with focused absolute-glob tests passing. No blocking findings were identified. |
|
Codex Review: Didn't find any major issues. Swish! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Review-Fix Loop Summary
Iteration log
Final state
|
|
Heads up — one case this PR doesn't cover: when a glob metacharacter appears in an ancestor of the allowed root. Example: with Why: How to make it work: return synthetic directory entries for the next-level path components leading to each contained root, instead of an empty listing. With
Then The trade-off: this discloses the names of allowed roots through ancestor globbing — minor, since the operator configured them, but the current empty-listing choice has a real privacy benefit. Happy to keep the current scope; if so, a scenario test asserting literal fallthrough for `/var//test-s--o` would lock in the intentional limitation. |
julesmcrt
left a comment
There was a problem hiding this comment.
Creating a Feature Request for #268 (comment)
### What does this PR do? Bumps `github.com/DataDog/rshell` from `v0.0.18` to `v0.0.20` and updates the corresponding `go.sum` entries. Adds focused `runCommand` coverage that pins stdout and stderr output-limit sentinels as action-level errors instead of normal `RunCommandOutputs`, since the output schema has no truncation marker. ### Motivation Pick up the latest `rshell` release in the Agent dependency set. Mainly for this security fix: DataDog/rshell#268 ### Describe how you validated your changes - `dda inv test --targets=./pkg/privateactionrunner/bundles/remoteaction/rshell --extra-args="-run ^TestRunCommandOutputLimitsReturnActionErrors$ -count=1 -timeout=30s"` - `git diff --check` ### Additional Notes The output-limit test documents the intentional integration contract: partial stdout/stderr from rshell output caps is not returned as a normal command result without an explicit truncation field. Co-authored-by: alexandre.yang <[email protected]>
Backport of #51247 --- Bumps `github.com/DataDog/rshell` from `v0.0.18` to `v0.0.20` and updates the corresponding `go.sum` entries. Adds focused `runCommand` coverage that pins stdout and stderr output-limit sentinels as action-level errors instead of normal `RunCommandOutputs`, since the output schema has no truncation marker. Pick up the latest `rshell` release in the Agent dependency set. Mainly for this security fix: DataDog/rshell#268 - `dda inv test --targets=./pkg/privateactionrunner/bundles/remoteaction/rshell --extra-args="-run ^TestRunCommandOutputLimitsReturnActionErrors$ -count=1 -timeout=30s"` - `git diff --check` The output-limit test documents the intentional integration contract: partial stdout/stderr from rshell output caps is not returned as a normal command result without an explicit truncation field. (cherry picked from commit 9101702) ### What does this PR do? ### Motivation ### Describe how you validated your changes ### Additional Notes Co-authored-by: florent.clarret <[email protected]>
What changed
Why
The shell expander validates non-meta absolute path components with ReadDirForGlob before it reaches the directory containing the glob metacharacters. When AllowedPaths was set to a nested root, those ancestor reads were denied, so patterns such as /var/tmp/test-s-*-o stayed literal even though the target directory was allowed.
Validation