mirror of
https://github.com/Homebrew/brew.git
synced 2026-08-12 14:23:48 +04:00
Allow cask runs to access the network
Structured cask install steps run in a sandbox that denies network access. This breaks casks whose upstream setup commands must retrieve installation-time dependencies, including `gcloud-cli` virtualenv creation. - Add a serialised `network_access:` option to `run` steps. - Omit the option from API data when it retains the default denial. - Enable network access only when an explicit run requests it. - Preserve network denial for existing steps and generated completions. - Document the opt-in and cover both sandbox policies in tests. This avoids disabling network restrictions for all cask operations while giving affected casks an explicit, auditable escape hatch. See #23495.
This commit is contained in:
@@ -201,13 +201,13 @@ module Cask
|
||||
cask.config
|
||||
end
|
||||
|
||||
sig { returns(T.nilable(Sandbox)) }
|
||||
def cask_sandbox
|
||||
sig { params(network_access_allowed: T::Boolean).returns(T.nilable(Sandbox)) }
|
||||
def cask_sandbox(network_access_allowed: false)
|
||||
return unless Sandbox.use_for?("running cask artifact operations")
|
||||
|
||||
Sandbox.new.tap do |sandbox|
|
||||
sandbox.allow_read(path: cask.staged_path, type: :subpath)
|
||||
sandbox.add_install_hook_rules(network_access_allowed: false)
|
||||
sandbox.add_install_hook_rules(network_access_allowed:)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -33,7 +33,9 @@ module Cask
|
||||
sig { params(command: T.class_of(SystemCommand), phase: Symbol).void }
|
||||
def run_steps(command, phase: :install)
|
||||
runner = Homebrew::InstallSteps::Runner.new(context: cask, command:)
|
||||
sandbox = cask_sandbox
|
||||
sandbox = cask_sandbox(network_access_allowed: steps.any? do |step|
|
||||
step["type"] == "run" && step["network_access"] == true
|
||||
end)
|
||||
unless sandbox
|
||||
runner.run(steps, phase:)
|
||||
return
|
||||
|
||||
@@ -683,11 +683,12 @@ module Homebrew
|
||||
chdir: ::T.nilable(::T.any(::String, ::Pathname)),
|
||||
writable_paths: Paths,
|
||||
writable_base: ::T.nilable(::T.any(::String, ::Symbol)),
|
||||
network_access: ::T::Boolean,
|
||||
).void
|
||||
}
|
||||
def run(command, args: [], base: nil, env: {}, sudo: false, must_succeed: true, print_stdout: false,
|
||||
print_stderr: true, stdin_path: nil, stdout_path: nil, chdir: nil, writable_paths: [],
|
||||
writable_base: nil)
|
||||
writable_base: nil, network_access: false)
|
||||
add_step("run",
|
||||
"command" => path_spec(command, base:, default_base: nil),
|
||||
"args" => args.map(&:to_s),
|
||||
@@ -703,7 +704,8 @@ module Homebrew
|
||||
writable_paths,
|
||||
base: writable_base,
|
||||
default_base: @default_base,
|
||||
))
|
||||
),
|
||||
"network_access" => network_access)
|
||||
end
|
||||
|
||||
sig {
|
||||
|
||||
@@ -64,7 +64,7 @@ RSpec.describe Cask::Artifact::AbstractInstallSteps, :cask do
|
||||
artifact = cask.artifacts.find { |candidate| candidate.is_a?(Cask::Artifact::PostflightSteps) }
|
||||
run_step = artifact.steps.find { |step| step["type"] == "run" }
|
||||
|
||||
expect(run_step).not_to include("print_stdout", "suppress_stderr", "writable_paths")
|
||||
expect(run_step).not_to include("print_stdout", "suppress_stderr", "writable_paths", "network_access")
|
||||
end
|
||||
|
||||
it "sandboxes complete step blocks, including system commands" do
|
||||
@@ -119,6 +119,28 @@ RSpec.describe Cask::Artifact::AbstractInstallSteps, :cask do
|
||||
expect(original_home/"Library/Application Support/cask-home-state").to exist
|
||||
end
|
||||
|
||||
it "allows network access for runs that request it" do
|
||||
cask = Cask::Cask.new("with-networked-install-step") do
|
||||
version "1.2.3"
|
||||
sha256 :no_check
|
||||
url "file://#{TEST_FIXTURE_DIR}/cask/container.zip"
|
||||
|
||||
postflight_steps do
|
||||
run "/usr/bin/true", network_access: true
|
||||
end
|
||||
end
|
||||
sandbox = instance_double(Sandbox).as_null_object
|
||||
cask.staged_path.mkpath
|
||||
cask.config_path.dirname.mkpath
|
||||
|
||||
allow(Sandbox).to receive_messages(available?: true, new: sandbox)
|
||||
allow(Sandbox).to receive(:with_preserved_brew_file).and_yield
|
||||
expect(sandbox).to receive(:add_install_hook_rules).with(network_access_allowed: true)
|
||||
expect(sandbox).to receive(:run)
|
||||
|
||||
Cask::Installer.new(cask, command: NeverSudoSystemCommand).install_artifacts
|
||||
end
|
||||
|
||||
context "when install steps may require sudo" do
|
||||
{
|
||||
"an explicitly privileged command" => proc { run "/usr/bin/true", sudo: true },
|
||||
|
||||
@@ -659,7 +659,7 @@ Relative paths default to `staged_path` for `base:`, `source_base:` and `target_
|
||||
|
||||
Use `if_path_exists`, `unless_path_exists`, `on_macos` and `on_linux` blocks to guard one or more steps. A condition is evaluated once when its scope begins, so related steps make the same decision. Use `unless_path_exists` around `write_file` when an existing file must be preserved.
|
||||
|
||||
`run` does not evaluate a shell command string. It supports a literal `env:`, `stdin_path:`, `stdout_path:`, `chdir:` and `sudo:`. Standard output is hidden by default and standard error is printed; use `print_stdout: true` or `print_stderr: false` to change that behaviour. Failure aborts the installation or uninstallation; pass `must_succeed: false` when a non-zero exit status is expected and should be ignored, such as a cleanup command that cannot run when its dependency is absent. Nothing is written to `stdout_path:` when an ignored command fails. The complete steps block runs in an isolated Ruby subprocess without network or general home-directory access where Homebrew has an available sandbox. The sandbox permits writes to declared step destinations, the caskroom, app directory, temporary and cache directories and Homebrew's link directories. Ruby file operations and system or cask-provided commands therefore share the same restrictions. Use `writable_paths:` with directory roots, and `writable_base:` for relative roots, when an opaque command needs another declared write location.
|
||||
`run` does not evaluate a shell command string. It supports a literal `env:`, `stdin_path:`, `stdout_path:`, `chdir:` and `sudo:`. Standard output is hidden by default and standard error is printed; use `print_stdout: true` or `print_stderr: false` to change that behaviour. Failure aborts the installation or uninstallation; pass `must_succeed: false` when a non-zero exit status is expected and should be ignored, such as a cleanup command that cannot run when its dependency is absent. Nothing is written to `stdout_path:` when an ignored command fails. The complete steps block runs in an isolated Ruby subprocess without network or general home-directory access where Homebrew has an available sandbox. Pass `network_access: true` when a `run` command must access the network. The sandbox permits writes to declared step destinations, the caskroom, app directory, temporary and cache directories and Homebrew's link directories. Ruby file operations and system or cask-provided commands therefore share the same restrictions. Use `writable_paths:` with directory roots, and `writable_base:` for relative roots, when an opaque command needs another declared write location.
|
||||
|
||||
#### Interpolation in steps blocks
|
||||
|
||||
|
||||
Reference in New Issue
Block a user