@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
|
@@ -4185,7 +4185,9 @@ var HIGH_RISK_COMMANDS = [
|
|
|
4185
4185
|
"git push",
|
|
4186
4186
|
"npm publish",
|
|
4187
4187
|
"docker",
|
|
4188
|
-
"kubectl"
|
|
4188
|
+
"kubectl",
|
|
4189
|
+
"gh pr create",
|
|
4190
|
+
"gh pr merge"
|
|
4189
4191
|
];
|
|
4190
4192
|
var SENSITIVE_PATHS = [
|
|
4191
4193
|
".ssh",
|
|
@@ -4204,6 +4206,164 @@ var SENSITIVE_PATHS = [
|
|
|
4204
4206
|
".pypirc"
|
|
4205
4207
|
];
|
|
4206
4208
|
|
|
4209
|
+
// src/local-evaluator/command-matcher.ts
|
|
4210
|
+
var WRAPPERS = /* @__PURE__ */ new Set([
|
|
4211
|
+
"sudo",
|
|
4212
|
+
"doas",
|
|
4213
|
+
"env",
|
|
4214
|
+
"nohup",
|
|
4215
|
+
"time",
|
|
4216
|
+
"timeout",
|
|
4217
|
+
"nice",
|
|
4218
|
+
"ionice",
|
|
4219
|
+
"stdbuf",
|
|
4220
|
+
"xargs",
|
|
4221
|
+
"command",
|
|
4222
|
+
"exec",
|
|
4223
|
+
"builtin"
|
|
4224
|
+
]);
|
|
4225
|
+
var ASSIGN_RE = /^[a-z_][a-z0-9_]*=/;
|
|
4226
|
+
var MAX_WRAPPER_HOPS = 4;
|
|
4227
|
+
var MAX_RECURSION_DEPTH = 3;
|
|
4228
|
+
var MAX_COMMAND_LENGTH = 1e5;
|
|
4229
|
+
var LOCAL_HOST_RE = /^(localhost|127\.0\.0\.1|::1|[a-z0-9.-]*\.localhost)$/;
|
|
4230
|
+
function buildRiskList(entries) {
|
|
4231
|
+
const singleWord = /* @__PURE__ */ new Set();
|
|
4232
|
+
const multiWord = [];
|
|
4233
|
+
for (const entry of entries) {
|
|
4234
|
+
const words = entry.toLowerCase().trim().split(/\s+/);
|
|
4235
|
+
if (words.length === 1) singleWord.add(words[0]);
|
|
4236
|
+
else multiWord.push(words);
|
|
4237
|
+
}
|
|
4238
|
+
multiWord.sort((a, b) => b.length - a.length);
|
|
4239
|
+
return { singleWord, multiWord };
|
|
4240
|
+
}
|
|
4241
|
+
function stripHeredocBodies(text) {
|
|
4242
|
+
const lines = text.split("\n");
|
|
4243
|
+
const kept = [];
|
|
4244
|
+
let terminator = null;
|
|
4245
|
+
for (const line of lines) {
|
|
4246
|
+
if (terminator !== null) {
|
|
4247
|
+
if (line.trim() === terminator) terminator = null;
|
|
4248
|
+
continue;
|
|
4249
|
+
}
|
|
4250
|
+
kept.push(line);
|
|
4251
|
+
const heredoc = line.match(/<<-?\s*['"]?(\w+)['"]?/);
|
|
4252
|
+
if (heredoc) terminator = heredoc[1];
|
|
4253
|
+
}
|
|
4254
|
+
return kept.join("\n");
|
|
4255
|
+
}
|
|
4256
|
+
function extractDollarSubstitutions(text, collect) {
|
|
4257
|
+
let result = text;
|
|
4258
|
+
let start = result.indexOf("$(");
|
|
4259
|
+
let iterations = 0;
|
|
4260
|
+
while (start !== -1 && iterations < 50) {
|
|
4261
|
+
iterations++;
|
|
4262
|
+
let depth = 0;
|
|
4263
|
+
let end = result.length;
|
|
4264
|
+
for (let i = start + 1; i < result.length; i++) {
|
|
4265
|
+
if (result[i] === "(") depth++;
|
|
4266
|
+
else if (result[i] === ")") {
|
|
4267
|
+
depth--;
|
|
4268
|
+
if (depth === 0) {
|
|
4269
|
+
end = i;
|
|
4270
|
+
break;
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
collect(result.slice(start + 2, end));
|
|
4275
|
+
result = `${result.slice(0, start)} ${result.slice(Math.min(end + 1, result.length))}`;
|
|
4276
|
+
start = result.indexOf("$(");
|
|
4277
|
+
}
|
|
4278
|
+
return result;
|
|
4279
|
+
}
|
|
4280
|
+
function extractBacktickSubstitutions(text, collect) {
|
|
4281
|
+
if (!text.includes("`")) return text;
|
|
4282
|
+
const chunks = text.split("`");
|
|
4283
|
+
const kept = [];
|
|
4284
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
4285
|
+
if (i % 2 === 1) collect(chunks[i]);
|
|
4286
|
+
else kept.push(chunks[i]);
|
|
4287
|
+
}
|
|
4288
|
+
return kept.join(" ");
|
|
4289
|
+
}
|
|
4290
|
+
function basename(token) {
|
|
4291
|
+
const slash = token.lastIndexOf("/");
|
|
4292
|
+
return slash === -1 ? token : token.slice(slash + 1);
|
|
4293
|
+
}
|
|
4294
|
+
function matchSegment(segment, list) {
|
|
4295
|
+
const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
|
|
4296
|
+
let i = 0;
|
|
4297
|
+
let hops = 0;
|
|
4298
|
+
while (i < tokens.length) {
|
|
4299
|
+
while (i < tokens.length && (tokens[i] === "export" || ASSIGN_RE.test(tokens[i]))) i++;
|
|
4300
|
+
if (i >= tokens.length) return null;
|
|
4301
|
+
const cmd = basename(tokens[i]);
|
|
4302
|
+
const significant = [cmd, ...tokens.slice(i + 1).filter((t) => !t.startsWith("-"))];
|
|
4303
|
+
for (const entry of list.multiWord) {
|
|
4304
|
+
if (entry.length <= significant.length && entry.every((w, j) => significant[j] === w)) {
|
|
4305
|
+
return entry.join(" ");
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
if (list.singleWord.has(cmd)) return cmd;
|
|
4309
|
+
if (WRAPPERS.has(cmd) && hops < MAX_WRAPPER_HOPS) {
|
|
4310
|
+
hops++;
|
|
4311
|
+
i++;
|
|
4312
|
+
while (i < tokens.length && (tokens[i].startsWith("-") || ASSIGN_RE.test(tokens[i]))) i++;
|
|
4313
|
+
continue;
|
|
4314
|
+
}
|
|
4315
|
+
return null;
|
|
4316
|
+
}
|
|
4317
|
+
return null;
|
|
4318
|
+
}
|
|
4319
|
+
function collectMatches(command, list, matches, depth) {
|
|
4320
|
+
if (depth > MAX_RECURSION_DEPTH || command.length > MAX_COMMAND_LENGTH) return;
|
|
4321
|
+
let text = command.toLowerCase();
|
|
4322
|
+
text = stripHeredocBodies(text);
|
|
4323
|
+
const recurse = (inner) => collectMatches(inner, list, matches, depth + 1);
|
|
4324
|
+
text = extractDollarSubstitutions(text, recurse);
|
|
4325
|
+
text = extractBacktickSubstitutions(text, recurse);
|
|
4326
|
+
text = text.replace(/'[^']*'/g, " ").replace(/"[^"]*"/g, " ");
|
|
4327
|
+
text = text.replace(/(^|\s)#[^\n]*/g, " ");
|
|
4328
|
+
const segments = text.split(/\|\||&&|[;|&\n]|[({]/);
|
|
4329
|
+
for (const segment of segments) {
|
|
4330
|
+
const matched = matchSegment(segment, list);
|
|
4331
|
+
if (matched) matches.add(matched);
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
function matchHighRiskCommands(command, highRiskCommands = HIGH_RISK_COMMANDS) {
|
|
4335
|
+
if (!command) return [];
|
|
4336
|
+
const matches = /* @__PURE__ */ new Set();
|
|
4337
|
+
collectMatches(command, buildRiskList(highRiskCommands), matches, 0);
|
|
4338
|
+
return [...matches];
|
|
4339
|
+
}
|
|
4340
|
+
function isLocalhostOnlyNetworkCommand(command) {
|
|
4341
|
+
const text = command.toLowerCase();
|
|
4342
|
+
const tokens = text.split(/\s+/).map((t) => t.replace(/^["'`]+|["'`,;)]+$/g, ""));
|
|
4343
|
+
let localCount = 0;
|
|
4344
|
+
for (const token of tokens) {
|
|
4345
|
+
let host = null;
|
|
4346
|
+
if (token.includes("://")) {
|
|
4347
|
+
let rest = token.slice(token.indexOf("://") + 3);
|
|
4348
|
+
const at = rest.indexOf("@");
|
|
4349
|
+
if (at !== -1) rest = rest.slice(at + 1);
|
|
4350
|
+
host = rest.split(/[/?#]/)[0];
|
|
4351
|
+
} else if (/^[a-z0-9.[\]:-]+:\d+(\/.*)?$/.test(token)) {
|
|
4352
|
+
host = token.split("/")[0];
|
|
4353
|
+
} else if (/^(localhost|127\.0\.0\.1|\[?::1\]?)(\/.*)?$/.test(token)) {
|
|
4354
|
+
host = token.split("/")[0];
|
|
4355
|
+
}
|
|
4356
|
+
if (host === null) continue;
|
|
4357
|
+
const bare = host.replace(/:\d+$/, "").replace(/^\[|\]$/g, "");
|
|
4358
|
+
if (LOCAL_HOST_RE.test(bare)) {
|
|
4359
|
+
localCount++;
|
|
4360
|
+
} else {
|
|
4361
|
+
return false;
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
return localCount > 0;
|
|
4365
|
+
}
|
|
4366
|
+
|
|
4207
4367
|
// src/local-evaluator/evaluator.ts
|
|
4208
4368
|
var LocalEvaluator = class {
|
|
4209
4369
|
constructor(policy) {
|
|
@@ -4354,12 +4514,10 @@ var LocalEvaluator = class {
|
|
|
4354
4514
|
return score;
|
|
4355
4515
|
}
|
|
4356
4516
|
if (context.purpose === "shell.exec" && context.target) {
|
|
4357
|
-
const
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
break;
|
|
4362
|
-
}
|
|
4517
|
+
const matched = matchHighRiskCommands(context.target);
|
|
4518
|
+
const localProbeOnly = matched.length > 0 && matched.every((cmd) => cmd === "curl" || cmd === "wget") && isLocalhostOnlyNetworkCommand(context.target);
|
|
4519
|
+
if (matched.length > 0 && !localProbeOnly) {
|
|
4520
|
+
score = Math.max(score, 80);
|
|
4363
4521
|
}
|
|
4364
4522
|
}
|
|
4365
4523
|
if ((context.purpose === "file.read" || context.purpose === "file.write" || context.purpose === "file.delete") && context.target) {
|
|
@@ -4440,7 +4598,7 @@ function getTrustLevel(score) {
|
|
|
4440
4598
|
}
|
|
4441
4599
|
|
|
4442
4600
|
// src/version.ts
|
|
4443
|
-
var SDK_VERSION = "3.
|
|
4601
|
+
var SDK_VERSION = "3.11.0";
|
|
4444
4602
|
|
|
4445
4603
|
// src/well-known.ts
|
|
4446
4604
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -4955,10 +5113,12 @@ function toGatewayConfig(config) {
|
|
|
4955
5113
|
};
|
|
4956
5114
|
}
|
|
4957
5115
|
function toVerificationRequest(context, astraId) {
|
|
5116
|
+
const dotIndex = context.purpose.indexOf(".");
|
|
5117
|
+
const dotted = dotIndex > 0;
|
|
4958
5118
|
return {
|
|
4959
5119
|
credentials: { astraId },
|
|
4960
|
-
purpose: context.purpose,
|
|
4961
|
-
action: context.action,
|
|
5120
|
+
purpose: dotted ? context.purpose.slice(0, dotIndex) : context.purpose,
|
|
5121
|
+
action: dotted ? context.purpose : context.action,
|
|
4962
5122
|
resourceType: context.resourceType,
|
|
4963
5123
|
resource: context.target,
|
|
4964
5124
|
transactionValue: context.transactionValue,
|
|
@@ -4998,8 +5158,75 @@ async function verifyOnline(config, context, astraId) {
|
|
|
4998
5158
|
// src/gateway/modes/hybrid.ts
|
|
4999
5159
|
var import_fs2 = require("fs");
|
|
5000
5160
|
var import_path = require("path");
|
|
5161
|
+
|
|
5162
|
+
// src/gateway/redact.ts
|
|
5163
|
+
var marker = (type2) => `[REDACTED:${type2}]`;
|
|
5164
|
+
var REDACTION_PATTERNS = [
|
|
5165
|
+
// Multi-line blocks first so later single-line patterns don't shred them.
|
|
5166
|
+
{
|
|
5167
|
+
type: "private-key",
|
|
5168
|
+
re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g
|
|
5169
|
+
},
|
|
5170
|
+
// URL userinfo credentials (https://user:pass@, x-access-token:ghp_…@).
|
|
5171
|
+
{
|
|
5172
|
+
type: "url-credentials",
|
|
5173
|
+
re: /([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+:[^\s/@]+@/gi,
|
|
5174
|
+
replacement: `$1${marker("url-credentials")}@`
|
|
5175
|
+
},
|
|
5176
|
+
// Known token formats.
|
|
5177
|
+
{ type: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{20,255}\b/g },
|
|
5178
|
+
{ type: "github-token", re: /\bgithub_pat_[A-Za-z0-9_]{20,255}\b/g },
|
|
5179
|
+
{ type: "astrasync-key", re: /\bkya_[A-Fa-f0-9]{32,128}\b/g },
|
|
5180
|
+
{ type: "openai-key", re: /\bsk-[A-Za-z0-9_-]{20,}\b/g },
|
|
5181
|
+
{ type: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/g },
|
|
5182
|
+
{ type: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g },
|
|
5183
|
+
// Bearer/token headers.
|
|
5184
|
+
{
|
|
5185
|
+
type: "bearer-token",
|
|
5186
|
+
re: /\b(bearer\s+)[a-z0-9._~+/=-]{16,}/gi,
|
|
5187
|
+
replacement: `$1${marker("bearer-token")}`
|
|
5188
|
+
},
|
|
5189
|
+
// Generic KEY=value / key: value assignments with secret-ish names.
|
|
5190
|
+
{
|
|
5191
|
+
type: "assigned-secret",
|
|
5192
|
+
re: /\b((?:password|passwd|secret|token|api[_-]?key|apikey|access[_-]?key|client[_-]?secret|private[_-]?key)\s*[=:]\s*)(['"]?)[^\s'"&;]{8,}\2/gi,
|
|
5193
|
+
replacement: `$1$2${marker("assigned-secret")}$2`
|
|
5194
|
+
}
|
|
5195
|
+
];
|
|
5196
|
+
function redactSecrets(text) {
|
|
5197
|
+
let result = text;
|
|
5198
|
+
for (const { type: type2, re, replacement } of REDACTION_PATTERNS) {
|
|
5199
|
+
result = result.replace(re, replacement ?? marker(type2));
|
|
5200
|
+
}
|
|
5201
|
+
return result;
|
|
5202
|
+
}
|
|
5203
|
+
function redactDeep(value) {
|
|
5204
|
+
if (typeof value === "string") {
|
|
5205
|
+
return redactSecrets(value);
|
|
5206
|
+
}
|
|
5207
|
+
if (Array.isArray(value)) {
|
|
5208
|
+
return value.map((item) => redactDeep(item));
|
|
5209
|
+
}
|
|
5210
|
+
if (value !== null && typeof value === "object" && !(value instanceof Date)) {
|
|
5211
|
+
const out = {};
|
|
5212
|
+
for (const [k, v] of Object.entries(value)) {
|
|
5213
|
+
out[k] = redactDeep(v);
|
|
5214
|
+
}
|
|
5215
|
+
return out;
|
|
5216
|
+
}
|
|
5217
|
+
return value;
|
|
5218
|
+
}
|
|
5219
|
+
|
|
5220
|
+
// src/gateway/modes/hybrid.ts
|
|
5001
5221
|
var MAX_QUEUE_SIZE = 1e3;
|
|
5002
5222
|
var MAX_RETRIES = 3;
|
|
5223
|
+
var FLUSH_TIMEOUT = /* @__PURE__ */ Symbol("flush-timeout");
|
|
5224
|
+
function delay(ms) {
|
|
5225
|
+
return new Promise((resolve) => {
|
|
5226
|
+
const timer = setTimeout(() => resolve(FLUSH_TIMEOUT), ms);
|
|
5227
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
5228
|
+
});
|
|
5229
|
+
}
|
|
5003
5230
|
var HybridSyncQueue = class {
|
|
5004
5231
|
constructor(config, persistPath) {
|
|
5005
5232
|
this.queue = [];
|
|
@@ -5020,12 +5247,12 @@ var HybridSyncQueue = class {
|
|
|
5020
5247
|
/**
|
|
5021
5248
|
* Stop periodic sync and flush remaining entries.
|
|
5022
5249
|
*/
|
|
5023
|
-
async stop() {
|
|
5250
|
+
async stop(flushOptions = {}) {
|
|
5024
5251
|
if (this.timer) {
|
|
5025
5252
|
clearInterval(this.timer);
|
|
5026
5253
|
this.timer = null;
|
|
5027
5254
|
}
|
|
5028
|
-
await this.flush();
|
|
5255
|
+
await this.flush(flushOptions);
|
|
5029
5256
|
this.saveToDisk();
|
|
5030
5257
|
}
|
|
5031
5258
|
/**
|
|
@@ -5037,8 +5264,8 @@ var HybridSyncQueue = class {
|
|
|
5037
5264
|
}
|
|
5038
5265
|
this.queue.push({
|
|
5039
5266
|
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
5040
|
-
context,
|
|
5041
|
-
decision,
|
|
5267
|
+
context: redactDeep(context),
|
|
5268
|
+
decision: redactDeep(decision),
|
|
5042
5269
|
timestamp: /* @__PURE__ */ new Date(),
|
|
5043
5270
|
retryCount: 0,
|
|
5044
5271
|
status: "pending"
|
|
@@ -5048,16 +5275,21 @@ var HybridSyncQueue = class {
|
|
|
5048
5275
|
/**
|
|
5049
5276
|
* Flush pending entries to the API.
|
|
5050
5277
|
*/
|
|
5051
|
-
async flush() {
|
|
5278
|
+
async flush(options = {}) {
|
|
5052
5279
|
if (this.processing) return;
|
|
5053
5280
|
this.processing = true;
|
|
5054
5281
|
try {
|
|
5055
5282
|
const pending = this.queue.filter((e) => e.status === "pending");
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5283
|
+
const batch = typeof options.limit === "number" ? pending.slice(0, options.limit) : pending;
|
|
5284
|
+
for (const entry of batch) {
|
|
5285
|
+
const attempt = verifyOnline(this.config, entry.context, this.config.agentId).then(() => "synced").catch(() => "error");
|
|
5286
|
+
const result = options.perEntryTimeoutMs ? await Promise.race([attempt, delay(options.perEntryTimeoutMs)]) : await attempt;
|
|
5287
|
+
if (result === FLUSH_TIMEOUT) {
|
|
5288
|
+
break;
|
|
5289
|
+
}
|
|
5290
|
+
if (result === "synced") {
|
|
5059
5291
|
entry.status = "synced";
|
|
5060
|
-
}
|
|
5292
|
+
} else {
|
|
5061
5293
|
entry.retryCount++;
|
|
5062
5294
|
if (entry.retryCount >= MAX_RETRIES) {
|
|
5063
5295
|
entry.status = "failed";
|
|
@@ -5134,8 +5366,22 @@ var TraceLogger = class {
|
|
|
5134
5366
|
id: this.generateId(),
|
|
5135
5367
|
timestamp: /* @__PURE__ */ new Date(),
|
|
5136
5368
|
type: "evaluation",
|
|
5137
|
-
context,
|
|
5138
|
-
decision
|
|
5369
|
+
context: redactDeep(context),
|
|
5370
|
+
decision: redactDeep(decision)
|
|
5371
|
+
});
|
|
5372
|
+
}
|
|
5373
|
+
/**
|
|
5374
|
+
* Log a tool call the guard saw but did not govern. One line per skip
|
|
5375
|
+
* keeps coverage holes (unmapped platform tools) visible in the traces
|
|
5376
|
+
* instead of silently indistinguishable from ALLOW.
|
|
5377
|
+
*/
|
|
5378
|
+
logSkipped(toolName, reason) {
|
|
5379
|
+
if (!this.enabled) return;
|
|
5380
|
+
this.write({
|
|
5381
|
+
id: this.generateId(),
|
|
5382
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
5383
|
+
type: "skipped",
|
|
5384
|
+
metadata: { toolName, reason }
|
|
5139
5385
|
});
|
|
5140
5386
|
}
|
|
5141
5387
|
/**
|
|
@@ -5252,10 +5498,12 @@ var AstraSyncGateway = class {
|
|
|
5252
5498
|
}
|
|
5253
5499
|
/**
|
|
5254
5500
|
* Graceful shutdown. Flushes sync queue and cleans up resources.
|
|
5501
|
+
* Stateless callers (hooks) pass bounded flush options so sync never
|
|
5502
|
+
* sits on a tool call's critical path.
|
|
5255
5503
|
*/
|
|
5256
|
-
async shutdown() {
|
|
5504
|
+
async shutdown(options) {
|
|
5257
5505
|
if (this.syncQueue) {
|
|
5258
|
-
await this.syncQueue.stop();
|
|
5506
|
+
await this.syncQueue.stop(options?.syncFlush ?? {});
|
|
5259
5507
|
this.syncQueue = null;
|
|
5260
5508
|
}
|
|
5261
5509
|
this.evaluator = null;
|
|
@@ -5278,7 +5526,7 @@ var AstraSyncGateway = class {
|
|
|
5278
5526
|
let decision;
|
|
5279
5527
|
switch (this.config.mode) {
|
|
5280
5528
|
case "online":
|
|
5281
|
-
decision = await verifyOnline(this.config, context, astraId);
|
|
5529
|
+
decision = await verifyOnline(this.config, context, astraId ?? this.config.agentId);
|
|
5282
5530
|
break;
|
|
5283
5531
|
case "local":
|
|
5284
5532
|
if (!this.evaluator) throw new Error("Local evaluator not initialized");
|
|
@@ -5333,6 +5581,13 @@ var AstraSyncGateway = class {
|
|
|
5333
5581
|
getSyncStatus() {
|
|
5334
5582
|
return this.syncQueue?.getStatus() || null;
|
|
5335
5583
|
}
|
|
5584
|
+
/**
|
|
5585
|
+
* Record a tool call the calling adapter saw but did not govern
|
|
5586
|
+
* (unmapped tool, skipTools, MCP with governMcp off). Trace-only.
|
|
5587
|
+
*/
|
|
5588
|
+
logSkippedTool(toolName, reason) {
|
|
5589
|
+
this.traceLogger?.logSkipped(toolName, reason);
|
|
5590
|
+
}
|
|
5336
5591
|
};
|
|
5337
5592
|
|
|
5338
5593
|
// src/git-trigger/git-hooks.ts
|
|
@@ -5461,11 +5716,11 @@ async function evaluateChangedFiles(gateway, files, config = {}) {
|
|
|
5461
5716
|
var import_fs5 = require("fs");
|
|
5462
5717
|
var import_path4 = require("path");
|
|
5463
5718
|
var ASTRASYNC_HOOK_MARKER = "astrasync-claude-hook";
|
|
5464
|
-
var HOOK_MATCHER = "Bash|Write|Edit|MultiEdit|NotebookEdit|Read|Glob|Grep|WebFetch|WebSearch|Task|Agent|mcp__.*";
|
|
5719
|
+
var HOOK_MATCHER = "Bash|PowerShell|Write|Edit|MultiEdit|NotebookEdit|Read|Glob|Grep|WebFetch|WebSearch|Task|Agent|mcp__.*";
|
|
5465
5720
|
var HOOK_TIMEOUT_SECONDS = 10;
|
|
5466
|
-
function isGuardEntry(entry) {
|
|
5721
|
+
function isGuardEntry(entry, marker2) {
|
|
5467
5722
|
return (entry.hooks ?? []).some(
|
|
5468
|
-
(h) => typeof h.command === "string" && h.command.includes(
|
|
5723
|
+
(h) => typeof h.command === "string" && h.command.includes(marker2)
|
|
5469
5724
|
);
|
|
5470
5725
|
}
|
|
5471
5726
|
function readSettings(settingsPath) {
|
|
@@ -5479,24 +5734,26 @@ function writeSettings(settingsPath, settings) {
|
|
|
5479
5734
|
(0, import_fs5.writeFileSync)(settingsPath, `${JSON.stringify(settings, null, 2)}
|
|
5480
5735
|
`, "utf-8");
|
|
5481
5736
|
}
|
|
5482
|
-
function buildHookEntry(hookCommand) {
|
|
5737
|
+
function buildHookEntry(hookCommand, matcher = HOOK_MATCHER, extraHookFields = {}) {
|
|
5483
5738
|
return {
|
|
5484
|
-
matcher
|
|
5485
|
-
hooks: [
|
|
5739
|
+
matcher,
|
|
5740
|
+
hooks: [
|
|
5741
|
+
{ type: "command", command: hookCommand, timeout: HOOK_TIMEOUT_SECONDS, ...extraHookFields }
|
|
5742
|
+
]
|
|
5486
5743
|
};
|
|
5487
5744
|
}
|
|
5488
|
-
function installClaudeHooks(settingsPath, hookCommand) {
|
|
5489
|
-
if (!hookCommand.includes(
|
|
5745
|
+
function installClaudeHooks(settingsPath, hookCommand, marker2 = ASTRASYNC_HOOK_MARKER, matcher = HOOK_MATCHER, extraHookFields = {}) {
|
|
5746
|
+
if (!hookCommand.includes(marker2)) {
|
|
5490
5747
|
throw new Error(
|
|
5491
|
-
`Hook command must reference ${
|
|
5748
|
+
`Hook command must reference ${marker2} (got: ${hookCommand}) \u2014 the marker is how install/uninstall identify the guard entry.`
|
|
5492
5749
|
);
|
|
5493
5750
|
}
|
|
5494
5751
|
const settings = readSettings(settingsPath);
|
|
5495
5752
|
settings.hooks = settings.hooks ?? {};
|
|
5496
5753
|
const preToolUse = settings.hooks.PreToolUse = settings.hooks.PreToolUse ?? [];
|
|
5497
|
-
const existing = preToolUse.find(isGuardEntry);
|
|
5754
|
+
const existing = preToolUse.find((entry) => isGuardEntry(entry, marker2));
|
|
5498
5755
|
if (existing) {
|
|
5499
|
-
const fresh = buildHookEntry(hookCommand);
|
|
5756
|
+
const fresh = buildHookEntry(hookCommand, matcher, extraHookFields);
|
|
5500
5757
|
const changed = JSON.stringify(existing) !== JSON.stringify(fresh);
|
|
5501
5758
|
if (changed) {
|
|
5502
5759
|
preToolUse[preToolUse.indexOf(existing)] = fresh;
|
|
@@ -5504,11 +5761,11 @@ function installClaudeHooks(settingsPath, hookCommand) {
|
|
|
5504
5761
|
}
|
|
5505
5762
|
return { installed: false, alreadyPresent: true, updated: changed };
|
|
5506
5763
|
}
|
|
5507
|
-
preToolUse.push(buildHookEntry(hookCommand));
|
|
5764
|
+
preToolUse.push(buildHookEntry(hookCommand, matcher, extraHookFields));
|
|
5508
5765
|
writeSettings(settingsPath, settings);
|
|
5509
5766
|
return { installed: true, alreadyPresent: false, updated: false };
|
|
5510
5767
|
}
|
|
5511
|
-
function uninstallClaudeHooks(settingsPath) {
|
|
5768
|
+
function uninstallClaudeHooks(settingsPath, marker2 = ASTRASYNC_HOOK_MARKER) {
|
|
5512
5769
|
if (!(0, import_fs5.existsSync)(settingsPath)) return false;
|
|
5513
5770
|
let settings;
|
|
5514
5771
|
try {
|
|
@@ -5518,7 +5775,7 @@ function uninstallClaudeHooks(settingsPath) {
|
|
|
5518
5775
|
}
|
|
5519
5776
|
const preToolUse = settings.hooks?.PreToolUse;
|
|
5520
5777
|
if (!preToolUse) return false;
|
|
5521
|
-
const remaining = preToolUse.filter((entry) => !isGuardEntry(entry));
|
|
5778
|
+
const remaining = preToolUse.filter((entry) => !isGuardEntry(entry, marker2));
|
|
5522
5779
|
if (remaining.length === preToolUse.length) return false;
|
|
5523
5780
|
if (remaining.length > 0) {
|
|
5524
5781
|
settings.hooks.PreToolUse = remaining;
|
|
@@ -5532,7 +5789,9 @@ function uninstallClaudeHooks(settingsPath) {
|
|
|
5532
5789
|
var GUARD_SELF_PROTECTION_PATTERNS = [
|
|
5533
5790
|
"**/.claude/settings.json",
|
|
5534
5791
|
"**/.claude/settings.local.json",
|
|
5535
|
-
"**/.astrasync/**"
|
|
5792
|
+
"**/.astrasync/**",
|
|
5793
|
+
"**/.codex/hooks.json",
|
|
5794
|
+
"**/.codex/config.toml"
|
|
5536
5795
|
];
|
|
5537
5796
|
function ensureGuardSelfProtection(policyPath) {
|
|
5538
5797
|
const parsed = parseFile(policyPath);
|
|
@@ -5579,6 +5838,25 @@ function buildDefaultClaudeCodePolicy(name = "claude-code-local-guard") {
|
|
|
5579
5838
|
};
|
|
5580
5839
|
}
|
|
5581
5840
|
|
|
5841
|
+
// src/codex/settings-installer.ts
|
|
5842
|
+
var ASTRASYNC_CODEX_HOOK_MARKER = "astrasync-codex-hook";
|
|
5843
|
+
var CODEX_HOOK_MATCHER = "Bash|apply_patch|Edit|Write|web_search|mcp__.*";
|
|
5844
|
+
var CODEX_STATUS_MESSAGE = "AstraSync Local Guard";
|
|
5845
|
+
function installCodexHooks(hooksPath, hookCommand) {
|
|
5846
|
+
return installClaudeHooks(
|
|
5847
|
+
hooksPath,
|
|
5848
|
+
hookCommand,
|
|
5849
|
+
ASTRASYNC_CODEX_HOOK_MARKER,
|
|
5850
|
+
CODEX_HOOK_MATCHER,
|
|
5851
|
+
{
|
|
5852
|
+
statusMessage: CODEX_STATUS_MESSAGE
|
|
5853
|
+
}
|
|
5854
|
+
);
|
|
5855
|
+
}
|
|
5856
|
+
function uninstallCodexHooks(hooksPath) {
|
|
5857
|
+
return uninstallClaudeHooks(hooksPath, ASTRASYNC_CODEX_HOOK_MARKER);
|
|
5858
|
+
}
|
|
5859
|
+
|
|
5582
5860
|
// src/cli/guard.ts
|
|
5583
5861
|
var DEFAULT_POLICY_PATH = "./local-policy.yaml";
|
|
5584
5862
|
var DEFAULT_CONFIG_DIR = ".astrasync";
|
|
@@ -5744,6 +6022,7 @@ async function cmdStatus() {
|
|
|
5744
6022
|
console.log(' Policy: not found \u2014 run "astrasync guard setup"');
|
|
5745
6023
|
}
|
|
5746
6024
|
printClaudeCodeStatus();
|
|
6025
|
+
printCodexStatus();
|
|
5747
6026
|
console.log("");
|
|
5748
6027
|
}
|
|
5749
6028
|
async function cmdCheck() {
|
|
@@ -5830,7 +6109,7 @@ async function cmdUninstallHooks() {
|
|
|
5830
6109
|
function resolveClaudeHookCommand() {
|
|
5831
6110
|
const sibling = path.join(__dirname, "astrasync-claude-hook.js");
|
|
5832
6111
|
if (fs.existsSync(sibling)) {
|
|
5833
|
-
return `
|
|
6112
|
+
return `"${process.execPath}" "${sibling}"`;
|
|
5834
6113
|
}
|
|
5835
6114
|
return "astrasync-claude-hook";
|
|
5836
6115
|
}
|
|
@@ -5922,6 +6201,108 @@ async function cmdUninstallClaudeCode() {
|
|
|
5922
6201
|
`[AstraSync] Policy and traces left in place at ${astrasyncDir} \u2014 delete manually if unwanted.`
|
|
5923
6202
|
);
|
|
5924
6203
|
}
|
|
6204
|
+
function resolveCodexHookCommand() {
|
|
6205
|
+
const sibling = path.join(__dirname, "astrasync-codex-hook.js");
|
|
6206
|
+
if (fs.existsSync(sibling)) {
|
|
6207
|
+
return `"${process.execPath}" "${sibling}"`;
|
|
6208
|
+
}
|
|
6209
|
+
return "astrasync-codex-hook";
|
|
6210
|
+
}
|
|
6211
|
+
function codexPaths(project) {
|
|
6212
|
+
const root = project ? process.cwd() : os.homedir();
|
|
6213
|
+
return {
|
|
6214
|
+
hooksPath: path.join(root, ".codex", "hooks.json"),
|
|
6215
|
+
astrasyncDir: path.join(root, ".astrasync")
|
|
6216
|
+
};
|
|
6217
|
+
}
|
|
6218
|
+
async function cmdInstallCodex() {
|
|
6219
|
+
const flags = parseClaudeCodeFlags();
|
|
6220
|
+
const { hooksPath, astrasyncDir } = codexPaths(flags.project);
|
|
6221
|
+
const policyPath = flags.policyOverride || path.join(astrasyncDir, "policy.yaml");
|
|
6222
|
+
const guardJsonPath = path.join(astrasyncDir, "guard.json");
|
|
6223
|
+
console.log("\nAstraSync Local Guard for Codex CLI \u2014 install\n");
|
|
6224
|
+
if (fs.existsSync(policyPath)) {
|
|
6225
|
+
console.log(` Policy: using existing ${policyPath} (shared across adapters)`);
|
|
6226
|
+
if (ensureGuardSelfProtection(policyPath)) {
|
|
6227
|
+
console.log(" added guard self-protection patterns to file.write");
|
|
6228
|
+
}
|
|
6229
|
+
} else if (flags.wizard) {
|
|
6230
|
+
const prompt = createPrompt();
|
|
6231
|
+
try {
|
|
6232
|
+
fs.mkdirSync(path.dirname(policyPath), { recursive: true });
|
|
6233
|
+
const wizard = new SetupWizard(prompt);
|
|
6234
|
+
await wizard.runAndSave(policyPath);
|
|
6235
|
+
} finally {
|
|
6236
|
+
prompt.close();
|
|
6237
|
+
}
|
|
6238
|
+
ensureGuardSelfProtection(policyPath);
|
|
6239
|
+
console.log(` Policy: created via wizard at ${policyPath}`);
|
|
6240
|
+
} else {
|
|
6241
|
+
fs.mkdirSync(path.dirname(policyPath), { recursive: true });
|
|
6242
|
+
fs.writeFileSync(policyPath, toYaml(buildDefaultClaudeCodePolicy()), "utf-8");
|
|
6243
|
+
console.log(` Policy: default coding-agent policy written to ${policyPath}`);
|
|
6244
|
+
console.log(" (re-run with --wizard for the interactive setup instead)");
|
|
6245
|
+
}
|
|
6246
|
+
if (!fs.existsSync(guardJsonPath)) {
|
|
6247
|
+
const guardSettings = {
|
|
6248
|
+
posture: flags.active ? "active" : "passive",
|
|
6249
|
+
governMcp: false
|
|
6250
|
+
};
|
|
6251
|
+
fs.writeFileSync(guardJsonPath, `${JSON.stringify(guardSettings, null, 2)}
|
|
6252
|
+
`, "utf-8");
|
|
6253
|
+
console.log(
|
|
6254
|
+
` Guard: ${guardJsonPath} (posture: ${guardSettings.posture}, governMcp: false)`
|
|
6255
|
+
);
|
|
6256
|
+
} else {
|
|
6257
|
+
console.log(` Guard: using existing ${guardJsonPath}`);
|
|
6258
|
+
}
|
|
6259
|
+
const hookCommand = resolveCodexHookCommand();
|
|
6260
|
+
const result = installCodexHooks(hooksPath, hookCommand);
|
|
6261
|
+
if (result.installed) {
|
|
6262
|
+
console.log(` Hook: installed in ${hooksPath}`);
|
|
6263
|
+
} else if (result.updated) {
|
|
6264
|
+
console.log(` Hook: updated in ${hooksPath}`);
|
|
6265
|
+
} else {
|
|
6266
|
+
console.log(` Hook: already present in ${hooksPath}`);
|
|
6267
|
+
}
|
|
6268
|
+
console.log("\n Next steps:");
|
|
6269
|
+
console.log(" 1. Start a NEW Codex session (hooks load at startup; /hooks to inspect).");
|
|
6270
|
+
console.log(` 2. Watch decisions: tail -f ${path.join(astrasyncDir, "traces")}/*.jsonl`);
|
|
6271
|
+
console.log(` 3. When the traces look right, set "posture": "active" in ${guardJsonPath}.`);
|
|
6272
|
+
console.log(" Denies block; approvals fall through to Codex's own prompt \u2014");
|
|
6273
|
+
console.log(" except in dontAsk/bypassPermissions modes, where they deny.");
|
|
6274
|
+
console.log(" 4. Note: Codex fails OPEN if the hook itself times out; the guard");
|
|
6275
|
+
console.log(" fails closed on every failure it can see.\n");
|
|
6276
|
+
}
|
|
6277
|
+
async function cmdUninstallCodex() {
|
|
6278
|
+
const flags = parseClaudeCodeFlags();
|
|
6279
|
+
const { hooksPath, astrasyncDir } = codexPaths(flags.project);
|
|
6280
|
+
const removed = uninstallCodexHooks(hooksPath);
|
|
6281
|
+
if (removed) {
|
|
6282
|
+
console.log(`[AstraSync] Removed Codex hook from ${hooksPath}`);
|
|
6283
|
+
} else {
|
|
6284
|
+
console.log(`[AstraSync] No AstraSync hook found in ${hooksPath}`);
|
|
6285
|
+
}
|
|
6286
|
+
console.log(
|
|
6287
|
+
`[AstraSync] Policy and traces left in place at ${astrasyncDir} \u2014 delete manually if unwanted.`
|
|
6288
|
+
);
|
|
6289
|
+
}
|
|
6290
|
+
function printCodexStatus() {
|
|
6291
|
+
for (const scope of ["user", "project"]) {
|
|
6292
|
+
const { hooksPath, astrasyncDir } = codexPaths(scope === "project");
|
|
6293
|
+
let hookInstalled = false;
|
|
6294
|
+
try {
|
|
6295
|
+
const content = fs.readFileSync(hooksPath, "utf-8");
|
|
6296
|
+
hookInstalled = content.includes(ASTRASYNC_CODEX_HOOK_MARKER);
|
|
6297
|
+
} catch {
|
|
6298
|
+
}
|
|
6299
|
+
if (hookInstalled) {
|
|
6300
|
+
const policyPath = path.join(astrasyncDir, "policy.yaml");
|
|
6301
|
+
const policy = fs.existsSync(policyPath) ? `policy: ${policyPath}` : "policy: missing";
|
|
6302
|
+
console.log(` Codex CLI (${scope}): hook installed, ${policy}`);
|
|
6303
|
+
}
|
|
6304
|
+
}
|
|
6305
|
+
}
|
|
5925
6306
|
function printClaudeCodeStatus() {
|
|
5926
6307
|
for (const scope of ["user", "project"]) {
|
|
5927
6308
|
const { settingsPath, astrasyncDir } = claudeCodePaths(scope === "project");
|
|
@@ -5979,6 +6360,12 @@ async function main() {
|
|
|
5979
6360
|
case "uninstall-claude-code":
|
|
5980
6361
|
await cmdUninstallClaudeCode();
|
|
5981
6362
|
break;
|
|
6363
|
+
case "install-codex":
|
|
6364
|
+
await cmdInstallCodex();
|
|
6365
|
+
break;
|
|
6366
|
+
case "uninstall-codex":
|
|
6367
|
+
await cmdUninstallCodex();
|
|
6368
|
+
break;
|
|
5982
6369
|
default:
|
|
5983
6370
|
console.log("AstraSync Local Guard\n");
|
|
5984
6371
|
console.log("Usage: astrasync guard <command>\n");
|
|
@@ -5996,6 +6383,9 @@ async function main() {
|
|
|
5996
6383
|
console.log(
|
|
5997
6384
|
" uninstall-claude-code Remove the Claude Code hook (--project for project scope)"
|
|
5998
6385
|
);
|
|
6386
|
+
console.log(" install-codex Install the Local Guard hook for OpenAI Codex CLI");
|
|
6387
|
+
console.log(" (same flags; policy shared with Claude Code)");
|
|
6388
|
+
console.log(" uninstall-codex Remove the Codex hook (--project for project scope)");
|
|
5999
6389
|
console.log("");
|
|
6000
6390
|
process.exit(command ? 1 : 0);
|
|
6001
6391
|
}
|