diff --git a/modulefirewall/rules.go b/modulefirewall/rules.go index d29a7ad..a8b8c0c 100644 --- a/modulefirewall/rules.go +++ b/modulefirewall/rules.go @@ -16,7 +16,13 @@ const ( DefaultApprovedWhenEmpty = "ALL" ) -var bundledMalwareTrustSeeds = []string{"npm", "npx", "yarn", "yarnpkg", "corepack", "pnpm"} +// DefaultTrustedPackageManagers is the empty-TrustedModules seed: +// deny every module except supported package managers. +var DefaultTrustedPackageManagers = []string{ + "npm", "npx", "yarn", "yarnpkg", "corepack", "pnpm", "vlt", +} + +var bundledMalwareTrustSeeds = DefaultTrustedPackageManagers // ExplicitModuleGrant is true only for an explicit local grant. // Empty lists, ALL, NOT ALL, HTTPS URLs, and bundled package-manager names do not count. @@ -54,12 +60,15 @@ func ExplicitModuleGrant(pkg PackageSpec, rules []string) bool { } // NormalizeTrustedModules applies the empty TrustedModules default: -// deny every module except bundled package managers, so npm/yarn/corepack +// deny every module except bundled package managers, so npm/yarn/corepack/vlt // can re-sign after an upgrade. func NormalizeTrustedModules(entries []string) []string { normalized := NormalizeList(entries, "") if len(normalized) == 0 { - return []string{"NOT ALL", "npm", "npx", "yarn", "yarnpkg", "corepack", "pnpm"} + out := make([]string, 1+len(DefaultTrustedPackageManagers)) + out[0] = "NOT ALL" + copy(out[1:], DefaultTrustedPackageManagers) + return out } return normalized } diff --git a/modulefirewall/rules_test.go b/modulefirewall/rules_test.go index 87aec03..bed5464 100644 --- a/modulefirewall/rules_test.go +++ b/modulefirewall/rules_test.go @@ -7,7 +7,7 @@ import ( func TestNormalizeTrustedModules_DefaultTrustsNpm(t *testing.T) { got := NormalizeTrustedModules(nil) - want := []string{"NOT ALL", "npm", "npx", "yarn", "yarnpkg", "corepack", "pnpm"} + want := []string{"NOT ALL", "npm", "npx", "yarn", "yarnpkg", "corepack", "pnpm", "vlt"} if len(got) != len(want) { t.Fatalf("got %#v", got) } @@ -37,6 +37,11 @@ func TestNormalizeTrustedModules_DefaultTrustsNpm(t *testing.T) { if err != nil || !ok { t.Fatalf("corepack default trust = %v err=%v", ok, err) } + vlt, _ := ParsePackageToken("vlt") + ok, err = IsPackageTrustedLocal(vlt, nil) + if err != nil || !ok { + t.Fatalf("vlt default trust = %v err=%v", ok, err) + } other, _ := ParsePackageToken("eslint") ok, err = IsPackageTrustedLocal(other, nil) if err != nil || ok { diff --git a/modulefirewall/trust_test.go b/modulefirewall/trust_test.go index 763d5b5..27a5100 100644 --- a/modulefirewall/trust_test.go +++ b/modulefirewall/trust_test.go @@ -134,6 +134,10 @@ func TestExplicitModuleGrant(t *testing.T) { if ExplicitModuleGrant(pkg, []string{"NOT ALL", "npm", "npx"}) { t.Fatal("bundled seeds") } + vlt := PackageSpec{Name: "vlt", Raw: "vlt"} + if ExplicitModuleGrant(vlt, NormalizeTrustedModules(nil)) { + t.Fatal("vlt default seed") + } if ExplicitModuleGrant(pkg, []string{"https://policy.example/fw"}) { t.Fatal("url") } diff --git a/settings/options.go b/settings/options.go index b68b57a..d369fe8 100644 --- a/settings/options.go +++ b/settings/options.go @@ -36,7 +36,7 @@ type Settings struct { Aliases []string `cfg:"aliases" reg:"Aliases" default:"" help:"Comma-delimited list of version aliases in the format alias=version." hidden:"true"` AllowedSigners []string `cfg:"allowed_signers" reg:"AllowedSigners" help:"Comma-delimited signer organization names (O=) allowed after Authenticode chain verification. Use to restrict vendors (for example OpenJS Foundation vs NodeSource)." hidden:"true"` AllowedThumbprints []string `cfg:"allowed_thumbprints" reg:"AllowedThumbprints" help:"Optional comma-delimited SHA-1 Authenticode leaf thumbprints (hex). When set, node.exe must match one pin after org allowlist. Empty disables pinning." hidden:"true"` - TrustedModules []string `cfg:"trusted_modules" reg:"TrustedModules" help:"Trust firewall: modules allowed to auto-reshim after self-update. NOT/! negation; empty defaults to NOT ALL plus npm, npx, yarn, yarnpkg, corepack, and pnpm. May include an HTTPS policy URL; local entries are checked first and only untrusted modules are POSTed to the URL." hidden:"true"` + TrustedModules []string `cfg:"trusted_modules" reg:"TrustedModules" help:"Trust firewall: modules allowed to auto-reshim after self-update. NOT/! negation; empty defaults to NOT ALL plus npm, npx, yarn, yarnpkg, corepack, pnpm, and vlt. May include an HTTPS policy URL; local entries are checked first and only untrusted modules are POSTed to the URL." hidden:"true"` UntrustedModuleHandlerAction string `cfg:"untrusted_module_handler_action" reg:"UntrustedModuleHandlerAction" default:"prompt" enum:"deny,prompt,allow" help:"Action when a global module changes without TrustedModules hit: prompt (default), allow (auto-reshim + quiet toast), or deny." hidden:"false"` ApprovedModules []string `cfg:"approved_modules" reg:"ApprovedModules" help:"Module firewall (certified): allow list for local installs. Empty defaults to ALL. May be a single HTTPS policy URL." hidden:"true"` ApprovedGlobalModules []string `cfg:"approved_global_modules" reg:"ApprovedGlobalModules" help:"Module firewall (certified): allow list for global installs. Empty defaults to ALL. May be a single HTTPS policy URL." hidden:"true"` diff --git a/verifycache/script_cache.go b/verifycache/script_cache.go index e337700..2389303 100644 --- a/verifycache/script_cache.go +++ b/verifycache/script_cache.go @@ -17,8 +17,9 @@ const scriptCacheBucket = "scripts" var ErrScriptCacheMiss = errors.New("delegated script cache entry missing") // SignVersionScripts records TPM-signed hashes for top-level .cmd/.bat launchers, -// package-manager JS entrypoints (npm-cli.js / npx-cli.js), and Authenticode -// verify-cache entries for top-level .exe helpers. +// package-manager JS entrypoints (npm-cli.js / npx-cli.js), Authenticode +// verify-cache entries for top-level .exe helpers, and Yarn Classic / pnpm +// global bin launchers outside the version directory. func SignVersionScripts(versionDir string) error { dataRoot, err := dataRootFromVersionDir(versionDir) if err != nil {