@codai/axiom-mcp 2.1.0 → 2.2.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/README.md +56 -24
- package/dist/{axm-lazy-CR-FmFVd.js → axm-lazy-LAnrfjUO.js} +32 -3
- package/dist/axm-lazy-Xin1nh7Q.js +6284 -0
- package/dist/axm-lazy.js +119 -9
- package/dist/cli-main.js +1650 -14959
- package/dist/dist-Bt9Wvd_P.js +6379 -0
- package/dist/{dist-FFqnyzCe.js → dist-D4M2Lyji.js} +264 -27
- package/dist/gate-lazy.js +581 -46
- package/dist/gc-lazy.js +6885 -0
- package/dist/http-lazy.js +18262 -9889
- package/dist/index.d.ts +140 -4
- package/dist/index.js +575 -41
- package/dist/mcp-lazy.js +23050 -0
- package/dist/migrate-lazy.js +48 -7
- package/dist/verify-tree-lazy.js +6893 -0
- package/package.json +14 -9
- package/spec/codai-tools.json +283 -0
- package/spec/tools.json +1487 -160
package/dist/gate-lazy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { realpath } from "node:fs";
|
|
2
2
|
import * as fs$1 from "node:fs/promises";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
4
4
|
import { cpus, homedir } from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { parseArgs, promisify } from "node:util";
|
|
@@ -5680,18 +5680,26 @@ const ERROR_CODES = [
|
|
|
5680
5680
|
"ERR_PROVIDER_FAILED",
|
|
5681
5681
|
"ERR_GUARD_TIMEOUT",
|
|
5682
5682
|
"ERR_GUARD_OUTPUT",
|
|
5683
|
+
"ERR_FACT_DISABLED",
|
|
5683
5684
|
"ERR_SIGNATURE_MISSING",
|
|
5684
5685
|
"ERR_SIGNATURE_INVALID",
|
|
5686
|
+
"ERR_TRUST_STATE_CORRUPT",
|
|
5685
5687
|
"ERR_ROLLBACK",
|
|
5686
5688
|
"ERR_EMITTER_UNKNOWN",
|
|
5687
5689
|
"ERR_TEMPLATE_UNKNOWN",
|
|
5688
5690
|
"ERR_TEMPLATE_PARAMS",
|
|
5691
|
+
"ERR_PATCH_FORMAT",
|
|
5692
|
+
"ERR_PATCH_PREIMAGE",
|
|
5693
|
+
"ERR_PATCH_NO_MATCH",
|
|
5689
5694
|
"ERR_GIT_NOT_FOUND",
|
|
5690
5695
|
"ERR_GIT_NOT_REPO",
|
|
5691
5696
|
"ERR_GIT_DIRTY",
|
|
5692
5697
|
"ERR_GIT_BRANCH_EXISTS",
|
|
5693
5698
|
"ERR_GIT_BRANCH_INVALID",
|
|
5694
5699
|
"ERR_GIT_FAILED",
|
|
5700
|
+
"ERR_TASK_NOT_FOUND",
|
|
5701
|
+
"ERR_TASK_CANCELLED",
|
|
5702
|
+
"ERR_PLAN_SESSION_STATE",
|
|
5695
5703
|
"ERR_REF_OFFLINE",
|
|
5696
5704
|
"ERR_NET_DISABLED",
|
|
5697
5705
|
"ERR_NET_DENIED",
|
|
@@ -5799,7 +5807,12 @@ object({
|
|
|
5799
5807
|
findings: array(FindingSchema),
|
|
5800
5808
|
factsDigest: DigestRefSchema,
|
|
5801
5809
|
durationMs: int().nonnegative(),
|
|
5802
|
-
providers: array(ProviderStatusSchema)
|
|
5810
|
+
providers: array(ProviderStatusSchema),
|
|
5811
|
+
preImage: _enum([
|
|
5812
|
+
"verified",
|
|
5813
|
+
"drifted",
|
|
5814
|
+
"unverified"
|
|
5815
|
+
]).optional()
|
|
5803
5816
|
}).strict();
|
|
5804
5817
|
const ApiVersionSchema = literal("axiom.dev/v2");
|
|
5805
5818
|
const INLINE_CONTENT_MAX = 262144;
|
|
@@ -5838,7 +5851,17 @@ const PlanArtifactSourceSchema = discriminatedUnion("type", [
|
|
|
5838
5851
|
emitter: string().min(1),
|
|
5839
5852
|
template: string().min(1),
|
|
5840
5853
|
params: record(string(), json()).default({})
|
|
5841
|
-
}).strict().describe("
|
|
5854
|
+
}).strict().describe("template source rendered by a registered emitter at compile time"),
|
|
5855
|
+
object({
|
|
5856
|
+
type: literal("patch"),
|
|
5857
|
+
format: _enum([
|
|
5858
|
+
"unified",
|
|
5859
|
+
"v4a",
|
|
5860
|
+
"search-replace"
|
|
5861
|
+
]),
|
|
5862
|
+
preImage: union([DigestRefSchema, literal("absent")]),
|
|
5863
|
+
body: string().max(INLINE_CONTENT_MAX)
|
|
5864
|
+
}).strict().describe("patch applied at compile time to the pre-image named by its digest")
|
|
5842
5865
|
]);
|
|
5843
5866
|
const PlanArtifactSchema = object({
|
|
5844
5867
|
path: RelPathSchema,
|
|
@@ -5913,6 +5936,7 @@ object({
|
|
|
5913
5936
|
diff: string().optional(),
|
|
5914
5937
|
journal: string().optional(),
|
|
5915
5938
|
git: GitResultSchema.optional(),
|
|
5939
|
+
drifted: array(RelPathSchema).optional(),
|
|
5916
5940
|
error: ApplyErrorSchema.optional()
|
|
5917
5941
|
}).strict().superRefine((r, ctx) => {
|
|
5918
5942
|
if (r.status === "failed" && r.error === void 0) ctx.addIssue({
|
|
@@ -5973,7 +5997,8 @@ const ManifestArtifactSchema = object({
|
|
|
5973
5997
|
"inline",
|
|
5974
5998
|
"template",
|
|
5975
5999
|
"cas",
|
|
5976
|
-
"ref"
|
|
6000
|
+
"ref",
|
|
6001
|
+
"patch"
|
|
5977
6002
|
]).optional()
|
|
5978
6003
|
}).strict().superRefine((a, ctx) => {
|
|
5979
6004
|
if (a.op !== "delete" && a.digest === void 0) ctx.addIssue({
|
|
@@ -5987,6 +6012,10 @@ const ToolchainSchema = object({
|
|
|
5987
6012
|
axiom: string().min(1),
|
|
5988
6013
|
emitters: record(string(), string())
|
|
5989
6014
|
}).strict();
|
|
6015
|
+
const PreImageEntrySchema = object({
|
|
6016
|
+
path: RelPathSchema,
|
|
6017
|
+
sha256: union([Sha256HexSchema, literal("absent")])
|
|
6018
|
+
}).strict();
|
|
5990
6019
|
const ManifestBodySchema = object({
|
|
5991
6020
|
apiVersion: ApiVersionSchema,
|
|
5992
6021
|
kind: literal("Manifest"),
|
|
@@ -5996,8 +6025,15 @@ const ManifestBodySchema = object({
|
|
|
5996
6025
|
artifacts: array(ManifestArtifactSchema).min(1).max(2e3),
|
|
5997
6026
|
checks: array(CheckRefSchema),
|
|
5998
6027
|
toolchain: ToolchainSchema,
|
|
5999
|
-
counter: int().nonnegative().optional()
|
|
6028
|
+
counter: int().nonnegative().optional(),
|
|
6029
|
+
preImage: array(PreImageEntrySchema).optional()
|
|
6000
6030
|
}).strict().superRefine((m, ctx) => {
|
|
6031
|
+
if (m.preImage !== void 0 && !isSortedUnique(m.preImage.map((p) => p.path))) ctx.addIssue({
|
|
6032
|
+
code: "custom",
|
|
6033
|
+
path: ["preImage"],
|
|
6034
|
+
message: "preImage must be sorted by path (UTF-8 byte order) and unique",
|
|
6035
|
+
params: { code: "ERR_NOT_CANONICAL" }
|
|
6036
|
+
});
|
|
6001
6037
|
if (!isSortedUnique(m.artifacts.map((a) => a.path))) ctx.addIssue({
|
|
6002
6038
|
code: "custom",
|
|
6003
6039
|
path: ["artifacts"],
|
|
@@ -6037,8 +6073,11 @@ const DsseEnvelopeSchema = object({
|
|
|
6037
6073
|
sig: base64()
|
|
6038
6074
|
}).strict())
|
|
6039
6075
|
}).strict();
|
|
6076
|
+
const AXIOM_MANIFEST_PAYLOAD_TYPE = "application/vnd.axiom.manifest+json";
|
|
6077
|
+
const AXIOM_MANIFEST_BOUND_PAYLOAD_TYPE = "application/vnd.axiom.manifest-bound+json";
|
|
6078
|
+
const RootIdSchema = string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:/@-]*$/, "rootId: letters, digits, . _ : / @ - only");
|
|
6040
6079
|
const ManifestSignatureSchema = object({
|
|
6041
|
-
payloadType: literal(
|
|
6080
|
+
payloadType: union([literal(AXIOM_MANIFEST_PAYLOAD_TYPE), literal(AXIOM_MANIFEST_BOUND_PAYLOAD_TYPE)]),
|
|
6042
6081
|
payload: base64(),
|
|
6043
6082
|
signatures: array(object({
|
|
6044
6083
|
keyid: string().optional(),
|
|
@@ -6055,12 +6094,14 @@ const TrustedKeySchema = object({
|
|
|
6055
6094
|
object({
|
|
6056
6095
|
version: literal(1),
|
|
6057
6096
|
keys: array(TrustedKeySchema),
|
|
6058
|
-
minCounter: int().nonnegative().optional()
|
|
6097
|
+
minCounter: int().nonnegative().optional(),
|
|
6098
|
+
rootId: RootIdSchema.optional()
|
|
6059
6099
|
}).strict();
|
|
6060
6100
|
object({
|
|
6061
6101
|
version: literal(1),
|
|
6062
6102
|
lastCounter: int().nonnegative(),
|
|
6063
|
-
manifestDigest: DigestRefSchema.optional()
|
|
6103
|
+
manifestDigest: DigestRefSchema.optional(),
|
|
6104
|
+
mac: string().regex(/^[0-9a-f]{64}$/).optional()
|
|
6064
6105
|
}).strict();
|
|
6065
6106
|
object({
|
|
6066
6107
|
manifest: ManifestBodySchema,
|
|
@@ -8290,9 +8331,14 @@ var import_picomatch = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((
|
|
|
8290
8331
|
function definePredicate(p) {
|
|
8291
8332
|
return p;
|
|
8292
8333
|
}
|
|
8334
|
+
function escapeRouteGroups(glob) {
|
|
8335
|
+
return glob.replace(/(^|[^\\@!?*+])\(([^()|*?[\]{}\\]+)\)/g, (_m, pre, inner) => {
|
|
8336
|
+
return `${pre}\\(${inner}\\)`;
|
|
8337
|
+
});
|
|
8338
|
+
}
|
|
8293
8339
|
function globMatcher(globs, whenEmpty) {
|
|
8294
8340
|
if (globs === void 0 || globs.length === 0) return () => whenEmpty;
|
|
8295
|
-
const m = (0, import_picomatch.default)(
|
|
8341
|
+
const m = (0, import_picomatch.default)(globs.map(escapeRouteGroups), { dot: true });
|
|
8296
8342
|
return (p) => m(p);
|
|
8297
8343
|
}
|
|
8298
8344
|
function finding(f) {
|
|
@@ -8315,60 +8361,156 @@ object({
|
|
|
8315
8361
|
"info"
|
|
8316
8362
|
]).optional()
|
|
8317
8363
|
}).strict();
|
|
8364
|
+
object({
|
|
8365
|
+
policies: string().min(1).max(65536),
|
|
8366
|
+
mode: _enum(["forbid", "permit"]).default("forbid"),
|
|
8367
|
+
message: string().max(2e3).optional(),
|
|
8368
|
+
severity: _enum([
|
|
8369
|
+
"error",
|
|
8370
|
+
"warn",
|
|
8371
|
+
"info"
|
|
8372
|
+
]).optional()
|
|
8373
|
+
}).strict();
|
|
8374
|
+
function luhnValid(digits) {
|
|
8375
|
+
let sum = 0;
|
|
8376
|
+
let double = false;
|
|
8377
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
8378
|
+
let d = digits.charCodeAt(i) - 48;
|
|
8379
|
+
if (d < 0 || d > 9) return false;
|
|
8380
|
+
if (double) {
|
|
8381
|
+
d *= 2;
|
|
8382
|
+
if (d > 9) d -= 9;
|
|
8383
|
+
}
|
|
8384
|
+
sum += d;
|
|
8385
|
+
double = !double;
|
|
8386
|
+
}
|
|
8387
|
+
return digits.length > 0 && sum % 10 === 0;
|
|
8388
|
+
}
|
|
8389
|
+
const UUID_RE = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi;
|
|
8390
|
+
const CARD_RE = /(?<![\w-])(?:\d[ -]?){12,15}\d(?![\w-])/g;
|
|
8391
|
+
function findCardNumber(text) {
|
|
8392
|
+
const scrubbed = text.replace(UUID_RE, (m) => " ".repeat(m.length));
|
|
8393
|
+
CARD_RE.lastIndex = 0;
|
|
8394
|
+
for (let m = CARD_RE.exec(scrubbed); m !== null; m = CARD_RE.exec(scrubbed)) {
|
|
8395
|
+
const digits = m[0].replace(/[ -]/g, "");
|
|
8396
|
+
if (digits.length < 13 || digits.length > 16) continue;
|
|
8397
|
+
if (/^(\d)\1+$/.test(digits)) continue;
|
|
8398
|
+
if (luhnValid(digits)) return true;
|
|
8399
|
+
}
|
|
8400
|
+
return false;
|
|
8401
|
+
}
|
|
8402
|
+
function cnpValid(digits) {
|
|
8403
|
+
if (!/^[1-9]\d{12}$/.test(digits)) return false;
|
|
8404
|
+
const mm = Number(digits.slice(3, 5));
|
|
8405
|
+
const dd = Number(digits.slice(5, 7));
|
|
8406
|
+
const jj = Number(digits.slice(7, 9));
|
|
8407
|
+
if (mm < 1 || mm > 12 || dd < 1 || dd > 31) return false;
|
|
8408
|
+
if (!(jj >= 1 && jj <= 52 || jj === 70)) return false;
|
|
8409
|
+
const w = "279146358279";
|
|
8410
|
+
let sum = 0;
|
|
8411
|
+
for (let i = 0; i < 12; i++) sum += (digits.charCodeAt(i) - 48) * (w.charCodeAt(i) - 48);
|
|
8412
|
+
return (sum % 11 === 10 ? 1 : sum % 11) === digits.charCodeAt(12) - 48;
|
|
8413
|
+
}
|
|
8414
|
+
const CNP_RE = /\b[1-9]\d{12}\b/g;
|
|
8415
|
+
function findCnp(text) {
|
|
8416
|
+
CNP_RE.lastIndex = 0;
|
|
8417
|
+
for (let m = CNP_RE.exec(text); m !== null; m = CNP_RE.exec(text)) if (cnpValid(m[0])) return true;
|
|
8418
|
+
return false;
|
|
8419
|
+
}
|
|
8420
|
+
const EMAIL_RE = /\b([A-Za-z0-9._%+-]+)@([A-Za-z0-9.-]+\.[A-Za-z]{2,})\b/g;
|
|
8421
|
+
const EMAIL_SKIP_DOMAIN = /(^|\.)(example\.(com|net|org)|example|test|invalid|localhost|localdomain)$|(^|\.)github\.com$/i;
|
|
8422
|
+
const EMAIL_SKIP_LOCAL = /^(git|noreply|no-reply|donotreply|do-not-reply)$/i;
|
|
8423
|
+
const EMAIL_SKIP_TLD_AS_EXT = /\.(png|jpe?g|gif|svg|webp|ico|css|js|ts|json|md|txt|html?|xml|yml|yaml|py)$/i;
|
|
8424
|
+
function findPersonalEmail(text) {
|
|
8425
|
+
EMAIL_RE.lastIndex = 0;
|
|
8426
|
+
for (let m = EMAIL_RE.exec(text); m !== null; m = EMAIL_RE.exec(text)) {
|
|
8427
|
+
const local = m[1] ?? "";
|
|
8428
|
+
const domain = m[2] ?? "";
|
|
8429
|
+
if (EMAIL_SKIP_DOMAIN.test(domain)) continue;
|
|
8430
|
+
if (EMAIL_SKIP_LOCAL.test(local)) continue;
|
|
8431
|
+
if (EMAIL_SKIP_TLD_AS_EXT.test(domain)) continue;
|
|
8432
|
+
return true;
|
|
8433
|
+
}
|
|
8434
|
+
return false;
|
|
8435
|
+
}
|
|
8436
|
+
const CRED_RE = /(?:^|[^A-Za-z0-9])[A-Za-z0-9_-]*?(password|passwd|pwd|secret|token|api[_-]?key|access[_-]?key|auth[_-]?token|client[_-]?secret)\b\s*[:=]\s*(?:new\s+\w+\s*\(\s*)?(["'`])([^"'`\r\n]{8,})\2/gi;
|
|
8437
|
+
const CRED_PLACEHOLDER = /^(\$\{|\{\{|<|%[(sd]|\$[A-Z_]|xxx|\*{3,}|\.{3,}|change[_-]?me|replace[_-]?me|your[_-]|my[_-]?(actual[_-]?)?(password|secret|token|key)|placeholder|example|dummy|sample|test(value|ing)?$|redacted|hunter2$|password$|secret$|123456)/i;
|
|
8438
|
+
function findCredentialLiteral(text) {
|
|
8439
|
+
CRED_RE.lastIndex = 0;
|
|
8440
|
+
for (let m = CRED_RE.exec(text); m !== null; m = CRED_RE.exec(text)) {
|
|
8441
|
+
const value = m[3] ?? "";
|
|
8442
|
+
if (CRED_PLACEHOLDER.test(value)) continue;
|
|
8443
|
+
if (/^[a-z_][a-z0-9_.]*$/i.test(value) && !/\d/.test(value)) continue;
|
|
8444
|
+
return true;
|
|
8445
|
+
}
|
|
8446
|
+
return false;
|
|
8447
|
+
}
|
|
8318
8448
|
const SECRET_PATTERNS = [
|
|
8319
8449
|
{
|
|
8320
8450
|
name: "cnp",
|
|
8321
|
-
|
|
8451
|
+
kind: "pii",
|
|
8452
|
+
test: findCnp
|
|
8322
8453
|
},
|
|
8323
8454
|
{
|
|
8324
8455
|
name: "email",
|
|
8325
|
-
|
|
8456
|
+
kind: "pii",
|
|
8457
|
+
test: findPersonalEmail
|
|
8326
8458
|
},
|
|
8327
8459
|
{
|
|
8328
8460
|
name: "phoneRo",
|
|
8461
|
+
kind: "pii",
|
|
8329
8462
|
re: /\b(\+4|0)7\d{8}\b/
|
|
8330
8463
|
},
|
|
8331
8464
|
{
|
|
8332
8465
|
name: "card",
|
|
8333
|
-
|
|
8466
|
+
kind: "pii",
|
|
8467
|
+
test: findCardNumber
|
|
8334
8468
|
},
|
|
8335
8469
|
{
|
|
8336
8470
|
name: "credentialAssignment",
|
|
8337
|
-
|
|
8471
|
+
kind: "secret",
|
|
8472
|
+
test: findCredentialLiteral
|
|
8338
8473
|
},
|
|
8339
8474
|
{
|
|
8340
8475
|
name: "awsKey",
|
|
8476
|
+
kind: "secret",
|
|
8341
8477
|
re: /AKIA[0-9A-Z]{16}/
|
|
8342
8478
|
},
|
|
8343
8479
|
{
|
|
8344
8480
|
name: "githubToken",
|
|
8481
|
+
kind: "secret",
|
|
8345
8482
|
re: /gh[pousr]_[A-Za-z0-9]{36,}/
|
|
8346
8483
|
},
|
|
8347
8484
|
{
|
|
8348
8485
|
name: "privateKey",
|
|
8486
|
+
kind: "secret",
|
|
8349
8487
|
re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/
|
|
8350
8488
|
},
|
|
8351
8489
|
{
|
|
8352
8490
|
name: "jwt",
|
|
8491
|
+
kind: "secret",
|
|
8353
8492
|
re: /eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/
|
|
8354
8493
|
},
|
|
8355
8494
|
{
|
|
8356
8495
|
name: "slackToken",
|
|
8496
|
+
kind: "secret",
|
|
8357
8497
|
re: /xox[abpr]-/
|
|
8358
8498
|
}
|
|
8359
8499
|
];
|
|
8360
8500
|
SECRET_PATTERNS.map((p) => p.name);
|
|
8501
|
+
SECRET_PATTERNS.filter((p) => p.kind === "pii").map((p) => p.name);
|
|
8361
8502
|
const SCAN_LIMIT = 4194304;
|
|
8362
8503
|
const contentNoSecrets = definePredicate({
|
|
8363
8504
|
id: "content.noSecrets",
|
|
8364
8505
|
params: object({
|
|
8365
8506
|
disable: array(string()).default([]),
|
|
8366
|
-
allowPaths: array(string().min(1)).default([])
|
|
8507
|
+
allowPaths: array(string().min(1)).default([]),
|
|
8508
|
+
pii: boolean().default(false)
|
|
8367
8509
|
}).strict(),
|
|
8368
8510
|
requires: ["manifest", "content"],
|
|
8369
|
-
async run(ctx, { disable, allowPaths }) {
|
|
8511
|
+
async run(ctx, { disable, allowPaths, pii }) {
|
|
8370
8512
|
const skip = globMatcher(allowPaths, false);
|
|
8371
|
-
const active = SECRET_PATTERNS.filter((p) => !disable.includes(p.name));
|
|
8513
|
+
const active = SECRET_PATTERNS.filter((p) => !disable.includes(p.name) && (pii || p.kind === "secret"));
|
|
8372
8514
|
const out = [];
|
|
8373
8515
|
const decoder = new TextDecoder("utf-8", { fatal: false });
|
|
8374
8516
|
for (const a of ctx.manifest.artifacts) {
|
|
@@ -8377,7 +8519,7 @@ const contentNoSecrets = definePredicate({
|
|
|
8377
8519
|
if (bytes === void 0) continue;
|
|
8378
8520
|
const text = decoder.decode(bytes.subarray(0, SCAN_LIMIT));
|
|
8379
8521
|
for (const p of active) {
|
|
8380
|
-
if (!p.
|
|
8522
|
+
if (!(p.test !== void 0 ? p.test(text) : p.re?.test(text) ?? false)) continue;
|
|
8381
8523
|
out.push(finding({
|
|
8382
8524
|
id: `content.noSecrets.${p.name}`,
|
|
8383
8525
|
predicate: "content.noSecrets",
|
|
@@ -8433,7 +8575,7 @@ object({
|
|
|
8433
8575
|
command: string().min(1),
|
|
8434
8576
|
args: array(string()).default([]),
|
|
8435
8577
|
cwd: _enum(["root", "staging"]).default("root"),
|
|
8436
|
-
timeoutMs: int().min(1).max(
|
|
8578
|
+
timeoutMs: int().min(1).max(9e5).default(3e4),
|
|
8437
8579
|
env: record(string(), string()).optional(),
|
|
8438
8580
|
stdin: _enum([
|
|
8439
8581
|
"bundle",
|
|
@@ -8497,7 +8639,8 @@ object({ rules: array(object({
|
|
|
8497
8639
|
when: string().min(1),
|
|
8498
8640
|
expect: array(object({
|
|
8499
8641
|
name: string().min(1),
|
|
8500
|
-
match: string().min(1)
|
|
8642
|
+
match: string().min(1),
|
|
8643
|
+
mustChange: boolean().default(false)
|
|
8501
8644
|
}).strict()).min(1)
|
|
8502
8645
|
}).strict()).min(1) }).strict();
|
|
8503
8646
|
object({
|
|
@@ -8516,6 +8659,7 @@ const GateProfileSchema = object({
|
|
|
8516
8659
|
deny: array(string().min(1)).default([]),
|
|
8517
8660
|
allow: array(string().min(1)).optional(),
|
|
8518
8661
|
noSecrets: boolean().default(true),
|
|
8662
|
+
pii: boolean().default(false),
|
|
8519
8663
|
maxBytes: int().positive().optional()
|
|
8520
8664
|
}).strict();
|
|
8521
8665
|
const DEFAULT_GATE_PROFILE = {
|
|
@@ -8528,7 +8672,8 @@ const DEFAULT_GATE_PROFILE = {
|
|
|
8528
8672
|
".env.*",
|
|
8529
8673
|
"**/node_modules/**"
|
|
8530
8674
|
],
|
|
8531
|
-
noSecrets: true
|
|
8675
|
+
noSecrets: true,
|
|
8676
|
+
pii: false
|
|
8532
8677
|
};
|
|
8533
8678
|
async function loadGateProfile(opts) {
|
|
8534
8679
|
const candidates = [];
|
|
@@ -8671,6 +8816,262 @@ function isWriteTool(toolName) {
|
|
|
8671
8816
|
if (n.includes("apply_patch") || n.includes("multi_replace")) return true;
|
|
8672
8817
|
return !NON_WRITE_HINTS.some((h) => n.includes(h));
|
|
8673
8818
|
}
|
|
8819
|
+
const SHELL_TOOL_HINTS = [
|
|
8820
|
+
"bash",
|
|
8821
|
+
"shell",
|
|
8822
|
+
"run_in_terminal",
|
|
8823
|
+
"terminal",
|
|
8824
|
+
"execute_command",
|
|
8825
|
+
"exec_command",
|
|
8826
|
+
"run_command",
|
|
8827
|
+
"powershell",
|
|
8828
|
+
"cmd"
|
|
8829
|
+
];
|
|
8830
|
+
const SHELL_COMMAND_KEYS = [
|
|
8831
|
+
"command",
|
|
8832
|
+
"cmd",
|
|
8833
|
+
"script",
|
|
8834
|
+
"commandLine",
|
|
8835
|
+
"command_line"
|
|
8836
|
+
];
|
|
8837
|
+
function isShellTool(toolName) {
|
|
8838
|
+
const n = toolName.toLowerCase();
|
|
8839
|
+
return SHELL_TOOL_HINTS.some((h) => n.includes(h));
|
|
8840
|
+
}
|
|
8841
|
+
function classifyTool(toolName) {
|
|
8842
|
+
if (isWriteTool(toolName)) return "write";
|
|
8843
|
+
if (isShellTool(toolName)) return "shell";
|
|
8844
|
+
return "other";
|
|
8845
|
+
}
|
|
8846
|
+
const REDIRECT_RE = /(?:^|[\s;&|])(?:\d?>{1,2}|&>)\s*("([^"]+)"|'([^']+)'|([^\s;&|<>]+))/g;
|
|
8847
|
+
const SHELL_SPLIT_RE = /(?:^|[;&|]+|\|\||&&)\s*/;
|
|
8848
|
+
const WRITE_CMDS = {
|
|
8849
|
+
tee: {
|
|
8850
|
+
skipFlags: true,
|
|
8851
|
+
operands: "all"
|
|
8852
|
+
},
|
|
8853
|
+
rm: {
|
|
8854
|
+
skipFlags: true,
|
|
8855
|
+
operands: "all"
|
|
8856
|
+
},
|
|
8857
|
+
rmdir: {
|
|
8858
|
+
skipFlags: true,
|
|
8859
|
+
operands: "all"
|
|
8860
|
+
},
|
|
8861
|
+
mv: {
|
|
8862
|
+
skipFlags: true,
|
|
8863
|
+
operands: "last"
|
|
8864
|
+
},
|
|
8865
|
+
cp: {
|
|
8866
|
+
skipFlags: true,
|
|
8867
|
+
operands: "last"
|
|
8868
|
+
},
|
|
8869
|
+
touch: {
|
|
8870
|
+
skipFlags: true,
|
|
8871
|
+
operands: "all"
|
|
8872
|
+
},
|
|
8873
|
+
truncate: {
|
|
8874
|
+
skipFlags: true,
|
|
8875
|
+
operands: "all"
|
|
8876
|
+
},
|
|
8877
|
+
mkdir: {
|
|
8878
|
+
skipFlags: true,
|
|
8879
|
+
operands: "all"
|
|
8880
|
+
},
|
|
8881
|
+
ln: {
|
|
8882
|
+
skipFlags: true,
|
|
8883
|
+
operands: "last"
|
|
8884
|
+
},
|
|
8885
|
+
chmod: {
|
|
8886
|
+
skipFlags: true,
|
|
8887
|
+
operands: "all"
|
|
8888
|
+
},
|
|
8889
|
+
unlink: {
|
|
8890
|
+
skipFlags: true,
|
|
8891
|
+
operands: "all"
|
|
8892
|
+
},
|
|
8893
|
+
install: {
|
|
8894
|
+
skipFlags: true,
|
|
8895
|
+
operands: "last"
|
|
8896
|
+
},
|
|
8897
|
+
dd: {
|
|
8898
|
+
skipFlags: false,
|
|
8899
|
+
operands: "all"
|
|
8900
|
+
},
|
|
8901
|
+
sed: {
|
|
8902
|
+
skipFlags: true,
|
|
8903
|
+
operands: "afterI"
|
|
8904
|
+
},
|
|
8905
|
+
"remove-item": {
|
|
8906
|
+
skipFlags: true,
|
|
8907
|
+
operands: "all"
|
|
8908
|
+
},
|
|
8909
|
+
"set-content": {
|
|
8910
|
+
skipFlags: true,
|
|
8911
|
+
operands: "all"
|
|
8912
|
+
},
|
|
8913
|
+
"add-content": {
|
|
8914
|
+
skipFlags: true,
|
|
8915
|
+
operands: "all"
|
|
8916
|
+
},
|
|
8917
|
+
"out-file": {
|
|
8918
|
+
skipFlags: true,
|
|
8919
|
+
operands: "all"
|
|
8920
|
+
},
|
|
8921
|
+
"move-item": {
|
|
8922
|
+
skipFlags: true,
|
|
8923
|
+
operands: "all"
|
|
8924
|
+
},
|
|
8925
|
+
"copy-item": {
|
|
8926
|
+
skipFlags: true,
|
|
8927
|
+
operands: "all"
|
|
8928
|
+
},
|
|
8929
|
+
"new-item": {
|
|
8930
|
+
skipFlags: true,
|
|
8931
|
+
operands: "all"
|
|
8932
|
+
},
|
|
8933
|
+
del: {
|
|
8934
|
+
skipFlags: true,
|
|
8935
|
+
operands: "all"
|
|
8936
|
+
},
|
|
8937
|
+
erase: {
|
|
8938
|
+
skipFlags: true,
|
|
8939
|
+
operands: "all"
|
|
8940
|
+
},
|
|
8941
|
+
move: {
|
|
8942
|
+
skipFlags: true,
|
|
8943
|
+
operands: "all"
|
|
8944
|
+
},
|
|
8945
|
+
copy: {
|
|
8946
|
+
skipFlags: true,
|
|
8947
|
+
operands: "all"
|
|
8948
|
+
}
|
|
8949
|
+
};
|
|
8950
|
+
const GIT_WRITE_SUBCMDS = /* @__PURE__ */ new Set([
|
|
8951
|
+
"checkout",
|
|
8952
|
+
"reset",
|
|
8953
|
+
"clean",
|
|
8954
|
+
"restore",
|
|
8955
|
+
"rm",
|
|
8956
|
+
"mv",
|
|
8957
|
+
"stash"
|
|
8958
|
+
]);
|
|
8959
|
+
function shellWords(segment) {
|
|
8960
|
+
const out = [];
|
|
8961
|
+
const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
8962
|
+
for (let m = re.exec(segment); m !== null; m = re.exec(segment)) out.push(m[1] ?? m[2] ?? m[3] ?? "");
|
|
8963
|
+
return out;
|
|
8964
|
+
}
|
|
8965
|
+
function looksLikePath(word) {
|
|
8966
|
+
if (word === "" || word === "-" || word === "--") return false;
|
|
8967
|
+
if (word.startsWith("-") && !word.startsWith("./") && !word.startsWith("../")) return false;
|
|
8968
|
+
if (/^\$\(|^`|^\$[A-Za-z_{]/.test(word)) return false;
|
|
8969
|
+
return true;
|
|
8970
|
+
}
|
|
8971
|
+
function extractShellWriteTargets(command) {
|
|
8972
|
+
const out = [];
|
|
8973
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8974
|
+
const push = (rawPath, op) => {
|
|
8975
|
+
if (!looksLikePath(rawPath) || seen.has(rawPath)) return;
|
|
8976
|
+
seen.add(rawPath);
|
|
8977
|
+
out.push({
|
|
8978
|
+
rawPath,
|
|
8979
|
+
op,
|
|
8980
|
+
content: void 0
|
|
8981
|
+
});
|
|
8982
|
+
};
|
|
8983
|
+
for (let m = REDIRECT_RE.exec(command); m !== null; m = REDIRECT_RE.exec(command)) {
|
|
8984
|
+
const target = m[2] ?? m[3] ?? m[4] ?? "";
|
|
8985
|
+
if (target !== "" && !/^&\d$/.test(target) && !/^\/dev\//.test(target)) push(target, "write");
|
|
8986
|
+
}
|
|
8987
|
+
REDIRECT_RE.lastIndex = 0;
|
|
8988
|
+
for (const seg of command.split(SHELL_SPLIT_RE)) {
|
|
8989
|
+
const words = shellWords(seg);
|
|
8990
|
+
let i = 0;
|
|
8991
|
+
while (i < words.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(words[i] ?? "")) i++;
|
|
8992
|
+
while (i < words.length && [
|
|
8993
|
+
"sudo",
|
|
8994
|
+
"exec",
|
|
8995
|
+
"command",
|
|
8996
|
+
"nohup",
|
|
8997
|
+
"time"
|
|
8998
|
+
].includes(words[i] ?? "")) i++;
|
|
8999
|
+
const cmd = (words[i] ?? "").toLowerCase().replace(/^.*[\\/]/, "");
|
|
9000
|
+
const rest = words.slice(i + 1);
|
|
9001
|
+
if (cmd === "git") {
|
|
9002
|
+
const sub = rest.find((w) => !w.startsWith("-"));
|
|
9003
|
+
if (sub !== void 0 && GIT_WRITE_SUBCMDS.has(sub)) {
|
|
9004
|
+
const afterSub = rest.slice(rest.indexOf(sub) + 1);
|
|
9005
|
+
const dd = afterSub.indexOf("--");
|
|
9006
|
+
const operands = dd === -1 ? afterSub.filter((w) => !w.startsWith("-")) : afterSub.slice(dd + 1);
|
|
9007
|
+
if (sub === "clean" || sub === "reset" && afterSub.includes("--hard")) push(".", "delete");
|
|
9008
|
+
for (const p of operands) push(p, sub === "rm" || sub === "clean" ? "delete" : "write");
|
|
9009
|
+
}
|
|
9010
|
+
continue;
|
|
9011
|
+
}
|
|
9012
|
+
const rule = WRITE_CMDS[cmd];
|
|
9013
|
+
if (rule === void 0) continue;
|
|
9014
|
+
if (cmd === "sed") {
|
|
9015
|
+
if (!rest.some((w) => /^-[a-zA-Z]*i/.test(w) || w === "--in-place" || w.startsWith("--in-place="))) continue;
|
|
9016
|
+
const hasScriptFlag = rest.some((w) => /^-[a-zA-Z]*[ef]/.test(w) || /^--(expression|file)/.test(w));
|
|
9017
|
+
const nonFlags = [];
|
|
9018
|
+
for (let k = 0; k < rest.length; k++) {
|
|
9019
|
+
const w = rest[k] ?? "";
|
|
9020
|
+
if (w.startsWith("-")) {
|
|
9021
|
+
if (/^-[a-zA-Z]*[ef]$/.test(w) || w === "--expression" || w === "--file") k++;
|
|
9022
|
+
continue;
|
|
9023
|
+
}
|
|
9024
|
+
nonFlags.push(w);
|
|
9025
|
+
}
|
|
9026
|
+
const files = hasScriptFlag ? nonFlags : nonFlags.slice(1);
|
|
9027
|
+
for (const w of files) push(w, "write");
|
|
9028
|
+
continue;
|
|
9029
|
+
}
|
|
9030
|
+
if (cmd === "dd") {
|
|
9031
|
+
for (const w of rest) if (w.startsWith("of=")) push(w.slice(3), "write");
|
|
9032
|
+
continue;
|
|
9033
|
+
}
|
|
9034
|
+
let operands;
|
|
9035
|
+
if (cmd.includes("-")) {
|
|
9036
|
+
operands = [];
|
|
9037
|
+
const PATH_PARAMS = [
|
|
9038
|
+
"path",
|
|
9039
|
+
"literalpath",
|
|
9040
|
+
"filepath",
|
|
9041
|
+
"destination",
|
|
9042
|
+
"newname",
|
|
9043
|
+
"target"
|
|
9044
|
+
];
|
|
9045
|
+
const VALUE_PARAMS = /* @__PURE__ */ new Set([
|
|
9046
|
+
...PATH_PARAMS,
|
|
9047
|
+
"value",
|
|
9048
|
+
"encoding",
|
|
9049
|
+
"filter",
|
|
9050
|
+
"include",
|
|
9051
|
+
"exclude",
|
|
9052
|
+
"itemtype",
|
|
9053
|
+
"name"
|
|
9054
|
+
]);
|
|
9055
|
+
for (let k = 0; k < rest.length; k++) {
|
|
9056
|
+
const w = rest[k] ?? "";
|
|
9057
|
+
if (w.startsWith("-")) {
|
|
9058
|
+
const name = w.slice(1).toLowerCase();
|
|
9059
|
+
const val = rest[k + 1];
|
|
9060
|
+
if (PATH_PARAMS.includes(name) && val !== void 0) operands.push(val);
|
|
9061
|
+
if (VALUE_PARAMS.has(name) && val !== void 0 && !val.startsWith("-")) k++;
|
|
9062
|
+
continue;
|
|
9063
|
+
}
|
|
9064
|
+
operands.push(w);
|
|
9065
|
+
}
|
|
9066
|
+
} else operands = rule.skipFlags ? rest.filter((w) => !w.startsWith("-") || w === "-") : rest;
|
|
9067
|
+
const op = cmd === "rm" || cmd === "rmdir" || cmd === "unlink" || cmd === "del" || cmd === "erase" || cmd === "remove-item" ? "delete" : "write";
|
|
9068
|
+
if (rule.operands === "last") {
|
|
9069
|
+
const last = operands[operands.length - 1];
|
|
9070
|
+
if (last !== void 0) push(last, op);
|
|
9071
|
+
} else for (const w of operands) push(w, op);
|
|
9072
|
+
}
|
|
9073
|
+
return out;
|
|
9074
|
+
}
|
|
8674
9075
|
const PATCH_HEADER = /^\*\*\* (Update|Add|Delete) File: (.+?)\s*$/;
|
|
8675
9076
|
const PATCH_MOVE = /^\*\*\* Move to: (.+?)\s*$/;
|
|
8676
9077
|
function parseApplyPatch(patch) {
|
|
@@ -8715,7 +9116,33 @@ function joinContent(parts) {
|
|
|
8715
9116
|
return present.length === 0 ? void 0 : present.join("\n");
|
|
8716
9117
|
}
|
|
8717
9118
|
function extractTargets(p) {
|
|
8718
|
-
|
|
9119
|
+
const cls = classifyTool(p.toolName);
|
|
9120
|
+
if (cls === "other") return {
|
|
9121
|
+
cls,
|
|
9122
|
+
targets: [],
|
|
9123
|
+
undetermined: false
|
|
9124
|
+
};
|
|
9125
|
+
if (cls === "shell") {
|
|
9126
|
+
const command = firstString(p.input, SHELL_COMMAND_KEYS);
|
|
9127
|
+
if (command === void 0) return {
|
|
9128
|
+
cls,
|
|
9129
|
+
targets: [],
|
|
9130
|
+
undetermined: false
|
|
9131
|
+
};
|
|
9132
|
+
return {
|
|
9133
|
+
cls,
|
|
9134
|
+
targets: extractShellWriteTargets(command),
|
|
9135
|
+
undetermined: false
|
|
9136
|
+
};
|
|
9137
|
+
}
|
|
9138
|
+
const targets = extractWriteTargets(p);
|
|
9139
|
+
return {
|
|
9140
|
+
cls,
|
|
9141
|
+
targets,
|
|
9142
|
+
undetermined: targets.length === 0
|
|
9143
|
+
};
|
|
9144
|
+
}
|
|
9145
|
+
function extractWriteTargets(p) {
|
|
8719
9146
|
const n = p.toolName.toLowerCase();
|
|
8720
9147
|
const input = p.input;
|
|
8721
9148
|
if (n.includes("apply_patch")) {
|
|
@@ -8773,9 +9200,9 @@ const PATH_CODES = /* @__PURE__ */ new Set([
|
|
|
8773
9200
|
"ERR_PATH_RESERVED_NAME",
|
|
8774
9201
|
"ERR_PATH_INVALID_CHAR"
|
|
8775
9202
|
]);
|
|
8776
|
-
async function resolveGateTarget(rootReal, target) {
|
|
9203
|
+
async function resolveGateTarget(rootReal, target, cwdReal = rootReal) {
|
|
8777
9204
|
const supplied = target.rawPath.replace(/\\/g, "/");
|
|
8778
|
-
let abs = path.isAbsolute(supplied) ? path.resolve(supplied) : path.resolve(
|
|
9205
|
+
let abs = path.isAbsolute(supplied) ? path.resolve(supplied) : path.resolve(cwdReal, supplied);
|
|
8779
9206
|
if (path.isAbsolute(supplied)) abs = await canonicalizeExisting(abs);
|
|
8780
9207
|
const rel = path.relative(rootReal, abs);
|
|
8781
9208
|
if (rel === "" || rel === "." || rel.startsWith("..") || path.isAbsolute(rel)) throw new GateDeny("ERR_CONTAINMENT", "target escapes root", supplied);
|
|
@@ -8865,7 +9292,8 @@ async function runGatePredicates(targets, profile) {
|
|
|
8865
9292
|
const hasContent = targets.some((t) => t.target.content !== void 0);
|
|
8866
9293
|
if (hasContent && profile.noSecrets) findings.push(...await contentNoSecrets.run(ctx, {
|
|
8867
9294
|
disable: [],
|
|
8868
|
-
allowPaths: []
|
|
9295
|
+
allowPaths: [],
|
|
9296
|
+
pii: profile.pii
|
|
8869
9297
|
}));
|
|
8870
9298
|
if (hasContent && profile.maxBytes !== void 0) findings.push(...await contentMaxBytes.run(ctx, { max: profile.maxBytes }));
|
|
8871
9299
|
const f = findings[0];
|
|
@@ -8884,29 +9312,84 @@ const RANK = {
|
|
|
8884
9312
|
info: 2,
|
|
8885
9313
|
debug: 3
|
|
8886
9314
|
};
|
|
8887
|
-
function
|
|
9315
|
+
function denyDocument(d, reason, extra) {
|
|
9316
|
+
return {
|
|
9317
|
+
hookSpecificOutput: {
|
|
9318
|
+
hookEventName: "PreToolUse",
|
|
9319
|
+
permissionDecision: "deny",
|
|
9320
|
+
permissionDecisionReason: reason
|
|
9321
|
+
},
|
|
9322
|
+
permissionDecision: "deny",
|
|
9323
|
+
permissionDecisionReason: reason,
|
|
9324
|
+
axiom: {
|
|
9325
|
+
verdict: "deny",
|
|
9326
|
+
code: d.code,
|
|
9327
|
+
...d.relPath === void 0 ? {} : { path: d.relPath },
|
|
9328
|
+
...extra.toolClass === void 0 ? {} : { toolClass: extra.toolClass },
|
|
9329
|
+
standard: "owasp-acs/0.1"
|
|
9330
|
+
}
|
|
9331
|
+
};
|
|
9332
|
+
}
|
|
9333
|
+
function denyResult(d, stderr, t0, extra = {}) {
|
|
8888
9334
|
const where = d.relPath === void 0 ? "" : ` (${d.relPath})`;
|
|
8889
9335
|
const reason = `AXIOM GATE DENY ${d.code}: ${d.message}${where}`;
|
|
8890
9336
|
stderr.push(reason);
|
|
8891
9337
|
return {
|
|
8892
9338
|
decision: "deny",
|
|
9339
|
+
verdict: "deny",
|
|
8893
9340
|
exitCode: 2,
|
|
8894
9341
|
code: d.code,
|
|
8895
9342
|
reason: d.message,
|
|
8896
9343
|
...d.relPath === void 0 ? {} : { path: d.relPath },
|
|
8897
|
-
stdout: JSON.stringify(
|
|
8898
|
-
hookEventName: "PreToolUse",
|
|
8899
|
-
permissionDecision: "deny",
|
|
8900
|
-
permissionDecisionReason: reason
|
|
8901
|
-
} }),
|
|
9344
|
+
stdout: JSON.stringify(denyDocument(d, reason, extra)),
|
|
8902
9345
|
stderr,
|
|
8903
|
-
durationMs: performance.now() - t0
|
|
8904
|
-
|
|
9346
|
+
durationMs: performance.now() - t0,
|
|
9347
|
+
...extra.toolClass === void 0 ? {} : { toolClass: extra.toolClass },
|
|
9348
|
+
...extra.root === void 0 ? {} : { root: extra.root }
|
|
9349
|
+
};
|
|
9350
|
+
}
|
|
9351
|
+
const AXIOM_REPO_MARKERS = [
|
|
9352
|
+
"journal",
|
|
9353
|
+
"cas",
|
|
9354
|
+
"applied",
|
|
9355
|
+
"profiles",
|
|
9356
|
+
"trust",
|
|
9357
|
+
"lock",
|
|
9358
|
+
"backup"
|
|
9359
|
+
];
|
|
9360
|
+
async function discoverRoot(start, home = homedir()) {
|
|
9361
|
+
const homeReal = path.resolve(home);
|
|
9362
|
+
const isHomeOrAbove = (p) => {
|
|
9363
|
+
const a = IS_WIN32 ? p.toLowerCase() : p;
|
|
9364
|
+
const h = IS_WIN32 ? homeReal.toLowerCase() : homeReal;
|
|
9365
|
+
return a === h || h.startsWith(a.endsWith(path.sep) ? a : a + path.sep);
|
|
9366
|
+
};
|
|
9367
|
+
let cur = start;
|
|
9368
|
+
for (let depth = 0; depth < 64; depth++) {
|
|
9369
|
+
if (isHomeOrAbove(cur)) break;
|
|
9370
|
+
try {
|
|
9371
|
+
const st = await lstat(path.join(cur, ".git"));
|
|
9372
|
+
if (st.isDirectory() || st.isFile()) return cur;
|
|
9373
|
+
} catch {}
|
|
9374
|
+
try {
|
|
9375
|
+
const ax = path.join(cur, ".axiom");
|
|
9376
|
+
if ((await lstat(ax)).isDirectory()) for (const m of AXIOM_REPO_MARKERS) try {
|
|
9377
|
+
await lstat(path.join(ax, m));
|
|
9378
|
+
return cur;
|
|
9379
|
+
} catch {}
|
|
9380
|
+
} catch {}
|
|
9381
|
+
const parent = path.dirname(cur);
|
|
9382
|
+
if (parent === cur) break;
|
|
9383
|
+
cur = parent;
|
|
9384
|
+
}
|
|
9385
|
+
return start;
|
|
8905
9386
|
}
|
|
8906
9387
|
async function runGate(payloadText, opts = {}) {
|
|
8907
9388
|
const t0 = performance.now();
|
|
8908
9389
|
const level = opts.logLevel ?? "warn";
|
|
8909
9390
|
const stderr = [];
|
|
9391
|
+
let toolClass;
|
|
9392
|
+
let rootUsed;
|
|
8910
9393
|
const log = (lvl, msg) => {
|
|
8911
9394
|
if (RANK[lvl] <= RANK[level]) stderr.push(`AXIOM GATE ${lvl.toUpperCase()}: ${msg}`);
|
|
8912
9395
|
};
|
|
@@ -8914,15 +9397,24 @@ async function runGate(payloadText, opts = {}) {
|
|
|
8914
9397
|
log("debug", `allow: ${why}`);
|
|
8915
9398
|
return {
|
|
8916
9399
|
decision: "allow",
|
|
9400
|
+
verdict: "allow",
|
|
8917
9401
|
exitCode: 0,
|
|
8918
9402
|
stderr,
|
|
8919
|
-
durationMs: performance.now() - t0
|
|
9403
|
+
durationMs: performance.now() - t0,
|
|
9404
|
+
...toolClass === void 0 ? {} : { toolClass },
|
|
9405
|
+
...rootUsed === void 0 ? {} : { root: rootUsed }
|
|
8920
9406
|
};
|
|
8921
9407
|
};
|
|
9408
|
+
const deny = (d) => denyResult(d, stderr, t0, {
|
|
9409
|
+
...toolClass === void 0 ? {} : { toolClass },
|
|
9410
|
+
...rootUsed === void 0 ? {} : { root: rootUsed }
|
|
9411
|
+
});
|
|
8922
9412
|
const internal = (why) => {
|
|
8923
|
-
if (opts.
|
|
8924
|
-
|
|
8925
|
-
|
|
9413
|
+
if (opts.failOpen === true) {
|
|
9414
|
+
log("warn", `${why} — failing open (--fail-open)`);
|
|
9415
|
+
return allow("internal error");
|
|
9416
|
+
}
|
|
9417
|
+
return deny(new GateDeny("ERR_INTERNAL", `${why} (fail-closed; pass --fail-open to allow)`));
|
|
8926
9418
|
};
|
|
8927
9419
|
if (payloadText === void 0) return internal("no payload on stdin (timeout or empty)");
|
|
8928
9420
|
let raw;
|
|
@@ -8932,26 +9424,61 @@ async function runGate(payloadText, opts = {}) {
|
|
|
8932
9424
|
return internal(`payload is not JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
8933
9425
|
}
|
|
8934
9426
|
const payload = normalizePayload(raw);
|
|
8935
|
-
if (payload === void 0)
|
|
8936
|
-
|
|
8937
|
-
|
|
9427
|
+
if (payload === void 0) {
|
|
9428
|
+
log("info", "payload carries no tool name");
|
|
9429
|
+
return allow("payload carries no tool name");
|
|
9430
|
+
}
|
|
9431
|
+
const extracted = extractTargets(payload);
|
|
9432
|
+
toolClass = extracted.cls;
|
|
9433
|
+
if (extracted.cls === "other") return allow(`tool ${payload.toolName} is not a write/shell tool`);
|
|
9434
|
+
if (extracted.cls === "shell" && opts.noShellScan === true) return allow(`tool ${payload.toolName} is a shell tool and --no-shell-scan is set`);
|
|
9435
|
+
if (extracted.undetermined) {
|
|
9436
|
+
if (opts.failOpen === true) {
|
|
9437
|
+
log("warn", `write tool ${payload.toolName} has no recognised path key — allowing (--fail-open)`);
|
|
9438
|
+
return allow("undetermined write target");
|
|
9439
|
+
}
|
|
9440
|
+
return deny(new GateDeny("ERR_UNSUPPORTED_OP", `write tool "${payload.toolName}" carries no recognised path key (${PATH_KEYS.join(", ")}); add it to the gate or pass --fail-open`));
|
|
9441
|
+
}
|
|
9442
|
+
const targets = extracted.targets;
|
|
9443
|
+
if (targets.length === 0) return allow(`tool ${payload.toolName} writes nothing recognisable`);
|
|
8938
9444
|
try {
|
|
8939
9445
|
const rootArg = payload.cwd ?? opts.root ?? (opts.cwdFallback ?? (() => process.cwd()))();
|
|
8940
9446
|
let rootReal = await realpathNative(path.resolve(rootArg));
|
|
8941
9447
|
if (IS_WIN32 && rootReal.startsWith("\\\\?\\")) rootReal = rootReal.slice(4);
|
|
9448
|
+
const cwdReal = rootReal;
|
|
9449
|
+
if (opts.noRootDiscovery !== true) {
|
|
9450
|
+
const discovered = await discoverRoot(rootReal, opts.home);
|
|
9451
|
+
if (discovered !== rootReal) {
|
|
9452
|
+
log("debug", `root discovered ${rootReal} → ${discovered}`);
|
|
9453
|
+
rootReal = discovered;
|
|
9454
|
+
}
|
|
9455
|
+
}
|
|
9456
|
+
rootUsed = rootReal;
|
|
8942
9457
|
const { profile, source } = await loadGateProfile({
|
|
8943
9458
|
root: rootReal,
|
|
8944
9459
|
...opts.profilePath === void 0 ? {} : { profilePath: opts.profilePath },
|
|
8945
9460
|
...opts.home === void 0 ? {} : { home: opts.home }
|
|
8946
9461
|
});
|
|
8947
|
-
log("debug", `root=${rootReal} profile=${source} targets=${targets.length}`);
|
|
9462
|
+
log("debug", `root=${rootReal} profile=${source} class=${extracted.cls} targets=${targets.length}`);
|
|
8948
9463
|
const resolved = [];
|
|
8949
|
-
for (const t of targets)
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
9464
|
+
for (const t of targets) try {
|
|
9465
|
+
resolved.push(await resolveGateTarget(rootReal, t, cwdReal));
|
|
9466
|
+
} catch (err) {
|
|
9467
|
+
if (extracted.cls === "shell" && err instanceof GateDeny && err.code !== "ERR_CONTAINMENT") {
|
|
9468
|
+
log("debug", `shell operand ${JSON.stringify(t.rawPath)} skipped: ${err.code}`);
|
|
9469
|
+
continue;
|
|
9470
|
+
}
|
|
9471
|
+
if (extracted.cls === "shell" && err instanceof GateDeny && t.rawPath === ".") {
|
|
9472
|
+
if (profile.deny.length > 0) throw new GateDeny("path.deny", "destructive git command on the whole tree", ".");
|
|
9473
|
+
continue;
|
|
9474
|
+
}
|
|
9475
|
+
throw err;
|
|
9476
|
+
}
|
|
9477
|
+
const verdict = await runGatePredicates(resolved, profile);
|
|
9478
|
+
if (verdict !== void 0) return deny(verdict);
|
|
9479
|
+
return allow(`${resolved.length} target(s) passed (${extracted.cls})`);
|
|
8953
9480
|
} catch (err) {
|
|
8954
|
-
if (err instanceof GateDeny) return
|
|
9481
|
+
if (err instanceof GateDeny) return deny(err);
|
|
8955
9482
|
return internal(err instanceof Error ? err.message : String(err));
|
|
8956
9483
|
}
|
|
8957
9484
|
}
|
|
@@ -9001,7 +9528,7 @@ function readStdin(stream, { timeoutMs = GATE_STDIN_TIMEOUT_MS, maxBytes = GATE_
|
|
|
9001
9528
|
stream.resume();
|
|
9002
9529
|
});
|
|
9003
9530
|
}
|
|
9004
|
-
const GATE_USAGE = "usage: axiom gate --stdin [--root <dir>] [--profile <file>] [--
|
|
9531
|
+
const GATE_USAGE = "usage: axiom gate --stdin [--root <dir>] [--profile <file>] [--fail-open] [--no-shell-scan] [--no-root-discovery] [--log-level error|warn|info|debug]";
|
|
9005
9532
|
async function gateMain(argv, io = {}) {
|
|
9006
9533
|
const t0 = performance.now();
|
|
9007
9534
|
let values;
|
|
@@ -9013,6 +9540,9 @@ async function gateMain(argv, io = {}) {
|
|
|
9013
9540
|
root: { type: "string" },
|
|
9014
9541
|
profile: { type: "string" },
|
|
9015
9542
|
strict: { type: "boolean" },
|
|
9543
|
+
"fail-open": { type: "boolean" },
|
|
9544
|
+
"no-shell-scan": { type: "boolean" },
|
|
9545
|
+
"no-root-discovery": { type: "boolean" },
|
|
9016
9546
|
"log-level": { type: "string" }
|
|
9017
9547
|
},
|
|
9018
9548
|
strict: true,
|
|
@@ -9021,6 +9551,7 @@ async function gateMain(argv, io = {}) {
|
|
|
9021
9551
|
} catch (err) {
|
|
9022
9552
|
return {
|
|
9023
9553
|
decision: "deny",
|
|
9554
|
+
verdict: "deny",
|
|
9024
9555
|
exitCode: 2,
|
|
9025
9556
|
reason: err instanceof Error ? err.message : String(err),
|
|
9026
9557
|
stderr: [`AXIOM GATE ERROR: ${err instanceof Error ? err.message : String(err)}`, GATE_USAGE],
|
|
@@ -9029,6 +9560,7 @@ async function gateMain(argv, io = {}) {
|
|
|
9029
9560
|
}
|
|
9030
9561
|
if (values.stdin !== true) return {
|
|
9031
9562
|
decision: "deny",
|
|
9563
|
+
verdict: "deny",
|
|
9032
9564
|
exitCode: 2,
|
|
9033
9565
|
reason: "--stdin is required",
|
|
9034
9566
|
stderr: ["AXIOM GATE ERROR: --stdin is required", GATE_USAGE],
|
|
@@ -9041,6 +9573,9 @@ async function gateMain(argv, io = {}) {
|
|
|
9041
9573
|
if (values.root !== void 0) opts.root = values.root;
|
|
9042
9574
|
if (values.profile !== void 0) opts.profilePath = values.profile;
|
|
9043
9575
|
if (values.strict === true) opts.strict = true;
|
|
9576
|
+
if (values["fail-open"] === true) opts.failOpen = true;
|
|
9577
|
+
if (values["no-shell-scan"] === true) opts.noShellScan = true;
|
|
9578
|
+
if (values["no-root-discovery"] === true) opts.noRootDiscovery = true;
|
|
9044
9579
|
if (io.cwdFallback !== void 0) opts.cwdFallback = io.cwdFallback;
|
|
9045
9580
|
if (io.home !== void 0) opts.home = io.home;
|
|
9046
9581
|
if (read.tooLarge) {
|