@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,72 @@
1
+ import { writeEngagementState } from './state.js';
2
+ import { emitEvent, makeEvent } from './events.js';
3
+ export function installSignalHandlers(opts) {
4
+ const exit = opts.exit ?? ((code) => process.exit(code));
5
+ const on = opts.on ?? ((sig, h) => process.on(sig, h));
6
+ const handlers = [];
7
+ const dumpAndEvent = (reason, exitCode, kind) => {
8
+ let dumpPath;
9
+ try {
10
+ const state = opts.getState();
11
+ // Update the snapshot's pause-reason and timestamp before flushing.
12
+ const stamped = {
13
+ ...state,
14
+ paused_at: new Date().toISOString(),
15
+ reason: `signal-${reason}`,
16
+ };
17
+ if (opts.stateFilePath) {
18
+ writeEngagementState(opts.stateFilePath, stamped);
19
+ dumpPath = opts.stateFilePath;
20
+ }
21
+ }
22
+ catch {
23
+ // Dump is best-effort; never let a state-write failure mask the exit.
24
+ }
25
+ const ev = kind === 'kill'
26
+ ? makeEvent(opts.engagementId, 'kill', { signal: reason, ...(dumpPath ? { state_dump_path: dumpPath } : {}) })
27
+ : makeEvent(opts.engagementId, 'intervention', {
28
+ kind: 'pause',
29
+ trigger: `signal-${reason}`,
30
+ reason: 'operator-paused',
31
+ });
32
+ try {
33
+ // Chain the event into the same hash-chain as regular siege events
34
+ // when a ChainedEmitter is provided. This keeps audit-verify happy
35
+ // when the engagement is killed mid-flight (otherwise the kill
36
+ // event would be a chain break).
37
+ if (opts.chainEmitter) {
38
+ opts.chainEmitter.emit(ev);
39
+ }
40
+ else {
41
+ emitEvent(ev, opts.eventSink);
42
+ }
43
+ }
44
+ catch {
45
+ // best-effort
46
+ }
47
+ exit(exitCode);
48
+ };
49
+ const sigint = () => dumpAndEvent('SIGINT', 130, 'kill');
50
+ const sigterm = () => dumpAndEvent('SIGTERM', 143, 'kill');
51
+ const sigusr1 = () => dumpAndEvent('SIGUSR1', 0, 'pause');
52
+ on('SIGINT', sigint);
53
+ handlers.push({ sig: 'SIGINT', handler: sigint });
54
+ on('SIGTERM', sigterm);
55
+ handlers.push({ sig: 'SIGTERM', handler: sigterm });
56
+ on('SIGUSR1', sigusr1);
57
+ handlers.push({ sig: 'SIGUSR1', handler: sigusr1 });
58
+ return {
59
+ uninstall() {
60
+ for (const { sig, handler } of handlers) {
61
+ try {
62
+ process.removeListener(sig, handler);
63
+ }
64
+ catch {
65
+ // Tests may inject a stub on() that never registers in the real
66
+ // process — removeListener is a no-op then.
67
+ }
68
+ }
69
+ },
70
+ };
71
+ }
72
+ //# sourceMappingURL=signals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signals.js","sourceRoot":"","sources":["../../src/runtime/signals.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ import { z } from 'zod';
2
+ import type { Finding } from '../types.js';
3
+ export declare const EngagementStateSchema: z.ZodObject<{
4
+ state_version: z.ZodLiteral<"0.1.0">;
5
+ engagement_id: z.ZodString;
6
+ target: z.ZodString;
7
+ roe_id: z.ZodString;
8
+ completed_phases: z.ZodArray<z.ZodEnum<["recon", "discovery", "exploitation", "reporting"]>, "many">;
9
+ findings_so_far: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
10
+ paused_at: z.ZodString;
11
+ reason: z.ZodString;
12
+ }, "strict", z.ZodTypeAny, {
13
+ reason: string;
14
+ target: string;
15
+ roe_id: string;
16
+ engagement_id: string;
17
+ completed_phases: ("recon" | "discovery" | "exploitation" | "reporting")[];
18
+ state_version: "0.1.0";
19
+ findings_so_far: Record<string, unknown>[];
20
+ paused_at: string;
21
+ }, {
22
+ reason: string;
23
+ target: string;
24
+ roe_id: string;
25
+ engagement_id: string;
26
+ completed_phases: ("recon" | "discovery" | "exploitation" | "reporting")[];
27
+ state_version: "0.1.0";
28
+ findings_so_far: Record<string, unknown>[];
29
+ paused_at: string;
30
+ }>;
31
+ export interface EngagementState {
32
+ state_version: '0.1.0';
33
+ engagement_id: string;
34
+ target: string;
35
+ roe_id: string;
36
+ completed_phases: ('recon' | 'discovery' | 'exploitation' | 'reporting')[];
37
+ findings_so_far: Finding[];
38
+ paused_at: string;
39
+ reason: string;
40
+ }
41
+ /**
42
+ * Append the engagement-state snapshot as a single JSONL line.
43
+ *
44
+ * Was: `writeFileSync(path, JSON.stringify(state, null, 2))` which
45
+ * overwrote the entire state file — destroying every JSONL event that
46
+ * had been appended since the last snapshot. The audit on 2026-04-27
47
+ * flagged this as both a format inconsistency (mixed pretty-JSON +
48
+ * JSONL) AND a data-loss bug (events between persistStates were lost).
49
+ *
50
+ * Now: each call appends one JSONL line. Snapshots and events coexist
51
+ * in the same file. `loadEngagementState` reads the last snapshot.
52
+ */
53
+ export declare function writeEngagementState(path: string, state: EngagementState): void;
54
+ export interface LoadStateOk {
55
+ ok: true;
56
+ state: EngagementState;
57
+ }
58
+ export interface LoadStateFailure {
59
+ ok: false;
60
+ error: string;
61
+ phase: 'file-missing' | 'json-parse' | 'schema-validation';
62
+ }
63
+ export type LoadStateResult = LoadStateOk | LoadStateFailure;
64
+ /**
65
+ * Load the latest engagement-state snapshot from a JSONL state-file.
66
+ *
67
+ * Reads line-by-line, identifies snapshot lines by the presence of a
68
+ * `state_version` field, schema-validates each, and returns the LAST
69
+ * (most recent) successful match. Event lines (with `ts`+`event`) are
70
+ * skipped. Malformed lines are skipped with a count tracked for the
71
+ * debug error path; only snapshots that pass the strict schema are
72
+ * candidates.
73
+ *
74
+ * Back-compat: also accepts a single-object pretty-JSON file (the
75
+ * pre-audit format). When the entire file parses as one EngagementState
76
+ * object, return that. This preserves resume-from-pre-audit-state
77
+ * functionality during the migration window.
78
+ */
79
+ export declare function loadEngagementState(path: string): LoadStateResult;
80
+ /**
81
+ * Build a fresh engagement state at engagement start.
82
+ */
83
+ export declare function newEngagementState(args: {
84
+ engagement_id: string;
85
+ target: string;
86
+ roe_id: string;
87
+ }): EngagementState;
88
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/runtime/state.ts"],"names":[],"mappings":"AA+BA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAI3C,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWvB,CAAC;AAEZ,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,CAAC,OAAO,GAAG,WAAW,GAAG,cAAc,GAAG,WAAW,CAAC,EAAE,CAAC;IAC3E,eAAe,EAAE,OAAO,EAAE,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI,CAE/E;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,IAAI,CAAC;IACT,KAAK,EAAE,eAAe,CAAC;CACxB;AACD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,cAAc,GAAG,YAAY,GAAG,mBAAmB,CAAC;CAC5D;AACD,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,gBAAgB,CAAC;AAE7D;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CA8EjE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,eAAe,CAWlB"}
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Engagement state snapshot — the durable artifact that supports
3
+ * pause-and-resume.
4
+ *
5
+ * Closes APTS-HO-006 (Graceful Pause Mechanism with State Preservation) +
6
+ * APTS-HO-008 (Immediate Kill Switch with State Dump) jointly with
7
+ * runtime/signals.ts.
8
+ *
9
+ * Snapshot scope (Cluster-2 v1):
10
+ * - engagement_id (matches the JSONL event stream)
11
+ * - target + roe_id
12
+ * - completed_phases — engagement is phase-grained; resume restarts the
13
+ * next phase, not mid-phase. Simpler than the full mid-phase resume
14
+ * and sufficient for siege's 4-phase shape.
15
+ * - findings_so_far — the in-flight findings array
16
+ * - paused_at — ISO-8601 timestamp
17
+ * - reason — short rationale (e.g. "operator-SIGUSR1", "temporal-expiry")
18
+ *
19
+ * State-file format (audit-fix 2026-04-27):
20
+ * - PURE JSONL. Every line is a single-line JSON object.
21
+ * - Snapshots have a `state_version` field; events have `ts` + `event`.
22
+ * - Snapshots are APPENDED, not overwritten. `loadEngagementState`
23
+ * reads the file line-by-line and returns the LAST snapshot.
24
+ * - Previous behavior (overwriting with pretty-printed JSON) was
25
+ * destroying events between persistStates and breaking line-oriented
26
+ * tools like `jq -c`.
27
+ *
28
+ * Mid-phase resume is deferred to a future cluster — it requires
29
+ * scanner-level checkpointing which the orchestrator doesn't expose today.
30
+ */
31
+ import { readFileSync, appendFileSync, existsSync } from 'node:fs';
32
+ import { z } from 'zod';
33
+ const FindingPassthroughSchema = z.record(z.unknown());
34
+ export const EngagementStateSchema = z
35
+ .object({
36
+ state_version: z.literal('0.1.0'),
37
+ engagement_id: z.string().min(1),
38
+ target: z.string().min(1),
39
+ roe_id: z.string().min(1),
40
+ completed_phases: z.array(z.enum(['recon', 'discovery', 'exploitation', 'reporting'])),
41
+ findings_so_far: z.array(FindingPassthroughSchema),
42
+ paused_at: z.string().datetime({ offset: true }),
43
+ reason: z.string().min(1),
44
+ })
45
+ .strict();
46
+ /**
47
+ * Append the engagement-state snapshot as a single JSONL line.
48
+ *
49
+ * Was: `writeFileSync(path, JSON.stringify(state, null, 2))` which
50
+ * overwrote the entire state file — destroying every JSONL event that
51
+ * had been appended since the last snapshot. The audit on 2026-04-27
52
+ * flagged this as both a format inconsistency (mixed pretty-JSON +
53
+ * JSONL) AND a data-loss bug (events between persistStates were lost).
54
+ *
55
+ * Now: each call appends one JSONL line. Snapshots and events coexist
56
+ * in the same file. `loadEngagementState` reads the last snapshot.
57
+ */
58
+ export function writeEngagementState(path, state) {
59
+ appendFileSync(path, JSON.stringify(state) + '\n');
60
+ }
61
+ /**
62
+ * Load the latest engagement-state snapshot from a JSONL state-file.
63
+ *
64
+ * Reads line-by-line, identifies snapshot lines by the presence of a
65
+ * `state_version` field, schema-validates each, and returns the LAST
66
+ * (most recent) successful match. Event lines (with `ts`+`event`) are
67
+ * skipped. Malformed lines are skipped with a count tracked for the
68
+ * debug error path; only snapshots that pass the strict schema are
69
+ * candidates.
70
+ *
71
+ * Back-compat: also accepts a single-object pretty-JSON file (the
72
+ * pre-audit format). When the entire file parses as one EngagementState
73
+ * object, return that. This preserves resume-from-pre-audit-state
74
+ * functionality during the migration window.
75
+ */
76
+ export function loadEngagementState(path) {
77
+ if (!existsSync(path)) {
78
+ return { ok: false, error: `state file not found at ${path}`, phase: 'file-missing' };
79
+ }
80
+ let raw;
81
+ try {
82
+ raw = readFileSync(path, 'utf-8');
83
+ }
84
+ catch (err) {
85
+ return {
86
+ ok: false,
87
+ error: `state file unreadable at ${path}: ${err instanceof Error ? err.message : String(err)}`,
88
+ phase: 'file-missing',
89
+ };
90
+ }
91
+ // Back-compat: try parsing the whole file as a single JSON snapshot first.
92
+ // The pre-audit format wrote pretty-printed JSON without JSONL structure.
93
+ const trimmed = raw.trim();
94
+ if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
95
+ try {
96
+ const parsed = JSON.parse(trimmed);
97
+ const result = EngagementStateSchema.safeParse(parsed);
98
+ if (result.success) {
99
+ return { ok: true, state: result.data };
100
+ }
101
+ // Fall through to JSONL parse — the file might be JSONL where the
102
+ // first event is a single-line snapshot followed by event lines.
103
+ }
104
+ catch {
105
+ // Not a single-object file; fall through to JSONL.
106
+ }
107
+ }
108
+ // JSONL: parse line-by-line, return the last successful snapshot.
109
+ const lines = raw.split('\n').filter((l) => l.trim().length > 0);
110
+ let lastSnapshot = null;
111
+ let parseErrors = 0;
112
+ let schemaErrors = 0;
113
+ for (const line of lines) {
114
+ let parsed;
115
+ try {
116
+ parsed = JSON.parse(line);
117
+ }
118
+ catch {
119
+ parseErrors += 1;
120
+ continue;
121
+ }
122
+ if (typeof parsed !== 'object' || parsed === null)
123
+ continue;
124
+ if (!('state_version' in parsed))
125
+ continue; // event line, skip
126
+ const result = EngagementStateSchema.safeParse(parsed);
127
+ if (result.success) {
128
+ lastSnapshot = result.data;
129
+ }
130
+ else {
131
+ schemaErrors += 1;
132
+ }
133
+ }
134
+ if (lastSnapshot) {
135
+ return { ok: true, state: lastSnapshot };
136
+ }
137
+ if (parseErrors > 0 && lines.length === parseErrors) {
138
+ return {
139
+ ok: false,
140
+ error: `state file at ${path}: no parseable JSONL line found (${parseErrors} malformed)`,
141
+ phase: 'json-parse',
142
+ };
143
+ }
144
+ if (schemaErrors > 0) {
145
+ return {
146
+ ok: false,
147
+ error: `state file at ${path}: ${schemaErrors} snapshot-shaped lines failed schema validation`,
148
+ phase: 'schema-validation',
149
+ };
150
+ }
151
+ return {
152
+ ok: false,
153
+ error: `state file at ${path} contains no engagement-state snapshot`,
154
+ phase: 'schema-validation',
155
+ };
156
+ }
157
+ /**
158
+ * Build a fresh engagement state at engagement start.
159
+ */
160
+ export function newEngagementState(args) {
161
+ return {
162
+ state_version: '0.1.0',
163
+ engagement_id: args.engagement_id,
164
+ target: args.target,
165
+ roe_id: args.roe_id,
166
+ completed_phases: [],
167
+ findings_so_far: [],
168
+ paused_at: new Date().toISOString(),
169
+ reason: 'engagement-start',
170
+ };
171
+ }
172
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/runtime/state.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Continuous boundary monitoring + breach detection.
3
+ *
4
+ * Closes APTS-AL-016 (Continuous Boundary Monitoring and Breach
5
+ * Detection).
6
+ *
7
+ * Design notes:
8
+ * - Cluster-3 emits a single scope-validation event at engagement
9
+ * start. AL-016 requires *continuous* monitoring — every scanner
10
+ * emission re-validates the finding's target against the loaded
11
+ * RoE.
12
+ * - Strategy: per finding, validate the finding's location URL (or
13
+ * its `file` path for SAST). On out-of-scope detection, emit a
14
+ * scope-validation breach event and return a halt-decision. The
15
+ * orchestrator caller is responsible for actually halting; this
16
+ * module is pure-function so it remains test-friendly.
17
+ */
18
+ import { type RoE, type ValidationDecision } from '../roe/types.js';
19
+ export interface BreachDetectionResult {
20
+ /** True iff the finding's target/location is in-scope. */
21
+ in_scope: boolean;
22
+ decision: ValidationDecision;
23
+ /** The URL or file the finding referred to. */
24
+ inspected: string;
25
+ apts_refs: string[];
26
+ }
27
+ export interface FindingLike {
28
+ /** Optional finding-emitted target URL (DAST findings). */
29
+ target?: string;
30
+ /** Optional finding-emitted source file (SAST findings). */
31
+ file?: string;
32
+ /** Optional explicit location URL. */
33
+ location?: string;
34
+ }
35
+ /**
36
+ * Inspect a finding's location/target/file and validate against the RoE.
37
+ * Returns in_scope=true with the underlying decision when allowed, or
38
+ * in_scope=false with the rejection reason.
39
+ *
40
+ * Findings without any URL/path field (purely advisory) skip validation
41
+ * and return in_scope=true so the orchestrator does not halt on
42
+ * intentionally-unsourced findings.
43
+ */
44
+ export declare function detectScopeBreach(f: FindingLike, roe: RoE): BreachDetectionResult;
45
+ //# sourceMappingURL=boundary-monitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boundary-monitor.d.ts","sourceRoot":"","sources":["../../src/safety-controls/boundary-monitor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAyB,KAAK,GAAG,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE3F,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,CAAC,EAAE,WAAW,EACd,GAAG,EAAE,GAAG,GACP,qBAAqB,CAoCvB"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Continuous boundary monitoring + breach detection.
3
+ *
4
+ * Closes APTS-AL-016 (Continuous Boundary Monitoring and Breach
5
+ * Detection).
6
+ *
7
+ * Design notes:
8
+ * - Cluster-3 emits a single scope-validation event at engagement
9
+ * start. AL-016 requires *continuous* monitoring — every scanner
10
+ * emission re-validates the finding's target against the loaded
11
+ * RoE.
12
+ * - Strategy: per finding, validate the finding's location URL (or
13
+ * its `file` path for SAST). On out-of-scope detection, emit a
14
+ * scope-validation breach event and return a halt-decision. The
15
+ * orchestrator caller is responsible for actually halting; this
16
+ * module is pure-function so it remains test-friendly.
17
+ */
18
+ import { validateTargetInScope } from '../roe/types.js';
19
+ /**
20
+ * Inspect a finding's location/target/file and validate against the RoE.
21
+ * Returns in_scope=true with the underlying decision when allowed, or
22
+ * in_scope=false with the rejection reason.
23
+ *
24
+ * Findings without any URL/path field (purely advisory) skip validation
25
+ * and return in_scope=true so the orchestrator does not halt on
26
+ * intentionally-unsourced findings.
27
+ */
28
+ export function detectScopeBreach(f, roe) {
29
+ const inspect = pickTarget(f);
30
+ if (!inspect) {
31
+ return {
32
+ in_scope: true,
33
+ decision: {
34
+ allowed: true,
35
+ reason: 'finding has no inspectable URL/path; boundary check skipped',
36
+ apts_refs: ['APTS-AL-016'],
37
+ },
38
+ inspected: '',
39
+ apts_refs: ['APTS-AL-016'],
40
+ };
41
+ }
42
+ // For file paths (SAST), we do not URL-parse — boundary check is
43
+ // currently URL-shaped only. Future work: extend RoE.in_scope.repository_paths
44
+ // to gate file-path findings continuously.
45
+ if (!isUrlLike(inspect)) {
46
+ return {
47
+ in_scope: true,
48
+ decision: {
49
+ allowed: true,
50
+ reason: `finding location is a file path ("${inspect}"); URL boundary check not applicable`,
51
+ apts_refs: ['APTS-AL-016'],
52
+ },
53
+ inspected: inspect,
54
+ apts_refs: ['APTS-AL-016'],
55
+ };
56
+ }
57
+ const decision = validateTargetInScope(inspect, roe);
58
+ return {
59
+ in_scope: decision.allowed,
60
+ decision,
61
+ inspected: inspect,
62
+ apts_refs: ['APTS-AL-016', ...(decision.apts_refs ?? [])],
63
+ };
64
+ }
65
+ function pickTarget(f) {
66
+ if (f.location && f.location.length > 0)
67
+ return f.location;
68
+ if (f.target && f.target.length > 0)
69
+ return f.target;
70
+ if (f.file && f.file.length > 0)
71
+ return f.file;
72
+ return '';
73
+ }
74
+ function isUrlLike(s) {
75
+ return /^https?:\/\//i.test(s);
76
+ }
77
+ //# sourceMappingURL=boundary-monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boundary-monitor.js","sourceRoot":"","sources":["../../src/safety-controls/boundary-monitor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,qBAAqB,EAAqC,MAAM,iBAAiB,CAAC;AAoB3F;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,CAAc,EACd,GAAQ;IAER,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;YACL,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE;gBACR,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,6DAA6D;gBACrE,SAAS,EAAE,CAAC,aAAa,CAAC;aAC3B;YACD,SAAS,EAAE,EAAE;YACb,SAAS,EAAE,CAAC,aAAa,CAAC;SAC3B,CAAC;IACJ,CAAC;IACD,iEAAiE;IACjE,+EAA+E;IAC/E,2CAA2C;IAC3C,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;QACxB,OAAO;YACL,QAAQ,EAAE,IAAI;YACd,QAAQ,EAAE;gBACR,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,qCAAqC,OAAO,uCAAuC;gBAC3F,SAAS,EAAE,CAAC,aAAa,CAAC;aAC3B;YACD,SAAS,EAAE,OAAO;YAClB,SAAS,EAAE,CAAC,aAAa,CAAC;SAC3B,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACrD,OAAO;QACL,QAAQ,EAAE,QAAQ,CAAC,OAAO;QAC1B,QAAQ;QACR,SAAS,EAAE,OAAO;QAClB,SAAS,EAAE,CAAC,aAAa,EAAE,GAAG,CAAC,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;KAC1D,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,CAAc;IAChC,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC,QAAQ,CAAC;IAC3D,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC,MAAM,CAAC;IACrD,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC,IAAI,CAAC;IAC/C,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,SAAS,CAAC,CAAS;IAC1B,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjC,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Per-phase decision timeout with default-safe halt.
3
+ *
4
+ * Closes APTS-HO-003 (Decision Timeout and Default-Safe Behavior).
5
+ *
6
+ * Design notes:
7
+ * - HO-003 mandates that an indefinitely-running phase eventually
8
+ * hits a decision boundary; the default behavior on timeout is
9
+ * halt (the safe choice for an offensive engagement) rather than
10
+ * continue.
11
+ * - `withPhaseTimeout` wraps a phase promise. If the phase resolves
12
+ * before the deadline it returns its value; otherwise it returns
13
+ * a structured TimeoutResult so the caller can record the halt
14
+ * in the audit channel.
15
+ * - The wrapped promise is signaled via AbortController; whether it
16
+ * actually terminates is the wrapped function's responsibility.
17
+ * We always race so the orchestrator is never stuck.
18
+ */
19
+ export interface TimeoutOk<T> {
20
+ timed_out: false;
21
+ value: T;
22
+ }
23
+ export interface TimeoutFailure {
24
+ timed_out: true;
25
+ /** Phase name supplied by caller. */
26
+ phase: string;
27
+ /** Configured timeout in ms. */
28
+ timeout_ms: number;
29
+ /** Default-safe action that fired. Always 'halt' for offensive engagements. */
30
+ default_action: 'halt';
31
+ reason: string;
32
+ apts_refs: string[];
33
+ }
34
+ export type TimeoutResult<T> = TimeoutOk<T> | TimeoutFailure;
35
+ export interface PhaseTimeoutOptions {
36
+ phase: string;
37
+ timeout_ms: number;
38
+ /** AbortController to signal the wrapped phase. Caller wires it through. */
39
+ controller?: AbortController;
40
+ }
41
+ /**
42
+ * Wrap a phase promise with a default-safe timeout. On timeout, returns
43
+ * { timed_out: true, ... }; the caller emits a halt event and stops.
44
+ */
45
+ export declare function withPhaseTimeout<T>(phasePromise: Promise<T>, opts: PhaseTimeoutOptions): Promise<TimeoutResult<T>>;
46
+ /**
47
+ * Compute the per-phase timeout from RoE.stop_conditions when the
48
+ * operator supplies an explicit phase_timeout_minutes. Otherwise
49
+ * derive 1/4 of max_duration_minutes (4 phases) when present, else
50
+ * fall back to the supplied default.
51
+ */
52
+ export declare function derivePhaseTimeoutMs(stopConditions: {
53
+ max_duration_minutes?: number;
54
+ phase_timeout_minutes?: number;
55
+ }, defaultMs: number): number;
56
+ //# sourceMappingURL=decision-timeout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-timeout.d.ts","sourceRoot":"","sources":["../../src/safety-controls/decision-timeout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,SAAS,EAAE,KAAK,CAAC;IACjB,KAAK,EAAE,CAAC,CAAC;CACV;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,IAAI,CAAC;IAChB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC;AAE7D,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,eAAe,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,EACxB,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CA6B3B;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,cAAc,EAAE;IAAE,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAAC,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAAE,EACjF,SAAS,EAAE,MAAM,GAChB,MAAM,CAQR"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Per-phase decision timeout with default-safe halt.
3
+ *
4
+ * Closes APTS-HO-003 (Decision Timeout and Default-Safe Behavior).
5
+ *
6
+ * Design notes:
7
+ * - HO-003 mandates that an indefinitely-running phase eventually
8
+ * hits a decision boundary; the default behavior on timeout is
9
+ * halt (the safe choice for an offensive engagement) rather than
10
+ * continue.
11
+ * - `withPhaseTimeout` wraps a phase promise. If the phase resolves
12
+ * before the deadline it returns its value; otherwise it returns
13
+ * a structured TimeoutResult so the caller can record the halt
14
+ * in the audit channel.
15
+ * - The wrapped promise is signaled via AbortController; whether it
16
+ * actually terminates is the wrapped function's responsibility.
17
+ * We always race so the orchestrator is never stuck.
18
+ */
19
+ /**
20
+ * Wrap a phase promise with a default-safe timeout. On timeout, returns
21
+ * { timed_out: true, ... }; the caller emits a halt event and stops.
22
+ */
23
+ export async function withPhaseTimeout(phasePromise, opts) {
24
+ let timer = null;
25
+ const timeoutPromise = new Promise((resolve) => {
26
+ timer = setTimeout(() => {
27
+ if (opts.controller)
28
+ opts.controller.abort();
29
+ resolve({
30
+ timed_out: true,
31
+ phase: opts.phase,
32
+ timeout_ms: opts.timeout_ms,
33
+ default_action: 'halt',
34
+ reason: `phase "${opts.phase}" exceeded ${opts.timeout_ms} ms decision timeout — default-safe halt`,
35
+ apts_refs: ['APTS-HO-003'],
36
+ });
37
+ }, opts.timeout_ms);
38
+ if (timer && typeof timer.unref === 'function')
39
+ timer.unref();
40
+ });
41
+ const value = await Promise.race([
42
+ phasePromise.then((v) => ({ timed_out: false, value: v }), (err) => {
43
+ // Propagate errors so the caller's existing catch handles them.
44
+ throw err;
45
+ }),
46
+ timeoutPromise,
47
+ ]);
48
+ if (timer)
49
+ clearTimeout(timer);
50
+ return value;
51
+ }
52
+ /**
53
+ * Compute the per-phase timeout from RoE.stop_conditions when the
54
+ * operator supplies an explicit phase_timeout_minutes. Otherwise
55
+ * derive 1/4 of max_duration_minutes (4 phases) when present, else
56
+ * fall back to the supplied default.
57
+ */
58
+ export function derivePhaseTimeoutMs(stopConditions, defaultMs) {
59
+ if (stopConditions.phase_timeout_minutes !== undefined) {
60
+ return stopConditions.phase_timeout_minutes * 60_000;
61
+ }
62
+ if (stopConditions.max_duration_minutes !== undefined) {
63
+ return Math.round((stopConditions.max_duration_minutes * 60_000) / 4);
64
+ }
65
+ return defaultMs;
66
+ }
67
+ //# sourceMappingURL=decision-timeout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision-timeout.js","sourceRoot":"","sources":["../../src/safety-controls/decision-timeout.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA4BH;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,YAAwB,EACxB,IAAyB;IAEzB,IAAI,KAAK,GAA0B,IAAI,CAAC;IACxC,MAAM,cAAc,GAAG,IAAI,OAAO,CAAiB,CAAC,OAAO,EAAE,EAAE;QAC7D,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YACtB,IAAI,IAAI,CAAC,UAAU;gBAAE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YAC7C,OAAO,CAAC;gBACN,SAAS,EAAE,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,cAAc,EAAE,MAAM;gBACtB,MAAM,EAAE,UAAU,IAAI,CAAC,KAAK,cAAc,IAAI,CAAC,UAAU,0CAA0C;gBACnG,SAAS,EAAE,CAAC,aAAa,CAAC;aAC3B,CAAC,CAAC;QACL,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QACpB,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,UAAU;YAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;QAC/B,YAAY,CAAC,IAAI,CACf,CAAC,CAAC,EAAgB,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EACrD,CAAC,GAAG,EAAS,EAAE;YACb,gEAAgE;YAChE,MAAM,GAAG,CAAC;QACZ,CAAC,CACF;QACD,cAAc;KACf,CAAC,CAAC;IACH,IAAI,KAAK;QAAE,YAAY,CAAC,KAAK,CAAC,CAAC;IAC/B,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,cAAiF,EACjF,SAAiB;IAEjB,IAAI,cAAc,CAAC,qBAAqB,KAAK,SAAS,EAAE,CAAC;QACvD,OAAO,cAAc,CAAC,qBAAqB,GAAG,MAAM,CAAC;IACvD,CAAC;IACD,IAAI,cAAc,CAAC,oBAAoB,KAAK,SAAS,EAAE,CAAC;QACtD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,oBAAoB,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}