@blamejs/core 0.9.5 → 0.9.7
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/CHANGELOG.md +2 -0
- package/index.js +2 -0
- package/lib/compliance.js +61 -0
- package/lib/vex.js +365 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.9.x
|
|
10
10
|
|
|
11
|
+
- v0.9.7 (2026-05-13) — **SECURITY.md: release-tag verification path documented + signed-tag invariant from v0.9.7+**. SECURITY.md gains a "Verifying release authenticity" section documenting how operators verify a release tag's authenticity independently of GitHub's UI. The maintainer Ed25519 SSH signing key fingerprint (`SHA256:5oF/XWhFpMde9TRfEX2GAHiApAq/MXOS4vti5zQbD7g`) is published alongside the public-key retrieval URL (`https://github.com/dotCooCoo.keys`) and a `git tag -v` recipe that bypasses the "Verified" badge. From v0.9.7 onward, every release tag is an annotated SSH-signed tag; the repository's `release-tags` ruleset's `required_signatures` rule refuses any unsigned or lightweight tag push at the server side. Earlier tags (v0.9.6 and prior) remain as lightweight commits and don't verify via `git tag -v`; they continue to verify via the SLSA L3 npm provenance + Sigstore-keyless SBOM signatures already attached to those releases (the `cosign verify-blob` recipe is in the same SECURITY.md section). No framework-surface changes; this release ships the documentation + invariant only.
|
|
12
|
+
- v0.9.6 (2026-05-12) — **`b.vex` (OASIS CSAF 2.1 VEX) + framework-control compliance posture sweep**. *(PR feedback: CSAF-conformance fixes folded in pre-merge — `cwes` is now a list per §3.2.3.4 instead of a singleton `cwe` field; CWE alone is no longer accepted as a vulnerability identity per §3.2.3.2 (operator supplies `cveId` or `ids[]: [{ systemName, text }]` per §3.2.3.5); TLP allowlist corrected to TLP 2.0 (FIRST 2022) per §3.2.1.12.1.1 — `CLEAR / GREEN / AMBER / AMBER+STRICT / RED` (added the previously-omitted `AMBER+STRICT` restriction tier and removed the legacy TLP 1.0 `WHITE` label, which was renamed `CLEAR` in TLP 2.0). Public opt name `cwe` is now `cweId` to mirror `cveId`; this is a v0.9.6 surface that never shipped to npm so the rename is not a breaking change.)* Closes the framework-side findings from the 2026-05-11 exceptd framework-gap-analysis (49 gaps across CVE-triage / framework-compliance / threat-modeling / AI-security / identity-assurance / crypto-posture / supply-chain / sector-specific). **`b.vex.statement({ cveId, status, productIds, justification?, impactStatement?, references?, firstReleased?, lastUpdated? })`** builds an OASIS CSAF 2.1 §3.2.3 vulnerability statement with `product_status` keyed by status enum (`known_not_affected` / `affected` / `fixed` / `under_investigation`), `flags[].label` for §3.2.2.7 justifications (`component_not_present` / `vulnerable_code_not_present` / `vulnerable_code_not_in_execute_path` / `vulnerable_code_cannot_be_controlled_by_adversary` / `inline_mitigations_already_exist`), and `notes[].text` for impact narrative. Refuses missing CVE/CWE id, malformed CVE shape, unknown status, missing productIds, and `known_not_affected` without justification. **`b.vex.document({ documentId, title, publisher, trackingId, trackingVersion, currentReleaseDate, initialReleaseDate, statements, tlp? })`** assembles the §3.2 CSAF document envelope with category `csaf_vex`, csaf_version `2.1`, publisher category `vendor`, tracking status `final`, and `distribution.tlp.label` (default `CLEAR`; refuses non-TLP labels). **`b.vex.serialize(doc)`** routes through `b.canonicalJson.stringify` for byte-stable sorted-key output then re-indents at 2 spaces for human-diffable artifacts. Exports `STATUS_VALUES` / `JUSTIFICATION_VALUES` / `TLP_LABELS` / `CSAF_VERSION` / `VexError`. **25 new compliance postures** added to `b.compliance.KNOWN_POSTURES` (with matching `POSTURE_DEFAULTS` cascade entries): `nist-800-53` (NIST SP 800-53 Rev 5 control catalog), `nist-ai-rmf-1.0` (NIST AI Risk Management Framework 1.0), `iso-42001-2023` (AI management systems), `iso-23894-2023` (AI risk management guidance), `owasp-llm-top-10-2025` (LLM application risk catalog), `owasp-asvs-v5.0` (Application Security Verification Standard v5.0), `nist-800-218-ssdf` (Secure Software Development Framework), `nist-800-82-r3` (industrial control systems), `nist-800-63b-rev4` (digital identity authenticator guidance), `iec-62443-3-3` (industrial security), `fedramp-rev5-moderate` (federal cloud baseline), `hipaa-security-rule` (45 CFR §164.302-318 administrative + technical safeguards), `hitrust-csf-v11.4` (healthcare common security framework), `nerc-cip-007-6` (bulk electric system cyber asset security), `psd2-rts-sca` (PSD2 Regulatory Technical Standards for Strong Customer Authentication), `swift-cscf-v2026` (SWIFT Customer Security Controls Framework 2026), `slsa-v1.0-build-l3` (SLSA build-track L3 provenance), `vex-csaf-2.1` (the standard `b.vex` emits), `cyclonedx-v1.6` (already shipped via `sbom.cdx.json`), `spdx-v3.0` (SPDX 3.0 software bill of materials), `owasp-wstg-v5` (Web Security Testing Guide v5), `ptes` (Penetration Testing Execution Standard), `nist-800-115` (technical guide to information security testing), `cwe-top-25-2024` (CWE most dangerous software weaknesses 2024), `cis-controls-v8` (Center for Internet Security Critical Controls v8), `cmmc-2.0-level-2` (DoD CMMC Level 2 advanced; complements the existing `cmmc-2.0` posture). Each cascade entry encodes the regime's data-tier mandate (encrypted backups + signed audit chain + TLS 1.3 minimum + vacuum-after-erase where applicable).
|
|
11
13
|
- v0.9.5 (2026-05-12) — **Fix-up for v0.9.3 + v0.9.4 audit-derived primitives** (five reported reachability/contract bugs). (1) **`b.middleware.dpop` `trustForwardedHeaders` was unreachable** — the v0.9.4 X-Forwarded-* trust gate added the option to `_reconstructHtu` but the `create()` validateOpts whitelist still rejected unknown keys. Operators behind a trusted reverse proxy got `unknown-option` instead of the documented opt-in, leaving valid DPoP proofs failing htu matching. The whitelist now includes `trustForwardedHeaders`. (2) **`b.auth.jwt.verifyExternal` `allowKidlessJwks` was unreachable** — same shape, fixed the same way. (3) **OAuth `allowKidlessJwks` didn't reach token-exchange flows** — pre-v0.9.5 the opt was per-`verifyIdToken`-call, but `_normalizeTokens()` (called from `exchangeCode` / `pollDeviceCode` / `exchangeToken` / `refreshAccessToken`) passed a reduced `{ nonce, skipNonceCheck }` shape that dropped the operator opt. Surface promoted to client-level: pass `b.auth.oauth.create({ allowKidlessJwks: true })` once and it threads through every code path that lands on the verifier. The per-call `vopts.allowKidlessJwks` continues to work for direct `verifyIdToken` callers. (4) **`b.auth.oauth.refreshAccessToken` `checkAndInsert` return-value contract inverted** — pre-v0.9.5 interpreted `true` as "already seen → replay" but the framework-wide `checkAndInsert` contract (`b.nonceStore`, `b.auth.jwt`) is the opposite: `true` = unseen-and-now-inserted (first sighting), `false` = already-present (replay). Operators reusing an existing `b.nonceStore`-style backend got every first refresh attempt rejected as token theft, breaking normal refresh flows. The handler now normalizes `inserted === false` → `alreadySeen = true`, consistent with the rest of the framework. (5) **`b.auth.ciba` `_intervalState` memory leak on error paths** — pre-v0.9.5 entries were only deleted on successful token issuance; denied / expired auth requests, and ping/push delivery modes that never call `pollToken` successfully, left permanent entries causing unbounded growth in long-running processes. Now entries carry an `expireAtMs` derived from the IdP-supplied `expires_in` of the auth_req_id, and an opportunistic sweep runs on every `_registerInitialInterval` call (no separate timer needed). Terminal CIBA errors (`expired_token` / `access_denied` / `invalid_grant` / `transaction_failed`) also delete the entry immediately on the error path.
|
|
12
14
|
- v0.9.4 (2026-05-12) — **Audit hardening slice 4: kid-less JWKS lookup refusal + OCSP nonce CT compare + OAuth scope strict-split + DPoP `X-Forwarded-Proto` trust gate**. Closes the remaining MEDIUM-tier findings from the 2026-05-11 auth audit. **`b.auth.oauth.verifyIdToken` + `b.auth.jwt.verifyExternal` kid-less JWKS lookup refusal** — pre-v0.9.4 both verifiers fell back to `keys[0]` when the token carried NO `kid` and the JWKS had exactly one key. This is a latent vector during JWKS rotation: an attacker shipping a kid-less token gets the lone-key path during the window the rotated-out key is still cached at the IdP but the rotated-in key is already published. Every modern IdP includes `kid`; the framework now refuses kid-less tokens unconditionally. Operators with non-conforming IdPs that genuinely emit kid-less tokens opt out via `vopts.allowKidlessJwks: true`. **`b.network.tls` OCSP nonce constant-time compare** — `evaluateOcspResponse`'s `expectedNonce` match migrated from `Buffer.equals` to `b.crypto.timingSafeEqual` for module-wide consistency with the Merkle-root / NTS-cookie / cert-fingerprint paths that already use `timingSafeEqual`. **`b.auth.oauth` scope strict whitespace split** — RFC 6749 §3.3 says `scope` is space-separated, ONLY `U+0020`. Pre-v0.9.4 `raw.scope.split(/\s+/)` matched U+0085 NEL, U+00A0 NBSP, etc., so a hostile AS returning `scope: "admin<NEL>read"` would surface as `["admin", "read"]` and the operator's scope allowlist saw two distinct scopes. Now splits on single-space only; empty pieces filtered out. **`b.middleware.dpop` `X-Forwarded-*` trust gate** — `_reconstructHtu` previously read `X-Forwarded-Proto` / `X-Forwarded-Host` unconditionally; an attacker who can hit the origin directly while spoofing `X-Forwarded-Proto: https` could trick the middleware into building an `https` htu that the DPoP proof was signed for, when the origin is actually serving HTTP (RFC 9449 §4.3 says the htu MUST be the absolute URL the request was sent to). The default now derives proto/host from the socket; operators with a confirmed-trusted front proxy opt in via `opts.trustForwardedHeaders: true`.
|
|
13
15
|
- v0.9.3 (2026-05-11) — **Audit hardening slice 3: OAuth + OID4VCI + OID4VP + CIBA + constant-time-compare migrations**. Continues the 2026-05-11 auth audit follow-through. **`b.auth.oauth.refreshAccessToken` atomic check-and-insert** — new `ropts.checkAndInsert(token, expireAtMs)` callback contract replaces the previous `ropts.seen(token)` check-then-act race. Two concurrent refresh requests on the same event-loop tick could both see `seen === false` and both POST to the token endpoint, neither flagging the replay; the new contract requires an atomic test-and-set (Redis SETNX, DB INSERT ON CONFLICT) and is the OAuth 2.1 §6.1 / RFC 9700 §4.13 one-time-use defense surfacing the actual race window. Legacy `seen` callback continues to work for backwards-compat with operator code; the docstring documents the race + recommends migration to `checkAndInsert`. **`b.auth.oid4vci` constant-time compares** — pre-auth `tx_code` hash compare (was `!==` on sha3 hex) and proof-JWT `c_nonce` compare (was `!==` on attacker-supplied wallet payload) both route through `b.crypto.timingSafeEqual`. **`b.auth.oid4vp` per-presentation `vct` enforcement** — DCQL filters with 2+ `vct_values` entries previously bypassed vct validation entirely (the framework only set `expectedVct` when the filter pinned to a single value). Verifier now validates the presented vct against the DCQL filter list manually when length > 1; refuses with `vp_token['<id>'][<n>] vct '<presented>' is not in DCQL vct_values [...]` on over-disclosure. **`b.auth.ciba` slow_down honoring** — CIBA §11.3 requires the client to increase its polling interval by at least 5s on every `slow_down` response. Pre-v0.9.3 the framework client never bumped, leaving operators to do their own interval bookkeeping. Now `pollToken()` tracks per-`authReqId` interval state internally (Map keyed by authReqId, seeded from `startAuthentication`'s response, cleared on token issuance), bumps by `max(5s, IdP-suggested interval) <= MAX_INTERVAL_SEC` on every slow_down, and attaches the next-suggested interval to the thrown `auth-ciba/slow_down` error as `err.nextIntervalSec` so operators read a spec-correct back-off without manual bookkeeping. **`b.auth.ciba` notification-token entropy** — `clientNotificationToken` now refuses < 32 chars per CIBA §7.1.2's opaque-hard-to-guess requirement. Pre-v0.9.3 a 4-char token passed. **`b.auth.ciba.parseNotification` constant-time compare** — bearer-token hash compare migrated from `!==` to `b.crypto.timingSafeEqual` (both sides are fixed-width sha3-512 hex strings; defense-in-depth even though equal-length JS string compare is already widely understood as constant-time on V8).
|
package/index.js
CHANGED
|
@@ -148,6 +148,7 @@ var cacheStatus = require("./lib/cache-status");
|
|
|
148
148
|
var cdnCacheControl = require("./lib/cdn-cache-control");
|
|
149
149
|
var clientHints = require("./lib/client-hints");
|
|
150
150
|
var structuredFields = require("./lib/structured-fields");
|
|
151
|
+
var vex = require("./lib/vex");
|
|
151
152
|
var serverTiming = require("./lib/server-timing");
|
|
152
153
|
var earlyHints = require("./lib/early-hints");
|
|
153
154
|
var gateContract = require("./lib/gate-contract");
|
|
@@ -383,6 +384,7 @@ module.exports = {
|
|
|
383
384
|
cdnCacheControl: cdnCacheControl,
|
|
384
385
|
clientHints: clientHints,
|
|
385
386
|
structuredFields: structuredFields,
|
|
387
|
+
vex: vex,
|
|
386
388
|
serverTiming: serverTiming,
|
|
387
389
|
earlyHints: earlyHints,
|
|
388
390
|
gateContract: gateContract,
|
package/lib/compliance.js
CHANGED
|
@@ -212,6 +212,40 @@ var KNOWN_POSTURES = Object.freeze([
|
|
|
212
212
|
"nist-800-66-r2", // NIST SP 800-66 Rev 2 — HIPAA Security Rule implementation guidance // allow:raw-byte-literal — NIST publication number, not bytes
|
|
213
213
|
"ehds", // EU European Health Data Space (Regulation 2025/327; phased 2027-2029)
|
|
214
214
|
"circia", // US Cyber Incident Reporting for Critical Infrastructure Act (final rule pending)
|
|
215
|
+
// ---- v0.9.6 expansion — exceptd framework-control-gap closure ----
|
|
216
|
+
// Postures added to recognise every framework cited in the
|
|
217
|
+
// exceptd 2026-05-11 framework-control-gaps catalog. Each posture
|
|
218
|
+
// either (a) maps to a framework the operator must audit against,
|
|
219
|
+
// or (b) recognises a security testing methodology / SBOM /
|
|
220
|
+
// supply-chain attestation standard. Operators pin the posture
|
|
221
|
+
// and the framework's cascade defaults + audit emissions match
|
|
222
|
+
// the named regime's evidence expectations.
|
|
223
|
+
"nist-800-53", // NIST SP 800-53 Rev 5 — full Moderate / High baseline
|
|
224
|
+
"nist-ai-rmf-1.0", // NIST AI Risk Management Framework 1.0
|
|
225
|
+
"iso-42001-2023", // ISO/IEC 42001:2023 — AI management system (alias for v0.8.81 iso-42001 entry, kept for posture-vocabulary stability) // allow:raw-byte-literal — standard publication year, not bytes
|
|
226
|
+
"iso-23894-2023", // ISO/IEC 23894:2023 — AI risk management guidance (alias)
|
|
227
|
+
"owasp-llm-top-10-2025", // OWASP Top 10 for LLM Applications 2025
|
|
228
|
+
"owasp-asvs-v5.0", // OWASP Application Security Verification Standard v5.0
|
|
229
|
+
"nist-800-218-ssdf", // NIST SP 800-218 Secure Software Development Framework v1.1 // allow:raw-byte-literal — NIST pub number, not bytes
|
|
230
|
+
"nist-800-82-r3", // NIST SP 800-82 Rev 3 — OT security guide // allow:raw-byte-literal — NIST pub number, not bytes
|
|
231
|
+
"nist-800-63b-rev4", // NIST SP 800-63B Rev 4 — Digital Identity (AAL/IAL/FAL)
|
|
232
|
+
"iec-62443-3-3", // IEC 62443-3-3 — IACS system security
|
|
233
|
+
"fedramp-rev5-moderate", // FedRAMP Rev 5 Moderate baseline
|
|
234
|
+
"hipaa-security-rule", // HIPAA Security Rule 45 CFR §164.312 (technical safeguards) // allow:raw-byte-literal — CFR section, not bytes
|
|
235
|
+
"hitrust-csf-v11.4", // HITRUST CSF v11.4
|
|
236
|
+
"nerc-cip-007-6", // NERC CIP-007-6 — BES Cyber System Security Management
|
|
237
|
+
"psd2-rts-sca", // EU PSD2 RTS on Strong Customer Authentication (Commission Delegated Regulation 2018/389)
|
|
238
|
+
"swift-cscf-v2026", // SWIFT Customer Security Controls Framework v2026
|
|
239
|
+
"slsa-v1.0-build-l3", // SLSA v1.0 Build Track Level 3
|
|
240
|
+
"vex-csaf-2.1", // VEX via OASIS CSAF 2.1 — b.vex primitive ships this
|
|
241
|
+
"cyclonedx-v1.6", // CycloneDX v1.6 SBOM — framework ships sbom.cdx.json
|
|
242
|
+
"spdx-v3.0", // SPDX v3.0 SBOM — framework ships sbom.spdx.json (v0.9.6+)
|
|
243
|
+
"owasp-wstg-v5", // OWASP Web Security Testing Guide v5
|
|
244
|
+
"ptes", // Penetration Testing Execution Standard
|
|
245
|
+
"nist-800-115", // NIST SP 800-115 Technical Guide to Information Security Testing // allow:raw-byte-literal — NIST pub number, not bytes
|
|
246
|
+
"cwe-top-25-2024", // CWE Top 25 Most Dangerous Software Weaknesses (2024)
|
|
247
|
+
"cis-controls-v8", // CIS Controls v8
|
|
248
|
+
"cmmc-2.0-level-2", // CMMC 2.0 Level 2 (Advanced) — 110 NIST 800-171 Rev 2 controls // allow:raw-byte-literal — NIST pub number / level, not bytes
|
|
215
249
|
]);
|
|
216
250
|
|
|
217
251
|
var STATE = { posture: null, setAt: null };
|
|
@@ -964,6 +998,33 @@ var POSTURE_DEFAULTS = Object.freeze({
|
|
|
964
998
|
"nist-800-66-r2": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
|
|
965
999
|
"ehds": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
|
|
966
1000
|
"circia": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1001
|
+
// ---- v0.9.6 — exceptd framework-control-gap closure cascade ----
|
|
1002
|
+
"nist-800-53": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
|
|
1003
|
+
"nist-ai-rmf-1.0": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1004
|
+
"iso-42001-2023": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
|
|
1005
|
+
"iso-23894-2023": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1006
|
+
"owasp-llm-top-10-2025": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1007
|
+
"owasp-asvs-v5.0": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1008
|
+
"nist-800-218-ssdf": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1009
|
+
"nist-800-82-r3": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1010
|
+
"nist-800-63b-rev4": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1011
|
+
"iec-62443-3-3": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1012
|
+
"fedramp-rev5-moderate": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
|
|
1013
|
+
"hipaa-security-rule": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
|
|
1014
|
+
"hitrust-csf-v11.4": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
|
|
1015
|
+
"nerc-cip-007-6": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1016
|
+
"psd2-rts-sca": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1017
|
+
"swift-cscf-v2026": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1018
|
+
"slsa-v1.0-build-l3": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1019
|
+
"vex-csaf-2.1": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1020
|
+
"cyclonedx-v1.6": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1021
|
+
"spdx-v3.0": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1022
|
+
"owasp-wstg-v5": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1023
|
+
"ptes": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1024
|
+
"nist-800-115": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1025
|
+
"cwe-top-25-2024": Object.freeze({ backupEncryptionRequired: false, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1026
|
+
"cis-controls-v8": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: false }),
|
|
1027
|
+
"cmmc-2.0-level-2": Object.freeze({ backupEncryptionRequired: true, auditChainSignedRequired: true, tlsMinVersion: "TLSv1.3", requireVacuumAfterErase: true }),
|
|
967
1028
|
});
|
|
968
1029
|
|
|
969
1030
|
/**
|
package/lib/vex.js
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.vex
|
|
4
|
+
* @nav Supply Chain
|
|
5
|
+
* @title VEX — OASIS CSAF 2.1 Vulnerability Exploitability eXchange
|
|
6
|
+
* @order 720
|
|
7
|
+
*
|
|
8
|
+
* @intro
|
|
9
|
+
* VEX (Vulnerability Exploitability eXchange) statement builder per
|
|
10
|
+
* OASIS CSAF 2.1 §4.4. Operators ship a `vex.cdx.json` alongside
|
|
11
|
+
* `sbom.cdx.json` declaring per-vulnerability exploitability state
|
|
12
|
+
* for the framework's component set. Status vocabulary:
|
|
13
|
+
*
|
|
14
|
+
* "not_affected" — framework does not include / does not use
|
|
15
|
+
* the vulnerable component
|
|
16
|
+
* "affected" — framework includes and uses the vulnerable
|
|
17
|
+
* component; remediation required
|
|
18
|
+
* "fixed" — framework included the vulnerable component
|
|
19
|
+
* previously; the cited version ships the fix
|
|
20
|
+
* "under_investigation" — disclosure is being evaluated
|
|
21
|
+
*
|
|
22
|
+
* Justifications (when status=not_affected): `component_not_present`,
|
|
23
|
+
* `vulnerable_code_not_present`, `vulnerable_code_not_in_execute_path`,
|
|
24
|
+
* `vulnerable_code_cannot_be_controlled_by_adversary`,
|
|
25
|
+
* `inline_mitigations_already_exist`.
|
|
26
|
+
*
|
|
27
|
+
* `b.vex.statement({...})` produces a single VEX vulnerability
|
|
28
|
+
* record. `b.vex.document({...})` assembles a complete CSAF 2.1
|
|
29
|
+
* document with the framework's distributor metadata. `b.vex.serialize`
|
|
30
|
+
* round-trips to canonical JSON (RFC 8785 / sorted keys) for
|
|
31
|
+
* signing. Output is operator-shippable alongside SBOM.
|
|
32
|
+
*
|
|
33
|
+
* Why the framework ships VEX: the exceptd 2026-05-12 gap analysis
|
|
34
|
+
* surfaced VEX-CSAF-v2.1 as a 49-gap framework-control gap. The
|
|
35
|
+
* framework-side closure is "vendor-supplied VEX statements for
|
|
36
|
+
* every disclosed CVE the framework has been audited against."
|
|
37
|
+
* Operators consume the framework's VEX to populate their own
|
|
38
|
+
* organisational VEX without re-auditing each framework dependency.
|
|
39
|
+
*
|
|
40
|
+
* @card
|
|
41
|
+
* OASIS CSAF 2.1 VEX statement + document builder. Operators ship
|
|
42
|
+
* `vex.cdx.json` alongside `sbom.cdx.json` to declare per-CVE
|
|
43
|
+
* exploitability state. Framework provides VEX statements for its
|
|
44
|
+
* own denied-vendor set + audit-cleared dependencies.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
var canonicalJson = require("./canonical-json");
|
|
48
|
+
var validateOpts = require("./validate-opts");
|
|
49
|
+
var { defineClass } = require("./framework-error");
|
|
50
|
+
|
|
51
|
+
var VexError = defineClass("VexError", { alwaysPermanent: true });
|
|
52
|
+
|
|
53
|
+
// OASIS CSAF 2.1 §3.2.1 — top-level document structure constants.
|
|
54
|
+
var CSAF_VERSION = "2.1";
|
|
55
|
+
var DOCUMENT_CATEGORY_VEX = "csaf_vex";
|
|
56
|
+
|
|
57
|
+
// CSAF 2.1 §3.2.2.10 product_status vocabulary (relevant subset for VEX).
|
|
58
|
+
var STATUS_VALUES = Object.freeze([
|
|
59
|
+
"first_affected", "first_fixed", "fixed", "known_affected",
|
|
60
|
+
"known_not_affected", "last_affected", "recommended",
|
|
61
|
+
"under_investigation",
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
// CSAF 2.1 §3.2.2.7 — justifications for known_not_affected.
|
|
65
|
+
var JUSTIFICATION_VALUES = Object.freeze([
|
|
66
|
+
"component_not_present",
|
|
67
|
+
"vulnerable_code_not_present",
|
|
68
|
+
"vulnerable_code_not_in_execute_path",
|
|
69
|
+
"vulnerable_code_cannot_be_controlled_by_adversary",
|
|
70
|
+
"inline_mitigations_already_exist",
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
// CSAF 2.1 §3.2.1.12.1.1 — TLP 2.0 (FIRST 2022) labels. TLP:WHITE
|
|
74
|
+
// was renamed CLEAR in TLP 2.0; AMBER+STRICT is the additional
|
|
75
|
+
// restriction tier introduced in TLP 2.0. CSAF 2.1 aligns with TLP
|
|
76
|
+
// 2.0 — operators emitting WHITE or omitting AMBER+STRICT get
|
|
77
|
+
// downstream validation failures against spec-conformant tooling.
|
|
78
|
+
var TLP_LABELS = Object.freeze(["CLEAR", "GREEN", "AMBER", "AMBER+STRICT", "RED"]);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @primitive b.vex.statement
|
|
82
|
+
* @signature b.vex.statement(opts)
|
|
83
|
+
* @since 0.9.6
|
|
84
|
+
* @status stable
|
|
85
|
+
* @related b.vex.document, b.vex.serialize
|
|
86
|
+
*
|
|
87
|
+
* Build a single CSAF 2.1 VEX vulnerability record. Returns an object
|
|
88
|
+
* shaped per CSAF 2.1 §3.2.3 vulnerability schema with the supplied
|
|
89
|
+
* CVE ID + product status + (when applicable) justification + impact
|
|
90
|
+
* statement.
|
|
91
|
+
*
|
|
92
|
+
* Required: a vulnerability identity — `cveId` (CSAF §3.2.3.2)
|
|
93
|
+
* and/or `ids` (CSAF §3.2.3.5 — array of `{ systemName, text }`
|
|
94
|
+
* non-CVE tracking identifiers for advisories without an assigned
|
|
95
|
+
* CVE). A `cweId` alone is NOT a valid CSAF vulnerability identity
|
|
96
|
+
* (CWE is a weakness classification, not a per-vulnerability id);
|
|
97
|
+
* supply `ids` alongside `cweId` when issuing a non-CVE statement.
|
|
98
|
+
* Also required: `status` (one of STATUS_VALUES), `productIds`
|
|
99
|
+
* (array of product identifiers the statement applies to).
|
|
100
|
+
*
|
|
101
|
+
* When `status === "known_not_affected"`, `justification` is required
|
|
102
|
+
* per CSAF 2.1 §3.2.3.13.
|
|
103
|
+
*
|
|
104
|
+
* @opts
|
|
105
|
+
* cveId: string, // CVE-YYYY-NNNN
|
|
106
|
+
* cweId: string, // CWE-NNN (emitted as cwes[0] per CSAF §3.2.3.4)
|
|
107
|
+
* ids: object[], // [{ systemName, text }] non-CVE tracking ids
|
|
108
|
+
* title: string, // human-readable vulnerability title
|
|
109
|
+
* status: string, // one of STATUS_VALUES
|
|
110
|
+
* productIds: string[], // CSAF product identifiers
|
|
111
|
+
* justification: string, // required when status=known_not_affected
|
|
112
|
+
* impactStatement: string, // operator-readable impact / mitigation note
|
|
113
|
+
* references: string[], // URIs to advisories / vendor pages
|
|
114
|
+
* firstReleased: string, // ISO 8601 timestamp
|
|
115
|
+
* lastUpdated: string, // ISO 8601 timestamp
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* b.vex.statement({
|
|
119
|
+
* cveId: "CVE-2024-21505",
|
|
120
|
+
* title: "axios SSRF",
|
|
121
|
+
* status: "known_not_affected",
|
|
122
|
+
* productIds: ["@blamejs/core"],
|
|
123
|
+
* justification: "component_not_present",
|
|
124
|
+
* impactStatement: "blamejs ships zero npm runtime deps; axios is never imported.",
|
|
125
|
+
* });
|
|
126
|
+
*/
|
|
127
|
+
function statement(opts) {
|
|
128
|
+
if (!opts || typeof opts !== "object" || Array.isArray(opts)) {
|
|
129
|
+
throw new VexError("vex/bad-opts",
|
|
130
|
+
"statement: opts must be a non-null object");
|
|
131
|
+
}
|
|
132
|
+
// CSAF 2.1 §3.2.3 — vulnerability identity. cveId OR ids is
|
|
133
|
+
// required. CWE alone is NOT a valid identity (CWE is a weakness
|
|
134
|
+
// classification, not a per-vulnerability id).
|
|
135
|
+
var hasIds = Array.isArray(opts.ids) && opts.ids.length > 0;
|
|
136
|
+
if (!opts.cveId && !hasIds) {
|
|
137
|
+
throw new VexError("vex/missing-vuln-id",
|
|
138
|
+
"statement: cveId or ids[] is required (CWE alone is not a CSAF " +
|
|
139
|
+
"vulnerability identity per §3.2.3.2 / §3.2.3.5)");
|
|
140
|
+
}
|
|
141
|
+
if (opts.cveId !== undefined) {
|
|
142
|
+
if (typeof opts.cveId !== "string" || !/^CVE-\d{4}-\d{4,}$/.test(opts.cveId)) {
|
|
143
|
+
throw new VexError("vex/bad-cve-id",
|
|
144
|
+
"statement: cveId must match `CVE-YYYY-NNNN` (got '" + opts.cveId + "')");
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (opts.cweId !== undefined) {
|
|
148
|
+
if (typeof opts.cweId !== "string" || !/^CWE-\d+$/.test(opts.cweId)) {
|
|
149
|
+
throw new VexError("vex/bad-cwe-id",
|
|
150
|
+
"statement: cweId must match `CWE-NNN` (got '" + opts.cweId + "')");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (opts.ids !== undefined) {
|
|
154
|
+
if (!Array.isArray(opts.ids)) {
|
|
155
|
+
throw new VexError("vex/bad-ids",
|
|
156
|
+
"statement: ids must be an array of { systemName, text }");
|
|
157
|
+
}
|
|
158
|
+
for (var ii = 0; ii < opts.ids.length; ii++) {
|
|
159
|
+
var entry = opts.ids[ii];
|
|
160
|
+
if (!entry || typeof entry !== "object" ||
|
|
161
|
+
typeof entry.systemName !== "string" || entry.systemName.length === 0 ||
|
|
162
|
+
typeof entry.text !== "string" || entry.text.length === 0) {
|
|
163
|
+
throw new VexError("vex/bad-ids",
|
|
164
|
+
"statement: ids[" + ii + "] must be { systemName: string, text: string }");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (STATUS_VALUES.indexOf(opts.status) === -1) {
|
|
169
|
+
throw new VexError("vex/bad-status",
|
|
170
|
+
"statement: status must be one of " + STATUS_VALUES.join(" / "));
|
|
171
|
+
}
|
|
172
|
+
if (opts.productIds === undefined || opts.productIds === null) {
|
|
173
|
+
throw new VexError("vex/missing-product-ids",
|
|
174
|
+
"statement: productIds is required (non-empty string array)");
|
|
175
|
+
}
|
|
176
|
+
validateOpts.optionalNonEmptyStringArray(opts.productIds, "statement.productIds",
|
|
177
|
+
VexError, "vex/bad-product-id");
|
|
178
|
+
if (opts.productIds.length === 0) {
|
|
179
|
+
throw new VexError("vex/missing-product-ids",
|
|
180
|
+
"statement: productIds must be a non-empty string array");
|
|
181
|
+
}
|
|
182
|
+
if (opts.status === "known_not_affected") {
|
|
183
|
+
if (!opts.justification || JUSTIFICATION_VALUES.indexOf(opts.justification) === -1) {
|
|
184
|
+
throw new VexError("vex/missing-justification",
|
|
185
|
+
"statement: when status=known_not_affected, justification is " +
|
|
186
|
+
"required (one of " + JUSTIFICATION_VALUES.join(" / ") + ")");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
var vuln = {};
|
|
191
|
+
if (opts.cveId) vuln.cve = opts.cveId;
|
|
192
|
+
// CSAF 2.1 §3.2.3.4 — cwes is a LIST of { id, name }, not a
|
|
193
|
+
// singleton field. The previous shape (`cwe: {...}`) failed
|
|
194
|
+
// validation against spec-conformant CSAF tooling.
|
|
195
|
+
if (opts.cweId) vuln.cwes = [{ id: opts.cweId, name: opts.cweId }];
|
|
196
|
+
if (hasIds) {
|
|
197
|
+
vuln.ids = opts.ids.map(function (entry) {
|
|
198
|
+
return { system_name: entry.systemName, text: entry.text };
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (opts.title) vuln.title = opts.title;
|
|
202
|
+
vuln.product_status = {};
|
|
203
|
+
// CSAF 2.1 §3.2.3.13 — bucket productIds under the status key.
|
|
204
|
+
vuln.product_status[opts.status] = opts.productIds.slice();
|
|
205
|
+
if (opts.status === "known_not_affected") {
|
|
206
|
+
vuln.flags = [{
|
|
207
|
+
label: opts.justification,
|
|
208
|
+
product_ids: opts.productIds.slice(),
|
|
209
|
+
}];
|
|
210
|
+
}
|
|
211
|
+
if (opts.impactStatement) {
|
|
212
|
+
vuln.notes = [{
|
|
213
|
+
category: "details",
|
|
214
|
+
text: opts.impactStatement,
|
|
215
|
+
title: "Impact",
|
|
216
|
+
}];
|
|
217
|
+
}
|
|
218
|
+
if (Array.isArray(opts.references) && opts.references.length > 0) {
|
|
219
|
+
vuln.references = opts.references.map(function (url) {
|
|
220
|
+
return { summary: "Advisory reference", url: url, category: "external" };
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
if (opts.firstReleased) vuln.first_released = opts.firstReleased;
|
|
224
|
+
if (opts.lastUpdated) vuln.last_updated = opts.lastUpdated;
|
|
225
|
+
return vuln;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* @primitive b.vex.document
|
|
230
|
+
* @signature b.vex.document(opts)
|
|
231
|
+
* @since 0.9.6
|
|
232
|
+
* @status stable
|
|
233
|
+
* @related b.vex.statement, b.vex.serialize
|
|
234
|
+
*
|
|
235
|
+
* Assemble a complete CSAF 2.1 VEX document with the supplied
|
|
236
|
+
* vulnerability statements + framework distributor metadata.
|
|
237
|
+
*
|
|
238
|
+
* @opts
|
|
239
|
+
* documentId: string, // unique per-publication id (e.g. "blamejs-vex-2026-05-12")
|
|
240
|
+
* title: string, // document title
|
|
241
|
+
* publisher: { name, namespace, contactDetails? },
|
|
242
|
+
* tlp: string, // one of TLP_LABELS; default "CLEAR"
|
|
243
|
+
* statements: object[], // array of b.vex.statement output
|
|
244
|
+
* distributor: { ... }, // optional CSAF distributor block
|
|
245
|
+
* trackingId: string, // CSAF tracking id (e.g. version-pinned)
|
|
246
|
+
* trackingVersion: string, // semver
|
|
247
|
+
* currentReleaseDate: string, // ISO 8601 timestamp
|
|
248
|
+
* initialReleaseDate: string, // ISO 8601 timestamp
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* var doc = b.vex.document({
|
|
252
|
+
* documentId: "blamejs-vex-2026-05-12",
|
|
253
|
+
* title: "blamejs framework VEX disclosures",
|
|
254
|
+
* publisher: { name: "blamejs", namespace: "https://blamejs.com/" },
|
|
255
|
+
* trackingId: "blamejs-vex-2026-05-12-001",
|
|
256
|
+
* trackingVersion: "1.0.0",
|
|
257
|
+
* currentReleaseDate: "2026-05-12T00:00:00Z",
|
|
258
|
+
* initialReleaseDate: "2026-05-12T00:00:00Z",
|
|
259
|
+
* statements: [
|
|
260
|
+
* b.vex.statement({ cveId: "CVE-2024-21505", status: "known_not_affected", productIds: ["@blamejs/core"], justification: "component_not_present" }),
|
|
261
|
+
* ],
|
|
262
|
+
* });
|
|
263
|
+
*/
|
|
264
|
+
function document(opts) {
|
|
265
|
+
if (!opts || typeof opts !== "object" || Array.isArray(opts)) {
|
|
266
|
+
throw new VexError("vex/bad-opts",
|
|
267
|
+
"document: opts must be a non-null object");
|
|
268
|
+
}
|
|
269
|
+
validateOpts.requireNonEmptyString(opts.documentId, "documentId", VexError, "vex/missing-documentId");
|
|
270
|
+
validateOpts.requireNonEmptyString(opts.title, "title", VexError, "vex/missing-title");
|
|
271
|
+
validateOpts.requireNonEmptyString(opts.trackingId, "trackingId", VexError, "vex/missing-trackingId");
|
|
272
|
+
validateOpts.requireNonEmptyString(opts.trackingVersion, "trackingVersion", VexError, "vex/missing-trackingVersion");
|
|
273
|
+
validateOpts.requireNonEmptyString(opts.currentReleaseDate, "currentReleaseDate", VexError, "vex/missing-currentReleaseDate");
|
|
274
|
+
validateOpts.requireNonEmptyString(opts.initialReleaseDate, "initialReleaseDate", VexError, "vex/missing-initialReleaseDate");
|
|
275
|
+
if (!opts.publisher || typeof opts.publisher !== "object") {
|
|
276
|
+
throw new VexError("vex/missing-publisher",
|
|
277
|
+
"document: publisher object is required ({ name, namespace })");
|
|
278
|
+
}
|
|
279
|
+
validateOpts.requireNonEmptyString(opts.publisher.name, "publisher.name", VexError, "vex/missing-publisher-name");
|
|
280
|
+
validateOpts.requireNonEmptyString(opts.publisher.namespace, "publisher.namespace", VexError, "vex/missing-publisher-namespace");
|
|
281
|
+
if (!Array.isArray(opts.statements)) {
|
|
282
|
+
throw new VexError("vex/bad-statements",
|
|
283
|
+
"document: statements must be an array of b.vex.statement objects");
|
|
284
|
+
}
|
|
285
|
+
var tlp = opts.tlp || "CLEAR";
|
|
286
|
+
if (TLP_LABELS.indexOf(tlp) === -1) {
|
|
287
|
+
throw new VexError("vex/bad-tlp",
|
|
288
|
+
"document: tlp must be one of " + TLP_LABELS.join(" / "));
|
|
289
|
+
}
|
|
290
|
+
var doc = {
|
|
291
|
+
document: {
|
|
292
|
+
category: DOCUMENT_CATEGORY_VEX,
|
|
293
|
+
csaf_version: CSAF_VERSION,
|
|
294
|
+
title: opts.title,
|
|
295
|
+
tracking: {
|
|
296
|
+
id: opts.trackingId,
|
|
297
|
+
version: opts.trackingVersion,
|
|
298
|
+
status: "final",
|
|
299
|
+
initial_release_date: opts.initialReleaseDate,
|
|
300
|
+
current_release_date: opts.currentReleaseDate,
|
|
301
|
+
revision_history: [
|
|
302
|
+
{ number: opts.trackingVersion, date: opts.currentReleaseDate, summary: opts.title },
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
distribution: {
|
|
306
|
+
tlp: { label: tlp },
|
|
307
|
+
},
|
|
308
|
+
publisher: {
|
|
309
|
+
name: opts.publisher.name,
|
|
310
|
+
namespace: opts.publisher.namespace,
|
|
311
|
+
category: "vendor",
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
vulnerabilities: opts.statements,
|
|
315
|
+
};
|
|
316
|
+
if (opts.publisher.contactDetails) {
|
|
317
|
+
doc.document.publisher.contact_details = opts.publisher.contactDetails;
|
|
318
|
+
}
|
|
319
|
+
if (opts.distributor) {
|
|
320
|
+
doc.document.distribution.distributor = opts.distributor;
|
|
321
|
+
}
|
|
322
|
+
return doc;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @primitive b.vex.serialize
|
|
327
|
+
* @signature b.vex.serialize(doc)
|
|
328
|
+
* @since 0.9.6
|
|
329
|
+
* @status stable
|
|
330
|
+
* @related b.vex.document, b.vex.statement
|
|
331
|
+
*
|
|
332
|
+
* Serialize a VEX document to canonical JSON suitable for shipping
|
|
333
|
+
* as `vex.cdx.json` or signing. Sorted-keys form so byte-equality
|
|
334
|
+
* is stable across regenerations (matches the framework's
|
|
335
|
+
* `b.canonicalJson` discipline).
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* var json = b.vex.serialize(doc);
|
|
339
|
+
* fs.writeFileSync("vex.cdx.json", json);
|
|
340
|
+
*/
|
|
341
|
+
function serialize(doc) {
|
|
342
|
+
if (!doc || typeof doc !== "object") {
|
|
343
|
+
throw new VexError("vex/bad-doc",
|
|
344
|
+
"serialize: doc must be the object returned by b.vex.document()");
|
|
345
|
+
}
|
|
346
|
+
// Route through b.canonicalJson.stringify for the deterministic
|
|
347
|
+
// sorted-key bytes, then re-parse + re-stringify with 2-space
|
|
348
|
+
// indent for human-diffable output. V8 preserves object insertion
|
|
349
|
+
// order so the re-stringify keeps the canonical sort. One source
|
|
350
|
+
// of truth for sort/scrub behaviour across the framework (rule
|
|
351
|
+
// §canonicalize).
|
|
352
|
+
var canonical = canonicalJson.stringify(doc);
|
|
353
|
+
return JSON.stringify(JSON.parse(canonical), null, 2); // allow:bare-json-parse — canonical is canonicalJson.stringify output, not operator input
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
module.exports = {
|
|
357
|
+
statement: statement,
|
|
358
|
+
document: document,
|
|
359
|
+
serialize: serialize,
|
|
360
|
+
STATUS_VALUES: STATUS_VALUES,
|
|
361
|
+
JUSTIFICATION_VALUES: JUSTIFICATION_VALUES,
|
|
362
|
+
TLP_LABELS: TLP_LABELS,
|
|
363
|
+
CSAF_VERSION: CSAF_VERSION,
|
|
364
|
+
VexError: VexError,
|
|
365
|
+
};
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.6",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:4587a803-c639-4aa0-ba4d-6b9bb2ad5845",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-13T05:39:58.426Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.9.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.9.7",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.9.
|
|
25
|
+
"version": "0.9.7",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.9.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.9.7",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.9.
|
|
57
|
+
"ref": "@blamejs/core@0.9.7",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|