Class: Homebrew::DevCmd::AdvisoryMatch::DirEmitter Private
- Defined in:
- dev-cmd/advisory-match.rb
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.
Instance Method Summary collapse
- #affected_formula_names(record) ⇒ Array<String> private
- #alias_protected?(record_id) ⇒ Boolean private
- #alias_record(path) ⇒ T.untyped private
- #alias_target_paths(record_id) ⇒ Array<String> private
- #emit(record, revalidated: false, initial_introduction: false) ⇒ void private
- #ensure_alias_index ⇒ void private
- #finish ⇒ void private
- #fixed_boundary_valid?(record_id, boundary) ⇒ Boolean private
- #history_required?(record_id) ⇒ Boolean private
- #homebrew_ranges(ranges) ⇒ Array<T.untyped> private
- #initialize(dir, verbose:, close_open_ranges:, reconcile_history: false, formula_names: nil) ⇒ void constructor private
- #matching_alias_paths(record_ids) ⇒ Array<String> private
- #prepare_aliases(formula_name, groups) ⇒ Array<String> private
-
#provenance_matches?(existing, record) ⇒ Boolean
private
The stored record must still be reached through the same upstream identifiers with evidence and a range basis the candidate reproduces.
-
#range_basis(record) ⇒ Hash{String => T.untyped}
private
Keep every strategy and runtime subject.
- #range_basis_changed?(record) ⇒ Boolean private
-
#reconcilable_record?(existing) ⇒ Boolean
private
Only matched, bump-fixed records are reconciled; generated patch fixes and withdrawn records keep their annotation-based ranges.
- #reconcile(record, result) ⇒ void private
-
#reconciliation_record(record) ⇒ Hash{String => T.untyped}?
private
Reconciliation never creates a record or changes its matching provenance.
-
#reconciliation_resource_records(formula_name) ⇒ Array<Homebrew::Vulns::Match::ResourceRecord>
private
Only pure registry-resource records can supply fallback query hints.
- #record_history_unavailable(formula_name) ⇒ void private
- #record_history_walk ⇒ void private
- #record_path(record_id) ⇒ String private
- #record_upstream_unavailable(formula_name) ⇒ void private
- #reintroduction_boundary_valid?(record_id, boundary) ⇒ Boolean private
- #reviewed_range_state(record_id) ⇒ Symbol? private
- #reviewed_states(path) ⇒ Array<Symbol>? private
-
#single_terminal_range?(existing) ⇒ Boolean
private
Exactly one ECOSYSTEM range holding an
introducedand afixedevent, each a non-blank string. - #skip_reconciliation(record_id, reasons) ⇒ void private
- #subject_identity(evidence) ⇒ Array<String> private
- #unversioned_key(key) ⇒ String private
Constructor Details
#initialize(dir, verbose:, close_open_ranges:, reconcile_history: false, formula_names: 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.
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 |
# File 'dev-cmd/advisory-match.rb', line 514 def initialize(dir, verbose:, close_open_ranges:, reconcile_history: false, formula_names: nil) super() FileUtils.mkdir_p(dir) @dir = dir @verbose = verbose @close_open_ranges = close_open_ranges @reconcile_history = reconcile_history @formula_names = formula_names @deleted = T.let(0, Integer) @reconciliation_skips = T.let({}, T::Hash[Symbol, Integer]) @reconciliation_seen = T.let({}, T::Hash[String, T::Boolean]) @written = T.let(0, Integer) @unchanged = T.let(0, Integer) @skipped_generated = T.let(0, Integer) @basis_changed = T.let(0, Integer) @history_walks = T.let(0, Integer) @history_unavailable_by_formula = T.let({}, T::Hash[String, Integer]) @alias_targets = T.let({}, T::Hash[String, T::Array[String]]) @protected_aliases = T.let({}, T::Hash[String, T::Boolean]) @alias_records = T.let({}, T::Hash[String, T.untyped]) @identity_paths = T.let({}, T::Hash[String, T::Array[String]]) @path_identities = T.let({}, T::Hash[String, T::Array[String]]) @alias_index_loaded = T.let(false, T::Boolean) end |
Instance Method Details
#affected_formula_names(record) ⇒ Array<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.
1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 |
# File 'dev-cmd/advisory-match.rb', line 1047 def affected_formula_names(record) Array(record["affected"]).filter_map do |entry| next unless entry.is_a?(Hash) package = entry["package"] next unless package.is_a?(Hash) next if package["ecosystem"] != Homebrew::Vulns::OsvExport::ECOSYSTEM package["name"] end.uniq end |
#alias_protected?(record_id) ⇒ 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.
637 638 639 |
# File 'dev-cmd/advisory-match.rb', line 637 def alias_protected?(record_id) @protected_aliases.fetch(record_id, false) end |
#alias_record(path) ⇒ T.untyped
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.
1038 1039 1040 1041 1042 1043 1044 |
# File 'dev-cmd/advisory-match.rb', line 1038 def alias_record(path) return @alias_records[path] if @alias_records.key?(path) @alias_records[path] = JSON.parse(File.read(path)) rescue JSON::ParserError @alias_records[path] = :malformed end |
#alias_target_paths(record_id) ⇒ Array<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.
1033 1034 1035 |
# File 'dev-cmd/advisory-match.rb', line 1033 def alias_target_paths(record_id) @alias_targets.fetch(record_id) { [record_path(record_id)] } end |
#emit(record, revalidated: false, initial_introduction: 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.
828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 |
# File 'dev-cmd/advisory-match.rb', line 828 def emit(record, revalidated: false, initial_introduction: false) updates = alias_target_paths(record.fetch(:id)).filter_map do |path| candidate = record.deep_dup existing = alias_record(path) if File.file?(path) if existing.is_a?(Hash) candidate[:id] = existing.fetch("id") candidate[:upstream] = (Array(existing["upstream"]) + Array(candidate[:upstream])).uniq existing_basis = range_basis(existing) candidate_basis = range_basis(candidate) if existing_basis != candidate_basis ranges = JSON.parse(JSON.generate(candidate)).dig("affected", 0, "ranges") existing_ranges = existing.dig("affected", 0, "ranges") no_reviewed_ranges = Array(existing_ranges).none? do |range| Homebrew::Vulns::OsvExport.ranges_open?([range]) || Homebrew::Vulns::OsvExport.ranges_terminal?([range]) end compatible = ranges == existing_ranges || (@close_open_ranges && no_reviewed_ranges) if !compatible || (!revalidated && !Homebrew::Vulns::OsvExport.ranges_open?(ranges)) fields = (existing_basis.keys | candidate_basis.keys).reject do |key| existing_basis[key] == candidate_basis[key] end Utils::Output.onoe "#{candidate[:id]}: reviewed range basis changed (#{fields.join(", ")}); " \ "leaving it unchanged.\n" \ "Run advisory-match for this formula with --json and history enabled,\n" \ "then review and update its ranges and provenance together." @basis_changed += 1 Homebrew.failed = true next end end elsif File.file?(path) candidate[:id] = File.basename(path, ".json") end merged = Homebrew::Vulns::OsvExport.merge_existing( path, candidate, close_open_ranges: @close_open_ranges, initial_introduction: ) [path, merged] end updates.each do |path, merged| if merged.nil? @unchanged += 1 next end File.write(path, "#{JSON.pretty_generate(merged)}\n") puts " wrote #{path}" if @verbose @written += 1 end end |
#ensure_alias_index ⇒ 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.
1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 |
# File 'dev-cmd/advisory-match.rb', line 1060 def ensure_alias_index return if @alias_index_loaded Dir.glob(File.join(@dir, "BREW-*.json")).each do |path| existing = alias_record(path) next unless existing.is_a?(Hash) formula_names = affected_formula_names(existing) next unless formula_names.one? identities = Array(existing["upstream"]).grep(String).map do |id| "BREW-#{formula_names.fetch(0)}-#{id}" end.uniq @path_identities[path] = identities identities.each { |identity| (@identity_paths[identity] ||= []) << path } end @alias_index_loaded = true end |
#finish ⇒ 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.
1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 |
# File 'dev-cmd/advisory-match.rb', line 1134 def finish history_unavailable = @history_unavailable_by_formula.values.sum Utils::Output.ohai "#{@written} records written to #{@dir} " \ "(#{@unchanged} unchanged, #{@skipped_generated} generated left as-is, " \ "#{@history_walks} history walks, #{history_unavailable} history-unavailable skips, " \ "#{@basis_changed} range-basis skips)" if @reconcile_history ensure_alias_index formula_names = @formula_names unmatched = @alias_records.count do |path, record| next false unless record.is_a?(Hash) next false if formula_names&.exclude?(record.dig("affected", 0, "package", "name")) record.dig("database_specific", "source") == "matched" && !@reconciliation_seen[path] end puts " Reconciliation: #{@deleted} deleted; #{unmatched} matched records not revisited" @reconciliation_skips.sort.each { |reason, count| puts " #{reason}: #{count}" } end return if @history_unavailable_by_formula.empty? puts " Unavailable history by formula:" @history_unavailable_by_formula.sort.each { |formula, count| puts " #{formula}: #{count}" } end |
#fixed_boundary_valid?(record_id, boundary) ⇒ 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.
953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 |
# File 'dev-cmd/advisory-match.rb', line 953 def fixed_boundary_valid?(record_id, boundary) alias_target_paths(record_id).all? do |path| next true unless File.file?(path) existing = alias_record(path) next false unless existing.is_a?(Hash) affected = existing["affected"] next false unless affected.is_a?(Array) affected.all? do |entry| entry.is_a?(Hash) && Homebrew::Vulns::OsvExport.fixed_follows?(homebrew_ranges(entry["ranges"]), boundary) end end end |
#history_required?(record_id) ⇒ 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.
934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 |
# File 'dev-cmd/advisory-match.rb', line 934 def history_required?(record_id) alias_target_paths(record_id).any? do |path| next true unless File.file?(path) existing = alias_record(path) next true unless existing.is_a?(Hash) affected = existing["affected"] next true unless affected.is_a?(Array) next true if affected.empty? affected.any? do |entry| !entry.is_a?(Hash) || !Homebrew::Vulns::OsvExport.ranges_terminal?(homebrew_ranges(entry["ranges"])) end end end |
#homebrew_ranges(ranges) ⇒ Array<T.untyped>
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.
1127 1128 1129 1130 1131 |
# File 'dev-cmd/advisory-match.rb', line 1127 def homebrew_ranges(ranges) Array(ranges).select do |range| range.is_a?(Hash) && range["type"] == "ECOSYSTEM" end end |
#matching_alias_paths(record_ids) ⇒ Array<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.
1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 |
# File 'dev-cmd/advisory-match.rb', line 1080 def matching_alias_paths(record_ids) pending = record_ids.dup identities = T.let({}, T::Hash[String, T::Boolean]) paths = T.let({}, T::Hash[String, T::Boolean]) until pending.empty? identity = pending.shift next if identity.nil? || identities[identity] identities[identity] = true direct = record_path(identity) linked = [direct, *@identity_paths.fetch(identity, [])] linked.each do |path| next unless File.file?(path) next if paths[path] paths[path] = true pending.concat(@path_identities.fetch(path, [])) end end paths.keys.sort end |
#prepare_aliases(formula_name, groups) ⇒ Array<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.
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 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 |
# File 'dev-cmd/advisory-match.rb', line 543 def prepare_aliases(formula_name, groups) ensure_alias_index errors = T.let([], T::Array[String]) targets = T.let({}, T::Hash[String, T::Array[String]]) protected = T.let({}, T::Hash[String, T::Boolean]) owners = T.let({}, T::Hash[String, String]) identity_owners = T.let({}, T::Hash[String, String]) generated_paths = T.let([], T::Array[String]) groups.each do |canonical_id, record_ids| record_ids.each do |record_id| if (owner = identity_owners[record_id]) && owner != canonical_id errors << "#{canonical_id}: identity also belongs to #{owner}; leaving both unchanged" else identity_owners[record_id] = canonical_id end end paths = matching_alias_paths(record_ids) paths.each do |path| if (owner = owners[path]) && owner != canonical_id errors << "#{canonical_id}: alias family also belongs to #{owner}; leaving both unchanged" else owners[path] = canonical_id end end writable = T.let([], T::Array[String]) generated = T.let([], T::Array[String]) paths.each do |path| existing = alias_record(path) if existing == :malformed errors << "#{canonical_id}: malformed alias #{path}; leaving family unchanged" next end unless existing.is_a?(Hash) errors << "#{canonical_id}: invalid alias #{path}; leaving family unchanged" next end if existing["id"] != File.basename(path, ".json") errors << "#{canonical_id}: #{path} has a mismatched id; leaving family unchanged" next end affected = existing["affected"] names = affected_formula_names(existing) if !affected.is_a?(Array) || !affected.one? || names != [formula_name] errors << "#{canonical_id}: #{path} has unsupported affected entries for " \ "#{formula_name}; leaving family unchanged" next end database_specific = existing["database_specific"] source = database_specific["source"] if database_specific.is_a?(Hash) if source == "generated" generated << path next end if source != "matched" errors << "#{canonical_id}: #{path} has unsupported source " \ "#{source.inspect}; leaving family unchanged" next end writable << path end if generated.any? targets[canonical_id] = [] protected[canonical_id] = true generated_paths.concat(generated) next end if writable.length > 1 errors << "#{canonical_id}: multiple alias records found; consolidate the family before matching" next end canonical_path = record_path(canonical_id) targets[canonical_id] = if writable.one? writable else [canonical_path] end protected[canonical_id] = false end return errors.uniq if errors.any? @alias_targets.merge!(targets) @protected_aliases.merge!(protected) @skipped_generated += generated_paths.uniq.length [] end |
#provenance_matches?(existing, record) ⇒ 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.
The stored record must still be reached through the same upstream identifiers with evidence and a range basis the candidate reproduces.
746 747 748 749 750 751 752 753 |
# File 'dev-cmd/advisory-match.rb', line 746 def provenance_matches?(existing, record) upstream = existing["upstream"] return false if !upstream.is_a?(Array) || upstream.empty? return false if (upstream - record.fetch(:upstream)).any? return false if Array(existing.dig("database_specific", "upstream_evidence")).empty? range_basis(existing) == range_basis(record) end |
#range_basis(record) ⇒ Hash{String => T.untyped}
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.
Keep every strategy and runtime subject. Ignore version values and resource labels, but retain checkability and separate copies of the same resource package: either can change the aggregate history.
882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 |
# File 'dev-cmd/advisory-match.rb', line 882 def range_basis(record) record = JSON.parse(JSON.generate(record)) affected = record["affected"] return {} unless affected.is_a?(Array) return {} unless affected.one? entry = affected.fetch(0) return {} unless entry.is_a?(Hash) ecosystem_specific = entry["ecosystem_specific"] ecosystem_specific = {} unless ecosystem_specific.is_a?(Hash) database_specific = record["database_specific"] evidence = Array(database_specific["upstream_evidence"]) if database_specific.is_a?(Hash) evidence = Array(evidence).grep(Hash) subjects = evidence.group_by { |row| row["resource"] }.map do |resource, rows| identities = rows.filter_map do |row| identity = subject_identity(row) next if identity.empty? [row["strategy"].to_s, identity, row["subject_version"].nil? ? "unknown" : "versioned"] end [resource ? "resource" : "primary", identities.uniq.sort] end basis = { "subjects" => subjects.sort } resource_purl = ecosystem_specific["resource_purl"] if resource_purl.is_a?(String) resource = evidence.find { |row| row["resource"] && row["key"] == resource_purl } basis["resource_purl"] = resource ? subject_identity(resource) : [unversioned_key(resource_purl)] end upstream_fixed_in = ecosystem_specific["upstream_fixed_in"] basis["upstream_fixed_in"] = upstream_fixed_in if upstream_fixed_in.is_a?(String) basis end |
#range_basis_changed?(record) ⇒ 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.
816 817 818 819 820 821 822 823 |
# File 'dev-cmd/advisory-match.rb', line 816 def range_basis_changed?(record) alias_target_paths(record.fetch(:id)).any? do |path| next false unless File.file?(path) existing = alias_record(path) existing.is_a?(Hash) && range_basis(existing) != range_basis(record) end end |
#reconcilable_record?(existing) ⇒ 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.
Only matched, bump-fixed records are reconciled; generated patch fixes and withdrawn records keep their annotation-based ranges.
717 718 719 720 721 722 |
# File 'dev-cmd/advisory-match.rb', line 717 def reconcilable_record?(existing) existing.is_a?(Hash) && existing.dig("database_specific", "source") == "matched" && existing["withdrawn"].blank? && existing.dig("affected", 0, "ecosystem_specific", "fix") == "bump" end |
#reconcile(record, result) ⇒ 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.
762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 |
# File 'dev-cmd/advisory-match.rb', line 762 def reconcile(record, result) existing = reconciliation_record(record) return unless existing id = record.fetch(:id) path = alias_target_paths(id).fetch(0) updated = existing.deep_dup if result.state == :range && (introduced = result.introduced) && (fixed = result.fixed) events = existing.fetch("affected").fetch(0).fetch("ranges").fetch(0).fetch("events") old_introduced = PkgVersion.parse(events.fetch(0).fetch("introduced")) old_fixed = PkgVersion.parse(events.fetch(1).fetch("fixed")) if PkgVersion.parse(introduced) >= PkgVersion.parse(fixed) skip_reconciliation(id, [:invalid_interval]) return end if PkgVersion.parse(introduced) < old_introduced || PkgVersion.parse(fixed) > old_fixed skip_reconciliation(id, [:range_expansion]) return end updated.fetch("affected").fetch(0).fetch("ranges").fetch(0)["events"] = [ { "introduced" => introduced }, { "fixed" => fixed } ] if updated == existing @unchanged += 1 return end updated["modified"] = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ") elsif result.state == :range && result.fixed.nil? skip_reconciliation(id, [:reopened]) return elsif result.state != :never_affected skip_reconciliation(id, result.reasons.presence || [:unsupported_result]) return end # A full walk can be slow. Do not overwrite an intervening review edit. if !File.file?(path) || JSON.parse(File.read(path)) != existing skip_reconciliation(id, [:record_changed]) return end if result.state == :never_affected File.unlink(path) @deleted += 1 puts " deleted #{path}" if @verbose else File.write(path, "#{JSON.pretty_generate(updated)}\n") @written += 1 puts " reconciled #{path}" if @verbose end rescue JSON::ParserError skip_reconciliation(record.fetch(:id), [:record_changed]) end |
#reconciliation_record(record) ⇒ Hash{String => T.untyped}?
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.
Reconciliation never creates a record or changes its matching provenance.
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 |
# File 'dev-cmd/advisory-match.rb', line 682 def reconciliation_record(record) id = record.fetch(:id) if alias_protected?(id) skip_reconciliation(id, [:alias_protected]) return end paths = alias_target_paths(id) unless paths.one? skip_reconciliation(id, [:ambiguous_alias_paths]) return end return unless File.file?(paths.fetch(0)) path = paths.fetch(0) @reconciliation_seen[path] = true existing = alias_record(path) unless reconcilable_record?(existing) skip_reconciliation(id, [:unsupported_record]) return end unless single_terminal_range?(existing) skip_reconciliation(id, [:unsupported_range]) return end unless provenance_matches?(existing, record) skip_reconciliation(id, [:provenance_changed]) return end existing end |
#reconciliation_resource_records(formula_name) ⇒ Array<Homebrew::Vulns::Match::ResourceRecord>
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.
Only pure registry-resource records can supply fallback query hints. Mixed or incomplete provenance retains the ordinary discovery guard.
644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 |
# File 'dev-cmd/advisory-match.rb', line 644 def reconciliation_resource_records(formula_name) fields = %w[ecosystem name resource subject_version key] Dir.glob(File.join(@dir, "BREW-#{formula_name}-*.json")).filter_map do |path| record = alias_record(path) next unless record.is_a?(Hash) next unless reconcilable_record?(record) next unless single_terminal_range?(record) next if affected_formula_names(record) != [formula_name] next if record["id"] != File.basename(path, ".json") upstream = record["upstream"] next unless upstream.is_a?(Array) next if upstream.empty? || !upstream.all?(String) rows = record.dig("database_specific", "upstream_evidence") next unless rows.is_a?(Array) next if rows.empty? evidence = rows.filter_map do |row| next unless row.is_a?(Hash) next if row["strategy"] != "registry" next unless fields.all? do |field| row[field].is_a?(String) && row[field].present? end Homebrew::Vulns::Match::Evidence.new( strategy: :registry, ecosystem: row.fetch("ecosystem"), name: row.fetch("name"), resource: row.fetch("resource"), subject_version: row.fetch("subject_version"), key: row.fetch("key") ).freeze end next if evidence.length != rows.length Homebrew::Vulns::Match::ResourceRecord.new(id: record.fetch("id"), upstream:, evidence: evidence.uniq) end end |
#record_history_unavailable(formula_name) ⇒ 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.
1009 1010 1011 1012 |
# File 'dev-cmd/advisory-match.rb', line 1009 def record_history_unavailable(formula_name) count = @history_unavailable_by_formula.fetch(formula_name, 0) @history_unavailable_by_formula[formula_name] = count + 1 end |
#record_history_walk ⇒ 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.
1003 1004 1005 1006 |
# File 'dev-cmd/advisory-match.rb', line 1003 def record_history_walk @history_walks += 1 puts " #{@history_walks} history walks" if @verbose && (@history_walks % 100).zero? end |
#record_path(record_id) ⇒ 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.
1028 1029 1030 |
# File 'dev-cmd/advisory-match.rb', line 1028 def record_path(record_id) File.join(@dir, "#{record_id}.json") end |
#record_upstream_unavailable(formula_name) ⇒ 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.
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 |
# File 'dev-cmd/advisory-match.rb', line 1015 def record_upstream_unavailable(formula_name) ensure_alias_index @alias_records.each do |path, record| next unless record.is_a?(Hash) next if record.dig("database_specific", "source") != "matched" next unless affected_formula_names(record).include?(formula_name) @reconciliation_seen[path] = true skip_reconciliation(record.fetch("id"), [:upstream_unavailable]) end end |
#reintroduction_boundary_valid?(record_id, boundary) ⇒ 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.
983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 |
# File 'dev-cmd/advisory-match.rb', line 983 def reintroduction_boundary_valid?(record_id, boundary) alias_target_paths(record_id).all? do |path| next true unless File.file?(path) existing = alias_record(path) next false unless existing.is_a?(Hash) affected = existing["affected"] next false unless affected.is_a?(Array) affected.all? do |entry| next false unless entry.is_a?(Hash) ranges = homebrew_ranges(entry["ranges"]) Homebrew::Vulns::OsvExport.reintroduction_follows?(ranges, boundary) end end end |
#reviewed_range_state(record_id) ⇒ Symbol?
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.
971 972 973 974 975 976 977 978 979 980 |
# File 'dev-cmd/advisory-match.rb', line 971 def reviewed_range_state(record_id) paths = alias_target_paths(record_id).select { |path| File.file?(path) } states_by_path = paths.map { |path| reviewed_states(path) } return if states_by_path.any?(&:nil?) states = states_by_path.flatten return if states.empty? states.fetch(0) end |
#reviewed_states(path) ⇒ Array<Symbol>?
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.
1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 |
# File 'dev-cmd/advisory-match.rb', line 1103 def reviewed_states(path) existing = alias_record(path) return unless existing.is_a?(Hash) affected = existing["affected"] return unless affected.is_a?(Array) return if affected.empty? states = affected.filter_map do |entry| next unless entry.is_a?(Hash) ranges = homebrew_ranges(entry["ranges"]) if Homebrew::Vulns::OsvExport.ranges_terminal?(ranges) :terminal elsif Homebrew::Vulns::OsvExport.ranges_open?(ranges) :open end end return if states.length != affected.length states end |
#single_terminal_range?(existing) ⇒ 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.
Exactly one ECOSYSTEM range holding an introduced and a fixed
event, each a non-blank string.
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 |
# File 'dev-cmd/advisory-match.rb', line 727 def single_terminal_range?(existing) ranges = existing.dig("affected", 0, "ranges") return false if !ranges.is_a?(Array) || !ranges.one? range = ranges.fetch(0) return false if !range.is_a?(Hash) || range["type"] != "ECOSYSTEM" events = range["events"] return false if !events.is_a?(Array) || events.length != 2 introduced, fixed = events introduced.is_a?(Hash) && introduced.keys == ["introduced"] && introduced["introduced"].is_a?(String) && introduced["introduced"].present? && fixed.is_a?(Hash) && fixed.keys == ["fixed"] && fixed["fixed"].is_a?(String) && fixed["fixed"].present? end |
#skip_reconciliation(record_id, reasons) ⇒ 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.
756 757 758 759 |
# File 'dev-cmd/advisory-match.rb', line 756 def skip_reconciliation(record_id, reasons) reasons.each { |reason| @reconciliation_skips[reason] = @reconciliation_skips.fetch(reason, 0) + 1 } Utils::Output.opoo "#{record_id}: #{reasons.join(", ")}; leaving it unchanged" if @verbose end |
#subject_identity(evidence) ⇒ Array<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.
918 919 920 921 922 923 924 925 |
# File 'dev-cmd/advisory-match.rb', line 918 def subject_identity(evidence) ecosystem = evidence["ecosystem"] name = evidence["name"] return [ecosystem, name] if ecosystem.is_a?(String) && name.is_a?(String) key = evidence["key"] key.is_a?(String) ? [unversioned_key(key)] : [] end |
#unversioned_key(key) ⇒ 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.
928 929 930 931 |
# File 'dev-cmd/advisory-match.rb', line 928 def unversioned_key(key) key = key.delete_prefix("upstream:") key.start_with?("pkg:") ? key.sub(%r{@[^/@]*\z}, "") : key end |