@tuent/sentinel 0.1.3 → 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/dist/Sentinel-4QKPFHTI.js +10 -0
- package/dist/{Sentinel-BVoMEF3F.d.ts → Sentinel-DT0IyGQi.d.ts} +112 -9
- package/dist/{chunk-PDWWRZXF.js → chunk-2IPSTUNH.js} +18 -7
- package/dist/chunk-B6S2PBS4.js +47 -0
- package/dist/{chunk-JTR2E7RD.js → chunk-FIEIGBYL.js} +222 -186
- package/dist/{chunk-G74MMDKA.js → chunk-HRI2Y326.js} +76 -21
- package/dist/{chunk-SSDIBY52.js → chunk-I2FVDDSG.js} +223 -90
- package/dist/{chunk-WLIDSTS4.js → chunk-KWZ7JKKO.js} +221 -27
- package/dist/{chunk-2TJ5Z53T.js → chunk-LTBVWF5H.js} +59 -20
- package/dist/cli.js +33 -35
- package/dist/gateway/index.d.ts +10 -1
- package/dist/gateway/index.js +4 -4
- package/dist/gatewayDaemon.js +5 -5
- package/dist/index.d.ts +2 -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-XX6BQXNB.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
|
@@ -16,6 +16,7 @@ interface SkillNode {
|
|
|
16
16
|
core?: boolean;
|
|
17
17
|
sharable?: boolean;
|
|
18
18
|
files?: string[];
|
|
19
|
+
eventCount?: number;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
interface DataPoint {
|
|
@@ -29,6 +30,7 @@ interface DataPoint {
|
|
|
29
30
|
files?: string[];
|
|
30
31
|
sharable?: boolean;
|
|
31
32
|
core?: boolean;
|
|
33
|
+
eventCount?: number;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
interface StoredProfile {
|
|
@@ -89,16 +91,41 @@ declare class ProfileStore {
|
|
|
89
91
|
private petalCount;
|
|
90
92
|
private engine;
|
|
91
93
|
private profile;
|
|
94
|
+
/** Snapshot (label → serialized point) of the on-disk-synced dataPoint set,
|
|
95
|
+
* refreshed on every load/create/write. The base for save()'s three-way merge
|
|
96
|
+
* of concurrent external writes — lets us tell "I deleted this" (in baseline,
|
|
97
|
+
* gone from memory) from "another writer added this" (on disk, not in baseline). */
|
|
98
|
+
private baseline;
|
|
92
99
|
constructor(options: {
|
|
93
100
|
backend: StorageBackend;
|
|
94
101
|
eventBus?: EventBus;
|
|
95
102
|
petalCount?: number;
|
|
96
103
|
});
|
|
97
104
|
load(): Promise<StoredProfile | null>;
|
|
105
|
+
/** Snapshot the on-disk-synced dataPoint set for save()'s three-way merge. */
|
|
106
|
+
private captureBaseline;
|
|
98
107
|
create(name: string): Promise<StoredProfile>;
|
|
99
108
|
addPoint(point: DataPoint): Promise<void>;
|
|
100
109
|
addPoints(points: DataPoint[]): void;
|
|
101
110
|
save(): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Three-way merge of a concurrent external write into the in-memory profile,
|
|
113
|
+
* invoked by save() when the backend reports the file changed since we last
|
|
114
|
+
* synced. Writers in this app are single-user but multi-process (agent scanner,
|
|
115
|
+
* diary watcher, dev server, CLI), so concurrent writes are real but rare.
|
|
116
|
+
*
|
|
117
|
+
* Model (base = the dataPoint set at our last load/write, see `baseline`):
|
|
118
|
+
* - a point present in memory → kept as-is (the SAVING process's adds/edits
|
|
119
|
+
* win on a same-point conflict — save() is an explicit "persist my state");
|
|
120
|
+
* - a disk point whose label is NEW since baseline → another writer's
|
|
121
|
+
* addition → preserved;
|
|
122
|
+
* - a point we dropped (in baseline, absent from memory) → our deletion is
|
|
123
|
+
* honored, UNLESS the other writer modified it (then their version is kept
|
|
124
|
+
* so a concurrent edit is never silently lost).
|
|
125
|
+
* Net: nothing vanishes except a same-point simultaneous edit, resolved
|
|
126
|
+
* last-writer (this save) — a deliberate, documented tie-break.
|
|
127
|
+
*/
|
|
128
|
+
private mergeExternalChanges;
|
|
102
129
|
build(): SkillNode[];
|
|
103
130
|
getProfile(): StoredProfile | null;
|
|
104
131
|
}
|
|
@@ -380,7 +407,6 @@ interface SentinelConfig {
|
|
|
380
407
|
mcpLogDir?: string;
|
|
381
408
|
webhookPort?: number;
|
|
382
409
|
webhookApiKey?: string;
|
|
383
|
-
roleDefinitionPath?: string;
|
|
384
410
|
readExisting?: boolean;
|
|
385
411
|
}[];
|
|
386
412
|
alerts?: {
|
|
@@ -400,9 +426,6 @@ interface SentinelConfig {
|
|
|
400
426
|
minKind?: "informational" | "actionable";
|
|
401
427
|
}[];
|
|
402
428
|
};
|
|
403
|
-
alertWebhook?: string;
|
|
404
|
-
baselineWindowDays?: number;
|
|
405
|
-
checkIntervalMs?: number;
|
|
406
429
|
}
|
|
407
430
|
/** A declared task/intent for an agent. */
|
|
408
431
|
interface TaskIntent {
|
|
@@ -447,13 +470,18 @@ interface IntentAlignmentResult {
|
|
|
447
470
|
bypassed?: boolean;
|
|
448
471
|
bypassReason?: string | null;
|
|
449
472
|
}
|
|
450
|
-
/**
|
|
473
|
+
/**
|
|
474
|
+
* Configuration for intent alignment scoring.
|
|
475
|
+
*
|
|
476
|
+
* NOTE: the keywordBoost / acceptableActionBoost / baseScore /
|
|
477
|
+
* missingTaskScore knobs were removed — they were defined, defaulted, and
|
|
478
|
+
* typed but never read by any scoring path (vestiges of a pre-weighted
|
|
479
|
+
* scoring model superseded by SimilarityEngine.keywordSimilarity). They
|
|
480
|
+
* promised tunability that did not exist; the live weights are fixed in
|
|
481
|
+
* SimilarityEngine. defaultTtlMs is the only consumed numeric knob.
|
|
482
|
+
*/
|
|
451
483
|
interface IntentAlignmentConfig {
|
|
452
484
|
defaultTtlMs: number;
|
|
453
|
-
keywordBoost: number;
|
|
454
|
-
acceptableActionBoost: number;
|
|
455
|
-
baseScore: number;
|
|
456
|
-
missingTaskScore: number;
|
|
457
485
|
disableDefaultAcceptable?: boolean;
|
|
458
486
|
denyAcceptable?: string[];
|
|
459
487
|
similarityThreshold?: number;
|
|
@@ -864,6 +892,20 @@ declare class AuditTrail {
|
|
|
864
892
|
* the file when an external append actually happened.
|
|
865
893
|
*/
|
|
866
894
|
private reanchorFromDiskTail;
|
|
895
|
+
/**
|
|
896
|
+
* Scan the on-disk log backward for the last entry carrying a valid (64-hex)
|
|
897
|
+
* hash, SKIPPING torn/corrupt trailing lines, and return that hash (or null
|
|
898
|
+
* when no parseable hashed entry exists).
|
|
899
|
+
*
|
|
900
|
+
* Shared by open() (chain resume) and reanchorFromDiskTail() (live
|
|
901
|
+
* re-anchor). The earlier inline versions inspected only the LAST non-empty
|
|
902
|
+
* line and broke: a torn tail line (a partial write) left the head at genesis
|
|
903
|
+
* (open) or stale (reanchor), so the next append forked a fresh chain from
|
|
904
|
+
* genesis — destroying all prior linkage. Walking back leaves the torn line
|
|
905
|
+
* as a single localized gap that verify() still flags, while the chain head
|
|
906
|
+
* stays anchored to the last good entry.
|
|
907
|
+
*/
|
|
908
|
+
private lastHashOnDisk;
|
|
867
909
|
private readAllEntries;
|
|
868
910
|
private loadCumulativeStats;
|
|
869
911
|
/**
|
|
@@ -916,6 +958,54 @@ interface AgentClassifierConfig {
|
|
|
916
958
|
minDurationMs: number;
|
|
917
959
|
}
|
|
918
960
|
|
|
961
|
+
interface SensitivityRule {
|
|
962
|
+
pattern: string;
|
|
963
|
+
sensitivity: number;
|
|
964
|
+
category: string;
|
|
965
|
+
description: string;
|
|
966
|
+
}
|
|
967
|
+
interface TargetSensitivityResult {
|
|
968
|
+
sensitivity: number;
|
|
969
|
+
category: string;
|
|
970
|
+
description: string;
|
|
971
|
+
matchedRule: string;
|
|
972
|
+
actionMultiplier: number;
|
|
973
|
+
effectiveScore: number;
|
|
974
|
+
groundedInRepoMap: boolean;
|
|
975
|
+
}
|
|
976
|
+
declare class TargetSensitivityScorer {
|
|
977
|
+
private rules;
|
|
978
|
+
private repoMap;
|
|
979
|
+
private repoRoot;
|
|
980
|
+
private overlay;
|
|
981
|
+
constructor(customRules?: SensitivityRule[], repoMap?: RepoSensitivityMap, repoRoot?: string, overlay?: SensitivityOverlay);
|
|
982
|
+
scoreTarget(target: string, action: string): TargetSensitivityResult;
|
|
983
|
+
/** Pattern-only scoring (Tier 2). Used directly by reject decisions. */
|
|
984
|
+
private _scoreByPatterns;
|
|
985
|
+
/**
|
|
986
|
+
* URL-aware component scoring (Sprint 6b W1 fix).
|
|
987
|
+
* Parses the URL, extracts path/query-values/fragment, decodes each once,
|
|
988
|
+
* scores each against forbidden-pattern rules, takes MAX.
|
|
989
|
+
*/
|
|
990
|
+
private _scoreUrlTarget;
|
|
991
|
+
scoreEvent(event: AgentActivityEvent): TargetSensitivityResult;
|
|
992
|
+
/**
|
|
993
|
+
* Replaces the active repo sensitivity map. Pass null to clear.
|
|
994
|
+
* If newRepoRoot is provided, it overrides the map's repoRoot.
|
|
995
|
+
*/
|
|
996
|
+
updateRepoMap(newMap: RepoSensitivityMap | null, newRepoRoot?: string): void;
|
|
997
|
+
/**
|
|
998
|
+
* Replaces the active sensitivity overlay. Pass null to clear.
|
|
999
|
+
*/
|
|
1000
|
+
updateOverlay(newOverlay: SensitivityOverlay | null): void;
|
|
1001
|
+
/**
|
|
1002
|
+
* Returns all pattern-based rules that match the target. Does NOT
|
|
1003
|
+
* consult the repo map. Used by RepoSensitivityScanner during
|
|
1004
|
+
* scan-time enumeration.
|
|
1005
|
+
*/
|
|
1006
|
+
getAllMatchingRules(target: string): SensitivityRule[];
|
|
1007
|
+
}
|
|
1008
|
+
|
|
919
1009
|
/** Lazy-init wrapper passed via RoleValidatorOptions so Sentinel can own the cache. */
|
|
920
1010
|
interface ForbiddenInodeCacheRef {
|
|
921
1011
|
getOrBuild: () => Set<bigint>;
|
|
@@ -1229,6 +1319,7 @@ declare class SentinelRunner {
|
|
|
1229
1319
|
private _hookEngine?;
|
|
1230
1320
|
private _maturityConfig?;
|
|
1231
1321
|
private _validatorOptions?;
|
|
1322
|
+
private _sensitivityScorer?;
|
|
1232
1323
|
private intentTracker;
|
|
1233
1324
|
private similarityEngine;
|
|
1234
1325
|
private recentAlignmentScores;
|
|
@@ -1249,6 +1340,12 @@ declare class SentinelRunner {
|
|
|
1249
1340
|
setMaturityConfig(config: Partial<BaselineMaturityConfig>): void;
|
|
1250
1341
|
/** Set RoleValidator options (exception approval fn, audit callback). */
|
|
1251
1342
|
setRoleValidatorOptions(options: RoleValidatorOptions): void;
|
|
1343
|
+
/**
|
|
1344
|
+
* Inject the facade's shared sensitivity scorer (overlay + repo map aware)
|
|
1345
|
+
* so processEvent's validator scores identically to the facade's check().
|
|
1346
|
+
* Must be called BEFORE start() builds the validator.
|
|
1347
|
+
*/
|
|
1348
|
+
setSensitivityScorer(scorer: TargetSensitivityScorer): void;
|
|
1252
1349
|
/** Set the behavioral baseline and start periodic gap checking. */
|
|
1253
1350
|
setBaseline(baseline: AgentBaseline): void;
|
|
1254
1351
|
/** Declare a new task intent for this agent. */
|
|
@@ -1265,6 +1362,12 @@ declare class SentinelRunner {
|
|
|
1265
1362
|
getActiveTask(): TaskIntent | null;
|
|
1266
1363
|
/** Expose the similarity engine for pre-execution intent checks. */
|
|
1267
1364
|
getSimilarityEngine(): SimilarityEngine;
|
|
1365
|
+
/**
|
|
1366
|
+
* Read-only view of the rolling misaligned-score window, for the
|
|
1367
|
+
* pre-execution gate (Sentinel.check) to apply the SAME sustained-drift
|
|
1368
|
+
* severity upgrade as the recording path — without mutating the window.
|
|
1369
|
+
*/
|
|
1370
|
+
getRecentAlignmentScores(): readonly number[];
|
|
1268
1371
|
/**
|
|
1269
1372
|
* Side-effect-free pre-execution gate: evaluate an event and fire pre_execution
|
|
1270
1373
|
* hooks WITHOUT running processEvent's full pipeline.
|
|
@@ -16,6 +16,9 @@ var LogAdapter = class {
|
|
|
16
16
|
pendingFragment = "";
|
|
17
17
|
running = false;
|
|
18
18
|
polling = false;
|
|
19
|
+
/** The in-flight poll (if any), so stop() can let it finish emitting its
|
|
20
|
+
* already-read batch before persisting the cursor. */
|
|
21
|
+
pollInFlight = null;
|
|
19
22
|
pollTimer = null;
|
|
20
23
|
onEvent = null;
|
|
21
24
|
readExisting;
|
|
@@ -33,6 +36,7 @@ var LogAdapter = class {
|
|
|
33
36
|
if (this.running) return;
|
|
34
37
|
this.onEvent = onEvent;
|
|
35
38
|
this.running = true;
|
|
39
|
+
this.pendingFragment = "";
|
|
36
40
|
const savedPosition = await this.loadCursor();
|
|
37
41
|
if (savedPosition !== null) {
|
|
38
42
|
this.lastReadPosition = savedPosition;
|
|
@@ -47,7 +51,9 @@ var LogAdapter = class {
|
|
|
47
51
|
} else {
|
|
48
52
|
this.lastReadPosition = 0;
|
|
49
53
|
}
|
|
50
|
-
this.pollTimer = setInterval(() =>
|
|
54
|
+
this.pollTimer = setInterval(() => {
|
|
55
|
+
this.pollInFlight = this.poll();
|
|
56
|
+
}, this.pollIntervalMs);
|
|
51
57
|
console.log(`LogAdapter watching ${this.logPath} for agent ${this.agentId}`);
|
|
52
58
|
}
|
|
53
59
|
async stop() {
|
|
@@ -55,6 +61,13 @@ var LogAdapter = class {
|
|
|
55
61
|
clearInterval(this.pollTimer);
|
|
56
62
|
this.pollTimer = null;
|
|
57
63
|
}
|
|
64
|
+
if (this.pollInFlight) {
|
|
65
|
+
try {
|
|
66
|
+
await this.pollInFlight;
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
this.pollInFlight = null;
|
|
70
|
+
}
|
|
58
71
|
await this.saveCursor();
|
|
59
72
|
this.running = false;
|
|
60
73
|
this.onEvent = null;
|
|
@@ -66,7 +79,6 @@ var LogAdapter = class {
|
|
|
66
79
|
try {
|
|
67
80
|
const lines = await this.readNewLines();
|
|
68
81
|
for (const line of lines) {
|
|
69
|
-
if (!this.running) break;
|
|
70
82
|
const event = this.parseLine(line);
|
|
71
83
|
if (event) {
|
|
72
84
|
try {
|
|
@@ -194,10 +206,9 @@ var LogAdapter = class {
|
|
|
194
206
|
try {
|
|
195
207
|
const { writeFile, mkdir } = await import("fs/promises");
|
|
196
208
|
await mkdir(this.stateDir, { recursive: true });
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
);
|
|
209
|
+
const fragmentBytes = Buffer.byteLength(this.pendingFragment, "utf-8");
|
|
210
|
+
const position = Math.max(0, this.lastReadPosition - fragmentBytes);
|
|
211
|
+
await writeFile(path, JSON.stringify({ position, logPath: this.logPath }) + "\n");
|
|
201
212
|
} catch (err) {
|
|
202
213
|
console.warn(`Failed to save log cursor: ${err}`);
|
|
203
214
|
}
|
|
@@ -235,4 +246,4 @@ var LogAdapter = class {
|
|
|
235
246
|
export {
|
|
236
247
|
LogAdapter
|
|
237
248
|
};
|
|
238
|
-
//# sourceMappingURL=chunk-
|
|
249
|
+
//# sourceMappingURL=chunk-2IPSTUNH.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/setup/policyDiscovery.ts
|
|
2
|
+
import { existsSync, statSync } from "fs";
|
|
3
|
+
import { resolve, join, dirname } from "path";
|
|
4
|
+
function isTrustedPolicyStat(stats, processUid) {
|
|
5
|
+
if ((stats.mode & 2) !== 0) {
|
|
6
|
+
return { trusted: false, reason: "file is world-writable" };
|
|
7
|
+
}
|
|
8
|
+
if (processUid !== void 0 && stats.uid !== processUid) {
|
|
9
|
+
return {
|
|
10
|
+
trusted: false,
|
|
11
|
+
reason: `file is owned by uid ${stats.uid}, not the current user (uid ${processUid})`
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return { trusted: true, reason: null };
|
|
15
|
+
}
|
|
16
|
+
function isTrustedOnDisk(candidate) {
|
|
17
|
+
if (process.platform === "win32") return true;
|
|
18
|
+
let stats;
|
|
19
|
+
try {
|
|
20
|
+
stats = statSync(candidate);
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const processUid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
25
|
+
const verdict = isTrustedPolicyStat({ uid: stats.uid, mode: stats.mode }, processUid);
|
|
26
|
+
if (!verdict.trusted) {
|
|
27
|
+
console.warn(`[Sentinel] Ignoring untrusted policy file ${candidate}: ${verdict.reason}`);
|
|
28
|
+
}
|
|
29
|
+
return verdict.trusted;
|
|
30
|
+
}
|
|
31
|
+
function discoverPolicy(startDir, home) {
|
|
32
|
+
let dir = resolve(startDir);
|
|
33
|
+
const homeAbs = resolve(home);
|
|
34
|
+
while (true) {
|
|
35
|
+
const candidate = join(dir, ".sentinel.yaml");
|
|
36
|
+
if (existsSync(candidate) && isTrustedOnDisk(candidate)) return candidate;
|
|
37
|
+
if (dir === homeAbs) return null;
|
|
38
|
+
const parent = dirname(dir);
|
|
39
|
+
if (parent === dir) return null;
|
|
40
|
+
dir = parent;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
discoverPolicy
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=chunk-B6S2PBS4.js.map
|