periapsis
v1.0.3
Published
Lightweight SBOM/license checker with allowlist, exceptions, and upstream chains.
Maintainers
Readme
Periapsis
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.
Install / Run
npm install
npx periapsis --violations-out sbom-violations.jsonInitialize governed policy files:
npx periapsis init --preset strictinit now asks which dependency types should be checked by default (unless provided via flags).
Commands
periapsis: run SBOM + license gateperiapsis init --preset <strict|standard|permissive> [--policy-dir policy] [--force]periapsis exceptions add [--policy-dir policy](interactive)periapsis licenses allow add [--policy-dir policy](interactive)periapsis policy migrate [--from allowedConfig.json] [--policy-dir policy] [--force]
Automation mode:
periapsis exceptions add --non-interactive ...periapsis licenses allow add --non-interactive ...periapsis --dep-types dependencies,peerDependenciesperiapsis --production-only(same as--dep-types dependencies)
Policy Files
Periapsis now uses governed policy metadata files under policy/:
policy/policy.jsonpolicy/licenses.jsonpolicy/exceptions.json
How they work together:
policy/policy.json: global behavior and category fallback policy.policy/licenses.json: explicit license allow records with audit metadata and expiration.policy/exceptions.json: package-level overrides when a dependency cannot be covered by license policy alone.
Load behavior:
- Periapsis prefers
policy/files. - If
policy/policy.jsonis missing, it can temporarily fall back to legacyallowedConfig.json(with warning). - Use
periapsis policy migrateto move legacy config into governed policy files.
Validation behavior:
- All
policy/*.jsonfiles are schema-validated on load. licenses addandexceptions addvalidate the full policy bundle before writing.- Invalid files fail fast with field-level schema error messages.
policy/policy.json
{
"allowedCategories": [
"Permissive Licenses",
"Weak Copyleft Licenses"
],
"failOnUnknownLicense": true,
"timezone": "America/Edmonton",
"dependencyTypes": [
"dependencies",
"devDependencies",
"peerDependencies",
"optionalDependencies",
"bundledDependencies"
]
}dependencyTypes controls which package groups are checked by default:
dependencies: runtime production packagesdevDependencies: development/test/build packagespeerDependencies: host-provided/shared dependenciesoptionalDependencies: non-critical optional packagesbundledDependencies: dependencies bundled with the package tarball
policy/licenses.json
[
{
"identifier": "MIT",
"category": "Permissive Licenses",
"fullName": "MIT License",
"notes": "Default permissive license.",
"rationale": "Permissive, low legal risk for SaaS distribution.",
"approvedBy": ["security"],
"approvedAt": "2026-02-13T18:00:00Z",
"expiresAt": null,
"evidenceRef": "JIRA-1234"
}
]policy/exceptions.json
[
{
"package": "@pdftron/pdfjs-express-viewer",
"scope": { "type": "exact", "version": "8.7.5" },
"detectedLicenses": ["SEE LICENSE IN LICENSE"],
"reason": "Commercial dependency required for PDF rendering.",
"notes": "Revisit annually.",
"approvedBy": ["legal", "security"],
"approvedAt": "2026-02-13T18:00:00Z",
"expiresAt": "2026-08-13T00:00:00Z",
"evidenceRef": "JIRA-5678"
}
]Practical authoring rules:
- Keep
packageas package name only (for examplecaniuse-lite), and encode version logic inscope. - Prefer
scope.type = "exact"for least risk; userangecarefully; avoidanyunless necessary. - Use non-empty
evidenceRefvalues that link to a ticket, issue, or approval artifact. - Do not delete old records to “update” policy; add follow-up records so history stays audit-friendly.
License Categories
Periapsis uses three license policy categories:
Disclaimer: This section provides operational guidance for engineering policy decisions and is not legal advice. Consult qualified legal counsel for binding interpretation.
Permissive Licenses
Examples:
MITBSDApache-2.0
These generally allow:
- Commercial use
- Modification
- Distribution
With minimal obligations, usually attribution.
Typical risk level for most SMEs: Low.
Weak Copyleft Licenses
Examples:
LGPL
These typically require:
- Sharing modifications to the licensed component
- Following specific distribution rules
Typical risk level: Moderate, depending on usage and distribution model.
Strong Copyleft Licenses
Examples:
GPLAGPL
These may require:
- Distribution of source code when software is distributed
- Sharing modifications
- Careful handling to avoid proprietary code exposure
Typical risk level: High in some commercial contexts.
Interactive Governance Workflows
Add an exception
npx periapsis exceptions addPrompts include:
- package
- scope (
exact,range,any) - detected license identifiers / expression
- reason (required, multiline)
- notes (optional)
- approvedBy (required, comma-separated)
- approvedAt (default now)
- expiresAt (
ISO datetimeornever) - evidenceRef (required)
If same package+scope exists, Periapsis prompts to add a follow-up record (recommended) or edit the latest record.
Add an allowed license
npx periapsis licenses allow addPrompts include:
- SPDX identifier (warns if unknown to local SPDX catalog)
- fullName (optional, auto-filled when known)
- notes
- approvedBy
- approvedAt
- expiresAt (
ISO datetimeornever) - category (
Permissive Licenses,Weak Copyleft Licenses,Strong Copyleft Licenses, orUncategorized / Needs Review) - rationale
- evidenceRef
If identifier already exists, Periapsis appends a follow-up record.
Non-interactive examples
Add allowed license without prompts:
npx periapsis licenses allow add \
--non-interactive \
--identifier MIT \
--approved-by security,legal \
--category "Permissive Licenses" \
--rationale "Approved baseline license" \
--evidence-ref JIRA-1234Add exception without prompts:
npx periapsis exceptions add \
--non-interactive \
--package caniuse-lite \
--scope-type exact \
--version 1.0.30001767 \
--reason "Reviewed and accepted by security" \
--approved-by security \
--expires-at 2027-02-13T00:00:00.000Z \
--evidence-ref JIRA-5678Run checker against only production dependencies:
npx periapsis --production-onlyRun checker against a custom dependency set:
npx periapsis --dep-types dependencies,peerDependenciesExpiration and Follow-up Behavior
A policy record is active when:
expiresAtisnull, orexpiresAtis later than current time (UTC comparison)
Evaluation rules:
- 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).
- If no explicit license record exists, SPDX category fallback uses
policy.allowedCategories. - SPDX expressions are parsed structurally (for example
MIT OR Apache-2.0) before evaluating allow rules. - If only expired records match and no active follow-up exists, this is a violation.
- Exceptions support
exact,range(semver), andanyscopes. - If a violation is covered by an active exception, gate passes for that package.
- If only expired exception records match and no active follow-up exists, gate fails.
Violation messages include expired record details and remediation commands.
CI / GitHub Actions
Example:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npx periapsis --violations-out sbom-violations.jsonWhen violations exist, Periapsis exits non-zero and prints deterministic markdown summary suitable for Actions logs.
Troubleshooting Large Violation Sets
When you get a large burst of violations, use this order to reduce noise quickly:
- Confirm you are running the expected CLI version.
- Confirm policy files are being read from the expected repo/path.
- Group by
Typein the output and fix one class at a time.
Quick checks:
- If many rows show
license-not-allowed, add explicit records viaperiapsis licenses allow addfor the most common licenses first (MIT,Apache-2.0,ISC,BSD-3-Clause). - If many rows show
expired-license-policyorexpired-exception, add follow-up records instead of editing/deleting old records. - If one package appears repeatedly blocked across versions, prefer a targeted exception with
scope.type = "range"orexact. - If unknown license expressions are noisy and expected, decide whether to keep strict mode or set
failOnUnknownLicensetofalseinpolicy/policy.json. - If a command fails with schema validation errors, fix the specific field path reported, then rerun.
- If violations are mostly from test/build tooling, start with
--production-only, then expand scope incrementally.
Recommended triage workflow:
- Run
npx periapsis --violations-out sbom-violations.json. - Count by license in
sbom-licenses.jsonand prioritize highest-frequency licenses. - Add 1-3 high-impact explicit license records.
- Re-run and verify violation count drops.
- Add narrowly scoped exceptions only for true outliers.
Team process tips:
- Treat
licenses.jsonas strategic policy (broad impact) andexceptions.jsonas tactical policy (narrow impact). - Require CODEOWNERS/legal-security review for policy edits.
- Add expirations intentionally, then renew with follow-up records before they expire.
Governance Recommendation
Protect policy changes with CODEOWNERS review:
/policy/* @security-team @legal-teamUse expiring entries plus follow-up records to preserve decision history without overwriting prior approvals.
