@blamejs/exceptd-skills 0.12.25 → 0.12.27
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 +82 -0
- package/bin/exceptd.js +73 -1
- package/data/_indexes/_meta.json +19 -18
- package/data/_indexes/activity-feed.json +11 -4
- package/data/_indexes/catalog-summaries.json +4 -4
- package/data/_indexes/chains.json +241 -6
- package/data/_indexes/currency.json +10 -1
- package/data/_indexes/frequency.json +170 -55
- package/data/_indexes/handoff-dag.json +4 -0
- package/data/_indexes/jurisdiction-map.json +23 -12
- package/data/_indexes/section-offsets.json +94 -0
- package/data/_indexes/stale-content.json +14 -2
- package/data/_indexes/summary-cards.json +54 -0
- package/data/_indexes/token-budget.json +58 -3
- package/data/_indexes/trigger-table.json +66 -0
- package/data/_indexes/xref.json +58 -8
- package/data/atlas-ttps.json +37 -0
- package/data/framework-control-gaps.json +162 -0
- package/data/rfc-references.json +2 -1
- package/lib/flag-suggest.js +4 -0
- package/lib/playbook-runner.js +117 -10
- package/manifest-snapshot.json +75 -2
- package/manifest-snapshot.sha256 +1 -1
- package/manifest.json +126 -40
- package/package.json +1 -1
- package/sbom.cdx.json +7 -7
- package/skills/sector-telecom/skill.md +259 -0
|
@@ -1964,5 +1964,167 @@
|
|
|
1964
1964
|
"attack_refs": [
|
|
1965
1965
|
"T1195.001"
|
|
1966
1966
|
]
|
|
1967
|
+
},
|
|
1968
|
+
"FCC-CPNI-4.1": {
|
|
1969
|
+
"framework": "FCC-CPNI",
|
|
1970
|
+
"control_id": "47-CFR-64.2009(e)",
|
|
1971
|
+
"control_name": "CPNI Annual Certification + Operational Compliance",
|
|
1972
|
+
"designed_for": "Annual certification by US telecom carriers that they have established operating procedures to comply with CPNI rules; covers customer-proprietary network info disclosure to law enforcement and third parties.",
|
|
1973
|
+
"misses": [
|
|
1974
|
+
"Lawful-intercept (LI) gateway compromise detection — CPNI rules predate Salt Typhoon-class adversary access to CALEA-mandated systems",
|
|
1975
|
+
"Anomalous LI activation requests from compromised admin accounts",
|
|
1976
|
+
"Cross-PLMN signaling spike detection (SS7 / Diameter / GTP)",
|
|
1977
|
+
"OEM firmware drift attestation on equipment that touches CPNI flows"
|
|
1978
|
+
],
|
|
1979
|
+
"real_requirement": "Annual CPNI certification PLUS quarterly LI-gateway activation audit (PRC / Salt-Typhoon-class threat model), gNB firmware hash attestation, signaling-anomaly baselines per PLMN-pair.",
|
|
1980
|
+
"status": "open",
|
|
1981
|
+
"opened_date": "2026-05-15",
|
|
1982
|
+
"evidence_cves": [],
|
|
1983
|
+
"atlas_refs": ["AML.T0040"],
|
|
1984
|
+
"attack_refs": ["T1078", "T1098", "T1199"]
|
|
1985
|
+
},
|
|
1986
|
+
"FCC-Cyber-Incident-Notification-2024": {
|
|
1987
|
+
"framework": "FCC",
|
|
1988
|
+
"control_id": "47-CFR-64.2011",
|
|
1989
|
+
"control_name": "FCC Cyber Incident Notification (4 business days)",
|
|
1990
|
+
"designed_for": "Notification rule requiring US telecom carriers to report PII or CPNI breaches within 4 business days of discovery (effective 2024-03-13).",
|
|
1991
|
+
"misses": [
|
|
1992
|
+
"No requirement to notify on lawful-intercept-system compromise that does not exfiltrate PII directly (e.g. Salt Typhoon access to LI feeds covering counter-intelligence targets)",
|
|
1993
|
+
"No requirement to notify on signaling-protocol intrusion (SS7 / Diameter abuse) absent PII loss",
|
|
1994
|
+
"4-business-day window is too slow for an ongoing nation-state campaign — peer regulators set tighter limits (NIS2 24h, DORA 4h)",
|
|
1995
|
+
"No structured-data feed for cross-carrier IOC sharing"
|
|
1996
|
+
],
|
|
1997
|
+
"real_requirement": "4-business-day window paired with 24-hour preliminary signal-flag for LI-system compromise + structured CISA / NSA / FBI IOC handoff format.",
|
|
1998
|
+
"status": "open",
|
|
1999
|
+
"opened_date": "2026-05-15",
|
|
2000
|
+
"evidence_cves": [],
|
|
2001
|
+
"atlas_refs": [],
|
|
2002
|
+
"attack_refs": ["T1199", "T1078"]
|
|
2003
|
+
},
|
|
2004
|
+
"NIS2-Annex-I-Telecom": {
|
|
2005
|
+
"framework": "NIS2",
|
|
2006
|
+
"control_id": "Annex-I-Telecommunications",
|
|
2007
|
+
"control_name": "NIS2 Annex I — telecommunications essential entities",
|
|
2008
|
+
"designed_for": "NIS2 Directive (EU) 2022/2555 Annex I classifies telecom providers as essential entities, mandating risk management measures + 24h incident notification + supply-chain due diligence.",
|
|
2009
|
+
"misses": [
|
|
2010
|
+
"No specific obligations on lawful-intercept-system access controls (national-security scope deferred to MS-level law)",
|
|
2011
|
+
"Supply-chain due diligence (Art. 21(2)(d)) does not name OEM-vendor-equipment firmware integrity attestation specifically",
|
|
2012
|
+
"AI-RAN security obligations absent — O-RAN deployments span the entity boundary in ways the NIS2 risk-management framework does not yet model",
|
|
2013
|
+
"24h notification clock starts at significant-incident-detection but signaling-protocol intrusion + slow-roll campaigns evade the trigger"
|
|
2014
|
+
],
|
|
2015
|
+
"real_requirement": "Annex I + explicit LI-gateway operator-attested firmware hash + AI-RAN model-tampering controls + cross-PLMN signaling baseline obligation.",
|
|
2016
|
+
"status": "open",
|
|
2017
|
+
"opened_date": "2026-05-15",
|
|
2018
|
+
"evidence_cves": [],
|
|
2019
|
+
"atlas_refs": ["AML.T0040"],
|
|
2020
|
+
"attack_refs": ["T1199", "T1078", "T1098"]
|
|
2021
|
+
},
|
|
2022
|
+
"DORA-Art-21-Telecom-ICT": {
|
|
2023
|
+
"framework": "DORA",
|
|
2024
|
+
"control_id": "Art-21-Telecom-ICT",
|
|
2025
|
+
"control_name": "DORA Art. 21 — ICT third-party risk (telecom-adjacent application)",
|
|
2026
|
+
"designed_for": "DORA Reg. (EU) 2022/2554 Art. 21 ICT third-party risk management; applies to financial entities consuming telecom-provided ICT services.",
|
|
2027
|
+
"misses": [
|
|
2028
|
+
"Telecom-to-financial trust boundary is asymmetric — DORA binds the financial entity but telecom providers (essential entities under NIS2) may not align reporting cadences",
|
|
2029
|
+
"Lawful-intercept access by the telecom upstream is not in DORA scope but creates a parallel data-exposure surface",
|
|
2030
|
+
"CTPP (critical third-party provider) oversight does not yet cover OEM equipment vendors transitively",
|
|
2031
|
+
"No bridge to 5G slice-isolation obligations for financial-sector dedicated network slices"
|
|
2032
|
+
],
|
|
2033
|
+
"real_requirement": "DORA Art. 21 + alignment with NIS2 telecom-essential-entity reporting + slice-isolation attestation for finance-dedicated 5G slices.",
|
|
2034
|
+
"status": "open",
|
|
2035
|
+
"opened_date": "2026-05-15",
|
|
2036
|
+
"evidence_cves": [],
|
|
2037
|
+
"atlas_refs": [],
|
|
2038
|
+
"attack_refs": ["T1199"]
|
|
2039
|
+
},
|
|
2040
|
+
"UK-CAF-B5": {
|
|
2041
|
+
"framework": "UK-CAF",
|
|
2042
|
+
"control_id": "Principle-B5",
|
|
2043
|
+
"control_name": "Resilient networks and systems",
|
|
2044
|
+
"designed_for": "NCSC Cyber Assessment Framework Principle B5 — outcome-tested network resilience for essential service operators (incl. telecom under TSA 2021).",
|
|
2045
|
+
"misses": [
|
|
2046
|
+
"Signaling-protocol attack-surface (SS7 / Diameter / GTP) not in CAF B5 outcome tests",
|
|
2047
|
+
"gNB / DU / CU integrity attestation not modeled — CAF B5 expects network-availability resilience, not equipment-supply-chain attestation",
|
|
2048
|
+
"Lawful-intercept access path covered by separate IPA 2016 + TSA 2021 + Code of Practice — not CAF scope",
|
|
2049
|
+
"AI-RAN slice-isolation testing not in the CAF outcome catalog"
|
|
2050
|
+
],
|
|
2051
|
+
"real_requirement": "CAF B5 + signaling-anomaly detection + gNB firmware attestation outcome test + slice-isolation outcome test.",
|
|
2052
|
+
"status": "open",
|
|
2053
|
+
"opened_date": "2026-05-15",
|
|
2054
|
+
"evidence_cves": [],
|
|
2055
|
+
"atlas_refs": [],
|
|
2056
|
+
"attack_refs": ["T1199", "T1078"]
|
|
2057
|
+
},
|
|
2058
|
+
"AU-ISM-1556": {
|
|
2059
|
+
"framework": "au-ism",
|
|
2060
|
+
"control_id": "ISM-1556",
|
|
2061
|
+
"control_name": "Multi-factor authentication for privileged users (telecom NMS application)",
|
|
2062
|
+
"designed_for": "Australian Government ISM control ISM-1556 — phishing-resistant MFA for privileged users + remote access.",
|
|
2063
|
+
"misses": [
|
|
2064
|
+
"Telecom NMS service accounts (which hold gNB / EMS / OSS access) often bypass human MFA",
|
|
2065
|
+
"Service-account credential management policy is ISM-1559 (covered separately); ISM-1556 alone is insufficient for telecom OEM-vendor support tunnels",
|
|
2066
|
+
"Lawful-intercept gateway operator credentials specifically uncovered — these are not always classified as privileged in telecom RBAC models",
|
|
2067
|
+
"OEM remote-support inbound tunnels (Cisco TAC, Ericsson ENS) often pass credentials via shared mailbox — defeats MFA intent"
|
|
2068
|
+
],
|
|
2069
|
+
"real_requirement": "ISM-1556 + telecom-NMS service-account FIDO2 enforcement + LI-gateway-specific MFA + OEM remote-support-tunnel federated-MFA mandate.",
|
|
2070
|
+
"status": "open",
|
|
2071
|
+
"opened_date": "2026-05-15",
|
|
2072
|
+
"evidence_cves": [],
|
|
2073
|
+
"atlas_refs": [],
|
|
2074
|
+
"attack_refs": ["T1078", "T1098"]
|
|
2075
|
+
},
|
|
2076
|
+
"GSMA-NESAS-Deployment": {
|
|
2077
|
+
"framework": "GSMA-NESAS",
|
|
2078
|
+
"control_id": "NESAS-Deployment-Gap",
|
|
2079
|
+
"control_name": "NESAS at-deployment posture",
|
|
2080
|
+
"designed_for": "GSMA Network Equipment Security Assurance Scheme — product-time certification of telecom OEM equipment against 3GPP SCAS test cases (GSMA FS.13 / FS.14 / FS.15).",
|
|
2081
|
+
"misses": [
|
|
2082
|
+
"Certification is product-time (one snapshot, vendor-attested); deployment posture drifts as firmware / config evolves",
|
|
2083
|
+
"No post-deployment attested-runtime check — operator has no canonical way to confirm a running gNB matches the certified build",
|
|
2084
|
+
"Firmware update cadence is not tied to NESAS re-certification — vendor patches between certifications run uncertified",
|
|
2085
|
+
"NESAS scope excludes the EMS / OSS / NMS systems that operate the equipment, which is where Salt Typhoon-class campaigns gained access"
|
|
2086
|
+
],
|
|
2087
|
+
"real_requirement": "NESAS product-time certification PLUS operator-attested-runtime gNB hash + EMS / OSS NESAS-equivalent scheme + firmware-update-cadence-tied recertification.",
|
|
2088
|
+
"status": "open",
|
|
2089
|
+
"opened_date": "2026-05-15",
|
|
2090
|
+
"evidence_cves": [],
|
|
2091
|
+
"atlas_refs": [],
|
|
2092
|
+
"attack_refs": ["T1199"]
|
|
2093
|
+
},
|
|
2094
|
+
"3GPP-TR-33.926": {
|
|
2095
|
+
"framework": "3GPP",
|
|
2096
|
+
"control_id": "TR-33.926",
|
|
2097
|
+
"control_name": "3GPP Security Assurance Specification (gNB / eNB)",
|
|
2098
|
+
"designed_for": "3GPP TR 33.926 Security Assurance Specification — security test cases applied against the gNB / eNB product class, paired with the broader 5G security architecture in TS 33.501.",
|
|
2099
|
+
"misses": [
|
|
2100
|
+
"TR 33.926 covers the equipment itself; the AI-RAN / O-RAN deployment is outside scope (O-RAN SFG / WG11 handles separately)",
|
|
2101
|
+
"Test cases assume deterministic equipment behavior — adversary-modified firmware that passes TR 33.926 tests at submission time is undetected after the fact",
|
|
2102
|
+
"N6 / N9 interface isolation testing is in TS 33.501 not TR 33.926 — operators consume both, gap is at the join",
|
|
2103
|
+
"No bridge to ISM-1556-class operator-account hardening for the NMS that operates the certified equipment"
|
|
2104
|
+
],
|
|
2105
|
+
"real_requirement": "TR 33.926 + post-deployment hash-attestation + O-RAN security WG11 alignment + cross-spec join testing between TR 33.926 and TS 33.501.",
|
|
2106
|
+
"status": "open",
|
|
2107
|
+
"opened_date": "2026-05-15",
|
|
2108
|
+
"evidence_cves": [],
|
|
2109
|
+
"atlas_refs": [],
|
|
2110
|
+
"attack_refs": ["T1199"]
|
|
2111
|
+
},
|
|
2112
|
+
"ITU-T-X.805": {
|
|
2113
|
+
"framework": "ITU-T",
|
|
2114
|
+
"control_id": "X.805",
|
|
2115
|
+
"control_name": "ITU-T X.805 — 8-dimension security architecture for end-to-end communications",
|
|
2116
|
+
"designed_for": "ITU-T Recommendation X.805 (2003) — generic 8-dimension security architecture (access control, authentication, non-repudiation, data confidentiality, communication security, data integrity, availability, privacy) for telecom networks.",
|
|
2117
|
+
"misses": [
|
|
2118
|
+
"Specification predates 5G, O-RAN, AI-RAN, and CALEA / IPA-LI surface evolution; control language remains generic",
|
|
2119
|
+
"No mapping to modern threat models (Salt Typhoon, signaling-protocol abuse)",
|
|
2120
|
+
"Treated as reference architecture, not as a deployment-validation framework — operators rarely audit posture against X.805 dimensions directly",
|
|
2121
|
+
"No bridge to NESAS / 3GPP / NIS2 / FCC CPNI specific obligations"
|
|
2122
|
+
],
|
|
2123
|
+
"real_requirement": "X.805 8-dimension framing PLUS modern-threat-model annexes (LI-system compromise, signaling-protocol abuse, slice-isolation) + deployment-validation checklist.",
|
|
2124
|
+
"status": "open",
|
|
2125
|
+
"opened_date": "2026-05-15",
|
|
2126
|
+
"evidence_cves": [],
|
|
2127
|
+
"atlas_refs": [],
|
|
2128
|
+
"attack_refs": ["T1199"]
|
|
1967
2129
|
}
|
|
1968
2130
|
}
|
package/data/rfc-references.json
CHANGED
|
@@ -463,7 +463,8 @@
|
|
|
463
463
|
"relevance": "Transport Services (TAPS) architecture — abstracts the choice of underlying transport (TCP, QUIC, SCTP) behind a unified API that selects the protocol at runtime based on path conditions. Forward-watch relevance: webapp / AI-API client libraries that adopt TAPS may transparently shift between TCP-HTTP/2 and QUIC-HTTP/3 mid-session, complicating boundary inspection assumptions that pin to a single transport. Currently tracked as a deployment-watch item rather than an operational control point.",
|
|
464
464
|
"lag_notes": "Architecture document; companion documents (TAPS implementation, TAPS programming interface) are still in progress at IETF TAPS WG. Enterprise tooling impact is forward-looking.",
|
|
465
465
|
"skills_referencing": [
|
|
466
|
-
"webapp-security"
|
|
466
|
+
"webapp-security",
|
|
467
|
+
"sector-telecom"
|
|
467
468
|
],
|
|
468
469
|
"last_verified": "2026-05-15"
|
|
469
470
|
},
|
package/lib/flag-suggest.js
CHANGED
|
@@ -79,21 +79,25 @@ const VERB_FLAG_ALLOWLIST = Object.freeze({
|
|
|
79
79
|
'mode', 'air-gap', 'force-stale', 'operator', 'ack', 'csaf-status',
|
|
80
80
|
'publisher-namespace', 'vex', 'all', 'scope', 'required', 'format',
|
|
81
81
|
'strict-preconditions', 'block-on-jurisdiction-clock', 'tlp',
|
|
82
|
+
'bundle-deterministic', 'bundle-epoch',
|
|
82
83
|
],
|
|
83
84
|
'run-all': [
|
|
84
85
|
'evidence', 'evidence-dir', 'session-id', 'force-overwrite', 'attestation-root',
|
|
85
86
|
'mode', 'air-gap', 'force-stale', 'operator', 'ack', 'csaf-status',
|
|
86
87
|
'publisher-namespace', 'vex', 'scope', 'strict-preconditions', 'tlp',
|
|
88
|
+
'bundle-deterministic', 'bundle-epoch',
|
|
87
89
|
],
|
|
88
90
|
'ai-run': [
|
|
89
91
|
'evidence', 'no-stream', 'session-id', 'force-overwrite', 'attestation-root',
|
|
90
92
|
'operator', 'ack', 'csaf-status', 'publisher-namespace', 'air-gap',
|
|
91
93
|
'mode', 'force-stale', 'tlp',
|
|
94
|
+
'bundle-deterministic', 'bundle-epoch',
|
|
92
95
|
],
|
|
93
96
|
ingest: [
|
|
94
97
|
'evidence', 'session-id', 'force-overwrite', 'attestation-root', 'operator',
|
|
95
98
|
'ack', 'csaf-status', 'publisher-namespace', 'air-gap', 'force-stale',
|
|
96
99
|
'strict-preconditions',
|
|
100
|
+
'bundle-deterministic', 'bundle-epoch',
|
|
97
101
|
],
|
|
98
102
|
brief: ['all', 'scope', 'directives', 'flat', 'phase'],
|
|
99
103
|
discover: ['scan-only', 'scope'],
|
package/lib/playbook-runner.js
CHANGED
|
@@ -1515,6 +1515,13 @@ function close(playbookId, directiveId, analyzeResult, validateResult, agentSign
|
|
|
1515
1515
|
// bypass run() (e.g. unit tests).
|
|
1516
1516
|
const sessionId = runOpts.session_id || crypto.randomBytes(8).toString('hex');
|
|
1517
1517
|
|
|
1518
|
+
// v0.12.27: when opt-in deterministic bundle mode is set, resolve the
|
|
1519
|
+
// single frozen epoch used by every timestamp surface below. Cached for
|
|
1520
|
+
// the whole close() call so notification_actions, regression_schedule,
|
|
1521
|
+
// and the bundle emitter all agree on the same Date.
|
|
1522
|
+
const deterministic = runOpts.bundleDeterministic === true;
|
|
1523
|
+
const frozenEpoch = deterministic ? resolveFrozenEpoch(runOpts, playbook) : null;
|
|
1524
|
+
|
|
1518
1525
|
// notification_actions — compute ISO deadlines from clock_starts events.
|
|
1519
1526
|
// v0.11.12 (#123): enrich each entry with the matched obligation's
|
|
1520
1527
|
// jurisdiction/regulation/window_hours/evidence_required fields. The
|
|
@@ -1605,7 +1612,10 @@ function close(playbookId, directiveId, analyzeResult, validateResult, agentSign
|
|
|
1605
1612
|
framework_id: playbook.domain.frameworks_in_scope[0] || 'unspecified',
|
|
1606
1613
|
control_id: analyzeResult.framework_gap_mapping?.[0]?.claimed_control || 'unspecified',
|
|
1607
1614
|
ciso_name: agentSignals.ciso_name || '<CISO NAME>',
|
|
1608
|
-
|
|
1615
|
+
// v0.12.27: deterministic mode roots acceptance_date in the
|
|
1616
|
+
// frozen epoch so two runs against the same evidence emit the
|
|
1617
|
+
// same auditor-facing date.
|
|
1618
|
+
acceptance_date: (deterministic ? frozenEpoch : new Date().toISOString()).slice(0, 10),
|
|
1609
1619
|
duration_expiry: agentSignals.duration_expiry || 'until vendor patch'
|
|
1610
1620
|
})
|
|
1611
1621
|
};
|
|
@@ -1628,7 +1638,11 @@ function close(playbookId, directiveId, analyzeResult, validateResult, agentSign
|
|
|
1628
1638
|
// spurious millisecond drift on tracking.initial_release_date /
|
|
1629
1639
|
// timestamp / current_release_date.
|
|
1630
1640
|
const evidencePackage = c.evidence_package ? (() => {
|
|
1631
|
-
|
|
1641
|
+
// v0.12.27: deterministic mode pins issuedAt to the frozen epoch so
|
|
1642
|
+
// CSAF tracking.{initial_release_date,current_release_date,
|
|
1643
|
+
// generator.date,revision_history[0].date} and OpenVEX timestamp +
|
|
1644
|
+
// statements[].timestamp all collapse to a single, byte-stable value.
|
|
1645
|
+
const issuedAt = deterministic ? frozenEpoch : new Date().toISOString();
|
|
1632
1646
|
const builtFormats = new Map();
|
|
1633
1647
|
const buildOnce = (format) => {
|
|
1634
1648
|
if (!builtFormats.has(format)) {
|
|
@@ -1680,11 +1694,27 @@ function close(playbookId, directiveId, analyzeResult, validateResult, agentSign
|
|
|
1680
1694
|
} : { enabled: false };
|
|
1681
1695
|
|
|
1682
1696
|
// regression_schedule
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1697
|
+
//
|
|
1698
|
+
// v0.12.27: deterministic mode re-derives next_run from the frozen epoch
|
|
1699
|
+
// rather than wall-clock-now-at-validate-time. Without this, two runs
|
|
1700
|
+
// against the same evidence diverge on next_run by the interval between
|
|
1701
|
+
// the two `validate()` invocations. Frozen base + the same interval set
|
|
1702
|
+
// = byte-identical schedule.
|
|
1703
|
+
const regressionSchedule = c.regression_schedule ? (() => {
|
|
1704
|
+
let nextRun = validateResult.regression_next_run;
|
|
1705
|
+
if (deterministic) {
|
|
1706
|
+
// Re-derive against the validate phase's trigger set (not the
|
|
1707
|
+
// close phase's regression_schedule subtree — close has no triggers
|
|
1708
|
+
// of its own, just the canonical interval declared upstream).
|
|
1709
|
+
const v = resolvedPhase(playbook, directiveId, 'validate');
|
|
1710
|
+
nextRun = frozenRegressionNextRun(v.regression_trigger || [], new Date(frozenEpoch));
|
|
1711
|
+
}
|
|
1712
|
+
return {
|
|
1713
|
+
next_run: nextRun,
|
|
1714
|
+
trigger: c.regression_schedule.trigger,
|
|
1715
|
+
notify_on_skip: c.regression_schedule.notify_on_skip !== false
|
|
1716
|
+
};
|
|
1717
|
+
})() : null;
|
|
1688
1718
|
|
|
1689
1719
|
// feeds_into chaining — full analyze result is exposed so conditions can
|
|
1690
1720
|
// reference `analyze.compliance_theater_check.verdict` etc.
|
|
@@ -1996,6 +2026,40 @@ function getEngineVersion() {
|
|
|
1996
2026
|
return _CACHED_PKG_VERSION;
|
|
1997
2027
|
}
|
|
1998
2028
|
|
|
2029
|
+
// v0.12.27: deterministic-bundle epoch resolution. Priority:
|
|
2030
|
+
// 1. runOpts.bundleEpoch (operator-supplied --bundle-epoch <ISO>)
|
|
2031
|
+
// 2. playbook._meta.last_threat_review (the freshness anchor that already
|
|
2032
|
+
// gates every shipped playbook — stable across re-runs of the same
|
|
2033
|
+
// catalog version)
|
|
2034
|
+
// 3. '1970-01-01T00:00:00Z' fallback (effectively impossible in practice
|
|
2035
|
+
// because every shipped playbook carries last_threat_review, but
|
|
2036
|
+
// guarantees the deterministic path never crashes on a malformed
|
|
2037
|
+
// playbook).
|
|
2038
|
+
// Returns a full ISO-8601 timestamp (date-only inputs are normalised).
|
|
2039
|
+
function resolveFrozenEpoch(runOpts, playbook) {
|
|
2040
|
+
const raw = runOpts && runOpts.bundleEpoch
|
|
2041
|
+
? runOpts.bundleEpoch
|
|
2042
|
+
: (playbook && playbook._meta && playbook._meta.last_threat_review)
|
|
2043
|
+
|| '1970-01-01T00:00:00Z';
|
|
2044
|
+
try { return new Date(raw).toISOString(); }
|
|
2045
|
+
catch { return '1970-01-01T00:00:00Z'; }
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
// Recompute regression_schedule.next_run against a frozen `now` so two
|
|
2049
|
+
// deterministic-mode runs of the same playbook produce byte-identical
|
|
2050
|
+
// schedules. Mirrors computeRegressionNextRun but with an injected base
|
|
2051
|
+
// date. Returns the soonest ISO timestamp or null when no interval-based
|
|
2052
|
+
// trigger fired.
|
|
2053
|
+
function frozenRegressionNextRun(triggers, frozenNow) {
|
|
2054
|
+
let soonest = null;
|
|
2055
|
+
for (const t of (triggers || [])) {
|
|
2056
|
+
const parsed = parseInterval(t.interval, frozenNow);
|
|
2057
|
+
if (!parsed || !parsed.date) continue;
|
|
2058
|
+
if (!soonest || parsed.date < soonest) soonest = parsed.date;
|
|
2059
|
+
}
|
|
2060
|
+
return soonest ? soonest.toISOString() : null;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
1999
2063
|
// Operator-supplied identity strings (--operator) and publisher namespace
|
|
2000
2064
|
// URLs (--publisher-namespace) flow into operator-facing CSAF surfaces.
|
|
2001
2065
|
// Strip ASCII control characters as defence in depth — bin/exceptd.js
|
|
@@ -2410,7 +2474,19 @@ function buildEvidenceBundle(format, playbook, analyze, validate, agentSignals,
|
|
|
2410
2474
|
if (branches.length > 0) tree.branches = branches;
|
|
2411
2475
|
return tree;
|
|
2412
2476
|
})(),
|
|
2413
|
-
vulnerabilities:
|
|
2477
|
+
vulnerabilities: (function () {
|
|
2478
|
+
// v0.12.27: deterministic mode sorts vulnerabilities[] by their
|
|
2479
|
+
// primary identifier (cve_id for CVE entries, ids[0].text otherwise)
|
|
2480
|
+
// ascending. Default mode preserves insertion order so existing
|
|
2481
|
+
// operators see byte-identical output to pre-v0.12.27.
|
|
2482
|
+
const all = [...cveVulns, ...indicatorVulns];
|
|
2483
|
+
if (runOpts && runOpts.bundleDeterministic === true) {
|
|
2484
|
+
const keyOf = (v) => (typeof v.cve === 'string' && v.cve)
|
|
2485
|
+
|| (Array.isArray(v.ids) && v.ids[0] && typeof v.ids[0].text === 'string' ? v.ids[0].text : '');
|
|
2486
|
+
return all.slice().sort((a, b) => keyOf(a).localeCompare(keyOf(b)));
|
|
2487
|
+
}
|
|
2488
|
+
return all;
|
|
2489
|
+
})(),
|
|
2414
2490
|
exceptd_extension: {
|
|
2415
2491
|
classification: analyze._detect_classification,
|
|
2416
2492
|
rwep: analyze.rwep,
|
|
@@ -2642,7 +2718,17 @@ function buildEvidenceBundle(format, playbook, analyze, validate, agentSignals,
|
|
|
2642
2718
|
author: 'exceptd',
|
|
2643
2719
|
timestamp: issued,
|
|
2644
2720
|
version: 1,
|
|
2645
|
-
statements:
|
|
2721
|
+
statements: (function () {
|
|
2722
|
+
// v0.12.27: deterministic mode sorts statements[] by
|
|
2723
|
+
// vulnerability['@id'] ascending. Insertion order otherwise.
|
|
2724
|
+
const all = [...cveStatements, ...indicatorStatements];
|
|
2725
|
+
if (runOpts && runOpts.bundleDeterministic === true) {
|
|
2726
|
+
const keyOf = (s) => (s && s.vulnerability && typeof s.vulnerability['@id'] === 'string')
|
|
2727
|
+
? s.vulnerability['@id'] : '';
|
|
2728
|
+
return all.slice().sort((a, b) => keyOf(a).localeCompare(keyOf(b)));
|
|
2729
|
+
}
|
|
2730
|
+
return all;
|
|
2731
|
+
})(),
|
|
2646
2732
|
};
|
|
2647
2733
|
}
|
|
2648
2734
|
|
|
@@ -2948,7 +3034,28 @@ function run(playbookId, directiveId, agentSubmission = {}, runOpts = {}) {
|
|
|
2948
3034
|
// Without the single-source-of-truth, close() would mint its own id
|
|
2949
3035
|
// and operators correlating attestation files to embedded bundle URNs
|
|
2950
3036
|
// would see mismatches.
|
|
2951
|
-
|
|
3037
|
+
//
|
|
3038
|
+
// v0.12.27: when runOpts.bundleDeterministic is set AND the operator did
|
|
3039
|
+
// not pass --session-id, derive the session_id from the submission shape
|
|
3040
|
+
// so two runs against identical evidence produce the same id (and
|
|
3041
|
+
// therefore the same CSAF tracking.id / OpenVEX @id / attestation file
|
|
3042
|
+
// name). Mirrors the evidence_hash path further down but is computed
|
|
3043
|
+
// here so close() can thread it through. Operator-supplied --session-id
|
|
3044
|
+
// still wins on collision.
|
|
3045
|
+
let sessionId;
|
|
3046
|
+
if (runOpts.session_id) {
|
|
3047
|
+
sessionId = runOpts.session_id;
|
|
3048
|
+
} else if (runOpts.bundleDeterministic) {
|
|
3049
|
+
const submissionDigest = crypto.createHash('sha256')
|
|
3050
|
+
.update(canonicalStringify(extractSubmissionForHash(agentSubmission)))
|
|
3051
|
+
.digest('hex');
|
|
3052
|
+
sessionId = crypto.createHash('sha256')
|
|
3053
|
+
.update(`${playbookId}\0${submissionDigest}\0${getEngineVersion()}`)
|
|
3054
|
+
.digest('hex')
|
|
3055
|
+
.slice(0, 16);
|
|
3056
|
+
} else {
|
|
3057
|
+
sessionId = crypto.randomBytes(8).toString('hex');
|
|
3058
|
+
}
|
|
2952
3059
|
const cachedRunOpts = { ...runOpts, _playbookCache: playbook, session_id: sessionId };
|
|
2953
3060
|
// Run-time error accumulator for evalCondition regex failures and other
|
|
2954
3061
|
// non-fatal anomalies surfaced into analyze.runtime_errors[].
|
package/manifest-snapshot.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "Auto-generated by scripts/refresh-manifest-snapshot.js — do not hand-edit. Public skill surface used by check-manifest-snapshot.js to detect breaking removals.",
|
|
3
|
-
"_generated_at": "2026-05-
|
|
3
|
+
"_generated_at": "2026-05-15T22:38:13.114Z",
|
|
4
4
|
"atlas_version": "5.1.0",
|
|
5
|
-
"skill_count":
|
|
5
|
+
"skill_count": 39,
|
|
6
6
|
"skills": [
|
|
7
7
|
{
|
|
8
8
|
"name": "age-gates-child-safety",
|
|
@@ -1543,6 +1543,79 @@
|
|
|
1543
1543
|
"d3fend_refs": [],
|
|
1544
1544
|
"dlp_refs": []
|
|
1545
1545
|
},
|
|
1546
|
+
{
|
|
1547
|
+
"name": "sector-telecom",
|
|
1548
|
+
"version": "1.0.0",
|
|
1549
|
+
"triggers": [
|
|
1550
|
+
"3gpp tr 33.926",
|
|
1551
|
+
"3gpp ts 33.501",
|
|
1552
|
+
"4-business-day notification",
|
|
1553
|
+
"5g core",
|
|
1554
|
+
"au soci",
|
|
1555
|
+
"calea",
|
|
1556
|
+
"diameter",
|
|
1557
|
+
"fcc cpni",
|
|
1558
|
+
"gnb integrity",
|
|
1559
|
+
"gsma nesas",
|
|
1560
|
+
"gtp",
|
|
1561
|
+
"itu-t x.805",
|
|
1562
|
+
"lawful intercept",
|
|
1563
|
+
"n6 n9 isolation",
|
|
1564
|
+
"nis2 annex i",
|
|
1565
|
+
"o-ran",
|
|
1566
|
+
"salt typhoon",
|
|
1567
|
+
"ss7",
|
|
1568
|
+
"telecom security",
|
|
1569
|
+
"tssr",
|
|
1570
|
+
"uk tsa 2021",
|
|
1571
|
+
"volt typhoon"
|
|
1572
|
+
],
|
|
1573
|
+
"data_deps": [
|
|
1574
|
+
"atlas-ttps.json",
|
|
1575
|
+
"cve-catalog.json",
|
|
1576
|
+
"cwe-catalog.json",
|
|
1577
|
+
"d3fend-catalog.json",
|
|
1578
|
+
"framework-control-gaps.json",
|
|
1579
|
+
"global-frameworks.json"
|
|
1580
|
+
],
|
|
1581
|
+
"atlas_refs": [
|
|
1582
|
+
"AML.T0040"
|
|
1583
|
+
],
|
|
1584
|
+
"attack_refs": [
|
|
1585
|
+
"T1071",
|
|
1586
|
+
"T1078",
|
|
1587
|
+
"T1098",
|
|
1588
|
+
"T1190",
|
|
1589
|
+
"T1199",
|
|
1590
|
+
"T1556"
|
|
1591
|
+
],
|
|
1592
|
+
"framework_gaps": [
|
|
1593
|
+
"3GPP-TR-33.926",
|
|
1594
|
+
"AU-ISM-1556",
|
|
1595
|
+
"DORA-Art-21-Telecom-ICT",
|
|
1596
|
+
"FCC-CPNI-4.1",
|
|
1597
|
+
"FCC-Cyber-Incident-Notification-2024",
|
|
1598
|
+
"GSMA-NESAS-Deployment",
|
|
1599
|
+
"ITU-T-X.805",
|
|
1600
|
+
"NIS2-Annex-I-Telecom",
|
|
1601
|
+
"UK-CAF-B5"
|
|
1602
|
+
],
|
|
1603
|
+
"rfc_refs": [
|
|
1604
|
+
"RFC-9622"
|
|
1605
|
+
],
|
|
1606
|
+
"cwe_refs": [
|
|
1607
|
+
"CWE-287",
|
|
1608
|
+
"CWE-306",
|
|
1609
|
+
"CWE-918"
|
|
1610
|
+
],
|
|
1611
|
+
"d3fend_refs": [
|
|
1612
|
+
"D3-IOPR",
|
|
1613
|
+
"D3-NI",
|
|
1614
|
+
"D3-NTA",
|
|
1615
|
+
"D3-NTPM"
|
|
1616
|
+
],
|
|
1617
|
+
"dlp_refs": []
|
|
1618
|
+
},
|
|
1546
1619
|
{
|
|
1547
1620
|
"name": "security-maturity-tiers",
|
|
1548
1621
|
"version": "1.0.0",
|
package/manifest-snapshot.sha256
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
259bbbc7ec375bfb21c2e8fe4c397cca265b33b357e19282d34acff932752237 manifest-snapshot.json
|