diff --git a/packages/constructive-pnpm-policy/README.md b/packages/constructive-pnpm-policy/README.md index cff6659..2b80a71 100644 --- a/packages/constructive-pnpm-policy/README.md +++ b/packages/constructive-pnpm-policy/README.md @@ -27,7 +27,7 @@ pnpm add -D pnpm-policy @constructive-io/pnpm-policy `pnpm-policy.yaml` at the workspace root: ```yaml -minimumReleaseAge: 14d +minimumReleaseAge: 2d blockExoticSubdeps: true maintainers: - pyramation diff --git a/packages/constructive-pnpm-policy/pnpm-policy.yaml b/packages/constructive-pnpm-policy/pnpm-policy.yaml index 9eecde2..921e697 100644 --- a/packages/constructive-pnpm-policy/pnpm-policy.yaml +++ b/packages/constructive-pnpm-policy/pnpm-policy.yaml @@ -5,9 +5,9 @@ # ships it plus the generated inventory so our repos can point at one pinned # version instead of each maintaining its own exemption list. -# Third-party releases wait two weeks. Most compromised releases are found and -# yanked well inside that window. -minimumReleaseAge: 14d +# Third-party releases wait two days. A compromised release is normally reported +# and yanked within hours, so the short wait catches it without stalling upgrades. +minimumReleaseAge: 2d # Transitive dependencies must resolve from the registry, not from git or a URL. blockExoticSubdeps: true diff --git a/packages/pnpm-policy/README.md b/packages/pnpm-policy/README.md index 037acb7..441bf06 100644 --- a/packages/pnpm-policy/README.md +++ b/packages/pnpm-policy/README.md @@ -20,12 +20,12 @@ `minimumReleaseAge` is the single most effective supply-chain control pnpm ships: a package must have existed for N days before it can be installed, and most compromised releases are caught and yanked well inside that window. It is also the one control a package maintainer cannot turn on. -You publish `@acme/parser` at 2pm and consume it in three workspaces at 2:05pm. A 14-day quarantine means your own release is unusable for two weeks, in every repo you own. So the cooldown gets set to `0`, and the protection that would have stopped a compromised transitive dependency is gone — not because you decided the risk was acceptable, but because the tool could not tell your packages from everyone else's. +You publish `@acme/parser` at 2pm and consume it in three workspaces at 2:05pm. Even a two-day quarantine means your own release is unusable until Thursday, in every repo you own. So the cooldown gets set to `0`, and the protection that would have stopped a compromised transitive dependency is gone — not because you decided the risk was acceptable, but because the tool could not tell your packages from everyone else's. `pnpm-policy` makes that distinction. It asks npm what your maintainer accounts publish, and writes the answer into `pnpm-workspace.yaml` as an exemption list: ```yaml -minimumReleaseAge: 20160 # 14 days, for everything third-party +minimumReleaseAge: 2880 # 2 days, for everything third-party minimumReleaseAgeExclude: - "@acme/*" # a scope you own - my-unscoped-package # a package you publish @@ -60,7 +60,7 @@ Commit `pnpm-policy.yaml`, `pnpm-policy.inventory.json`, and the generated `pnpm ```yaml # How old a third-party release must be before it may be installed. # Accepts 14d / 2w / 36h / 90m, or a bare number of minutes (what pnpm stores). -minimumReleaseAge: 14d +minimumReleaseAge: 2d # Transitive dependencies must resolve from the registry, not from git or a URL. blockExoticSubdeps: true @@ -97,7 +97,7 @@ settings: | Key | Type | Default | Meaning | | --- | --- | --- | --- | -| `minimumReleaseAge` | duration | `14d` | Quarantine applied to everything not exempted. | +| `minimumReleaseAge` | duration | `2d` | Quarantine applied to everything not exempted. | | `blockExoticSubdeps` | boolean | `false` | Refuse transitive deps from git/URL sources. | | `maintainers` | string[] | `[]` | **Your own** npm accounts. See the warning below. | | `scopes` | string[] | `[]` | Scopes you own, emitted as globs. | @@ -196,9 +196,9 @@ packages: # Managed by pnpm-policy — run `pnpm-policy generate` after editing pnpm-policy.yaml. -# A third-party release must be 2w old before it can be installed. +# A third-party release must be 2d old before it can be installed. # Most malicious releases are found and yanked well inside that window. -minimumReleaseAge: 20160 +minimumReleaseAge: 2880 # Exempt from the wait: 1 scope glob(s), 2 first-party package(s). # First-party membership comes from what your-npm-username publishes on npm — waiting on your own release protects nothing. minimumReleaseAgeExclude: diff --git a/packages/pnpm-policy/__tests__/policy.test.ts b/packages/pnpm-policy/__tests__/policy.test.ts index 2f996d9..6edcb1b 100644 --- a/packages/pnpm-policy/__tests__/policy.test.ts +++ b/packages/pnpm-policy/__tests__/policy.test.ts @@ -24,8 +24,8 @@ const resolve = (config: PolicyConfig, resolved?: string[]) => }); describe('resolvePolicy', () => { - it('defaults to a two-week wait', () => { - expect(resolve({}).settings.minimumReleaseAge).toBe(20160); + it('defaults to a two-day wait', () => { + expect(resolve({}).settings.minimumReleaseAge).toBe(2880); }); it('converts a human duration to the minutes pnpm expects', () => { @@ -124,7 +124,7 @@ describe('resolvePolicy', () => { it('explains the wait and where the exemptions came from', () => { const { comments } = resolve({ maintainers: ['pyramation'] }); - expect(commentAt(comments.before, ['minimumReleaseAge'])).toContain('2w'); + expect(commentAt(comments.before, ['minimumReleaseAge'])).toContain('2d'); expect(commentAt(comments.before, ['minimumReleaseAgeExclude'])).toContain('pyramation'); }); diff --git a/packages/pnpm-policy/__tests__/workspace.test.ts b/packages/pnpm-policy/__tests__/workspace.test.ts index 33db8e4..3f69eaf 100644 --- a/packages/pnpm-policy/__tests__/workspace.test.ts +++ b/packages/pnpm-policy/__tests__/workspace.test.ts @@ -17,7 +17,7 @@ describe('applyPolicy', () => { it('creates the policy block in an empty file', () => { const out = applyPolicy('', policyFor()); expect(parseYaml(out)).toEqual({ - minimumReleaseAge: 20160, + minimumReleaseAge: 2880, minimumReleaseAgeExclude: ['@constructive-io/*', 'yanse'], blockExoticSubdeps: false }); @@ -51,7 +51,7 @@ describe('applyPolicy', () => { it('replaces a hand-edited value rather than appending a second key', () => { const out = applyPolicy('minimumReleaseAge: 0\n', policyFor()); expect(out.match(/minimumReleaseAge:/g)).toHaveLength(1); - expect(parseYaml(out)).toMatchObject({ minimumReleaseAge: 20160 }); + expect(parseYaml(out)).toMatchObject({ minimumReleaseAge: 2880 }); }); it('removes a managed key the policy no longer sets', () => { diff --git a/packages/pnpm-policy/src/cli.ts b/packages/pnpm-policy/src/cli.ts index 5aeaab4..e50334c 100644 --- a/packages/pnpm-policy/src/cli.ts +++ b/packages/pnpm-policy/src/cli.ts @@ -44,7 +44,7 @@ const STARTER = `# pnpm-policy — https://github.com/constructive-io/dev-utils # Run \`pnpm-policy generate\` to patch these settings into pnpm-workspace.yaml. # How long a third-party release must exist before it may be installed. -minimumReleaseAge: 14d +minimumReleaseAge: 2d # Transitive dependencies must come from the registry, not from git or a URL. blockExoticSubdeps: true diff --git a/packages/pnpm-policy/src/config.ts b/packages/pnpm-policy/src/config.ts index 61e809c..3068584 100644 --- a/packages/pnpm-policy/src/config.ts +++ b/packages/pnpm-policy/src/config.ts @@ -13,8 +13,11 @@ import type { AllowedBuild, PolicyConfig, PolicyException, ResolvedConfig } from /** Filenames searched for, in order, when no explicit path is given. */ export const CONFIG_FILENAMES = ['pnpm-policy.yaml', 'pnpm-policy.yml', 'pnpm-policy.json']; -/** Two weeks: long enough that a malicious release is usually yanked first. */ -export const DEFAULT_MINIMUM_RELEASE_AGE = '14d'; +/** + * Two days: a compromised release is normally reported and yanked within hours, + * so this catches the attack without holding legitimate upgrades for a fortnight. + */ +export const DEFAULT_MINIMUM_RELEASE_AGE = '2d'; /** Find the config file for a directory, or undefined if there is none. */ export function findConfig(dir: string): string | undefined { diff --git a/packages/pnpm-policy/src/duration.ts b/packages/pnpm-policy/src/duration.ts index 7146cf2..cf5c825 100644 --- a/packages/pnpm-policy/src/duration.ts +++ b/packages/pnpm-policy/src/duration.ts @@ -1,8 +1,8 @@ /** * Durations, in the unit pnpm speaks. * - * `minimumReleaseAge` is minutes, which nobody wants to write for a two-week - * cooldown, so the config takes `14d` and this converts. + * `minimumReleaseAge` is minutes, which nobody wants to write for a multi-day + * cooldown, so the config takes `2d` and this converts. */ import { PolicyError } from './errors';