Skip to content

fix(cron): write macOS launchd logs to ~/Library/Logs, not /tmp#29144

Open
alii wants to merge 2 commits into
mainfrom
claude/cron-macos-log-dir
Open

fix(cron): write macOS launchd logs to ~/Library/Logs, not /tmp#29144
alii wants to merge 2 commits into
mainfrom
claude/cron-macos-log-dir

Conversation

@alii

@alii alii commented Apr 10, 2026

Copy link
Copy Markdown
Member

What

Bun.cron() on macOS wrote the launchd plist's StandardOutPath/StandardErrorPath to /tmp/bun.cron.<title>.{stdout,stderr}.log. This PR moves them to ~/Library/Logs/bun/cron/.

Why

/tmp is world-writable and the log path is fully predictable from the job title. On a multi-user Mac, another local user can pre-create a symlink at that path before the job first runs; launchd will then open and append through the symlink as the registering user (CWE-59 / CWE-377). ~/Library/Logs is per-user and not attacker-controllable.

Change

  • cron.zig: create ~/Library/Logs/bun/cron alongside ~/Library/LaunchAgents, XML-escape the path, and use it for both log entries in the plist.
  • cron.test.ts: the existing log-path test now asserts the new directory and not.toContain("<string>/tmp/"); e2e cleanup paths updated.
  • docs/runtime/cron.mdx: updated log paths.

Supersedes #28299 (rebased onto current main; dropped the unrelated WorkingDirectory change which overlaps with #28283).

Fixes #28298

Test

USE_SYSTEM_BUN=1 bun test cron.test.ts -t "plist log paths are under"   # fails — plist still has /tmp
bun bd test cron.test.ts -t "plist log paths are under"                 # 1 pass, 0 fail

@robobun

robobun commented Apr 10, 2026

Copy link
Copy Markdown
Collaborator
Updated 7:27 AM PT - May 30th, 2026

@autofix-ci[bot], your commit 6690afa797a653686757f98ac480c423d31470cc passed in Build #59257! 🎉


🧪   To try this PR locally:

bunx bun-pr 29144

That installs a local version of the PR into your bun-29144 executable, so you can run:

bun-29144 --bun

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Bun.cron(…) writes logs to the insecure globally shared temp dir on macOS #28298 - Directly reports the security vulnerability where Bun.cron() writes launchd logs to the world-writable /tmp directory on macOS; this PR moves them to ~/Library/Logs/bun/cron/

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #28298

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Replace macOS cron stdout/stderr log paths from /tmp to the per-user directory ~/Library/Logs/bun/cron across implementation, tests, bindings, runtime wiring, and documentation; create the log directory and embed its XML-escaped path into generated launchd plists.

Changes

macOS Cron Log Paths

Layer / File(s) Summary
Create per-user log dir and refactor plist generation
src/runtime/api/cron.rs
start_mac validates the cron schedule, builds ~/Library/Logs/bun/cron, ensures the directory exists, and delegates plist construction to build_launchd_plist which XML-escapes inputs and emits StandardOutPath/StandardErrorPath pointing to `bun.cron.<title>.(stdout
Expose plist builder to JS and register runtime function
src/runtime/api/BunObject.bind.ts, src/js/internal-for-testing.ts, src/runtime/hw_exports.rs
Adds cronPlistForTesting bindgen wrapper and JS export, plus hw_exports shims and JS2Native registration that call build_launchd_plist and map PlistError variants to JS errors.
Update macOS plist tests and cleanup
test/js/bun/cron/cron.test.ts
New unit tests assert HOME-derived, XML-escaped log paths under ${HOME}/Library/Logs/bun/cron, ensure invalid cron expressions throw, rework e2e expectations to avoid /tmp, verify the log directory exists, and update cleanup unlink targets.
Documentation and type declaration updates
docs/runtime/cron.mdx, packages/bun-types/bun.d.ts
Documentation and type declaration updated example commands and paths to reference `~/Library/Logs/bun/cron/bun.cron.<title>.(stdout
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely and clearly summarizes the main change: moving macOS cron launchd logs from /tmp to ~/Library/Logs, which is the core objective of the PR.
Description check ✅ Passed The description covers both required template sections: What (moving logs from /tmp to ~/Library/Logs), Why (security against CWE-59/CWE-377), and how verification was done (test results). It is comprehensive and well-structured.
Linked Issues check ✅ Passed Changes address all primary objectives from #28298: prevent logs in global /tmp [cron.rs], use per-user ~/Library/Logs directory [cron.rs], XML-escape paths [cron.rs], and update tests and docs accordingly [cron.test.ts, cron.mdx].
Out of Scope Changes check ✅ Passed All changes are scoped to the cron logging security fix: updated cron implementation, tests, documentation, and added a testing helper for plist generation. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. fix(cron): use user-specific log directory instead of /tmp on macOS #28299 - Same fix: moves macOS launchd plist log paths from /tmp to a user-specific directory; this PR explicitly supersedes fix(cron): use user-specific log directory instead of /tmp on macOS #28299

🤖 Generated with Claude Code

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — clean security fix moving log paths from world-writable /tmp to ~/Library/Logs/bun/cron, with proper directory creation, XML-escaping, and test coverage.

Extended reasoning...

Overview

The PR modifies three files: cron.zig (core macOS plist generation), cron.test.ts (test assertions and cleanup), and docs/runtime/cron.mdx (documentation). The change is narrow and self-contained: replace hardcoded /tmp/bun.cron.${title}.{stdout,stderr}.log paths with per-user paths under \~/Library/Logs/bun/cron/.

Security risks

This PR fixes a CWE-59/CWE-377 symlink/predictable-path vulnerability. The new path (\~/Library/Logs) is user-owned and not controllable by other local users. The log_dir is XML-escaped before embedding in the plist. No new security risks are introduced.

Level of scrutiny

Low-to-medium. The logic change is mechanical: swap a hardcoded /tmp prefix for a computed per-user directory with directory creation, XML escaping, and proper defer free() cleanup. The format string argument count increase (8 → 10) matches the two new {s} placeholders. Error paths follow the existing pattern. The pre-existing memory leak flagged by the bug hunter is unrelated to this PR and is already filed as a separate finding.

Other factors

The test now both asserts the new path and asserts absence of /tmp/, giving good regression coverage. Docs are updated in sync. No CODEOWNERS files cover this path, and the change is additive with no behavioral regression on Linux or Windows.

Comment thread src/bun.js/api/cron.zig
@alii

alii commented Apr 11, 2026

Copy link
Copy Markdown
Member Author

@robobun adopt

@robobun

robobun commented Apr 11, 2026

Copy link
Copy Markdown
Collaborator

✅ Ready to merge — the diff is green; the one red lane is CI infrastructure, not this change.

The fix (live Rust src/runtime/api/cron.rs): create ~/Library/Logs/bun/cron at registration and point both StandardOutPath/StandardErrorPath there instead of world-writable /tmp (CWE-59/377). Plist body built by a platform-independent helper so the log-path / XML-escaping logic is unit-tested on every OS via a bun:internal-for-testing hook (the real launchd path is darwin-only). Bun.cron() JSDoc + docs updated.

cargo build + clippy + rustfmt clean; all review threads resolved; CodeRabbit + claude both clean. The cron tests pass on both macOS lanes that ran — darwin-14-x64-test-bun ✅ and darwin-26-aarch64-test-bun ✅.

The only non-green check is darwin-14-aarch64-test-bun, status "Expired" — the macOS aarch64 agent queue timed out and the job never ran (jobs_failed: 0, no test output). Re-running just that lane clears it. Fixes #28298.

@alii

alii commented May 30, 2026

Copy link
Copy Markdown
Member Author

@robobun rebase and get this mergable

@robobun robobun force-pushed the claude/cron-macos-log-dir branch from ea6e5f0 to 504c4a8 Compare May 30, 2026 09:18
Comment thread docs/runtime/cron.mdx
@robobun robobun force-pushed the claude/cron-macos-log-dir branch from 504c4a8 to bb294ef Compare May 30, 2026 09:46

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — focused security fix that mirrors the existing LaunchAgents directory-creation pattern; tests and docs are in sync.

Extended reasoning...

Overview

This PR moves macOS Bun.cron() launchd log paths from /tmp/bun.cron.<title>.{stdout,stderr}.log to ~/Library/Logs/bun/cron/. It touches four files: src/runtime/api/cron.rs (creates the per-user log directory and uses it for StandardOutPath/StandardErrorPath in the plist, with XML-escaping), test/js/bun/cron/cron.test.ts (asserts the new path, asserts /tmp is not present, and updates e2e cleanup), docs/runtime/cron.mdx (updated log paths), and packages/bun-types/bun.d.ts (one-line JSDoc update to match).

Security risks

This is itself a security hardening — it eliminates a CWE-59/377 symlink-follow risk by moving from world-writable /tmp to a per-user directory. The new code introduces no new attack surface: the log dir is built from $HOME (already used one block above for ~/Library/LaunchAgents), XML-escaped via the same try_escape! macro as every other dynamic value in the plist, and created with the same Fd::cwd().make_path() call used for the LaunchAgents directory.

Level of scrutiny

Low-to-moderate. The Rust change is a near-verbatim copy of the launch_agents_dir block immediately above it (lines 512–524 vs 526–541), so it follows an established and already-reviewed pattern. The format-string change correctly adds {5} and a sixth positional argument. The test, docs, and JSDoc changes are mechanical path-string updates.

Other factors

Both of my prior inline comments are resolved: the pre-existing Zig memory-leak note was correctly marked out-of-scope (and the Zig file is now dead reference code post-#30412), and the stale bun.d.ts JSDoc was fixed in bb294ef. The bug-hunting system found no issues on the current revision. The packages/bun-types/ CODEOWNER (alii) is the one driving this PR. The updated test includes both positive and negative assertions plus a directory-existence check.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — focused security hardening that mirrors the existing ~/Library/LaunchAgents mkdir pattern; both prior review threads addressed.

Extended reasoning...

Overview

Moves macOS Bun.cron() launchd log paths from world-writable /tmp/bun.cron.<title>.{stdout,stderr}.log to per-user ~/Library/Logs/bun/cron/. Touches four files: src/runtime/api/cron.rs (build the log dir path, Fd::cwd().make_path() it, XML-escape, interpolate as {5} in the plist template), test/js/bun/cron/cron.test.ts (assert the new path, assert not.toContain("<string>/tmp/"), assert the dir exists, update e2e cleanup), docs/runtime/cron.mdx and packages/bun-types/bun.d.ts (doc/JSDoc strings).

Security risks

This is the security fix itself — it removes a CWE-59/377 predictable-path symlink-follow vector in /tmp. The new code writes only under $HOME/Library/Logs/bun/cron, a directory the user already controls; make_pathmkdir_recursive_at so the multi-level create works on a fresh machine where ~/Library/Logs/bun doesn't exist yet. The log-dir string is run through the same xml_escape as the other plist fields, so a $HOME containing &/< won't break the plist. No new injection, auth, or data-exposure surface.

Level of scrutiny

Low–medium. The Rust hunk is 18 lines that copy the exact shape of the launch_agents_dir block 12 lines above (build → make_path → error → finish), then add one positional arg to an existing write!. The rest is doc strings and test expectations. macOS-only code path behind #[cfg(target_os = "macos")].

Other factors

Both of my earlier inline threads are resolved: the pre-existing Zig memory leak was correctly scoped out (and the code has since been ported to Rust anyway), and the stale bun.d.ts JSDoc was fixed in bb294ef — confirmed in the current diff. The bug-hunting pass found nothing this round. The one CI red is streams-leak.test.ts on a single Linux aarch64 shard, unrelated to a macOS-only plist change. The .d.ts file is CODEOWNED by alii, who is the one driving this PR.

@robobun robobun force-pushed the claude/cron-macos-log-dir branch from 35e35ce to f540bd3 Compare May 30, 2026 10:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/js/bun/cron/cron.test.ts`:
- Around line 69-71: The test currently calls cronPlistForTesting via require()
inside the describe block; instead add a module-scope named import for
cronPlistForTesting from "bun:internal-for-testing" (matching the exported name
used in the diff) at the top of the file and remove the inline require() call
inside the describe; ensure any TypeScript typing (the function signature) is
preserved via an import type or local type annotation if needed so usages of
cronPlistForTesting in the tests remain correctly typed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d7cd4efa-37c4-4d50-8603-d269776653a8

📥 Commits

Reviewing files that changed from the base of the PR and between 35e35ce and f540bd3.

📒 Files selected for processing (7)
  • docs/runtime/cron.mdx
  • packages/bun-types/bun.d.ts
  • src/js/internal-for-testing.ts
  • src/runtime/api/BunObject.bind.ts
  • src/runtime/api/cron.rs
  • src/runtime/hw_exports.rs
  • test/js/bun/cron/cron.test.ts

Comment thread test/js/bun/cron/cron.test.ts Outdated
The launchd plist hard-coded StandardOutPath/StandardErrorPath under
/tmp with a predictable name (bun.cron.<title>.{stdout,stderr}.log). On
a multi-user machine another local user can pre-create a symlink at that
path and have launchd follow it when the job first runs, writing through
to an arbitrary file as the registering user (CWE-59/377).

Create ~/Library/Logs/bun/cron at registration time and point both log
paths there instead. The directory is per-user and not world-writable.

The plist body is built by a platform-independent helper
(build_launchd_plist) so the log-path and XML-escaping logic can be
unit-tested on any host via a bun:internal-for-testing hook — the real
start_mac path is darwin-only-compiled. Also updates the Bun.cron()
JSDoc and docs.

Fixes #28298. Supersedes #28299.
@robobun robobun force-pushed the claude/cron-macos-log-dir branch from f9dbb74 to 082412e Compare May 30, 2026 11:18

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I didn't find any issues in this revision — all prior review threads are resolved — but since this is a security fix that also adds new unsafe FFI plumbing in hw_exports.rs, it's worth a quick human sign-off before merge.

Extended reasoning...

Overview

This PR fixes CWE-59/377 in Bun.cron() on macOS by moving the launchd plist's StandardOutPath/StandardErrorPath from world-writable /tmp to per-user ~/Library/Logs/bun/cron/. To make the fix testable on non-darwin CI, the plist body construction is extracted into a platform-independent build_launchd_plist() helper and exposed to JS via a new cronPlistForTesting bindgen function. The change spans 7 files: the Rust implementation (cron.rs), new FFI wiring (hw_exports.rs, BunObject.bind.ts, internal-for-testing.ts), tests, docs, and the bun.d.ts JSDoc.

Security risks

The PR is itself a security hardening — it removes a predictable-path symlink-follow vector in /tmp. The new log directory is created with Fd::cwd().make_path() under $HOME, and the path is XML-escaped before being embedded in the plist. I don't see new attack surface introduced; the cronPlistForTesting hook is a pure string-builder gated behind bun:internal-for-testing and doesn't touch the filesystem. No injection, auth, or data-exposure concerns beyond what the fix addresses.

Level of scrutiny

This warrants human review rather than bot approval: (1) it's a security fix, so a maintainer should confirm the new location and make_path semantics are what they want; (2) it adds ~70 lines of handwritten unsafe FFI in hw_exports.rs (raw pointer derefs, HOST_EXPORT markers, jsc_abi_extern! entries) — these closely follow the existing fmtString/getDeinitCountForTesting patterns in the same file, but unsafe ABI glue is exactly the kind of thing that benefits from a second pair of eyes; (3) the refactor moves the plist template into a shared helper and re-validates the schedule twice in start_mac — behavior-preserving but a structural change to a production path.

Other factors

All three prior review threads (pre-existing memory leak — out of scope; stale bun.d.ts JSDoc — fixed; require() → module import — fixed) are resolved. The new cross-platform unit tests for log paths and XML escaping are solid, and the macOS e2e test was updated to assert the new directory exists and /tmp is absent. The robobun build status shows linker warnings/failures on several lanes, but they look like the ambient -no-pie infrastructure noise rather than anything this PR introduced.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bun.cron(…) writes logs to the insecure globally shared temp dir on macOS

2 participants