Merge pull request #17148 from alebcay/sandbox-strict-typing
sandbox: enable strict typing
This commit is contained in:
commit
a47c45dbda
@ -1,4 +1,4 @@
|
|||||||
# typed: true
|
# typed: strict
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "erb"
|
require "erb"
|
||||||
@ -20,51 +20,63 @@ class Sandbox
|
|||||||
|
|
||||||
sig { void }
|
sig { void }
|
||||||
def initialize
|
def initialize
|
||||||
@profile = SandboxProfile.new
|
@profile = T.let(SandboxProfile.new, SandboxProfile)
|
||||||
|
@failed = T.let(false, T::Boolean)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { params(file: T.any(String, Pathname)).void }
|
||||||
def record_log(file)
|
def record_log(file)
|
||||||
@logfile = file
|
@logfile = T.let(file, T.nilable(T.any(String, Pathname)))
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_rule(rule)
|
sig { params(allow: T::Boolean, operation: String, filter: T.nilable(String), modifier: T.nilable(String)).void }
|
||||||
|
def add_rule(allow:, operation:, filter: nil, modifier: nil)
|
||||||
|
rule = SandboxRule.new(allow:, operation:, filter:, modifier:)
|
||||||
@profile.add_rule(rule)
|
@profile.add_rule(rule)
|
||||||
end
|
end
|
||||||
|
|
||||||
def allow_write(path, options = {})
|
sig { params(path: T.any(String, Pathname), type: Symbol).void }
|
||||||
add_rule allow: true, operation: "file-write*", filter: path_filter(path, options[:type])
|
def allow_write(path:, type: :literal)
|
||||||
add_rule allow: true, operation: "file-write-setugid", filter: path_filter(path, options[:type])
|
add_rule allow: true, operation: "file-write*", filter: path_filter(path, type)
|
||||||
|
add_rule allow: true, operation: "file-write-setugid", filter: path_filter(path, type)
|
||||||
end
|
end
|
||||||
|
|
||||||
def deny_write(path, options = {})
|
sig { params(path: T.any(String, Pathname), type: Symbol).void }
|
||||||
add_rule allow: false, operation: "file-write*", filter: path_filter(path, options[:type])
|
def deny_write(path:, type: :literal)
|
||||||
|
add_rule allow: false, operation: "file-write*", filter: path_filter(path, type)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { params(path: T.any(String, Pathname)).void }
|
||||||
def allow_write_path(path)
|
def allow_write_path(path)
|
||||||
allow_write path, type: :subpath
|
allow_write path:, type: :subpath
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { params(path: T.any(String, Pathname)).void }
|
||||||
def deny_write_path(path)
|
def deny_write_path(path)
|
||||||
deny_write path, type: :subpath
|
deny_write path:, type: :subpath
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def allow_write_temp_and_cache
|
def allow_write_temp_and_cache
|
||||||
allow_write_path "/private/tmp"
|
allow_write_path "/private/tmp"
|
||||||
allow_write_path "/private/var/tmp"
|
allow_write_path "/private/var/tmp"
|
||||||
allow_write "^/private/var/folders/[^/]+/[^/]+/[C,T]/", type: :regex
|
allow_write path: "^/private/var/folders/[^/]+/[^/]+/[C,T]/", type: :regex
|
||||||
allow_write_path HOMEBREW_TEMP
|
allow_write_path HOMEBREW_TEMP
|
||||||
allow_write_path HOMEBREW_CACHE
|
allow_write_path HOMEBREW_CACHE
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def allow_cvs
|
def allow_cvs
|
||||||
allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.cvspass"
|
allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.cvspass"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def allow_fossil
|
def allow_fossil
|
||||||
allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.fossil"
|
allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.fossil"
|
||||||
allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.fossil-journal"
|
allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.fossil-journal"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { params(formula: Formula).void }
|
||||||
def allow_write_cellar(formula)
|
def allow_write_cellar(formula)
|
||||||
allow_write_path formula.rack
|
allow_write_path formula.rack
|
||||||
allow_write_path formula.etc
|
allow_write_path formula.etc
|
||||||
@ -72,17 +84,20 @@ class Sandbox
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Xcode projects expect access to certain cache/archive dirs.
|
# Xcode projects expect access to certain cache/archive dirs.
|
||||||
|
sig { void }
|
||||||
def allow_write_xcode
|
def allow_write_xcode
|
||||||
allow_write_path "#{Dir.home(ENV.fetch("USER"))}/Library/Developer"
|
allow_write_path "#{Dir.home(ENV.fetch("USER"))}/Library/Developer"
|
||||||
allow_write_path "#{Dir.home(ENV.fetch("USER"))}/Library/Caches/org.swift.swiftpm"
|
allow_write_path "#{Dir.home(ENV.fetch("USER"))}/Library/Caches/org.swift.swiftpm"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { params(formula: Formula).void }
|
||||||
def allow_write_log(formula)
|
def allow_write_log(formula)
|
||||||
allow_write_path formula.logs
|
allow_write_path formula.logs
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { void }
|
||||||
def deny_write_homebrew_repository
|
def deny_write_homebrew_repository
|
||||||
deny_write HOMEBREW_BREW_FILE
|
deny_write path: HOMEBREW_BREW_FILE
|
||||||
if HOMEBREW_PREFIX.to_s == HOMEBREW_REPOSITORY.to_s
|
if HOMEBREW_PREFIX.to_s == HOMEBREW_REPOSITORY.to_s
|
||||||
deny_write_path HOMEBREW_LIBRARY
|
deny_write_path HOMEBREW_LIBRARY
|
||||||
deny_write_path HOMEBREW_REPOSITORY/".git"
|
deny_write_path HOMEBREW_REPOSITORY/".git"
|
||||||
@ -117,11 +132,12 @@ class Sandbox
|
|||||||
allow_network path:, type: :literal
|
allow_network path:, type: :literal
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { params(args: T.any(String, Pathname)).void }
|
||||||
def exec(*args)
|
def exec(*args)
|
||||||
seatbelt = Tempfile.new(["homebrew", ".sb"], HOMEBREW_TEMP)
|
seatbelt = Tempfile.new(["homebrew", ".sb"], HOMEBREW_TEMP)
|
||||||
seatbelt.write(@profile.dump)
|
seatbelt.write(@profile.dump)
|
||||||
seatbelt.close
|
seatbelt.close
|
||||||
@start = Time.now
|
@start = T.let(Time.now, T.nilable(Time))
|
||||||
|
|
||||||
begin
|
begin
|
||||||
command = [SANDBOX_EXEC, "-f", seatbelt.path, *args]
|
command = [SANDBOX_EXEC, "-f", seatbelt.path, *args]
|
||||||
@ -219,20 +235,46 @@ class Sandbox
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
sig { params(path: Pathname).returns(Pathname) }
|
||||||
def expand_realpath(path)
|
def expand_realpath(path)
|
||||||
raise unless path.absolute?
|
raise unless path.absolute?
|
||||||
|
|
||||||
path.exist? ? path.realpath : expand_realpath(path.parent)/path.basename
|
path.exist? ? path.realpath : expand_realpath(path.parent)/path.basename
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { params(path: T.any(String, Pathname), type: Symbol).returns(String) }
|
||||||
def path_filter(path, type)
|
def path_filter(path, type)
|
||||||
case type
|
case type
|
||||||
when :regex then "regex #\"#{path}\""
|
when :regex then "regex #\"#{path}\""
|
||||||
when :subpath then "subpath \"#{expand_realpath(Pathname.new(path))}\""
|
when :subpath then "subpath \"#{expand_realpath(Pathname.new(path))}\""
|
||||||
when :literal, nil then "literal \"#{expand_realpath(Pathname.new(path))}\""
|
when :literal then "literal \"#{expand_realpath(Pathname.new(path))}\""
|
||||||
|
else raise ArgumentError, "Invalid path filter type: #{type}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class SandboxRule
|
||||||
|
sig { returns(T::Boolean) }
|
||||||
|
attr_reader :allow
|
||||||
|
|
||||||
|
sig { returns(String) }
|
||||||
|
attr_reader :operation
|
||||||
|
|
||||||
|
sig { returns(T.nilable(String)) }
|
||||||
|
attr_reader :filter
|
||||||
|
|
||||||
|
sig { returns(T.nilable(String)) }
|
||||||
|
attr_reader :modifier
|
||||||
|
|
||||||
|
sig { params(allow: T::Boolean, operation: String, filter: T.nilable(String), modifier: T.nilable(String)).void }
|
||||||
|
def initialize(allow:, operation:, filter:, modifier:)
|
||||||
|
@allow = allow
|
||||||
|
@operation = operation
|
||||||
|
@filter = filter
|
||||||
|
@modifier = modifier
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_constant :SandboxRule
|
||||||
|
|
||||||
# Configuration profile for a sandbox.
|
# Configuration profile for a sandbox.
|
||||||
class SandboxProfile
|
class SandboxProfile
|
||||||
SEATBELT_ERB = <<~ERB
|
SEATBELT_ERB = <<~ERB
|
||||||
@ -256,23 +298,26 @@ class Sandbox
|
|||||||
(allow default) ; allow everything else
|
(allow default) ; allow everything else
|
||||||
ERB
|
ERB
|
||||||
|
|
||||||
|
sig { returns(T::Array[String]) }
|
||||||
attr_reader :rules
|
attr_reader :rules
|
||||||
|
|
||||||
sig { void }
|
sig { void }
|
||||||
def initialize
|
def initialize
|
||||||
@rules = []
|
@rules = T.let([], T::Array[String])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { params(rule: SandboxRule).void }
|
||||||
def add_rule(rule)
|
def add_rule(rule)
|
||||||
s = +"("
|
s = +"("
|
||||||
s << (rule[:allow] ? "allow" : "deny")
|
s << (rule.allow ? "allow" : "deny")
|
||||||
s << " #{rule[:operation]}"
|
s << " #{rule.operation}"
|
||||||
s << " (#{rule[:filter]})" if rule[:filter]
|
s << " (#{rule.filter})" if rule.filter
|
||||||
s << " (with #{rule[:modifier]})" if rule[:modifier]
|
s << " (with #{rule.modifier})" if rule.modifier
|
||||||
s << ")"
|
s << ")"
|
||||||
@rules << s.freeze
|
@rules << s.freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
|
sig { returns(String) }
|
||||||
def dump
|
def dump
|
||||||
ERB.new(SEATBELT_ERB).result(binding)
|
ERB.new(SEATBELT_ERB).result(binding)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -15,7 +15,7 @@ RSpec.describe Sandbox, :needs_macos do
|
|||||||
end
|
end
|
||||||
|
|
||||||
specify "#allow_write" do
|
specify "#allow_write" do
|
||||||
sandbox.allow_write file
|
sandbox.allow_write path: file
|
||||||
sandbox.exec "touch", file
|
sandbox.exec "touch", file
|
||||||
|
|
||||||
expect(file).to exist
|
expect(file).to exist
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user