From a84efe24fad99a9bdab477727a2a624eb9681243 Mon Sep 17 00:00:00 2001 From: mjnong Date: Fri, 28 Aug 2026 23:55:59 +0200 Subject: [PATCH] [CEL-1522] Allow version-pinned npm: aliases of allowed private packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The e-label frontend pins its public scan renderer to a frozen release via a registry alias: @cellarnode/ui-renderer-0-154 -> npm:@cellarnode/ui@0.154.0. The manifest guard rejected it three ways: the alias NAME is not in allowed_private_packages, the npm: value tripped the protocol check (which, unlike the lock validator's non_registry_protocol, had no npm: exemption), and the scope slash in the alias value tripped the path-or-archive heuristic. Allow exactly this shape and nothing wider: the alias name joins the allow-lists, and a value is exempt from the source checks only when it is anchored npm:@cellarnode/@ with no whitespace, slashes, or backslashes in the version. Aliases pointing at public or unknown packages, path traversal in the version spec, and every previously rejected source shape still fail — fixtures added for all three. --- .github/tests/deploy-static-lock-validator.test.rb | 3 +++ .github/workflows/deploy-static-website.yaml | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/tests/deploy-static-lock-validator.test.rb b/.github/tests/deploy-static-lock-validator.test.rb index 87e1ef4..917c8a8 100644 --- a/.github/tests/deploy-static-lock-validator.test.rb +++ b/.github/tests/deploy-static-lock-validator.test.rb @@ -259,6 +259,9 @@ "unknown private manifest package" => ["@cellarnode/internal-secrets", "1.0.0", false], "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 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], "aliased public manifest package" => ["@cellarnode/ui", "npm:evil-package@1.0.0", false], "git manifest package" => ["public-package", "git+https://evil.example/package.git", false], diff --git a/.github/workflows/deploy-static-website.yaml b/.github/workflows/deploy-static-website.yaml index 2d5b692..9935b30 100644 --- a/.github/workflows/deploy-static-website.yaml +++ b/.github/workflows/deploy-static-website.yaml @@ -190,7 +190,14 @@ jobs: finance i18n ui + ui-renderer-0-154 ].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/ failures = [] manifests = [] Find.find(".") do |path| @@ -221,8 +228,12 @@ jobs: value.scan(private_package).flatten.each do |package| failures << "#{path}:#{keys.join('.')}" unless allowed_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. if keys.any? { |key| dependency_sections.include?(key) } && - (protocol_dependency_source.match?(value) || path_or_archive_dependency_source.match?(value)) + !allowed_registry_alias.match?(value) && + (protocol_dependency_source.match?(value) || + path_or_archive_dependency_source.match?(value)) failures << "#{path}:#{keys.join('.')}" end end @@ -261,6 +272,7 @@ jobs: finance i18n ui + ui-renderer-0-154 ].freeze failures = [] validate_private_packages = lambda do |text, keys|