Re-review test/[ command#59
Conversation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This reverts commit 3c53f7b.
|
I can only run on private repositories. |
AlexandreYang
left a comment
There was a problem hiding this comment.
Review Summary
Reviewed: Re-addition of the test and [ builtin commands — 557 lines of implementation, ~580 lines of Go tests (functional, GNU compat, pentest, Unix, Windows), and 24 YAML scenario test files.
Overall assessment: Safe to merge with minor suggestions below.
The implementation is well-structured and security-conscious:
- All file access goes through
callCtx.StatFile,callCtx.LstatFile, andcallCtx.AccessFile— no directos.Stat/os.Opencalls - Recursion depth is bounded at 128 levels (with pentest validation up to 1000)
- Integer overflow is safely clamped to
MinInt64/MaxInt64matching GNU test behavior - Context cancellation is checked in the parse loop
- All imported symbols are on the builtin allowlist
- POSIX disambiguation rules for ambiguous expressions (e.g.,
! = !,( = () are correctly implemented
Findings
Positive Observations
- Thorough pentest coverage — integer edge cases, deep nesting, null bytes in paths, very long strings, path traversal, and flag-like filenames are all tested
- Good POSIX disambiguation — the recursive-descent parser correctly handles all the tricky cases where
!,(,)can be either operators or string operands - Platform-aware tests — separate Unix (symlinks, FIFOs, executable bit) and Windows (reserved device names) test files
- Clean separation — sandbox file access is delegated through the
CallContextinterface, keeping the builtin implementation free of direct filesystem calls - Scenario tests are preferred over Go tests for behavior verification, with
skip_assert_against_bash: trueonly where intentionally divergent (help flags, error message format)
|
Iteration 1 self-review result: COMMENT — 3 findings (3×P3). No P0/P1/P2 issues. Overall assessment: safe to merge. |
- Add comment noting permission bit fallback limitation for -r/-w/-x when AccessFile is nil (no ownership check) - Add scenario test for integer overflow clamping behavior (skip_assert_against_bash: bash rejects overflow, rshell clamps) - Add scenario test for extra-argument error (exit code 2) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
I can only run on private repositories. |
AlexandreYang
left a comment
There was a problem hiding this comment.
Iteration 2 Re-Review
Reviewed: Full diff re-examined — 560 lines of implementation (testcmd.go), ~1,079 lines of Go tests (4 test files), 24 YAML scenario files, and the builtin registration hook.
Result: No remaining issues found.
The 3 P3 findings from iteration 1 have all been addressed:
| Previous Finding | Status |
|---|---|
| Permission bit fallback ignores ownership | Addressed — explanatory comment documents this is a dead-code fallback; production always uses AccessFile |
| Missing scenario test for integer overflow | Fixed — tests/scenarios/cmd/test/integers/overflow.yaml added |
| Missing scenario for extra-argument error | Fixed — tests/scenarios/cmd/test/errors/extra_argument.yaml added |
Verification
go vet ./interp/builtins/testcmd/— cleango test ./interp/builtins/testcmd/...— all passgo test ./tests/ -run TestShellScenarios— all pass
Implementation Quality Notes
- No panics possible from
advance()— all call sites are properly bounds-guarded before invoking the unchecked array access - Recursive-descent parser is clean and correct: precedence is
-o<-a<!< primary, matching POSIX - POSIX disambiguation for ambiguous token sequences (e.g.,
! = !,( = () uses correct lookahead - Security posture is strong: no direct filesystem calls, recursion depth bounded at 128, context cancellation checked in loops, integer overflow clamped safely
skip_assert_against_bash: trueis used only where intentionally divergent (help output, error message format, overflow clamping)
No new issues found. Safe to merge.
|
Iteration 2 self-review result: APPROVE — 0 findings. All previous P3 issues resolved. PR is clean. |
Review-Fix Loop Summary
Iteration log
Final state
Remaining issues |
|
I can only run on private repositories. |
Summary
Testing
Committed on branch |
|
I can only run on private repositories. |
AlexandreYang
left a comment
There was a problem hiding this comment.
Code Review Summary
Reviewed: Implementation of the test and [ builtin commands — 560 lines of implementation code plus ~800 lines of Go tests, ~200 lines of pentest tests, and 26 YAML scenario tests.
Overall assessment: safe to merge (with minor documentation gap to address)
Security
- No direct filesystem access — all file operations go through
CallContext.StatFile,LstatFile, andAccessFile, which are sandbox-mediated. Noos.Open,os.Stat,os.ReadFile, or similar calls. - Safe imports — only
context,io/fs,math,strconv,strings, and the internalbuiltinspackage. - Recursion depth bounded —
maxParenDepth = 128prevents stack overflow from deeply nested expressions. - Integer overflow handled — overflow clamps to
math.MinInt64/math.MaxInt64rather than panicking. - Context cancellation checked in parsing loops.
- Path traversal tested — pentest covers
../traversal, null bytes, double slashes, and Windows reserved names.
Bash Compatibility
- Core behavior matches bash for strings, integers, file tests, logical operators, and bracket syntax.
skip_assert_against_bashis used appropriately: only for help text, error message formatting differences,allowed_pathsusage, and the intentional overflow clamping divergence.- POSIX disambiguation rules (1-arg, 2-arg, 3-arg, 4-arg forms) are correctly implemented with lookahead.
Test Coverage
Excellent coverage across all dimensions: scenario tests, Go unit tests, GNU compatibility tests, pentest tests, and platform-specific tests (unix + windows).
Finding 1:
Missing documentation for test/[ builtins
Per AGENTS.md: "README.md and SHELL_FEATURES.md must be kept up to date with the implementation."
The test and [ builtins are not listed in the Builtins section of SHELL_FEATURES.md or README.md. Please add an entry like:
- ✅ `test EXPRESSION` / `[ EXPRESSION ]` — evaluate conditional expression (file tests, string/integer comparison, logical operators)
(This file is not in the diff so an inline comment could not be placed.)
Positive Observations
- Clean separation of parsing (recursive descent) from evaluation.
- File access exclusively through sandbox-mediated
CallContextmethods — the most critical security invariant. - Thorough pentest coverage: integer edge cases, deep nesting, null bytes, path traversal, very long strings.
- Platform-specific tests for symlinks/FIFOs (unix) and reserved names (windows).
- POSIX disambiguation rules handled correctly with lookahead, matching bash behavior for tricky cases like
test '!' = '!'andtest '(' = '('.
|
Iteration 1 self-review result: COMMENT
Overall: Implementation is well-structured and secure. All file access goes through sandbox-mediated CallContext methods. Good test coverage with 26 scenario tests + Go unit tests + GNU compat tests. |
- Add test/[ builtin to SHELL_FEATURES.md and README.md (P2 finding) - Add == (double-equals) operator scenario test to equality.yaml (P3) - Rename unary_expected.yaml to unary_option.yaml to match content (P3) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
I can only run on private repositories. |
AlexandreYang
left a comment
There was a problem hiding this comment.
Review Summary (Iteration 7) — APPROVE
Reviewed: test/[ builtin implementation — POSIX 3/4-arg disambiguation, subexpression boundary tracking, -a FILE support, == operator, integer overflow rejection, /dev/null sandbox stat bypass, and all associated scenario + Go tests.
Overall assessment: safe to merge ✅
| # | Priority | File | Finding |
|---|---|---|---|
| — | — | — | No findings |
No code changes since iteration 4. This is a re-verification confirming all prior approvals (iterations 3–6).
Security
- The
/dev/nullstat bypass inallowed_paths.gois correctly scoped viaisDevNull()— only matches the platform null device. - No new unsafe imports (
os/exec,net/http,unsafe). No direct filesystem access outside the sandbox. findMatchingParenhas no unbounded loops (bounded bylen(args)).maxParenDepth(128) prevents stack exhaustion from deeply nested expressions.
Bash Compatibility
- Integer overflow now rejects with exit 2 + error message, matching bash behavior (previously clamped to MinInt64/MaxInt64).
- POSIX 3-arg and 4-arg disambiguation rules correctly handle ambiguous tokens (
!,(,-a,-o) as both operators and literal strings. - Error message changed from "extra argument" to "too many arguments" to match bash.
Correctness
subexprStart/subexprEndboundary tracking is saved and restored consistently across all recursive calls (parseNot, parsePrimary with parens, 4-arg rule).- All edge cases (lone
!, lone(,! ! = !,( ! ) ),-f -a -d) are handled correctly per POSIX rules.
Test Coverage
- Adequate. All new code paths are covered by scenario tests (12 new/modified YAML scenarios) and Go tests (pentest + functional). Bash comparison is enabled for all scenarios except those using
allowed_pathsor differing error message prefixes.
Positive Observations
- Excellent documentation in both code comments and doc strings explaining the POSIX disambiguation rules.
- The subexpression boundary approach is a clean design that avoids the common pitfall of using global remaining-count for disambiguation.
|
Iteration 7 self-review result: APPROVE
|
AlexandreYang
left a comment
There was a problem hiding this comment.
Review Summary (Iteration 8 — Self-Review)
Verdict: APPROVE (submitted as COMMENT because GitHub prevents approving your own PR)
Reviewed the test/[ builtin implementation including:
- Security: The
isDevNullbypass instat/lstatis tightly scoped — only/dev/null(Unix) orNUL(Windows, case-insensitive) triggersos.Statdirectly; all other paths go through theos.Rootsandbox. No new imports of concern. No sandbox escape vectors identified. - Bash compatibility: POSIX disambiguation rules (1-arg, 3-arg, 4-arg) are correctly implemented with subexpression boundary tracking. Integer overflow now correctly rejects with exit 2 (matching bash). Error message changed to
too many arguments(matches bash).==synonym and-afile test added correctly. - Correctness: The recursive-descent parser properly saves/restores
subexprStart/subexprEndacross nested contexts (negation, parentheses).findMatchingParenhandles nested groups.maxParenDepthprevents stack overflow. - Test coverage: Comprehensive scenario tests cover all new code paths — 3-arg disambiguation, 4-arg parenthesized forms, negated subexpressions,
-a/-oprecedence,==equality, integer overflow rejection. Pentest assertions strengthened. Windows test updated for NUL device.
No code changes since iteration 4. Iterations 3–7 all approved with 0 findings. No new findings.
| # | Priority | File | Finding |
|---|---|---|---|
| — | — | — | No findings |
Positive Observations
- Subexpression boundary tracking (
subexprStart/subexprEnd) is a clean design for POSIX disambiguation - All scenario tests validated against bash by default (only
skip_assert_against_bashwhere justified) - Pentest tests now assert exit codes instead of discarding them
- The
isDevNullspecial-case in the sandbox is minimal and well-documented
|
I can only run on private repositories. |
|
Iteration 8 self-review result: APPROVE (6th consecutive, 0 findings) |
|
/merge |
AlexandreYang
left a comment
There was a problem hiding this comment.
Review Summary (Iteration 9) — APPROVE (0 findings)
Reviewed: interp/builtins/testcmd/testcmd.go, interp/allowed_paths.go, all test files, documentation updates.
Overall assessment: Safe to merge — 0 findings.
No code changes since iteration 4. Iterations 3–8 all approved with 0 findings. This iteration confirms the same.
Security
- The
/dev/nullsandbox bypass inallowed_paths.go(stat/lstat) is safe:isDevNull()strictly matches/dev/nullor case-insensitiveNULon Windows only. No path traversal vector. - The
os.Stat(os.DevNull)call is metadata-only on a well-known device — no information leak or sandbox escape. - All file tests route through sandbox callbacks (
StatFile,LstatFile,AccessFile). No directos.Open/os.Statin the builtin. - The
mathimport was removed (overflow clamping deleted); no new unsafe imports added.
Bash Compatibility
- Integer overflow: now rejects with exit 2 + "integer expression expected" — matches bash.
- Error message: "extra argument" → "too many arguments" — matches bash.
==added as synonym for=— matches bash.-a FILEadded as deprecated POSIX file-existence test — matches bash.- POSIX 3-arg and 4-arg disambiguation rules correctly implemented and thoroughly tested.
Correctness
subexprStart/subexprEndtracking correctly scopes disambiguation to current subexpression boundaries (parenthesized groups and!negation), preventing false triggers in-a/-ochains.findMatchingParenhandles nested parens correctly; fallback tolen(args)on mismatch produces a clean error.- State save/restore pattern is consistent across all call sites (
parseNot,parsePrimary3-arg, 4-arg, and general grouping).
Test Coverage
| Code path | Scenario test | Go test | Status |
|---|---|---|---|
-a FILE existence |
files/existence_a.yaml |
— | Covered |
== string comparison |
strings/equality.yaml |
— | Covered |
| Integer overflow rejection | integers/overflow.yaml |
testcmd_test.go, testcmd_pentest_test.go |
Covered |
| POSIX 3-arg binary disambiguation | logical/binary_disambiguation.yaml |
— | Covered |
POSIX 3-arg ( X ) |
logical/paren_three_arg.yaml |
— | Covered |
POSIX 4-arg ( X Y ) |
logical/paren_four_arg.yaml |
— | Covered |
| Negated subexpression disambiguation | logical/negated_disambiguation.yaml |
— | Covered |
| Paren subexpression with unary ops | logical/paren_subexpr.yaml, logical/paren_disambiguation.yaml |
— | Covered |
-a/-o precedence |
logical/precedence.yaml |
— | Covered |
Dangling ! after -a/-o |
logical/dangling_not.yaml |
— | Covered |
| Extra arguments error | errors/extra_argument.yaml |
testcmd_test.go |
Covered |
/dev/null stat via sandbox |
— | testcmd_pentest_test.go |
Covered |
| Windows NUL device | — | testcmd_windows_test.go |
Covered |
Overall coverage: Adequate.
Positive Observations
- The subexpression boundary approach is a clean solution to POSIX disambiguation that avoids the fragile "count remaining args" pattern.
- Thorough scenario test suite covering many POSIX edge cases, all validated against bash.
- Documentation (
README.md,SHELL_FEATURES.md) kept in sync with implementation. - Pentest tests now assert exit codes instead of discarding them.
|
Iteration 9 self-review result: APPROVE (7th consecutive, 0 findings) |
|
I can only run on private repositories. |
|
/merge |
|
View all feedbacks in Devflow UI.
The expected merge time in
|
AlexandreYang
left a comment
There was a problem hiding this comment.
Review Summary (Iteration 10 — Final)
Result: APPROVE (posted as COMMENT due to self-review restriction) — 0 findings.
Reviewed the full test/[ builtin implementation (interp/builtins/testcmd/testcmd.go), sandbox changes (interp/allowed_paths.go), all Go tests, and all 12 scenario YAML files.
Security
- File tests correctly route through
callCtx.StatFile/callCtx.LstatFile/callCtx.AccessFile— no directos.Open/os.Statcalls on user-supplied paths. - The
/dev/nullspecial-case inallowed_paths.gousesisDevNull()gating andos.Stat(os.DevNull)(platform constant, not user input) — safe. maxParenDepth = 128prevents stack exhaustion from deeply nested expressions.- No new imports of
unsafe,os/exec, ornet/http.
Bash Compatibility
- Integer overflow correctly rejected with exit 2 (matches bash).
- Error message "too many arguments" matches bash.
- POSIX 3-arg and 4-arg disambiguation rules correctly implemented with subexpression boundary tracking.
-a FILE(deprecated POSIX synonym for-e) and==synonym for=both supported.
Test Coverage
Adequate — comprehensive scenario tests cover all new code paths.
Verdict
No code changes since iteration 4. Iterations 3–10 all APPROVE with 0 findings. Safe to merge.
| # | Priority | File | Finding |
|---|---|---|---|
| — | — | — | No findings |
|
Iteration 10 self-review result: APPROVE (8th consecutive, 0 findings — FINAL iteration) |
Review-Fix Loop Summary
Iteration log
Final state
Fixes applied across all iterations
Remaining issues
🤖 Generated with Claude Code |
Summary
Testing
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1be9e92b89
ℹ️ 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".
| saved := p.subexprStart | ||
| p.subexprStart = p.pos // new subexpression after ! | ||
| result := !p.parseNot() |
There was a problem hiding this comment.
Avoid 3-arg disambiguation after stacked negations
Resetting subexprStart on every ! causes nested negations to incorrectly trigger the 3-argument -a/-o rule on trailing tokens, so invalid expressions get accepted instead of returning syntax errors. For example, test ! ! -f -a = now returns a boolean result in rshell, while bash returns exit code 2 (too many arguments); this regression is introduced by treating each nested ! as a fresh 3-token subexpression and then parsing -f -a = as a valid binary form.
Useful? React with 👍 / 👎.
|
View all feedbacks in Devflow UI.
This PR is already merged |
Summary
testand[builtin command implementation and all associated testsTest plan
test/[scenario tests pass🤖 Generated with Claude Code