@tuent/sentinel 0.1.2 → 0.1.4
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/SECURITY_MODEL.md +9 -0
- package/dist/Sentinel-4QKPFHTI.js +10 -0
- package/dist/{Sentinel-xFCyXH45.d.ts → Sentinel-DT0IyGQi.d.ts} +127 -9
- package/dist/{chunk-PDWWRZXF.js → chunk-2IPSTUNH.js} +18 -7
- package/dist/chunk-B6S2PBS4.js +47 -0
- package/dist/{chunk-QIYQWOLO.js → chunk-FIEIGBYL.js} +387 -242
- package/dist/{chunk-L4R3LPJS.js → chunk-HRI2Y326.js} +119 -35
- package/dist/{chunk-GRN5P3H2.js → chunk-I2FVDDSG.js} +242 -94
- package/dist/{chunk-WLIDSTS4.js → chunk-KWZ7JKKO.js} +221 -27
- package/dist/{chunk-FWIISAZZ.js → chunk-LTBVWF5H.js} +201 -80
- package/dist/chunk-TKAKHSZ3.js +1 -0
- package/dist/cli.js +33 -35
- package/dist/gateway/index.d.ts +20 -1
- package/dist/gateway/index.js +4 -4
- package/dist/gatewayDaemon.js +38 -16
- package/dist/index.d.ts +31 -19
- package/dist/index.js +9 -8
- package/dist/logAdapter-WM43W3S7.js +7 -0
- package/dist/{mcpAdapter-R47GX2P3.js → mcpAdapter-WYAXUE7T.js} +2 -2
- package/dist/{policyLoader-KZL2U4M2.js → policyLoader-XX6BQXNB.js} +8 -4
- package/package.json +1 -1
- package/dist/Sentinel-XMSJE4DZ.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-QIYQWOLO.js";
|
|
18
|
+
walkForbiddenInodeRoots
|
|
19
|
+
} from "./chunk-FIEIGBYL.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-KWZ7JKKO.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;
|
|
@@ -315,6 +330,9 @@ var ProfileStore = class {
|
|
|
315
330
|
this.profile = await this.backend.read();
|
|
316
331
|
this.engine = new DataEngine({ petalCount: this.petalCount });
|
|
317
332
|
if (this.profile) {
|
|
333
|
+
if (!Array.isArray(this.profile.dataPoints)) {
|
|
334
|
+
this.profile.dataPoints = [];
|
|
335
|
+
}
|
|
318
336
|
for (const point of this.profile.dataPoints) {
|
|
319
337
|
this.engine.addPoint(point);
|
|
320
338
|
}
|
|
@@ -333,8 +351,15 @@ var ProfileStore = class {
|
|
|
333
351
|
payload: { profileId: this.profile.id }
|
|
334
352
|
});
|
|
335
353
|
}
|
|
354
|
+
this.captureBaseline();
|
|
336
355
|
return this.profile;
|
|
337
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
|
+
}
|
|
338
363
|
async create(name) {
|
|
339
364
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
340
365
|
this.profile = {
|
|
@@ -346,6 +371,7 @@ var ProfileStore = class {
|
|
|
346
371
|
updatedAt: now
|
|
347
372
|
};
|
|
348
373
|
await this.backend.write(this.profile);
|
|
374
|
+
this.captureBaseline();
|
|
349
375
|
return this.profile;
|
|
350
376
|
}
|
|
351
377
|
async addPoint(point) {
|
|
@@ -365,6 +391,7 @@ var ProfileStore = class {
|
|
|
365
391
|
}
|
|
366
392
|
this.profile.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
367
393
|
await this.backend.write(this.profile);
|
|
394
|
+
this.captureBaseline();
|
|
368
395
|
this.eventBus?.emit({
|
|
369
396
|
type: "data:updated",
|
|
370
397
|
payload: { timestamp: Date.now() }
|
|
@@ -396,9 +423,46 @@ var ProfileStore = class {
|
|
|
396
423
|
async save() {
|
|
397
424
|
if (!this.profile) throw new Error("No profile loaded");
|
|
398
425
|
if (this.backend.hasExternalChanges && await this.backend.hasExternalChanges()) {
|
|
399
|
-
await this.
|
|
426
|
+
await this.mergeExternalChanges();
|
|
400
427
|
}
|
|
401
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);
|
|
402
466
|
}
|
|
403
467
|
build() {
|
|
404
468
|
return this.engine.build();
|
|
@@ -611,13 +675,15 @@ var AgentActivityClassifier = class {
|
|
|
611
675
|
weight,
|
|
612
676
|
source: "agent-monitor",
|
|
613
677
|
files: Array.from(session.targetSet).slice(0, 50),
|
|
614
|
-
connections: []
|
|
678
|
+
connections: [],
|
|
679
|
+
eventCount
|
|
615
680
|
};
|
|
616
681
|
}
|
|
617
682
|
};
|
|
618
683
|
|
|
619
684
|
// src/auditTrail.ts
|
|
620
685
|
import { createHash } from "crypto";
|
|
686
|
+
import { existsSync } from "fs";
|
|
621
687
|
var AuditTrail = class _AuditTrail {
|
|
622
688
|
static GENESIS_HASH = "0".repeat(64);
|
|
623
689
|
/** S21-P4: honest label for cumulative-stats.json's totalEntries. */
|
|
@@ -651,6 +717,11 @@ var AuditTrail = class _AuditTrail {
|
|
|
651
717
|
this.agentId = agentId;
|
|
652
718
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
|
|
653
719
|
const logDir = options?.logDir ?? `${home}/.dahlia/agents/${agentId}`;
|
|
720
|
+
if (options?.logDir && !existsSync(`${logDir}/audit.log`) && existsSync(`${logDir}/${agentId}/audit.log`)) {
|
|
721
|
+
throw new Error(
|
|
722
|
+
`AuditTrail logDir must be the agent's own directory, not the agents root: "${logDir}" contains no audit.log, but "${logDir}/${agentId}/audit.log" exists. Pass join(agentsRoot, "${agentId}") instead.`
|
|
723
|
+
);
|
|
724
|
+
}
|
|
654
725
|
this.logPath = `${logDir}/audit.log`;
|
|
655
726
|
this.statsPath = `${logDir}/cumulative-stats.json`;
|
|
656
727
|
this.manifestPath = `${logDir}/audit.manifest`;
|
|
@@ -659,27 +730,11 @@ var AuditTrail = class _AuditTrail {
|
|
|
659
730
|
this.maxFileSizeBytes = (options?.maxFileSizeMB ?? 10) * 1024 * 1024;
|
|
660
731
|
}
|
|
661
732
|
async open() {
|
|
662
|
-
const { mkdir, appendFile
|
|
733
|
+
const { mkdir, appendFile } = await import("fs/promises");
|
|
663
734
|
const { dirname: dirname2 } = await import("path");
|
|
664
735
|
await mkdir(dirname2(this.logPath), { recursive: true });
|
|
665
736
|
await appendFile(this.logPath, "");
|
|
666
|
-
this.currentChainHead = _AuditTrail.GENESIS_HASH;
|
|
667
|
-
try {
|
|
668
|
-
const raw = await readFile(this.logPath, "utf-8");
|
|
669
|
-
const lines = raw.trimEnd().split("\n");
|
|
670
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
671
|
-
if (lines[i].length === 0) continue;
|
|
672
|
-
try {
|
|
673
|
-
const last = JSON.parse(lines[i]);
|
|
674
|
-
if (typeof last.hash === "string" && last.hash.length === 64) {
|
|
675
|
-
this.currentChainHead = last.hash;
|
|
676
|
-
}
|
|
677
|
-
} catch {
|
|
678
|
-
}
|
|
679
|
-
break;
|
|
680
|
-
}
|
|
681
|
-
} catch {
|
|
682
|
-
}
|
|
737
|
+
this.currentChainHead = await this.lastHashOnDisk() ?? _AuditTrail.GENESIS_HASH;
|
|
683
738
|
try {
|
|
684
739
|
const { stat } = await import("fs/promises");
|
|
685
740
|
this._lastKnownSize = (await stat(this.logPath)).size;
|
|
@@ -749,6 +804,9 @@ var AuditTrail = class _AuditTrail {
|
|
|
749
804
|
if (finding.dedupKey !== void 0) {
|
|
750
805
|
entry.dedupKey = finding.dedupKey;
|
|
751
806
|
}
|
|
807
|
+
if (finding.targetKey !== void 0) {
|
|
808
|
+
entry.targetKey = finding.targetKey;
|
|
809
|
+
}
|
|
752
810
|
await this.writeLine(entry);
|
|
753
811
|
}
|
|
754
812
|
/**
|
|
@@ -874,7 +932,11 @@ var AuditTrail = class _AuditTrail {
|
|
|
874
932
|
for (const entry of entries) {
|
|
875
933
|
if (options.type && entry.type !== options.type) continue;
|
|
876
934
|
const entryMs = new Date(entry.timestamp).getTime();
|
|
877
|
-
if (entryMs
|
|
935
|
+
if (Number.isNaN(entryMs)) {
|
|
936
|
+
if (options.startTime || options.endTime) continue;
|
|
937
|
+
} else if (entryMs < startMs || entryMs > endMs) {
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
878
940
|
if (options.severity && entry.type === "finding") {
|
|
879
941
|
if (entry.severity !== options.severity) continue;
|
|
880
942
|
}
|
|
@@ -967,6 +1029,7 @@ var AuditTrail = class _AuditTrail {
|
|
|
967
1029
|
let signedEntries = 0;
|
|
968
1030
|
let unsignedEntries = 0;
|
|
969
1031
|
let invalidSignatures = 0;
|
|
1032
|
+
let seenHashedEntry = false;
|
|
970
1033
|
let priorFileLastHash = null;
|
|
971
1034
|
for (const file of files) {
|
|
972
1035
|
const base = basename(file);
|
|
@@ -1037,6 +1100,7 @@ var AuditTrail = class _AuditTrail {
|
|
|
1037
1100
|
unsignedEntries++;
|
|
1038
1101
|
}
|
|
1039
1102
|
globalIndex++;
|
|
1103
|
+
seenHashedEntry = true;
|
|
1040
1104
|
checkpointReached = true;
|
|
1041
1105
|
startIndex = aIdx + 1;
|
|
1042
1106
|
previousHash = checkpoint.anchorHash;
|
|
@@ -1073,10 +1137,26 @@ var AuditTrail = class _AuditTrail {
|
|
|
1073
1137
|
try {
|
|
1074
1138
|
entry = JSON.parse(line);
|
|
1075
1139
|
} catch {
|
|
1076
|
-
|
|
1077
|
-
|
|
1140
|
+
return attach({
|
|
1141
|
+
valid: false,
|
|
1142
|
+
totalEntries: globalIndex + 1,
|
|
1143
|
+
brokenAt: globalIndex,
|
|
1144
|
+
signedEntries,
|
|
1145
|
+
unsignedEntries,
|
|
1146
|
+
invalidSignatures
|
|
1147
|
+
});
|
|
1078
1148
|
}
|
|
1079
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
|
+
}
|
|
1080
1160
|
globalIndex++;
|
|
1081
1161
|
continue;
|
|
1082
1162
|
}
|
|
@@ -1116,6 +1196,7 @@ var AuditTrail = class _AuditTrail {
|
|
|
1116
1196
|
}
|
|
1117
1197
|
previousHash = storedHash;
|
|
1118
1198
|
globalIndex++;
|
|
1199
|
+
seenHashedEntry = true;
|
|
1119
1200
|
}
|
|
1120
1201
|
priorFileLastHash = previousHash;
|
|
1121
1202
|
}
|
|
@@ -1753,6 +1834,23 @@ var AuditTrail = class _AuditTrail {
|
|
|
1753
1834
|
* the file when an external append actually happened.
|
|
1754
1835
|
*/
|
|
1755
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() {
|
|
1756
1854
|
const { readFile } = await import("fs/promises");
|
|
1757
1855
|
try {
|
|
1758
1856
|
const raw = await readFile(this.logPath, "utf-8");
|
|
@@ -1760,16 +1858,16 @@ var AuditTrail = class _AuditTrail {
|
|
|
1760
1858
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1761
1859
|
if (lines[i].length === 0) continue;
|
|
1762
1860
|
try {
|
|
1763
|
-
const
|
|
1764
|
-
if (typeof
|
|
1765
|
-
|
|
1861
|
+
const entry = JSON.parse(lines[i]);
|
|
1862
|
+
if (typeof entry.hash === "string" && entry.hash.length === 64) {
|
|
1863
|
+
return entry.hash;
|
|
1766
1864
|
}
|
|
1767
1865
|
} catch {
|
|
1768
1866
|
}
|
|
1769
|
-
break;
|
|
1770
1867
|
}
|
|
1771
1868
|
} catch {
|
|
1772
1869
|
}
|
|
1870
|
+
return null;
|
|
1773
1871
|
}
|
|
1774
1872
|
async readAllEntries() {
|
|
1775
1873
|
const { readFile } = await import("fs/promises");
|
|
@@ -2013,7 +2111,7 @@ var DeviationDetector = class {
|
|
|
2013
2111
|
checkVolumeSpike(dataPoint) {
|
|
2014
2112
|
if (this.baseline.averageEventsPerSession === 0) return null;
|
|
2015
2113
|
const description = dataPoint.description ?? "";
|
|
2016
|
-
const eventCount = parseEventCount(description) ?? estimateEventsFromWeight(dataPoint.weight ?? 0);
|
|
2114
|
+
const eventCount = dataPoint.eventCount ?? parseEventCount(description) ?? estimateEventsFromWeight(dataPoint.weight ?? 0);
|
|
2017
2115
|
const threshold = this.baseline.averageEventsPerSession * this.config.volumeSpikeMultiplier;
|
|
2018
2116
|
if (eventCount <= threshold) return null;
|
|
2019
2117
|
const multiplier = (eventCount / this.baseline.averageEventsPerSession).toFixed(1);
|
|
@@ -2162,7 +2260,7 @@ var DeviationDetector = class {
|
|
|
2162
2260
|
checkActivityDrop(dataPoint) {
|
|
2163
2261
|
if (this.baseline.averageEventsPerSession === 0) return null;
|
|
2164
2262
|
const description = dataPoint.description ?? "";
|
|
2165
|
-
const eventCount = parseEventCount(description) ?? estimateEventsFromWeight(dataPoint.weight ?? 0);
|
|
2263
|
+
const eventCount = dataPoint.eventCount ?? parseEventCount(description) ?? estimateEventsFromWeight(dataPoint.weight ?? 0);
|
|
2166
2264
|
if (eventCount === 0) return null;
|
|
2167
2265
|
const ratio = eventCount / this.baseline.averageEventsPerSession;
|
|
2168
2266
|
if (ratio < 0.1) {
|
|
@@ -2356,12 +2454,8 @@ var COMMON_ABBREVIATIONS = /* @__PURE__ */ new Map([
|
|
|
2356
2454
|
["development", "dev"]
|
|
2357
2455
|
]);
|
|
2358
2456
|
var DEFAULT_INTENT_CONFIG = {
|
|
2359
|
-
defaultTtlMs: 8 * 60 * 60 * 1e3
|
|
2457
|
+
defaultTtlMs: 8 * 60 * 60 * 1e3
|
|
2360
2458
|
// 8 hours
|
|
2361
|
-
keywordBoost: 0.15,
|
|
2362
|
-
acceptableActionBoost: 0.3,
|
|
2363
|
-
baseScore: 0.2,
|
|
2364
|
-
missingTaskScore: 0.5
|
|
2365
2459
|
};
|
|
2366
2460
|
var DEFAULT_ACCEPTABLE_ACTIONS = [
|
|
2367
2461
|
// Config/manifest reads
|
|
@@ -2411,15 +2505,14 @@ var DEFAULT_ACCEPTABLE_ACTIONS = [
|
|
|
2411
2505
|
{ action: "command_exec", targetPattern: "tsc*", reason: "TypeScript compiler" }
|
|
2412
2506
|
];
|
|
2413
2507
|
function extractKeywords(description) {
|
|
2414
|
-
const
|
|
2415
|
-
const
|
|
2416
|
-
for (const
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
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];
|
|
2423
2516
|
}
|
|
2424
2517
|
var IntentTracker = class {
|
|
2425
2518
|
tasks = /* @__PURE__ */ new Map();
|
|
@@ -2708,6 +2801,17 @@ var SimilarityEngine = class {
|
|
|
2708
2801
|
// src/evaluation.ts
|
|
2709
2802
|
var DRIFT_WINDOW_SIZE = 5;
|
|
2710
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
|
+
}
|
|
2711
2815
|
function evaluateEvent(event, state, deps) {
|
|
2712
2816
|
const findings = [];
|
|
2713
2817
|
let intentAlignment;
|
|
@@ -2728,9 +2832,9 @@ function evaluateEvent(event, state, deps) {
|
|
|
2728
2832
|
if (newScores.length > DRIFT_WINDOW_SIZE) {
|
|
2729
2833
|
newScores = newScores.slice(newScores.length - DRIFT_WINDOW_SIZE);
|
|
2730
2834
|
}
|
|
2731
|
-
const
|
|
2835
|
+
const driftVerdict = computeDriftSeverity(alignment.score, newScores);
|
|
2732
2836
|
const driftFinding = {
|
|
2733
|
-
severity:
|
|
2837
|
+
severity: driftVerdict.severity,
|
|
2734
2838
|
kind: "informational",
|
|
2735
2839
|
type: "intent_drift",
|
|
2736
2840
|
agentId: event.agentId,
|
|
@@ -2745,17 +2849,9 @@ function evaluateEvent(event, state, deps) {
|
|
|
2745
2849
|
recommendation: `Review whether ${describeEvent(event)} is necessary for the current task.`,
|
|
2746
2850
|
timestamp: event.timestamp
|
|
2747
2851
|
};
|
|
2748
|
-
if (
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
const sustainedSeverity = "HIGH";
|
|
2752
|
-
const severityRank = { LOW: 0, MEDIUM: 1, HIGH: 2, CRITICAL: 3 };
|
|
2753
|
-
if (severityRank[sustainedSeverity] > severityRank[driftFinding.severity]) {
|
|
2754
|
-
driftFinding.severity = sustainedSeverity;
|
|
2755
|
-
}
|
|
2756
|
-
driftFinding.description = `Sustained intent drift detected (${DRIFT_WINDOW_SIZE} consecutive misaligned actions, avg score ${avg.toFixed(2)}): ${alignment.reason}`;
|
|
2757
|
-
driftFinding.softSignal = true;
|
|
2758
|
-
}
|
|
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;
|
|
2759
2855
|
}
|
|
2760
2856
|
findings.push(driftFinding);
|
|
2761
2857
|
} else {
|
|
@@ -2798,6 +2894,9 @@ var SentinelRunner = class {
|
|
|
2798
2894
|
_maturityConfig;
|
|
2799
2895
|
// Role validator options (injected by Sentinel facade for exception handling)
|
|
2800
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;
|
|
2801
2900
|
// Intent alignment
|
|
2802
2901
|
intentTracker;
|
|
2803
2902
|
similarityEngine;
|
|
@@ -2843,6 +2942,14 @@ var SentinelRunner = class {
|
|
|
2843
2942
|
setRoleValidatorOptions(options) {
|
|
2844
2943
|
this._validatorOptions = options;
|
|
2845
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
|
+
}
|
|
2846
2953
|
/** Set the behavioral baseline and start periodic gap checking. */
|
|
2847
2954
|
setBaseline(baseline) {
|
|
2848
2955
|
this._baseline = baseline;
|
|
@@ -2899,6 +3006,14 @@ var SentinelRunner = class {
|
|
|
2899
3006
|
getSimilarityEngine() {
|
|
2900
3007
|
return this.similarityEngine;
|
|
2901
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
|
+
}
|
|
2902
3017
|
/**
|
|
2903
3018
|
* Side-effect-free pre-execution gate: evaluate an event and fire pre_execution
|
|
2904
3019
|
* hooks WITHOUT running processEvent's full pipeline.
|
|
@@ -2978,6 +3093,7 @@ var SentinelRunner = class {
|
|
|
2978
3093
|
}
|
|
2979
3094
|
async runGapCheck() {
|
|
2980
3095
|
if (!this._deviationDetector || !this.auditTrail) return;
|
|
3096
|
+
if (this._eventCount === 0) return;
|
|
2981
3097
|
const finding = await this._deviationDetector.checkActivityGap(this.auditTrail);
|
|
2982
3098
|
if (finding) {
|
|
2983
3099
|
this.findings.push(finding);
|
|
@@ -3009,7 +3125,7 @@ var SentinelRunner = class {
|
|
|
3009
3125
|
if (role) {
|
|
3010
3126
|
this.validator = new RoleValidator(
|
|
3011
3127
|
role,
|
|
3012
|
-
new TargetSensitivityScorer(),
|
|
3128
|
+
this._sensitivityScorer ?? new TargetSensitivityScorer(),
|
|
3013
3129
|
this._validatorOptions
|
|
3014
3130
|
);
|
|
3015
3131
|
}
|
|
@@ -3277,7 +3393,7 @@ var SentinelRunner = class {
|
|
|
3277
3393
|
const { type } = this.adapterConfig;
|
|
3278
3394
|
if (type === "manual") return;
|
|
3279
3395
|
if (type === "log") {
|
|
3280
|
-
const { LogAdapter } = await import("./logAdapter-
|
|
3396
|
+
const { LogAdapter } = await import("./logAdapter-WM43W3S7.js");
|
|
3281
3397
|
const logPath = this.adapterConfig.logPath;
|
|
3282
3398
|
if (!logPath) {
|
|
3283
3399
|
throw new Error(`SentinelRunner: logPath is required for adapter type "log"`);
|
|
@@ -3295,7 +3411,7 @@ var SentinelRunner = class {
|
|
|
3295
3411
|
}).catch((err) => console.warn(`SentinelRunner processEvent error: ${err}`));
|
|
3296
3412
|
});
|
|
3297
3413
|
} else if (type === "mcp") {
|
|
3298
|
-
const { McpAdapter } = await import("./mcpAdapter-
|
|
3414
|
+
const { McpAdapter } = await import("./mcpAdapter-WYAXUE7T.js");
|
|
3299
3415
|
this.adapter = new McpAdapter(this.agentId, this.agentId, {
|
|
3300
3416
|
logDir: this.adapterConfig.mcpLogDir,
|
|
3301
3417
|
pollIntervalMs: this.adapterConfig.pollIntervalMs
|
|
@@ -3425,6 +3541,9 @@ var SentinelManager = class {
|
|
|
3425
3541
|
if (options?.validatorOptions) {
|
|
3426
3542
|
runner.setRoleValidatorOptions(options.validatorOptions);
|
|
3427
3543
|
}
|
|
3544
|
+
if (options?.sensitivityScorer) {
|
|
3545
|
+
runner.setSensitivityScorer(options.sensitivityScorer);
|
|
3546
|
+
}
|
|
3428
3547
|
await runner.start();
|
|
3429
3548
|
return runner;
|
|
3430
3549
|
}
|
|
@@ -3529,7 +3648,7 @@ var SentinelManager = class {
|
|
|
3529
3648
|
};
|
|
3530
3649
|
|
|
3531
3650
|
// src/Sentinel.ts
|
|
3532
|
-
import { lstatSync, existsSync } from "fs";
|
|
3651
|
+
import { lstatSync, existsSync as existsSync2 } from "fs";
|
|
3533
3652
|
import { resolve as resolve2, dirname, sep as sep2 } from "path";
|
|
3534
3653
|
|
|
3535
3654
|
// src/baselineBuilder.ts
|
|
@@ -3725,9 +3844,11 @@ var AlertManager = class {
|
|
|
3725
3844
|
const topLevelMinKind = this.config.minKind ?? "actionable";
|
|
3726
3845
|
const findingKindRank = KIND_ORDER[finding.kind] ?? KIND_ORDER.actionable;
|
|
3727
3846
|
const isConvergedSoftSignal = finding.softSignal === true;
|
|
3847
|
+
let routed = false;
|
|
3728
3848
|
for (const channel of this.config.channels) {
|
|
3729
3849
|
const channelMinKind = channel.minKind ?? topLevelMinKind;
|
|
3730
3850
|
if (!isConvergedSoftSignal && findingKindRank < KIND_ORDER[channelMinKind]) continue;
|
|
3851
|
+
routed = true;
|
|
3731
3852
|
try {
|
|
3732
3853
|
if (channel.type === "console") {
|
|
3733
3854
|
await this.sendConsole(finding);
|
|
@@ -3740,7 +3861,9 @@ var AlertManager = class {
|
|
|
3740
3861
|
console.warn(`Alert channel "${channel.name}" failed:`, err);
|
|
3741
3862
|
}
|
|
3742
3863
|
}
|
|
3743
|
-
|
|
3864
|
+
if (routed) {
|
|
3865
|
+
this.recentAlerts.set(dedupeKey, Date.now());
|
|
3866
|
+
}
|
|
3744
3867
|
}
|
|
3745
3868
|
clearDedupeCache() {
|
|
3746
3869
|
this.recentAlerts.clear();
|
|
@@ -5644,7 +5767,7 @@ var HookEngine = class {
|
|
|
5644
5767
|
candidate = { kind: "allow" };
|
|
5645
5768
|
selectedRegistration = null;
|
|
5646
5769
|
}
|
|
5647
|
-
if (evaluationResult && candidate.kind === "guide" && evaluationResult.findings.some(
|
|
5770
|
+
if (checkpoint === "pre_execution" && evaluationResult && candidate.kind === "guide" && evaluationResult.findings.some(
|
|
5648
5771
|
(f) => (f.severity === "HIGH" || f.severity === "CRITICAL") && f.kind === "actionable"
|
|
5649
5772
|
)) {
|
|
5650
5773
|
const severityRank = { LOW: 0, MEDIUM: 1, HIGH: 2, CRITICAL: 3 };
|
|
@@ -5667,7 +5790,7 @@ var HookEngine = class {
|
|
|
5667
5790
|
finding: blockingFinding
|
|
5668
5791
|
};
|
|
5669
5792
|
}
|
|
5670
|
-
if (candidate.kind === "allow" && evaluationResult && evaluationResult.findings.some(
|
|
5793
|
+
if (checkpoint === "pre_execution" && candidate.kind === "allow" && evaluationResult && evaluationResult.findings.some(
|
|
5671
5794
|
(f) => (f.severity === "HIGH" || f.severity === "CRITICAL") && f.kind === "actionable"
|
|
5672
5795
|
)) {
|
|
5673
5796
|
const severityRank = { LOW: 0, MEDIUM: 1, HIGH: 2, CRITICAL: 3 };
|
|
@@ -5913,8 +6036,8 @@ var Sentinel = class _Sentinel {
|
|
|
5913
6036
|
this.manager = new SentinelManager(this.profileManager);
|
|
5914
6037
|
this.sensitivityScorer = new TargetSensitivityScorer();
|
|
5915
6038
|
this.environment = config?.environment ?? "development";
|
|
5916
|
-
this.restrictThreshold = config?.enforcement?.restrictAfter ??
|
|
5917
|
-
this.quarantineThreshold = config?.enforcement?.quarantineAfter ??
|
|
6039
|
+
this.restrictThreshold = config?.enforcement?.restrictAfter ?? DEFAULT_RESTRICT_AFTER;
|
|
6040
|
+
this.quarantineThreshold = config?.enforcement?.quarantineAfter ?? DEFAULT_QUARANTINE_AFTER;
|
|
5918
6041
|
this.enablePreExecutionHooks = config?.enablePreExecutionHooks ?? false;
|
|
5919
6042
|
this.promoteSet = new Set(config?.enforcement?.promote ?? []);
|
|
5920
6043
|
this.maturityConfig = config?.enforcement?.baselineMaturity;
|
|
@@ -6154,10 +6277,18 @@ var Sentinel = class _Sentinel {
|
|
|
6154
6277
|
);
|
|
6155
6278
|
}
|
|
6156
6279
|
async logModeChange(agentId, mode, reason, previousMode) {
|
|
6157
|
-
const
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
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
|
+
}
|
|
6161
6292
|
}
|
|
6162
6293
|
async stopAgent(agentId, reason) {
|
|
6163
6294
|
const { mkdir, writeFile } = await import("fs/promises");
|
|
@@ -6264,7 +6395,10 @@ var Sentinel = class _Sentinel {
|
|
|
6264
6395
|
const engine = runner.getSimilarityEngine();
|
|
6265
6396
|
const alignment = engine.computeAlignment(activeTask, event);
|
|
6266
6397
|
if (!alignment.aligned && !alignment.bypassed) {
|
|
6267
|
-
const
|
|
6398
|
+
const projectedWindow = [...runner.getRecentAlignmentScores(), alignment.score].slice(
|
|
6399
|
+
-DRIFT_WINDOW_SIZE
|
|
6400
|
+
);
|
|
6401
|
+
const { severity } = computeDriftSeverity(alignment.score, projectedWindow);
|
|
6268
6402
|
return {
|
|
6269
6403
|
severity,
|
|
6270
6404
|
kind: "informational",
|
|
@@ -6389,7 +6523,10 @@ var Sentinel = class _Sentinel {
|
|
|
6389
6523
|
// Approach-1 anchoring: give the runner's processEvent validator the SAME
|
|
6390
6524
|
// workspaceRoot check() uses (this._workspaceRoot), supplied BEFORE start()
|
|
6391
6525
|
// builds it — otherwise it logs phantom scope_violations on legit reads.
|
|
6392
|
-
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
|
|
6393
6530
|
});
|
|
6394
6531
|
this.wireRunner(runner);
|
|
6395
6532
|
await this.enableAuditSigning(runner, agentId);
|
|
@@ -6418,7 +6555,7 @@ var Sentinel = class _Sentinel {
|
|
|
6418
6555
|
);
|
|
6419
6556
|
}
|
|
6420
6557
|
}
|
|
6421
|
-
if (!
|
|
6558
|
+
if (!existsSync2(root)) {
|
|
6422
6559
|
console.warn(
|
|
6423
6560
|
`[Sentinel] Workspace root '${root}' does not exist on disk \u2014 anchored allowed patterns will match nothing and every allowed read may be denied.`
|
|
6424
6561
|
);
|
|
@@ -6480,7 +6617,8 @@ var Sentinel = class _Sentinel {
|
|
|
6480
6617
|
// Approach-1 anchoring: same workspaceRoot as check() (this._workspaceRoot),
|
|
6481
6618
|
// supplied BEFORE start() builds the runner's validator. This is the live
|
|
6482
6619
|
// gateway path (fromPolicy → monitor).
|
|
6483
|
-
validatorOptions: { workspaceRoot: this._workspaceRoot }
|
|
6620
|
+
validatorOptions: { workspaceRoot: this._workspaceRoot },
|
|
6621
|
+
sensitivityScorer: this.sensitivityScorer
|
|
6484
6622
|
});
|
|
6485
6623
|
this.wireRunner(runner);
|
|
6486
6624
|
await this.enableAuditSigning(runner, agentId);
|
|
@@ -6543,7 +6681,8 @@ var Sentinel = class _Sentinel {
|
|
|
6543
6681
|
await this.profileManager.saveRole(agentId, role);
|
|
6544
6682
|
const runner = await this.manager.getOrCreateAgent(agentId, void 0, {
|
|
6545
6683
|
auditLogDir: this.agentsDir,
|
|
6546
|
-
validatorOptions: { workspaceRoot: options.workspaceRoot, approvalFn: () => true }
|
|
6684
|
+
validatorOptions: { workspaceRoot: options.workspaceRoot, approvalFn: () => true },
|
|
6685
|
+
sensitivityScorer: this.sensitivityScorer
|
|
6547
6686
|
});
|
|
6548
6687
|
this.wireRunner(runner, options.workspaceRoot);
|
|
6549
6688
|
await this.enableAuditSigning(runner, agentId);
|
|
@@ -6604,9 +6743,7 @@ var Sentinel = class _Sentinel {
|
|
|
6604
6743
|
workspaceRoot,
|
|
6605
6744
|
exceptions: role.exceptions,
|
|
6606
6745
|
networkHosts: role.networkHosts,
|
|
6607
|
-
expectedSchedule: role.expectedSchedule
|
|
6608
|
-
maxEventsPerHour: role.maxEventsPerHour,
|
|
6609
|
-
maxSessionDuration: role.maxSessionDuration
|
|
6746
|
+
expectedSchedule: role.expectedSchedule
|
|
6610
6747
|
});
|
|
6611
6748
|
if (policy.enforcement?.approvalRequired) {
|
|
6612
6749
|
sentinel.onApprovalRequired(createCliApproval());
|
|
@@ -6742,7 +6879,16 @@ var Sentinel = class _Sentinel {
|
|
|
6742
6879
|
const recordOpts = this.enablePreExecutionHooks ? { _skipPreHooks: true } : void 0;
|
|
6743
6880
|
const modeResult = await this.enforceMode(agentId, fullEvent);
|
|
6744
6881
|
if (modeResult && modeResult.finding) {
|
|
6745
|
-
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
|
+
}
|
|
6746
6892
|
await this.maybeEscalate(agentId, modeResult.finding);
|
|
6747
6893
|
return { blocked: true, finding: modeResult.finding, result: void 0 };
|
|
6748
6894
|
}
|
|
@@ -7421,8 +7567,10 @@ var Sentinel = class _Sentinel {
|
|
|
7421
7567
|
const distinctKeys = /* @__PURE__ */ new Set();
|
|
7422
7568
|
let keyless = 0;
|
|
7423
7569
|
for (const f of survivors) {
|
|
7424
|
-
const
|
|
7425
|
-
|
|
7570
|
+
const tk = f.targetKey;
|
|
7571
|
+
const dk = f.dedupKey;
|
|
7572
|
+
if (typeof tk === "string" && tk.length > 0) distinctKeys.add(`t:${tk}`);
|
|
7573
|
+
else if (typeof dk === "string" && dk.length > 0) distinctKeys.add(`c:${dk}`);
|
|
7426
7574
|
else keyless++;
|
|
7427
7575
|
}
|
|
7428
7576
|
return distinctKeys.size + keyless;
|
|
@@ -7470,4 +7618,4 @@ export {
|
|
|
7470
7618
|
createCliApproval,
|
|
7471
7619
|
Sentinel
|
|
7472
7620
|
};
|
|
7473
|
-
//# sourceMappingURL=chunk-
|
|
7621
|
+
//# sourceMappingURL=chunk-I2FVDDSG.js.map
|