From 3ce9d0e0ed9c27c01d93b3ac4a9294305d585e90 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 1 Sep 2026 10:37:35 +0200 Subject: [PATCH 1/6] [CEL-1560] Allow frozen UI renderer 0.155 alias --- .github/tests/deploy-static-lock-validator.test.rb | 14 ++++++++++++++ .github/workflows/deploy-static-website.yaml | 2 ++ 2 files changed, 16 insertions(+) diff --git a/.github/tests/deploy-static-lock-validator.test.rb b/.github/tests/deploy-static-lock-validator.test.rb index 917c8a8..603fdbe 100644 --- a/.github/tests/deploy-static-lock-validator.test.rb +++ b/.github/tests/deploy-static-lock-validator.test.rb @@ -193,6 +193,19 @@ resolution: integrity: sha512-safe YAML + "frozen 0.155 renderer alias" => [<<~YAML, true], + lockfileVersion: '9.0' + importers: + .: + dependencies: + '@cellarnode/ui-renderer-0-155': + specifier: npm:@cellarnode/ui@0.155.1 + version: '@cellarnode/ui@0.155.1' + packages: + '@cellarnode/ui@0.155.1': + resolution: + integrity: sha512-safe + YAML "unknown private package" => [<<~YAML, false], lockfileVersion: '9.0' importers: {} @@ -260,6 +273,7 @@ "aliased unknown private manifest package" => ["hidden-package", "npm:@cellarnode/internal-secrets@1.0.0", false], "allowed private manifest package" => ["@cellarnode/ui", "1.0.0", true], "frozen renderer registry alias" => ["@cellarnode/ui-renderer-0-154", "npm:@cellarnode/ui@0.154.0", true], + "frozen 0.155 renderer registry alias" => ["@cellarnode/ui-renderer-0-155", "npm:@cellarnode/ui@0.155.1", true], "frozen renderer alias to public target" => ["@cellarnode/ui-renderer-0-154", "npm:leftpad@1.0.0", false], "allowed alias with path traversal version" => ["@cellarnode/ui", "npm:@cellarnode/ui@0.154.0/../../evil", false], "allowed public registry range" => ["public-package", "^1.0.0", true], diff --git a/.github/workflows/deploy-static-website.yaml b/.github/workflows/deploy-static-website.yaml index 9935b30..60c94d6 100644 --- a/.github/workflows/deploy-static-website.yaml +++ b/.github/workflows/deploy-static-website.yaml @@ -191,6 +191,7 @@ jobs: i18n ui ui-renderer-0-154 + ui-renderer-0-155 ].freeze # A version-pinned npm: alias is registry-sourced, but only aliases # that TARGET an allowed @cellarnode package are exempt from the @@ -273,6 +274,7 @@ jobs: i18n ui ui-renderer-0-154 + ui-renderer-0-155 ].freeze failures = [] validate_private_packages = lambda do |text, keys| From 94b23190732a2c746d8e2065dd97d010ae5efde3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 1 Sep 2026 10:44:10 +0200 Subject: [PATCH 2/6] [CEL-1560] Bind frozen renderer aliases to exact UI versions --- .../deploy-static-lock-validator.test.rb | 30 +++++++++++ .github/workflows/deploy-static-website.yaml | 51 +++++++++++++++---- 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/.github/tests/deploy-static-lock-validator.test.rb b/.github/tests/deploy-static-lock-validator.test.rb index 603fdbe..c5eb348 100644 --- a/.github/tests/deploy-static-lock-validator.test.rb +++ b/.github/tests/deploy-static-lock-validator.test.rb @@ -206,6 +206,32 @@ resolution: integrity: sha512-safe YAML + "frozen 0.155 renderer alias with wrong version" => [<<~YAML, false], + lockfileVersion: '9.0' + importers: + .: + dependencies: + '@cellarnode/ui-renderer-0-155': + specifier: npm:@cellarnode/ui@9.9.9 + version: '@cellarnode/ui@9.9.9' + YAML + "frozen 0.155 renderer alias with wrong target" => [<<~YAML, false], + lockfileVersion: '9.0' + importers: + .: + dependencies: + '@cellarnode/ui-renderer-0-155': + specifier: npm:@cellarnode/auth@1.0.0 + version: '@cellarnode/auth@1.0.0' + YAML + "frozen 0.155 renderer as direct lock package" => [<<~YAML, false], + lockfileVersion: '9.0' + importers: {} + packages: + '@cellarnode/ui-renderer-0-155@1.0.0': + resolution: + integrity: sha512-safe + YAML "unknown private package" => [<<~YAML, false], lockfileVersion: '9.0' importers: {} @@ -274,6 +300,10 @@ "allowed private manifest package" => ["@cellarnode/ui", "1.0.0", true], "frozen renderer registry alias" => ["@cellarnode/ui-renderer-0-154", "npm:@cellarnode/ui@0.154.0", true], "frozen 0.155 renderer registry alias" => ["@cellarnode/ui-renderer-0-155", "npm:@cellarnode/ui@0.155.1", true], + "frozen 0.155 renderer alias with wrong version" => ["@cellarnode/ui-renderer-0-155", "npm:@cellarnode/ui@9.9.9", false], + "frozen 0.155 renderer alias with wrong target" => ["@cellarnode/ui-renderer-0-155", "npm:@cellarnode/auth@1.0.0", false], + "frozen 0.155 renderer alias with plain version" => ["@cellarnode/ui-renderer-0-155", "1.0.0", false], + "nested frozen 0.155 renderer alias" => ["@cellarnode/ui", "npm:@cellarnode/ui-renderer-0-155@1.0.0", false], "frozen renderer alias to public target" => ["@cellarnode/ui-renderer-0-154", "npm:leftpad@1.0.0", false], "allowed alias with path traversal version" => ["@cellarnode/ui", "npm:@cellarnode/ui@0.154.0/../../evil", false], "allowed public registry range" => ["public-package", "^1.0.0", true], diff --git a/.github/workflows/deploy-static-website.yaml b/.github/workflows/deploy-static-website.yaml index 60c94d6..5527e5f 100644 --- a/.github/workflows/deploy-static-website.yaml +++ b/.github/workflows/deploy-static-website.yaml @@ -183,22 +183,24 @@ jobs: ].freeze protocol_dependency_source = /\A[a-z][a-z0-9+.-]*:/i path_or_archive_dependency_source = /[\\\/]|\.(?:tgz|tar(?:\.gz)?)\z/i - allowed_private_packages = %w[ + canonical_private_packages = %w[ auth beverage-utils elabel-compliance finance i18n ui - ui-renderer-0-154 - ui-renderer-0-155 ].freeze + frozen_registry_aliases = { + "@cellarnode/ui-renderer-0-154" => "npm:@cellarnode/ui@0.154.0", + "@cellarnode/ui-renderer-0-155" => "npm:@cellarnode/ui@0.155.1", + }.freeze # A version-pinned npm: alias is registry-sourced, but only aliases # that TARGET an allowed @cellarnode package are exempt from the # protocol check (e.g. @cellarnode/ui-renderer-0-154 -> # npm:@cellarnode/ui@0.154.0, the frozen e-label public renderer, # CEL-1522). An npm: alias pointing anywhere else still fails. - allowed_registry_alias = /\Anpm:@cellarnode\/(?:#{Regexp.union(allowed_private_packages).source})@[^\s\/\\]+\z/ + allowed_registry_alias = /\Anpm:@cellarnode\/(?:#{Regexp.union(canonical_private_packages).source})@[^\s\/\\]+\z/ failures = [] manifests = [] Find.find(".") do |path| @@ -219,15 +221,21 @@ jobs: value.each do |key, child| child_keys = keys + [key.to_s] key.to_s.scan(private_package).flatten.each do |package| - failures << "#{path}:#{child_keys.join('.')}" unless allowed_private_packages.include?(package.downcase) + alias_name = "@cellarnode/#{package.downcase}" + allowed_frozen_alias_key = frozen_registry_aliases.key?(alias_name) && + child_keys.any? { |part| dependency_sections.include?(part) } && + child_keys.last.downcase == alias_name + failures << "#{path}:#{child_keys.join('.')}" unless canonical_private_packages.include?(package.downcase) || allowed_frozen_alias_key end + expected_alias = frozen_registry_aliases[key.to_s.downcase] + failures << "#{path}:#{child_keys.join('.')}" if expected_alias && child != expected_alias visit.call(child, path, child_keys) end when Array value.each_with_index { |child, index| visit.call(child, path, keys + [index.to_s]) } when String value.scan(private_package).flatten.each do |package| - failures << "#{path}:#{keys.join('.')}" unless allowed_private_packages.include?(package.downcase) + failures << "#{path}:#{keys.join('.')}" unless canonical_private_packages.include?(package.downcase) end # An allowed registry alias is exempt from BOTH source checks: its # scope slash would otherwise trip the path heuristic. @@ -266,20 +274,33 @@ jobs: repository_shorthand = /\A[a-z0-9._-]+(?:[\\\/][a-z0-9._-]+)+(?:#.*)?\z/i private_package = /@cellarnode\/([a-z0-9._-]+)/i dependency_sections = %w[dependencies devDependencies optionalDependencies peerDependencies].freeze - allowed_private_packages = %w[ + canonical_private_packages = %w[ auth beverage-utils elabel-compliance finance i18n ui - ui-renderer-0-154 - ui-renderer-0-155 ].freeze + frozen_registry_aliases = { + "@cellarnode/ui-renderer-0-154" => { + "specifier" => "npm:@cellarnode/ui@0.154.0", + "version" => /\A@cellarnode\/ui@0\.154\.0(?:\([^\n]*\))?\z/, + }.freeze, + "@cellarnode/ui-renderer-0-155" => { + "specifier" => "npm:@cellarnode/ui@0.155.1", + "version" => /\A@cellarnode\/ui@0\.155\.1(?:\([^\n]*\))?\z/, + }.freeze, + }.freeze failures = [] validate_private_packages = lambda do |text, keys| text.to_s.scan(private_package).flatten.each do |package| - failures << keys.join(".") unless allowed_private_packages.include?(package.downcase) + alias_name = "@cellarnode/#{package.downcase}" + allowed_frozen_alias_key = frozen_registry_aliases.key?(alias_name) && + keys.first == "importers" && + dependency_sections.include?(keys[-2]) && + keys.last.downcase == alias_name + failures << keys.join(".") unless canonical_private_packages.include?(package.downcase) || allowed_frozen_alias_key end end visit = lambda do |value, keys| @@ -289,6 +310,16 @@ jobs: name = key.to_s child_keys = keys + [name] validate_private_packages.call(name, child_keys) + expected_alias = frozen_registry_aliases[name.downcase] + if expected_alias + valid_alias = keys.first == "importers" && + dependency_sections.include?(keys.last) && + child.is_a?(Hash) && + child["specifier"] == expected_alias.fetch("specifier") && + child["version"].is_a?(String) && + expected_alias.fetch("version").match?(child["version"]) + failures << child_keys.join(".") unless valid_alias + end if name == "resolution" valid_resolution = child.is_a?(Hash) && child.keys.map(&:to_s) == ["integrity"] && From 34abf6551b6309c4c4936e83d89a6b926cafdebb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 1 Sep 2026 10:51:24 +0200 Subject: [PATCH 3/6] [CEL-1560] Reject nested frozen renderer aliases --- .../deploy-static-lock-validator.test.rb | 31 +++++++++++++++++-- .github/workflows/deploy-static-website.yaml | 10 +++--- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/.github/tests/deploy-static-lock-validator.test.rb b/.github/tests/deploy-static-lock-validator.test.rb index c5eb348..4f5621f 100644 --- a/.github/tests/deploy-static-lock-validator.test.rb +++ b/.github/tests/deploy-static-lock-validator.test.rb @@ -193,6 +193,15 @@ resolution: integrity: sha512-safe YAML + "frozen 0.154 renderer alias" => [<<~YAML, true], + lockfileVersion: '9.0' + importers: + .: + dependencies: + '@cellarnode/ui-renderer-0-154': + specifier: npm:@cellarnode/ui@0.154.0 + version: '@cellarnode/ui@0.154.0(react@19.1.1)' + YAML "frozen 0.155 renderer alias" => [<<~YAML, true], lockfileVersion: '9.0' importers: @@ -200,7 +209,7 @@ dependencies: '@cellarnode/ui-renderer-0-155': specifier: npm:@cellarnode/ui@0.155.1 - version: '@cellarnode/ui@0.155.1' + version: '@cellarnode/ui@0.155.1(react@19.1.1)' packages: '@cellarnode/ui@0.155.1': resolution: @@ -304,6 +313,22 @@ "frozen 0.155 renderer alias with wrong target" => ["@cellarnode/ui-renderer-0-155", "npm:@cellarnode/auth@1.0.0", false], "frozen 0.155 renderer alias with plain version" => ["@cellarnode/ui-renderer-0-155", "1.0.0", false], "nested frozen 0.155 renderer alias" => ["@cellarnode/ui", "npm:@cellarnode/ui-renderer-0-155@1.0.0", false], + "case-variant frozen 0.155 renderer alias" => ["@cellarnode/UI-renderer-0-155", "npm:@cellarnode/ui@0.155.1", false], + "nested frozen 0.155 renderer alias key" => [ + "unused", + "unused", + false, + nil, + <<~JSON, + { + "dependencies": { + "wrapper": { + "@cellarnode/ui-renderer-0-155": "npm:@cellarnode/ui@0.155.1" + } + } + } + JSON + ], "frozen renderer alias to public target" => ["@cellarnode/ui-renderer-0-154", "npm:leftpad@1.0.0", false], "allowed alias with path traversal version" => ["@cellarnode/ui", "npm:@cellarnode/ui@0.154.0/../../evil", false], "allowed public registry range" => ["public-package", "^1.0.0", true], @@ -394,11 +419,11 @@ } dependency_scripts.each do |job_name, (validation_script, non_frozen_script)| - manifest_fixtures.each do |name, (package, version, expected_install, workspace)| + manifest_fixtures.each do |name, (package, version, expected_install, workspace, manifest)| Dir.mktmpdir("cel1328-manifest-boundary") do |directory| File.write( File.join(directory, "package.json"), - <<~JSON, + manifest || <<~JSON, { "dependencies": { "#{package}": "#{version}" diff --git a/.github/workflows/deploy-static-website.yaml b/.github/workflows/deploy-static-website.yaml index 5527e5f..a134669 100644 --- a/.github/workflows/deploy-static-website.yaml +++ b/.github/workflows/deploy-static-website.yaml @@ -223,11 +223,11 @@ jobs: key.to_s.scan(private_package).flatten.each do |package| alias_name = "@cellarnode/#{package.downcase}" allowed_frozen_alias_key = frozen_registry_aliases.key?(alias_name) && - child_keys.any? { |part| dependency_sections.include?(part) } && - child_keys.last.downcase == alias_name + dependency_sections.include?(child_keys[-2]) && + child_keys.last == alias_name failures << "#{path}:#{child_keys.join('.')}" unless canonical_private_packages.include?(package.downcase) || allowed_frozen_alias_key end - expected_alias = frozen_registry_aliases[key.to_s.downcase] + expected_alias = frozen_registry_aliases[key.to_s] failures << "#{path}:#{child_keys.join('.')}" if expected_alias && child != expected_alias visit.call(child, path, child_keys) end @@ -299,7 +299,7 @@ jobs: allowed_frozen_alias_key = frozen_registry_aliases.key?(alias_name) && keys.first == "importers" && dependency_sections.include?(keys[-2]) && - keys.last.downcase == alias_name + keys.last == alias_name failures << keys.join(".") unless canonical_private_packages.include?(package.downcase) || allowed_frozen_alias_key end end @@ -310,7 +310,7 @@ jobs: name = key.to_s child_keys = keys + [name] validate_private_packages.call(name, child_keys) - expected_alias = frozen_registry_aliases[name.downcase] + expected_alias = frozen_registry_aliases[name] if expected_alias valid_alias = keys.first == "importers" && dependency_sections.include?(keys.last) && From 363dd9474e115650f4f039299f4e644812c99463 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 1 Sep 2026 10:57:59 +0200 Subject: [PATCH 4/6] [CEL-1560] Restrict renderer aliases to direct dependencies --- .../deploy-static-lock-validator.test.rb | 57 +++++++++++++++++++ .github/workflows/deploy-static-website.yaml | 15 +++-- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/.github/tests/deploy-static-lock-validator.test.rb b/.github/tests/deploy-static-lock-validator.test.rb index 4f5621f..7374211 100644 --- a/.github/tests/deploy-static-lock-validator.test.rb +++ b/.github/tests/deploy-static-lock-validator.test.rb @@ -202,6 +202,15 @@ specifier: npm:@cellarnode/ui@0.154.0 version: '@cellarnode/ui@0.154.0(react@19.1.1)' YAML + "frozen 0.154 renderer alias with wrong version" => [<<~YAML, false], + lockfileVersion: '9.0' + importers: + .: + dependencies: + '@cellarnode/ui-renderer-0-154': + specifier: npm:@cellarnode/ui@0.154.1 + version: '@cellarnode/ui@0.154.1' + YAML "frozen 0.155 renderer alias" => [<<~YAML, true], lockfileVersion: '9.0' importers: @@ -241,6 +250,17 @@ resolution: integrity: sha512-safe YAML + "nested frozen 0.155 renderer lock alias key" => [<<~YAML, false], + lockfileVersion: '9.0' + importers: + .: + dependencies: + wrapper: + dependencies: + '@cellarnode/ui-renderer-0-155': + specifier: npm:@cellarnode/ui@0.155.1 + version: '@cellarnode/ui@0.155.1' + YAML "unknown private package" => [<<~YAML, false], lockfileVersion: '9.0' importers: {} @@ -308,6 +328,7 @@ "aliased unknown private manifest package" => ["hidden-package", "npm:@cellarnode/internal-secrets@1.0.0", false], "allowed private manifest package" => ["@cellarnode/ui", "1.0.0", true], "frozen renderer registry alias" => ["@cellarnode/ui-renderer-0-154", "npm:@cellarnode/ui@0.154.0", true], + "frozen 0.154 renderer alias with wrong version" => ["@cellarnode/ui-renderer-0-154", "npm:@cellarnode/ui@0.154.1", false], "frozen 0.155 renderer registry alias" => ["@cellarnode/ui-renderer-0-155", "npm:@cellarnode/ui@0.155.1", true], "frozen 0.155 renderer alias with wrong version" => ["@cellarnode/ui-renderer-0-155", "npm:@cellarnode/ui@9.9.9", false], "frozen 0.155 renderer alias with wrong target" => ["@cellarnode/ui-renderer-0-155", "npm:@cellarnode/auth@1.0.0", false], @@ -329,6 +350,42 @@ } JSON ], + "frozen 0.155 renderer alias in pnpm overrides" => [ + "unused", + "unused", + false, + nil, + <<~JSON, + { + "dependencies": { "public-package": "1.0.0" }, + "pnpm": { + "overrides": { + "@cellarnode/ui-renderer-0-155": "npm:@cellarnode/ui@0.155.1" + } + } + } + JSON + ], + "frozen 0.155 renderer alias in package extension" => [ + "unused", + "unused", + false, + nil, + <<~JSON, + { + "dependencies": { "public-package": "1.0.0" }, + "pnpm": { + "packageExtensions": { + "public-package@1.0.0": { + "dependencies": { + "@cellarnode/ui-renderer-0-155": "npm:@cellarnode/ui@0.155.1" + } + } + } + } + } + JSON + ], "frozen renderer alias to public target" => ["@cellarnode/ui-renderer-0-154", "npm:leftpad@1.0.0", false], "allowed alias with path traversal version" => ["@cellarnode/ui", "npm:@cellarnode/ui@0.154.0/../../evil", false], "allowed public registry range" => ["public-package", "^1.0.0", true], diff --git a/.github/workflows/deploy-static-website.yaml b/.github/workflows/deploy-static-website.yaml index a134669..e1b075a 100644 --- a/.github/workflows/deploy-static-website.yaml +++ b/.github/workflows/deploy-static-website.yaml @@ -223,7 +223,8 @@ jobs: key.to_s.scan(private_package).flatten.each do |package| alias_name = "@cellarnode/#{package.downcase}" allowed_frozen_alias_key = frozen_registry_aliases.key?(alias_name) && - dependency_sections.include?(child_keys[-2]) && + child_keys.length == 2 && + dependency_sections.include?(child_keys.first) && child_keys.last == alias_name failures << "#{path}:#{child_keys.join('.')}" unless canonical_private_packages.include?(package.downcase) || allowed_frozen_alias_key end @@ -297,9 +298,10 @@ jobs: text.to_s.scan(private_package).flatten.each do |package| alias_name = "@cellarnode/#{package.downcase}" allowed_frozen_alias_key = frozen_registry_aliases.key?(alias_name) && - keys.first == "importers" && - dependency_sections.include?(keys[-2]) && - keys.last == alias_name + keys.length == 4 && + keys[0] == "importers" && + dependency_sections.include?(keys[2]) && + keys[3] == alias_name failures << keys.join(".") unless canonical_private_packages.include?(package.downcase) || allowed_frozen_alias_key end end @@ -312,8 +314,9 @@ jobs: validate_private_packages.call(name, child_keys) expected_alias = frozen_registry_aliases[name] if expected_alias - valid_alias = keys.first == "importers" && - dependency_sections.include?(keys.last) && + valid_alias = keys.length == 3 && + keys[0] == "importers" && + dependency_sections.include?(keys[2]) && child.is_a?(Hash) && child["specifier"] == expected_alias.fetch("specifier") && child["version"].is_a?(String) && From 4b4ba95de3545134bba6aabc1e6bb798bb21feef Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 1 Sep 2026 11:02:33 +0200 Subject: [PATCH 5/6] [CEL-1560] Limit renderer aliases to dependency fields --- .../deploy-static-lock-validator.test.rb | 65 +++++++++++++++++++ .github/workflows/deploy-static-website.yaml | 8 ++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/.github/tests/deploy-static-lock-validator.test.rb b/.github/tests/deploy-static-lock-validator.test.rb index 7374211..f7078c0 100644 --- a/.github/tests/deploy-static-lock-validator.test.rb +++ b/.github/tests/deploy-static-lock-validator.test.rb @@ -233,6 +233,15 @@ specifier: npm:@cellarnode/ui@9.9.9 version: '@cellarnode/ui@9.9.9' YAML + "frozen 0.155 renderer alias with wrong resolved version" => [<<~YAML, false], + lockfileVersion: '9.0' + importers: + .: + dependencies: + '@cellarnode/ui-renderer-0-155': + specifier: npm:@cellarnode/ui@0.155.1 + version: '@cellarnode/ui@9.9.9' + YAML "frozen 0.155 renderer alias with wrong target" => [<<~YAML, false], lockfileVersion: '9.0' importers: @@ -386,6 +395,62 @@ } JSON ], + "frozen 0.155 renderer alias in root override" => [ + "unused", + "unused", + false, + nil, + <<~JSON, + { + "dependencies": { "public-package": "1.0.0" }, + "overrides": { + "@cellarnode/ui-renderer-0-155": "npm:@cellarnode/ui@0.155.1" + } + } + JSON + ], + "frozen 0.155 renderer alias in root package extension" => [ + "unused", + "unused", + false, + nil, + <<~JSON, + { + "dependencies": { "public-package": "1.0.0" }, + "packageExtensions": { + "@cellarnode/ui-renderer-0-155": "npm:@cellarnode/ui@0.155.1" + } + } + JSON + ], + "frozen 0.155 renderer alias in root resolution" => [ + "unused", + "unused", + false, + nil, + <<~JSON, + { + "dependencies": { "public-package": "1.0.0" }, + "resolutions": { + "@cellarnode/ui-renderer-0-155": "npm:@cellarnode/ui@0.155.1" + } + } + JSON + ], + "frozen 0.155 renderer alias in root catalog" => [ + "unused", + "unused", + false, + nil, + <<~JSON, + { + "dependencies": { "public-package": "1.0.0" }, + "catalog": { + "@cellarnode/ui-renderer-0-155": "npm:@cellarnode/ui@0.155.1" + } + } + JSON + ], "frozen renderer alias to public target" => ["@cellarnode/ui-renderer-0-154", "npm:leftpad@1.0.0", false], "allowed alias with path traversal version" => ["@cellarnode/ui", "npm:@cellarnode/ui@0.154.0/../../evil", false], "allowed public registry range" => ["public-package", "^1.0.0", true], diff --git a/.github/workflows/deploy-static-website.yaml b/.github/workflows/deploy-static-website.yaml index e1b075a..bfec82c 100644 --- a/.github/workflows/deploy-static-website.yaml +++ b/.github/workflows/deploy-static-website.yaml @@ -181,6 +181,12 @@ jobs: peerDependencies resolutions ].freeze + direct_dependency_sections = %w[ + dependencies + devDependencies + optionalDependencies + peerDependencies + ].freeze protocol_dependency_source = /\A[a-z][a-z0-9+.-]*:/i path_or_archive_dependency_source = /[\\\/]|\.(?:tgz|tar(?:\.gz)?)\z/i canonical_private_packages = %w[ @@ -224,7 +230,7 @@ jobs: alias_name = "@cellarnode/#{package.downcase}" allowed_frozen_alias_key = frozen_registry_aliases.key?(alias_name) && child_keys.length == 2 && - dependency_sections.include?(child_keys.first) && + direct_dependency_sections.include?(child_keys.first) && child_keys.last == alias_name failures << "#{path}:#{child_keys.join('.')}" unless canonical_private_packages.include?(package.downcase) || allowed_frozen_alias_key end From 1badd4f6cf4c2840f439996b66503b3ff1c603ae Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 1 Sep 2026 11:12:11 +0200 Subject: [PATCH 6/6] [CEL-1560] Validate frozen renderer peer contexts --- .../deploy-static-lock-validator.test.rb | 36 +++++++++++++ .github/workflows/deploy-static-website.yaml | 52 +++++++++++++++++-- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/.github/tests/deploy-static-lock-validator.test.rb b/.github/tests/deploy-static-lock-validator.test.rb index f7078c0..78631f8 100644 --- a/.github/tests/deploy-static-lock-validator.test.rb +++ b/.github/tests/deploy-static-lock-validator.test.rb @@ -224,6 +224,15 @@ resolution: integrity: sha512-safe YAML + "frozen 0.155 renderer alias with hashed peer context" => [<<~YAML, true], + lockfileVersion: '9.0' + importers: + .: + dependencies: + '@cellarnode/ui-renderer-0-155': + specifier: npm:@cellarnode/ui@0.155.1 + version: '@cellarnode/ui@0.155.1(e4ae7f10a11970144bc87c4d16ad3111)' + YAML "frozen 0.155 renderer alias with wrong version" => [<<~YAML, false], lockfileVersion: '9.0' importers: @@ -242,6 +251,33 @@ specifier: npm:@cellarnode/ui@0.155.1 version: '@cellarnode/ui@9.9.9' YAML + "frozen 0.155 renderer alias with protocol peer context" => [<<~YAML, false], + lockfileVersion: '9.0' + importers: + .: + dependencies: + '@cellarnode/ui-renderer-0-155': + specifier: npm:@cellarnode/ui@0.155.1 + version: '@cellarnode/ui@0.155.1(file:../evil)' + YAML + "frozen 0.155 renderer alias with traversal peer context" => [<<~YAML, false], + lockfileVersion: '9.0' + importers: + .: + dependencies: + '@cellarnode/ui-renderer-0-155': + specifier: npm:@cellarnode/ui@0.155.1 + version: '@cellarnode/ui@0.155.1(react@19.1.1/../../evil)' + YAML + "frozen 0.155 renderer alias with empty peer context" => [<<~YAML, false], + lockfileVersion: '9.0' + importers: + .: + dependencies: + '@cellarnode/ui-renderer-0-155': + specifier: npm:@cellarnode/ui@0.155.1 + version: '@cellarnode/ui@0.155.1()' + YAML "frozen 0.155 renderer alias with wrong target" => [<<~YAML, false], lockfileVersion: '9.0' importers: diff --git a/.github/workflows/deploy-static-website.yaml b/.github/workflows/deploy-static-website.yaml index bfec82c..5375a06 100644 --- a/.github/workflows/deploy-static-website.yaml +++ b/.github/workflows/deploy-static-website.yaml @@ -289,14 +289,54 @@ jobs: i18n ui ].freeze + peer_locator = /\G(?:@[a-z0-9._-]+\/[a-z0-9._-]+|[a-z0-9._-]+)@[0-9a-z][0-9a-z.+_-]*/ + parse_peer_group = nil + parse_peer_group = lambda do |text, index| + next nil unless text[index] == "(" + + index += 1 + locator = peer_locator.match(text, index) + next nil unless locator + + index = locator.end(0) + valid = true + while valid && text[index] == "(" + nested_end = parse_peer_group.call(text, index) + if nested_end + index = nested_end + else + valid = false + end + end + next nil unless valid + next nil unless text[index] == ")" + + index + 1 + end + valid_peer_suffix = lambda do |suffix| + next true if suffix.empty? + next true if suffix.match?(/\A\([a-f0-9]{32}\)\z/) + + index = 0 + valid = true + while valid && index < suffix.length + group_end = parse_peer_group.call(suffix, index) + if group_end + index = group_end + else + valid = false + end + end + valid && index.positive? + end frozen_registry_aliases = { "@cellarnode/ui-renderer-0-154" => { "specifier" => "npm:@cellarnode/ui@0.154.0", - "version" => /\A@cellarnode\/ui@0\.154\.0(?:\([^\n]*\))?\z/, + "version" => "@cellarnode/ui@0.154.0", }.freeze, "@cellarnode/ui-renderer-0-155" => { "specifier" => "npm:@cellarnode/ui@0.155.1", - "version" => /\A@cellarnode\/ui@0\.155\.1(?:\([^\n]*\))?\z/, + "version" => "@cellarnode/ui@0.155.1", }.freeze, }.freeze failures = [] @@ -320,13 +360,17 @@ jobs: validate_private_packages.call(name, child_keys) expected_alias = frozen_registry_aliases[name] if expected_alias + resolved_version = child["version"] if child.is_a?(Hash) + expected_version = expected_alias.fetch("version") + valid_version = resolved_version.is_a?(String) && + resolved_version.start_with?(expected_version) && + valid_peer_suffix.call(resolved_version.delete_prefix(expected_version)) valid_alias = keys.length == 3 && keys[0] == "importers" && dependency_sections.include?(keys[2]) && child.is_a?(Hash) && child["specifier"] == expected_alias.fetch("specifier") && - child["version"].is_a?(String) && - expected_alias.fetch("version").match?(child["version"]) + valid_version failures << child_keys.join(".") unless valid_alias end if name == "resolution"