fix(session-flow): degrade a failing tzdata bundle to exit 3, not exit 1 - #2673
Conversation
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Claude finished @kyle-sexton's task in 2m 48s —— View job Security review complete
No security issues found in the files changed by this PR ( This PR is itself a security hardening change (predictable-tempdir / planted-cache mitigation, CWE-377/CWE-426), so I focused on whether the new trust logic actually closes the gap it claims to:
One thing I looked at but am not flagging as a finding: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4a196c90fd
ℹ️ 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".
|
Last security-reviewed head: |
|
Claude finished @kyle-sexton's task in 5m 29s —— View job Code review complete
Scope note: This repo carries a Findings🔴 Important — 🟡 Nit — Everything else — the |
|
Claude has reviewed this PR 2 times. The lane skips further automatic reviews after 5; deleting this comment resets the count. |
_ensure_bundled_tzdata() had no exception handling around ZipFile and extractall, so a corrupt or truncated vendor zip raised at import time and the script exited 1. In this script's contract exit 1 means "the limit still holds" — a damaged bundle told the caller to keep waiting. A missing bundle already degraded correctly to exit 3, which is the documented "cannot determine" code; every other failure now joins it. The cache directory also derived its name from the sha256 of a publicly committed file, making the path fully predictable, and a cache hit put that directory on sys.path without checking it. A pre-planted tzdata/__init__.py at that path executed. On Windows %TEMP% is per-user so this was not a cross-user vector there, but on a shared world-writable /tmp any local user could plant against it (CWE-377, CWE-426). The cache name now carries a per-user component, and a cache hit is trusted only when the directory is not a symlink and — where the platform can report ownership — is owned by this user. os.getuid does not exist on Windows, so that half is a no-op there and the per-user name carries the isolation instead; the docstring says so rather than implying a stronger guarantee. Both defects came from the final commit of #2648, which changed vendoring from a directory tree to a runtime-extracted zip after the last security review (both bot reviews pin 1b51417). Adds five regression tests. The corrupt and truncated cases fail against the pre-change code and pass after it.
50c4ba8 to
bed6268
Compare
Pass PYTHONTZPATH="" so corrupt/truncated zip cases actually exit 3 on Linux CI. Bump to 0.23.4; clarify the directory-at-bundle-path coverage. Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
bed6268 to
6f19a0e
Compare
Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
|
Claude finished @cursor[bot]'s task in 3m 1s —— View job Code review complete
Scope note: This repo carries What changed since the last code-review passTwo follow-up commits landed after the prior
Both directly address the two findings raised on the previous head (mine and Codex's, both pointing at the same FindingsNone. I re-read the full script and test file (not just the incremental diff) looking for anything the fixup commits might have introduced or missed, and didn't find anything a careful reviewer would block or flag:
Verification noteI could not execute the test suite myself — this lane's tool grant is |
Closes #2672
Summary
The tzdata bundling that fixed #2647 shipped two defects, both from that PR's final commit (
e86c2a98), which changed vendoring from a checked-in directory to a runtime-extracted zip after the last security review — both bot reviews pin1b514176, which never extracted anything.The #2647 fix itself is sound and untouched here: IANA zones still resolve on a host with no system TZDB.
Fix
1. A failing bundle no longer reports "the limit still holds."
_ensure_bundled_tzdata()had no exception handling aroundZipFile/extractall, so a corrupt or truncated zip raised at import time and exited 1 — which in this script's contract means the limit is still active, hand back and stop. Every failure now degrades to exit 3, matching what a missing bundle already did.2. The cache is no longer trusted blindly. The directory name now carries a per-user component, and a cache hit is trusted only when the directory is not a symlink and — where the platform can report ownership — is owned by this user.
On the ownership check's honest limits:
os.getuiddoes not exist on Windows, so that half is a no-op there and the per-user directory name carries the isolation instead. That is stated in the docstring rather than implied away. It is also the right shape for the actual risk:%TEMP%is already per-user on Windows, so the predictable-shared-directory problem is a POSIX-shared-/tmpproblem.Verification
Degradation, fixed vs.
origin/main— scratch copy of the script with a sabotaged bundle, asking a question whose answer should be "lifted":Planted-cache probe — computing the cache path from the committed zip's digest, planting
tzdata/__init__.py, running the script:Exit codes 0/1/2 unchanged for valid input:
Suite: 12/12 pass (7 pre-existing + 5 new) on Windows 11 / CPython 3.14. The corrupt and truncated cases fail against the pre-change code, so the tests prove the fix rather than merely accompanying it.
One test-design note:
chmod 000is not a portable probe for the unreadable-bundle class — it does not restrict an Administrator on Windows NTFS, so the file stays readable and the run legitimately succeeds. The suite uses a directory at the bundle path instead. I found this by writing the weaker test first and watching it pass for the wrong reason on both branches.Not run: the wider
plugin-gatesuite. This plugin's Python tests run only onubuntu-24.04in CI, so the POSIX half of the ownership check — the branch that actually comparesst_uid, and the only branch where the planted-directory scenario is a real cross-user vector — is exercised by CI and not by me. Everything above was verified on Windows.Related note, deliberately not fixed
A typo'd zone (
Not/AZone) now yields exit 3 with "report the timezone failure", where it previously yielded 2. Exit 3 is documented inkeep-going/SKILL.mdand the CHANGELOG so the change is disclosed, but genuinely unparsable input arguably still belongs at 2. Left for a maintainer decision.Separately, and filed in #2672's closing note: the checker cannot parse a date-form reset clause —
"resets Aug 18, 8pm (America/New_York)"returnsunparsed(exit 2), because only same-day time forms are recognized. A weekly limit states its reset exactly that way, so the script is blind to the longest limits. I hit this live while recovering from a weekly limit during this work.No version bump or CHANGELOG entry — a consolidating bump is landing separately, and parallel PRs staging the same version collide on
check-changelog-parity.sh --check-order.Related
N/A