periapsis 0.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +308 -33
- package/bin/periapsis.mjs +608 -318
- package/lib/periapsis-core.mjs +1053 -0
- package/package.json +21 -1
- package/policy/exceptions.json +1 -0
- package/policy/licenses.json +1 -0
- package/policy/policy.json +14 -0
- package/sbom-licenses.json +12 -0
- package/spdx_licenses_with_categories.json +726 -726
- package/test/cli.test.mjs +175 -0
- package/test/policy.test.mjs +318 -0
package/README.md
CHANGED
|
@@ -1,50 +1,290 @@
|
|
|
1
|
-
# Periapsis
|
|
1
|
+
# Periapsis
|
|
2
2
|
|
|
3
|
-
License compliance
|
|
3
|
+
License compliance gate for Node.js dependencies. Periapsis reads `package-lock.json` + installed `node_modules`, writes an SBOM, and fails CI when dependency licenses are not covered by active policy.
|
|
4
4
|
|
|
5
5
|
## Install / Run
|
|
6
6
|
|
|
7
|
-
Local clone:
|
|
8
|
-
|
|
9
7
|
```sh
|
|
10
|
-
npm install
|
|
11
|
-
npx periapsis --
|
|
8
|
+
npm install
|
|
9
|
+
npx periapsis --violations-out sbom-violations.json
|
|
12
10
|
```
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
Initialize governed policy files:
|
|
15
13
|
|
|
16
14
|
```sh
|
|
17
|
-
npx periapsis
|
|
15
|
+
npx periapsis init --preset strict
|
|
18
16
|
```
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
`init` now asks which dependency types should be checked by default (unless provided via flags).
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
- `periapsis`: run SBOM + license gate
|
|
23
|
+
- `periapsis init --preset <strict|standard|permissive> [--policy-dir policy] [--force]`
|
|
24
|
+
- `periapsis exceptions add [--policy-dir policy]` (interactive)
|
|
25
|
+
- `periapsis licenses allow add [--policy-dir policy]` (interactive)
|
|
26
|
+
- `periapsis policy migrate [--from allowedConfig.json] [--policy-dir policy] [--force]`
|
|
27
|
+
|
|
28
|
+
Automation mode:
|
|
29
|
+
|
|
30
|
+
- `periapsis exceptions add --non-interactive ...`
|
|
31
|
+
- `periapsis licenses allow add --non-interactive ...`
|
|
32
|
+
- `periapsis --dep-types dependencies,peerDependencies`
|
|
33
|
+
- `periapsis --production-only` (same as `--dep-types dependencies`)
|
|
34
|
+
|
|
35
|
+
## Policy Files
|
|
36
|
+
|
|
37
|
+
Periapsis now uses governed policy metadata files under `policy/`:
|
|
38
|
+
|
|
39
|
+
- `policy/policy.json`
|
|
40
|
+
- `policy/licenses.json`
|
|
41
|
+
- `policy/exceptions.json`
|
|
42
|
+
|
|
43
|
+
How they work together:
|
|
44
|
+
|
|
45
|
+
- `policy/policy.json`: global behavior and category fallback policy.
|
|
46
|
+
- `policy/licenses.json`: explicit license allow records with audit metadata and expiration.
|
|
47
|
+
- `policy/exceptions.json`: package-level overrides when a dependency cannot be covered by license policy alone.
|
|
25
48
|
|
|
26
|
-
|
|
49
|
+
Load behavior:
|
|
27
50
|
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
- `--allowed <file>`: allowlist JSON
|
|
32
|
-
- `--violations-out <file>`: write violations JSON (includes upstream chains)
|
|
33
|
-
- `--quiet`: suppress summary output
|
|
34
|
-
- `init --preset <level>`: write `allowedConfig.json` (strict | standard | permissive)
|
|
35
|
-
- `init --force`: overwrite existing `allowedConfig.json`
|
|
51
|
+
- Periapsis prefers `policy/` files.
|
|
52
|
+
- If `policy/policy.json` is missing, it can temporarily fall back to legacy `allowedConfig.json` (with warning).
|
|
53
|
+
- Use `periapsis policy migrate` to move legacy config into governed policy files.
|
|
36
54
|
|
|
37
|
-
|
|
55
|
+
Validation behavior:
|
|
56
|
+
|
|
57
|
+
- All `policy/*.json` files are schema-validated on load.
|
|
58
|
+
- `licenses add` and `exceptions add` validate the full policy bundle before writing.
|
|
59
|
+
- Invalid files fail fast with field-level schema error messages.
|
|
60
|
+
|
|
61
|
+
### `policy/policy.json`
|
|
38
62
|
|
|
39
63
|
```json
|
|
40
64
|
{
|
|
41
|
-
"allowedCategories": [
|
|
42
|
-
|
|
43
|
-
|
|
65
|
+
"allowedCategories": [
|
|
66
|
+
"Permissive Licenses",
|
|
67
|
+
"Weak Copyleft Licenses"
|
|
68
|
+
],
|
|
69
|
+
"failOnUnknownLicense": true,
|
|
70
|
+
"timezone": "America/Edmonton",
|
|
71
|
+
"dependencyTypes": [
|
|
72
|
+
"dependencies",
|
|
73
|
+
"devDependencies",
|
|
74
|
+
"peerDependencies",
|
|
75
|
+
"optionalDependencies",
|
|
76
|
+
"bundledDependencies"
|
|
77
|
+
]
|
|
44
78
|
}
|
|
45
79
|
```
|
|
46
80
|
|
|
47
|
-
|
|
81
|
+
`dependencyTypes` controls which package groups are checked by default:
|
|
82
|
+
|
|
83
|
+
- `dependencies`: runtime production packages
|
|
84
|
+
- `devDependencies`: development/test/build packages
|
|
85
|
+
- `peerDependencies`: host-provided/shared dependencies
|
|
86
|
+
- `optionalDependencies`: non-critical optional packages
|
|
87
|
+
- `bundledDependencies`: dependencies bundled with the package tarball
|
|
88
|
+
|
|
89
|
+
### `policy/licenses.json`
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
[
|
|
93
|
+
{
|
|
94
|
+
"identifier": "MIT",
|
|
95
|
+
"category": "Permissive Licenses",
|
|
96
|
+
"fullName": "MIT License",
|
|
97
|
+
"notes": "Default permissive license.",
|
|
98
|
+
"rationale": "Permissive, low legal risk for SaaS distribution.",
|
|
99
|
+
"approvedBy": ["security"],
|
|
100
|
+
"approvedAt": "2026-02-13T18:00:00Z",
|
|
101
|
+
"expiresAt": null,
|
|
102
|
+
"evidenceRef": "JIRA-1234"
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `policy/exceptions.json`
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
[
|
|
111
|
+
{
|
|
112
|
+
"package": "@pdftron/pdfjs-express-viewer",
|
|
113
|
+
"scope": { "type": "exact", "version": "8.7.5" },
|
|
114
|
+
"detectedLicenses": ["SEE LICENSE IN LICENSE"],
|
|
115
|
+
"reason": "Commercial dependency required for PDF rendering.",
|
|
116
|
+
"notes": "Revisit annually.",
|
|
117
|
+
"approvedBy": ["legal", "security"],
|
|
118
|
+
"approvedAt": "2026-02-13T18:00:00Z",
|
|
119
|
+
"expiresAt": "2026-08-13T00:00:00Z",
|
|
120
|
+
"evidenceRef": "JIRA-5678"
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Practical authoring rules:
|
|
126
|
+
|
|
127
|
+
- Keep `package` as package name only (for example `caniuse-lite`), and encode version logic in `scope`.
|
|
128
|
+
- Prefer `scope.type = "exact"` for least risk; use `range` carefully; avoid `any` unless necessary.
|
|
129
|
+
- Use non-empty `evidenceRef` values that link to a ticket, issue, or approval artifact.
|
|
130
|
+
- Do not delete old records to “update” policy; add follow-up records so history stays audit-friendly.
|
|
131
|
+
|
|
132
|
+
## License Categories
|
|
133
|
+
|
|
134
|
+
Periapsis uses three license policy categories:
|
|
135
|
+
|
|
136
|
+
Disclaimer: This section provides operational guidance for engineering policy decisions and is not legal advice. Consult qualified legal counsel for binding interpretation.
|
|
137
|
+
|
|
138
|
+
### `Permissive Licenses`
|
|
139
|
+
|
|
140
|
+
Examples:
|
|
141
|
+
|
|
142
|
+
- `MIT`
|
|
143
|
+
- `BSD`
|
|
144
|
+
- `Apache-2.0`
|
|
145
|
+
|
|
146
|
+
These generally allow:
|
|
147
|
+
|
|
148
|
+
- Commercial use
|
|
149
|
+
- Modification
|
|
150
|
+
- Distribution
|
|
151
|
+
|
|
152
|
+
With minimal obligations, usually attribution.
|
|
153
|
+
|
|
154
|
+
Typical risk level for most SMEs: Low.
|
|
155
|
+
|
|
156
|
+
### `Weak Copyleft Licenses`
|
|
157
|
+
|
|
158
|
+
Examples:
|
|
159
|
+
|
|
160
|
+
- `LGPL`
|
|
161
|
+
|
|
162
|
+
These typically require:
|
|
163
|
+
|
|
164
|
+
- Sharing modifications to the licensed component
|
|
165
|
+
- Following specific distribution rules
|
|
166
|
+
|
|
167
|
+
Typical risk level: Moderate, depending on usage and distribution model.
|
|
168
|
+
|
|
169
|
+
### `Strong Copyleft Licenses`
|
|
170
|
+
|
|
171
|
+
Examples:
|
|
172
|
+
|
|
173
|
+
- `GPL`
|
|
174
|
+
- `AGPL`
|
|
175
|
+
|
|
176
|
+
These may require:
|
|
177
|
+
|
|
178
|
+
- Distribution of source code when software is distributed
|
|
179
|
+
- Sharing modifications
|
|
180
|
+
- Careful handling to avoid proprietary code exposure
|
|
181
|
+
|
|
182
|
+
Typical risk level: High in some commercial contexts.
|
|
183
|
+
|
|
184
|
+
## Interactive Governance Workflows
|
|
185
|
+
|
|
186
|
+
### Add an exception
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
npx periapsis exceptions add
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Prompts include:
|
|
193
|
+
|
|
194
|
+
- package
|
|
195
|
+
- scope (`exact`, `range`, `any`)
|
|
196
|
+
- detected license identifiers / expression
|
|
197
|
+
- reason (required, multiline)
|
|
198
|
+
- notes (optional)
|
|
199
|
+
- approvedBy (required, comma-separated)
|
|
200
|
+
- approvedAt (default now)
|
|
201
|
+
- expiresAt (`ISO datetime` or `never`)
|
|
202
|
+
- evidenceRef (required)
|
|
203
|
+
|
|
204
|
+
If same package+scope exists, Periapsis prompts to add a follow-up record (recommended) or edit the latest record.
|
|
205
|
+
|
|
206
|
+
### Add an allowed license
|
|
207
|
+
|
|
208
|
+
```sh
|
|
209
|
+
npx periapsis licenses allow add
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Prompts include:
|
|
213
|
+
|
|
214
|
+
- SPDX identifier (warns if unknown to local SPDX catalog)
|
|
215
|
+
- fullName (optional, auto-filled when known)
|
|
216
|
+
- notes
|
|
217
|
+
- approvedBy
|
|
218
|
+
- approvedAt
|
|
219
|
+
- expiresAt (`ISO datetime` or `never`)
|
|
220
|
+
- category (`Permissive Licenses`, `Weak Copyleft Licenses`, `Strong Copyleft Licenses`, or `Uncategorized / Needs Review`)
|
|
221
|
+
- rationale
|
|
222
|
+
- evidenceRef
|
|
223
|
+
|
|
224
|
+
If identifier already exists, Periapsis appends a follow-up record.
|
|
225
|
+
|
|
226
|
+
### Non-interactive examples
|
|
227
|
+
|
|
228
|
+
Add allowed license without prompts:
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
npx periapsis licenses allow add \
|
|
232
|
+
--non-interactive \
|
|
233
|
+
--identifier MIT \
|
|
234
|
+
--approved-by security,legal \
|
|
235
|
+
--category "Permissive Licenses" \
|
|
236
|
+
--rationale "Approved baseline license" \
|
|
237
|
+
--evidence-ref JIRA-1234
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Add exception without prompts:
|
|
241
|
+
|
|
242
|
+
```sh
|
|
243
|
+
npx periapsis exceptions add \
|
|
244
|
+
--non-interactive \
|
|
245
|
+
--package caniuse-lite \
|
|
246
|
+
--scope-type exact \
|
|
247
|
+
--version 1.0.30001767 \
|
|
248
|
+
--reason "Reviewed and accepted by security" \
|
|
249
|
+
--approved-by security \
|
|
250
|
+
--expires-at 2027-02-13T00:00:00.000Z \
|
|
251
|
+
--evidence-ref JIRA-5678
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Run checker against only production dependencies:
|
|
255
|
+
|
|
256
|
+
```sh
|
|
257
|
+
npx periapsis --production-only
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Run checker against a custom dependency set:
|
|
261
|
+
|
|
262
|
+
```sh
|
|
263
|
+
npx periapsis --dep-types dependencies,peerDependencies
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Expiration and Follow-up Behavior
|
|
267
|
+
|
|
268
|
+
A policy record is active when:
|
|
269
|
+
|
|
270
|
+
- `expiresAt` is `null`, or
|
|
271
|
+
- `expiresAt` is later than current time (UTC comparison)
|
|
272
|
+
|
|
273
|
+
Evaluation rules:
|
|
274
|
+
|
|
275
|
+
- If an active explicit license record exists for an SPDX identifier, that license is allowed (record category is metadata and does not gate the decision).
|
|
276
|
+
- If no explicit license record exists, SPDX category fallback uses `policy.allowedCategories`.
|
|
277
|
+
- SPDX expressions are parsed structurally (for example `MIT OR Apache-2.0`) before evaluating allow rules.
|
|
278
|
+
- If only expired records match and no active follow-up exists, this is a violation.
|
|
279
|
+
- Exceptions support `exact`, `range` (semver), and `any` scopes.
|
|
280
|
+
- If a violation is covered by an active exception, gate passes for that package.
|
|
281
|
+
- If only expired exception records match and no active follow-up exists, gate fails.
|
|
282
|
+
|
|
283
|
+
Violation messages include expired record details and remediation commands.
|
|
284
|
+
|
|
285
|
+
## CI / GitHub Actions
|
|
286
|
+
|
|
287
|
+
Example:
|
|
48
288
|
|
|
49
289
|
```yaml
|
|
50
290
|
- uses: actions/checkout@v4
|
|
@@ -53,13 +293,48 @@ Allowlist file schema:
|
|
|
53
293
|
node-version: 20
|
|
54
294
|
cache: npm
|
|
55
295
|
- run: npm ci
|
|
56
|
-
- run: npx periapsis --
|
|
57
|
-
- run: |
|
|
58
|
-
node -e "const fs=require('fs');const p='sbom-violations.json';if(!fs.existsSync(p))process.exit(1);const d=JSON.parse(fs.readFileSync(p,'utf8'));const c=Array.isArray(d)?d.length:Array.isArray(d.violations)?d.violations.length:0;if(c>0){console.error(`Found ${c} license violations`);process.exit(1);}console.log('No license violations');"
|
|
296
|
+
- run: npx periapsis --violations-out sbom-violations.json
|
|
59
297
|
```
|
|
60
298
|
|
|
61
|
-
|
|
299
|
+
When violations exist, Periapsis exits non-zero and prints deterministic markdown summary suitable for Actions logs.
|
|
300
|
+
|
|
301
|
+
## Troubleshooting Large Violation Sets
|
|
302
|
+
|
|
303
|
+
When you get a large burst of violations, use this order to reduce noise quickly:
|
|
304
|
+
|
|
305
|
+
1. Confirm you are running the expected CLI version.
|
|
306
|
+
2. Confirm policy files are being read from the expected repo/path.
|
|
307
|
+
3. Group by `Type` in the output and fix one class at a time.
|
|
308
|
+
|
|
309
|
+
Quick checks:
|
|
310
|
+
|
|
311
|
+
- If many rows show `license-not-allowed`, add explicit records via `periapsis licenses allow add` for the most common licenses first (`MIT`, `Apache-2.0`, `ISC`, `BSD-3-Clause`).
|
|
312
|
+
- If many rows show `expired-license-policy` or `expired-exception`, add follow-up records instead of editing/deleting old records.
|
|
313
|
+
- If one package appears repeatedly blocked across versions, prefer a targeted exception with `scope.type = "range"` or `exact`.
|
|
314
|
+
- If unknown license expressions are noisy and expected, decide whether to keep strict mode or set `failOnUnknownLicense` to `false` in `policy/policy.json`.
|
|
315
|
+
- If a command fails with schema validation errors, fix the specific field path reported, then rerun.
|
|
316
|
+
- If violations are mostly from test/build tooling, start with `--production-only`, then expand scope incrementally.
|
|
317
|
+
|
|
318
|
+
Recommended triage workflow:
|
|
319
|
+
|
|
320
|
+
1. Run `npx periapsis --violations-out sbom-violations.json`.
|
|
321
|
+
2. Count by license in `sbom-licenses.json` and prioritize highest-frequency licenses.
|
|
322
|
+
3. Add 1-3 high-impact explicit license records.
|
|
323
|
+
4. Re-run and verify violation count drops.
|
|
324
|
+
5. Add narrowly scoped exceptions only for true outliers.
|
|
325
|
+
|
|
326
|
+
Team process tips:
|
|
327
|
+
|
|
328
|
+
- Treat `licenses.json` as strategic policy (broad impact) and `exceptions.json` as tactical policy (narrow impact).
|
|
329
|
+
- Require CODEOWNERS/legal-security review for policy edits.
|
|
330
|
+
- Add expirations intentionally, then renew with follow-up records before they expire.
|
|
331
|
+
|
|
332
|
+
## Governance Recommendation
|
|
333
|
+
|
|
334
|
+
Protect policy changes with CODEOWNERS review:
|
|
335
|
+
|
|
336
|
+
```txt
|
|
337
|
+
/policy/* @security-team @legal-team
|
|
338
|
+
```
|
|
62
339
|
|
|
63
|
-
-
|
|
64
|
-
- Add CI (lint/test) and changelog.
|
|
65
|
-
- Tag a release and publish to npm: `npm publish`.
|
|
340
|
+
Use expiring entries plus follow-up records to preserve decision history without overwriting prior approvals.
|