@sanctuary-framework/mcp-server 1.2.8 → 1.2.10
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/cli.cjs +470 -2
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +470 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +448 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +82 -66
- package/dist/index.d.ts +82 -66
- package/dist/index.js +448 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -21787,6 +21787,11 @@ var init_sentinel_dispatcher = __esm({
|
|
|
21787
21787
|
fortressId: this.fortressId,
|
|
21788
21788
|
auditLog: this.auditLog,
|
|
21789
21789
|
now: this.now,
|
|
21790
|
+
// Phi-5 meta-sentinel reads the per-fortress finding store to
|
|
21791
|
+
// detect patterns across other sentinels' findings. First-order
|
|
21792
|
+
// sentinels ignore the field; the dispatcher always attaches it
|
|
21793
|
+
// because the store is already in scope here.
|
|
21794
|
+
findingStore: this.findingStore,
|
|
21790
21795
|
...contextOverrides ?? {}
|
|
21791
21796
|
};
|
|
21792
21797
|
const sentinel = await this.registry.subscribe(sentinelId, context);
|
|
@@ -21928,6 +21933,230 @@ var init_sentinel_dispatcher = __esm({
|
|
|
21928
21933
|
}
|
|
21929
21934
|
});
|
|
21930
21935
|
|
|
21936
|
+
// src/anomaly-detection/types.ts
|
|
21937
|
+
var init_types4 = __esm({
|
|
21938
|
+
"src/anomaly-detection/types.ts"() {
|
|
21939
|
+
}
|
|
21940
|
+
});
|
|
21941
|
+
var ANOMALY_AUDIT_OPS, DEFAULT_TICK_INTERVAL_MS2, AnomalyPipelineDispatcher;
|
|
21942
|
+
var init_anomaly_pipeline = __esm({
|
|
21943
|
+
"src/anomaly-detection/anomaly-pipeline.ts"() {
|
|
21944
|
+
init_types4();
|
|
21945
|
+
ANOMALY_AUDIT_OPS = {
|
|
21946
|
+
DETECTOR_REGISTERED: "anomaly_detector_registered",
|
|
21947
|
+
DETECTOR_UNREGISTERED: "anomaly_detector_unregistered",
|
|
21948
|
+
FINDING_EMITTED: "anomaly_finding_emitted",
|
|
21949
|
+
EVALUATION_FAILED: "anomaly_evaluation_failed",
|
|
21950
|
+
TRAINING_COMPLETED: "anomaly_training_completed",
|
|
21951
|
+
TRAINING_FAILED: "anomaly_training_failed"
|
|
21952
|
+
};
|
|
21953
|
+
DEFAULT_TICK_INTERVAL_MS2 = 6e4;
|
|
21954
|
+
AnomalyPipelineDispatcher = class {
|
|
21955
|
+
findingStore;
|
|
21956
|
+
auditLog;
|
|
21957
|
+
storage;
|
|
21958
|
+
masterKey;
|
|
21959
|
+
fortressId;
|
|
21960
|
+
identityId;
|
|
21961
|
+
now;
|
|
21962
|
+
tickIntervalMs;
|
|
21963
|
+
detectors = /* @__PURE__ */ new Map();
|
|
21964
|
+
listeners = /* @__PURE__ */ new Set();
|
|
21965
|
+
tickTimer = null;
|
|
21966
|
+
tickInFlight = false;
|
|
21967
|
+
constructor(deps) {
|
|
21968
|
+
this.findingStore = deps.findingStore;
|
|
21969
|
+
this.auditLog = deps.auditLog;
|
|
21970
|
+
this.storage = deps.storage;
|
|
21971
|
+
this.masterKey = deps.masterKey;
|
|
21972
|
+
this.fortressId = deps.fortressId;
|
|
21973
|
+
this.identityId = deps.identityId;
|
|
21974
|
+
this.now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
21975
|
+
this.tickIntervalMs = deps.tickIntervalMs ?? DEFAULT_TICK_INTERVAL_MS2;
|
|
21976
|
+
}
|
|
21977
|
+
onEvent(listener) {
|
|
21978
|
+
this.listeners.add(listener);
|
|
21979
|
+
return () => this.listeners.delete(listener);
|
|
21980
|
+
}
|
|
21981
|
+
/**
|
|
21982
|
+
* Register + subscribe a detector to this fortress. Idempotent: a
|
|
21983
|
+
* second call with the same detectorId returns the already-
|
|
21984
|
+
* registered instance without re-subscribing.
|
|
21985
|
+
*/
|
|
21986
|
+
async registerDetector(detector) {
|
|
21987
|
+
const existing = this.detectors.get(detector.detectorId);
|
|
21988
|
+
if (existing) return existing;
|
|
21989
|
+
const context = {
|
|
21990
|
+
fortressId: this.fortressId,
|
|
21991
|
+
auditLog: this.auditLog,
|
|
21992
|
+
storage: this.storage,
|
|
21993
|
+
masterKey: this.masterKey,
|
|
21994
|
+
now: this.now
|
|
21995
|
+
};
|
|
21996
|
+
await detector.subscribe(context);
|
|
21997
|
+
this.detectors.set(detector.detectorId, detector);
|
|
21998
|
+
this.auditLog.append(
|
|
21999
|
+
"l2",
|
|
22000
|
+
ANOMALY_AUDIT_OPS.DETECTOR_REGISTERED,
|
|
22001
|
+
this.identityId,
|
|
22002
|
+
{ detector_id: detector.detectorId, fortress_id: this.fortressId }
|
|
22003
|
+
);
|
|
22004
|
+
return detector;
|
|
22005
|
+
}
|
|
22006
|
+
/**
|
|
22007
|
+
* Unregister + tear down a detector. Idempotent. Returns true when
|
|
22008
|
+
* an active registration was removed.
|
|
22009
|
+
*/
|
|
22010
|
+
async unregisterDetector(detectorId) {
|
|
22011
|
+
const detector = this.detectors.get(detectorId);
|
|
22012
|
+
if (!detector) return false;
|
|
22013
|
+
try {
|
|
22014
|
+
await detector.unsubscribe();
|
|
22015
|
+
} finally {
|
|
22016
|
+
this.detectors.delete(detectorId);
|
|
22017
|
+
}
|
|
22018
|
+
this.auditLog.append(
|
|
22019
|
+
"l2",
|
|
22020
|
+
ANOMALY_AUDIT_OPS.DETECTOR_UNREGISTERED,
|
|
22021
|
+
this.identityId,
|
|
22022
|
+
{ detector_id: detectorId, fortress_id: this.fortressId }
|
|
22023
|
+
);
|
|
22024
|
+
return true;
|
|
22025
|
+
}
|
|
22026
|
+
listDetectors() {
|
|
22027
|
+
return [...this.detectors.keys()];
|
|
22028
|
+
}
|
|
22029
|
+
/** Run one evaluation pass over every registered detector. */
|
|
22030
|
+
async tick() {
|
|
22031
|
+
if (this.tickInFlight) return [];
|
|
22032
|
+
this.tickInFlight = true;
|
|
22033
|
+
try {
|
|
22034
|
+
const findings = [];
|
|
22035
|
+
for (const [detectorId, detector] of this.detectors.entries()) {
|
|
22036
|
+
try {
|
|
22037
|
+
const detectorFindings = await detector.evaluate();
|
|
22038
|
+
for (const raw of detectorFindings) {
|
|
22039
|
+
const stamped = await this.routeFinding(detectorId, raw);
|
|
22040
|
+
findings.push(stamped);
|
|
22041
|
+
}
|
|
22042
|
+
try {
|
|
22043
|
+
const trainingResult = await detector.classifier.train();
|
|
22044
|
+
this.auditLog.append(
|
|
22045
|
+
"l2",
|
|
22046
|
+
ANOMALY_AUDIT_OPS.TRAINING_COMPLETED,
|
|
22047
|
+
this.identityId,
|
|
22048
|
+
{
|
|
22049
|
+
detector_id: detectorId,
|
|
22050
|
+
classifier_id: detector.classifier.classifierId,
|
|
22051
|
+
trained_at: trainingResult.trained_at,
|
|
22052
|
+
sample_count: trainingResult.sample_count,
|
|
22053
|
+
agent_count: trainingResult.agent_count,
|
|
22054
|
+
fortress_id: this.fortressId
|
|
22055
|
+
}
|
|
22056
|
+
);
|
|
22057
|
+
} catch (trainErr) {
|
|
22058
|
+
const message = trainErr instanceof Error ? trainErr.message : String(trainErr);
|
|
22059
|
+
this.auditLog.append(
|
|
22060
|
+
"l2",
|
|
22061
|
+
ANOMALY_AUDIT_OPS.TRAINING_FAILED,
|
|
22062
|
+
this.identityId,
|
|
22063
|
+
{
|
|
22064
|
+
detector_id: detectorId,
|
|
22065
|
+
error_message: message,
|
|
22066
|
+
fortress_id: this.fortressId
|
|
22067
|
+
},
|
|
22068
|
+
"failure"
|
|
22069
|
+
);
|
|
22070
|
+
}
|
|
22071
|
+
} catch (err) {
|
|
22072
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
22073
|
+
const observedAt = this.now().toISOString();
|
|
22074
|
+
this.auditLog.append(
|
|
22075
|
+
"l2",
|
|
22076
|
+
ANOMALY_AUDIT_OPS.EVALUATION_FAILED,
|
|
22077
|
+
this.identityId,
|
|
22078
|
+
{
|
|
22079
|
+
detector_id: detectorId,
|
|
22080
|
+
error_message: message,
|
|
22081
|
+
fortress_id: this.fortressId
|
|
22082
|
+
},
|
|
22083
|
+
"failure"
|
|
22084
|
+
);
|
|
22085
|
+
this.emit({
|
|
22086
|
+
type: "evaluation_failed",
|
|
22087
|
+
detector_id: detectorId,
|
|
22088
|
+
error_message: message,
|
|
22089
|
+
observed_at: observedAt
|
|
22090
|
+
});
|
|
22091
|
+
}
|
|
22092
|
+
}
|
|
22093
|
+
return findings;
|
|
22094
|
+
} finally {
|
|
22095
|
+
this.tickInFlight = false;
|
|
22096
|
+
}
|
|
22097
|
+
}
|
|
22098
|
+
start() {
|
|
22099
|
+
if (this.tickTimer !== null) return;
|
|
22100
|
+
if (this.tickIntervalMs <= 0) return;
|
|
22101
|
+
this.tickTimer = setInterval(() => {
|
|
22102
|
+
void this.tick();
|
|
22103
|
+
}, this.tickIntervalMs);
|
|
22104
|
+
if (typeof this.tickTimer.unref === "function") {
|
|
22105
|
+
this.tickTimer.unref();
|
|
22106
|
+
}
|
|
22107
|
+
}
|
|
22108
|
+
stop() {
|
|
22109
|
+
if (this.tickTimer === null) return;
|
|
22110
|
+
clearInterval(this.tickTimer);
|
|
22111
|
+
this.tickTimer = null;
|
|
22112
|
+
}
|
|
22113
|
+
async dispose() {
|
|
22114
|
+
this.stop();
|
|
22115
|
+
const ids = [...this.detectors.keys()];
|
|
22116
|
+
for (const id of ids) {
|
|
22117
|
+
try {
|
|
22118
|
+
await this.unregisterDetector(id);
|
|
22119
|
+
} catch {
|
|
22120
|
+
}
|
|
22121
|
+
}
|
|
22122
|
+
this.listeners.clear();
|
|
22123
|
+
}
|
|
22124
|
+
async routeFinding(detectorId, raw) {
|
|
22125
|
+
const stamped = {
|
|
22126
|
+
...raw,
|
|
22127
|
+
finding_id: raw.finding_id || randomUUID(),
|
|
22128
|
+
fortress_id: this.fortressId,
|
|
22129
|
+
observed_at: raw.observed_at || this.now().toISOString()
|
|
22130
|
+
};
|
|
22131
|
+
await this.findingStore.saveFinding(stamped);
|
|
22132
|
+
this.auditLog.append(
|
|
22133
|
+
"l2",
|
|
22134
|
+
ANOMALY_AUDIT_OPS.FINDING_EMITTED,
|
|
22135
|
+
this.identityId,
|
|
22136
|
+
{
|
|
22137
|
+
detector_id: detectorId,
|
|
22138
|
+
finding_id: stamped.finding_id,
|
|
22139
|
+
severity: stamped.severity,
|
|
22140
|
+
anomaly_score: stamped.details["anomaly_score"] ?? null,
|
|
22141
|
+
...stamped.agent_id !== void 0 ? { agent_id: stamped.agent_id } : {},
|
|
22142
|
+
fortress_id: this.fortressId
|
|
22143
|
+
}
|
|
22144
|
+
);
|
|
22145
|
+
this.emit({ type: "finding", finding: stamped });
|
|
22146
|
+
return stamped;
|
|
22147
|
+
}
|
|
22148
|
+
emit(event) {
|
|
22149
|
+
for (const listener of this.listeners) {
|
|
22150
|
+
try {
|
|
22151
|
+
listener(event);
|
|
22152
|
+
} catch {
|
|
22153
|
+
}
|
|
22154
|
+
}
|
|
22155
|
+
}
|
|
22156
|
+
};
|
|
22157
|
+
}
|
|
22158
|
+
});
|
|
22159
|
+
|
|
21931
22160
|
// src/sentinel/sentinel.ts
|
|
21932
22161
|
var Sentinel;
|
|
21933
22162
|
var init_sentinel = __esm({
|
|
@@ -23097,6 +23326,229 @@ var init_suspicious_tool_call_detector = __esm({
|
|
|
23097
23326
|
}
|
|
23098
23327
|
});
|
|
23099
23328
|
|
|
23329
|
+
// src/sentinel/sentinels/anomaly-trigger.ts
|
|
23330
|
+
function computeCompoundFindings(windowZero, now) {
|
|
23331
|
+
const byAgent = /* @__PURE__ */ new Map();
|
|
23332
|
+
for (const f of windowZero) {
|
|
23333
|
+
if (!f.agent_id) continue;
|
|
23334
|
+
if (!isWarnOrAlert(f.severity)) continue;
|
|
23335
|
+
let bucket = byAgent.get(f.agent_id);
|
|
23336
|
+
if (!bucket) {
|
|
23337
|
+
bucket = [];
|
|
23338
|
+
byAgent.set(f.agent_id, bucket);
|
|
23339
|
+
}
|
|
23340
|
+
bucket.push(f);
|
|
23341
|
+
}
|
|
23342
|
+
const out = [];
|
|
23343
|
+
for (const [agentId, group] of byAgent.entries()) {
|
|
23344
|
+
const distinctSentinels = new Set(group.map((f) => f.sentinel_id));
|
|
23345
|
+
if (distinctSentinels.size < COMPOUND_TRIGGER_MIN_SENTINELS) continue;
|
|
23346
|
+
const contributingSentinels = [...distinctSentinels].sort();
|
|
23347
|
+
const evidence = group.map((f) => f.finding_id).filter((id) => id.length > 0).slice(0, 50);
|
|
23348
|
+
const summary = `${agentId} agent triggered ${distinctSentinels.size} distinct sentinels in the last 24h: ${contributingSentinels.join(", ")}. Compound suspicious behavior; review the contributing findings.`;
|
|
23349
|
+
out.push({
|
|
23350
|
+
finding_id: "",
|
|
23351
|
+
sentinel_id: ANOMALY_TRIGGER_SENTINEL_ID,
|
|
23352
|
+
severity: "alert",
|
|
23353
|
+
agent_id: agentId,
|
|
23354
|
+
summary,
|
|
23355
|
+
details: {
|
|
23356
|
+
trigger: "compound",
|
|
23357
|
+
agent_id: agentId,
|
|
23358
|
+
contributing_sentinels: contributingSentinels,
|
|
23359
|
+
contributing_finding_count: group.length
|
|
23360
|
+
},
|
|
23361
|
+
observed_at: now.toISOString(),
|
|
23362
|
+
evidence_audit_ids: evidence,
|
|
23363
|
+
fortress_id: ""
|
|
23364
|
+
});
|
|
23365
|
+
}
|
|
23366
|
+
return out;
|
|
23367
|
+
}
|
|
23368
|
+
function computeCountSpikeFinding(windowed, now) {
|
|
23369
|
+
const currentCount = (windowed[0] ?? []).length;
|
|
23370
|
+
const baselineCounts = [];
|
|
23371
|
+
for (let i = 1; i <= BASELINE_WINDOWS5; i += 1) {
|
|
23372
|
+
baselineCounts.push((windowed[i] ?? []).length);
|
|
23373
|
+
}
|
|
23374
|
+
const populated = baselineCounts.filter((c) => c > 0).length;
|
|
23375
|
+
if (populated < BASELINE_WINDOWS5) {
|
|
23376
|
+
return null;
|
|
23377
|
+
}
|
|
23378
|
+
const mean = baselineCounts.reduce((sum, c) => sum + c, 0) / baselineCounts.length;
|
|
23379
|
+
const variance = baselineCounts.reduce((sum, c) => sum + (c - mean) ** 2, 0) / baselineCounts.length;
|
|
23380
|
+
const stddev = Math.sqrt(variance);
|
|
23381
|
+
const warnThreshold = mean + WARN_SIGMA5 * stddev;
|
|
23382
|
+
const alertThreshold = mean + ALERT_SIGMA5 * stddev;
|
|
23383
|
+
if (currentCount > alertThreshold) {
|
|
23384
|
+
return buildCountFinding(
|
|
23385
|
+
currentCount,
|
|
23386
|
+
mean,
|
|
23387
|
+
stddev,
|
|
23388
|
+
ALERT_SIGMA5,
|
|
23389
|
+
"alert",
|
|
23390
|
+
windowed[0] ?? [],
|
|
23391
|
+
now
|
|
23392
|
+
);
|
|
23393
|
+
}
|
|
23394
|
+
if (currentCount > warnThreshold) {
|
|
23395
|
+
return buildCountFinding(
|
|
23396
|
+
currentCount,
|
|
23397
|
+
mean,
|
|
23398
|
+
stddev,
|
|
23399
|
+
WARN_SIGMA5,
|
|
23400
|
+
"warn",
|
|
23401
|
+
windowed[0] ?? [],
|
|
23402
|
+
now
|
|
23403
|
+
);
|
|
23404
|
+
}
|
|
23405
|
+
return null;
|
|
23406
|
+
}
|
|
23407
|
+
function buildCountFinding(currentCount, mean, stddev, sigma, severity, windowZero, now) {
|
|
23408
|
+
const ratio = mean === 0 ? Number.POSITIVE_INFINITY : currentCount / mean;
|
|
23409
|
+
const ratioStr = Number.isFinite(ratio) ? `${ratio.toFixed(1)}x normal` : "no prior baseline";
|
|
23410
|
+
const summary = `Fortress finding count is ${ratioStr}: ${currentCount} findings in last 24h, baseline ${mean.toFixed(1)} +/- ${stddev.toFixed(1)}. Crossed +${sigma} sigma threshold across all sentinels.`;
|
|
23411
|
+
const evidence = windowZero.map((f) => f.finding_id).filter((id) => id.length > 0).slice(0, 50);
|
|
23412
|
+
return {
|
|
23413
|
+
finding_id: "",
|
|
23414
|
+
sentinel_id: ANOMALY_TRIGGER_SENTINEL_ID,
|
|
23415
|
+
severity,
|
|
23416
|
+
summary,
|
|
23417
|
+
details: {
|
|
23418
|
+
trigger: "count_spike",
|
|
23419
|
+
current_count: currentCount,
|
|
23420
|
+
baseline_mean: mean,
|
|
23421
|
+
baseline_stddev: stddev,
|
|
23422
|
+
sigma_threshold: sigma,
|
|
23423
|
+
ratio: Number.isFinite(ratio) ? ratio : null
|
|
23424
|
+
},
|
|
23425
|
+
observed_at: now.toISOString(),
|
|
23426
|
+
evidence_audit_ids: evidence,
|
|
23427
|
+
fortress_id: ""
|
|
23428
|
+
};
|
|
23429
|
+
}
|
|
23430
|
+
function computeNovelComboFinding(windowed, now) {
|
|
23431
|
+
const distinctByWindow = [];
|
|
23432
|
+
for (let i = 0; i <= BASELINE_WINDOWS5; i += 1) {
|
|
23433
|
+
const set = /* @__PURE__ */ new Set();
|
|
23434
|
+
for (const f of windowed[i] ?? []) {
|
|
23435
|
+
set.add(f.sentinel_id);
|
|
23436
|
+
}
|
|
23437
|
+
distinctByWindow.push(set);
|
|
23438
|
+
}
|
|
23439
|
+
const populatedBaselineWindows = distinctByWindow.slice(1).filter((s) => s.size > 0).length;
|
|
23440
|
+
if (populatedBaselineWindows < BASELINE_WINDOWS5) {
|
|
23441
|
+
return null;
|
|
23442
|
+
}
|
|
23443
|
+
const currentCombo = distinctByWindow[0];
|
|
23444
|
+
if (currentCombo.size < 2) return null;
|
|
23445
|
+
const currentKey = comboKey(currentCombo);
|
|
23446
|
+
for (let i = 1; i <= BASELINE_WINDOWS5; i += 1) {
|
|
23447
|
+
if (comboKey(distinctByWindow[i]) === currentKey) {
|
|
23448
|
+
return null;
|
|
23449
|
+
}
|
|
23450
|
+
}
|
|
23451
|
+
const sentinelIds = [...currentCombo].sort();
|
|
23452
|
+
const summary = `Novel sentinel-ID combination this 24h window: ${sentinelIds.join(" + ")}. This co-occurrence pattern has not appeared in the prior ${BASELINE_WINDOWS5} baseline windows.`;
|
|
23453
|
+
const evidence = (windowed[0] ?? []).map((f) => f.finding_id).filter((id) => id.length > 0).slice(0, 50);
|
|
23454
|
+
return {
|
|
23455
|
+
finding_id: "",
|
|
23456
|
+
sentinel_id: ANOMALY_TRIGGER_SENTINEL_ID,
|
|
23457
|
+
severity: "info",
|
|
23458
|
+
summary,
|
|
23459
|
+
details: {
|
|
23460
|
+
trigger: "novel_combo",
|
|
23461
|
+
sentinel_ids: sentinelIds,
|
|
23462
|
+
baseline_window_count: BASELINE_WINDOWS5
|
|
23463
|
+
},
|
|
23464
|
+
observed_at: now.toISOString(),
|
|
23465
|
+
evidence_audit_ids: evidence,
|
|
23466
|
+
fortress_id: ""
|
|
23467
|
+
};
|
|
23468
|
+
}
|
|
23469
|
+
function isWarnOrAlert(s) {
|
|
23470
|
+
return s === "warn" || s === "alert";
|
|
23471
|
+
}
|
|
23472
|
+
function bucketByWindow(findings, nowMs) {
|
|
23473
|
+
const buckets = Array.from(
|
|
23474
|
+
{ length: BASELINE_WINDOWS5 + 1 },
|
|
23475
|
+
() => []
|
|
23476
|
+
);
|
|
23477
|
+
for (const f of findings) {
|
|
23478
|
+
const ts = Date.parse(f.observed_at);
|
|
23479
|
+
if (!Number.isFinite(ts)) continue;
|
|
23480
|
+
const age = nowMs - ts;
|
|
23481
|
+
if (age < 0) continue;
|
|
23482
|
+
const idx = Math.floor(age / WINDOW_MS);
|
|
23483
|
+
if (idx > BASELINE_WINDOWS5) continue;
|
|
23484
|
+
buckets[idx].push(f);
|
|
23485
|
+
}
|
|
23486
|
+
return buckets;
|
|
23487
|
+
}
|
|
23488
|
+
function comboKey(set) {
|
|
23489
|
+
return [...set].sort().join("|");
|
|
23490
|
+
}
|
|
23491
|
+
var ANOMALY_TRIGGER_SENTINEL_ID, WARN_SIGMA5, ALERT_SIGMA5, BASELINE_WINDOWS5, QUERY_LIMIT5, WINDOW_MS, COMPOUND_TRIGGER_MIN_SENTINELS, AnomalyTriggerWatcher;
|
|
23492
|
+
var init_anomaly_trigger = __esm({
|
|
23493
|
+
"src/sentinel/sentinels/anomaly-trigger.ts"() {
|
|
23494
|
+
init_sentinel();
|
|
23495
|
+
ANOMALY_TRIGGER_SENTINEL_ID = "anomaly-trigger";
|
|
23496
|
+
WARN_SIGMA5 = 3;
|
|
23497
|
+
ALERT_SIGMA5 = 6;
|
|
23498
|
+
BASELINE_WINDOWS5 = 7;
|
|
23499
|
+
QUERY_LIMIT5 = 5e3;
|
|
23500
|
+
WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
23501
|
+
COMPOUND_TRIGGER_MIN_SENTINELS = 2;
|
|
23502
|
+
AnomalyTriggerWatcher = class extends Sentinel {
|
|
23503
|
+
sentinelId = ANOMALY_TRIGGER_SENTINEL_ID;
|
|
23504
|
+
description = "Meta-sentinel. Watches for patterns ACROSS other sentinels' findings: compound suspicious behavior on one agent, fortress-wide finding-count spikes, and novel cross-sentinel combinations. Closes WP-V1.3-1 Sentinel Baseline Pack.";
|
|
23505
|
+
async subscribe(context) {
|
|
23506
|
+
if (!context.findingStore) {
|
|
23507
|
+
throw new Error(
|
|
23508
|
+
`${ANOMALY_TRIGGER_SENTINEL_ID}: findingStore missing from SentinelContext; this meta-sentinel requires the Phi-1 finding store`
|
|
23509
|
+
);
|
|
23510
|
+
}
|
|
23511
|
+
await super.subscribe(context);
|
|
23512
|
+
}
|
|
23513
|
+
async evaluate() {
|
|
23514
|
+
const ctx = this.requireContext();
|
|
23515
|
+
const findingStore = ctx.findingStore;
|
|
23516
|
+
if (!findingStore) {
|
|
23517
|
+
return [];
|
|
23518
|
+
}
|
|
23519
|
+
const now = ctx.now();
|
|
23520
|
+
const nowMs = now.getTime();
|
|
23521
|
+
const windowSpanMs = (BASELINE_WINDOWS5 + 1) * WINDOW_MS;
|
|
23522
|
+
const sinceIso = new Date(nowMs - windowSpanMs).toISOString();
|
|
23523
|
+
let findings;
|
|
23524
|
+
try {
|
|
23525
|
+
findings = await findingStore.listFindings({
|
|
23526
|
+
since: sinceIso,
|
|
23527
|
+
limit: QUERY_LIMIT5
|
|
23528
|
+
});
|
|
23529
|
+
} catch {
|
|
23530
|
+
return [];
|
|
23531
|
+
}
|
|
23532
|
+
const firstOrderFindings = findings.filter(
|
|
23533
|
+
(f) => f.sentinel_id !== ANOMALY_TRIGGER_SENTINEL_ID
|
|
23534
|
+
);
|
|
23535
|
+
const windowed = bucketByWindow(firstOrderFindings, nowMs);
|
|
23536
|
+
const out = [];
|
|
23537
|
+
const compoundFindings = computeCompoundFindings(
|
|
23538
|
+
windowed[0] ?? [],
|
|
23539
|
+
now
|
|
23540
|
+
);
|
|
23541
|
+
out.push(...compoundFindings);
|
|
23542
|
+
const countSpikeFinding = computeCountSpikeFinding(windowed, now);
|
|
23543
|
+
if (countSpikeFinding) out.push(countSpikeFinding);
|
|
23544
|
+
const novelComboFinding = computeNovelComboFinding(windowed, now);
|
|
23545
|
+
if (novelComboFinding) out.push(novelComboFinding);
|
|
23546
|
+
return out;
|
|
23547
|
+
}
|
|
23548
|
+
};
|
|
23549
|
+
}
|
|
23550
|
+
});
|
|
23551
|
+
|
|
23100
23552
|
// src/sentinel/sentinels/index.ts
|
|
23101
23553
|
var PHI1_BASELINE_CATALOG;
|
|
23102
23554
|
var init_sentinels = __esm({
|
|
@@ -23105,6 +23557,7 @@ var init_sentinels = __esm({
|
|
|
23105
23557
|
init_cross_agent_chatter_watcher();
|
|
23106
23558
|
init_credential_usage_watcher();
|
|
23107
23559
|
init_suspicious_tool_call_detector();
|
|
23560
|
+
init_anomaly_trigger();
|
|
23108
23561
|
PHI1_BASELINE_CATALOG = [
|
|
23109
23562
|
{
|
|
23110
23563
|
sentinelId: EGRESS_VOLUME_SENTINEL_ID,
|
|
@@ -23125,6 +23578,11 @@ var init_sentinels = __esm({
|
|
|
23125
23578
|
sentinelId: SUSPICIOUS_TOOL_CALL_SENTINEL_ID,
|
|
23126
23579
|
description: "Surfaces tool calls whose argument shape, call frequency, or permission combination looks unusual for the fortress's recent history.",
|
|
23127
23580
|
factory: () => new SuspiciousToolCallDetector()
|
|
23581
|
+
},
|
|
23582
|
+
{
|
|
23583
|
+
sentinelId: ANOMALY_TRIGGER_SENTINEL_ID,
|
|
23584
|
+
description: "Meta-sentinel. Watches for patterns ACROSS other sentinels' findings: compound suspicious behavior on one agent, fortress-wide finding-count spikes, and novel cross-sentinel combinations. Closes WP-V1.3-1 Sentinel Baseline Pack.",
|
|
23585
|
+
factory: () => new AnomalyTriggerWatcher()
|
|
23128
23586
|
}
|
|
23129
23587
|
];
|
|
23130
23588
|
}
|
|
@@ -34643,7 +35101,7 @@ var init_recovery_key_disclosure = __esm({
|
|
|
34643
35101
|
});
|
|
34644
35102
|
|
|
34645
35103
|
// src/hub/types.ts
|
|
34646
|
-
var
|
|
35104
|
+
var init_types5 = __esm({
|
|
34647
35105
|
"src/hub/types.ts"() {
|
|
34648
35106
|
}
|
|
34649
35107
|
});
|
|
@@ -35682,7 +36140,7 @@ var init_hub = __esm({
|
|
|
35682
36140
|
"src/hub/index.ts"() {
|
|
35683
36141
|
init_constants3();
|
|
35684
36142
|
init_errors4();
|
|
35685
|
-
|
|
36143
|
+
init_types5();
|
|
35686
36144
|
init_agent_registry();
|
|
35687
36145
|
init_inbox_store();
|
|
35688
36146
|
init_inbox_aggregator();
|
|
@@ -42186,6 +42644,15 @@ ${err.message}
|
|
|
42186
42644
|
if (dashboard) {
|
|
42187
42645
|
dashboard.setSentinelDispatcher(sentinelDispatcher);
|
|
42188
42646
|
}
|
|
42647
|
+
const anomalyDispatcher = new AnomalyPipelineDispatcher({
|
|
42648
|
+
findingStore: sentinelFindingStore,
|
|
42649
|
+
auditLog,
|
|
42650
|
+
storage,
|
|
42651
|
+
masterKey,
|
|
42652
|
+
fortressId: fortressIdForAggregator,
|
|
42653
|
+
identityId: aggregatorIdentityId
|
|
42654
|
+
});
|
|
42655
|
+
anomalyDispatcher.start();
|
|
42189
42656
|
const policyTools = createPrincipalPolicyTools(policy, baseline, auditLog);
|
|
42190
42657
|
const { tools: sanctuaryMetaTools } = createSanctuaryTools({
|
|
42191
42658
|
config,
|
|
@@ -42382,6 +42849,7 @@ var init_src = __esm({
|
|
|
42382
42849
|
init_sentinel_finding_store();
|
|
42383
42850
|
init_sentinel_registry();
|
|
42384
42851
|
init_sentinel_dispatcher();
|
|
42852
|
+
init_anomaly_pipeline();
|
|
42385
42853
|
init_sentinels();
|
|
42386
42854
|
init_subscription_store();
|
|
42387
42855
|
init_tools4();
|