@tuent/sentinel 0.1.3 → 0.1.5
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/dist/{Sentinel-BVoMEF3F.d.ts → Sentinel-CJJ4iYDh.d.ts} +122 -11
- package/dist/Sentinel-XP6NFG6Z.js +10 -0
- package/dist/{chunk-PDWWRZXF.js → chunk-2IPSTUNH.js} +18 -7
- package/dist/{chunk-G74MMDKA.js → chunk-3WT3K5TH.js} +151 -24
- package/dist/{chunk-SSDIBY52.js → chunk-7R6EA7JG.js} +223 -90
- package/dist/chunk-B6S2PBS4.js +47 -0
- package/dist/{chunk-JTR2E7RD.js → chunk-M5EEVMLU.js} +222 -186
- package/dist/{chunk-WLIDSTS4.js → chunk-SKE74CYZ.js} +231 -28
- package/dist/{chunk-2TJ5Z53T.js → chunk-UVNRPML4.js} +59 -20
- package/dist/cli.js +33 -35
- package/dist/gateway/index.d.ts +26 -1
- package/dist/gateway/index.js +4 -4
- package/dist/gatewayDaemon.js +5 -5
- package/dist/index.d.ts +12 -12
- package/dist/index.js +5 -5
- package/dist/logAdapter-WM43W3S7.js +7 -0
- package/dist/{mcpAdapter-R47GX2P3.js → mcpAdapter-WYAXUE7T.js} +2 -2
- package/dist/{policyLoader-KZL2U4M2.js → policyLoader-NUPBBRKH.js} +8 -4
- package/package.json +1 -1
- package/dist/Sentinel-5CQ6HKXS.js +0 -10
- package/dist/chunk-FMZWHT4M.js +0 -20
- package/dist/logAdapter-IB6ZDEV2.js +0 -7
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_MAP_PATH,
|
|
3
|
-
DEFAULT_MEDIUM_DISPOSITION,
|
|
4
3
|
DEFAULT_OVERLAY_PATH,
|
|
5
4
|
RoleValidator,
|
|
6
5
|
TargetSensitivityScorer,
|
|
@@ -16,14 +15,17 @@ import {
|
|
|
16
15
|
saveMap,
|
|
17
16
|
saveOverlay,
|
|
18
17
|
unionWithDefaultForbiddenPatterns,
|
|
19
|
-
walkForbiddenInodeRoots
|
|
20
|
-
|
|
21
|
-
} from "./chunk-JTR2E7RD.js";
|
|
18
|
+
walkForbiddenInodeRoots
|
|
19
|
+
} from "./chunk-M5EEVMLU.js";
|
|
22
20
|
import {
|
|
21
|
+
DEFAULT_MEDIUM_DISPOSITION,
|
|
22
|
+
DEFAULT_QUARANTINE_AFTER,
|
|
23
|
+
DEFAULT_RESTRICT_AFTER,
|
|
23
24
|
loadPolicy,
|
|
24
25
|
policyToConfig,
|
|
25
|
-
policyToRole
|
|
26
|
-
|
|
26
|
+
policyToRole,
|
|
27
|
+
withPolicyReadExceptions
|
|
28
|
+
} from "./chunk-SKE74CYZ.js";
|
|
27
29
|
import {
|
|
28
30
|
publicKeyToBase64,
|
|
29
31
|
reconstructSigningPayload,
|
|
@@ -86,6 +88,10 @@ function decayWeight(daysSinceActive, currentWeight = 1, halfLifeDays = DECAY_HA
|
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
// ../core/src/dataEngine.ts
|
|
91
|
+
function parseLastActiveMs(lastActive) {
|
|
92
|
+
const t = new Date(lastActive).getTime();
|
|
93
|
+
return Number.isNaN(t) ? 0 : t;
|
|
94
|
+
}
|
|
89
95
|
var DataEngine = class _DataEngine {
|
|
90
96
|
points = /* @__PURE__ */ new Map();
|
|
91
97
|
colors;
|
|
@@ -113,7 +119,7 @@ var DataEngine = class _DataEngine {
|
|
|
113
119
|
if (p.core) {
|
|
114
120
|
weights.set(p.label, Math.max(base, CORE_WEIGHT_FLOOR));
|
|
115
121
|
} else {
|
|
116
|
-
const lastActiveMs =
|
|
122
|
+
const lastActiveMs = parseLastActiveMs(p.lastActive);
|
|
117
123
|
const daysSince = (effectiveNow - lastActiveMs) / (1e3 * 60 * 60 * 24);
|
|
118
124
|
weights.set(p.label, decayWeight(daysSince, base));
|
|
119
125
|
}
|
|
@@ -135,7 +141,7 @@ var DataEngine = class _DataEngine {
|
|
|
135
141
|
}
|
|
136
142
|
build() {
|
|
137
143
|
const sorted = [...this.points.values()].sort(
|
|
138
|
-
(a, b) =>
|
|
144
|
+
(a, b) => parseLastActiveMs(a.lastActive) - parseLastActiveMs(b.lastActive)
|
|
139
145
|
);
|
|
140
146
|
const normalizedWeights = _DataEngine.normalizeWeights(sorted);
|
|
141
147
|
const now = Date.now();
|
|
@@ -146,14 +152,14 @@ var DataEngine = class _DataEngine {
|
|
|
146
152
|
let minTime = now;
|
|
147
153
|
let maxTime = 0;
|
|
148
154
|
for (const p of sorted) {
|
|
149
|
-
const t =
|
|
155
|
+
const t = parseLastActiveMs(p.lastActive);
|
|
150
156
|
if (t < minTime) minTime = t;
|
|
151
157
|
if (t > maxTime) maxTime = t;
|
|
152
158
|
}
|
|
153
159
|
const timeRange = maxTime - minTime || 1;
|
|
154
160
|
const petals = sorted.map((point, i) => {
|
|
155
161
|
const layer = sorted.length > 1 ? i / (sorted.length - 1) : 0.5;
|
|
156
|
-
const t =
|
|
162
|
+
const t = parseLastActiveMs(point.lastActive);
|
|
157
163
|
const recency = (t - minTime) / timeRange;
|
|
158
164
|
const openness = 0.15 + layer * 0.8;
|
|
159
165
|
const connections = (point.connections ?? []).map((label) => labelToIdMap.get(label)).filter((id) => id !== void 0);
|
|
@@ -172,7 +178,8 @@ var DataEngine = class _DataEngine {
|
|
|
172
178
|
source: point.source,
|
|
173
179
|
core: point.core,
|
|
174
180
|
sharable: point.sharable,
|
|
175
|
-
files: point.files
|
|
181
|
+
files: point.files,
|
|
182
|
+
eventCount: point.eventCount
|
|
176
183
|
};
|
|
177
184
|
});
|
|
178
185
|
const realCount = petals.length;
|
|
@@ -288,15 +295,18 @@ function mergeAgentPoints(existing, incoming) {
|
|
|
288
295
|
const mergedWeight = Math.min((existing.weight ?? 0) + (incoming.weight ?? 0), 1);
|
|
289
296
|
const mergedConnections = incoming.connections || existing.connections ? [.../* @__PURE__ */ new Set([...incoming.connections ?? [], ...existing.connections ?? []])] : void 0;
|
|
290
297
|
const mergedFiles = incoming.files || existing.files ? [.../* @__PURE__ */ new Set([...incoming.files ?? [], ...existing.files ?? []])].slice(0, 20) : void 0;
|
|
298
|
+
const mergedLastActive = new Date(incoming.lastActive) >= new Date(existing.lastActive) ? incoming.lastActive : existing.lastActive;
|
|
299
|
+
const mergedEventCount = incoming.eventCount ?? existing.eventCount;
|
|
291
300
|
return preserveUserMetadata(existing, {
|
|
292
301
|
label: incoming.label,
|
|
293
302
|
category: incoming.category,
|
|
294
|
-
lastActive:
|
|
303
|
+
lastActive: mergedLastActive,
|
|
295
304
|
description: incoming.description,
|
|
296
305
|
weight: mergedWeight,
|
|
297
306
|
source: "agent",
|
|
298
307
|
...mergedConnections && { connections: mergedConnections },
|
|
299
|
-
...mergedFiles && { files: mergedFiles }
|
|
308
|
+
...mergedFiles && { files: mergedFiles },
|
|
309
|
+
...mergedEventCount !== void 0 && { eventCount: mergedEventCount }
|
|
300
310
|
});
|
|
301
311
|
}
|
|
302
312
|
var ProfileStore = class {
|
|
@@ -305,6 +315,11 @@ var ProfileStore = class {
|
|
|
305
315
|
petalCount;
|
|
306
316
|
engine;
|
|
307
317
|
profile = null;
|
|
318
|
+
/** Snapshot (label → serialized point) of the on-disk-synced dataPoint set,
|
|
319
|
+
* refreshed on every load/create/write. The base for save()'s three-way merge
|
|
320
|
+
* of concurrent external writes — lets us tell "I deleted this" (in baseline,
|
|
321
|
+
* gone from memory) from "another writer added this" (on disk, not in baseline). */
|
|
322
|
+
baseline = /* @__PURE__ */ new Map();
|
|
308
323
|
constructor(options) {
|
|
309
324
|
this.backend = options.backend;
|
|
310
325
|
this.eventBus = options.eventBus;
|
|
@@ -336,8 +351,15 @@ var ProfileStore = class {
|
|
|
336
351
|
payload: { profileId: this.profile.id }
|
|
337
352
|
});
|
|
338
353
|
}
|
|
354
|
+
this.captureBaseline();
|
|
339
355
|
return this.profile;
|
|
340
356
|
}
|
|
357
|
+
/** Snapshot the on-disk-synced dataPoint set for save()'s three-way merge. */
|
|
358
|
+
captureBaseline() {
|
|
359
|
+
this.baseline = new Map(
|
|
360
|
+
(this.profile?.dataPoints ?? []).map((p) => [p.label, JSON.stringify(p)])
|
|
361
|
+
);
|
|
362
|
+
}
|
|
341
363
|
async create(name) {
|
|
342
364
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
343
365
|
this.profile = {
|
|
@@ -349,6 +371,7 @@ var ProfileStore = class {
|
|
|
349
371
|
updatedAt: now
|
|
350
372
|
};
|
|
351
373
|
await this.backend.write(this.profile);
|
|
374
|
+
this.captureBaseline();
|
|
352
375
|
return this.profile;
|
|
353
376
|
}
|
|
354
377
|
async addPoint(point) {
|
|
@@ -368,6 +391,7 @@ var ProfileStore = class {
|
|
|
368
391
|
}
|
|
369
392
|
this.profile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
370
393
|
await this.backend.write(this.profile);
|
|
394
|
+
this.captureBaseline();
|
|
371
395
|
this.eventBus?.emit({
|
|
372
396
|
type: "data:updated",
|
|
373
397
|
payload: { timestamp: Date.now() }
|
|
@@ -399,9 +423,46 @@ var ProfileStore = class {
|
|
|
399
423
|
async save() {
|
|
400
424
|
if (!this.profile) throw new Error("No profile loaded");
|
|
401
425
|
if (this.backend.hasExternalChanges && await this.backend.hasExternalChanges()) {
|
|
402
|
-
await this.
|
|
426
|
+
await this.mergeExternalChanges();
|
|
403
427
|
}
|
|
404
428
|
await this.backend.write(this.profile);
|
|
429
|
+
this.captureBaseline();
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Three-way merge of a concurrent external write into the in-memory profile,
|
|
433
|
+
* invoked by save() when the backend reports the file changed since we last
|
|
434
|
+
* synced. Writers in this app are single-user but multi-process (agent scanner,
|
|
435
|
+
* diary watcher, dev server, CLI), so concurrent writes are real but rare.
|
|
436
|
+
*
|
|
437
|
+
* Model (base = the dataPoint set at our last load/write, see `baseline`):
|
|
438
|
+
* - a point present in memory → kept as-is (the SAVING process's adds/edits
|
|
439
|
+
* win on a same-point conflict — save() is an explicit "persist my state");
|
|
440
|
+
* - a disk point whose label is NEW since baseline → another writer's
|
|
441
|
+
* addition → preserved;
|
|
442
|
+
* - a point we dropped (in baseline, absent from memory) → our deletion is
|
|
443
|
+
* honored, UNLESS the other writer modified it (then their version is kept
|
|
444
|
+
* so a concurrent edit is never silently lost).
|
|
445
|
+
* Net: nothing vanishes except a same-point simultaneous edit, resolved
|
|
446
|
+
* last-writer (this save) — a deliberate, documented tie-break.
|
|
447
|
+
*/
|
|
448
|
+
async mergeExternalChanges() {
|
|
449
|
+
if (!this.profile) return;
|
|
450
|
+
const disk = await this.backend.read();
|
|
451
|
+
if (!disk || !Array.isArray(disk.dataPoints)) return;
|
|
452
|
+
const merged = /* @__PURE__ */ new Map();
|
|
453
|
+
for (const p of this.profile.dataPoints) merged.set(p.label, p);
|
|
454
|
+
for (const dp of disk.dataPoints) {
|
|
455
|
+
if (merged.has(dp.label)) continue;
|
|
456
|
+
const baseJson = this.baseline.get(dp.label);
|
|
457
|
+
if (baseJson === void 0) {
|
|
458
|
+
merged.set(dp.label, dp);
|
|
459
|
+
} else if (baseJson !== JSON.stringify(dp)) {
|
|
460
|
+
merged.set(dp.label, dp);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
this.profile.dataPoints = [...merged.values()];
|
|
464
|
+
this.engine = new DataEngine({ petalCount: this.petalCount });
|
|
465
|
+
for (const p of this.profile.dataPoints) this.engine.addPoint(p);
|
|
405
466
|
}
|
|
406
467
|
build() {
|
|
407
468
|
return this.engine.build();
|
|
@@ -614,7 +675,8 @@ var AgentActivityClassifier = class {
|
|
|
614
675
|
weight,
|
|
615
676
|
source: "agent-monitor",
|
|
616
677
|
files: Array.from(session.targetSet).slice(0, 50),
|
|
617
|
-
connections: []
|
|
678
|
+
connections: [],
|
|
679
|
+
eventCount
|
|
618
680
|
};
|
|
619
681
|
}
|
|
620
682
|
};
|
|
@@ -668,27 +730,11 @@ var AuditTrail = class _AuditTrail {
|
|
|
668
730
|
this.maxFileSizeBytes = (options?.maxFileSizeMB ?? 10) * 1024 * 1024;
|
|
669
731
|
}
|
|
670
732
|
async open() {
|
|
671
|
-
const { mkdir, appendFile
|
|
733
|
+
const { mkdir, appendFile } = await import("fs/promises");
|
|
672
734
|
const { dirname: dirname2 } = await import("path");
|
|
673
735
|
await mkdir(dirname2(this.logPath), { recursive: true });
|
|
674
736
|
await appendFile(this.logPath, "");
|
|
675
|
-
this.currentChainHead = _AuditTrail.GENESIS_HASH;
|
|
676
|
-
try {
|
|
677
|
-
const raw = await readFile(this.logPath, "utf-8");
|
|
678
|
-
const lines = raw.trimEnd().split("\n");
|
|
679
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
680
|
-
if (lines[i].length === 0) continue;
|
|
681
|
-
try {
|
|
682
|
-
const last = JSON.parse(lines[i]);
|
|
683
|
-
if (typeof last.hash === "string" && last.hash.length === 64) {
|
|
684
|
-
this.currentChainHead = last.hash;
|
|
685
|
-
}
|
|
686
|
-
} catch {
|
|
687
|
-
}
|
|
688
|
-
break;
|
|
689
|
-
}
|
|
690
|
-
} catch {
|
|
691
|
-
}
|
|
737
|
+
this.currentChainHead = await this.lastHashOnDisk() ?? _AuditTrail.GENESIS_HASH;
|
|
692
738
|
try {
|
|
693
739
|
const { stat } = await import("fs/promises");
|
|
694
740
|
this._lastKnownSize = (await stat(this.logPath)).size;
|
|
@@ -886,7 +932,11 @@ var AuditTrail = class _AuditTrail {
|
|
|
886
932
|
for (const entry of entries) {
|
|
887
933
|
if (options.type && entry.type !== options.type) continue;
|
|
888
934
|
const entryMs = new Date(entry.timestamp).getTime();
|
|
889
|
-
if (entryMs
|
|
935
|
+
if (Number.isNaN(entryMs)) {
|
|
936
|
+
if (options.startTime || options.endTime) continue;
|
|
937
|
+
} else if (entryMs < startMs || entryMs > endMs) {
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
890
940
|
if (options.severity && entry.type === "finding") {
|
|
891
941
|
if (entry.severity !== options.severity) continue;
|
|
892
942
|
}
|
|
@@ -979,6 +1029,7 @@ var AuditTrail = class _AuditTrail {
|
|
|
979
1029
|
let signedEntries = 0;
|
|
980
1030
|
let unsignedEntries = 0;
|
|
981
1031
|
let invalidSignatures = 0;
|
|
1032
|
+
let seenHashedEntry = false;
|
|
982
1033
|
let priorFileLastHash = null;
|
|
983
1034
|
for (const file of files) {
|
|
984
1035
|
const base = basename(file);
|
|
@@ -1049,6 +1100,7 @@ var AuditTrail = class _AuditTrail {
|
|
|
1049
1100
|
unsignedEntries++;
|
|
1050
1101
|
}
|
|
1051
1102
|
globalIndex++;
|
|
1103
|
+
seenHashedEntry = true;
|
|
1052
1104
|
checkpointReached = true;
|
|
1053
1105
|
startIndex = aIdx + 1;
|
|
1054
1106
|
previousHash = checkpoint.anchorHash;
|
|
@@ -1085,10 +1137,26 @@ var AuditTrail = class _AuditTrail {
|
|
|
1085
1137
|
try {
|
|
1086
1138
|
entry = JSON.parse(line);
|
|
1087
1139
|
} catch {
|
|
1088
|
-
|
|
1089
|
-
|
|
1140
|
+
return attach({
|
|
1141
|
+
valid: false,
|
|
1142
|
+
totalEntries: globalIndex + 1,
|
|
1143
|
+
brokenAt: globalIndex,
|
|
1144
|
+
signedEntries,
|
|
1145
|
+
unsignedEntries,
|
|
1146
|
+
invalidSignatures
|
|
1147
|
+
});
|
|
1090
1148
|
}
|
|
1091
1149
|
if (typeof entry.hash !== "string" || entry.hash.length !== 64) {
|
|
1150
|
+
if (seenHashedEntry) {
|
|
1151
|
+
return attach({
|
|
1152
|
+
valid: false,
|
|
1153
|
+
totalEntries: globalIndex + 1,
|
|
1154
|
+
brokenAt: globalIndex,
|
|
1155
|
+
signedEntries,
|
|
1156
|
+
unsignedEntries,
|
|
1157
|
+
invalidSignatures
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1092
1160
|
globalIndex++;
|
|
1093
1161
|
continue;
|
|
1094
1162
|
}
|
|
@@ -1128,6 +1196,7 @@ var AuditTrail = class _AuditTrail {
|
|
|
1128
1196
|
}
|
|
1129
1197
|
previousHash = storedHash;
|
|
1130
1198
|
globalIndex++;
|
|
1199
|
+
seenHashedEntry = true;
|
|
1131
1200
|
}
|
|
1132
1201
|
priorFileLastHash = previousHash;
|
|
1133
1202
|
}
|
|
@@ -1765,6 +1834,23 @@ var AuditTrail = class _AuditTrail {
|
|
|
1765
1834
|
* the file when an external append actually happened.
|
|
1766
1835
|
*/
|
|
1767
1836
|
async reanchorFromDiskTail() {
|
|
1837
|
+
const lastHash = await this.lastHashOnDisk();
|
|
1838
|
+
if (lastHash) this.currentChainHead = lastHash;
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Scan the on-disk log backward for the last entry carrying a valid (64-hex)
|
|
1842
|
+
* hash, SKIPPING torn/corrupt trailing lines, and return that hash (or null
|
|
1843
|
+
* when no parseable hashed entry exists).
|
|
1844
|
+
*
|
|
1845
|
+
* Shared by open() (chain resume) and reanchorFromDiskTail() (live
|
|
1846
|
+
* re-anchor). The earlier inline versions inspected only the LAST non-empty
|
|
1847
|
+
* line and broke: a torn tail line (a partial write) left the head at genesis
|
|
1848
|
+
* (open) or stale (reanchor), so the next append forked a fresh chain from
|
|
1849
|
+
* genesis — destroying all prior linkage. Walking back leaves the torn line
|
|
1850
|
+
* as a single localized gap that verify() still flags, while the chain head
|
|
1851
|
+
* stays anchored to the last good entry.
|
|
1852
|
+
*/
|
|
1853
|
+
async lastHashOnDisk() {
|
|
1768
1854
|
const { readFile } = await import("fs/promises");
|
|
1769
1855
|
try {
|
|
1770
1856
|
const raw = await readFile(this.logPath, "utf-8");
|
|
@@ -1772,16 +1858,16 @@ var AuditTrail = class _AuditTrail {
|
|
|
1772
1858
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1773
1859
|
if (lines[i].length === 0) continue;
|
|
1774
1860
|
try {
|
|
1775
|
-
const
|
|
1776
|
-
if (typeof
|
|
1777
|
-
|
|
1861
|
+
const entry = JSON.parse(lines[i]);
|
|
1862
|
+
if (typeof entry.hash === "string" && entry.hash.length === 64) {
|
|
1863
|
+
return entry.hash;
|
|
1778
1864
|
}
|
|
1779
1865
|
} catch {
|
|
1780
1866
|
}
|
|
1781
|
-
break;
|
|
1782
1867
|
}
|
|
1783
1868
|
} catch {
|
|
1784
1869
|
}
|
|
1870
|
+
return null;
|
|
1785
1871
|
}
|
|
1786
1872
|
async readAllEntries() {
|
|
1787
1873
|
const { readFile } = await import("fs/promises");
|
|
@@ -2025,7 +2111,7 @@ var DeviationDetector = class {
|
|
|
2025
2111
|
checkVolumeSpike(dataPoint) {
|
|
2026
2112
|
if (this.baseline.averageEventsPerSession === 0) return null;
|
|
2027
2113
|
const description = dataPoint.description ?? "";
|
|
2028
|
-
const eventCount = parseEventCount(description) ?? estimateEventsFromWeight(dataPoint.weight ?? 0);
|
|
2114
|
+
const eventCount = dataPoint.eventCount ?? parseEventCount(description) ?? estimateEventsFromWeight(dataPoint.weight ?? 0);
|
|
2029
2115
|
const threshold = this.baseline.averageEventsPerSession * this.config.volumeSpikeMultiplier;
|
|
2030
2116
|
if (eventCount <= threshold) return null;
|
|
2031
2117
|
const multiplier = (eventCount / this.baseline.averageEventsPerSession).toFixed(1);
|
|
@@ -2174,7 +2260,7 @@ var DeviationDetector = class {
|
|
|
2174
2260
|
checkActivityDrop(dataPoint) {
|
|
2175
2261
|
if (this.baseline.averageEventsPerSession === 0) return null;
|
|
2176
2262
|
const description = dataPoint.description ?? "";
|
|
2177
|
-
const eventCount = parseEventCount(description) ?? estimateEventsFromWeight(dataPoint.weight ?? 0);
|
|
2263
|
+
const eventCount = dataPoint.eventCount ?? parseEventCount(description) ?? estimateEventsFromWeight(dataPoint.weight ?? 0);
|
|
2178
2264
|
if (eventCount === 0) return null;
|
|
2179
2265
|
const ratio = eventCount / this.baseline.averageEventsPerSession;
|
|
2180
2266
|
if (ratio < 0.1) {
|
|
@@ -2368,12 +2454,8 @@ var COMMON_ABBREVIATIONS = /* @__PURE__ */ new Map([
|
|
|
2368
2454
|
["development", "dev"]
|
|
2369
2455
|
]);
|
|
2370
2456
|
var DEFAULT_INTENT_CONFIG = {
|
|
2371
|
-
defaultTtlMs: 8 * 60 * 60 * 1e3
|
|
2457
|
+
defaultTtlMs: 8 * 60 * 60 * 1e3
|
|
2372
2458
|
// 8 hours
|
|
2373
|
-
keywordBoost: 0.15,
|
|
2374
|
-
acceptableActionBoost: 0.3,
|
|
2375
|
-
baseScore: 0.2,
|
|
2376
|
-
missingTaskScore: 0.5
|
|
2377
2459
|
};
|
|
2378
2460
|
var DEFAULT_ACCEPTABLE_ACTIONS = [
|
|
2379
2461
|
// Config/manifest reads
|
|
@@ -2423,15 +2505,14 @@ var DEFAULT_ACCEPTABLE_ACTIONS = [
|
|
|
2423
2505
|
{ action: "command_exec", targetPattern: "tsc*", reason: "TypeScript compiler" }
|
|
2424
2506
|
];
|
|
2425
2507
|
function extractKeywords(description) {
|
|
2426
|
-
const
|
|
2427
|
-
const
|
|
2428
|
-
for (const
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
return [...withVariants];
|
|
2508
|
+
const rawTokens = description.split(/[^a-zA-Z0-9]+/).map((w) => w.toLowerCase()).filter((w) => w.length > 0);
|
|
2509
|
+
const keywords = /* @__PURE__ */ new Set();
|
|
2510
|
+
for (const token of rawTokens) {
|
|
2511
|
+
if (token.length > 2 && !STOP_WORDS.has(token)) keywords.add(token);
|
|
2512
|
+
const variant = COMMON_ABBREVIATIONS.get(token);
|
|
2513
|
+
if (variant) keywords.add(variant);
|
|
2514
|
+
}
|
|
2515
|
+
return [...keywords];
|
|
2435
2516
|
}
|
|
2436
2517
|
var IntentTracker = class {
|
|
2437
2518
|
tasks = /* @__PURE__ */ new Map();
|
|
@@ -2720,6 +2801,17 @@ var SimilarityEngine = class {
|
|
|
2720
2801
|
// src/evaluation.ts
|
|
2721
2802
|
var DRIFT_WINDOW_SIZE = 5;
|
|
2722
2803
|
var SUSTAINED_DRIFT_THRESHOLD = 0.25;
|
|
2804
|
+
function computeDriftSeverity(score, windowScores) {
|
|
2805
|
+
const base = score < 0.1 ? "CRITICAL" : score < 0.2 ? "HIGH" : "MEDIUM";
|
|
2806
|
+
if (windowScores.length < DRIFT_WINDOW_SIZE) {
|
|
2807
|
+
return { severity: base, sustained: false, avg: null };
|
|
2808
|
+
}
|
|
2809
|
+
const avg = windowScores.reduce((sum, s) => sum + s, 0) / windowScores.length;
|
|
2810
|
+
if (avg >= SUSTAINED_DRIFT_THRESHOLD) {
|
|
2811
|
+
return { severity: base, sustained: false, avg };
|
|
2812
|
+
}
|
|
2813
|
+
return { severity: base === "CRITICAL" ? "CRITICAL" : "HIGH", sustained: true, avg };
|
|
2814
|
+
}
|
|
2723
2815
|
function evaluateEvent(event, state, deps) {
|
|
2724
2816
|
const findings = [];
|
|
2725
2817
|
let intentAlignment;
|
|
@@ -2740,9 +2832,9 @@ function evaluateEvent(event, state, deps) {
|
|
|
2740
2832
|
if (newScores.length > DRIFT_WINDOW_SIZE) {
|
|
2741
2833
|
newScores = newScores.slice(newScores.length - DRIFT_WINDOW_SIZE);
|
|
2742
2834
|
}
|
|
2743
|
-
const
|
|
2835
|
+
const driftVerdict = computeDriftSeverity(alignment.score, newScores);
|
|
2744
2836
|
const driftFinding = {
|
|
2745
|
-
severity:
|
|
2837
|
+
severity: driftVerdict.severity,
|
|
2746
2838
|
kind: "informational",
|
|
2747
2839
|
type: "intent_drift",
|
|
2748
2840
|
agentId: event.agentId,
|
|
@@ -2757,17 +2849,9 @@ function evaluateEvent(event, state, deps) {
|
|
|
2757
2849
|
recommendation: `Review whether ${describeEvent(event)} is necessary for the current task.`,
|
|
2758
2850
|
timestamp: event.timestamp
|
|
2759
2851
|
};
|
|
2760
|
-
if (
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
const sustainedSeverity = "HIGH";
|
|
2764
|
-
const severityRank = { LOW: 0, MEDIUM: 1, HIGH: 2, CRITICAL: 3 };
|
|
2765
|
-
if (severityRank[sustainedSeverity] > severityRank[driftFinding.severity]) {
|
|
2766
|
-
driftFinding.severity = sustainedSeverity;
|
|
2767
|
-
}
|
|
2768
|
-
driftFinding.description = `Sustained intent drift detected (${DRIFT_WINDOW_SIZE} consecutive misaligned actions, avg score ${avg.toFixed(2)}): ${alignment.reason}`;
|
|
2769
|
-
driftFinding.softSignal = true;
|
|
2770
|
-
}
|
|
2852
|
+
if (driftVerdict.sustained) {
|
|
2853
|
+
driftFinding.description = `Sustained intent drift detected (${DRIFT_WINDOW_SIZE} consecutive misaligned actions, avg score ${driftVerdict.avg.toFixed(2)}): ${alignment.reason}`;
|
|
2854
|
+
driftFinding.softSignal = true;
|
|
2771
2855
|
}
|
|
2772
2856
|
findings.push(driftFinding);
|
|
2773
2857
|
} else {
|
|
@@ -2810,6 +2894,9 @@ var SentinelRunner = class {
|
|
|
2810
2894
|
_maturityConfig;
|
|
2811
2895
|
// Role validator options (injected by Sentinel facade for exception handling)
|
|
2812
2896
|
_validatorOptions;
|
|
2897
|
+
// Facade's shared overlay/repo-map-aware scorer (injected before start()).
|
|
2898
|
+
// Falls back to a bare scorer when the runner is used standalone (tests).
|
|
2899
|
+
_sensitivityScorer;
|
|
2813
2900
|
// Intent alignment
|
|
2814
2901
|
intentTracker;
|
|
2815
2902
|
similarityEngine;
|
|
@@ -2855,6 +2942,14 @@ var SentinelRunner = class {
|
|
|
2855
2942
|
setRoleValidatorOptions(options) {
|
|
2856
2943
|
this._validatorOptions = options;
|
|
2857
2944
|
}
|
|
2945
|
+
/**
|
|
2946
|
+
* Inject the facade's shared sensitivity scorer (overlay + repo map aware)
|
|
2947
|
+
* so processEvent's validator scores identically to the facade's check().
|
|
2948
|
+
* Must be called BEFORE start() builds the validator.
|
|
2949
|
+
*/
|
|
2950
|
+
setSensitivityScorer(scorer) {
|
|
2951
|
+
this._sensitivityScorer = scorer;
|
|
2952
|
+
}
|
|
2858
2953
|
/** Set the behavioral baseline and start periodic gap checking. */
|
|
2859
2954
|
setBaseline(baseline) {
|
|
2860
2955
|
this._baseline = baseline;
|
|
@@ -2911,6 +3006,14 @@ var SentinelRunner = class {
|
|
|
2911
3006
|
getSimilarityEngine() {
|
|
2912
3007
|
return this.similarityEngine;
|
|
2913
3008
|
}
|
|
3009
|
+
/**
|
|
3010
|
+
* Read-only view of the rolling misaligned-score window, for the
|
|
3011
|
+
* pre-execution gate (Sentinel.check) to apply the SAME sustained-drift
|
|
3012
|
+
* severity upgrade as the recording path — without mutating the window.
|
|
3013
|
+
*/
|
|
3014
|
+
getRecentAlignmentScores() {
|
|
3015
|
+
return this.recentAlignmentScores;
|
|
3016
|
+
}
|
|
2914
3017
|
/**
|
|
2915
3018
|
* Side-effect-free pre-execution gate: evaluate an event and fire pre_execution
|
|
2916
3019
|
* hooks WITHOUT running processEvent's full pipeline.
|
|
@@ -3022,7 +3125,7 @@ var SentinelRunner = class {
|
|
|
3022
3125
|
if (role) {
|
|
3023
3126
|
this.validator = new RoleValidator(
|
|
3024
3127
|
role,
|
|
3025
|
-
new TargetSensitivityScorer(),
|
|
3128
|
+
this._sensitivityScorer ?? new TargetSensitivityScorer(),
|
|
3026
3129
|
this._validatorOptions
|
|
3027
3130
|
);
|
|
3028
3131
|
}
|
|
@@ -3290,7 +3393,7 @@ var SentinelRunner = class {
|
|
|
3290
3393
|
const { type } = this.adapterConfig;
|
|
3291
3394
|
if (type === "manual") return;
|
|
3292
3395
|
if (type === "log") {
|
|
3293
|
-
const { LogAdapter } = await import("./logAdapter-
|
|
3396
|
+
const { LogAdapter } = await import("./logAdapter-WM43W3S7.js");
|
|
3294
3397
|
const logPath = this.adapterConfig.logPath;
|
|
3295
3398
|
if (!logPath) {
|
|
3296
3399
|
throw new Error(`SentinelRunner: logPath is required for adapter type "log"`);
|
|
@@ -3308,7 +3411,7 @@ var SentinelRunner = class {
|
|
|
3308
3411
|
}).catch((err) => console.warn(`SentinelRunner processEvent error: ${err}`));
|
|
3309
3412
|
});
|
|
3310
3413
|
} else if (type === "mcp") {
|
|
3311
|
-
const { McpAdapter } = await import("./mcpAdapter-
|
|
3414
|
+
const { McpAdapter } = await import("./mcpAdapter-WYAXUE7T.js");
|
|
3312
3415
|
this.adapter = new McpAdapter(this.agentId, this.agentId, {
|
|
3313
3416
|
logDir: this.adapterConfig.mcpLogDir,
|
|
3314
3417
|
pollIntervalMs: this.adapterConfig.pollIntervalMs
|
|
@@ -3438,6 +3541,9 @@ var SentinelManager = class {
|
|
|
3438
3541
|
if (options?.validatorOptions) {
|
|
3439
3542
|
runner.setRoleValidatorOptions(options.validatorOptions);
|
|
3440
3543
|
}
|
|
3544
|
+
if (options?.sensitivityScorer) {
|
|
3545
|
+
runner.setSensitivityScorer(options.sensitivityScorer);
|
|
3546
|
+
}
|
|
3441
3547
|
await runner.start();
|
|
3442
3548
|
return runner;
|
|
3443
3549
|
}
|
|
@@ -3738,9 +3844,11 @@ var AlertManager = class {
|
|
|
3738
3844
|
const topLevelMinKind = this.config.minKind ?? "actionable";
|
|
3739
3845
|
const findingKindRank = KIND_ORDER[finding.kind] ?? KIND_ORDER.actionable;
|
|
3740
3846
|
const isConvergedSoftSignal = finding.softSignal === true;
|
|
3847
|
+
let routed = false;
|
|
3741
3848
|
for (const channel of this.config.channels) {
|
|
3742
3849
|
const channelMinKind = channel.minKind ?? topLevelMinKind;
|
|
3743
3850
|
if (!isConvergedSoftSignal && findingKindRank < KIND_ORDER[channelMinKind]) continue;
|
|
3851
|
+
routed = true;
|
|
3744
3852
|
try {
|
|
3745
3853
|
if (channel.type === "console") {
|
|
3746
3854
|
await this.sendConsole(finding);
|
|
@@ -3753,7 +3861,9 @@ var AlertManager = class {
|
|
|
3753
3861
|
console.warn(`Alert channel "${channel.name}" failed:`, err);
|
|
3754
3862
|
}
|
|
3755
3863
|
}
|
|
3756
|
-
|
|
3864
|
+
if (routed) {
|
|
3865
|
+
this.recentAlerts.set(dedupeKey, Date.now());
|
|
3866
|
+
}
|
|
3757
3867
|
}
|
|
3758
3868
|
clearDedupeCache() {
|
|
3759
3869
|
this.recentAlerts.clear();
|
|
@@ -5657,7 +5767,7 @@ var HookEngine = class {
|
|
|
5657
5767
|
candidate = { kind: "allow" };
|
|
5658
5768
|
selectedRegistration = null;
|
|
5659
5769
|
}
|
|
5660
|
-
if (evaluationResult && candidate.kind === "guide" && evaluationResult.findings.some(
|
|
5770
|
+
if (checkpoint === "pre_execution" && evaluationResult && candidate.kind === "guide" && evaluationResult.findings.some(
|
|
5661
5771
|
(f) => (f.severity === "HIGH" || f.severity === "CRITICAL") && f.kind === "actionable"
|
|
5662
5772
|
)) {
|
|
5663
5773
|
const severityRank = { LOW: 0, MEDIUM: 1, HIGH: 2, CRITICAL: 3 };
|
|
@@ -5680,7 +5790,7 @@ var HookEngine = class {
|
|
|
5680
5790
|
finding: blockingFinding
|
|
5681
5791
|
};
|
|
5682
5792
|
}
|
|
5683
|
-
if (candidate.kind === "allow" && evaluationResult && evaluationResult.findings.some(
|
|
5793
|
+
if (checkpoint === "pre_execution" && candidate.kind === "allow" && evaluationResult && evaluationResult.findings.some(
|
|
5684
5794
|
(f) => (f.severity === "HIGH" || f.severity === "CRITICAL") && f.kind === "actionable"
|
|
5685
5795
|
)) {
|
|
5686
5796
|
const severityRank = { LOW: 0, MEDIUM: 1, HIGH: 2, CRITICAL: 3 };
|
|
@@ -5926,8 +6036,8 @@ var Sentinel = class _Sentinel {
|
|
|
5926
6036
|
this.manager = new SentinelManager(this.profileManager);
|
|
5927
6037
|
this.sensitivityScorer = new TargetSensitivityScorer();
|
|
5928
6038
|
this.environment = config?.environment ?? "development";
|
|
5929
|
-
this.restrictThreshold = config?.enforcement?.restrictAfter ??
|
|
5930
|
-
this.quarantineThreshold = config?.enforcement?.quarantineAfter ??
|
|
6039
|
+
this.restrictThreshold = config?.enforcement?.restrictAfter ?? DEFAULT_RESTRICT_AFTER;
|
|
6040
|
+
this.quarantineThreshold = config?.enforcement?.quarantineAfter ?? DEFAULT_QUARANTINE_AFTER;
|
|
5931
6041
|
this.enablePreExecutionHooks = config?.enablePreExecutionHooks ?? false;
|
|
5932
6042
|
this.promoteSet = new Set(config?.enforcement?.promote ?? []);
|
|
5933
6043
|
this.maturityConfig = config?.enforcement?.baselineMaturity;
|
|
@@ -6167,10 +6277,18 @@ var Sentinel = class _Sentinel {
|
|
|
6167
6277
|
);
|
|
6168
6278
|
}
|
|
6169
6279
|
async logModeChange(agentId, mode, reason, previousMode) {
|
|
6170
|
-
const
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6280
|
+
const liveTrail = this.manager.getRunner(agentId)?.getAuditTrail();
|
|
6281
|
+
if (liveTrail) {
|
|
6282
|
+
await liveTrail.logModeChange(mode, reason, previousMode);
|
|
6283
|
+
return;
|
|
6284
|
+
}
|
|
6285
|
+
const trail = new AuditTrail(agentId, { logDir: `${this.agentsDir}/${agentId}` });
|
|
6286
|
+
await trail.open();
|
|
6287
|
+
try {
|
|
6288
|
+
await trail.logModeChange(mode, reason, previousMode);
|
|
6289
|
+
} finally {
|
|
6290
|
+
await trail.close();
|
|
6291
|
+
}
|
|
6174
6292
|
}
|
|
6175
6293
|
async stopAgent(agentId, reason) {
|
|
6176
6294
|
const { mkdir, writeFile } = await import("fs/promises");
|
|
@@ -6277,7 +6395,10 @@ var Sentinel = class _Sentinel {
|
|
|
6277
6395
|
const engine = runner.getSimilarityEngine();
|
|
6278
6396
|
const alignment = engine.computeAlignment(activeTask, event);
|
|
6279
6397
|
if (!alignment.aligned && !alignment.bypassed) {
|
|
6280
|
-
const
|
|
6398
|
+
const projectedWindow = [...runner.getRecentAlignmentScores(), alignment.score].slice(
|
|
6399
|
+
-DRIFT_WINDOW_SIZE
|
|
6400
|
+
);
|
|
6401
|
+
const { severity } = computeDriftSeverity(alignment.score, projectedWindow);
|
|
6281
6402
|
return {
|
|
6282
6403
|
severity,
|
|
6283
6404
|
kind: "informational",
|
|
@@ -6402,7 +6523,10 @@ var Sentinel = class _Sentinel {
|
|
|
6402
6523
|
// Approach-1 anchoring: give the runner's processEvent validator the SAME
|
|
6403
6524
|
// workspaceRoot check() uses (this._workspaceRoot), supplied BEFORE start()
|
|
6404
6525
|
// builds it — otherwise it logs phantom scope_violations on legit reads.
|
|
6405
|
-
validatorOptions: { workspaceRoot: this._workspaceRoot }
|
|
6526
|
+
validatorOptions: { workspaceRoot: this._workspaceRoot },
|
|
6527
|
+
// Share the facade's overlay/repo-map-aware scorer so record() scores
|
|
6528
|
+
// sensitivity identically to check() (same live instance).
|
|
6529
|
+
sensitivityScorer: this.sensitivityScorer
|
|
6406
6530
|
});
|
|
6407
6531
|
this.wireRunner(runner);
|
|
6408
6532
|
await this.enableAuditSigning(runner, agentId);
|
|
@@ -6493,7 +6617,8 @@ var Sentinel = class _Sentinel {
|
|
|
6493
6617
|
// Approach-1 anchoring: same workspaceRoot as check() (this._workspaceRoot),
|
|
6494
6618
|
// supplied BEFORE start() builds the runner's validator. This is the live
|
|
6495
6619
|
// gateway path (fromPolicy → monitor).
|
|
6496
|
-
validatorOptions: { workspaceRoot: this._workspaceRoot }
|
|
6620
|
+
validatorOptions: { workspaceRoot: this._workspaceRoot },
|
|
6621
|
+
sensitivityScorer: this.sensitivityScorer
|
|
6497
6622
|
});
|
|
6498
6623
|
this.wireRunner(runner);
|
|
6499
6624
|
await this.enableAuditSigning(runner, agentId);
|
|
@@ -6556,7 +6681,8 @@ var Sentinel = class _Sentinel {
|
|
|
6556
6681
|
await this.profileManager.saveRole(agentId, role);
|
|
6557
6682
|
const runner = await this.manager.getOrCreateAgent(agentId, void 0, {
|
|
6558
6683
|
auditLogDir: this.agentsDir,
|
|
6559
|
-
validatorOptions: { workspaceRoot: options.workspaceRoot, approvalFn: () => true }
|
|
6684
|
+
validatorOptions: { workspaceRoot: options.workspaceRoot, approvalFn: () => true },
|
|
6685
|
+
sensitivityScorer: this.sensitivityScorer
|
|
6560
6686
|
});
|
|
6561
6687
|
this.wireRunner(runner, options.workspaceRoot);
|
|
6562
6688
|
await this.enableAuditSigning(runner, agentId);
|
|
@@ -6617,9 +6743,7 @@ var Sentinel = class _Sentinel {
|
|
|
6617
6743
|
workspaceRoot,
|
|
6618
6744
|
exceptions: role.exceptions,
|
|
6619
6745
|
networkHosts: role.networkHosts,
|
|
6620
|
-
expectedSchedule: role.expectedSchedule
|
|
6621
|
-
maxEventsPerHour: role.maxEventsPerHour,
|
|
6622
|
-
maxSessionDuration: role.maxSessionDuration
|
|
6746
|
+
expectedSchedule: role.expectedSchedule
|
|
6623
6747
|
});
|
|
6624
6748
|
if (policy.enforcement?.approvalRequired) {
|
|
6625
6749
|
sentinel.onApprovalRequired(createCliApproval());
|
|
@@ -6755,7 +6879,16 @@ var Sentinel = class _Sentinel {
|
|
|
6755
6879
|
const recordOpts = this.enablePreExecutionHooks ? { _skipPreHooks: true } : void 0;
|
|
6756
6880
|
const modeResult = await this.enforceMode(agentId, fullEvent);
|
|
6757
6881
|
if (modeResult && modeResult.finding) {
|
|
6758
|
-
await this._recordInternal(
|
|
6882
|
+
const recordResult = await this._recordInternal(
|
|
6883
|
+
{ ...fullEvent, authorized: false },
|
|
6884
|
+
recordOpts
|
|
6885
|
+
);
|
|
6886
|
+
const alreadyCounted = recordResult.findings.some(
|
|
6887
|
+
(f) => _Sentinel.isEscalationEligible(f) && f.mentionOnly !== true
|
|
6888
|
+
);
|
|
6889
|
+
if (!alreadyCounted) {
|
|
6890
|
+
await this.logFinding(agentId, modeResult.finding);
|
|
6891
|
+
}
|
|
6759
6892
|
await this.maybeEscalate(agentId, modeResult.finding);
|
|
6760
6893
|
return { blocked: true, finding: modeResult.finding, result: void 0 };
|
|
6761
6894
|
}
|
|
@@ -7485,4 +7618,4 @@ export {
|
|
|
7485
7618
|
createCliApproval,
|
|
7486
7619
|
Sentinel
|
|
7487
7620
|
};
|
|
7488
|
-
//# sourceMappingURL=chunk-
|
|
7621
|
+
//# sourceMappingURL=chunk-7R6EA7JG.js.map
|