diff --git a/Library/Homebrew/cask/lib/hbc/audit.rb b/Library/Homebrew/cask/lib/hbc/audit.rb index 12cefb9395..cee1fe8070 100644 --- a/Library/Homebrew/cask/lib/hbc/audit.rb +++ b/Library/Homebrew/cask/lib/hbc/audit.rb @@ -1,16 +1,18 @@ require "hbc/checkable" require "hbc/download" require "digest" +require "utils/git" module Hbc class Audit include Checkable - attr_reader :cask, :download + attr_reader :cask, :commit_range, :download - def initialize(cask, download: false, check_token_conflicts: false, command: SystemCommand) + def initialize(cask, download: false, check_token_conflicts: false, commit_range: nil, command: SystemCommand) @cask = cask @download = download + @commit_range = commit_range @check_token_conflicts = check_token_conflicts @command = command end @@ -21,6 +23,7 @@ module Hbc def run! check_required_stanzas + check_version_and_checksum check_version check_sha256 check_appcast @@ -57,6 +60,24 @@ module Hbc add_error "at least one activatable artifact stanza is required" if installable_artifacts.empty? end + def check_version_and_checksum + return if @cask.sourcefile_path.nil? + + tap = Tap.select { |t| t.cask_file?(@cask.sourcefile_path) }.first + return if tap.nil? + + return if commit_range.nil? + previous_cask_contents = Git.last_revision_of_file(tap.path, @cask.sourcefile_path, before_commit: commit_range) + return if previous_cask_contents.empty? + + previous_cask = CaskLoader.load_from_string(previous_cask_contents) + + return unless previous_cask.version == cask.version + return if previous_cask.sha256 == cask.sha256 + + add_error "only sha256 changed (see: https://github.com/caskroom/homebrew-cask/blob/master/doc/cask_language_reference/stanzas/sha256.md)" + end + def check_version return unless cask.version check_no_string_version_latest diff --git a/Library/Homebrew/cask/lib/hbc/auditor.rb b/Library/Homebrew/cask/lib/hbc/auditor.rb index ec17f3cad6..48f36a54d5 100644 --- a/Library/Homebrew/cask/lib/hbc/auditor.rb +++ b/Library/Homebrew/cask/lib/hbc/auditor.rb @@ -1,14 +1,15 @@ module Hbc class Auditor - def self.audit(cask, audit_download: false, check_token_conflicts: false) - new(cask, audit_download, check_token_conflicts).audit + def self.audit(cask, audit_download: false, check_token_conflicts: false, commit_range: nil) + new(cask, audit_download, check_token_conflicts, commit_range).audit end - attr_reader :cask + attr_reader :cask, :commit_range - def initialize(cask, audit_download, check_token_conflicts) + def initialize(cask, audit_download, check_token_conflicts, commit_range) @cask = cask @audit_download = audit_download + @commit_range = commit_range @check_token_conflicts = check_token_conflicts end @@ -50,7 +51,8 @@ module Hbc def audit_cask_instance(cask) download = audit_download? && Download.new(cask) audit = Audit.new(cask, download: download, - check_token_conflicts: check_token_conflicts?) + check_token_conflicts: check_token_conflicts?, + commit_range: commit_range) audit.run! puts audit.summary audit.success? diff --git a/Library/Homebrew/cask/lib/hbc/cli/internal_audit_modified_casks.rb b/Library/Homebrew/cask/lib/hbc/cli/internal_audit_modified_casks.rb index 9467cccc71..1a8ca0e985 100644 --- a/Library/Homebrew/cask/lib/hbc/cli/internal_audit_modified_casks.rb +++ b/Library/Homebrew/cask/lib/hbc/cli/internal_audit_modified_casks.rb @@ -97,7 +97,8 @@ module Hbc audit_download = audit_download?(cask, cask_file) check_token_conflicts = added_cask_files.include?(cask_file) success = Auditor.audit(cask, audit_download: audit_download, - check_token_conflicts: check_token_conflicts) + check_token_conflicts: check_token_conflicts, + commit_range: commit_range) failed_casks << cask unless success end diff --git a/Library/Homebrew/dev-cmd/audit.rb b/Library/Homebrew/dev-cmd/audit.rb index 3c42b45a1d..516388c688 100644 --- a/Library/Homebrew/dev-cmd/audit.rb +++ b/Library/Homebrew/dev-cmd/audit.rb @@ -746,6 +746,15 @@ class FormulaAuditor return if @new_formula fv = FormulaVersions.new(formula) + + previous_version_and_checksum = fv.previous_version_and_checksum("origin/master") + [:stable, :devel].each do |spec_sym| + next unless spec = formula.send(spec_sym) + next unless previous_version_and_checksum[spec_sym][:version] == spec.version + next if previous_version_and_checksum[spec_sym][:checksum] == spec.checksum + problem "#{spec_sym}: sha256 changed without the version also changing; please create an issue upstream to rule out malicious circumstances and to find out why the file changed." + end + attributes = [:revision, :version_scheme] attributes_map = fv.version_attributes_map(attributes, "origin/master") diff --git a/Library/Homebrew/formula_versions.rb b/Library/Homebrew/formula_versions.rb index 70706a2f01..5c4e0ecc96 100644 --- a/Library/Homebrew/formula_versions.rb +++ b/Library/Homebrew/formula_versions.rb @@ -63,6 +63,26 @@ class FormulaVersions map end + def previous_version_and_checksum(branch) + map = {} + + rev_list(branch) do |rev| + formula_at_revision(rev) do |f| + [:stable, :devel].each do |spec_sym| + next unless spec = f.send(spec_sym) + map[spec_sym] ||= { version: spec.version, checksum: spec.checksum } + end + + break if map[:stable] && map[:devel] + end + end + + map[:stable] ||= {} + map[:devel] ||= {} + + map + end + def version_attributes_map(attributes, branch) attributes_map = {} return attributes_map if attributes.empty? diff --git a/Library/Homebrew/utils/git.rb b/Library/Homebrew/utils/git.rb index 1b4d248946..43d93b64ec 100644 --- a/Library/Homebrew/utils/git.rb +++ b/Library/Homebrew/utils/git.rb @@ -1,3 +1,31 @@ +require "open3" + +module Git + module_function + + def last_revision_commit_of_file(repo, file, before_commit: nil) + args = [before_commit.nil? ? "--skip=1" : before_commit.split("..").first] + + out, = Open3.capture3( + HOMEBREW_SHIMS_PATH/"scm/git", "-C", repo, + "log", "--oneline", "--max-count=1", *args, "--", file + ) + out.split(" ").first + end + + def last_revision_of_file(repo, file, before_commit: nil) + relative_file = Pathname(file).relative_path_from(repo) + + commit_hash = last_revision_commit_of_file(repo, file, before_commit: before_commit) + + out, = Open3.capture3( + HOMEBREW_SHIMS_PATH/"scm/git", "-C", repo, + "show", "#{commit_hash}:#{relative_file}" + ) + out + end +end + module Utils def self.git_available? return @git if instance_variable_defined?(:@git)