@astrasyncai/verification-gateway 3.10.0 → 3.11.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/adapter-interface/interface.d.mts +1 -1
- package/dist/adapter-interface/interface.d.ts +1 -1
- package/dist/adapters/express.js +1 -1
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +1 -1
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.js +1 -1
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +1 -1
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.js +1 -1
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +1 -1
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.js +1 -1
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +1 -1
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/bin/astrasync-claude-hook.js +317 -35
- package/dist/bin/astrasync-codex-hook.js +5107 -0
- package/dist/bin/astrasync-guard.js +430 -40
- package/dist/bin/astrasync.js +453 -41
- package/dist/browser/background.js +279 -24
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +279 -24
- package/dist/browser/background.mjs.map +1 -1
- package/dist/browser/browser-adapter.d.mts +1 -1
- package/dist/browser/browser-adapter.d.ts +1 -1
- package/dist/claude-code/claude-code-adapter.d.mts +1 -1
- package/dist/claude-code/claude-code-adapter.d.ts +1 -1
- package/dist/claude-code/claude-code-adapter.js +9 -1
- package/dist/claude-code/claude-code-adapter.js.map +1 -1
- package/dist/claude-code/claude-code-adapter.mjs +9 -1
- package/dist/claude-code/claude-code-adapter.mjs.map +1 -1
- package/dist/cli/index.d.mts +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/codex/index.d.mts +230 -0
- package/dist/codex/index.d.ts +230 -0
- package/dist/codex/index.js +5225 -0
- package/dist/codex/index.js.map +1 -0
- package/dist/codex/index.mjs +5197 -0
- package/dist/codex/index.mjs.map +1 -0
- package/dist/cursor/cursor-adapter.d.mts +1 -1
- package/dist/cursor/cursor-adapter.d.ts +1 -1
- package/dist/cursor/extension.d.mts +1 -1
- package/dist/cursor/extension.d.ts +1 -1
- package/dist/cursor/extension.js +279 -24
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +279 -24
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/gateway/gateway.d.mts +32 -2
- package/dist/gateway/gateway.d.ts +32 -2
- package/dist/gateway/gateway.js +279 -24
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +279 -24
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +1 -1
- package/dist/git-trigger/git-hooks.d.ts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/local-evaluator/evaluator.d.mts +1 -1
- package/dist/local-evaluator/evaluator.d.ts +1 -1
- package/dist/local-evaluator/evaluator.js +165 -7
- package/dist/local-evaluator/evaluator.js.map +1 -1
- package/dist/local-evaluator/evaluator.mjs +165 -7
- package/dist/local-evaluator/evaluator.mjs.map +1 -1
- package/dist/{types-D0lCtFQ7.d.mts → types-BV6abBnF.d.mts} +6 -0
- package/dist/{types-YbiXYKx1.d.ts → types-C23_uaY8.d.ts} +6 -0
- package/dist/verify.js +1 -1
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +1 -1
- package/dist/verify.mjs.map +1 -1
- package/package.json +8 -2
|
@@ -25,9 +25,7 @@ __export(hook_exports, {
|
|
|
25
25
|
resolveGuardConfig: () => resolveGuardConfig
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(hook_exports);
|
|
28
|
-
var
|
|
29
|
-
var import_os = require("os");
|
|
30
|
-
var import_path3 = require("path");
|
|
28
|
+
var import_path4 = require("path");
|
|
31
29
|
|
|
32
30
|
// src/gateway/types.ts
|
|
33
31
|
var HIGH_RISK_COMMANDS = [
|
|
@@ -49,7 +47,9 @@ var HIGH_RISK_COMMANDS = [
|
|
|
49
47
|
"git push",
|
|
50
48
|
"npm publish",
|
|
51
49
|
"docker",
|
|
52
|
-
"kubectl"
|
|
50
|
+
"kubectl",
|
|
51
|
+
"gh pr create",
|
|
52
|
+
"gh pr merge"
|
|
53
53
|
];
|
|
54
54
|
var SENSITIVE_PATHS = [
|
|
55
55
|
".ssh",
|
|
@@ -68,6 +68,164 @@ var SENSITIVE_PATHS = [
|
|
|
68
68
|
".pypirc"
|
|
69
69
|
];
|
|
70
70
|
|
|
71
|
+
// src/local-evaluator/command-matcher.ts
|
|
72
|
+
var WRAPPERS = /* @__PURE__ */ new Set([
|
|
73
|
+
"sudo",
|
|
74
|
+
"doas",
|
|
75
|
+
"env",
|
|
76
|
+
"nohup",
|
|
77
|
+
"time",
|
|
78
|
+
"timeout",
|
|
79
|
+
"nice",
|
|
80
|
+
"ionice",
|
|
81
|
+
"stdbuf",
|
|
82
|
+
"xargs",
|
|
83
|
+
"command",
|
|
84
|
+
"exec",
|
|
85
|
+
"builtin"
|
|
86
|
+
]);
|
|
87
|
+
var ASSIGN_RE = /^[a-z_][a-z0-9_]*=/;
|
|
88
|
+
var MAX_WRAPPER_HOPS = 4;
|
|
89
|
+
var MAX_RECURSION_DEPTH = 3;
|
|
90
|
+
var MAX_COMMAND_LENGTH = 1e5;
|
|
91
|
+
var LOCAL_HOST_RE = /^(localhost|127\.0\.0\.1|::1|[a-z0-9.-]*\.localhost)$/;
|
|
92
|
+
function buildRiskList(entries) {
|
|
93
|
+
const singleWord = /* @__PURE__ */ new Set();
|
|
94
|
+
const multiWord = [];
|
|
95
|
+
for (const entry of entries) {
|
|
96
|
+
const words = entry.toLowerCase().trim().split(/\s+/);
|
|
97
|
+
if (words.length === 1) singleWord.add(words[0]);
|
|
98
|
+
else multiWord.push(words);
|
|
99
|
+
}
|
|
100
|
+
multiWord.sort((a, b) => b.length - a.length);
|
|
101
|
+
return { singleWord, multiWord };
|
|
102
|
+
}
|
|
103
|
+
function stripHeredocBodies(text) {
|
|
104
|
+
const lines = text.split("\n");
|
|
105
|
+
const kept = [];
|
|
106
|
+
let terminator = null;
|
|
107
|
+
for (const line of lines) {
|
|
108
|
+
if (terminator !== null) {
|
|
109
|
+
if (line.trim() === terminator) terminator = null;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
kept.push(line);
|
|
113
|
+
const heredoc = line.match(/<<-?\s*['"]?(\w+)['"]?/);
|
|
114
|
+
if (heredoc) terminator = heredoc[1];
|
|
115
|
+
}
|
|
116
|
+
return kept.join("\n");
|
|
117
|
+
}
|
|
118
|
+
function extractDollarSubstitutions(text, collect) {
|
|
119
|
+
let result = text;
|
|
120
|
+
let start = result.indexOf("$(");
|
|
121
|
+
let iterations = 0;
|
|
122
|
+
while (start !== -1 && iterations < 50) {
|
|
123
|
+
iterations++;
|
|
124
|
+
let depth = 0;
|
|
125
|
+
let end = result.length;
|
|
126
|
+
for (let i = start + 1; i < result.length; i++) {
|
|
127
|
+
if (result[i] === "(") depth++;
|
|
128
|
+
else if (result[i] === ")") {
|
|
129
|
+
depth--;
|
|
130
|
+
if (depth === 0) {
|
|
131
|
+
end = i;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
collect(result.slice(start + 2, end));
|
|
137
|
+
result = `${result.slice(0, start)} ${result.slice(Math.min(end + 1, result.length))}`;
|
|
138
|
+
start = result.indexOf("$(");
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
function extractBacktickSubstitutions(text, collect) {
|
|
143
|
+
if (!text.includes("`")) return text;
|
|
144
|
+
const chunks = text.split("`");
|
|
145
|
+
const kept = [];
|
|
146
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
147
|
+
if (i % 2 === 1) collect(chunks[i]);
|
|
148
|
+
else kept.push(chunks[i]);
|
|
149
|
+
}
|
|
150
|
+
return kept.join(" ");
|
|
151
|
+
}
|
|
152
|
+
function basename(token) {
|
|
153
|
+
const slash = token.lastIndexOf("/");
|
|
154
|
+
return slash === -1 ? token : token.slice(slash + 1);
|
|
155
|
+
}
|
|
156
|
+
function matchSegment(segment, list) {
|
|
157
|
+
const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
|
|
158
|
+
let i = 0;
|
|
159
|
+
let hops = 0;
|
|
160
|
+
while (i < tokens.length) {
|
|
161
|
+
while (i < tokens.length && (tokens[i] === "export" || ASSIGN_RE.test(tokens[i]))) i++;
|
|
162
|
+
if (i >= tokens.length) return null;
|
|
163
|
+
const cmd = basename(tokens[i]);
|
|
164
|
+
const significant = [cmd, ...tokens.slice(i + 1).filter((t) => !t.startsWith("-"))];
|
|
165
|
+
for (const entry of list.multiWord) {
|
|
166
|
+
if (entry.length <= significant.length && entry.every((w, j) => significant[j] === w)) {
|
|
167
|
+
return entry.join(" ");
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (list.singleWord.has(cmd)) return cmd;
|
|
171
|
+
if (WRAPPERS.has(cmd) && hops < MAX_WRAPPER_HOPS) {
|
|
172
|
+
hops++;
|
|
173
|
+
i++;
|
|
174
|
+
while (i < tokens.length && (tokens[i].startsWith("-") || ASSIGN_RE.test(tokens[i]))) i++;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
function collectMatches(command, list, matches, depth) {
|
|
182
|
+
if (depth > MAX_RECURSION_DEPTH || command.length > MAX_COMMAND_LENGTH) return;
|
|
183
|
+
let text = command.toLowerCase();
|
|
184
|
+
text = stripHeredocBodies(text);
|
|
185
|
+
const recurse = (inner) => collectMatches(inner, list, matches, depth + 1);
|
|
186
|
+
text = extractDollarSubstitutions(text, recurse);
|
|
187
|
+
text = extractBacktickSubstitutions(text, recurse);
|
|
188
|
+
text = text.replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
|
|
189
|
+
text = text.replace(/(^|\s)#[^\n]*/g, " ");
|
|
190
|
+
const segments = text.split(/\|\||&&|[;|&\n]|[({]/);
|
|
191
|
+
for (const segment of segments) {
|
|
192
|
+
const matched = matchSegment(segment, list);
|
|
193
|
+
if (matched) matches.add(matched);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function matchHighRiskCommands(command, highRiskCommands = HIGH_RISK_COMMANDS) {
|
|
197
|
+
if (!command) return [];
|
|
198
|
+
const matches = /* @__PURE__ */ new Set();
|
|
199
|
+
collectMatches(command, buildRiskList(highRiskCommands), matches, 0);
|
|
200
|
+
return [...matches];
|
|
201
|
+
}
|
|
202
|
+
function isLocalhostOnlyNetworkCommand(command) {
|
|
203
|
+
const text = command.toLowerCase();
|
|
204
|
+
const tokens = text.split(/\s+/).map((t) => t.replace(/^["'`]+|["'`,;)]+$/g, ""));
|
|
205
|
+
let localCount = 0;
|
|
206
|
+
for (const token of tokens) {
|
|
207
|
+
let host = null;
|
|
208
|
+
if (token.includes("://")) {
|
|
209
|
+
let rest = token.slice(token.indexOf("://") + 3);
|
|
210
|
+
const at = rest.indexOf("@");
|
|
211
|
+
if (at !== -1) rest = rest.slice(at + 1);
|
|
212
|
+
host = rest.split(/[/?#]/)[0];
|
|
213
|
+
} else if (/^[a-z0-9.[\]:-]+:\d+(\/.*)?$/.test(token)) {
|
|
214
|
+
host = token.split("/")[0];
|
|
215
|
+
} else if (/^(localhost|127\.0\.0\.1|\[?::1\]?)(\/.*)?$/.test(token)) {
|
|
216
|
+
host = token.split("/")[0];
|
|
217
|
+
}
|
|
218
|
+
if (host === null) continue;
|
|
219
|
+
const bare = host.replace(/:\d+$/, "").replace(/^\[|\]$/g, "");
|
|
220
|
+
if (LOCAL_HOST_RE.test(bare)) {
|
|
221
|
+
localCount++;
|
|
222
|
+
} else {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return localCount > 0;
|
|
227
|
+
}
|
|
228
|
+
|
|
71
229
|
// src/local-evaluator/evaluator.ts
|
|
72
230
|
var LocalEvaluator = class {
|
|
73
231
|
constructor(policy) {
|
|
@@ -218,12 +376,10 @@ var LocalEvaluator = class {
|
|
|
218
376
|
return score;
|
|
219
377
|
}
|
|
220
378
|
if (context.purpose === "shell.exec" && context.target) {
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
break;
|
|
226
|
-
}
|
|
379
|
+
const matched = matchHighRiskCommands(context.target);
|
|
380
|
+
const localProbeOnly = matched.length > 0 && matched.every((cmd) => cmd === "curl" || cmd === "wget") && isLocalhostOnlyNetworkCommand(context.target);
|
|
381
|
+
if (matched.length > 0 && !localProbeOnly) {
|
|
382
|
+
score = Math.max(score, 80);
|
|
227
383
|
}
|
|
228
384
|
}
|
|
229
385
|
if ((context.purpose === "file.read" || context.purpose === "file.write" || context.purpose === "file.delete") && context.target) {
|
|
@@ -3534,7 +3690,7 @@ function getTrustLevel(score) {
|
|
|
3534
3690
|
}
|
|
3535
3691
|
|
|
3536
3692
|
// src/version.ts
|
|
3537
|
-
var SDK_VERSION = "3.
|
|
3693
|
+
var SDK_VERSION = "3.11.0";
|
|
3538
3694
|
|
|
3539
3695
|
// src/well-known.ts
|
|
3540
3696
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -4049,10 +4205,12 @@ function toGatewayConfig(config) {
|
|
|
4049
4205
|
};
|
|
4050
4206
|
}
|
|
4051
4207
|
function toVerificationRequest(context, astraId) {
|
|
4208
|
+
const dotIndex = context.purpose.indexOf(".");
|
|
4209
|
+
const dotted = dotIndex > 0;
|
|
4052
4210
|
return {
|
|
4053
4211
|
credentials: { astraId },
|
|
4054
|
-
purpose: context.purpose,
|
|
4055
|
-
action: context.action,
|
|
4212
|
+
purpose: dotted ? context.purpose.slice(0, dotIndex) : context.purpose,
|
|
4213
|
+
action: dotted ? context.purpose : context.action,
|
|
4056
4214
|
resourceType: context.resourceType,
|
|
4057
4215
|
resource: context.target,
|
|
4058
4216
|
transactionValue: context.transactionValue,
|
|
@@ -4092,8 +4250,75 @@ async function verifyOnline(config, context, astraId) {
|
|
|
4092
4250
|
// src/gateway/modes/hybrid.ts
|
|
4093
4251
|
var import_fs2 = require("fs");
|
|
4094
4252
|
var import_path = require("path");
|
|
4253
|
+
|
|
4254
|
+
// src/gateway/redact.ts
|
|
4255
|
+
var marker = (type2) => `[REDACTED:${type2}]`;
|
|
4256
|
+
var REDACTION_PATTERNS = [
|
|
4257
|
+
// Multi-line blocks first so later single-line patterns don't shred them.
|
|
4258
|
+
{
|
|
4259
|
+
type: "private-key",
|
|
4260
|
+
re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g
|
|
4261
|
+
},
|
|
4262
|
+
// URL userinfo credentials (https://user:pass@, x-access-token:ghp_…@).
|
|
4263
|
+
{
|
|
4264
|
+
type: "url-credentials",
|
|
4265
|
+
re: /([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+:[^\s/@]+@/gi,
|
|
4266
|
+
replacement: `$1${marker("url-credentials")}@`
|
|
4267
|
+
},
|
|
4268
|
+
// Known token formats.
|
|
4269
|
+
{ type: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{20,255}\b/g },
|
|
4270
|
+
{ type: "github-token", re: /\bgithub_pat_[A-Za-z0-9_]{20,255}\b/g },
|
|
4271
|
+
{ type: "astrasync-key", re: /\bkya_[A-Fa-f0-9]{32,128}\b/g },
|
|
4272
|
+
{ type: "openai-key", re: /\bsk-[A-Za-z0-9_-]{20,}\b/g },
|
|
4273
|
+
{ type: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/g },
|
|
4274
|
+
{ type: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g },
|
|
4275
|
+
// Bearer/token headers.
|
|
4276
|
+
{
|
|
4277
|
+
type: "bearer-token",
|
|
4278
|
+
re: /\b(bearer\s+)[a-z0-9._~+/=-]{16,}/gi,
|
|
4279
|
+
replacement: `$1${marker("bearer-token")}`
|
|
4280
|
+
},
|
|
4281
|
+
// Generic KEY=value / key: value assignments with secret-ish names.
|
|
4282
|
+
{
|
|
4283
|
+
type: "assigned-secret",
|
|
4284
|
+
re: /\b((?:password|passwd|secret|token|api[_-]?key|apikey|access[_-]?key|client[_-]?secret|private[_-]?key)\s*[=:]\s*)(['"]?)[^\s'"&;]{8,}\2/gi,
|
|
4285
|
+
replacement: `$1$2${marker("assigned-secret")}$2`
|
|
4286
|
+
}
|
|
4287
|
+
];
|
|
4288
|
+
function redactSecrets(text) {
|
|
4289
|
+
let result = text;
|
|
4290
|
+
for (const { type: type2, re, replacement } of REDACTION_PATTERNS) {
|
|
4291
|
+
result = result.replace(re, replacement ?? marker(type2));
|
|
4292
|
+
}
|
|
4293
|
+
return result;
|
|
4294
|
+
}
|
|
4295
|
+
function redactDeep(value) {
|
|
4296
|
+
if (typeof value === "string") {
|
|
4297
|
+
return redactSecrets(value);
|
|
4298
|
+
}
|
|
4299
|
+
if (Array.isArray(value)) {
|
|
4300
|
+
return value.map((item) => redactDeep(item));
|
|
4301
|
+
}
|
|
4302
|
+
if (value !== null && typeof value === "object" && !(value instanceof Date)) {
|
|
4303
|
+
const out = {};
|
|
4304
|
+
for (const [k, v] of Object.entries(value)) {
|
|
4305
|
+
out[k] = redactDeep(v);
|
|
4306
|
+
}
|
|
4307
|
+
return out;
|
|
4308
|
+
}
|
|
4309
|
+
return value;
|
|
4310
|
+
}
|
|
4311
|
+
|
|
4312
|
+
// src/gateway/modes/hybrid.ts
|
|
4095
4313
|
var MAX_QUEUE_SIZE = 1e3;
|
|
4096
4314
|
var MAX_RETRIES = 3;
|
|
4315
|
+
var FLUSH_TIMEOUT = /* @__PURE__ */ Symbol("flush-timeout");
|
|
4316
|
+
function delay(ms) {
|
|
4317
|
+
return new Promise((resolve) => {
|
|
4318
|
+
const timer = setTimeout(() => resolve(FLUSH_TIMEOUT), ms);
|
|
4319
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
4320
|
+
});
|
|
4321
|
+
}
|
|
4097
4322
|
var HybridSyncQueue = class {
|
|
4098
4323
|
constructor(config, persistPath) {
|
|
4099
4324
|
this.queue = [];
|
|
@@ -4114,12 +4339,12 @@ var HybridSyncQueue = class {
|
|
|
4114
4339
|
/**
|
|
4115
4340
|
* Stop periodic sync and flush remaining entries.
|
|
4116
4341
|
*/
|
|
4117
|
-
async stop() {
|
|
4342
|
+
async stop(flushOptions = {}) {
|
|
4118
4343
|
if (this.timer) {
|
|
4119
4344
|
clearInterval(this.timer);
|
|
4120
4345
|
this.timer = null;
|
|
4121
4346
|
}
|
|
4122
|
-
await this.flush();
|
|
4347
|
+
await this.flush(flushOptions);
|
|
4123
4348
|
this.saveToDisk();
|
|
4124
4349
|
}
|
|
4125
4350
|
/**
|
|
@@ -4131,8 +4356,8 @@ var HybridSyncQueue = class {
|
|
|
4131
4356
|
}
|
|
4132
4357
|
this.queue.push({
|
|
4133
4358
|
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
4134
|
-
context,
|
|
4135
|
-
decision,
|
|
4359
|
+
context: redactDeep(context),
|
|
4360
|
+
decision: redactDeep(decision),
|
|
4136
4361
|
timestamp: /* @__PURE__ */ new Date(),
|
|
4137
4362
|
retryCount: 0,
|
|
4138
4363
|
status: "pending"
|
|
@@ -4142,16 +4367,21 @@ var HybridSyncQueue = class {
|
|
|
4142
4367
|
/**
|
|
4143
4368
|
* Flush pending entries to the API.
|
|
4144
4369
|
*/
|
|
4145
|
-
async flush() {
|
|
4370
|
+
async flush(options = {}) {
|
|
4146
4371
|
if (this.processing) return;
|
|
4147
4372
|
this.processing = true;
|
|
4148
4373
|
try {
|
|
4149
4374
|
const pending = this.queue.filter((e) => e.status === "pending");
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4375
|
+
const batch = typeof options.limit === "number" ? pending.slice(0, options.limit) : pending;
|
|
4376
|
+
for (const entry of batch) {
|
|
4377
|
+
const attempt = verifyOnline(this.config, entry.context, this.config.agentId).then(() => "synced").catch(() => "error");
|
|
4378
|
+
const result = options.perEntryTimeoutMs ? await Promise.race([attempt, delay(options.perEntryTimeoutMs)]) : await attempt;
|
|
4379
|
+
if (result === FLUSH_TIMEOUT) {
|
|
4380
|
+
break;
|
|
4381
|
+
}
|
|
4382
|
+
if (result === "synced") {
|
|
4153
4383
|
entry.status = "synced";
|
|
4154
|
-
}
|
|
4384
|
+
} else {
|
|
4155
4385
|
entry.retryCount++;
|
|
4156
4386
|
if (entry.retryCount >= MAX_RETRIES) {
|
|
4157
4387
|
entry.status = "failed";
|
|
@@ -4228,8 +4458,22 @@ var TraceLogger = class {
|
|
|
4228
4458
|
id: this.generateId(),
|
|
4229
4459
|
timestamp: /* @__PURE__ */ new Date(),
|
|
4230
4460
|
type: "evaluation",
|
|
4231
|
-
context,
|
|
4232
|
-
decision
|
|
4461
|
+
context: redactDeep(context),
|
|
4462
|
+
decision: redactDeep(decision)
|
|
4463
|
+
});
|
|
4464
|
+
}
|
|
4465
|
+
/**
|
|
4466
|
+
* Log a tool call the guard saw but did not govern. One line per skip
|
|
4467
|
+
* keeps coverage holes (unmapped platform tools) visible in the traces
|
|
4468
|
+
* instead of silently indistinguishable from ALLOW.
|
|
4469
|
+
*/
|
|
4470
|
+
logSkipped(toolName, reason) {
|
|
4471
|
+
if (!this.enabled) return;
|
|
4472
|
+
this.write({
|
|
4473
|
+
id: this.generateId(),
|
|
4474
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
4475
|
+
type: "skipped",
|
|
4476
|
+
metadata: { toolName, reason }
|
|
4233
4477
|
});
|
|
4234
4478
|
}
|
|
4235
4479
|
/**
|
|
@@ -4346,10 +4590,12 @@ var AstraSyncGateway = class {
|
|
|
4346
4590
|
}
|
|
4347
4591
|
/**
|
|
4348
4592
|
* Graceful shutdown. Flushes sync queue and cleans up resources.
|
|
4593
|
+
* Stateless callers (hooks) pass bounded flush options so sync never
|
|
4594
|
+
* sits on a tool call's critical path.
|
|
4349
4595
|
*/
|
|
4350
|
-
async shutdown() {
|
|
4596
|
+
async shutdown(options) {
|
|
4351
4597
|
if (this.syncQueue) {
|
|
4352
|
-
await this.syncQueue.stop();
|
|
4598
|
+
await this.syncQueue.stop(options?.syncFlush ?? {});
|
|
4353
4599
|
this.syncQueue = null;
|
|
4354
4600
|
}
|
|
4355
4601
|
this.evaluator = null;
|
|
@@ -4372,7 +4618,7 @@ var AstraSyncGateway = class {
|
|
|
4372
4618
|
let decision;
|
|
4373
4619
|
switch (this.config.mode) {
|
|
4374
4620
|
case "online":
|
|
4375
|
-
decision = await verifyOnline(this.config, context, astraId);
|
|
4621
|
+
decision = await verifyOnline(this.config, context, astraId ?? this.config.agentId);
|
|
4376
4622
|
break;
|
|
4377
4623
|
case "local":
|
|
4378
4624
|
if (!this.evaluator) throw new Error("Local evaluator not initialized");
|
|
@@ -4427,6 +4673,13 @@ var AstraSyncGateway = class {
|
|
|
4427
4673
|
getSyncStatus() {
|
|
4428
4674
|
return this.syncQueue?.getStatus() || null;
|
|
4429
4675
|
}
|
|
4676
|
+
/**
|
|
4677
|
+
* Record a tool call the calling adapter saw but did not govern
|
|
4678
|
+
* (unmapped tool, skipTools, MCP with governMcp off). Trace-only.
|
|
4679
|
+
*/
|
|
4680
|
+
logSkippedTool(toolName, reason) {
|
|
4681
|
+
this.traceLogger?.logSkipped(toolName, reason);
|
|
4682
|
+
}
|
|
4430
4683
|
};
|
|
4431
4684
|
|
|
4432
4685
|
// src/adapter-interface/interface.ts
|
|
@@ -4518,6 +4771,11 @@ function extractNetworkDomains(target) {
|
|
|
4518
4771
|
// src/claude-code/tool-mapping.ts
|
|
4519
4772
|
var CLAUDE_CODE_TOOL_MAP = {
|
|
4520
4773
|
Bash: "shell.exec",
|
|
4774
|
+
// Native Windows ships shell execution as a distinct PowerShell tool
|
|
4775
|
+
// (hooks docs: "Tool Names in PreToolUse"). Found live on 10 Jul 2026 —
|
|
4776
|
+
// the desktop-app runtime verification showed shell commands bypassing
|
|
4777
|
+
// the guard entirely on Windows because only Bash was mapped/matched.
|
|
4778
|
+
PowerShell: "shell.exec",
|
|
4521
4779
|
Write: "file.write",
|
|
4522
4780
|
Edit: "file.write",
|
|
4523
4781
|
MultiEdit: "file.write",
|
|
@@ -4662,7 +4920,10 @@ var ClaudeCodeAdapter = class {
|
|
|
4662
4920
|
timestamp: /* @__PURE__ */ new Date()
|
|
4663
4921
|
};
|
|
4664
4922
|
const interception = await this.interceptAction(action);
|
|
4665
|
-
if (!interception.intercepted || !interception.context)
|
|
4923
|
+
if (!interception.intercepted || !interception.context) {
|
|
4924
|
+
this.gateway.logSkippedTool(payload.tool_name, interception.skipReason ?? "not intercepted");
|
|
4925
|
+
return null;
|
|
4926
|
+
}
|
|
4666
4927
|
const decision = await this.gateway.evaluate(interception.context);
|
|
4667
4928
|
return this.decisionToHookOutput(decision);
|
|
4668
4929
|
}
|
|
@@ -4674,9 +4935,10 @@ var ClaudeCodeAdapter = class {
|
|
|
4674
4935
|
}
|
|
4675
4936
|
};
|
|
4676
4937
|
|
|
4677
|
-
// src/claude-code/
|
|
4678
|
-
var
|
|
4679
|
-
var
|
|
4938
|
+
// src/claude-code/guard-config.ts
|
|
4939
|
+
var import_fs4 = require("fs");
|
|
4940
|
+
var import_os = require("os");
|
|
4941
|
+
var import_path3 = require("path");
|
|
4680
4942
|
function resolveGuardConfig(env, payloadCwd, home = (0, import_os.homedir)()) {
|
|
4681
4943
|
const candidateRoots = [env.CLAUDE_PROJECT_DIR, payloadCwd, home].filter(
|
|
4682
4944
|
(root) => typeof root === "string" && root.length > 0
|
|
@@ -4698,6 +4960,12 @@ function resolveGuardConfig(env, payloadCwd, home = (0, import_os.homedir)()) {
|
|
|
4698
4960
|
}
|
|
4699
4961
|
return null;
|
|
4700
4962
|
}
|
|
4963
|
+
|
|
4964
|
+
// src/claude-code/hook.ts
|
|
4965
|
+
var MAX_STDIN_BYTES = 1024 * 1024;
|
|
4966
|
+
var STDIN_TIMEOUT_MS = 5e3;
|
|
4967
|
+
var HYBRID_FLUSH_LIMIT = 3;
|
|
4968
|
+
var HYBRID_FLUSH_TIMEOUT_MS = 1500;
|
|
4701
4969
|
function denyOutput(reason) {
|
|
4702
4970
|
return {
|
|
4703
4971
|
hookSpecificOutput: {
|
|
@@ -4729,13 +4997,25 @@ async function processHookPayload(payloadJson, options = {}) {
|
|
|
4729
4997
|
}
|
|
4730
4998
|
const failOpen = envFailOpen || resolved.settings.failOpen === true;
|
|
4731
4999
|
try {
|
|
5000
|
+
let mode = resolved.settings.mode === "hybrid" ? "hybrid" : "local";
|
|
5001
|
+
if (mode === "hybrid" && !(resolved.settings.apiBaseUrl && resolved.settings.apiKey)) {
|
|
5002
|
+
console.error(
|
|
5003
|
+
"AstraSync Local Guard: guard.json requests hybrid mode but apiBaseUrl/apiKey are missing \u2014 running local-only"
|
|
5004
|
+
);
|
|
5005
|
+
mode = "local";
|
|
5006
|
+
}
|
|
4732
5007
|
const gatewayConfig = {
|
|
4733
|
-
mode
|
|
5008
|
+
mode,
|
|
4734
5009
|
policyFile: resolved.policyFile,
|
|
4735
5010
|
posture: resolved.settings.posture ?? "active",
|
|
4736
5011
|
traceEnabled: true,
|
|
4737
|
-
tracePath: (0,
|
|
4738
|
-
debug: false
|
|
5012
|
+
tracePath: (0, import_path4.join)(resolved.guardDir, "traces"),
|
|
5013
|
+
debug: false,
|
|
5014
|
+
...mode === "hybrid" && {
|
|
5015
|
+
apiBaseUrl: resolved.settings.apiBaseUrl,
|
|
5016
|
+
apiKey: resolved.settings.apiKey,
|
|
5017
|
+
agentId: resolved.settings.agentId
|
|
5018
|
+
}
|
|
4739
5019
|
};
|
|
4740
5020
|
const gateway = options.gatewayFactory ? options.gatewayFactory(gatewayConfig) : new AstraSyncGateway(gatewayConfig);
|
|
4741
5021
|
await gateway.initialize();
|
|
@@ -4748,7 +5028,9 @@ async function processHookPayload(payloadJson, options = {}) {
|
|
|
4748
5028
|
await adapter.initialize({ gateway, adapterOptions: {} });
|
|
4749
5029
|
const output = await adapter.evaluatePayload(payload);
|
|
4750
5030
|
await adapter.shutdown();
|
|
4751
|
-
await gateway.shutdown(
|
|
5031
|
+
await gateway.shutdown({
|
|
5032
|
+
syncFlush: { limit: HYBRID_FLUSH_LIMIT, perEntryTimeoutMs: HYBRID_FLUSH_TIMEOUT_MS }
|
|
5033
|
+
});
|
|
4752
5034
|
return { output, exitCode: 0 };
|
|
4753
5035
|
} catch (err) {
|
|
4754
5036
|
if (failOpen) return { output: null, exitCode: 0 };
|