Conversation
|
@codex review |
matt-dz
left a comment
There was a problem hiding this comment.
Review Summary
Reviewed the full find builtin implementation across 4 new Go files, 24 YAML test scenarios, and supporting changes (registration, import allowlist, documentation).
Overall assessment: needs fixes
The architecture is sound — expression parser with bounded AST, explicit-stack DFS traversal, all filesystem access through sandbox APIs (callCtx.ReadDir, callCtx.LstatFile, callCtx.StatFile). The blocked-predicates list correctly prevents -exec, -delete, -regex, and friends. No direct os filesystem calls.
However, there is one P1 correctness bug and several P2 gaps that need attention before merge.
Positive Observations
- All filesystem access uses sandbox APIs — no direct
os.Open/os.Stat/os.ReadDircalls - Expression parser has proper AST bounds (64 depth, 256 nodes) preventing resource exhaustion
- DFS traversal uses explicit stack capped at 256 depth
- Context cancellation checked before every ReadDir and on each stack iteration
- Blocked predicates list is comprehensive and well-justified
path.Match(Go stdlib) is immune to ReDoS-newerfile access goes through sandbox — no information leak outside AllowedPaths
matt-dz
left a comment
There was a problem hiding this comment.
Review Summary (Round 2)
Re-reviewed after fixes for round 1 findings.
Overall assessment: safe to merge (with minor P3 nits)
The P1 from round 1 (-maxdepth/-mindepth extraction corrupting predicate arguments) has been properly fixed — depth options are now only parsed from leading positions before the expression starts. The dead followLink field has been removed, -type "" validation added, baseName documented, and 6 new test scenarios added for -path, -ipath, -mmin, -mtime, -size variants, and -type "" error.
No P0 or P1 findings remain. Two minor P3 items noted below.
Positive Observations
- P1 fix is clean — leading-only parse with
breakat first non-depth-option, passes remaining args as a slice without mutation - All round 1 P2 findings addressed: dead field removed,
-type ""validated, new test scenarios cover previously-untested predicates - Sandbox integrity remains solid — all filesystem access exclusively through
callCtx.ReadDir,callCtx.StatFile,callCtx.LstatFile - Expression parser bounds (64 depth, 256 nodes) prevent resource exhaustion
- Blocked predicate list is comprehensive
-Lsymlink loop bounded bymaxTraversalDepth=256(consistent withls -Rapproach)
This comment has been minimized.
This comment has been minimized.
|
Great PR overall — I did a full pass on the new What I reviewed
I also ran:
Findings1) P1 – Bash compatibility bug:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d0c155cb1c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
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".
Add a sandboxed find builtin for searching directory trees by name, type, size, and time predicates. Uses a recursive-descent expression parser with bounded AST (max 64 depth, 256 nodes) and explicit-stack DFS traversal (capped at 256 depth). Blocks -exec, -delete, -regex and other unsafe predicates for sandbox safety. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P1: Fix -maxdepth/-mindepth extraction to only parse leading positions, preventing corruption of predicate arguments (e.g. find . -name -maxdepth). P2: Remove dead followLink field from evalContext, add empty-string validation for -type predicate, add test scenarios for -path, -ipath, -mmin, -mtime, -size variants, and -type empty error. P3: Document baseName forward-slash-only convention. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The field was nested under expect: where it was silently ignored by the YAML decoder. Removed since the test passes against bash anyway. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- builtins.go: Expand ReadDir comment to explain that sorted entries cause builtins (ls -R, find) to produce deterministic but different output ordering than GNU coreutils/findutils - find.go: Add NOTE explaining the ordering divergence at the walker - empty_file.yaml: Add skip_assert_against_bash (ordering divergence) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
d0c155c to
b3d04ee
Compare
P1: Surface -newer reference file errors — report to stderr and set exit code 1 instead of silently returning false. Cache reference file modtime to avoid redundant stat calls per entry. P1: Detect symlink loops with -L — track visited directory paths to break cycles, preventing CPU/memory DoS on attacker-controlled trees. P2: Reject malformed -type arguments — validate comma-separated format properly, rejecting leading/trailing/consecutive commas and adjacent type chars without separators (e.g. ",", "f,", ",d", "fd"). Low: Add comment explaining why errorf uses errors.New(fmt.Sprintf()) instead of fmt.Errorf (fmt.Errorf is not in the import allowlist). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add fmt.Errorf to the import allowlist (pure function, no I/O) and replace all errorf() calls with fmt.Errorf() directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Absolutely — I completed a comprehensive code + security review of this PR’s Scope reviewed
I also validated behavior with:
FindingsP1 —
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 10e4148c02
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
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".
Cover all major code paths including symlinks (-L, loop detection, broken links), -newer, -true/-false, comma-separated -type, size units (c/w/k/M/b), exact numeric comparisons, logical operator aliases (-not/-and/-or), parser error paths, sandbox-blocked predicates, paths with spaces, pipe integration, and explicit -print. Brings total find test scenarios from 29 to 81. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix P1 bug where -newer with a nonexistent reference file would cache
a zero-time sentinel, causing subsequent entries (with fresh
evalContexts) to match against time.Time{} instead of returning false.
Replace per-evalContext newerErr flag with a shared newerErrors map
that persists across all entries in the traversal, consistent with
newerCache.
Also improve documentation:
- Clarify -maxdepth/-mindepth leading-only parse is intentional
- Document symlink loop detection path-text limitation and
maxTraversalDepth=256 safety bound
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move depth option parsing from a leading-only extraction loop into the recursive-descent expression parser itself. This allows commands like `find . -name '*.go' -maxdepth 1` to work correctly, while the parser's natural token ownership prevents the argument-stealing bug that the previous naive scan-and-extract approach suffered from. Add 9 test scenarios covering parser integration, anti-stealing, depth band selection, last-wins semantics, and edge cases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
julesmcrt
left a comment
There was a problem hiding this comment.
Review Summary
This PR implements a find builtin with good overall design: sandbox enforcement goes through os.Root (immune to .. traversal), blocked predicates (-exec, -delete, -regex, etc.) are enforced at parse time, expression AST size is bounded (64 depth / 256 nodes), and the explicit-stack DFS avoids deep Go recursion. The import allowlist is correctly updated. No P0/P1 issues found.
Verdict: needs minor fixes — four P2 findings (one correctness bug, three coverage gaps) and two P3 issues.
…ings Path-based cycle detection missed loops that re-enter the same directory under different textual paths (e.g. dir/a/link_to_dir → dir), allowing expansion until maxTraversalDepth=256. Now track visited directories by canonical file identity (device + inode) on Unix via FileInfo.Sys(), with path-based fallback on Windows. Zero additional I/O — reuses FileInfo already in hand. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The evalMtime refactor introduced time.Hour and time.Second usage which were not in the allowed symbols list. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7375410f46
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
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".
| if ancestorPaths[path] { | ||
| callCtx.Errf("find: File system loop detected; '%s' has already been visited.\n", path) |
There was a problem hiding this comment.
Detect symlink cycles when file identity lookup fails
The fallback cycle check uses the raw traversed path string, so loops like a/loop -> . become a/loop, a/loop/loop, a/loop/loop/loop, etc. and never hit ancestorPaths[path]; this means if FileIdentity is unavailable for a subtree (e.g., platform/ACL-specific identity lookup failure), loop detection is effectively disabled and traversal repeats until the hard depth cap, causing avoidable CPU and output amplification.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
[Claude Opus 4.6] Valid observation. The path-based fallback won't detect cycles because the traversed path keeps growing (a/loop, a/loop/loop, ...) and never matches an ancestor entry.
However, I believe this is acceptable as-is for the following reasons:
-
FileIdentity works on all supported platforms (Linux/macOS via dev+inode, Windows via volume serial+file index). The path-based fallback only activates when the syscall fails, which is rare (exotic filesystems, ACL edge cases).
-
maxTraversalDepth=256 provides a hard safety cap — even if the fallback fails, traversal stops at depth 256. For an AI agent sandbox, this is the ultimate bound.
-
A proper fix would require symlink resolution (
os.EvalSymlinksor equivalent), which isn't available within the builtin sandbox and would add syscall overhead per directory entry.
The existing code comments (lines 267-274) explicitly acknowledge the depth cap as the ultimate safety bound. If we want tighter cycle detection in the fallback, we could resolve canonical paths via StatFile + comparing dev/inode from the info, but that would duplicate what FileIdentity already does.
Leaving this for the reviewer to decide — not resolving the thread.
Narrow the OpenDir return type from *os.File to fs.ReadDirFile so builtin implementations only see read-only directory methods (ReadDir, Close, Read, Stat) instead of the full os.File method set (Seek, Write, Truncate, Sync, Fd, etc.). *os.File already satisfies fs.ReadDirFile, so no wrapper is needed. Addresses PR #36 feedback from thieman. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ching 1. Set failed=true when ctx.Err() is detected in both the path iteration loop and the walk loop, so cancelled/timed-out find returns exit code 1 instead of silently succeeding with partial output. 2. Remove the negative diff clamp in evalMtime's exact-match branch. GNU find computes negative day buckets for future-dated files, so they never match non-negative -mtime N. The clamp incorrectly forced them into bucket 0. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Hey @thieman — addressed your
Ready for another look when you have a moment! |
|
@chatgpt-codex-connector — addressed your review comments:
Could you take another look? |
1. mmin_plus_zero: use explicit mod_time in the past instead of relying on file creation timing. On fast Windows CI runners, the file could be created in the same time quantum as Now(), making -mmin +0 fail. 2. ls/sandbox/outside_allowed_paths: update stderr_windows to match current Go os.Root error format (no longer includes "statat" prefix). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The scenario expected " 3\n" (width-7 padding) but GNU wc outputs "3\n" for single-column stdin. Verified with: docker run --rm debian:bookworm-slim bash -c 'printf "one\ntwo\nthree\n" | wc -l' Removed skip_assert_against_bash since our output now matches bash. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a9fbd9aa44
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
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".
GNU find evaluates age predicates relative to a single invocation timestamp. Previously, walkPath called callCtx.Now() per root path, so multi-path invocations could produce inconsistent results for files near minute/day boundaries. Now captured once in run() and passed via walkOptions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds TestNowCalledOnce which runs find with two root paths and a -mmin predicate, asserting that callCtx.Now() is invoked exactly once (not per root path). This guards against regressions where multi-path find invocations evaluate age predicates against inconsistent timestamps. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@chatgpt-codex-connector — addressed your latest feedback:
Ready for another look! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4a4339116e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
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".
GNU find falls back to lstat when a -newer reference is a dangling symlink under -L mode, rather than treating it as fatal. Apply the same fallback in both eager validation (find.go) and evalNewer (eval.go) to match bash-compatible behavior. Added scenario test for -L with dangling symlink -newer reference. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@chatgpt-codex-connector — addressed your latest comment:
Ready for another look! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f4b613a5e9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
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".
Set mod_time on new.txt to ensure it is strictly newer than the symlink reference, avoiding nondeterminism on filesystems with coarse or same-tick timestamps. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nt-find-builtin # Conflicts: # allowedpaths/portable.go # allowedpaths/portable_unix.go # allowedpaths/portable_windows.go # allowedpaths/sandbox.go # interp/register_builtins.go # tests/allowed_symbols_test.go
The Windows error message for accessing /etc varies between Go versions (with/without "statat" prefix). Use stderr_contains_windows to match just the stable prefix instead of the exact string. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@chatgpt-codex-connector — merged main and resolved conflicts. All functionality intact, CI green on Linux/macOS/Docker. The only remaining failure is the Ready for another look! |
On fast Windows CI runners, the file could be created with the same timestamp as Now(), making -mmin 0 match (ceil(0) == 0). Use explicit mod_time in the past to ensure the file is always > 0 minutes old. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Codex Review: Didn't find any major issues. Breezy! ℹ️ 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 make a comprehensive code and security review |
…d-builtin # Conflicts: # allowedsymbols/symbols_builtins.go
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 32c6ee0d18
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
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".
| if followLinks { | ||
| if _, lerr := callCtx.LstatFile(ctx, ref); lerr == nil { | ||
| continue |
There was a problem hiding this comment.
Reject escaped -newer references when -L cannot follow target
In -L mode this fallback treats every StatFile failure as if it were a dangling symlink and silently switches to LstatFile. If a -newer reference is a symlink inside allowed_paths that points outside the sandbox, StatFile returns a permission/path-escape error but LstatFile succeeds, so find -L ... -newer ref proceeds and exits 0 instead of enforcing the sandbox denial (while direct outside refs fail). This weakens the path restriction model and changes matching behavior; the fallback should only apply to dangling-target errors.
Useful? React with 👍 / 👎.
| if err != nil { | ||
| // Dangling symlink: stat fails but lstat succeeds. | ||
| childInfo, err = callCtx.LstatFile(ctx, childPath) |
There was a problem hiding this comment.
Surface symlink escape errors during -L directory walk
The -L walker also falls back from StatFile to LstatFile on any error, so symlinks that resolve outside allowed_paths are silently treated as regular links instead of reporting a sandbox violation. In practice, an in-sandbox link like out -> /etc makes find -L . succeed and print ./out with exit code 0, even though following the link is disallowed. This masks policy violations and makes -L ineffective for enforcing follow-link access checks.
Useful? React with 👍 / 👎.
Summary
findbuiltin for searching directory trees with expression-based predicates-name,-iname,-path,-ipath,-type,-size,-empty,-newer,-mtime,-mmin,-maxdepth,-mindepth,-print,-print0,-prune,-true,-false, and logical operators (!,-a,-o,())-exec,-delete,-regex,-fprint, etc.) for sandbox safetyImplementation
interp/builtins/find/find.go— Command handler, arg parsing, DFS walkerinterp/builtins/find/expr.go— AST types + recursive-descent parserinterp/builtins/find/eval.go— Expression evaluatorinterp/builtins/find/match.go— Glob/type/size matching helperspath.Matchandstrings.ToLowerTest plan
go test ./interp/... ./tests/...— all passRSHELL_BASH_TEST=1 go test ./tests/ -run TestShellScenariosAgainstBash— all pass (ordering-sensitive tests markedskip_assert_against_bashdue to ReadDir returning sorted entries vs GNU find's filesystem order)🤖 Generated with Claude Code