Skip to content

Move time.Now() into restrictedtime package for sandboxing#113

Closed
AlexandreYang wants to merge 7 commits intomainfrom
alex/time_now
Closed

Move time.Now() into restrictedtime package for sandboxing#113
AlexandreYang wants to merge 7 commits intomainfrom
alex/time_now

Conversation

@AlexandreYang
Copy link
Copy Markdown
Member

@AlexandreYang AlexandreYang commented Mar 16, 2026

Summary

Extracts time.Now() out of builtins into a new restrictedtime package to strengthen the sandbox. Builtins no longer have direct access to wall-clock time — they receive boolean-returning closures (MatchMtime, MatchMmin, IsRecentEnough) via CallContext.

Changes

  • New restrictedtime package — contains MatchMtime, MatchMmin, IsRecentEnough functions and a NewCallbacks() factory that captures time.Now() once per command invocation
  • CallContext API change — replaced Now func() time.Time with three callback fields: MatchMtime, MatchMmin, IsRecentEnough
  • find builtin — delegates -mtime/-mmin evaluation to CallContext callbacks instead of computing time comparisons directly
  • ls builtinformatTime uses IsRecentEnough callback instead of direct time comparison
  • Allowlist updatestime.Now moved from interp allowlist to restrictedtime-specific allowlist; time/math symbols removed from builtins allowlist (no longer needed)
  • Tests — unit tests migrated from find/eval_test.go to restrictedtime/timecomp_test.go; added TestNewCallbacks for closure wiring; strengthened TestMatchMminCalledConsistently

Security

time.Now() is now confined to a single call site in restrictedtime.NewCallbacks(). Builtins cannot obtain the raw current time — they can only ask boolean questions about file modification times relative to the invocation timestamp.

AlexandreYang and others added 5 commits March 16, 2026 21:33
…xing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…mbols

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rp allowlist

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@AlexandreYang
Copy link
Copy Markdown
Member Author

Changes Summary

Problem

CallContext.Now func() time.Time gave every builtin unrestricted access to wall-clock time, which is a security/sandboxing concern — builtins could fingerprint, measure durations, or leak timing information.

Solution

Replaced Now() with a boolean-only time-comparison API — the most restrictive option that never exposes raw time.Time or time.Duration values to builtins.

What changed

New restrictedtime/ package with its own allowedsymbols enforcement:

  • MatchMtime(now, modTime, n, cmp) — GNU find day-bucketing logic (floor rounding, second-truncated now, overflow guards)
  • MatchMmin(now, modTime, n, cmp) — GNU find minute-bucketing logic (ceiling rounding, overflow guards)
  • IsRecentEnough(now, modTime, months) — ls "recent vs old" timestamp format decision
  • NewCallbacks() — factory that captures time.Now() once and returns three closures, so callers never import time directly

builtins/builtins.goNow func() time.Time replaced with:

  • MatchMtime func(modTime time.Time, n int64, cmp int) bool
  • MatchMmin func(modTime time.Time, n int64, cmp int) bool
  • IsRecentEnough func(modTime time.Time, months int) bool

interp/runner_exec.go — no longer imports time at all. Uses restrictedtime.NewCallbacks() to wire the three closures.

builtins/find/evalMtime/evalMmin now delegate to callCtx.MatchMtime/callCtx.MatchMmin. Removed now field from evalContext and walkOptions.

builtins/ls/ — removed now time.Time parameter from listDir, printEntry, formatTime. Uses callCtx.IsRecentEnough(t, 6) instead.

allowedsymbols/ — removed time.Now and time.Time from interp allowlist. Added dedicated symbols_restrictedtime.go with test + verification coverage. Cleaned up unused math.Ceil, math.Floor, time.Duration, time.Hour, time.Minute, time.Second from builtins common/per-command lists.

@AlexandreYang
Copy link
Copy Markdown
Member Author

@codex review this PR

Important: Read the SPECS section of the PR description. If SPECS are present: make sure the implementation matches ALL the specs.
The specs override other instructions (code, inline comments in code, etc). ALL specs MUST be implemented.

Copy link
Copy Markdown
Member Author

@AlexandreYang AlexandreYang left a comment

Choose a reason for hiding this comment

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

Review Summary

This PR refactors time-related operations out of builtins (find, ls) into a new restrictedtime package. The key design change is that builtins no longer have access to time.Now() directly — instead, they receive boolean-returning closures (MatchMtime, MatchMmin, IsRecentEnough) via CallContext, with the invocation timestamp captured once inside NewCallbacks().

Overall assessment: Safe to merge with minor observations.

What was reviewed

  • restrictedtime/timecomp.go — new package with time-comparison logic
  • restrictedtime/timecomp_test.go — relocated and adapted unit tests
  • builtins/builtins.goCallContext API change (Now → three callback fields)
  • builtins/find/eval.go, find.go — delegation to CallContext callbacks
  • builtins/find/now_test.go — adapted test for new API
  • builtins/ls/ls.goformatTime now uses IsRecentEnough callback
  • interp/runner_exec.go — wiring NewCallbacks() into CallContext
  • allowedsymbols/ — symbol list updates and new restrictedtime allowlist

Findings

# Priority File Finding
1 P3 Badge restrictedtime/timecomp.go:101 NewCallbacks() captures time.Now() per command — no unit test for the closure wiring
2 P3 Badge restrictedtime/timecomp.go:65 Missing overflow guard for CmpExact in MatchMmin when diff is extreme
3 P3 Badge builtins/find/now_test.go:73 Weakened assertion: test no longer verifies timestamp consistency across root paths

Finding Details

1. P3 Badge No unit test for NewCallbacks() closure wiring

Severity: P3 — Code Quality / Test Coverage
Location: restrictedtime/timecomp.go:96-111

NewCallbacks() is the public API that captures time.Now() and returns closures. The individual functions (MatchMtime, MatchMmin, IsRecentEnough) are well tested with explicit now parameters, but there's no test that verifies NewCallbacks() itself returns working closures that delegate correctly. A simple test calling NewCallbacks() and invoking each returned function would provide coverage of the wiring.

2. P3 Badge MatchMmin CmpExact has no overflow guard for extreme diff

Severity: P3 — Correctness (hardening)
Location: restrictedtime/timecomp.go:78-80

For CmpExact, diff.Minutes() returns a float64, and math.Ceil is applied before converting to int64. If diff is extremely large (e.g., now and modTime are centuries apart), diff.Minutes() could produce a float64 that exceeds int64 range, causing undefined behavior in the int64() cast. The same pattern existed in the old code, so this is not a regression — just a hardening opportunity. In practice, file mtimes won't produce such extreme values.

The same applies to MatchMtime's CmpExact path with diff.Hours() / 24.

3. P3 Badge Weakened test assertion in now_test.go

Severity: P3 — Test Coverage
Location: builtins/find/now_test.go:73

The old test (TestNowCalledOnce) verified that Now() was called exactly once per find invocation — guaranteeing timestamp consistency across multiple root paths. The new test (TestMatchMminCalledConsistently) only checks that MatchMmin is called at least once. The consistency guarantee still holds (because NewCallbacks() captures now once in the runner), but the test no longer verifies this invariant at the find level. This is acceptable since the architecture now guarantees it by construction, but worth noting.


Positive Observations

  1. Excellent security hardening — Moving time.Now() out of builtins and into a controlled package with its own symbol allowlist is a strong defense-in-depth measure. Builtins can no longer access wall-clock time directly.

  2. Clean separation of concerns — The restrictedtime package has a minimal, well-scoped allowlist. The time.Now call is isolated to a single NewCallbacks() function.

  3. Behavioral preservation — The time-comparison logic was moved verbatim from find/eval.go to restrictedtime/timecomp.go. The tests were faithfully migrated and all pass.

  4. Overflow guards preservedMaxMtimeN and MaxMminN constants and their guards were correctly moved.

  5. Symbol allowlist enforcement — The new allowedsymbols/symbols_restrictedtime.go and its verification tests ensure the restrictedtime package can't silently add dangerous imports.

  6. cmpOp values alignment — The CmpLess/CmpExact/CmpMore constants in restrictedtime (-1/0/1) exactly match find's cmpOp values, and the int(cmp) cast in evalMtime/evalMmin is correct.

Coverage Summary

Code path Scenario test Go test Status
MatchMmin ceiling bucketing find mmin scenarios restrictedtime/timecomp_test.go:TestMatchMminCeiling Covered
MatchMmin overflow find mtime_int64_overflow scenario restrictedtime/timecomp_test.go:TestMatchMminOverflow Covered
MatchMtime floor bucketing find mtime scenarios restrictedtime/timecomp_test.go:TestMatchMtimeFloor Covered
IsRecentEnough ls long_format scenarios restrictedtime/timecomp_test.go:TestIsRecentEnough Covered
NewCallbacks() wiring Not covered (P3)
Symbol allowlist enforcement allowedsymbols/symbols_restrictedtime_verification_test.go Covered

Overall coverage: Adequate — one minor gap noted above.

Comment thread restrictedtime/timecomp.go
Comment thread builtins/find/now_test.go Outdated
@AlexandreYang
Copy link
Copy Markdown
Member Author

Iteration 1 self-review result: COMMENT

  • Findings: 3 total (0×P0, 0×P1, 0×P2, 3×P3)
  • Summary: The refactoring is clean and correct. Three low-priority (P3) observations:
    1. No unit test for NewCallbacks() closure wiring itself
    2. Missing overflow guard for CmpExact in extreme diff scenarios (pre-existing)
    3. Weakened assertion in now_test.go
  • No blocking issues found.

- Add TestNewCallbacks to verify NewCallbacks() closure wiring
- Strengthen TestMatchMminCalledConsistently to assert MatchMmin is
  called at least once per file across both root paths, with a comment
  explaining timestamp consistency is guaranteed by construction

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@AlexandreYang
Copy link
Copy Markdown
Member Author

@codex review this PR

Important: Read the SPECS section of the PR description. If SPECS are present: make sure the implementation matches ALL the specs.
The specs override other instructions (code, inline comments in code, etc). ALL specs MUST be implemented.

Copy link
Copy Markdown
Member Author

@AlexandreYang AlexandreYang left a comment

Choose a reason for hiding this comment

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

Review Summary (Iteration 2)

Re-reviewed the full diff after the previous review identified 3 P3 findings. All have been addressed or were acceptable as-is.

Overall assessment: Safe to merge.

Previous Findings Status

# Previous Finding Status
1 P3: No unit test for NewCallbacks() closure wiring FixedTestNewCallbacks added in restrictedtime/timecomp_test.go
2 P3: MatchMmin CmpExact no overflow guard for extreme diff Acceptable — pre-existing behavior, not a regression, impractical to trigger with real file mtimes
3 P3: Weakened test assertion in now_test.go Acceptable — timestamp consistency is now guaranteed by construction (closures capture now once in NewCallbacks())

New Findings

No new issues found on re-review.

Verification

  • All tests pass: restrictedtime/, builtins/find/, builtins/ls/, allowedsymbols/
  • time.Now() is not called directly in any builtin production code
  • cmpOp values (-1/0/1) align correctly between find and restrictedtime
  • IsRecentEnough logic is equivalent to the old formatTime condition
  • Symbol allowlists are correctly updated (removed from builtins/interp, added to restrictedtime)
  • No remaining references to the old CallContext.Now field

Positive Observations

  1. Clean security hardening — builtins can no longer access wall-clock time directly; they only receive boolean callbacks.
  2. Faithful logic migration — comparison logic moved verbatim with all overflow guards preserved.
  3. Strong test coverage — the new TestNewCallbacks test addresses the wiring gap from iteration 1.
  4. Well-scoped allowlistrestrictedtime has its own minimal symbol allowlist with verification tests.

Coverage Summary

Code path Test Status
MatchMmin ceiling bucketing TestMatchMminCeiling Covered
MatchMmin overflow TestMatchMminOverflow Covered
MatchMtime floor bucketing TestMatchMtimeFloor Covered
IsRecentEnough TestIsRecentEnough Covered
NewCallbacks() wiring TestNewCallbacks Covered (new)
Symbol allowlist enforcement TestTimecompAllowedSymbols + verification tests Covered

@AlexandreYang AlexandreYang changed the title time now Wrap time now Mar 16, 2026
@AlexandreYang
Copy link
Copy Markdown
Member Author

Iteration 2 self-review result: APPROVE (no new findings)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

ℹ️ 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".

@AlexandreYang
Copy link
Copy Markdown
Member Author

@codex review this PR

Important: Read the SPECS section of the PR description. If SPECS are present: make sure the implementation matches ALL the specs.
The specs override other instructions (code, inline comments in code, etc). ALL specs MUST be implemented.

@AlexandreYang
Copy link
Copy Markdown
Member Author

Iteration 3 self-review result: APPROVE (no findings)

  • No issues found after thorough re-review of all 14 changed files
  • All previous P3 findings addressed
  • Architecture, correctness, allowlist hygiene, test coverage, and formatting all verified clean
  • All tests pass
  • PR is ready to merge

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

ℹ️ 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".

@AlexandreYang
Copy link
Copy Markdown
Member Author

@codex review this PR

Important: Read the SPECS section of the PR description. If SPECS are present: make sure the implementation matches ALL the specs.
The specs override other instructions (code, inline comments in code, etc). ALL specs MUST be implemented.

@AlexandreYang
Copy link
Copy Markdown
Member Author

Iteration 4 self-review result: APPROVE (no blocking findings)

  • No P0/P1/P2 issues found
  • Two cosmetic P3 observations about test naming ("Timecomp" vs "restrictedtime") — non-blocking
  • All tests pass, gofmt clean
  • PR remains ready to merge

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

ℹ️ 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".

@AlexandreYang AlexandreYang changed the title Wrap time now Move time.Now() into restrictedtime package for sandboxing Mar 16, 2026
@AlexandreYang
Copy link
Copy Markdown
Member Author

@codex review this PR

Important: Read the SPECS section of the PR description. If SPECS are present: make sure the implementation matches ALL the specs.
The specs override other instructions (code, inline comments in code, etc). ALL specs MUST be implemented.

Copy link
Copy Markdown
Member Author

@AlexandreYang AlexandreYang left a comment

Choose a reason for hiding this comment

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

Review Summary (Iteration 5 — stability check)

Reviewed the extraction of time.Now() from builtins into the new restrictedtime package. This is a pure security-hardening refactor that confines wall-clock access to a single call site.

Overall assessment: safe to merge — no findings. (Note: cannot submit APPROVE on own PR; this would be an APPROVE.)

What was reviewed

  • restrictedtime/timecomp.go — new package with MatchMtime, MatchMmin, IsRecentEnough functions and NewCallbacks() factory
  • builtins/builtins.goCallContext API change from Now func() time.Time to three boolean-returning callbacks
  • builtins/find/eval.go — delegation of -mtime/-mmin to CallContext callbacks
  • builtins/find/find.go — removal of now plumbing through walkOptions and walkPath
  • builtins/ls/ls.goformatTime now uses IsRecentEnough callback
  • interp/runner_exec.goNewCallbacks() wiring in r.call()
  • allowedsymbols/ — symbol list migration and new restrictedtime allowlist with verification tests
  • Tests — migrated and new unit tests in restrictedtime/timecomp_test.go, updated find/now_test.go

Security

  • time.Now() is now confined to restrictedtime.NewCallbacks() — builtins cannot obtain raw wall-clock time.
  • NewCallbacks() is called once per builtin invocation in r.call(), ensuring consistent timestamps across multi-root find invocations.
  • No new imports of unsafe/restricted packages. The restrictedtime package only uses math and time.
  • Symbol allowlist is properly enforced via TestTimecompAllowedSymbols and verification tests.
  • Builtins do not import restrictedtime directly (verified by grep).

Correctness

  • The cmpOp values (-1, 0, 1) in find/expr.go exactly match CmpLess, CmpExact, CmpMore in restrictedtime — the int(cmp) conversion is safe.
  • The time-comparison logic is a direct code move with no behavioral changes.
  • Overflow guards (MaxMtimeN, MaxMminN) are preserved.
  • IsRecentEnough correctly replicates the previous formatTime logic (6-month cutoff + future detection).

Test Coverage

Code path Test Status
MatchMmin ceiling bucketing restrictedtime/timecomp_test.go:TestMatchMminCeiling Covered
MatchMmin overflow restrictedtime/timecomp_test.go:TestMatchMminOverflow Covered
MatchMtime floor bucketing restrictedtime/timecomp_test.go:TestMatchMtimeFloor Covered
IsRecentEnough restrictedtime/timecomp_test.go:TestIsRecentEnough Covered
NewCallbacks wiring restrictedtime/timecomp_test.go:TestNewCallbacks Covered
Consistent invocation across roots find/now_test.go:TestMatchMminCalledConsistently Covered
Symbol allowlist enforcement allowedsymbols/symbols_restrictedtime_test.go Covered
Symbol allowlist verification allowedsymbols/symbols_restrictedtime_verification_test.go Covered

All tests pass locally (go test on all 4 affected packages).

Positive Observations

  • Clean separation of concerns: builtins receive only boolean-returning closures, eliminating the ability to leak raw time values.
  • Comprehensive allowlist verification tests that detect both unlisted symbols and exempt internal imports.
  • The refactor preserves all existing GNU find/ls behavioral semantics.
  • Overflow guards were correctly migrated, not lost.

Iteration 5 stability check: consistent with previous 4 approvals. No new concerns.

@AlexandreYang
Copy link
Copy Markdown
Member Author

Iteration 5 self-review result: APPROVE (no findings)

  • Consistent with previous 4 approvals
  • Security, correctness, test coverage, and allowlist enforcement all verified clean
  • All tests pass

@AlexandreYang
Copy link
Copy Markdown
Member Author

@codex review this PR

Important: Read the SPECS section of the PR description. If SPECS are present: make sure the implementation matches ALL the specs.
The specs override other instructions (code, inline comments in code, etc). ALL specs MUST be implemented.

Copy link
Copy Markdown
Member Author

@AlexandreYang AlexandreYang left a comment

Choose a reason for hiding this comment

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

Review Summary (Iteration 6 — Stability Check)

Reviewed: 14 changed files extracting time.Now() from builtins into a dedicated restrictedtime package.

Overall assessment: APPROVE (submitted as COMMENT due to GitHub self-review restriction)

What was reviewed

  • Security: The core change confines time.Now() to a single call site in restrictedtime.NewCallbacks(). Builtins (find, ls) can no longer obtain raw wall-clock time — they only receive boolean-returning closures via CallContext. The allowlist changes are consistent: time.Now removed from interp, math.Ceil/math.Floor/time.Duration/time.Hour/time.Minute/time.Second removed from builtins (moved to restrictedtime). No new filesystem access, no unsafe imports, no sandbox bypasses.

  • Correctness: MatchMtime preserves GNU find’s floor-bucketed day semantics for exact match and second-precision truncation for +N/-N. MatchMmin preserves ceiling-bucketed minute semantics for exact match and raw-second comparison for +N/-N. IsRecentEnough correctly handles the 6-month window boundary and future dates. Overflow guards (MaxMtimeN, MaxMminN) prevent integer overflow on large N values.

  • Bash compatibility: No behavioral change — the time comparison logic is unchanged, only relocated. The cmpOp values (-1, 0, 1) map correctly to CmpLess/CmpExact/CmpMore constants.

  • Test coverage: Comprehensive unit tests in restrictedtime/timecomp_test.go cover ceiling rounding, floor rounding, overflow, boundary conditions, and the NewCallbacks factory. find/now_test.go verifies the delegation integration. All tests pass locally.

  • Allowlist integrity: symbols_restrictedtime.go correctly lists only the symbols needed by the new package. Verification tests enforce the allowlist cannot be circumvented. time.Now properly removed from interp allowlist since runner_exec.go now calls it via restrictedtime.NewCallbacks().

  • Platform compatibility: No platform-specific code introduced; the change is pure Go with no OS dependencies.

Positive Observations

  • Clean separation of concerns: builtins cannot reconstruct wall-clock time from the boolean callbacks
  • NewCallbacks() captures time.Now() once per command invocation, ensuring consistent time reference across all predicates
  • Overflow protection is thorough with MaxMtimeN and MaxMminN constants
  • The allowlist enforcement tests verify the new package cannot sneak in unauthorized imports
  • Good test coverage with both unit tests and integration tests
# Priority File Finding
No findings

@AlexandreYang
Copy link
Copy Markdown
Member Author

Iteration 6 self-review result: APPROVE (no findings)

  • Consistent with all previous approvals
  • No security, correctness, compatibility, or coverage findings

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

ℹ️ 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".

@AlexandreYang
Copy link
Copy Markdown
Member Author

@codex review this PR

Important: Read the SPECS section of the PR description. If SPECS are present: make sure the implementation matches ALL the specs.
The specs override other instructions (code, inline comments in code, etc). ALL specs MUST be implemented.

Copy link
Copy Markdown
Member Author

@AlexandreYang AlexandreYang left a comment

Choose a reason for hiding this comment

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

Review Summary (Iteration 7 — Stability Check)

Verdict: APPROVE (submitted as COMMENT due to GitHub self-review restriction)

Reviewed all 14 changed files across allowedsymbols/, builtins/find/, builtins/ls/, interp/, and the new restrictedtime/ package.

Overall Assessment: Safe to merge

This is a well-executed security hardening change that extracts time.Now() from builtins into a dedicated restrictedtime package. The key security properties are:

  1. time.Now() is confined to a single call site (restrictedtime.NewCallbacks()), captured once per command invocation.
  2. Builtins only receive boolean-returning closures — they cannot obtain raw wall-clock time.
  3. Allowlist changes are correcttime.Now, time.Duration, time.Hour, time.Minute, time.Second, math.Ceil, math.Floor removed from builtins allowlist; time.Now added only to restrictedtime-specific allowlist.
  4. No new unsafe importsrestrictedtime only uses math and time.
  5. Overflow protection preservedMaxMtimeN and MaxMminN constants with proper guards.
  6. Test coverage is adequate — unit tests for MatchMtime, MatchMmin, IsRecentEnough, NewCallbacks, overflow cases, and allowlist verification tests all present.
  7. GNU find semantics preserved — floor bucketing for -mtime, ceiling bucketing for -mmin, truncation to second precision for +N/-N comparisons.

Findings

No P0, P1, P2, or P3 findings.

Positive Observations

  • The callback pattern (NewCallbacks returning closures) is a clean design that enforces the security boundary at the type level — builtins physically cannot access time.Now().
  • The restrictedtime allowlist verification tests (TestVerificationTimecomp*) provide defense-in-depth: even if someone adds a new import to the package, the allowlist check will catch it.
  • Overflow guards for large -mtime/-mmin values prevent integer overflow without panicking.
  • The single time.Now() capture per invocation ensures consistent time reference across multiple root paths in find.

@AlexandreYang
Copy link
Copy Markdown
Member Author

Iteration 7 self-review result: APPROVE (no findings)

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

ℹ️ 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".

@AlexandreYang
Copy link
Copy Markdown
Member Author

@codex review this PR

Important: Read the SPECS section of the PR description. If SPECS are present: make sure the implementation matches ALL the specs.
The specs override other instructions (code, inline comments in code, etc). ALL specs MUST be implemented.

Copy link
Copy Markdown
Member Author

@AlexandreYang AlexandreYang left a comment

Choose a reason for hiding this comment

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

Review Summary

Reviewed all 14 changed files across the restrictedtime package, builtins/find, builtins/ls, interp/runner_exec.go, and allowedsymbols/.

Overall assessment: safe to merge (APPROVE — submitted as COMMENT because GitHub prevents approving own PR)

# Priority File Finding
No findings

Security

This PR strengthens the sandbox by confining time.Now() to a single call site in restrictedtime.NewCallbacks(). Previously, time.Now was in the interp allowlist and builtins had direct access to time-comparison math symbols (math.Ceil, math.Floor, time.Duration, time.Hour, time.Minute, time.Second). Now:

  • Builtins receive only boolean-returning closures (MatchMtime, MatchMmin, IsRecentEnough) via CallContext — they cannot obtain raw wall-clock time.
  • time.Now is removed from the interp allowlist and confined to restrictedtimeAllowedSymbols.
  • The restrictedtime package has its own dedicated allowlist enforced by TestTimecompAllowedSymbols.
  • Verified: no time.Now() calls remain in any builtin production code.

Correctness

  • MatchMtime, MatchMmin, and IsRecentEnough logic is preserved identically (moved from find/eval.go to restrictedtime/timecomp.go).
  • NewCallbacks() captures time.Now() once per command invocation (runner_exec.go:269), ensuring consistent time reference across all predicates evaluated during a single find or ls execution.
  • evalMtime/evalMmin in find properly delegate through CallContext callbacks.
  • formatTime in ls correctly uses the IsRecentEnough callback.
  • Overflow guards (MaxMtimeN, MaxMminN) are preserved with the same logic.

Test Coverage

Adequate coverage across all changed code paths:

Code path Test Status
MatchMmin ceiling bucketing restrictedtime/timecomp_test.go:TestMatchMminCeiling Covered
MatchMmin overflow restrictedtime/timecomp_test.go:TestMatchMminOverflow Covered
MatchMtime floor bucketing restrictedtime/timecomp_test.go:TestMatchMtimeFloor Covered
IsRecentEnough boundary restrictedtime/timecomp_test.go:TestIsRecentEnough Covered
NewCallbacks closure wiring restrictedtime/timecomp_test.go:TestNewCallbacks Covered
find -mmin delegation builtins/find/now_test.go:TestMatchMminCalledConsistently Covered
Allowlist enforcement allowedsymbols/symbols_restrictedtime_test.go Covered
Allowlist verification (clean/unlisted/exempt) allowedsymbols/symbols_restrictedtime_verification_test.go Covered

Positive Observations

  • Clean separation of concerns: time-comparison logic in its own package with its own allowlist.
  • The callback pattern (boolean-returning closures) is an effective defense-in-depth measure — even if a builtin is compromised, it cannot extract the current time.
  • NewCallbacks() captures time.Now() once, ensuring consistent time reference and preventing TOCTOU issues in predicates like -mmin evaluated across many files.
  • Thorough verification tests for the allowlist itself (clean pass, unlisted symbol detection, exempt import handling).
  • All tests pass locally.

@AlexandreYang
Copy link
Copy Markdown
Member Author

Iteration 8 self-review result: APPROVE (no findings)

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

ℹ️ 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".

@AlexandreYang
Copy link
Copy Markdown
Member Author

@codex review this PR

Important: Read the SPECS section of the PR description. If SPECS are present: make sure the implementation matches ALL the specs.
The specs override other instructions (code, inline comments in code, etc). ALL specs MUST be implemented.

Copy link
Copy Markdown
Member Author

@AlexandreYang AlexandreYang left a comment

Choose a reason for hiding this comment

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

Review Summary

Reviewed the extraction of time.Now() from builtins into a dedicated restrictedtime package. This PR strengthens the sandbox by confining wall-clock access to a single call site, replacing the previous CallContext.Now() function with three boolean-returning closures (MatchMtime, MatchMmin, IsRecentEnough).

Overall assessment: safe to merge (would APPROVE but cannot approve own PR)

No P0, P1, P2, or P3 findings.

Security

  • time.Now() confinement is correct. The only call site is restrictedtime.NewCallbacks(), invoked once per command in interp/runner_exec.go. Builtins cannot import the restrictedtime package (blocked by the builtin import allowlist) and receive only boolean-returning closures via CallContext.
  • No raw time leakage. The now value captured in NewCallbacks() is a closure-local variable; builtins cannot extract it.
  • Allowlist changes are correct. time.Now moved from interpAllowedSymbols to restrictedtimeAllowedSymbols. math.Ceil, math.Floor, time.Duration, time.Hour, time.Minute, time.Second removed from builtin allowlists and added to the restrictedtime allowlist. No new capabilities granted to builtins.
  • No filesystem access outside sandbox. No new os.Open, os.Stat, etc. calls introduced.

Bash Compatibility

  • MatchMtime logic is a direct extraction from evalMtime — same floor-bucketing for exact, same truncation to second precision for +N/-N, same overflow guards.
  • MatchMmin logic is a direct extraction from evalMmin — same ceiling-bucketing for exact, same raw-second comparison for +N/-N, same overflow guards.
  • IsRecentEnough in ls is semantically equivalent to the old formatTime logic: !modTime.Before(cutoff) && !modTime.After(now) === old !(t.Before(sixMonthsAgo) || t.After(now)).

Correctness

  • NewCallbacks() captures time.Now() once, ensuring consistent timestamps across all predicate evaluations within a single command invocation (same guarantee as the old callCtx.Now() approach).
  • The cmpOp to int conversion at the call site (int(cmp)) correctly maps the find-internal constants to the restrictedtime constants (CmpLess=-1, CmpExact=0, CmpMore=1).
  • The now field removed from evalContext and walkOptions — no remaining references. Clean removal.

Test Coverage

Code path Test Status
MatchMmin ceiling bucketing restrictedtime/timecomp_test.go:TestMatchMminCeiling Covered
MatchMmin overflow restrictedtime/timecomp_test.go:TestMatchMminOverflow Covered
MatchMtime floor bucketing restrictedtime/timecomp_test.go:TestMatchMtimeFloor Covered
NewCallbacks closure wiring restrictedtime/timecomp_test.go:TestNewCallbacks Covered
IsRecentEnough boundary cases restrictedtime/timecomp_test.go:TestIsRecentEnough Covered
find delegates to MatchMmin builtins/find/now_test.go:TestMatchMminCalledConsistently Covered
restrictedtime allowlist allowedsymbols/symbols_restrictedtime_test.go Covered
restrictedtime verification allowedsymbols/symbols_restrictedtime_verification_test.go Covered
MatchMtime overflow guards Pre-existing gap (not introduced by this PR)

All tests pass locally (go test ./restrictedtime/ ./builtins/find/ ./builtins/ls/ ./allowedsymbols/ ./tests/).

Positive Observations

  • Excellent security architecture: builtins can only ask boolean questions about file times, never obtain raw time.Time values.
  • Clean separation of concerns with the restrictedtime package having its own import allowlist and verification tests.
  • The NewCallbacks() factory pattern is elegant — captures time.Now() once and returns typed closures, making it impossible for builtins to call time.Now() repeatedly or obtain the captured instant.
  • Thorough test migration: all time-comparison tests moved from find/eval_test.go to restrictedtime/timecomp_test.go with new TestNewCallbacks and TestIsRecentEnough tests added.

@AlexandreYang
Copy link
Copy Markdown
Member Author

Iteration 9 self-review result: APPROVE (no findings)

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

ℹ️ 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".

@AlexandreYang
Copy link
Copy Markdown
Member Author

@codex review this PR

Important: Read the SPECS section of the PR description. If SPECS are present: make sure the implementation matches ALL the specs.
The specs override other instructions (code, inline comments in code, etc). ALL specs MUST be implemented.

Copy link
Copy Markdown
Member Author

@AlexandreYang AlexandreYang left a comment

Choose a reason for hiding this comment

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

Review Summary (Iteration 10 — final stability check)

All previous iterations APPROVE. This iteration confirms: safe to merge.

Reviewed the extraction of time.Now() from builtins into a dedicated restrictedtime package. This is a well-structured security hardening change that confines wall-clock access to a single call site.

Overall Assessment: Safe to merge (no P0/P1/P2 findings)

What was reviewed

  • restrictedtime/timecomp.go — new package with MatchMtime, MatchMmin, IsRecentEnough functions and NewCallbacks() factory
  • builtins/builtins.goCallContext API change: Now func() time.Time replaced with three boolean-returning callbacks
  • builtins/find/eval.goevalMtime/evalMmin now delegate to CallContext callbacks
  • builtins/find/find.go — removed now capture and threading through walkOptions/evalContext
  • builtins/ls/ls.goformatTime now uses IsRecentEnough callback instead of direct time comparison
  • interp/runner_exec.go — wires restrictedtime.NewCallbacks() into CallContext
  • allowedsymbols/time.Now moved from interp to restrictedtime; math.Ceil/math.Floor/time.Duration/etc. moved from builtins to restrictedtime
  • Tests — unit tests migrated correctly; TestMatchMminCalledConsistently replaces TestNowCalledOnce; verification tests for symbol allowlist

Security Analysis

  1. Sandbox integrity: Strengthened. Builtins can no longer call time.Now() or obtain raw wall-clock values. They can only ask boolean questions about file modification times via closures.
  2. time.Now() confinement: Single call site in NewCallbacks() captures the timestamp once per command invocation — matches GNU find's single-snapshot semantics.
  3. No new imports in builtins: The find builtin dropped its math import; ls dropped its now parameter threading. Neither imports restrictedtime directly.
  4. Allowlist enforcement: New restrictedtimeAllowedSymbols list is properly tested with verification tests (clean pass, unlisted symbol, exempt import).
  5. cmpOp to int mapping: find's cmpLess/cmpExact/cmpMore values (-1/0/1) match restrictedtime's CmpLess/CmpExact/CmpMore exactly. The int(cmp) conversion in evalMtime/evalMmin is safe.

Correctness Analysis

  1. MatchMtime/MatchMmin logic is a direct move from builtins/find/eval.go with the same overflow guards, truncation behavior, and bucketing semantics.
  2. IsRecentEnough logic correctly inverts the old formatTime condition: old t.Before(sixMonthsAgo) || t.After(now) maps to new !modTime.Before(cutoff) && !modTime.After(now).
  3. NewCallbacks() captures time.Now() once and returns closures — each command invocation gets a consistent timestamp.

Test Coverage

Code path Test Status
MatchMmin ceiling bucketing restrictedtime/TestMatchMminCeiling Covered
MatchMmin overflow restrictedtime/TestMatchMminOverflow Covered
MatchMtime floor bucketing restrictedtime/TestMatchMtimeFloor Covered
NewCallbacks wiring restrictedtime/TestNewCallbacks Covered
IsRecentEnough restrictedtime/TestIsRecentEnough Covered
MatchMmin delegation from find find/TestMatchMminCalledConsistently Covered
MatchMtime overflow Pre-existing gap (P3)
Allowlist enforcement allowedsymbols/TestTimecompAllowedSymbols + verification tests Covered

Positive Observations

  • Clean separation of concerns: time logic is isolated in a dedicated package with its own symbol allowlist
  • The boolean-callback pattern effectively prevents builtins from extracting wall-clock information
  • Tests were migrated faithfully — same test cases, just with updated function signatures
  • Verification tests for the allowlist catch both unlisted symbols and exempt internal imports
  • All tests pass locally

Minor Note (not a finding)

MatchMtime overflow handling (MaxMtimeN guard) is not explicitly unit-tested, unlike MatchMmin overflow. This is a pre-existing gap that predates this PR.

@AlexandreYang
Copy link
Copy Markdown
Member Author

Iteration 10 self-review result: APPROVE (no findings)

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

ℹ️ 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".

@AlexandreYang
Copy link
Copy Markdown
Member Author

Review-Fix Loop Summary

Iteration log

# Review result Findings Fixes applied CI status
1 COMMENT 3 (3×P3) 2 fixed (TestNewCallbacks, strengthened now_test assertion) Pending
2 APPROVE 0 1 (gofmt fix) gofmt FAILURE → fixed
3 APPROVE 0 Passing
4 APPROVE 0 Passing
5 APPROVE 0 Passing
6 APPROVE 0 Passing
7 APPROVE 0 Passing
8 APPROVE 0 Passing
9 APPROVE 0 Passing
10 APPROVE 0 Passing

Final state

  • Self-review: APPROVE (no findings across 9 consecutive approvals)
  • Unresolved external comments: 0
  • CI: Passing (all checks green)
  • Codex reviews: All responded with no issues

Remaining issues

None. PR is clean and ready for human review.

@AlexandreYang AlexandreYang deleted the alex/time_now branch March 17, 2026 22:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant