mirror of
https://github.com/Homebrew/brew.git
synced 2026-08-12 22:29:27 +04:00
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.
116 lines
4.0 KiB
Ruby
116 lines
4.0 KiB
Ruby
# typed: strict
|
|
# frozen_string_literal: true
|
|
|
|
require "cask/artifact/abstract_artifact"
|
|
require "install_steps"
|
|
require "keg"
|
|
|
|
module Cask
|
|
module Artifact
|
|
# Abstract superclass for install steps artifacts.
|
|
class AbstractInstallSteps < AbstractArtifact
|
|
abstract!
|
|
|
|
sig { params(cask: Cask, steps: Homebrew::InstallSteps::Steps).void }
|
|
def initialize(cask, steps)
|
|
super
|
|
@steps = T.let(Homebrew::InstallSteps::DSL.normalise_steps(steps), Homebrew::InstallSteps::Steps)
|
|
end
|
|
|
|
sig { returns(Homebrew::InstallSteps::Steps) }
|
|
attr_reader :steps
|
|
|
|
sig { override.returns(T::Array[T.anything]) }
|
|
def to_args = [{ steps: }]
|
|
|
|
sig { override.returns(String) }
|
|
def summarize
|
|
::Utils.pluralize("install step", steps.length, include_count: true)
|
|
end
|
|
|
|
private
|
|
|
|
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(network_access_allowed: steps.any? do |step|
|
|
step["type"] == "run" && step["network_access"] == true
|
|
end)
|
|
unless sandbox
|
|
runner.run(steps, phase:)
|
|
return
|
|
end
|
|
|
|
sandbox.allow_write_path cask.caskroom_path
|
|
sandbox.allow_write_path cask.config.appdir
|
|
sandbox.allow_process_exec "/usr/bin/sudo", no_sandbox: true if runner.sudo_required?(steps)
|
|
Keg.keg_link_directories.each { |directory| sandbox.allow_write_path HOMEBREW_PREFIX/directory }
|
|
original_home = Pathname(Dir.home).expand_path
|
|
runner.sandbox_write_paths(steps, phase:).each do |path|
|
|
sandbox.allow_write_path path
|
|
sandbox.allow_read(path:, type: :subpath) if path.expand_path.ascend.include?(original_home)
|
|
end
|
|
run_cask_sandbox(
|
|
sandbox,
|
|
{
|
|
"action" => "install_steps",
|
|
"context" => {
|
|
"name" => cask.name,
|
|
"token" => cask.token,
|
|
"version" => cask.version.to_s,
|
|
"staged_path" => cask.staged_path.to_s,
|
|
"caskroom_path" => cask.caskroom_path.to_s,
|
|
"home" => Dir.home,
|
|
"config" => cask.config.to_json,
|
|
},
|
|
"phase" => phase.to_s,
|
|
"steps" => steps,
|
|
},
|
|
)
|
|
end
|
|
end
|
|
|
|
# Artifact corresponding to the `preflight_steps` stanza.
|
|
class PreflightSteps < AbstractInstallSteps
|
|
sig { params(command: T.class_of(SystemCommand), _options: T.anything).void }
|
|
def install_phase(command: SystemCommand, **_options)
|
|
run_steps(command)
|
|
end
|
|
|
|
sig { params(command: T.class_of(SystemCommand), _options: T.anything).void }
|
|
def uninstall_phase(command: SystemCommand, **_options)
|
|
run_steps(command, phase: :uninstall)
|
|
end
|
|
end
|
|
|
|
# Artifact corresponding to the `postflight_steps` stanza.
|
|
class PostflightSteps < AbstractInstallSteps
|
|
sig { params(command: T.class_of(SystemCommand), _options: T.anything).void }
|
|
def install_phase(command: SystemCommand, **_options)
|
|
run_steps(command)
|
|
end
|
|
|
|
sig { params(command: T.class_of(SystemCommand), _options: T.anything).void }
|
|
def uninstall_phase(command: SystemCommand, **_options)
|
|
run_steps(command, phase: :uninstall)
|
|
end
|
|
end
|
|
|
|
# Artifact corresponding to the `uninstall_preflight_steps` stanza.
|
|
class UninstallPreflightSteps < AbstractInstallSteps
|
|
sig { params(command: T.class_of(SystemCommand), _options: T.anything).void }
|
|
def uninstall_phase(command: SystemCommand, **_options)
|
|
run_steps(command)
|
|
end
|
|
end
|
|
|
|
# Artifact corresponding to the `uninstall_postflight_steps` stanza.
|
|
class UninstallPostflightSteps < AbstractInstallSteps
|
|
sig { params(command: T.class_of(SystemCommand), _options: T.anything).void }
|
|
def uninstall_phase(command: SystemCommand, **_options)
|
|
run_steps(command)
|
|
end
|
|
end
|
|
end
|
|
end
|