diff --git a/lib/tmpdir.rb b/lib/tmpdir.rb
index f78fd72..6558a7e 100644
--- a/lib/tmpdir.rb
+++ b/lib/tmpdir.rb
@@ -22,6 +22,18 @@ class Dir
# require 'tmpdir'
# Dir.tmpdir # => "/tmp"
+ # Returns whether world-writable directories without the sticky bit
+ # should be allowed as temporary directories.
+ # Set +RUBY_TMPDIR_ALLOW_WORLD_WRITABLE+ environment variable to
+ # 1, true, or yes to allow.
+ # This is useful in container environments (e.g. Kubernetes with
+ # emptyDir volumes) where temporary directories are mounted with
+ # mode 0777 without the sticky bit.
+ def self.allow_world_writable?
+ /\A(1|true|yes)\z/i.match?(ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"])
+ end
+ private_class_method :allow_world_writable?
+
def self.tmpdir
Tmpname::TMPDIR_CANDIDATES.find do |name, dir|
unless dir
@@ -37,6 +49,9 @@ def self.tmpdir
# writable just from stat; OS mechanisms other than user/group/world bits can affect this.
warn "#{name} is not writable: #{dir}"
when stat.world_writable? && !stat.sticky?
+ if allow_world_writable?
+ break dir
+ end
warn "#{name} is world-writable: #{dir}"
else
break dir
@@ -104,7 +119,7 @@ def self.mktmpdir(prefix_suffix=nil, *rest, **options, &block)
begin
yield path.dup
ensure
- unless base
+ unless base or allow_world_writable?
base = File.dirname(path)
stat = File.stat(base)
if stat.world_writable? and !stat.sticky?
diff --git a/test/test_tmpdir.rb b/test/test_tmpdir.rb
index c91fc33..a0b9c42 100644
--- a/test/test_tmpdir.rb
+++ b/test/test_tmpdir.rb
@@ -47,6 +47,50 @@ def test_world_writable
end
end
+ def test_world_writable_allowed_by_env
+ omit "no meaning on this platform" if /mswin|mingw/ =~ RUBY_PLATFORM
+ Dir.mktmpdir do |tmpdir|
+ envs = %w[TMPDIR TMP TEMP]
+ oldenv = envs.each_with_object({}) {|v, h| h[v] = ENV.delete(v)}
+ old_allow = ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"]
+ begin
+ ENV[envs[0]] = tmpdir
+ File.chmod(0777, tmpdir)
+
+ # Without env var, world-writable without sticky should be rejected
+ ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"] = nil
+ assert_not_equal(tmpdir, assert_warn(/world-writable/) {Dir.tmpdir})
+
+ # With env var set, world-writable without sticky should be accepted
+ ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"] = "1"
+ assert_equal(tmpdir, Dir.tmpdir)
+
+ # mktmpdir should also work
+ newdir = Dir.mktmpdir("d", tmpdir) do |dir|
+ assert_file.directory? dir
+ assert_equal(tmpdir, File.dirname(dir))
+ dir
+ end
+ assert_file.not_exist?(newdir)
+
+ # Other accepted values
+ %w[true yes TRUE Yes].each do |val|
+ ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"] = val
+ assert_equal(tmpdir, Dir.tmpdir, "should accept with value #{val.inspect}")
+ end
+
+ # Invalid values should not bypass
+ %w[0 false no 2 enabled].each do |val|
+ ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"] = val
+ assert_not_equal(tmpdir, Dir.tmpdir, "should reject with value #{val.inspect}")
+ end
+ ensure
+ ENV["RUBY_TMPDIR_ALLOW_WORLD_WRITABLE"] = old_allow
+ ENV.update(oldenv)
+ end
+ end
+ end
+
def test_tmpdir_not_empty_parent
Dir.mktmpdir do |tmpdir|
envs = %w[TMPDIR TMP TEMP]