Uh oh!
There was an error while loading. Please reload this page.
Fix 2 — Writability fallback (lines 36-47) - #69
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adjusts Dir.tmpdir selection to better handle containerized environments where File.writable? (access(2)) can report a false negative, by adding a fallback acceptance path based on stat.writable?, and adds tests covering the new behavior.
Changes:
- Add a fallback in
Dir.tmpdirto accept a candidate directory whenFile.writable?is false butstat.writable?indicates it should be writable, emitting a warning. - Add tests for (1) the fallback acceptance-with-warning path and (2) the rejection path when both checks indicate non-writable.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| lib/tmpdir.rb | Adds the new “writability fallback” behavior and warning during tmpdir candidate selection. |
| test/test_tmpdir.rb | Adds regression tests to validate the fallback and non-writable rejection behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
ioquatix
commented
Jun 21, 2026
I tested this with a local Kubernetes cluster ( For default disk-backed {uid: 1000,euid: 1000,mode: "40777",writable: true,stat_writable: true,sticky: false,world: 511,write: true}So in the reproduced Kubernetes case:
Stock I also tested {tmpdir: "/tmp",mode: "41777",sticky: true}This suggests the Kubernetes/Rails issue is a sticky-bit problem, not a I also tested a read-only root filesystem without a writable File.writable?("/tmp")# falseFile.stat("/tmp").writable?# trueFile.write("/tmp/probe","x")# Errno::EROFSWith this PR's fallback, Based on this, I think the correct fix is Kubernetes/pod configuration setting the sticky bit ( |
ioquatix
commented
Jun 21, 2026
As a temporary application-level workaround until Kubernetes can mount disk-backed For example, in an app boot file that runs early: require"tmpdir"classDirclass << selfalias_method:tmpdir_without_kubernetes_emptydir_workaround,:tmpdirdeftmpdirtmpdir_without_kubernetes_emptydir_workaroundrescueArgumentErrordir=ENV["RUBY_TMPDIR_ALLOW_UNSAFE_EMPTYDIR"]raiseunlessdir && !dir.empty?dir=File.expand_path(dir)stat=File.stat(dir)raiseunlessstat.directory?raiseunlessFile.writable?(dir)path=File.join(dir,".ruby-tmpdir-check-#{Process.pid}")File.open(path,File::WRONLY | File::CREAT | File::EXCL,0600){}File.unlink(path)warn"Using world-writable non-sticky tmpdir due to RUBY_TMPDIR_ALLOW_UNSAFE_EMPTYDIR=#{dir}: #{dir}"direndendendThen configure the pod explicitly: env:
- name: TMPDIRvalue: /tmp
- name: RUBY_TMPDIR_ALLOW_UNSAFE_EMPTYDIRvalue: /tmpThis intentionally bypasses Ruby's sticky-bit safety check, so it should only be used in tightly controlled containers where the temp directory is not shared with untrusted users/processes. It also uses a real write probe, not Preferred fixes remain: mount a temp directory with mode |
In some containerized environments (Kubernetes with read-only root filesystem + emptyDir volumes), File.writable? (which uses the access(2) syscall) can return
false even though the directory is actually writable via mounted volumes.
See rails/rails#56997 for more context.
Changes
Added a fallback to stat.writable? when File.writable? fails.
Ruby 3.4 changed stat.writable? to File.writable? (which calls the kernel's access(2) syscall). In some container environments, access(2) can return false even though the directory IS writable (due to security modules, mounted volumes, etc.). We now fall back to stat.writable? — if the mode bits say writable, we accept the directory with a warning.
Usage for K8s users
This can occur in containerized environments with:
Kubernetes with read-only root filesystem + emptyDir volumes: The security context may restrict access checks at the syscall level, but the mounted volume is still writable SELinux/AppArmor policies: Security modules can affect access(2) results without actually blocking write operations NFS mounts with root squashing or specific export options: As you mentioned, NFS can exhibit similar behavior User namespaces in containers: UID mapping can cause access(2) to return incorrect results. The issue is that access(2) checks permissions based on the real UID/GID and may not account for all the kernel mechanisms that ultimately determine writability. The actual open()/write() syscalls can succeed even when access() says no.
I don't have a specific reproducible environment to share right now, but I can try to create a minimal Kubernetes manifest that demonstrates this if that would
help. @rhenium