@mneme-ai/core 1.70.0 → 1.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/precog/adversarial_mutation.d.ts +39 -0
- package/dist/precog/adversarial_mutation.d.ts.map +1 -0
- package/dist/precog/adversarial_mutation.js +71 -0
- package/dist/precog/adversarial_mutation.js.map +1 -0
- package/dist/precog/council_mutation.test.d.ts +5 -0
- package/dist/precog/council_mutation.test.d.ts.map +1 -0
- package/dist/precog/council_mutation.test.js +82 -0
- package/dist/precog/council_mutation.test.js.map +1 -0
- package/dist/precog/index.d.ts +4 -0
- package/dist/precog/index.d.ts.map +1 -1
- package/dist/precog/index.js +4 -0
- package/dist/precog/index.js.map +1 -1
- package/dist/precog/multi_voice_council.d.ts +50 -0
- package/dist/precog/multi_voice_council.d.ts.map +1 -0
- package/dist/precog/multi_voice_council.js +105 -0
- package/dist/precog/multi_voice_council.js.map +1 -0
- package/dist/sentinel/audit_ledger.d.ts +46 -0
- package/dist/sentinel/audit_ledger.d.ts.map +1 -0
- package/dist/sentinel/audit_ledger.js +115 -0
- package/dist/sentinel/audit_ledger.js.map +1 -0
- package/dist/sentinel/command_detector.d.ts +59 -0
- package/dist/sentinel/command_detector.d.ts.map +1 -0
- package/dist/sentinel/command_detector.js +265 -0
- package/dist/sentinel/command_detector.js.map +1 -0
- package/dist/sentinel/index.d.ts +43 -0
- package/dist/sentinel/index.d.ts.map +1 -0
- package/dist/sentinel/index.js +105 -0
- package/dist/sentinel/index.js.map +1 -0
- package/dist/sentinel/risk_scorer.d.ts +34 -0
- package/dist/sentinel/risk_scorer.d.ts.map +1 -0
- package/dist/sentinel/risk_scorer.js +92 -0
- package/dist/sentinel/risk_scorer.js.map +1 -0
- package/dist/sentinel/scope_enforcer.d.ts +38 -0
- package/dist/sentinel/scope_enforcer.d.ts.map +1 -0
- package/dist/sentinel/scope_enforcer.js +145 -0
- package/dist/sentinel/scope_enforcer.js.map +1 -0
- package/dist/sentinel/sentinel.d.ts +63 -0
- package/dist/sentinel/sentinel.d.ts.map +1 -0
- package/dist/sentinel/sentinel.js +123 -0
- package/dist/sentinel/sentinel.js.map +1 -0
- package/dist/sentinel/sentinel.test.d.ts +5 -0
- package/dist/sentinel/sentinel.test.d.ts.map +1 -0
- package/dist/sentinel/sentinel.test.js +179 -0
- package/dist/sentinel/sentinel.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1.71.0 -- SENTINEL S3: CONTEXTUAL RISK SCORER.
|
|
3
|
+
*
|
|
4
|
+
* Same command in different contexts has DIFFERENT risk. Examples:
|
|
5
|
+
*
|
|
6
|
+
* rm -rf ./node_modules -> SAFE (well-known cleanup pattern)
|
|
7
|
+
* rm -rf $UNVALIDATED_VAR -> CRITICAL (variable expansion attack)
|
|
8
|
+
* rm -rf /tmp/build-out -> SAFE (well-scoped temp)
|
|
9
|
+
* rm -rf / -> CRITICAL (filesystem wipe)
|
|
10
|
+
*
|
|
11
|
+
* The scorer combines:
|
|
12
|
+
* - Detector match risk (signature catalog)
|
|
13
|
+
* - Scope violation count
|
|
14
|
+
* - Variable-expansion suspicion
|
|
15
|
+
* - Pipe-chain length (longer chains = more attack surface)
|
|
16
|
+
* - Network reach (does it talk to the internet?)
|
|
17
|
+
*
|
|
18
|
+
* Output: composite score 0..100 + recommended action.
|
|
19
|
+
*/
|
|
20
|
+
import { type CommandDetectionReport } from "./command_detector.js";
|
|
21
|
+
import { type ScopeReport } from "./scope_enforcer.js";
|
|
22
|
+
export type RecommendedAction = "ALLOW" | "AUDIT" | "WARN" | "BLOCK";
|
|
23
|
+
export interface RiskScoreReport {
|
|
24
|
+
/** Composite score 0..100; higher = more dangerous. */
|
|
25
|
+
score: number;
|
|
26
|
+
recommendedAction: RecommendedAction;
|
|
27
|
+
/** Score contributions (for transparency). */
|
|
28
|
+
contributions: Record<string, number>;
|
|
29
|
+
detection: CommandDetectionReport;
|
|
30
|
+
scope: ScopeReport;
|
|
31
|
+
headline: string;
|
|
32
|
+
}
|
|
33
|
+
export declare function scoreRisk(repoRoot: string, command: string): RiskScoreReport;
|
|
34
|
+
//# sourceMappingURL=risk_scorer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"risk_scorer.d.ts","sourceRoot":"","sources":["../../src/sentinel/risk_scorer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAmB,KAAK,sBAAsB,EAAkB,MAAM,uBAAuB,CAAC;AACrG,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAErE,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAErE,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,8CAA8C;IAC9C,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,SAAS,EAAE,sBAAsB,CAAC;IAClC,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAWD,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,eAAe,CA8D5E"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1.71.0 -- SENTINEL S3: CONTEXTUAL RISK SCORER.
|
|
3
|
+
*
|
|
4
|
+
* Same command in different contexts has DIFFERENT risk. Examples:
|
|
5
|
+
*
|
|
6
|
+
* rm -rf ./node_modules -> SAFE (well-known cleanup pattern)
|
|
7
|
+
* rm -rf $UNVALIDATED_VAR -> CRITICAL (variable expansion attack)
|
|
8
|
+
* rm -rf /tmp/build-out -> SAFE (well-scoped temp)
|
|
9
|
+
* rm -rf / -> CRITICAL (filesystem wipe)
|
|
10
|
+
*
|
|
11
|
+
* The scorer combines:
|
|
12
|
+
* - Detector match risk (signature catalog)
|
|
13
|
+
* - Scope violation count
|
|
14
|
+
* - Variable-expansion suspicion
|
|
15
|
+
* - Pipe-chain length (longer chains = more attack surface)
|
|
16
|
+
* - Network reach (does it talk to the internet?)
|
|
17
|
+
*
|
|
18
|
+
* Output: composite score 0..100 + recommended action.
|
|
19
|
+
*/
|
|
20
|
+
import { detectDangerous } from "./command_detector.js";
|
|
21
|
+
import { enforceScope } from "./scope_enforcer.js";
|
|
22
|
+
const RISK_TO_POINTS = { low: 10, medium: 25, high: 50, critical: 80 };
|
|
23
|
+
const NETWORK_INDICATORS = [
|
|
24
|
+
/\bcurl\b/, /\bwget\b/, /\bnc\b/, /\bssh\b/, /\bscp\b/, /\brsync\b/,
|
|
25
|
+
/\bnpx\s+\S+\b/, // npx fetches from registry
|
|
26
|
+
];
|
|
27
|
+
const ENV_VAR_EXPANSION = /\$\{?[A-Z_][A-Z0-9_]*\}?/;
|
|
28
|
+
export function scoreRisk(repoRoot, command) {
|
|
29
|
+
const detection = detectDangerous(command);
|
|
30
|
+
const scope = enforceScope(repoRoot, command);
|
|
31
|
+
const contributions = {};
|
|
32
|
+
// 1. Detector hits.
|
|
33
|
+
let detPoints = 0;
|
|
34
|
+
for (const m of detection.matches) {
|
|
35
|
+
detPoints += RISK_TO_POINTS[m.signature.risk];
|
|
36
|
+
}
|
|
37
|
+
contributions["detector"] = Math.min(80, detPoints);
|
|
38
|
+
// 2. Scope violations.
|
|
39
|
+
let scopePoints = 0;
|
|
40
|
+
for (const v of scope.violations) {
|
|
41
|
+
if (v.category === "system")
|
|
42
|
+
scopePoints += 25;
|
|
43
|
+
else if (v.category === "device")
|
|
44
|
+
scopePoints += 40;
|
|
45
|
+
else if (v.category === "network-mount")
|
|
46
|
+
scopePoints += 20;
|
|
47
|
+
else if (v.category === "parent-escape")
|
|
48
|
+
scopePoints += 15;
|
|
49
|
+
else if (v.category === "home-outside-mneme")
|
|
50
|
+
scopePoints += 8;
|
|
51
|
+
}
|
|
52
|
+
contributions["scope"] = Math.min(60, scopePoints);
|
|
53
|
+
// 3. Variable expansion suspicion: each $VAR in destructive context adds risk.
|
|
54
|
+
if (ENV_VAR_EXPANSION.test(command) && /\b(rm|mv|cp|dd|chmod|chown)\b/.test(command)) {
|
|
55
|
+
contributions["unvalidated-var"] = 15;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
contributions["unvalidated-var"] = 0;
|
|
59
|
+
}
|
|
60
|
+
// 4. Pipe chain length.
|
|
61
|
+
const pipeCount = (command.match(/\|/g) ?? []).length;
|
|
62
|
+
contributions["pipe-chain"] = Math.min(20, Math.max(0, (pipeCount - 1) * 5));
|
|
63
|
+
// 5. Network reach.
|
|
64
|
+
let networkPoints = 0;
|
|
65
|
+
for (const re of NETWORK_INDICATORS) {
|
|
66
|
+
if (re.test(command)) {
|
|
67
|
+
networkPoints += 10;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
contributions["network"] = networkPoints;
|
|
72
|
+
// 6. sudo amplifier.
|
|
73
|
+
if (/\bsudo\b/.test(command)) {
|
|
74
|
+
contributions["sudo"] = 15;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
contributions["sudo"] = 0;
|
|
78
|
+
}
|
|
79
|
+
const score = Math.min(100, Object.values(contributions).reduce((a, b) => a + b, 0));
|
|
80
|
+
let recommendedAction;
|
|
81
|
+
if (score >= 70)
|
|
82
|
+
recommendedAction = "BLOCK";
|
|
83
|
+
else if (score >= 45)
|
|
84
|
+
recommendedAction = "WARN";
|
|
85
|
+
else if (score >= 15)
|
|
86
|
+
recommendedAction = "AUDIT";
|
|
87
|
+
else
|
|
88
|
+
recommendedAction = "ALLOW";
|
|
89
|
+
const headline = `Risk ${score}/100 -> ${recommendedAction}. Detector: ${detection.matches.length} hit(s); scope: ${scope.violations.length} violation(s).`;
|
|
90
|
+
return { score, recommendedAction, contributions, detection, scope, headline };
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=risk_scorer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"risk_scorer.js","sourceRoot":"","sources":["../../src/sentinel/risk_scorer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1.71.0 -- SENTINEL S2: REPO-SCOPE BOUNDARY ENFORCER.
|
|
3
|
+
*
|
|
4
|
+
* Even when no danger-pattern matches, a command that touches paths
|
|
5
|
+
* OUTSIDE the user's repo (or ~/.mneme, ~/.cache, etc) is suspect.
|
|
6
|
+
*
|
|
7
|
+
* The wild rule: Mneme's job is to defend ONE repo. If the AI is
|
|
8
|
+
* about to touch /etc, /usr, /var, /sys, /proc, /dev, /root, or any
|
|
9
|
+
* path outside the repo + ~/.{mneme,cache,npm,config,local}, raise
|
|
10
|
+
* an out-of-scope alert.
|
|
11
|
+
*
|
|
12
|
+
* Per-repo scope means: the AI can do anything inside its sandbox,
|
|
13
|
+
* but cannot reach into the operating system's furniture.
|
|
14
|
+
*/
|
|
15
|
+
export interface PathExtraction {
|
|
16
|
+
/** The raw path that appeared. */
|
|
17
|
+
raw: string;
|
|
18
|
+
/** Normalized absolute path (best-effort). */
|
|
19
|
+
resolved: string;
|
|
20
|
+
/** Position in source command. */
|
|
21
|
+
offset: number;
|
|
22
|
+
}
|
|
23
|
+
export interface ScopeViolation {
|
|
24
|
+
path: PathExtraction;
|
|
25
|
+
reason: string;
|
|
26
|
+
/** "system" / "home-outside-mneme" / "device" / "network-mount". */
|
|
27
|
+
category: "system" | "home-outside-mneme" | "device" | "network-mount" | "parent-escape";
|
|
28
|
+
}
|
|
29
|
+
export interface ScopeReport {
|
|
30
|
+
extractedPaths: PathExtraction[];
|
|
31
|
+
violations: ScopeViolation[];
|
|
32
|
+
insideRepo: PathExtraction[];
|
|
33
|
+
insideMnemeHome: PathExtraction[];
|
|
34
|
+
headline: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function extractPaths(command: string): PathExtraction[];
|
|
37
|
+
export declare function enforceScope(repoRoot: string, command: string): ScopeReport;
|
|
38
|
+
//# sourceMappingURL=scope_enforcer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope_enforcer.d.ts","sourceRoot":"","sources":["../../src/sentinel/scope_enforcer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,QAAQ,EAAE,QAAQ,GAAG,oBAAoB,GAAG,QAAQ,GAAG,eAAe,GAAG,eAAe,CAAC;CAC1F;AAED,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,cAAc,EAAE,CAAC;IACjC,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAcD,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CA0B9D;AAsCD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,CAqD3E"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1.71.0 -- SENTINEL S2: REPO-SCOPE BOUNDARY ENFORCER.
|
|
3
|
+
*
|
|
4
|
+
* Even when no danger-pattern matches, a command that touches paths
|
|
5
|
+
* OUTSIDE the user's repo (or ~/.mneme, ~/.cache, etc) is suspect.
|
|
6
|
+
*
|
|
7
|
+
* The wild rule: Mneme's job is to defend ONE repo. If the AI is
|
|
8
|
+
* about to touch /etc, /usr, /var, /sys, /proc, /dev, /root, or any
|
|
9
|
+
* path outside the repo + ~/.{mneme,cache,npm,config,local}, raise
|
|
10
|
+
* an out-of-scope alert.
|
|
11
|
+
*
|
|
12
|
+
* Per-repo scope means: the AI can do anything inside its sandbox,
|
|
13
|
+
* but cannot reach into the operating system's furniture.
|
|
14
|
+
*/
|
|
15
|
+
import { isAbsolute, resolve, normalize, sep } from "node:path";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
const SYSTEM_PREFIXES = ["/etc", "/usr", "/var", "/sys", "/proc", "/dev", "/root", "/boot", "/lib", "/lib64", "/bin", "/sbin"];
|
|
18
|
+
const NETWORK_PREFIXES = ["//", "smb://", "nfs://", "\\\\"]; // SMB / NFS
|
|
19
|
+
const MNEME_HOME_SUBDIRS = [".mneme", ".cache/mneme", ".config/mneme"];
|
|
20
|
+
const ALLOWED_HOME_SUBDIRS = [...MNEME_HOME_SUBDIRS, ".npm", ".cache", ".config", ".local", ".ssh"]; // .ssh allowed for READ but creds caught by detector
|
|
21
|
+
// v1.71.0 -- permissive path extraction. Matches any "/foo/bar" or
|
|
22
|
+
// "~/foo" path-shape regardless of surrounding context. URLs (http://)
|
|
23
|
+
// get filtered out post-match.
|
|
24
|
+
const PATH_RE = /(\/[\w./~_-]+|~\/[\w./~_-]+)/g;
|
|
25
|
+
const RELATIVE_RE = /(\.\.\/(?:[\w.-]+\/?)+)/g;
|
|
26
|
+
const DEVICE_RE = /\/dev\/(sd[a-z]\d*|nvme\d+n\d+(?:p\d+)?|hd[a-z]\d*|disk\d+|mapper\/\w+|zero|null|random)\b/g;
|
|
27
|
+
export function extractPaths(command) {
|
|
28
|
+
const out = [];
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
// Pre-filter: identify URL spans so we don't extract their path components.
|
|
31
|
+
const urlSpans = [];
|
|
32
|
+
for (const u of command.matchAll(/\b(?:https?|ftp|ssh|file):\/\/\S+/g)) {
|
|
33
|
+
urlSpans.push([u.index, u.index + u[0].length]);
|
|
34
|
+
}
|
|
35
|
+
const insideUrl = (offset) => urlSpans.some(([s, e]) => offset >= s && offset < e);
|
|
36
|
+
const push = (raw, offset) => {
|
|
37
|
+
if (insideUrl(offset))
|
|
38
|
+
return;
|
|
39
|
+
const expanded = raw.startsWith("~") ? raw.replace(/^~/, homedir()) : raw;
|
|
40
|
+
// Keep forward slashes -- normalize() flips to backslashes on Windows
|
|
41
|
+
// which breaks our prefix checks below.
|
|
42
|
+
const resolvedPath = isAbsolute(expanded)
|
|
43
|
+
? normalize(expanded).replace(/\\/g, "/")
|
|
44
|
+
: expanded;
|
|
45
|
+
const key = `${resolvedPath}|${offset}`;
|
|
46
|
+
if (seen.has(key))
|
|
47
|
+
return;
|
|
48
|
+
seen.add(key);
|
|
49
|
+
out.push({ raw, resolved: resolvedPath, offset });
|
|
50
|
+
};
|
|
51
|
+
for (const m of command.matchAll(PATH_RE))
|
|
52
|
+
push(m[1], m.index ?? 0);
|
|
53
|
+
for (const m of command.matchAll(RELATIVE_RE))
|
|
54
|
+
push(m[1], m.index ?? 0);
|
|
55
|
+
for (const m of command.matchAll(DEVICE_RE))
|
|
56
|
+
push(m[0], m.index ?? 0);
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
function startsWithAny(path, prefixes) {
|
|
60
|
+
const lower = path.toLowerCase();
|
|
61
|
+
return prefixes.some((p) => lower === p.toLowerCase() || lower.startsWith(p.toLowerCase() + sep) || lower.startsWith(p.toLowerCase() + "/"));
|
|
62
|
+
}
|
|
63
|
+
function toPosix(p) { return p.replace(/\\/g, "/"); }
|
|
64
|
+
function isInsideRepo(repoRoot, p) {
|
|
65
|
+
if (!isAbsolute(p)) {
|
|
66
|
+
return !p.startsWith("..");
|
|
67
|
+
}
|
|
68
|
+
const root = toPosix(resolve(repoRoot));
|
|
69
|
+
const rel = toPosix(resolve(p));
|
|
70
|
+
return rel === root || rel.startsWith(root + "/");
|
|
71
|
+
}
|
|
72
|
+
function isMnemeHome(p) {
|
|
73
|
+
if (!isAbsolute(p))
|
|
74
|
+
return false;
|
|
75
|
+
const home = homedir();
|
|
76
|
+
const pp = toPosix(p);
|
|
77
|
+
return MNEME_HOME_SUBDIRS.some((sub) => {
|
|
78
|
+
const full = toPosix(resolve(home, sub));
|
|
79
|
+
return pp === full || pp.startsWith(full + "/");
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function isAllowedHome(p) {
|
|
83
|
+
if (!isAbsolute(p))
|
|
84
|
+
return false;
|
|
85
|
+
const home = homedir();
|
|
86
|
+
const pp = toPosix(p);
|
|
87
|
+
return ALLOWED_HOME_SUBDIRS.some((sub) => {
|
|
88
|
+
const full = toPosix(resolve(home, sub));
|
|
89
|
+
return pp === full || pp.startsWith(full + "/");
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
export function enforceScope(repoRoot, command) {
|
|
93
|
+
const paths = extractPaths(command);
|
|
94
|
+
const violations = [];
|
|
95
|
+
const insideRepo = [];
|
|
96
|
+
const insideMnemeHome = [];
|
|
97
|
+
for (const p of paths) {
|
|
98
|
+
// Device prefixes (check FIRST so /dev/sda is "device" not "system").
|
|
99
|
+
if (p.resolved.startsWith("/dev/")) {
|
|
100
|
+
violations.push({ path: p, reason: `Path "${p.raw}" is a block device; never legitimate for repo work.`, category: "device" });
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
// System prefixes.
|
|
104
|
+
if (startsWithAny(p.resolved, SYSTEM_PREFIXES)) {
|
|
105
|
+
violations.push({ path: p, reason: `Path "${p.raw}" is in a system directory; outside repo scope.`, category: "system" });
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
// Network mounts.
|
|
109
|
+
if (NETWORK_PREFIXES.some((pre) => p.resolved.startsWith(pre))) {
|
|
110
|
+
violations.push({ path: p, reason: `Path "${p.raw}" is a network mount; outside repo scope.`, category: "network-mount" });
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// Parent escapes that resolve outside repo.
|
|
114
|
+
if (p.raw.startsWith("..") && isAbsolute(repoRoot)) {
|
|
115
|
+
const projected = resolve(repoRoot, p.raw);
|
|
116
|
+
if (!projected.startsWith(resolve(repoRoot) + sep) && projected !== resolve(repoRoot)) {
|
|
117
|
+
violations.push({ path: p, reason: `Path "${p.raw}" escapes the repo via parent reference.`, category: "parent-escape" });
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Inside repo: good.
|
|
122
|
+
if (isInsideRepo(repoRoot, p.resolved)) {
|
|
123
|
+
insideRepo.push(p);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
// Mneme home: good.
|
|
127
|
+
if (isMnemeHome(p.resolved)) {
|
|
128
|
+
insideMnemeHome.push(p);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
// Other allowed home subdirs: low-priority alert.
|
|
132
|
+
if (!isAllowedHome(p.resolved) && p.resolved.startsWith(homedir())) {
|
|
133
|
+
violations.push({
|
|
134
|
+
path: p,
|
|
135
|
+
reason: `Path "${p.raw}" is in $HOME but outside Mneme/cache scope.`,
|
|
136
|
+
category: "home-outside-mneme",
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const headline = violations.length === 0
|
|
141
|
+
? `${paths.length} path(s) extracted; all within repo + Mneme scope.`
|
|
142
|
+
: `${violations.length} scope violation(s) across ${new Set(violations.map((v) => v.category)).size} categor(ies).`;
|
|
143
|
+
return { extractedPaths: paths, violations, insideRepo, insideMnemeHome, headline };
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=scope_enforcer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scope_enforcer.js","sourceRoot":"","sources":["../../src/sentinel/scope_enforcer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1.71.0 -- SENTINEL ORCHESTRATOR.
|
|
3
|
+
*
|
|
4
|
+
* The full intercept pipeline for AI-proposed shell commands:
|
|
5
|
+
*
|
|
6
|
+
* 1. Command Detector (signature catalog)
|
|
7
|
+
* 2. Scope Enforcer (repo boundary)
|
|
8
|
+
* 3. Risk Scorer (composite score + action)
|
|
9
|
+
* 4. Self-Learning (per-repo policy from past audit)
|
|
10
|
+
* 5. HMAC Audit Ledger (tamper-evident log)
|
|
11
|
+
*
|
|
12
|
+
* Returns a SentinelDecision that the MCP layer (or any executor)
|
|
13
|
+
* consults BEFORE running the command. The decision carries
|
|
14
|
+
* action = ALLOW / AUDIT / WARN / BLOCK + complete reasoning.
|
|
15
|
+
*
|
|
16
|
+
* NEW IN v1.71: CHRONOLOGICAL TRUST DECAY. Commands seen N times in
|
|
17
|
+
* past audit at ALLOW level + no tampering -> trust score rises ->
|
|
18
|
+
* subsequent identical commands skip heavy verification.
|
|
19
|
+
*/
|
|
20
|
+
import { type RiskScoreReport, type RecommendedAction } from "./risk_scorer.js";
|
|
21
|
+
import { type AuditEntry } from "./audit_ledger.js";
|
|
22
|
+
export interface LearnedPolicy {
|
|
23
|
+
/** sha256(command) -> trust record. */
|
|
24
|
+
trustByHash: Record<string, {
|
|
25
|
+
allowed: number;
|
|
26
|
+
lastSeenTs: string;
|
|
27
|
+
firstSeenTs: string;
|
|
28
|
+
}>;
|
|
29
|
+
updatedAt: string;
|
|
30
|
+
}
|
|
31
|
+
export interface SentinelDecision {
|
|
32
|
+
command: string;
|
|
33
|
+
action: RecommendedAction;
|
|
34
|
+
score: number;
|
|
35
|
+
/** Verdict reasons -- list of named factors. */
|
|
36
|
+
reasons: string[];
|
|
37
|
+
riskReport: RiskScoreReport;
|
|
38
|
+
/** Whether this command's hash has been ALLOWed in the past. */
|
|
39
|
+
trustLevel: "novel" | "seen-once" | "trusted";
|
|
40
|
+
/** How many past ALLOW occurrences. */
|
|
41
|
+
pastAllows: number;
|
|
42
|
+
/** Audit entry written (when action != ALLOW or auditAlways). */
|
|
43
|
+
auditEntry: AuditEntry | null;
|
|
44
|
+
headline: string;
|
|
45
|
+
}
|
|
46
|
+
export interface SentinelOptions {
|
|
47
|
+
/** Vendor (AI agent) name. */
|
|
48
|
+
vendor?: string;
|
|
49
|
+
/** Always audit (even ALLOW). Default false. */
|
|
50
|
+
auditAlways?: boolean;
|
|
51
|
+
/** Persist trust learning. Default true. */
|
|
52
|
+
learn?: boolean;
|
|
53
|
+
/** When the command was actually executed (caller flips after action). */
|
|
54
|
+
executed?: boolean;
|
|
55
|
+
}
|
|
56
|
+
export declare function intercept(repoRoot: string, command: string, opts?: SentinelOptions): SentinelDecision;
|
|
57
|
+
/** Auto-mint a vaccine from past BLOCK decisions -- the per-repo
|
|
58
|
+
* learning loop. */
|
|
59
|
+
export declare function harvestVaccines(repoRoot: string): {
|
|
60
|
+
newVaccines: number;
|
|
61
|
+
sample: string[];
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=sentinel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../../src/sentinel/sentinel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAMH,OAAO,EAAa,KAAK,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC3F,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAI/E,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1F,SAAS,EAAE,MAAM,CAAC;CACnB;AAoBD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,eAAe,CAAC;IAC5B,gEAAgE;IAChE,UAAU,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAAC;IAC9C,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,eAAe,GAAG,gBAAgB,CAwDrG;AAED;qBACqB;AACrB,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAqB3F"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v1.71.0 -- SENTINEL ORCHESTRATOR.
|
|
3
|
+
*
|
|
4
|
+
* The full intercept pipeline for AI-proposed shell commands:
|
|
5
|
+
*
|
|
6
|
+
* 1. Command Detector (signature catalog)
|
|
7
|
+
* 2. Scope Enforcer (repo boundary)
|
|
8
|
+
* 3. Risk Scorer (composite score + action)
|
|
9
|
+
* 4. Self-Learning (per-repo policy from past audit)
|
|
10
|
+
* 5. HMAC Audit Ledger (tamper-evident log)
|
|
11
|
+
*
|
|
12
|
+
* Returns a SentinelDecision that the MCP layer (or any executor)
|
|
13
|
+
* consults BEFORE running the command. The decision carries
|
|
14
|
+
* action = ALLOW / AUDIT / WARN / BLOCK + complete reasoning.
|
|
15
|
+
*
|
|
16
|
+
* NEW IN v1.71: CHRONOLOGICAL TRUST DECAY. Commands seen N times in
|
|
17
|
+
* past audit at ALLOW level + no tampering -> trust score rises ->
|
|
18
|
+
* subsequent identical commands skip heavy verification.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
21
|
+
import { createHash } from "node:crypto";
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import { scoreRisk } from "./risk_scorer.js";
|
|
24
|
+
import { appendAudit, readAuditLog } from "./audit_ledger.js";
|
|
25
|
+
const POLICY_FILE = ".mneme/sentinel/policy.json";
|
|
26
|
+
function hashCmd(c) {
|
|
27
|
+
return createHash("sha256").update(c.trim()).digest("hex").slice(0, 16);
|
|
28
|
+
}
|
|
29
|
+
function readPolicy(repoRoot) {
|
|
30
|
+
const p = join(repoRoot, POLICY_FILE);
|
|
31
|
+
if (!existsSync(p))
|
|
32
|
+
return { trustByHash: {}, updatedAt: new Date(0).toISOString() };
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(readFileSync(p, "utf8"));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return { trustByHash: {}, updatedAt: new Date(0).toISOString() };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function writePolicy(repoRoot, policy) {
|
|
41
|
+
const dir = join(repoRoot, ".mneme/sentinel");
|
|
42
|
+
if (!existsSync(dir))
|
|
43
|
+
mkdirSync(dir, { recursive: true });
|
|
44
|
+
writeFileSync(join(repoRoot, POLICY_FILE), JSON.stringify(policy, null, 2) + "\n", "utf8");
|
|
45
|
+
}
|
|
46
|
+
export function intercept(repoRoot, command, opts) {
|
|
47
|
+
const risk = scoreRisk(repoRoot, command);
|
|
48
|
+
const reasons = [];
|
|
49
|
+
// Apply trust decay: commands previously allowed N times relax the action.
|
|
50
|
+
const policy = readPolicy(repoRoot);
|
|
51
|
+
const h = hashCmd(command);
|
|
52
|
+
const trustRec = policy.trustByHash[h];
|
|
53
|
+
const pastAllows = trustRec?.allowed ?? 0;
|
|
54
|
+
let trustLevel = "novel";
|
|
55
|
+
if (pastAllows >= 5)
|
|
56
|
+
trustLevel = "trusted";
|
|
57
|
+
else if (pastAllows >= 1)
|
|
58
|
+
trustLevel = "seen-once";
|
|
59
|
+
// Action: trust-adjusted.
|
|
60
|
+
let action = risk.recommendedAction;
|
|
61
|
+
for (const m of risk.detection.matches) {
|
|
62
|
+
reasons.push(`${m.signature.id}: ${m.signature.rationale}`);
|
|
63
|
+
}
|
|
64
|
+
for (const v of risk.scope.violations) {
|
|
65
|
+
reasons.push(`scope-${v.category}: ${v.reason}`);
|
|
66
|
+
}
|
|
67
|
+
if (trustLevel === "trusted" && action === "AUDIT") {
|
|
68
|
+
action = "ALLOW";
|
|
69
|
+
reasons.push(`trust: command seen ALLOW ${pastAllows}x previously -- demoted from AUDIT.`);
|
|
70
|
+
}
|
|
71
|
+
// Never demote BLOCK / WARN via trust alone -- explicit catalog hits stay.
|
|
72
|
+
// Write audit when action != ALLOW or caller requests.
|
|
73
|
+
let auditEntry = null;
|
|
74
|
+
if (action !== "ALLOW" || opts?.auditAlways) {
|
|
75
|
+
auditEntry = appendAudit(repoRoot, command, risk, {
|
|
76
|
+
vendor: opts?.vendor ?? "unknown",
|
|
77
|
+
executed: opts?.executed ?? false,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// Update learned policy when action == ALLOW (and learn is on).
|
|
81
|
+
if (opts?.learn !== false && action === "ALLOW") {
|
|
82
|
+
const next = { ...policy };
|
|
83
|
+
next.trustByHash[h] = {
|
|
84
|
+
allowed: (trustRec?.allowed ?? 0) + 1,
|
|
85
|
+
firstSeenTs: trustRec?.firstSeenTs ?? new Date().toISOString(),
|
|
86
|
+
lastSeenTs: new Date().toISOString(),
|
|
87
|
+
};
|
|
88
|
+
next.updatedAt = new Date().toISOString();
|
|
89
|
+
writePolicy(repoRoot, next);
|
|
90
|
+
}
|
|
91
|
+
const headline = `SENTINEL ${action}: risk=${risk.score}/100, trust=${trustLevel}, ${risk.detection.matches.length} catalog hit(s).`;
|
|
92
|
+
return {
|
|
93
|
+
command, action, score: risk.score, reasons,
|
|
94
|
+
riskReport: risk,
|
|
95
|
+
trustLevel, pastAllows,
|
|
96
|
+
auditEntry, headline,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** Auto-mint a vaccine from past BLOCK decisions -- the per-repo
|
|
100
|
+
* learning loop. */
|
|
101
|
+
export function harvestVaccines(repoRoot) {
|
|
102
|
+
const audit = readAuditLog(repoRoot);
|
|
103
|
+
const blocked = audit.filter((e) => e.action === "BLOCK");
|
|
104
|
+
// Group by class.
|
|
105
|
+
const byClass = new Map();
|
|
106
|
+
for (const e of blocked) {
|
|
107
|
+
for (const c of e.classes) {
|
|
108
|
+
const arr = byClass.get(c) ?? [];
|
|
109
|
+
arr.push(e);
|
|
110
|
+
byClass.set(c, arr);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const sample = [];
|
|
114
|
+
let total = 0;
|
|
115
|
+
for (const [cls, entries] of byClass) {
|
|
116
|
+
if (entries.length >= 2) {
|
|
117
|
+
total += 1;
|
|
118
|
+
sample.push(`${cls}: ${entries.length} block(s) -> vaccine`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return { newVaccines: total, sample };
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=sentinel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentinel.js","sourceRoot":"","sources":["../../src/sentinel/sentinel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,SAAS,EAAgD,MAAM,kBAAkB,CAAC;AAC3F,OAAO,EAAE,WAAW,EAAE,YAAY,EAAmB,MAAM,mBAAmB,CAAC;AAE/E,MAAM,WAAW,GAAG,6BAA6B,CAAC;AAQlD,SAAS,OAAO,CAAC,CAAS;IACxB,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC1E,CAAC;AAED,SAAS,UAAU,CAAC,QAAgB;IAClC,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;IACrF,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAkB,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QAAC,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;IAAC,CAAC;AAC/E,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB,EAAE,MAAqB;IAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IAC9C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;AAC7F,CAAC;AA6BD,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,OAAe,EAAE,IAAsB;IACjF,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,2EAA2E;IAC3E,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACpC,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3B,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,QAAQ,EAAE,OAAO,IAAI,CAAC,CAAC;IAC1C,IAAI,UAAU,GAAmC,OAAO,CAAC;IACzD,IAAI,UAAU,IAAI,CAAC;QAAE,UAAU,GAAG,SAAS,CAAC;SACvC,IAAI,UAAU,IAAI,CAAC;QAAE,UAAU,GAAG,WAAW,CAAC;IAEnD,0BAA0B;IAC1B,IAAI,MAAM,GAAsB,IAAI,CAAC,iBAAiB,CAAC;IACvD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,CAAC,SAAS,CAAC,SAAS,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QACtC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,UAAU,KAAK,SAAS,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;QACnD,MAAM,GAAG,OAAO,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,6BAA6B,UAAU,qCAAqC,CAAC,CAAC;IAC7F,CAAC;IACD,2EAA2E;IAE3E,uDAAuD;IACvD,IAAI,UAAU,GAAsB,IAAI,CAAC;IACzC,IAAI,MAAM,KAAK,OAAO,IAAI,IAAI,EAAE,WAAW,EAAE,CAAC;QAC5C,UAAU,GAAG,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE;YAChD,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,SAAS;YACjC,QAAQ,EAAE,IAAI,EAAE,QAAQ,IAAI,KAAK;SAClC,CAAC,CAAC;IACL,CAAC;IAED,gEAAgE;IAChE,IAAI,IAAI,EAAE,KAAK,KAAK,KAAK,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;QAChD,MAAM,IAAI,GAAkB,EAAE,GAAG,MAAM,EAAE,CAAC;QAC1C,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG;YACpB,OAAO,EAAE,CAAC,QAAQ,EAAE,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC;YACrC,WAAW,EAAE,QAAQ,EAAE,WAAW,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC9D,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACrC,CAAC;QACF,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC1C,WAAW,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED,MAAM,QAAQ,GAAG,YAAY,MAAM,UAAU,IAAI,CAAC,KAAK,eAAe,UAAU,KAAK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,kBAAkB,CAAC;IAErI,OAAO;QACL,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO;QAC3C,UAAU,EAAE,IAAI;QAChB,UAAU,EAAE,UAAU;QACtB,UAAU,EAAE,QAAQ;KACrB,CAAC;AACJ,CAAC;AAED;qBACqB;AACrB,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,MAAM,KAAK,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC;IAC1D,kBAAkB;IAClB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAwB,CAAC;IAChD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACjC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,OAAO,EAAE,CAAC;QACrC,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;YACxB,KAAK,IAAI,CAAC,CAAC;YACX,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,KAAK,OAAO,CAAC,MAAM,sBAAsB,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IACD,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentinel.test.d.ts","sourceRoot":"","sources":["../../src/sentinel/sentinel.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|