@aegis-scan/core 0.16.6 → 0.17.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/README.md +37 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/manipulation-resistance/ai-io-boundary.d.ts +84 -0
- package/dist/manipulation-resistance/ai-io-boundary.d.ts.map +1 -0
- package/dist/manipulation-resistance/ai-io-boundary.js +216 -0
- package/dist/manipulation-resistance/ai-io-boundary.js.map +1 -0
- package/dist/manipulation-resistance/config-integrity.d.ts +28 -0
- package/dist/manipulation-resistance/config-integrity.d.ts.map +1 -0
- package/dist/manipulation-resistance/config-integrity.js +53 -0
- package/dist/manipulation-resistance/config-integrity.js.map +1 -0
- package/dist/manipulation-resistance/index.d.ts +16 -0
- package/dist/manipulation-resistance/index.d.ts.map +1 -0
- package/dist/manipulation-resistance/index.js +16 -0
- package/dist/manipulation-resistance/index.js.map +1 -0
- package/dist/manipulation-resistance/instruction-boundary.d.ts +50 -0
- package/dist/manipulation-resistance/instruction-boundary.d.ts.map +1 -0
- package/dist/manipulation-resistance/instruction-boundary.js +114 -0
- package/dist/manipulation-resistance/instruction-boundary.js.map +1 -0
- package/dist/manipulation-resistance/oob-blocker.d.ts +58 -0
- package/dist/manipulation-resistance/oob-blocker.d.ts.map +1 -0
- package/dist/manipulation-resistance/oob-blocker.js +55 -0
- package/dist/manipulation-resistance/oob-blocker.js.map +1 -0
- package/dist/manipulation-resistance/redirect-policy.d.ts +43 -0
- package/dist/manipulation-resistance/redirect-policy.d.ts.map +1 -0
- package/dist/manipulation-resistance/redirect-policy.js +197 -0
- package/dist/manipulation-resistance/redirect-policy.js.map +1 -0
- package/dist/manipulation-resistance/response-validator.d.ts +33 -0
- package/dist/manipulation-resistance/response-validator.d.ts.map +1 -0
- package/dist/manipulation-resistance/response-validator.js +186 -0
- package/dist/manipulation-resistance/response-validator.js.map +1 -0
- package/dist/manipulation-resistance/scope-expansion-detector.d.ts +33 -0
- package/dist/manipulation-resistance/scope-expansion-detector.d.ts.map +1 -0
- package/dist/manipulation-resistance/scope-expansion-detector.js +68 -0
- package/dist/manipulation-resistance/scope-expansion-detector.js.map +1 -0
- package/dist/oversight/approval-gates.d.ts +77 -0
- package/dist/oversight/approval-gates.d.ts.map +1 -0
- package/dist/oversight/approval-gates.js +133 -0
- package/dist/oversight/approval-gates.js.map +1 -0
- package/dist/oversight/authority-matrix.d.ts +39 -0
- package/dist/oversight/authority-matrix.d.ts.map +1 -0
- package/dist/oversight/authority-matrix.js +75 -0
- package/dist/oversight/authority-matrix.js.map +1 -0
- package/dist/oversight/cia-scoring.d.ts +56 -0
- package/dist/oversight/cia-scoring.d.ts.map +1 -0
- package/dist/oversight/cia-scoring.js +98 -0
- package/dist/oversight/cia-scoring.js.map +1 -0
- package/dist/oversight/escalation.d.ts +58 -0
- package/dist/oversight/escalation.d.ts.map +1 -0
- package/dist/oversight/escalation.js +97 -0
- package/dist/oversight/escalation.js.map +1 -0
- package/dist/oversight/index.d.ts +15 -0
- package/dist/oversight/index.d.ts.map +1 -0
- package/dist/oversight/index.js +15 -0
- package/dist/oversight/index.js.map +1 -0
- package/dist/roe/index.d.ts +3 -0
- package/dist/roe/index.d.ts.map +1 -0
- package/dist/roe/index.js +3 -0
- package/dist/roe/index.js.map +1 -0
- package/dist/roe/loader.d.ts +15 -0
- package/dist/roe/loader.d.ts.map +1 -0
- package/dist/roe/loader.js +56 -0
- package/dist/roe/loader.js.map +1 -0
- package/dist/roe/types.d.ts +738 -0
- package/dist/roe/types.d.ts.map +1 -0
- package/dist/roe/types.js +525 -0
- package/dist/roe/types.js.map +1 -0
- package/dist/runtime/chain.d.ts +60 -0
- package/dist/runtime/chain.d.ts.map +1 -0
- package/dist/runtime/chain.js +156 -0
- package/dist/runtime/chain.js.map +1 -0
- package/dist/runtime/events.d.ts +104 -0
- package/dist/runtime/events.d.ts.map +1 -0
- package/dist/runtime/events.js +68 -0
- package/dist/runtime/events.js.map +1 -0
- package/dist/runtime/hash.d.ts +16 -0
- package/dist/runtime/hash.d.ts.map +1 -0
- package/dist/runtime/hash.js +70 -0
- package/dist/runtime/hash.js.map +1 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +7 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/notifications.d.ts +24 -0
- package/dist/runtime/notifications.d.ts.map +1 -0
- package/dist/runtime/notifications.js +41 -0
- package/dist/runtime/notifications.js.map +1 -0
- package/dist/runtime/signals.d.ts +56 -0
- package/dist/runtime/signals.d.ts.map +1 -0
- package/dist/runtime/signals.js +72 -0
- package/dist/runtime/signals.js.map +1 -0
- package/dist/runtime/state.d.ts +88 -0
- package/dist/runtime/state.d.ts.map +1 -0
- package/dist/runtime/state.js +172 -0
- package/dist/runtime/state.js.map +1 -0
- package/dist/safety-controls/boundary-monitor.d.ts +45 -0
- package/dist/safety-controls/boundary-monitor.d.ts.map +1 -0
- package/dist/safety-controls/boundary-monitor.js +77 -0
- package/dist/safety-controls/boundary-monitor.js.map +1 -0
- package/dist/safety-controls/decision-timeout.d.ts +56 -0
- package/dist/safety-controls/decision-timeout.d.ts.map +1 -0
- package/dist/safety-controls/decision-timeout.js +67 -0
- package/dist/safety-controls/decision-timeout.js.map +1 -0
- package/dist/safety-controls/health-monitor.d.ts +61 -0
- package/dist/safety-controls/health-monitor.d.ts.map +1 -0
- package/dist/safety-controls/health-monitor.js +79 -0
- package/dist/safety-controls/health-monitor.js.map +1 -0
- package/dist/safety-controls/index.d.ts +13 -0
- package/dist/safety-controls/index.d.ts.map +1 -0
- package/dist/safety-controls/index.js +13 -0
- package/dist/safety-controls/index.js.map +1 -0
- package/dist/safety-controls/kill-switch.d.ts +45 -0
- package/dist/safety-controls/kill-switch.d.ts.map +1 -0
- package/dist/safety-controls/kill-switch.js +117 -0
- package/dist/safety-controls/kill-switch.js.map +1 -0
- package/dist/safety-controls/post-test-integrity.d.ts +51 -0
- package/dist/safety-controls/post-test-integrity.d.ts.map +1 -0
- package/dist/safety-controls/post-test-integrity.js +79 -0
- package/dist/safety-controls/post-test-integrity.js.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
- 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":"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"}
|
|
@@ -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"}
|