@aegis-scan/core 0.16.6 → 0.18.0

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.
Files changed (125) hide show
  1. package/README.md +37 -0
  2. package/dist/index.d.ts +5 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +5 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/manipulation-resistance/ai-io-boundary.d.ts +84 -0
  7. package/dist/manipulation-resistance/ai-io-boundary.d.ts.map +1 -0
  8. package/dist/manipulation-resistance/ai-io-boundary.js +216 -0
  9. package/dist/manipulation-resistance/ai-io-boundary.js.map +1 -0
  10. package/dist/manipulation-resistance/config-integrity.d.ts +28 -0
  11. package/dist/manipulation-resistance/config-integrity.d.ts.map +1 -0
  12. package/dist/manipulation-resistance/config-integrity.js +53 -0
  13. package/dist/manipulation-resistance/config-integrity.js.map +1 -0
  14. package/dist/manipulation-resistance/index.d.ts +16 -0
  15. package/dist/manipulation-resistance/index.d.ts.map +1 -0
  16. package/dist/manipulation-resistance/index.js +16 -0
  17. package/dist/manipulation-resistance/index.js.map +1 -0
  18. package/dist/manipulation-resistance/instruction-boundary.d.ts +50 -0
  19. package/dist/manipulation-resistance/instruction-boundary.d.ts.map +1 -0
  20. package/dist/manipulation-resistance/instruction-boundary.js +114 -0
  21. package/dist/manipulation-resistance/instruction-boundary.js.map +1 -0
  22. package/dist/manipulation-resistance/oob-blocker.d.ts +58 -0
  23. package/dist/manipulation-resistance/oob-blocker.d.ts.map +1 -0
  24. package/dist/manipulation-resistance/oob-blocker.js +55 -0
  25. package/dist/manipulation-resistance/oob-blocker.js.map +1 -0
  26. package/dist/manipulation-resistance/redirect-policy.d.ts +43 -0
  27. package/dist/manipulation-resistance/redirect-policy.d.ts.map +1 -0
  28. package/dist/manipulation-resistance/redirect-policy.js +197 -0
  29. package/dist/manipulation-resistance/redirect-policy.js.map +1 -0
  30. package/dist/manipulation-resistance/response-validator.d.ts +33 -0
  31. package/dist/manipulation-resistance/response-validator.d.ts.map +1 -0
  32. package/dist/manipulation-resistance/response-validator.js +186 -0
  33. package/dist/manipulation-resistance/response-validator.js.map +1 -0
  34. package/dist/manipulation-resistance/scope-expansion-detector.d.ts +33 -0
  35. package/dist/manipulation-resistance/scope-expansion-detector.d.ts.map +1 -0
  36. package/dist/manipulation-resistance/scope-expansion-detector.js +68 -0
  37. package/dist/manipulation-resistance/scope-expansion-detector.js.map +1 -0
  38. package/dist/oversight/approval-gates.d.ts +77 -0
  39. package/dist/oversight/approval-gates.d.ts.map +1 -0
  40. package/dist/oversight/approval-gates.js +133 -0
  41. package/dist/oversight/approval-gates.js.map +1 -0
  42. package/dist/oversight/authority-matrix.d.ts +39 -0
  43. package/dist/oversight/authority-matrix.d.ts.map +1 -0
  44. package/dist/oversight/authority-matrix.js +75 -0
  45. package/dist/oversight/authority-matrix.js.map +1 -0
  46. package/dist/oversight/cia-scoring.d.ts +56 -0
  47. package/dist/oversight/cia-scoring.d.ts.map +1 -0
  48. package/dist/oversight/cia-scoring.js +98 -0
  49. package/dist/oversight/cia-scoring.js.map +1 -0
  50. package/dist/oversight/escalation.d.ts +58 -0
  51. package/dist/oversight/escalation.d.ts.map +1 -0
  52. package/dist/oversight/escalation.js +97 -0
  53. package/dist/oversight/escalation.js.map +1 -0
  54. package/dist/oversight/index.d.ts +15 -0
  55. package/dist/oversight/index.d.ts.map +1 -0
  56. package/dist/oversight/index.js +15 -0
  57. package/dist/oversight/index.js.map +1 -0
  58. package/dist/roe/index.d.ts +3 -0
  59. package/dist/roe/index.d.ts.map +1 -0
  60. package/dist/roe/index.js +3 -0
  61. package/dist/roe/index.js.map +1 -0
  62. package/dist/roe/loader.d.ts +15 -0
  63. package/dist/roe/loader.d.ts.map +1 -0
  64. package/dist/roe/loader.js +56 -0
  65. package/dist/roe/loader.js.map +1 -0
  66. package/dist/roe/types.d.ts +738 -0
  67. package/dist/roe/types.d.ts.map +1 -0
  68. package/dist/roe/types.js +525 -0
  69. package/dist/roe/types.js.map +1 -0
  70. package/dist/runtime/chain.d.ts +60 -0
  71. package/dist/runtime/chain.d.ts.map +1 -0
  72. package/dist/runtime/chain.js +156 -0
  73. package/dist/runtime/chain.js.map +1 -0
  74. package/dist/runtime/events.d.ts +104 -0
  75. package/dist/runtime/events.d.ts.map +1 -0
  76. package/dist/runtime/events.js +68 -0
  77. package/dist/runtime/events.js.map +1 -0
  78. package/dist/runtime/hash.d.ts +16 -0
  79. package/dist/runtime/hash.d.ts.map +1 -0
  80. package/dist/runtime/hash.js +70 -0
  81. package/dist/runtime/hash.js.map +1 -0
  82. package/dist/runtime/index.d.ts +7 -0
  83. package/dist/runtime/index.d.ts.map +1 -0
  84. package/dist/runtime/index.js +7 -0
  85. package/dist/runtime/index.js.map +1 -0
  86. package/dist/runtime/notifications.d.ts +24 -0
  87. package/dist/runtime/notifications.d.ts.map +1 -0
  88. package/dist/runtime/notifications.js +41 -0
  89. package/dist/runtime/notifications.js.map +1 -0
  90. package/dist/runtime/signals.d.ts +56 -0
  91. package/dist/runtime/signals.d.ts.map +1 -0
  92. package/dist/runtime/signals.js +72 -0
  93. package/dist/runtime/signals.js.map +1 -0
  94. package/dist/runtime/state.d.ts +88 -0
  95. package/dist/runtime/state.d.ts.map +1 -0
  96. package/dist/runtime/state.js +172 -0
  97. package/dist/runtime/state.js.map +1 -0
  98. package/dist/safety-controls/boundary-monitor.d.ts +45 -0
  99. package/dist/safety-controls/boundary-monitor.d.ts.map +1 -0
  100. package/dist/safety-controls/boundary-monitor.js +77 -0
  101. package/dist/safety-controls/boundary-monitor.js.map +1 -0
  102. package/dist/safety-controls/decision-timeout.d.ts +56 -0
  103. package/dist/safety-controls/decision-timeout.d.ts.map +1 -0
  104. package/dist/safety-controls/decision-timeout.js +67 -0
  105. package/dist/safety-controls/decision-timeout.js.map +1 -0
  106. package/dist/safety-controls/health-monitor.d.ts +61 -0
  107. package/dist/safety-controls/health-monitor.d.ts.map +1 -0
  108. package/dist/safety-controls/health-monitor.js +79 -0
  109. package/dist/safety-controls/health-monitor.js.map +1 -0
  110. package/dist/safety-controls/index.d.ts +13 -0
  111. package/dist/safety-controls/index.d.ts.map +1 -0
  112. package/dist/safety-controls/index.js +13 -0
  113. package/dist/safety-controls/index.js.map +1 -0
  114. package/dist/safety-controls/kill-switch.d.ts +45 -0
  115. package/dist/safety-controls/kill-switch.d.ts.map +1 -0
  116. package/dist/safety-controls/kill-switch.js +117 -0
  117. package/dist/safety-controls/kill-switch.js.map +1 -0
  118. package/dist/safety-controls/post-test-integrity.d.ts +51 -0
  119. package/dist/safety-controls/post-test-integrity.d.ts.map +1 -0
  120. package/dist/safety-controls/post-test-integrity.js +79 -0
  121. package/dist/safety-controls/post-test-integrity.js.map +1 -0
  122. package/dist/types.d.ts +17 -0
  123. package/dist/types.d.ts.map +1 -1
  124. package/package.json +2 -1
  125. package/sbom.cdx.json +1 -1
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Hash-chained event emitter + chain verifier.
3
+ *
4
+ * Closes APTS-AR-012 (Tamper-Evident Logging with Hash Chains) +
5
+ * APTS-AL-005 (Mandatory Logging + Human-Reviewable Audit Trail).
6
+ *
7
+ * Each emitted event carries:
8
+ * - prev_hash: hash of the immediately-preceding event (null on first)
9
+ * - this_hash: SHA-256 of this event's canonical form (excluding this_hash)
10
+ *
11
+ * Verification: re-canonicalize each event without this_hash, recompute
12
+ * the hash, compare against this_hash. Compare prev_hash with the
13
+ * previous event's this_hash. Any tamper anywhere in the chain breaks
14
+ * verification at that line.
15
+ */
16
+ import { readFileSync, existsSync } from 'node:fs';
17
+ import { emitEvent } from './events.js';
18
+ import { hashCanonical } from './hash.js';
19
+ /**
20
+ * Stateful emitter that maintains the running prev_hash → this_hash chain.
21
+ * Each `emit` call pours the chain forward by one link. The current chain
22
+ * tail is exposed via `getTail()` so a resume operation can pick up where
23
+ * the prior session left off.
24
+ */
25
+ export class ChainedEmitter {
26
+ opts;
27
+ prevHash;
28
+ constructor(opts) {
29
+ this.opts = opts;
30
+ this.prevHash = opts.initialPrevHash ?? null;
31
+ }
32
+ emit(event) {
33
+ // Build the chained event. Strip any caller-supplied this_hash —
34
+ // the emitter is the sole source of truth for the hash field.
35
+ const { this_hash: _ignored, ...rest } = event;
36
+ void _ignored;
37
+ const withPrev = { ...rest, prev_hash: this.prevHash };
38
+ // Hash without this_hash so the field can't be part of its own preimage.
39
+ const this_hash = hashCanonical(withPrev);
40
+ const final = { ...withPrev, this_hash };
41
+ emitEvent(final, this.opts.sink);
42
+ this.prevHash = this_hash;
43
+ return final;
44
+ }
45
+ getTail() {
46
+ return this.prevHash;
47
+ }
48
+ }
49
+ /**
50
+ * Verify the integrity of a JSONL audit-log file. Each EVENT line must:
51
+ * - parse as JSON
52
+ * - carry this_hash + prev_hash fields
53
+ * - have a this_hash that matches the canonical hash of its other fields
54
+ * - have a prev_hash that matches the previous line's this_hash (null on line 0)
55
+ *
56
+ * Snapshot lines (those with `state_version` but no `event` field) are
57
+ * SKIPPED — they exist alongside events in the same JSONL file (audit-fix
58
+ * 2026-04-27 made writeEngagementState append-not-overwrite).
59
+ *
60
+ * KNOWN SCOPE LIMIT (audit-fix-2 2026-04-27):
61
+ * Snapshot lines are NOT part of the hash chain. An attacker with
62
+ * write access to the state-file can post-hoc tamper with snapshot
63
+ * fields (`completed_phases`, `findings_so_far`, `paused_at`,
64
+ * `reason`) without breaking chain verification. The EVENT timeline
65
+ * remains tamper-evident — every state transition emits an event,
66
+ * so a tampered snapshot is contradicted by the unchanged event
67
+ * stream. Resume-from-snapshot consumers MUST cross-check snapshot
68
+ * contents against the event chain before trusting them. Closing
69
+ * this gap fully would require chaining snapshot writes through
70
+ * ChainedEmitter (snapshot becomes an event with prev_hash +
71
+ * this_hash); deferred to a future cluster.
72
+ *
73
+ * Returns ok-with-tail-hash on success, or failure-at-line-N with reason.
74
+ */
75
+ export function verifyAuditChain(path) {
76
+ if (!existsSync(path)) {
77
+ return { ok: false, error: `audit-log not found at ${path}`, broken_at: 0, total_events_processed: 0 };
78
+ }
79
+ let raw;
80
+ try {
81
+ raw = readFileSync(path, 'utf-8');
82
+ }
83
+ catch (err) {
84
+ return {
85
+ ok: false,
86
+ error: `audit-log unreadable at ${path}: ${err instanceof Error ? err.message : String(err)}`,
87
+ broken_at: 0,
88
+ total_events_processed: 0,
89
+ };
90
+ }
91
+ const lines = raw.split(/\r?\n/).filter((line) => line.trim().length > 0);
92
+ let prevExpectedHash = null;
93
+ let eventsProcessed = 0;
94
+ for (let i = 0; i < lines.length; i++) {
95
+ const line = lines[i];
96
+ let parsed;
97
+ try {
98
+ parsed = JSON.parse(line);
99
+ }
100
+ catch (err) {
101
+ return {
102
+ ok: false,
103
+ error: `line ${i}: not valid JSON (${err instanceof Error ? err.message : String(err)})`,
104
+ broken_at: i,
105
+ total_events_processed: eventsProcessed,
106
+ };
107
+ }
108
+ // Skip snapshot lines — they have state_version but no event.
109
+ if ('state_version' in parsed && !('event' in parsed)) {
110
+ continue;
111
+ }
112
+ const lineThisHash = parsed.this_hash;
113
+ const linePrevHash = parsed.prev_hash;
114
+ if (typeof lineThisHash !== 'string') {
115
+ return {
116
+ ok: false,
117
+ error: `line ${i}: missing or non-string this_hash`,
118
+ broken_at: i,
119
+ total_events_processed: eventsProcessed,
120
+ };
121
+ }
122
+ if (linePrevHash !== null && typeof linePrevHash !== 'string') {
123
+ return {
124
+ ok: false,
125
+ error: `line ${i}: prev_hash must be string or null`,
126
+ broken_at: i,
127
+ total_events_processed: eventsProcessed,
128
+ };
129
+ }
130
+ // Recompute the canonical hash of the event without this_hash.
131
+ const withoutHash = { ...parsed };
132
+ delete withoutHash.this_hash;
133
+ const recomputed = hashCanonical(withoutHash);
134
+ if (recomputed !== lineThisHash) {
135
+ return {
136
+ ok: false,
137
+ error: `line ${i}: this_hash mismatch (event tampered or canonicalization mismatch)`,
138
+ broken_at: i,
139
+ total_events_processed: eventsProcessed,
140
+ };
141
+ }
142
+ // Check the chain link to the previous event.
143
+ if ((linePrevHash ?? null) !== prevExpectedHash) {
144
+ return {
145
+ ok: false,
146
+ error: `line ${i}: prev_hash chain break (expected ${prevExpectedHash ?? 'null'}, got ${linePrevHash ?? 'null'})`,
147
+ broken_at: i,
148
+ total_events_processed: eventsProcessed,
149
+ };
150
+ }
151
+ prevExpectedHash = lineThisHash;
152
+ eventsProcessed += 1;
153
+ }
154
+ return { ok: true, total_events: eventsProcessed, tail_hash: prevExpectedHash };
155
+ }
156
+ //# sourceMappingURL=chain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chain.js","sourceRoot":"","sources":["../../src/runtime/chain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEnD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAQ1C;;;;;GAKG;AACH,MAAM,OAAO,cAAc;IAEI;IADrB,QAAQ,CAAgB;IAChC,YAA6B,IAAwB;QAAxB,SAAI,GAAJ,IAAI,CAAoB;QACnD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC;IAC/C,CAAC;IAED,IAAI,CAAC,KAAsB;QACzB,iEAAiE;QACjE,8DAA8D;QAC9D,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;QAC/C,KAAK,QAAQ,CAAC;QACd,MAAM,QAAQ,GAAoB,EAAE,GAAG,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,QAAQ,EAAqB,CAAC;QAC3F,yEAAyE;QACzE,MAAM,SAAS,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAoB,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,CAAC;QAC1D,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;QAC1B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;CACF;AAgBD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,0BAA0B,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,sBAAsB,EAAE,CAAC,EAAE,CAAC;IACzG,CAAC;IACD,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,2BAA2B,IAAI,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;YAC7F,SAAS,EAAE,CAAC;YACZ,sBAAsB,EAAE,CAAC;SAC1B,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC1E,IAAI,gBAAgB,GAAkB,IAAI,CAAC;IAC3C,IAAI,eAAe,GAAG,CAAC,CAAC;IACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;QACvB,IAAI,MAA+B,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC;QACvD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,QAAQ,CAAC,qBAAqB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG;gBACxF,SAAS,EAAE,CAAC;gBACZ,sBAAsB,EAAE,eAAe;aACxC,CAAC;QACJ,CAAC;QACD,8DAA8D;QAC9D,IAAI,eAAe,IAAI,MAAM,IAAI,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,EAAE,CAAC;YACtD,SAAS;QACX,CAAC;QACD,MAAM,YAAY,GAAG,MAAM,CAAC,SAAS,CAAC;QACtC,MAAM,YAAY,GAAG,MAAM,CAAC,SAAS,CAAC;QACtC,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;YACrC,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,QAAQ,CAAC,mCAAmC;gBACnD,SAAS,EAAE,CAAC;gBACZ,sBAAsB,EAAE,eAAe;aACxC,CAAC;QACJ,CAAC;QACD,IAAI,YAAY,KAAK,IAAI,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;YAC9D,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,QAAQ,CAAC,oCAAoC;gBACpD,SAAS,EAAE,CAAC;gBACZ,sBAAsB,EAAE,eAAe;aACxC,CAAC;QACJ,CAAC;QACD,+DAA+D;QAC/D,MAAM,WAAW,GAA4B,EAAE,GAAG,MAAM,EAAE,CAAC;QAC3D,OAAO,WAAW,CAAC,SAAS,CAAC;QAC7B,MAAM,UAAU,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;QAC9C,IAAI,UAAU,KAAK,YAAY,EAAE,CAAC;YAChC,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,QAAQ,CAAC,oEAAoE;gBACpF,SAAS,EAAE,CAAC;gBACZ,sBAAsB,EAAE,eAAe;aACxC,CAAC;QACJ,CAAC;QACD,8CAA8C;QAC9C,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,KAAK,gBAAgB,EAAE,CAAC;YAChD,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,QAAQ,CAAC,qCAAqC,gBAAgB,IAAI,MAAM,SAAS,YAAY,IAAI,MAAM,GAAG;gBACjH,SAAS,EAAE,CAAC;gBACZ,sBAAsB,EAAE,eAAe;aACxC,CAAC;QACJ,CAAC;QACD,gBAAgB,GAAG,YAAY,CAAC;QAChC,eAAe,IAAI,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC;AAClF,CAAC"}
@@ -0,0 +1,104 @@
1
+ import type { Finding, Severity } from '../types.js';
2
+ export interface EngagementEventBase {
3
+ ts: string;
4
+ engagement_id: string;
5
+ event: string;
6
+ /** Hash of the previous event in the chain. null on the first event. */
7
+ prev_hash?: string | null;
8
+ /** SHA-256 of this event's canonical form (excluding this_hash itself). */
9
+ this_hash?: string;
10
+ }
11
+ export type EngagementEvent = (EngagementEventBase & {
12
+ event: 'engagement-start';
13
+ target: string;
14
+ roe_id: string;
15
+ roe_synthesized: boolean;
16
+ mode: string;
17
+ }) | (EngagementEventBase & {
18
+ event: 'phase-transition';
19
+ phase: 'recon' | 'discovery' | 'exploitation' | 'reporting';
20
+ transition: 'enter' | 'exit';
21
+ duration_ms?: number;
22
+ }) | (EngagementEventBase & {
23
+ event: 'finding-emitted';
24
+ finding_id: string;
25
+ severity: Severity;
26
+ title: string;
27
+ cwe?: number;
28
+ /** SHA-256 of the finding's canonical form (APTS-AR-010). */
29
+ evidence_hash?: string;
30
+ }) | (EngagementEventBase & {
31
+ event: 'critical-finding';
32
+ finding_id: string;
33
+ severity: Severity;
34
+ title: string;
35
+ cwe?: number;
36
+ stop_action: 'halt' | 'notify-and-continue' | 'continue';
37
+ }) | (EngagementEventBase & {
38
+ event: 'intervention';
39
+ kind: 'pause' | 'redirect' | 'kill';
40
+ trigger: 'signal-SIGUSR1' | 'signal-SIGTERM' | 'signal-SIGINT' | 'api';
41
+ reason?: string;
42
+ }) | (EngagementEventBase & {
43
+ event: 'resume';
44
+ from_state_file: string;
45
+ completed_phases: string[];
46
+ findings_carried: number;
47
+ }) | (EngagementEventBase & {
48
+ event: 'halt';
49
+ reason: string;
50
+ apts_refs?: string[];
51
+ }) | (EngagementEventBase & {
52
+ event: 'kill';
53
+ signal: 'SIGTERM' | 'SIGINT' | 'SIGUSR2';
54
+ state_dump_path?: string;
55
+ }) | (EngagementEventBase & {
56
+ event: 'completion';
57
+ duration_ms: number;
58
+ total_findings: number;
59
+ score: number;
60
+ grade: string;
61
+ blocked: boolean;
62
+ }) | (EngagementEventBase & {
63
+ event: 'scope-validation';
64
+ target: string;
65
+ action: string;
66
+ allowed: boolean;
67
+ reason: string;
68
+ apts_refs?: string[];
69
+ }) | (EngagementEventBase & {
70
+ event: 'operator-acknowledged-loopback';
71
+ target: string;
72
+ apts_refs: string[];
73
+ warning: string;
74
+ });
75
+ /**
76
+ * Emit an engagement event to a sink. The sink can be:
77
+ * - undefined → stdout (default; siege already writes to stdout for the
78
+ * human-readable spinner; emitting JSONL there is risky because the
79
+ * terminal user would see noisy lines. So we only emit JSONL when an
80
+ * explicit state-file path is configured.)
81
+ * - a file path → append the JSON line to that file.
82
+ * - a callback → in-process consumer (used by tests).
83
+ */
84
+ export type EventSink = string | ((event: EngagementEvent) => void) | undefined;
85
+ export declare function emitEvent(event: EngagementEvent, sink: EventSink): void;
86
+ export declare function makeEvent<T extends EngagementEvent['event']>(engagementId: string, event: T, payload: Omit<Extract<EngagementEvent, {
87
+ event: T;
88
+ }>, 'ts' | 'engagement_id' | 'event'>): Extract<EngagementEvent, {
89
+ event: T;
90
+ }>;
91
+ /**
92
+ * Helper: turn a Finding into a finding-emitted event. Caller decides
93
+ * whether to also emit a critical-finding event for high/critical/blocker
94
+ * severities. Includes evidence_hash (SHA-256 of finding's canonical form)
95
+ * per APTS-AR-010.
96
+ */
97
+ export declare function findingEvent(engagementId: string, f: Finding): EngagementEvent;
98
+ export declare function isCriticalSeverity(s: Severity): boolean;
99
+ /**
100
+ * Initialize a state-file: ensure it's empty / not appended onto a stale
101
+ * run. Caller invokes this at engagement start when --state-file is set.
102
+ */
103
+ export declare function initStateFile(path: string): void;
104
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/runtime/events.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Engagement event schema (JSONL).
3
+ *
4
+ * Closes APTS-HO-002 (Real-Time Monitoring and Intervention Capability) +
5
+ * APTS-AR-002 (State Transition Logging — partial closure pending Cluster-3
6
+ * hash-chain). Each event is one JSON line, written to stdout (default) or
7
+ * a state-file (when --state-file is set on siege).
8
+ *
9
+ * Event types:
10
+ * - engagement-start — emitted when siege begins (after RoE validated)
11
+ * - phase-transition — emitted before/after each of the 4 siege phases
12
+ * - finding-emitted — per-finding event so live monitors can react
13
+ * - critical-finding — high-severity emission, drives stop-conditions
14
+ * - intervention — operator pause/redirect/kill received via signal
15
+ * - resume — engagement re-started from a serialized state file
16
+ * - halt — engagement stopped (clean termination)
17
+ * - kill — engagement aborted (signal-triggered)
18
+ * - completion — engagement finished, final scoring emitted
19
+ * - scope-validation — target in/out of scope decision (APTS-SE-015)
20
+ * - operator-acknowledged-loopback — operator opted in to loopback target (--allow-loopback)
21
+ */
22
+ import { writeFileSync, appendFileSync } from 'node:fs';
23
+ import { hashCanonical } from './hash.js';
24
+ export function emitEvent(event, sink) {
25
+ if (sink === undefined)
26
+ return;
27
+ if (typeof sink === 'function') {
28
+ sink(event);
29
+ return;
30
+ }
31
+ // sink is a file path. Append JSONL — one JSON object per line.
32
+ appendFileSync(sink, JSON.stringify(event) + '\n');
33
+ }
34
+ export function makeEvent(engagementId, event, payload) {
35
+ return {
36
+ ts: new Date().toISOString(),
37
+ engagement_id: engagementId,
38
+ event,
39
+ ...payload,
40
+ };
41
+ }
42
+ /**
43
+ * Helper: turn a Finding into a finding-emitted event. Caller decides
44
+ * whether to also emit a critical-finding event for high/critical/blocker
45
+ * severities. Includes evidence_hash (SHA-256 of finding's canonical form)
46
+ * per APTS-AR-010.
47
+ */
48
+ export function findingEvent(engagementId, f) {
49
+ const evidence_hash = hashCanonical(f);
50
+ return makeEvent(engagementId, 'finding-emitted', {
51
+ finding_id: f.id,
52
+ severity: f.severity,
53
+ title: f.title,
54
+ ...(f.cwe !== undefined ? { cwe: f.cwe } : {}),
55
+ evidence_hash,
56
+ });
57
+ }
58
+ export function isCriticalSeverity(s) {
59
+ return s === 'blocker' || s === 'critical' || s === 'high';
60
+ }
61
+ /**
62
+ * Initialize a state-file: ensure it's empty / not appended onto a stale
63
+ * run. Caller invokes this at engagement start when --state-file is set.
64
+ */
65
+ export function initStateFile(path) {
66
+ writeFileSync(path, '');
67
+ }
68
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/runtime/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAExD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAmG1C,MAAM,UAAU,SAAS,CAAC,KAAsB,EAAE,IAAe;IAC/D,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO;IAC/B,IAAI,OAAO,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,CAAC;QACZ,OAAO;IACT,CAAC;IACD,gEAAgE;IAChE,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,YAAoB,EACpB,KAAQ,EACR,OAAuF;IAEvF,OAAO;QACL,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC5B,aAAa,EAAE,YAAY;QAC3B,KAAK;QACL,GAAG,OAAO;KAC0C,CAAC;AACzD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,YAAoB,EAAE,CAAU;IAC3D,MAAM,aAAa,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IACvC,OAAO,SAAS,CAAC,YAAY,EAAE,iBAAiB,EAAE;QAChD,UAAU,EAAE,CAAC,CAAC,EAAE;QAChB,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9C,aAAa;KACd,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,CAAW;IAC5C,OAAO,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,UAAU,IAAI,CAAC,KAAK,MAAM,CAAC;AAC7D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,aAAa,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * SHA-256 hash of a UTF-8 string, returned as lowercase hex.
3
+ */
4
+ export declare function sha256(input: string): string;
5
+ /**
6
+ * Canonicalize an arbitrary JSON-shaped value: sort keys at every depth,
7
+ * no whitespace, no surprising number formatting. Returns a string suitable
8
+ * for hashing.
9
+ */
10
+ export declare function canonicalize(value: unknown): string;
11
+ /**
12
+ * Hash a JSON-shaped value's canonical form. Convenience wrapper over
13
+ * sha256(canonicalize(value)).
14
+ */
15
+ export declare function hashCanonical(value: unknown): string;
16
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/runtime/hash.ts"],"names":[],"mappings":"AAkBA;;GAEG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEnD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Hash + canonical-serialization helpers.
3
+ *
4
+ * Closes APTS-AR-010 (Cryptographic Hashing of All Evidence) +
5
+ * APTS-AR-012 (Tamper-Evident Logging with Hash Chains) jointly with
6
+ * runtime/chain.ts.
7
+ *
8
+ * Design notes:
9
+ * - SHA-256 (Node `crypto` module). FIPS 180-4 + RFC 6234.
10
+ * - Canonical JSON: sorted keys recursively, no whitespace. Two
11
+ * semantically-equal objects always serialize to the same string.
12
+ * - The `this_hash` field is excluded from the canonical input — you
13
+ * cannot include the hash in its own preimage.
14
+ * - Used by both per-event chain (chain.ts) and per-finding evidence
15
+ * hash (events.ts findingEvent).
16
+ */
17
+ import { createHash } from 'node:crypto';
18
+ /**
19
+ * SHA-256 hash of a UTF-8 string, returned as lowercase hex.
20
+ */
21
+ export function sha256(input) {
22
+ return createHash('sha256').update(input, 'utf-8').digest('hex');
23
+ }
24
+ /**
25
+ * Canonicalize an arbitrary JSON-shaped value: sort keys at every depth,
26
+ * no whitespace, no surprising number formatting. Returns a string suitable
27
+ * for hashing.
28
+ */
29
+ export function canonicalize(value) {
30
+ return JSON.stringify(canonicalNormalize(value));
31
+ }
32
+ /**
33
+ * Hash a JSON-shaped value's canonical form. Convenience wrapper over
34
+ * sha256(canonicalize(value)).
35
+ */
36
+ export function hashCanonical(value) {
37
+ return sha256(canonicalize(value));
38
+ }
39
+ /**
40
+ * Recursively normalize an object so JSON.stringify produces a stable form.
41
+ * - Plain objects: keys sorted lexicographically; values recursed.
42
+ * - Arrays: order preserved, elements recursed.
43
+ * - Primitives + null: returned as-is.
44
+ * - Undefined values + functions are dropped (matching JSON.stringify's
45
+ * own behavior, but here we make it explicit so the canonicalization
46
+ * is consistent across runtimes).
47
+ */
48
+ function canonicalNormalize(value) {
49
+ if (value === null || value === undefined)
50
+ return null;
51
+ const t = typeof value;
52
+ if (t === 'string' || t === 'boolean' || t === 'number')
53
+ return value;
54
+ if (Array.isArray(value))
55
+ return value.map(canonicalNormalize);
56
+ if (t === 'object') {
57
+ const obj = value;
58
+ const keys = Object.keys(obj).sort();
59
+ const out = {};
60
+ for (const k of keys) {
61
+ const v = obj[k];
62
+ if (v === undefined || typeof v === 'function')
63
+ continue;
64
+ out[k] = canonicalNormalize(v);
65
+ }
66
+ return out;
67
+ }
68
+ return null;
69
+ }
70
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/runtime/hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;GAEG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,kBAAkB,CAAC,KAAc;IACxC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACvD,MAAM,CAAC,GAAG,OAAO,KAAK,CAAC;IACvB,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAC/D,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;QACnB,MAAM,GAAG,GAAG,KAAgC,CAAC;QAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QACrC,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YACjB,IAAI,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,UAAU;gBAAE,SAAS;YACzD,GAAG,CAAC,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAC;QACjC,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { emitEvent, makeEvent, findingEvent, isCriticalSeverity, initStateFile, type EngagementEvent, type EngagementEventBase, type EventSink, } from './events.js';
2
+ export { EngagementStateSchema, writeEngagementState, loadEngagementState, newEngagementState, type EngagementState, type LoadStateResult, type LoadStateOk, type LoadStateFailure, } from './state.js';
3
+ export { installSignalHandlers, type DumpReason, type SignalHandlerOptions, } from './signals.js';
4
+ export { dispatchNotification, type NotificationConfig, } from './notifications.js';
5
+ export { sha256, canonicalize, hashCanonical, } from './hash.js';
6
+ export { ChainedEmitter, verifyAuditChain, type ChainedEmitterOpts, type ChainVerifyResult, type ChainVerifyOk, type ChainVerifyFailure, } from './chain.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,SAAS,GACf,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,WAAW,EAChB,KAAK,gBAAgB,GACtB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,qBAAqB,EACrB,KAAK,UAAU,EACf,KAAK,oBAAoB,GAC1B,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,oBAAoB,EACpB,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,MAAM,EACN,YAAY,EACZ,aAAa,GACd,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { emitEvent, makeEvent, findingEvent, isCriticalSeverity, initStateFile, } from './events.js';
2
+ export { EngagementStateSchema, writeEngagementState, loadEngagementState, newEngagementState, } from './state.js';
3
+ export { installSignalHandlers, } from './signals.js';
4
+ export { dispatchNotification, } from './notifications.js';
5
+ export { sha256, canonicalize, hashCanonical, } from './hash.js';
6
+ export { ChainedEmitter, verifyAuditChain, } from './chain.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,aAAa,GAId,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,GAKnB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,qBAAqB,GAGtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,oBAAoB,GAErB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,MAAM,EACN,YAAY,EACZ,aAAa,GACd,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,cAAc,EACd,gBAAgB,GAKjB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Notification dispatcher.
3
+ *
4
+ * Closes APTS-HO-015 (Real-Time Activity Monitoring and Multi-Channel
5
+ * Notification — partial closure: webhook channel only; multi-channel
6
+ * (Slack/email/PagerDuty) is Cluster-2.5 work).
7
+ *
8
+ * Operator declares one or more webhook URLs in the RoE schema (notifications
9
+ * field) or via the siege --notify-webhook flag. The dispatcher fires
10
+ * fire-and-forget HTTP POST with the JSONL event payload. Failures are
11
+ * logged to the same event channel as `notification-failed` events but do
12
+ * not halt the engagement.
13
+ */
14
+ import type { EngagementEvent, EventSink } from './events.js';
15
+ export interface NotificationConfig {
16
+ /** Webhook URLs to POST events to. Operators may set multiple. */
17
+ webhooks: string[];
18
+ /** Subset of event types to forward. Defaults to high-signal events. */
19
+ events?: EngagementEvent['event'][];
20
+ /** Per-request timeout in ms. Default 5000. */
21
+ timeoutMs?: number;
22
+ }
23
+ export declare function dispatchNotification(event: EngagementEvent, config: NotificationConfig, eventSink: EventSink, fetcher?: typeof fetch): Promise<void>;
24
+ //# sourceMappingURL=notifications.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../src/runtime/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG9D,MAAM,WAAW,kBAAkB;IACjC,kEAAkE;IAClE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,wEAAwE;IACxE,MAAM,CAAC,EAAE,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC;IACpC,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAWD,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,kBAAkB,EAC1B,SAAS,EAAE,SAAS,EACpB,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,IAAI,CAAC,CAkCf"}
@@ -0,0 +1,41 @@
1
+ import { emitEvent, makeEvent } from './events.js';
2
+ const DEFAULT_FORWARDED = [
3
+ 'engagement-start',
4
+ 'critical-finding',
5
+ 'intervention',
6
+ 'halt',
7
+ 'kill',
8
+ 'completion',
9
+ ];
10
+ export async function dispatchNotification(event, config, eventSink, fetcher = fetch) {
11
+ const allowed = config.events ?? DEFAULT_FORWARDED;
12
+ if (!allowed.includes(event.event))
13
+ return;
14
+ const timeoutMs = config.timeoutMs ?? 5000;
15
+ for (const url of config.webhooks) {
16
+ const controller = new AbortController();
17
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
18
+ try {
19
+ const res = await fetcher(url, {
20
+ method: 'POST',
21
+ headers: { 'content-type': 'application/json' },
22
+ body: JSON.stringify(event),
23
+ signal: controller.signal,
24
+ });
25
+ if (!res.ok) {
26
+ emitEvent(makeEvent(event.engagement_id, 'halt', {
27
+ reason: `notification-webhook ${url} returned ${res.status} for event ${event.event} — non-fatal`,
28
+ }), eventSink);
29
+ }
30
+ }
31
+ catch (err) {
32
+ emitEvent(makeEvent(event.engagement_id, 'halt', {
33
+ reason: `notification-webhook ${url} threw for event ${event.event}: ${err instanceof Error ? err.message : String(err)} — non-fatal`,
34
+ }), eventSink);
35
+ }
36
+ finally {
37
+ clearTimeout(timer);
38
+ }
39
+ }
40
+ }
41
+ //# sourceMappingURL=notifications.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../src/runtime/notifications.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAWnD,MAAM,iBAAiB,GAA+B;IACpD,kBAAkB;IAClB,kBAAkB;IAClB,cAAc;IACd,MAAM;IACN,MAAM;IACN,YAAY;CACb,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAAsB,EACtB,MAA0B,EAC1B,SAAoB,EACpB,UAAwB,KAAK;IAE7B,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,IAAI,iBAAiB,CAAC;IACnD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,OAAO;IAE3C,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,IAAI,CAAC;IAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QAClC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;QAC9D,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE;gBAC7B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;gBAC3B,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,SAAS,CACP,SAAS,CAAC,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE;oBACrC,MAAM,EAAE,wBAAwB,GAAG,aAAa,GAAG,CAAC,MAAM,cAAc,KAAK,CAAC,KAAK,cAAc;iBAClG,CAAC,EACF,SAAS,CACV,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,SAAS,CACP,SAAS,CAAC,KAAK,CAAC,aAAa,EAAE,MAAM,EAAE;gBACrC,MAAM,EAAE,wBAAwB,GAAG,oBAAoB,KAAK,CAAC,KAAK,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc;aACtI,CAAC,EACF,SAAS,CACV,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Signal handlers for siege engagements.
3
+ *
4
+ * Closes APTS-HO-008 (Immediate Kill Switch with State Dump) +
5
+ * APTS-AL-012 (Kill Switch and Pause Capability) jointly with state.ts.
6
+ *
7
+ * Signal contract:
8
+ * - SIGINT (Ctrl+C) → kill, dump state, exit code 130
9
+ * - SIGTERM (kill <pid>) → kill, dump state, exit code 143
10
+ * - SIGUSR1 → graceful pause: dump state, exit code 0
11
+ * so the operator can later --resume
12
+ * - SIGUSR2 → reserved for future redirect (no-op v0.1)
13
+ *
14
+ * Handlers are installed once per engagement and cleared on completion.
15
+ * In-process tests can use a synchronous shim to verify the dump-state
16
+ * callback fires without actually emitting OS signals.
17
+ */
18
+ import type { EngagementState } from './state.js';
19
+ import type { EventSink } from './events.js';
20
+ import type { ChainedEmitter } from './chain.js';
21
+ export type DumpReason = 'SIGINT' | 'SIGTERM' | 'SIGUSR1' | 'SIGUSR2';
22
+ export interface SignalHandlerOptions {
23
+ /** Path to write the state-dump on signal. */
24
+ stateFilePath: string | null;
25
+ /** Provider of the current engagement state to dump. */
26
+ getState: () => EngagementState;
27
+ /** Sink to record the kill/intervention event. Same channel as the JSONL event-stream. */
28
+ eventSink: EventSink;
29
+ /** Engagement-id used by emit helpers. */
30
+ engagementId: string;
31
+ /**
32
+ * Optional ChainedEmitter — when provided, the kill/intervention event
33
+ * is appended to the same hash chain as the regular siege events so
34
+ * `audit-verify` continues to validate the full timeline. Without this
35
+ * option, the event is emitted directly via emitEvent (un-chained),
36
+ * which is the legacy path for back-compat.
37
+ */
38
+ chainEmitter?: ChainedEmitter;
39
+ /**
40
+ * Optional: process.exit hook (defaults to real process.exit). Tests
41
+ * inject a no-op to verify the cleanup path without halting the test.
42
+ */
43
+ exit?: (code: number) => void;
44
+ /**
45
+ * Optional: process.on registrar (defaults to real process.on). Tests
46
+ * inject an in-memory registrar to drive handler callbacks deterministically.
47
+ */
48
+ on?: (signal: string, handler: () => void) => void;
49
+ }
50
+ interface InstalledHandlers {
51
+ /** Removes all installed signal handlers. Call on engagement completion. */
52
+ uninstall(): void;
53
+ }
54
+ export declare function installSignalHandlers(opts: SignalHandlerOptions): InstalledHandlers;
55
+ export {};
56
+ //# sourceMappingURL=signals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signals.d.ts","sourceRoot":"","sources":["../../src/runtime/signals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,OAAO,KAAK,EAAmB,SAAS,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAEtE,MAAM,WAAW,oBAAoB;IACnC,8CAA8C;IAC9C,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wDAAwD;IACxD,QAAQ,EAAE,MAAM,eAAe,CAAC;IAChC,0FAA0F;IAC1F,SAAS,EAAE,SAAS,CAAC;IACrB,0CAA0C;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,cAAc,CAAC;IAC9B;;;OAGG;IACH,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B;;;OAGG;IACH,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CACpD;AAED,UAAU,iBAAiB;IACzB,4EAA4E;IAC5E,SAAS,IAAI,IAAI,CAAC;CACnB;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,oBAAoB,GAAG,iBAAiB,CAuEnF"}