Class: Sandbox Private

Inherits:
Object show all
Extended by:
Utils::Output::Mixin
Includes:
OS::Linux::Sandbox, OS::Mac::Sandbox, Utils::Output::Mixin
Defined in:
sandbox.rb,
extend/os/linux/sandbox/backend.rb,
extend/os/linux/sandbox/landlock.rb,
extend/os/linux/sandbox/bubblewrap.rb

Overview

This class is part of a private API. This class may only be used in the Homebrew/brew repository. Third parties should avoid using this class if possible, as it may be removed or changed without warning.

Helper class for running a sub-process inside of a sandboxed environment.

Defined Under Namespace

Classes: Bubblewrap, Landlock, LinuxBackend

Constant Summary collapse

PRIVILEGED_GROUPS =

This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.

Privileged groups that are expected to be able to use a working sandbox.

%w[admin staff root wheel].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils::Output::Mixin

issue_reporting_message, odebug, odeprecated, odie, odisabled, ofail, oh1, oh1_title, ohai, ohai_title, onoe, opoo, opoo_outside_github_actions, opoo_without_github_actions_annotation, pretty_deprecated, pretty_disabled, pretty_duration, pretty_install_status, pretty_installed, pretty_uninstalled, pretty_unmarked, pretty_upgradable, pretty_warning

Methods included from OS::Linux::Sandbox

#bubblewrap_args, bubblewrap_candidate_paths, bubblewrap_executable, bubblewrap_executable!, landlock?, sandbox_implementation, #writable_paths

Constructor Details

#initializevoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.



230
231
232
233
234
235
# File 'sandbox.rb', line 230

def initialize
  @profile = T.let(SandboxProfile.new, SandboxProfile)
  @failed = T.let(false, T::Boolean)
  @logfile = T.let(nil, T.nilable(T.any(String, Pathname)))
  @start = T.let(nil, T.nilable(Time))
end

Instance Attribute Details

#profileSandboxProfile (readonly)

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (SandboxProfile)


599
600
601
# File 'sandbox.rb', line 599

def profile
  @profile
end

Class Method Details

.available?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


79
80
81
# File 'sandbox.rb', line 79

def self.available?
  false
end

.avoid_nested_sandboxing?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Skip Homebrew's own sandbox when it is opted into via $HOMEBREW_AVOID_NESTED_SANDBOXING and already running inside another sandbox. The skip is only supported for an unprivileged user in a custom prefix; error out explaining why rather than silently sandboxing (and hanging) when either is not the case.

Returns:

  • (Boolean)


98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'sandbox.rb', line 98

def self.avoid_nested_sandboxing?
  return false unless Homebrew::EnvConfig.avoid_nested_sandboxing?
  return false unless nested_sandbox?

  if Homebrew.default_prefix?
    odie "Refusing to skip the sandbox: `$HOMEBREW_AVOID_NESTED_SANDBOXING` is set " \
         "inside another sandbox but Homebrew is using its default prefix " \
         "(#{HOMEBREW_PREFIX}); this is only supported in a custom prefix."
  end

  privileged_group = PRIVILEGED_GROUPS.find do |name|
    group = Etc.getgrnam(name)
    group && Process.groups.include?(group.gid)
  rescue ArgumentError
    false
  end
  if privileged_group
    odie "Refusing to skip the sandbox: `$HOMEBREW_AVOID_NESTED_SANDBOXING` is set " \
         "inside another sandbox but you are in the privileged `#{privileged_group}` " \
         "group; this is only supported for an unprivileged user."
  end

  true
end

.configuration_command_messagesArray<String>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



152
# File 'sandbox.rb', line 152

def self.configuration_command_messages = []

.configuration_commandsArray<String>

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



149
# File 'sandbox.rb', line 149

def self.configuration_commands = []

.configure!void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



158
159
160
161
# File 'sandbox.rb', line 158

def self.configure!
  ensure_sandbox_installed!
  reset_state!
end

.ensure_sandbox_available!void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



127
128
129
130
131
# File 'sandbox.rb', line 127

def self.ensure_sandbox_available!
  return if available?

  raise failure_reason || "The sandbox is not available."
end

.ensure_sandbox_installed!(install_from_tests: false) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:

  • install_from_tests (Boolean) (defaults to: false)


124
# File 'sandbox.rb', line 124

def self.ensure_sandbox_installed!(install_from_tests: false); end

.executablePathname?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'sandbox.rb', line 197

def self.executable
  executable_candidate_paths.each do |path|
    begin
      candidate = Pathname.new(File.expand_path(executable_name, path))
    rescue ArgumentError
      next
    end

    next if !candidate.file? || !candidate.executable?
    next unless executable_usable?(candidate)

    return candidate
  end

  nil
end

.executable!Pathname

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



215
216
217
# File 'sandbox.rb', line 215

def self.executable!
  executable || raise("#{executable_name} is required to use the sandbox.")
end

.executable_candidate_paths::PATH

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



189
190
191
192
193
194
# File 'sandbox.rb', line 189

def self.executable_candidate_paths
  executable_path = Pathname.new(executable_name)
  return PATH.new(executable_path.dirname) if executable_path.absolute?

  PATH.new(ORIGINAL_PATHS, ENV.fetch("PATH"), HOMEBREW_ORIGINAL_BREW_FILE.dirname)
end

.executable_nameString

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

Raises:

  • (NotImplementedError)


184
185
186
# File 'sandbox.rb', line 184

def self.executable_name
  raise NotImplementedError, "Sandbox is not implemented for this OS."
end

.executable_usable?(_candidate) ⇒ Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (Boolean)


220
221
222
# File 'sandbox.rb', line 220

def self.executable_usable?(_candidate)
  true
end

.failure_reasonString?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



139
140
141
142
143
# File 'sandbox.rb', line 139

def self.failure_reason
  return if state == :available

  "The sandbox is not available."
end

.full_write_isolation?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

  • (Boolean)


84
# File 'sandbox.rb', line 84

def self.full_write_isolation? = true

.nested_sandbox?Boolean

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Whether Homebrew is itself running inside another sandbox, which would make its own nested sandbox hang (macOS) or fail to start (Linux). Overridden per-OS.

Returns:

  • (Boolean)


90
# File 'sandbox.rb', line 90

def self.nested_sandbox? = false

.reset_state!void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



146
# File 'sandbox.rb', line 146

def self.reset_state!; end

.run_command(*command, writable_path:, deny_network: false) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'sandbox.rb', line 164

def self.run_command(*command, writable_path:, deny_network: false)
  ensure_sandbox_installed!
  ensure_sandbox_available!

  writable_path = Pathname(writable_path).expand_path
  if !writable_path.directory? || !writable_path.writable?
    raise UsageError,
          "`#{writable_path}` is not a writable directory."
  end

  writable_path = writable_path.realpath
  sandbox = new
  sandbox.allow_write_temp_and_cache
  sandbox.allow_write_path writable_path
  sandbox.deny_read_home
  sandbox.deny_all_network if deny_network
  sandbox.run "/bin/sh", "-c", "cd \"$1\" && shift && exec \"$@\"", "brew-sandbox-exec", writable_path, *command
end

.sandbox_install_commandString?

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



155
# File 'sandbox.rb', line 155

def self.sandbox_install_command = nil

.stateSymbol

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:



134
135
136
# File 'sandbox.rb', line 134

def self.state
  available? ? :available : :unavailable
end

.terminal_ioctl_requestInteger

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Returns:

Raises:

  • (NotImplementedError)


225
226
227
# File 'sandbox.rb', line 225

def self.terminal_ioctl_request
  raise NotImplementedError, "Sandbox is not implemented for this OS."
end

Instance Method Details

#add_rule(allow:, operation:, filter: nil, modifier: nil) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:

  • allow (Boolean)
  • operation (String)
  • filter (SandboxPathFilter, nil) (defaults to: nil)
  • modifier (String, nil) (defaults to: nil)


246
247
248
249
# File 'sandbox.rb', line 246

def add_rule(allow:, operation:, filter: nil, modifier: nil)
  rule = SandboxRule.new(allow:, operation:, filter:, modifier:)
  @profile.add_rule(rule)
end

#allow_cvsvoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



438
439
440
# File 'sandbox.rb', line 438

def allow_cvs
  allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.cvspass"
end

#allow_fossilvoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



443
444
445
446
# File 'sandbox.rb', line 443

def allow_fossil
  allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.fossil"
  allow_write_path "#{Dir.home(ENV.fetch("USER"))}/.fossil-journal"
end

#allow_network(path:, type: :literal) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



475
476
477
# File 'sandbox.rb', line 475

def allow_network(path:, type: :literal)
  add_rule allow: true, operation: "network*", filter: path_filter(path, type)
end

#allow_read(path:, type: :literal) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



252
253
254
# File 'sandbox.rb', line 252

def allow_read(path:, type: :literal)
  add_rule allow: true, operation: "file-read*", filter: path_filter(path, type)
end

#allow_read_if_exists(path:, type: :literal) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



394
395
396
397
398
399
# File 'sandbox.rb', line 394

def allow_read_if_exists(path:, type: :literal)
  return unless path
  return unless File.exist?(path)

  allow_read path:, type:
end

#allow_write(path:, type: :literal) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



402
403
404
405
406
# File 'sandbox.rb', line 402

def allow_write(path:, type: :literal)
  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)
  add_rule allow: true, operation: "file-write-mode", filter: path_filter(path, type)
end

#allow_write_cellar(formula) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



449
450
451
452
453
# File 'sandbox.rb', line 449

def allow_write_cellar(formula)
  allow_write_path formula.rack
  allow_write_path formula.etc
  allow_write_path formula.var
end

#allow_write_log(formula) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



459
460
461
# File 'sandbox.rb', line 459

def allow_write_log(formula)
  allow_write_path formula.logs
end

#allow_write_path(path) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



414
415
416
# File 'sandbox.rb', line 414

def allow_write_path(path)
  allow_write path:, type: :subpath
end

#allow_write_path_if_exists(path) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



419
420
421
422
423
424
# File 'sandbox.rb', line 419

def allow_write_path_if_exists(path)
  return unless path
  return unless File.exist?(path)

  allow_write_path path
end

#allow_write_temp_and_cachevoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



432
433
434
435
# File 'sandbox.rb', line 432

def allow_write_temp_and_cache
  allow_write_path HOMEBREW_TEMP
  allow_write_path HOMEBREW_CACHE
end

#allow_write_xcodevoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



456
# File 'sandbox.rb', line 456

def allow_write_xcode; end

#copy_pty_output(controller) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:

  • controller (IO)


602
603
604
605
606
607
# File 'sandbox.rb', line 602

def copy_pty_output(controller)
  controller.each_char { |c| print(c) }
rescue Errno::EIO
  # Linux marks a PTY as an I/O error when its peer closes, so treat this as EOF:
  # https://github.com/torvalds/linux/blob/master/drivers/tty/pty.c
end

#deny_all_networkvoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



480
481
482
# File 'sandbox.rb', line 480

def deny_all_network
  add_rule allow: false, operation: "network*"
end

#deny_read(path:, type: :literal) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



257
258
259
# File 'sandbox.rb', line 257

def deny_read(path:, type: :literal)
  add_rule allow: false, operation: "file-read*", filter: path_filter(path, type)
end

#deny_read_homevoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'sandbox.rb', line 267

def deny_read_home
  require "trust"

  home = Pathname(Dir.home(ENV.fetch("USER"))).realpath
  readable_paths = [
    HOMEBREW_PREFIX,
    HOMEBREW_REPOSITORY,
    HOMEBREW_CACHE,
    HOMEBREW_LOGS,
    HOMEBREW_TEMP,
    ENV.fetch("GITHUB_WORKSPACE", nil),
    ENV.fetch("RUNNER_WORKSPACE", nil),
    ENV.fetch("RUNNER_TEMP", nil),
    Homebrew::Trust.trust_file,
    *home_write_paths.select { |path| File.exist?(path) },
  ].compact.flat_map do |path|
    path = Pathname(path)
    [path.expand_path, (path.realpath if path.exist?)].compact
  end
  if readable_paths.any? { |path| path.ascend.include?(home) }
    # When Homebrew or CI needs some `$HOME` paths to stay readable, deny only
    # well-known credential and personal-data paths instead of enumerating all
    # of `$HOME`.
    [
      ".ssh",
      ".aws",
      ".azure",
      ".boto",
      ".docker",
      ".config/fish",
      ".config/gh",
      ".config/gcloud",
      ".config/huggingface",
      ".config/pip",
      ".config/pypoetry",
      ".config/rclone",
      ".config/containers/auth.json",
      ".config/composer/auth.json",
      ".config/sops/age/keys.txt",
      ".gnupg",
      ".git-credentials",
      ".gitconfig",
      ".gsutil",
      ".kube",
      ".netrc",
      ".npmrc",
      ".yarnrc",
      ".yarnrc.yml",
      ".pnpmrc",
      ".bunfig.toml",
      ".pypirc",
      ".pip",
      ".poetry",
      ".local/share/pypoetry",
      ".gem/credentials",
      ".bundle/config",
      ".cargo/credentials",
      ".cargo/credentials.toml",
      ".composer/auth.json",
      ".condarc",
      ".m2/settings.xml",
      ".gradle/gradle.properties",
      ".sbt/1.0/credentials.sbt",
      ".terraform.d/credentials.tfrc.json",
      ".pulumi/credentials.json",
      ".oci/config",
      ".huggingface/token",
      ".cache/huggingface/token",
      ".claude",
      ".claude.json",
      ".kiro",
      ".bash_login",
      ".bash_logout",
      ".bash_profile",
      ".bashrc",
      ".bash_history",
      ".profile",
      ".zlogin",
      ".zlogout",
      ".zprofile",
      ".zshenv",
      ".zshrc",
      ".zsh_history",
      ".python_history",
      ".mysql_history",
      ".psql_history",
      ".env",
      ".env.local",
      "Documents",
      "Movies",
      "Music",
      "Pictures",
      "Library/Keychains",
      "Library/Mobile Documents",
      "Library/CloudStorage",
      "Dropbox",
      "Google Drive",
      "OneDrive",
    ].each do |path|
      path = home/path
      next unless path.exist?

      path = path.realpath
      next unless path.ascend.include?(home)

      if (readable_path = readable_paths.find { |required_path| required_path.ascend.include?(path) })
        opoo <<~EOS
          The sandbox cannot prevent formulae from reading:
            #{path}
          because this required path is inside it:
            #{readable_path}
          Formulae may access personal data in this directory.
        EOS
        next
      end

      deny_read_path path
    rescue Errno::ENOENT
      nil
    end
    return
  end

  deny_read_path home
end

#deny_read_path(path) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



262
263
264
# File 'sandbox.rb', line 262

def deny_read_path(path)
  deny_read path:, type: :subpath
end

#deny_write(path:, type: :literal) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



409
410
411
# File 'sandbox.rb', line 409

def deny_write(path:, type: :literal)
  add_rule allow: false, operation: "file-write*", filter: path_filter(path, type)
end

#deny_write_homebrew_repositoryvoid

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.



464
465
466
467
468
469
470
471
472
# File 'sandbox.rb', line 464

def deny_write_homebrew_repository
  deny_write path: HOMEBREW_ORIGINAL_BREW_FILE
  if HOMEBREW_PREFIX.to_s == HOMEBREW_REPOSITORY.to_s
    deny_write_path HOMEBREW_LIBRARY
    deny_write_path HOMEBREW_REPOSITORY/".git"
  else
    deny_write_path HOMEBREW_REPOSITORY
  end
end

#deny_write_path(path) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



427
428
429
# File 'sandbox.rb', line 427

def deny_write_path(path)
  deny_write path:, type: :subpath
end

#path_filter(path, type) ⇒ SandboxPathFilter

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

Parameters:

Returns:

  • (SandboxPathFilter)


583
584
585
586
587
588
589
590
591
592
593
594
595
596
# File 'sandbox.rb', line 583

def path_filter(path, type)
  # Any character is allowed: the OS-specific renderer quotes paths safely
  # (the seatbelt renderer escapes the `"` and `\` string delimiters; the
  # Linux sandbox passes each path as a separate argument), so even paths
  # with spaces, parentheses, quotes, backslashes or newlines are expressible.
  filter_path = case type
  when :regex   then path.to_s
  when :subpath, :literal
    expand_realpath(Pathname.new(path)).to_s
  else raise ArgumentError, "Invalid path filter type: #{type}"
  end

  SandboxPathFilter.new(path: filter_path, type:)
end

#record_log(file) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



238
239
240
# File 'sandbox.rb', line 238

def record_log(file)
  @logfile = file
end

#run(*args) ⇒ void

This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.

This method returns an undefined value.

Parameters:



485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
# File 'sandbox.rb', line 485

def run(*args)
  Dir.mktmpdir("homebrew-sandbox", HOMEBREW_TEMP) do |tmpdir|
    allow_network path: File.join(tmpdir, "socket"), type: :literal if allow_network_for_error_pipe?
    @start = T.let(Time.now, T.nilable(Time))

    begin
      command = sandbox_command(args, tmpdir)
      # Start sandbox in a pseudoterminal to prevent access of the parent terminal.
      PTY.open do |controller, worker|
        # Set the PTY's window size to match the parent terminal.
        # Some formula tests are sensitive to the terminal size and fail if this is not set.
        winch = proc do |_sig|
          controller.winsize = if $stdout.tty?
            # We can only use IO#winsize if the IO object is a TTY.
            $stdout.winsize
          else
            # Otherwise, default to tput, if available.
            # This relies on ncurses rather than the system's ioctl.
            [Utils.popen_read("tput", "lines").to_i, Utils.popen_read("tput", "cols").to_i]
          end
        end

        write_to_pty = proc do
          # Don't hang if stdin is not able to be used - throw EIO instead.
          old_ttin = trap(:TTIN, "IGNORE")

          # Update the window size whenever the parent terminal's window size changes.
          old_winch = trap(:WINCH, &winch)
          winch.call(nil)

          stdin_thread = Thread.new do
            IO.copy_stream($stdin, controller)
          rescue Errno::EIO
            # stdin is unavailable - move on.
          end

          stdout_thread = Thread.new do
            copy_pty_output(controller)
          end

          Utils.safe_fork(directory: tmpdir, yield_parent: true) do |error_pipe|
            if error_pipe
              # Child side
              Process.setsid
              controller.close
              worker.ioctl(self.class.terminal_ioctl_request, 0) # Make this the controlling terminal.

              ensure_child_tty_available

              # Move into a non-denied directory before `exec` so subsequent
              # `getcwd(3)` calls (which walk every parent) never cross a
              # `deny_read_home` path inherited from the caller's CWD.
              Dir.chdir(tmpdir)

              worker.close_on_exec = true
              apply_sandbox
              exec(*command, in: worker, out: worker, err: worker) # And map everything to the PTY.
            else
              # Parent side
              worker.close
            end
          end
        rescue ChildProcessError => e
          raise ErrorDuringExecution.new(command, status: e.status)
        ensure
          stdin_thread&.kill
          stdout_thread&.kill
          trap(:TTIN, old_ttin)
          trap(:WINCH, old_winch)
        end

        if $stdin.tty?
          # If stdin is a TTY, use io.raw to set stdin to a raw, passthrough
          # mode while we copy the input/output of the process spawned in the
          # PTY. After we've finished copying to/from the PTY process, io.raw
          # will restore the stdin TTY to its original state.
          begin
            # Ignore SIGTTOU as setting raw mode will hang if the process is in the background.
            old_ttou = trap(:TTOU, "IGNORE")
            $stdin.raw(&write_to_pty)
          ensure
            trap(:TTOU, old_ttou)
          end
        else
          write_to_pty.call
        end
      end
    rescue
      @failed = true
      raise
    ensure
      record_sandbox_log
    end
  end
end