Skip to content

Commit 6dde5ac

Browse files
authored
Merge pull request #113 from constructive-io/feat/two-day-default
feat(pnpm-policy): default the wait to two days
2 parents d8aeec3 + 1823c33 commit 6dde5ac

8 files changed

Lines changed: 23 additions & 20 deletions

File tree

packages/constructive-pnpm-policy/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pnpm add -D pnpm-policy @constructive-io/pnpm-policy
2727
`pnpm-policy.yaml` at the workspace root:
2828

2929
```yaml
30-
minimumReleaseAge: 14d
30+
minimumReleaseAge: 2d
3131
blockExoticSubdeps: true
3232
maintainers:
3333
- pyramation

packages/constructive-pnpm-policy/pnpm-policy.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
# ships it plus the generated inventory so our repos can point at one pinned
66
# version instead of each maintaining its own exemption list.
77

8-
# Third-party releases wait two weeks. Most compromised releases are found and
9-
# yanked well inside that window.
10-
minimumReleaseAge: 14d
8+
# Third-party releases wait two days. A compromised release is normally reported
9+
# and yanked within hours, so the short wait catches it without stalling upgrades.
10+
minimumReleaseAge: 2d
1111

1212
# Transitive dependencies must resolve from the registry, not from git or a URL.
1313
blockExoticSubdeps: true

packages/pnpm-policy/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@
2020

2121
`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.
2222

23-
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.
23+
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.
2424

2525
`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:
2626

2727
```yaml
28-
minimumReleaseAge: 20160 # 14 days, for everything third-party
28+
minimumReleaseAge: 2880 # 2 days, for everything third-party
2929
minimumReleaseAgeExclude:
3030
- "@acme/*" # a scope you own
3131
- my-unscoped-package # a package you publish
@@ -60,7 +60,7 @@ Commit `pnpm-policy.yaml`, `pnpm-policy.inventory.json`, and the generated `pnpm
6060
```yaml
6161
# How old a third-party release must be before it may be installed.
6262
# Accepts 14d / 2w / 36h / 90m, or a bare number of minutes (what pnpm stores).
63-
minimumReleaseAge: 14d
63+
minimumReleaseAge: 2d
6464

6565
# Transitive dependencies must resolve from the registry, not from git or a URL.
6666
blockExoticSubdeps: true
@@ -97,7 +97,7 @@ settings:
9797
9898
| Key | Type | Default | Meaning |
9999
| --- | --- | --- | --- |
100-
| `minimumReleaseAge` | duration | `14d` | Quarantine applied to everything not exempted. |
100+
| `minimumReleaseAge` | duration | `2d` | Quarantine applied to everything not exempted. |
101101
| `blockExoticSubdeps` | boolean | `false` | Refuse transitive deps from git/URL sources. |
102102
| `maintainers` | string[] | `[]` | **Your own** npm accounts. See the warning below. |
103103
| `scopes` | string[] | `[]` | Scopes you own, emitted as globs. |
@@ -196,9 +196,9 @@ packages:
196196
197197
# Managed by pnpm-policy — run `pnpm-policy generate` after editing pnpm-policy.yaml.
198198

199-
# A third-party release must be 2w old before it can be installed.
199+
# A third-party release must be 2d old before it can be installed.
200200
# Most malicious releases are found and yanked well inside that window.
201-
minimumReleaseAge: 20160
201+
minimumReleaseAge: 2880
202202
# Exempt from the wait: 1 scope glob(s), 2 first-party package(s).
203203
# First-party membership comes from what your-npm-username publishes on npm — waiting on your own release protects nothing.
204204
minimumReleaseAgeExclude:

packages/pnpm-policy/__tests__/policy.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ const resolve = (config: PolicyConfig, resolved?: string[]) =>
2424
});
2525

2626
describe('resolvePolicy', () => {
27-
it('defaults to a two-week wait', () => {
28-
expect(resolve({}).settings.minimumReleaseAge).toBe(20160);
27+
it('defaults to a two-day wait', () => {
28+
expect(resolve({}).settings.minimumReleaseAge).toBe(2880);
2929
});
3030

3131
it('converts a human duration to the minutes pnpm expects', () => {
@@ -124,7 +124,7 @@ describe('resolvePolicy', () => {
124124

125125
it('explains the wait and where the exemptions came from', () => {
126126
const { comments } = resolve({ maintainers: ['pyramation'] });
127-
expect(commentAt(comments.before, ['minimumReleaseAge'])).toContain('2w');
127+
expect(commentAt(comments.before, ['minimumReleaseAge'])).toContain('2d');
128128
expect(commentAt(comments.before, ['minimumReleaseAgeExclude'])).toContain('pyramation');
129129
});
130130

packages/pnpm-policy/__tests__/workspace.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ describe('applyPolicy', () => {
1717
it('creates the policy block in an empty file', () => {
1818
const out = applyPolicy('', policyFor());
1919
expect(parseYaml(out)).toEqual({
20-
minimumReleaseAge: 20160,
20+
minimumReleaseAge: 2880,
2121
minimumReleaseAgeExclude: ['@constructive-io/*', 'yanse'],
2222
blockExoticSubdeps: false
2323
});
@@ -51,7 +51,7 @@ describe('applyPolicy', () => {
5151
it('replaces a hand-edited value rather than appending a second key', () => {
5252
const out = applyPolicy('minimumReleaseAge: 0\n', policyFor());
5353
expect(out.match(/minimumReleaseAge:/g)).toHaveLength(1);
54-
expect(parseYaml(out)).toMatchObject({ minimumReleaseAge: 20160 });
54+
expect(parseYaml(out)).toMatchObject({ minimumReleaseAge: 2880 });
5555
});
5656

5757
it('removes a managed key the policy no longer sets', () => {

packages/pnpm-policy/src/cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const STARTER = `# pnpm-policy — https://github.com/constructive-io/dev-utils
4444
# Run \`pnpm-policy generate\` to patch these settings into pnpm-workspace.yaml.
4545
4646
# How long a third-party release must exist before it may be installed.
47-
minimumReleaseAge: 14d
47+
minimumReleaseAge: 2d
4848
4949
# Transitive dependencies must come from the registry, not from git or a URL.
5050
blockExoticSubdeps: true

packages/pnpm-policy/src/config.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ import type { AllowedBuild, PolicyConfig, PolicyException, ResolvedConfig } from
1313
/** Filenames searched for, in order, when no explicit path is given. */
1414
export const CONFIG_FILENAMES = ['pnpm-policy.yaml', 'pnpm-policy.yml', 'pnpm-policy.json'];
1515

16-
/** Two weeks: long enough that a malicious release is usually yanked first. */
17-
export const DEFAULT_MINIMUM_RELEASE_AGE = '14d';
16+
/**
17+
* Two days: a compromised release is normally reported and yanked within hours,
18+
* so this catches the attack without holding legitimate upgrades for a fortnight.
19+
*/
20+
export const DEFAULT_MINIMUM_RELEASE_AGE = '2d';
1821

1922
/** Find the config file for a directory, or undefined if there is none. */
2023
export function findConfig(dir: string): string | undefined {

packages/pnpm-policy/src/duration.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/**
22
* Durations, in the unit pnpm speaks.
33
*
4-
* `minimumReleaseAge` is minutes, which nobody wants to write for a two-week
5-
* cooldown, so the config takes `14d` and this converts.
4+
* `minimumReleaseAge` is minutes, which nobody wants to write for a multi-day
5+
* cooldown, so the config takes `2d` and this converts.
66
*/
77

88
import { PolicyError } from './errors';

0 commit comments

Comments
 (0)