(policy, sourceLabel)
| 34 | } |
| 35 | |
| 36 | const validatePolicy = (policy, sourceLabel) => { |
| 37 | // Drop and warn about keys with forbidden semver ranges (^, ~, >=, <, *). |
| 38 | // The RFC only permits exact versions joined by `||`. Bare names like |
| 39 | // `canvas` and explicit name-only wildcards (`canvas@*`) are allowed. |
| 40 | if (!policy) { |
| 41 | return policy |
| 42 | } |
| 43 | const cleaned = {} |
| 44 | for (const [key, value] of Object.entries(policy)) { |
| 45 | let parsed |
| 46 | try { |
| 47 | parsed = npa(key) |
| 48 | } catch { |
| 49 | log.warn('allow-scripts', `${sourceLabel}: ignoring unparseable entry "${key}"`) |
| 50 | continue |
| 51 | } |
| 52 | if (parsed.type === 'tag') { |
| 53 | // `pkg@latest`, `pkg@next`, etc. look like a pin but behave name- |
| 54 | // only — the matcher has no way to verify what the tag points at |
| 55 | // when scripts run. Reject for the same reason as semver ranges. |
| 56 | log.warn( |
| 57 | 'allow-scripts', |
| 58 | `${sourceLabel}: ignoring "${key}" — dist-tag specs (@latest, @next, ...) are not allowed; ` + |
| 59 | 'use exact versions joined by "||", or the bare package name, instead' |
| 60 | ) |
| 61 | continue |
| 62 | } |
| 63 | if (parsed.type === 'range') { |
| 64 | const isNameOnly = parsed.fetchSpec === '*' |
| 65 | || parsed.rawSpec === '' |
| 66 | || parsed.rawSpec === '*' |
| 67 | if (!isNameOnly && !isExactVersionDisjunction(parsed.fetchSpec)) { |
| 68 | log.warn( |
| 69 | 'allow-scripts', |
| 70 | `${sourceLabel}: ignoring "${key}" — semver ranges (^, ~, >=, <) are not allowed; ` + |
| 71 | 'use exact versions joined by "||" instead' |
| 72 | ) |
| 73 | continue |
| 74 | } |
| 75 | } |
| 76 | cleaned[key] = value |
| 77 | } |
| 78 | return Object.keys(cleaned).length > 0 ? cleaned : null |
| 79 | } |
| 80 | |
| 81 | // Resolve the effective allowScripts policy from the layered sources. |
| 82 | // Returns `{ policy, source }` where: |
no test coverage detected
searching dependent graphs…