@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
|
@@ -3,9 +3,8 @@ import {
|
|
|
3
3
|
} from "./chunk-B5QKJHSV.js";
|
|
4
4
|
import {
|
|
5
5
|
discoverPolicy
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-B6S2PBS4.js";
|
|
7
7
|
import {
|
|
8
|
-
DEFAULT_FORBIDDEN_PATTERNS,
|
|
9
8
|
FORBIDDEN_BASENAMES,
|
|
10
9
|
classifyDeny,
|
|
11
10
|
isPositionallySafeMention,
|
|
@@ -14,16 +13,18 @@ import {
|
|
|
14
13
|
scanBashCommand,
|
|
15
14
|
scanContentForForbiddenBasenames,
|
|
16
15
|
scanGlobPattern,
|
|
17
|
-
tokenizePaths
|
|
18
|
-
|
|
16
|
+
tokenizePaths,
|
|
17
|
+
unionWithDefaultForbiddenPatterns
|
|
18
|
+
} from "./chunk-FIEIGBYL.js";
|
|
19
19
|
import {
|
|
20
|
+
DEFAULT_FORBIDDEN_PATTERNS,
|
|
20
21
|
loadPolicy,
|
|
21
22
|
policyToConfig,
|
|
22
23
|
policyToRole
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-KWZ7JKKO.js";
|
|
24
25
|
|
|
25
26
|
// src/gateway/workspaceRouter.ts
|
|
26
|
-
import { resolve, dirname } from "path";
|
|
27
|
+
import { resolve, dirname, sep } from "path";
|
|
27
28
|
|
|
28
29
|
// src/mergeRoles.ts
|
|
29
30
|
function isWithinActiveHours(hour, range) {
|
|
@@ -251,15 +252,35 @@ async function resolveWorkspace(cwd, home) {
|
|
|
251
252
|
const policyPath = discoverPolicy(start, home);
|
|
252
253
|
let root = start;
|
|
253
254
|
let workspaceRole = null;
|
|
255
|
+
const warnings = [];
|
|
254
256
|
if (policyPath) {
|
|
255
257
|
const policy = await loadPolicy(policyPath);
|
|
258
|
+
const policyDir = dirname(resolve(policyPath));
|
|
259
|
+
root = policyDir;
|
|
256
260
|
const repoRoot = policyToConfig(policy).repo?.root;
|
|
257
|
-
|
|
261
|
+
if (repoRoot) {
|
|
262
|
+
const declared = resolve(policyDir, repoRoot);
|
|
263
|
+
const homeAbs = resolve(home);
|
|
264
|
+
const declaredPrefix = declared.endsWith(sep) ? declared : declared + sep;
|
|
265
|
+
const containsPolicy = policyDir === declared || policyDir.startsWith(declaredPrefix);
|
|
266
|
+
const aboveHome = declared !== homeAbs && homeAbs.startsWith(declaredPrefix);
|
|
267
|
+
if (!containsPolicy) {
|
|
268
|
+
warnings.push(
|
|
269
|
+
`repo.root "${repoRoot}" (resolves to "${declared}") does not contain the policy file "${policyPath}" \u2014 anchoring to the policy's own directory "${policyDir}" instead`
|
|
270
|
+
);
|
|
271
|
+
} else if (aboveHome) {
|
|
272
|
+
warnings.push(
|
|
273
|
+
`repo.root "${repoRoot}" (resolves to "${declared}") reaches above the home directory \u2014 anchoring to the policy's own directory "${policyDir}" instead`
|
|
274
|
+
);
|
|
275
|
+
} else {
|
|
276
|
+
root = declared;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
258
279
|
workspaceRole = policyToRole(policy);
|
|
259
280
|
}
|
|
260
281
|
return {
|
|
261
282
|
ok: true,
|
|
262
|
-
resolution: { root, agentId: deriveAgentId(root), policyPath, workspaceRole }
|
|
283
|
+
resolution: { root, agentId: deriveAgentId(root), policyPath, workspaceRole, warnings }
|
|
263
284
|
};
|
|
264
285
|
}
|
|
265
286
|
function effectiveRole(ceiling, workspaceRole) {
|
|
@@ -347,11 +368,11 @@ var TranslatorRegistry = class {
|
|
|
347
368
|
|
|
348
369
|
// src/gateway/runtimeConstructionResolvers.ts
|
|
349
370
|
var MAX_RECURSION_DEPTH = 3;
|
|
350
|
-
var INTERPRETER_RE = /\b(?:python
|
|
371
|
+
var INTERPRETER_RE = /\b(?:python|node|ruby|perl|php)[0-9.]*\s+(?:\S+\s+)*?-[cer]/;
|
|
351
372
|
var NESTED_ENCODING_RE = /chr\(\d+\)|String\.fromCharCode|\\x[0-9a-fA-F]{2}|\\[0-7]{1,3}|printf\s/;
|
|
352
373
|
var PRINTF_HEX_RE = /printf\s+['"]?[^'"]*\\x[0-9a-fA-F]{2}/;
|
|
353
374
|
var PRINTF_OCT_RE = /printf\s+['"]?[^'"]*\\[0-7]{1,3}/;
|
|
354
|
-
var B1_CONTEXT_RE = /(?:\bbase64\s+(
|
|
375
|
+
var B1_CONTEXT_RE = /(?:\bbase64\s+(?:--decode\b|-[a-zA-Z]*[dD][a-zA-Z]*\b)|\bopenssl\s+(?:enc\s+)?(?:-?base64\s+(?:-\w+\s+)*-d|-d\s+(?:-\w+\s+)*-?base64)\b)/;
|
|
355
376
|
var ECHO_E_HEX_RE = /\becho\s+(?:-\w+\s+)*-\w*e\w*\b[^|;&\n]*\\x[0-9a-fA-F]{2}/;
|
|
356
377
|
var ANSI_C_QUOTE_HEX_RE = /\$'[^']*\\x[0-9a-fA-F]{2}/;
|
|
357
378
|
var ANSI_C_QUOTE_OCT_RE = /\$'[^']*\\[0-7]{1,3}/;
|
|
@@ -1032,17 +1053,25 @@ var ClaudeCodeTranslator = class {
|
|
|
1032
1053
|
};
|
|
1033
1054
|
|
|
1034
1055
|
// src/gateway/grepRewriter.ts
|
|
1056
|
+
function toRelativeExclusionGlob(pattern) {
|
|
1057
|
+
return pattern.startsWith("/") ? "**" + pattern : pattern;
|
|
1058
|
+
}
|
|
1035
1059
|
function buildGrepExclusions(forbiddenPatterns) {
|
|
1036
1060
|
const flags = [];
|
|
1037
1061
|
for (const pattern of forbiddenPatterns) {
|
|
1038
|
-
flags.push("--glob", `!${pattern}`);
|
|
1062
|
+
flags.push("--glob", `!${toRelativeExclusionGlob(pattern)}`);
|
|
1039
1063
|
}
|
|
1040
1064
|
return flags;
|
|
1041
1065
|
}
|
|
1042
1066
|
function isPathItselfForbidden(searchPath, forbiddenPatterns) {
|
|
1043
1067
|
const matched = [];
|
|
1044
1068
|
for (const pattern of forbiddenPatterns) {
|
|
1045
|
-
if (matchGlobInsensitive(pattern, searchPath))
|
|
1069
|
+
if (matchGlobInsensitive(pattern, searchPath) || // A directory-glob (`…/**`, e.g. `**/.ssh/**` or `/etc/**`) does NOT match
|
|
1070
|
+
// the bare directory itself — the trailing `/.*` requires a char after the
|
|
1071
|
+
// slash, so `matchGlob('**/.ssh/**', '/home/u/.ssh')` is false. When the
|
|
1072
|
+
// SEARCH PATH *is* the forbidden directory, treat it as forbidden so it
|
|
1073
|
+
// routes to DENY (no rewrite), not MODIFY.
|
|
1074
|
+
pattern.endsWith("/**") && matchGlobInsensitive(pattern.slice(0, -3), searchPath)) {
|
|
1046
1075
|
matched.push(pattern);
|
|
1047
1076
|
}
|
|
1048
1077
|
}
|
|
@@ -1424,6 +1453,9 @@ var SentinelGateway = class {
|
|
|
1424
1453
|
return { ok: false, finding };
|
|
1425
1454
|
}
|
|
1426
1455
|
const { root, agentId, workspaceRole } = resolved.resolution;
|
|
1456
|
+
for (const w of resolved.resolution.warnings) {
|
|
1457
|
+
console.warn(`[SENTINEL GATEWAY] workspace-resolution warning (${agentId}): ${w}`);
|
|
1458
|
+
}
|
|
1427
1459
|
const ceiling = this.operatorCeiling;
|
|
1428
1460
|
if (!ceiling) {
|
|
1429
1461
|
const check = {
|
|
@@ -1450,7 +1482,24 @@ var SentinelGateway = class {
|
|
|
1450
1482
|
name: agentId
|
|
1451
1483
|
});
|
|
1452
1484
|
this.sentinel.touchAgent(agentId);
|
|
1453
|
-
return { ok: true, agentId };
|
|
1485
|
+
return { ok: true, agentId, effectiveRole: merged.role };
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Forbidden-pattern list for ONE request: the gateway's construction-time
|
|
1489
|
+
* list unioned with the request's merged-role patterns (operator-ceiling ∩
|
|
1490
|
+
* workspace yaml). Without this, a workspace's custom forbid targets were
|
|
1491
|
+
* enforced by the role validator inside wrap() but never reached the
|
|
1492
|
+
* gateway's own bash-L1 / Grep layers, which checked only the static list.
|
|
1493
|
+
* Normalization is idempotent (the chokepoint globstar-prepend).
|
|
1494
|
+
*/
|
|
1495
|
+
patternsForRequest(effectiveRole2) {
|
|
1496
|
+
if (!effectiveRole2?.forbiddenTargetPatterns?.length) return this.forbiddenPatterns;
|
|
1497
|
+
return [
|
|
1498
|
+
.../* @__PURE__ */ new Set([
|
|
1499
|
+
...this.forbiddenPatterns,
|
|
1500
|
+
...effectiveRole2.forbiddenTargetPatterns.map(normalizeForbiddenPattern)
|
|
1501
|
+
])
|
|
1502
|
+
];
|
|
1454
1503
|
}
|
|
1455
1504
|
async handlePreToolUse(body, res, translator) {
|
|
1456
1505
|
let payload;
|
|
@@ -1466,6 +1515,7 @@ var SentinelGateway = class {
|
|
|
1466
1515
|
return;
|
|
1467
1516
|
}
|
|
1468
1517
|
let routingId = this.agentId;
|
|
1518
|
+
let requestForbiddenPatterns = this.forbiddenPatterns;
|
|
1469
1519
|
if (this.workspaceIsolation) {
|
|
1470
1520
|
const routed = await this.resolveBPathRouting(
|
|
1471
1521
|
event.metadata?.cwd,
|
|
@@ -1483,6 +1533,7 @@ var SentinelGateway = class {
|
|
|
1483
1533
|
}
|
|
1484
1534
|
routingId = routed.agentId;
|
|
1485
1535
|
event.agentId = routingId;
|
|
1536
|
+
requestForbiddenPatterns = this.patternsForRequest(routed.effectiveRole);
|
|
1486
1537
|
}
|
|
1487
1538
|
if (event.metadata?._unknownTool === "true") {
|
|
1488
1539
|
const unknownName = event.metadata.ccToolName ?? event.primaryTarget;
|
|
@@ -1555,7 +1606,7 @@ var SentinelGateway = class {
|
|
|
1555
1606
|
let matchedPath = null;
|
|
1556
1607
|
let matchedPattern = null;
|
|
1557
1608
|
for (const tokenPath of allTokenPaths) {
|
|
1558
|
-
for (const pattern of
|
|
1609
|
+
for (const pattern of requestForbiddenPatterns) {
|
|
1559
1610
|
if (matchGlobInsensitive(pattern, tokenPath)) {
|
|
1560
1611
|
matchedPath = tokenPath;
|
|
1561
1612
|
matchedPattern = pattern;
|
|
@@ -1737,7 +1788,7 @@ var SentinelGateway = class {
|
|
|
1737
1788
|
if (ccToolName === "Grep") {
|
|
1738
1789
|
const toolInput = parsedPayload.tool_input ?? {};
|
|
1739
1790
|
const searchPath = typeof toolInput.path === "string" && toolInput.path.length > 0 ? toolInput.path : parsedPayload.cwd ?? ".";
|
|
1740
|
-
const pathCheck = isPathItselfForbidden(searchPath,
|
|
1791
|
+
const pathCheck = isPathItselfForbidden(searchPath, requestForbiddenPatterns);
|
|
1741
1792
|
if (pathCheck.forbidden) {
|
|
1742
1793
|
const finding2 = {
|
|
1743
1794
|
severity: "HIGH",
|
|
@@ -1762,7 +1813,7 @@ var SentinelGateway = class {
|
|
|
1762
1813
|
this.sendJson(res, 200, response);
|
|
1763
1814
|
return;
|
|
1764
1815
|
}
|
|
1765
|
-
const exclusions = buildGrepExclusions(
|
|
1816
|
+
const exclusions = buildGrepExclusions(requestForbiddenPatterns);
|
|
1766
1817
|
const updatedInput = buildModifiedGrepInput(toolInput, exclusions);
|
|
1767
1818
|
const finding = {
|
|
1768
1819
|
severity: "MEDIUM",
|
|
@@ -1785,7 +1836,7 @@ var SentinelGateway = class {
|
|
|
1785
1836
|
await this.sentinel.logFinding(routingId, finding);
|
|
1786
1837
|
this.telemetry.recordToolCall(event.action, "pre", "allowed", 0);
|
|
1787
1838
|
const ccTranslator = translator;
|
|
1788
|
-
const excludedPatterns =
|
|
1839
|
+
const excludedPatterns = requestForbiddenPatterns.join(", ");
|
|
1789
1840
|
const additionalContext = `Sentinel: this Grep search was modified to exclude forbidden paths. Excluded patterns: ${excludedPatterns}. To search specific forbidden files, use Read with explicit approval.`;
|
|
1790
1841
|
if (typeof ccTranslator.formatPreToolUseModifyResponse === "function") {
|
|
1791
1842
|
const response = ccTranslator.formatPreToolUseModifyResponse({
|
|
@@ -2052,11 +2103,11 @@ async function runGatewayDaemon({
|
|
|
2052
2103
|
port = DEFAULT_PORT,
|
|
2053
2104
|
buildId
|
|
2054
2105
|
}) {
|
|
2055
|
-
const { Sentinel: SentinelClass } = await import("./Sentinel-
|
|
2106
|
+
const { Sentinel: SentinelClass } = await import("./Sentinel-4QKPFHTI.js");
|
|
2056
2107
|
const { writePidFile, writeReleaseToken } = await import("./pidManager-DOGVN6ZT.js");
|
|
2057
2108
|
const { homedir } = await import("os");
|
|
2058
2109
|
const { randomBytes } = await import("crypto");
|
|
2059
|
-
const { loadPolicy: loadPolicy2, policyToRole: policyToRole2, policyToConfig: policyToConfig2 } = await import("./policyLoader-
|
|
2110
|
+
const { loadPolicy: loadPolicy2, policyToRole: policyToRole2, policyToConfig: policyToConfig2 } = await import("./policyLoader-XX6BQXNB.js");
|
|
2060
2111
|
const sentinel = await SentinelClass.fromPolicy(policyPath);
|
|
2061
2112
|
const baseline = await sentinel.computeBaseline("claude-code");
|
|
2062
2113
|
sentinel.setBaseline("claude-code", baseline);
|
|
@@ -2075,7 +2126,11 @@ async function runGatewayDaemon({
|
|
|
2075
2126
|
releaseToken,
|
|
2076
2127
|
unknownTools: operatorConfig.enforcement?.unknownTools,
|
|
2077
2128
|
allowUnknownTools: operatorConfig.enforcement?.allowUnknownTools,
|
|
2078
|
-
buildId
|
|
2129
|
+
buildId,
|
|
2130
|
+
// The operator policy's custom forbid targets must reach the gateway's own
|
|
2131
|
+
// bash-L1/Grep layers (defaults-as-floor union, same chokepoint as role
|
|
2132
|
+
// construction) — previously the gateway saw only the built-in defaults.
|
|
2133
|
+
forbiddenPatterns: unionWithDefaultForbiddenPatterns(operatorCeiling.forbiddenTargetPatterns)
|
|
2079
2134
|
});
|
|
2080
2135
|
await gateway.start();
|
|
2081
2136
|
const home = homedir();
|
|
@@ -2088,4 +2143,4 @@ export {
|
|
|
2088
2143
|
SentinelGateway,
|
|
2089
2144
|
runGatewayDaemon
|
|
2090
2145
|
};
|
|
2091
|
-
//# sourceMappingURL=chunk-
|
|
2146
|
+
//# sourceMappingURL=chunk-HRI2Y326.js.map
|