calllint 0.9.3 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1130 -87
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
6
|
|
|
7
7
|
// src/args.ts
|
|
@@ -247,17 +247,17 @@ function loadPolicyFile(path) {
|
|
|
247
247
|
let text;
|
|
248
248
|
try {
|
|
249
249
|
text = readFileSync(path, "utf8");
|
|
250
|
-
} catch (
|
|
250
|
+
} catch (err2) {
|
|
251
251
|
throw new PolicyLoadError(
|
|
252
|
-
`Could not read policy file ${path}: ${
|
|
252
|
+
`Could not read policy file ${path}: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
253
253
|
);
|
|
254
254
|
}
|
|
255
255
|
let parsed;
|
|
256
256
|
try {
|
|
257
257
|
parsed = JSON.parse(text);
|
|
258
|
-
} catch (
|
|
258
|
+
} catch (err2) {
|
|
259
259
|
throw new PolicyLoadError(
|
|
260
|
-
`Policy file ${path} is not valid JSON: ${
|
|
260
|
+
`Policy file ${path} is not valid JSON: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
261
261
|
);
|
|
262
262
|
}
|
|
263
263
|
return validatePolicy(parsed);
|
|
@@ -771,7 +771,7 @@ var SECRET_HINTS = [
|
|
|
771
771
|
];
|
|
772
772
|
function looksSecret(key) {
|
|
773
773
|
const upper = key.toUpperCase();
|
|
774
|
-
return SECRET_HINTS.some((
|
|
774
|
+
return SECRET_HINTS.some((h2) => upper.includes(h2));
|
|
775
775
|
}
|
|
776
776
|
function extractDockerEnvKeys(args) {
|
|
777
777
|
const keys = [];
|
|
@@ -1095,14 +1095,14 @@ var MUTATION_HINTS = [
|
|
|
1095
1095
|
function collectHints(text) {
|
|
1096
1096
|
if (!text) return [];
|
|
1097
1097
|
const lower = text.toLowerCase();
|
|
1098
|
-
return MUTATION_HINTS.filter((
|
|
1098
|
+
return MUTATION_HINTS.filter((h2) => lower.includes(h2));
|
|
1099
1099
|
}
|
|
1100
1100
|
function detectExternalMutation(ctx) {
|
|
1101
1101
|
const { server, binding } = ctx;
|
|
1102
1102
|
const hints = /* @__PURE__ */ new Set();
|
|
1103
1103
|
const evidence = [];
|
|
1104
|
-
for (const
|
|
1105
|
-
hints.add(
|
|
1104
|
+
for (const h2 of collectHints(binding.packageName)) {
|
|
1105
|
+
hints.add(h2);
|
|
1106
1106
|
evidence.push({
|
|
1107
1107
|
type: "runtime-binding",
|
|
1108
1108
|
key: "package",
|
|
@@ -1110,8 +1110,8 @@ function detectExternalMutation(ctx) {
|
|
|
1110
1110
|
});
|
|
1111
1111
|
}
|
|
1112
1112
|
for (const tool of server.providedTools) {
|
|
1113
|
-
for (const
|
|
1114
|
-
hints.add(
|
|
1113
|
+
for (const h2 of collectHints(tool.name)) {
|
|
1114
|
+
hints.add(h2);
|
|
1115
1115
|
evidence.push({ type: "tool-metadata", key: "tool", value: tool.name });
|
|
1116
1116
|
}
|
|
1117
1117
|
}
|
|
@@ -1189,7 +1189,7 @@ var PAYMENT_ACTION_VERBS = [
|
|
|
1189
1189
|
function collectHints2(text) {
|
|
1190
1190
|
if (!text) return [];
|
|
1191
1191
|
const lower = text.toLowerCase();
|
|
1192
|
-
return FINANCIAL_HINTS.filter((
|
|
1192
|
+
return FINANCIAL_HINTS.filter((h2) => lower.includes(h2));
|
|
1193
1193
|
}
|
|
1194
1194
|
function collectActionVerbs(text) {
|
|
1195
1195
|
if (!text) return [];
|
|
@@ -1244,8 +1244,8 @@ function detectFinancialAction(ctx) {
|
|
|
1244
1244
|
}
|
|
1245
1245
|
const hints = /* @__PURE__ */ new Set();
|
|
1246
1246
|
const evidence = [];
|
|
1247
|
-
for (const
|
|
1248
|
-
hints.add(
|
|
1247
|
+
for (const h2 of collectHints2(binding.packageName)) {
|
|
1248
|
+
hints.add(h2);
|
|
1249
1249
|
evidence.push({
|
|
1250
1250
|
type: "runtime-binding",
|
|
1251
1251
|
key: "package",
|
|
@@ -1253,8 +1253,8 @@ function detectFinancialAction(ctx) {
|
|
|
1253
1253
|
});
|
|
1254
1254
|
}
|
|
1255
1255
|
for (const tool of server.providedTools) {
|
|
1256
|
-
for (const
|
|
1257
|
-
hints.add(
|
|
1256
|
+
for (const h2 of collectHints2(tool.name)) {
|
|
1257
|
+
hints.add(h2);
|
|
1258
1258
|
evidence.push({ type: "tool-metadata", key: "tool", value: tool.name });
|
|
1259
1259
|
}
|
|
1260
1260
|
}
|
|
@@ -1393,7 +1393,7 @@ var SEND_PATTERNS = [
|
|
|
1393
1393
|
function packageHint(name) {
|
|
1394
1394
|
if (!name) return void 0;
|
|
1395
1395
|
const lower = name.toLowerCase();
|
|
1396
|
-
return MESSAGING_PACKAGE_HINTS.find((
|
|
1396
|
+
return MESSAGING_PACKAGE_HINTS.find((h2) => lower.includes(h2));
|
|
1397
1397
|
}
|
|
1398
1398
|
function textMatchesSend(text) {
|
|
1399
1399
|
if (!text) return false;
|
|
@@ -1456,7 +1456,7 @@ function readOauth(raw) {
|
|
|
1456
1456
|
function broadScope(scopes) {
|
|
1457
1457
|
for (const s of scopes) {
|
|
1458
1458
|
const lower = s.toLowerCase();
|
|
1459
|
-
if (BROAD_SCOPE_HINTS.some((
|
|
1459
|
+
if (BROAD_SCOPE_HINTS.some((h2) => lower === h2 || lower.includes(h2))) return s;
|
|
1460
1460
|
}
|
|
1461
1461
|
return void 0;
|
|
1462
1462
|
}
|
|
@@ -1862,8 +1862,8 @@ var ConfigParseError = class extends Error {
|
|
|
1862
1862
|
function parseJsonText(text, path) {
|
|
1863
1863
|
try {
|
|
1864
1864
|
return JSON.parse(text);
|
|
1865
|
-
} catch (
|
|
1866
|
-
const reason =
|
|
1865
|
+
} catch (err2) {
|
|
1866
|
+
const reason = err2 instanceof Error ? err2.message : String(err2);
|
|
1867
1867
|
throw new ConfigParseError(`Invalid JSON: ${reason}`, path);
|
|
1868
1868
|
}
|
|
1869
1869
|
}
|
|
@@ -2576,7 +2576,7 @@ function classifySurface(path, content) {
|
|
|
2576
2576
|
if (/(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock)$/.test(n)) return "NOOP";
|
|
2577
2577
|
const base = basename2(path);
|
|
2578
2578
|
if (MCP_CONFIG_BASENAMES.has(base)) return "SCAN";
|
|
2579
|
-
if (MCP_PATH_HINTS.some((
|
|
2579
|
+
if (MCP_PATH_HINTS.some((h2) => normalize(path).includes(normalize(h2)))) return "SCAN";
|
|
2580
2580
|
if (base === "config.toml" && content && /\[mcp_servers?\.[^\]]+\]/.test(content)) {
|
|
2581
2581
|
return "SCAN";
|
|
2582
2582
|
}
|
|
@@ -3892,10 +3892,10 @@ function scanCommand(args, deps) {
|
|
|
3892
3892
|
let policy;
|
|
3893
3893
|
try {
|
|
3894
3894
|
policy = loadPolicyOrDefault(policyPath);
|
|
3895
|
-
} catch (
|
|
3895
|
+
} catch (err2) {
|
|
3896
3896
|
return {
|
|
3897
3897
|
stdout: "",
|
|
3898
|
-
stderr:
|
|
3898
|
+
stderr: err2 instanceof Error ? err2.message : String(err2),
|
|
3899
3899
|
exitCode: EXIT.ERROR
|
|
3900
3900
|
};
|
|
3901
3901
|
}
|
|
@@ -3918,15 +3918,15 @@ function scanOneConfig(text, configPath, policy, args, deps, allowReceipt = true
|
|
|
3918
3918
|
extraFindings: deps.online?.extraFindings,
|
|
3919
3919
|
surfaces
|
|
3920
3920
|
});
|
|
3921
|
-
} catch (
|
|
3922
|
-
if (
|
|
3921
|
+
} catch (err2) {
|
|
3922
|
+
if (err2 instanceof ConfigParseError) {
|
|
3923
3923
|
return {
|
|
3924
3924
|
stdout: "",
|
|
3925
|
-
stderr: `Parse error in ${configPath}: ${
|
|
3925
|
+
stderr: `Parse error in ${configPath}: ${err2.message}`,
|
|
3926
3926
|
exitCode: EXIT.ERROR
|
|
3927
3927
|
};
|
|
3928
3928
|
}
|
|
3929
|
-
throw
|
|
3929
|
+
throw err2;
|
|
3930
3930
|
}
|
|
3931
3931
|
if (deps.writeCacheFile !== false) {
|
|
3932
3932
|
try {
|
|
@@ -3936,8 +3936,8 @@ function scanOneConfig(text, configPath, policy, args, deps, allowReceipt = true
|
|
|
3936
3936
|
}
|
|
3937
3937
|
const stdout = renderSummary(summary, args);
|
|
3938
3938
|
if (allowReceipt && flagBool(args.flags, "receipt")) {
|
|
3939
|
-
const
|
|
3940
|
-
if (
|
|
3939
|
+
const err2 = writeReceiptFile(summary, text, configPath, policy, args, deps);
|
|
3940
|
+
if (err2) return { stdout: "", stderr: err2, exitCode: EXIT.ERROR };
|
|
3941
3941
|
}
|
|
3942
3942
|
const exitCode = flagBool(args.flags, "ci") ? exitCodeFor(summary, policy) : EXIT.OK;
|
|
3943
3943
|
return { stdout, exitCode };
|
|
@@ -3982,10 +3982,10 @@ function scanChangedCommand(args, deps) {
|
|
|
3982
3982
|
let policy;
|
|
3983
3983
|
try {
|
|
3984
3984
|
policy = loadPolicyOrDefault(policyPath);
|
|
3985
|
-
} catch (
|
|
3985
|
+
} catch (err2) {
|
|
3986
3986
|
return {
|
|
3987
3987
|
stdout: "",
|
|
3988
|
-
stderr:
|
|
3988
|
+
stderr: err2 instanceof Error ? err2.message : String(err2),
|
|
3989
3989
|
exitCode: EXIT.ERROR
|
|
3990
3990
|
};
|
|
3991
3991
|
}
|
|
@@ -4114,7 +4114,7 @@ function genRuleCommand(args, deps) {
|
|
|
4114
4114
|
const host = flagStr(args.flags, "host");
|
|
4115
4115
|
if (!host) {
|
|
4116
4116
|
const list = RULE_HOSTS.map(
|
|
4117
|
-
(
|
|
4117
|
+
(h2) => ` ${h2.padEnd(10)} \u2192 ${RULE_TARGETS[h2].path} (${RULE_TARGETS[h2].label})`
|
|
4118
4118
|
).join("\n");
|
|
4119
4119
|
return {
|
|
4120
4120
|
stdout: `Usage: calllint gen-rule --host <host> [--write] [--out <path>]
|
|
@@ -4167,10 +4167,10 @@ function diagnosticsCommand(args, deps) {
|
|
|
4167
4167
|
let policy;
|
|
4168
4168
|
try {
|
|
4169
4169
|
policy = loadPolicyOrDefault(flagStr(args.flags, "policy"));
|
|
4170
|
-
} catch (
|
|
4170
|
+
} catch (err2) {
|
|
4171
4171
|
return {
|
|
4172
4172
|
stdout: "",
|
|
4173
|
-
stderr:
|
|
4173
|
+
stderr: err2 instanceof Error ? err2.message : String(err2),
|
|
4174
4174
|
exitCode: EXIT.ERROR
|
|
4175
4175
|
};
|
|
4176
4176
|
}
|
|
@@ -4187,15 +4187,15 @@ function diagnosticsCommand(args, deps) {
|
|
|
4187
4187
|
generatedAt: deps.generatedAt,
|
|
4188
4188
|
extraFindings: deps.online?.extraFindings
|
|
4189
4189
|
});
|
|
4190
|
-
} catch (
|
|
4191
|
-
if (
|
|
4190
|
+
} catch (err2) {
|
|
4191
|
+
if (err2 instanceof ConfigParseError) {
|
|
4192
4192
|
return {
|
|
4193
4193
|
stdout: "",
|
|
4194
|
-
stderr: `Parse error in ${configPath}: ${
|
|
4194
|
+
stderr: `Parse error in ${configPath}: ${err2.message}`,
|
|
4195
4195
|
exitCode: EXIT.ERROR
|
|
4196
4196
|
};
|
|
4197
4197
|
}
|
|
4198
|
-
throw
|
|
4198
|
+
throw err2;
|
|
4199
4199
|
}
|
|
4200
4200
|
const stdout = renderDiagnostics(summary);
|
|
4201
4201
|
const exitCode = flagBool(args.flags, "ci") ? exitCodeFor(summary, policy) : EXIT.OK;
|
|
@@ -4255,10 +4255,10 @@ function policyCommand(args, deps) {
|
|
|
4255
4255
|
try {
|
|
4256
4256
|
const policy = loadPolicyOrDefault(flagStr(args.flags, "policy"));
|
|
4257
4257
|
return { stdout: JSON.stringify(policy, null, 2), exitCode: EXIT.OK };
|
|
4258
|
-
} catch (
|
|
4258
|
+
} catch (err2) {
|
|
4259
4259
|
return {
|
|
4260
4260
|
stdout: "",
|
|
4261
|
-
stderr:
|
|
4261
|
+
stderr: err2 instanceof Error ? err2.message : String(err2),
|
|
4262
4262
|
exitCode: EXIT.ERROR
|
|
4263
4263
|
};
|
|
4264
4264
|
}
|
|
@@ -4275,8 +4275,8 @@ import { join as join10 } from "node:path";
|
|
|
4275
4275
|
function loadPolicy(args) {
|
|
4276
4276
|
try {
|
|
4277
4277
|
return loadPolicyOrDefault(flagStr(args.flags, "policy"));
|
|
4278
|
-
} catch (
|
|
4279
|
-
return { error:
|
|
4278
|
+
} catch (err2) {
|
|
4279
|
+
return { error: err2 instanceof Error ? err2.message : String(err2) };
|
|
4280
4280
|
}
|
|
4281
4281
|
}
|
|
4282
4282
|
function baselinePathFor(args, cwd) {
|
|
@@ -4294,19 +4294,19 @@ function baselineCommand(args, deps) {
|
|
|
4294
4294
|
generatedAt: deps.generatedAt,
|
|
4295
4295
|
extraFindings: deps.online?.extraFindings
|
|
4296
4296
|
});
|
|
4297
|
-
} catch (
|
|
4298
|
-
if (
|
|
4299
|
-
return { stdout: "", stderr: `Parse error in ${input.configPath}: ${
|
|
4297
|
+
} catch (err2) {
|
|
4298
|
+
if (err2 instanceof ConfigParseError) {
|
|
4299
|
+
return { stdout: "", stderr: `Parse error in ${input.configPath}: ${err2.message}`, exitCode: EXIT.ERROR };
|
|
4300
4300
|
}
|
|
4301
|
-
throw
|
|
4301
|
+
throw err2;
|
|
4302
4302
|
}
|
|
4303
4303
|
const baseline = buildBaseline(summary, deps.generatedAt);
|
|
4304
4304
|
const path = baselinePathFor(args, deps.cwd);
|
|
4305
4305
|
if (deps.writeBaselineFile !== false) {
|
|
4306
4306
|
try {
|
|
4307
4307
|
writeBaseline(baseline, path);
|
|
4308
|
-
} catch (
|
|
4309
|
-
return { stdout: "", stderr: `Could not write baseline: ${String(
|
|
4308
|
+
} catch (err2) {
|
|
4309
|
+
return { stdout: "", stderr: `Could not write baseline: ${String(err2)}`, exitCode: EXIT.ERROR };
|
|
4310
4310
|
}
|
|
4311
4311
|
}
|
|
4312
4312
|
if (flagBool(args.flags, "json")) {
|
|
@@ -4342,11 +4342,11 @@ function verifyCommand(args, deps) {
|
|
|
4342
4342
|
generatedAt: deps.generatedAt,
|
|
4343
4343
|
extraFindings: deps.online?.extraFindings
|
|
4344
4344
|
});
|
|
4345
|
-
} catch (
|
|
4346
|
-
if (
|
|
4347
|
-
return { stdout: "", stderr: `Parse error in ${input.configPath}: ${
|
|
4345
|
+
} catch (err2) {
|
|
4346
|
+
if (err2 instanceof ConfigParseError) {
|
|
4347
|
+
return { stdout: "", stderr: `Parse error in ${input.configPath}: ${err2.message}`, exitCode: EXIT.ERROR };
|
|
4348
4348
|
}
|
|
4349
|
-
throw
|
|
4349
|
+
throw err2;
|
|
4350
4350
|
}
|
|
4351
4351
|
const drift = computeDrift(baseline, summary, deps.generatedAt);
|
|
4352
4352
|
const stdout = flagBool(args.flags, "json") ? renderDriftJson(drift) : renderDrift(drift);
|
|
@@ -4391,8 +4391,8 @@ function approveCommand(args, deps) {
|
|
|
4391
4391
|
if (deps.writeFile !== false) {
|
|
4392
4392
|
try {
|
|
4393
4393
|
writeApproved(state, path);
|
|
4394
|
-
} catch (
|
|
4395
|
-
return { stdout: "", stderr: `Could not write approved state: ${String(
|
|
4394
|
+
} catch (err2) {
|
|
4395
|
+
return { stdout: "", stderr: `Could not write approved state: ${String(err2)}`, exitCode: EXIT.ERROR };
|
|
4396
4396
|
}
|
|
4397
4397
|
}
|
|
4398
4398
|
if (flagBool(args.flags, "json")) {
|
|
@@ -4406,22 +4406,611 @@ function approveCommand(args, deps) {
|
|
|
4406
4406
|
}
|
|
4407
4407
|
|
|
4408
4408
|
// src/commands/receipt.ts
|
|
4409
|
-
import { readFileSync as readFileSync8 } from "node:fs";
|
|
4409
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "node:fs";
|
|
4410
4410
|
import { resolve as resolve4 } from "node:path";
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4411
|
+
|
|
4412
|
+
// ../../packages/signature/src/ed25519.ts
|
|
4413
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
4414
|
+
|
|
4415
|
+
// ../../node_modules/.pnpm/@noble+ed25519@2.3.0/node_modules/@noble/ed25519/index.js
|
|
4416
|
+
var ed25519_CURVE = {
|
|
4417
|
+
p: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn,
|
|
4418
|
+
n: 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3edn,
|
|
4419
|
+
h: 8n,
|
|
4420
|
+
a: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffecn,
|
|
4421
|
+
d: 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3n,
|
|
4422
|
+
Gx: 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an,
|
|
4423
|
+
Gy: 0x6666666666666666666666666666666666666666666666666666666666666658n
|
|
4424
|
+
};
|
|
4425
|
+
var { p: P, n: N, Gx, Gy, a: _a, d: _d } = ed25519_CURVE;
|
|
4426
|
+
var h = 8n;
|
|
4427
|
+
var L = 32;
|
|
4428
|
+
var L2 = 64;
|
|
4429
|
+
var err = (m = "") => {
|
|
4430
|
+
throw new Error(m);
|
|
4431
|
+
};
|
|
4432
|
+
var isBig = (n) => typeof n === "bigint";
|
|
4433
|
+
var isStr = (s) => typeof s === "string";
|
|
4434
|
+
var isBytes = (a) => a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
4435
|
+
var abytes = (a, l) => !isBytes(a) || typeof l === "number" && l > 0 && a.length !== l ? err("Uint8Array expected") : a;
|
|
4436
|
+
var u8n = (len) => new Uint8Array(len);
|
|
4437
|
+
var u8fr = (buf) => Uint8Array.from(buf);
|
|
4438
|
+
var padh = (n, pad) => n.toString(16).padStart(pad, "0");
|
|
4439
|
+
var bytesToHex = (b) => Array.from(abytes(b)).map((e) => padh(e, 2)).join("");
|
|
4440
|
+
var C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
|
|
4441
|
+
var _ch = (ch) => {
|
|
4442
|
+
if (ch >= C._0 && ch <= C._9)
|
|
4443
|
+
return ch - C._0;
|
|
4444
|
+
if (ch >= C.A && ch <= C.F)
|
|
4445
|
+
return ch - (C.A - 10);
|
|
4446
|
+
if (ch >= C.a && ch <= C.f)
|
|
4447
|
+
return ch - (C.a - 10);
|
|
4448
|
+
return;
|
|
4449
|
+
};
|
|
4450
|
+
var hexToBytes = (hex) => {
|
|
4451
|
+
const e = "hex invalid";
|
|
4452
|
+
if (!isStr(hex))
|
|
4453
|
+
return err(e);
|
|
4454
|
+
const hl = hex.length;
|
|
4455
|
+
const al = hl / 2;
|
|
4456
|
+
if (hl % 2)
|
|
4457
|
+
return err(e);
|
|
4458
|
+
const array = u8n(al);
|
|
4459
|
+
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
4460
|
+
const n1 = _ch(hex.charCodeAt(hi));
|
|
4461
|
+
const n2 = _ch(hex.charCodeAt(hi + 1));
|
|
4462
|
+
if (n1 === void 0 || n2 === void 0)
|
|
4463
|
+
return err(e);
|
|
4464
|
+
array[ai] = n1 * 16 + n2;
|
|
4465
|
+
}
|
|
4466
|
+
return array;
|
|
4467
|
+
};
|
|
4468
|
+
var toU8 = (a, len) => abytes(isStr(a) ? hexToBytes(a) : u8fr(abytes(a)), len);
|
|
4469
|
+
var cr = () => globalThis?.crypto;
|
|
4470
|
+
var subtle = () => cr()?.subtle ?? err("crypto.subtle must be defined");
|
|
4471
|
+
var concatBytes = (...arrs) => {
|
|
4472
|
+
const r = u8n(arrs.reduce((sum, a) => sum + abytes(a).length, 0));
|
|
4473
|
+
let pad = 0;
|
|
4474
|
+
arrs.forEach((a) => {
|
|
4475
|
+
r.set(a, pad);
|
|
4476
|
+
pad += a.length;
|
|
4477
|
+
});
|
|
4478
|
+
return r;
|
|
4479
|
+
};
|
|
4480
|
+
var randomBytes2 = (len = L) => {
|
|
4481
|
+
const c = cr();
|
|
4482
|
+
return c.getRandomValues(u8n(len));
|
|
4483
|
+
};
|
|
4484
|
+
var big = BigInt;
|
|
4485
|
+
var arange = (n, min, max, msg = "bad number: out of range") => isBig(n) && min <= n && n < max ? n : err(msg);
|
|
4486
|
+
var M = (a, b = P) => {
|
|
4487
|
+
const r = a % b;
|
|
4488
|
+
return r >= 0n ? r : b + r;
|
|
4489
|
+
};
|
|
4490
|
+
var modN = (a) => M(a, N);
|
|
4491
|
+
var invert = (num, md) => {
|
|
4492
|
+
if (num === 0n || md <= 0n)
|
|
4493
|
+
err("no inverse n=" + num + " mod=" + md);
|
|
4494
|
+
let a = M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n;
|
|
4495
|
+
while (a !== 0n) {
|
|
4496
|
+
const q = b / a, r = b % a;
|
|
4497
|
+
const m = x - u * q, n = y - v * q;
|
|
4498
|
+
b = a, a = r, x = u, y = v, u = m, v = n;
|
|
4499
|
+
}
|
|
4500
|
+
return b === 1n ? M(x, md) : err("no inverse");
|
|
4501
|
+
};
|
|
4502
|
+
var callHash = (name) => {
|
|
4503
|
+
const fn = etc[name];
|
|
4504
|
+
if (typeof fn !== "function")
|
|
4505
|
+
err("hashes." + name + " not set");
|
|
4506
|
+
return fn;
|
|
4507
|
+
};
|
|
4508
|
+
var apoint = (p) => p instanceof Point ? p : err("Point expected");
|
|
4509
|
+
var B256 = 2n ** 256n;
|
|
4510
|
+
var Point = class _Point {
|
|
4511
|
+
static BASE;
|
|
4512
|
+
static ZERO;
|
|
4513
|
+
ex;
|
|
4514
|
+
ey;
|
|
4515
|
+
ez;
|
|
4516
|
+
et;
|
|
4517
|
+
constructor(ex, ey, ez, et) {
|
|
4518
|
+
const max = B256;
|
|
4519
|
+
this.ex = arange(ex, 0n, max);
|
|
4520
|
+
this.ey = arange(ey, 0n, max);
|
|
4521
|
+
this.ez = arange(ez, 1n, max);
|
|
4522
|
+
this.et = arange(et, 0n, max);
|
|
4523
|
+
Object.freeze(this);
|
|
4524
|
+
}
|
|
4525
|
+
static fromAffine(p) {
|
|
4526
|
+
return new _Point(p.x, p.y, 1n, M(p.x * p.y));
|
|
4527
|
+
}
|
|
4528
|
+
/** RFC8032 5.1.3: Uint8Array to Point. */
|
|
4529
|
+
static fromBytes(hex, zip215 = false) {
|
|
4530
|
+
const d = _d;
|
|
4531
|
+
const normed = u8fr(abytes(hex, L));
|
|
4532
|
+
const lastByte = hex[31];
|
|
4533
|
+
normed[31] = lastByte & ~128;
|
|
4534
|
+
const y = bytesToNumLE(normed);
|
|
4535
|
+
const max = zip215 ? B256 : P;
|
|
4536
|
+
arange(y, 0n, max);
|
|
4537
|
+
const y2 = M(y * y);
|
|
4538
|
+
const u = M(y2 - 1n);
|
|
4539
|
+
const v = M(d * y2 + 1n);
|
|
4540
|
+
let { isValid, value: x } = uvRatio(u, v);
|
|
4541
|
+
if (!isValid)
|
|
4542
|
+
err("bad point: y not sqrt");
|
|
4543
|
+
const isXOdd = (x & 1n) === 1n;
|
|
4544
|
+
const isLastByteOdd = (lastByte & 128) !== 0;
|
|
4545
|
+
if (!zip215 && x === 0n && isLastByteOdd)
|
|
4546
|
+
err("bad point: x==0, isLastByteOdd");
|
|
4547
|
+
if (isLastByteOdd !== isXOdd)
|
|
4548
|
+
x = M(-x);
|
|
4549
|
+
return new _Point(x, y, 1n, M(x * y));
|
|
4550
|
+
}
|
|
4551
|
+
/** Checks if the point is valid and on-curve. */
|
|
4552
|
+
assertValidity() {
|
|
4553
|
+
const a = _a;
|
|
4554
|
+
const d = _d;
|
|
4555
|
+
const p = this;
|
|
4556
|
+
if (p.is0())
|
|
4557
|
+
throw new Error("bad point: ZERO");
|
|
4558
|
+
const { ex: X, ey: Y, ez: Z, et: T } = p;
|
|
4559
|
+
const X2 = M(X * X);
|
|
4560
|
+
const Y2 = M(Y * Y);
|
|
4561
|
+
const Z2 = M(Z * Z);
|
|
4562
|
+
const Z4 = M(Z2 * Z2);
|
|
4563
|
+
const aX2 = M(X2 * a);
|
|
4564
|
+
const left = M(Z2 * M(aX2 + Y2));
|
|
4565
|
+
const right = M(Z4 + M(d * M(X2 * Y2)));
|
|
4566
|
+
if (left !== right)
|
|
4567
|
+
throw new Error("bad point: equation left != right (1)");
|
|
4568
|
+
const XY = M(X * Y);
|
|
4569
|
+
const ZT = M(Z * T);
|
|
4570
|
+
if (XY !== ZT)
|
|
4571
|
+
throw new Error("bad point: equation left != right (2)");
|
|
4572
|
+
return this;
|
|
4573
|
+
}
|
|
4574
|
+
/** Equality check: compare points P&Q. */
|
|
4575
|
+
equals(other) {
|
|
4576
|
+
const { ex: X1, ey: Y1, ez: Z1 } = this;
|
|
4577
|
+
const { ex: X2, ey: Y2, ez: Z2 } = apoint(other);
|
|
4578
|
+
const X1Z2 = M(X1 * Z2);
|
|
4579
|
+
const X2Z1 = M(X2 * Z1);
|
|
4580
|
+
const Y1Z2 = M(Y1 * Z2);
|
|
4581
|
+
const Y2Z1 = M(Y2 * Z1);
|
|
4582
|
+
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
|
|
4583
|
+
}
|
|
4584
|
+
is0() {
|
|
4585
|
+
return this.equals(I);
|
|
4586
|
+
}
|
|
4587
|
+
/** Flip point over y coordinate. */
|
|
4588
|
+
negate() {
|
|
4589
|
+
return new _Point(M(-this.ex), this.ey, this.ez, M(-this.et));
|
|
4590
|
+
}
|
|
4591
|
+
/** Point doubling. Complete formula. Cost: `4M + 4S + 1*a + 6add + 1*2`. */
|
|
4592
|
+
double() {
|
|
4593
|
+
const { ex: X1, ey: Y1, ez: Z1 } = this;
|
|
4594
|
+
const a = _a;
|
|
4595
|
+
const A = M(X1 * X1);
|
|
4596
|
+
const B = M(Y1 * Y1);
|
|
4597
|
+
const C2 = M(2n * M(Z1 * Z1));
|
|
4598
|
+
const D = M(a * A);
|
|
4599
|
+
const x1y1 = X1 + Y1;
|
|
4600
|
+
const E = M(M(x1y1 * x1y1) - A - B);
|
|
4601
|
+
const G2 = D + B;
|
|
4602
|
+
const F = G2 - C2;
|
|
4603
|
+
const H = D - B;
|
|
4604
|
+
const X3 = M(E * F);
|
|
4605
|
+
const Y3 = M(G2 * H);
|
|
4606
|
+
const T3 = M(E * H);
|
|
4607
|
+
const Z3 = M(F * G2);
|
|
4608
|
+
return new _Point(X3, Y3, Z3, T3);
|
|
4609
|
+
}
|
|
4610
|
+
/** Point addition. Complete formula. Cost: `8M + 1*k + 8add + 1*2`. */
|
|
4611
|
+
add(other) {
|
|
4612
|
+
const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this;
|
|
4613
|
+
const { ex: X2, ey: Y2, ez: Z2, et: T2 } = apoint(other);
|
|
4614
|
+
const a = _a;
|
|
4615
|
+
const d = _d;
|
|
4616
|
+
const A = M(X1 * X2);
|
|
4617
|
+
const B = M(Y1 * Y2);
|
|
4618
|
+
const C2 = M(T1 * d * T2);
|
|
4619
|
+
const D = M(Z1 * Z2);
|
|
4620
|
+
const E = M((X1 + Y1) * (X2 + Y2) - A - B);
|
|
4621
|
+
const F = M(D - C2);
|
|
4622
|
+
const G2 = M(D + C2);
|
|
4623
|
+
const H = M(B - a * A);
|
|
4624
|
+
const X3 = M(E * F);
|
|
4625
|
+
const Y3 = M(G2 * H);
|
|
4626
|
+
const T3 = M(E * H);
|
|
4627
|
+
const Z3 = M(F * G2);
|
|
4628
|
+
return new _Point(X3, Y3, Z3, T3);
|
|
4629
|
+
}
|
|
4630
|
+
/**
|
|
4631
|
+
* Point-by-scalar multiplication. Scalar must be in range 1 <= n < CURVE.n.
|
|
4632
|
+
* Uses {@link wNAF} for base point.
|
|
4633
|
+
* Uses fake point to mitigate side-channel leakage.
|
|
4634
|
+
* @param n scalar by which point is multiplied
|
|
4635
|
+
* @param safe safe mode guards against timing attacks; unsafe mode is faster
|
|
4636
|
+
*/
|
|
4637
|
+
multiply(n, safe = true) {
|
|
4638
|
+
if (!safe && (n === 0n || this.is0()))
|
|
4639
|
+
return I;
|
|
4640
|
+
arange(n, 1n, N);
|
|
4641
|
+
if (n === 1n)
|
|
4642
|
+
return this;
|
|
4643
|
+
if (this.equals(G))
|
|
4644
|
+
return wNAF(n).p;
|
|
4645
|
+
let p = I;
|
|
4646
|
+
let f = G;
|
|
4647
|
+
for (let d = this; n > 0n; d = d.double(), n >>= 1n) {
|
|
4648
|
+
if (n & 1n)
|
|
4649
|
+
p = p.add(d);
|
|
4650
|
+
else if (safe)
|
|
4651
|
+
f = f.add(d);
|
|
4652
|
+
}
|
|
4653
|
+
return p;
|
|
4654
|
+
}
|
|
4655
|
+
/** Convert point to 2d xy affine point. (X, Y, Z) ∋ (x=X/Z, y=Y/Z) */
|
|
4656
|
+
toAffine() {
|
|
4657
|
+
const { ex: x, ey: y, ez: z } = this;
|
|
4658
|
+
if (this.equals(I))
|
|
4659
|
+
return { x: 0n, y: 1n };
|
|
4660
|
+
const iz = invert(z, P);
|
|
4661
|
+
if (M(z * iz) !== 1n)
|
|
4662
|
+
err("invalid inverse");
|
|
4663
|
+
return { x: M(x * iz), y: M(y * iz) };
|
|
4664
|
+
}
|
|
4665
|
+
toBytes() {
|
|
4666
|
+
const { x, y } = this.assertValidity().toAffine();
|
|
4667
|
+
const b = numTo32bLE(y);
|
|
4668
|
+
b[31] |= x & 1n ? 128 : 0;
|
|
4669
|
+
return b;
|
|
4670
|
+
}
|
|
4671
|
+
toHex() {
|
|
4672
|
+
return bytesToHex(this.toBytes());
|
|
4673
|
+
}
|
|
4674
|
+
// encode to hex string
|
|
4675
|
+
clearCofactor() {
|
|
4676
|
+
return this.multiply(big(h), false);
|
|
4677
|
+
}
|
|
4678
|
+
isSmallOrder() {
|
|
4679
|
+
return this.clearCofactor().is0();
|
|
4680
|
+
}
|
|
4681
|
+
isTorsionFree() {
|
|
4682
|
+
let p = this.multiply(N / 2n, false).double();
|
|
4683
|
+
if (N % 2n)
|
|
4684
|
+
p = p.add(this);
|
|
4685
|
+
return p.is0();
|
|
4686
|
+
}
|
|
4687
|
+
static fromHex(hex, zip215) {
|
|
4688
|
+
return _Point.fromBytes(toU8(hex), zip215);
|
|
4689
|
+
}
|
|
4690
|
+
get x() {
|
|
4691
|
+
return this.toAffine().x;
|
|
4692
|
+
}
|
|
4693
|
+
get y() {
|
|
4694
|
+
return this.toAffine().y;
|
|
4695
|
+
}
|
|
4696
|
+
toRawBytes() {
|
|
4697
|
+
return this.toBytes();
|
|
4698
|
+
}
|
|
4699
|
+
};
|
|
4700
|
+
var G = new Point(Gx, Gy, 1n, M(Gx * Gy));
|
|
4701
|
+
var I = new Point(0n, 1n, 1n, 0n);
|
|
4702
|
+
Point.BASE = G;
|
|
4703
|
+
Point.ZERO = I;
|
|
4704
|
+
var numTo32bLE = (num) => hexToBytes(padh(arange(num, 0n, B256), L2)).reverse();
|
|
4705
|
+
var bytesToNumLE = (b) => big("0x" + bytesToHex(u8fr(abytes(b)).reverse()));
|
|
4706
|
+
var pow2 = (x, power) => {
|
|
4707
|
+
let r = x;
|
|
4708
|
+
while (power-- > 0n) {
|
|
4709
|
+
r *= r;
|
|
4710
|
+
r %= P;
|
|
4711
|
+
}
|
|
4712
|
+
return r;
|
|
4713
|
+
};
|
|
4714
|
+
var pow_2_252_3 = (x) => {
|
|
4715
|
+
const x2 = x * x % P;
|
|
4716
|
+
const b2 = x2 * x % P;
|
|
4717
|
+
const b4 = pow2(b2, 2n) * b2 % P;
|
|
4718
|
+
const b5 = pow2(b4, 1n) * x % P;
|
|
4719
|
+
const b10 = pow2(b5, 5n) * b5 % P;
|
|
4720
|
+
const b20 = pow2(b10, 10n) * b10 % P;
|
|
4721
|
+
const b40 = pow2(b20, 20n) * b20 % P;
|
|
4722
|
+
const b80 = pow2(b40, 40n) * b40 % P;
|
|
4723
|
+
const b160 = pow2(b80, 80n) * b80 % P;
|
|
4724
|
+
const b240 = pow2(b160, 80n) * b80 % P;
|
|
4725
|
+
const b250 = pow2(b240, 10n) * b10 % P;
|
|
4726
|
+
const pow_p_5_8 = pow2(b250, 2n) * x % P;
|
|
4727
|
+
return { pow_p_5_8, b2 };
|
|
4728
|
+
};
|
|
4729
|
+
var RM1 = 0x2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0n;
|
|
4730
|
+
var uvRatio = (u, v) => {
|
|
4731
|
+
const v3 = M(v * v * v);
|
|
4732
|
+
const v7 = M(v3 * v3 * v);
|
|
4733
|
+
const pow = pow_2_252_3(u * v7).pow_p_5_8;
|
|
4734
|
+
let x = M(u * v3 * pow);
|
|
4735
|
+
const vx2 = M(v * x * x);
|
|
4736
|
+
const root1 = x;
|
|
4737
|
+
const root2 = M(x * RM1);
|
|
4738
|
+
const useRoot1 = vx2 === u;
|
|
4739
|
+
const useRoot2 = vx2 === M(-u);
|
|
4740
|
+
const noRoot = vx2 === M(-u * RM1);
|
|
4741
|
+
if (useRoot1)
|
|
4742
|
+
x = root1;
|
|
4743
|
+
if (useRoot2 || noRoot)
|
|
4744
|
+
x = root2;
|
|
4745
|
+
if ((M(x) & 1n) === 1n)
|
|
4746
|
+
x = M(-x);
|
|
4747
|
+
return { isValid: useRoot1 || useRoot2, value: x };
|
|
4748
|
+
};
|
|
4749
|
+
var modL_LE = (hash) => modN(bytesToNumLE(hash));
|
|
4750
|
+
var sha512a = (...m) => etc.sha512Async(...m);
|
|
4751
|
+
var sha512s = (...m) => callHash("sha512Sync")(...m);
|
|
4752
|
+
var hash2extK = (hashed) => {
|
|
4753
|
+
const head = hashed.slice(0, L);
|
|
4754
|
+
head[0] &= 248;
|
|
4755
|
+
head[31] &= 127;
|
|
4756
|
+
head[31] |= 64;
|
|
4757
|
+
const prefix = hashed.slice(L, L2);
|
|
4758
|
+
const scalar = modL_LE(head);
|
|
4759
|
+
const point = G.multiply(scalar);
|
|
4760
|
+
const pointBytes = point.toBytes();
|
|
4761
|
+
return { head, prefix, scalar, point, pointBytes };
|
|
4762
|
+
};
|
|
4763
|
+
var getExtendedPublicKeyAsync = (priv) => sha512a(toU8(priv, L)).then(hash2extK);
|
|
4764
|
+
var getExtendedPublicKey = (priv) => hash2extK(sha512s(toU8(priv, L)));
|
|
4765
|
+
var getPublicKey = (priv) => getExtendedPublicKey(priv).pointBytes;
|
|
4766
|
+
var hashFinishS = (res) => res.finish(sha512s(res.hashable));
|
|
4767
|
+
var _sign = (e, rBytes, msg) => {
|
|
4768
|
+
const { pointBytes: P2, scalar: s } = e;
|
|
4769
|
+
const r = modL_LE(rBytes);
|
|
4770
|
+
const R = G.multiply(r).toBytes();
|
|
4771
|
+
const hashable = concatBytes(R, P2, msg);
|
|
4772
|
+
const finish = (hashed) => {
|
|
4773
|
+
const S = modN(r + modL_LE(hashed) * s);
|
|
4774
|
+
return abytes(concatBytes(R, numTo32bLE(S)), L2);
|
|
4775
|
+
};
|
|
4776
|
+
return { hashable, finish };
|
|
4777
|
+
};
|
|
4778
|
+
var sign = (msg, privKey) => {
|
|
4779
|
+
const m = toU8(msg);
|
|
4780
|
+
const e = getExtendedPublicKey(privKey);
|
|
4781
|
+
const rBytes = sha512s(e.prefix, m);
|
|
4782
|
+
return hashFinishS(_sign(e, rBytes, m));
|
|
4783
|
+
};
|
|
4784
|
+
var veriOpts = { zip215: true };
|
|
4785
|
+
var _verify = (sig, msg, pub, opts = veriOpts) => {
|
|
4786
|
+
sig = toU8(sig, L2);
|
|
4787
|
+
msg = toU8(msg);
|
|
4788
|
+
pub = toU8(pub, L);
|
|
4789
|
+
const { zip215 } = opts;
|
|
4790
|
+
let A;
|
|
4791
|
+
let R;
|
|
4792
|
+
let s;
|
|
4793
|
+
let SB;
|
|
4794
|
+
let hashable = Uint8Array.of();
|
|
4795
|
+
try {
|
|
4796
|
+
A = Point.fromHex(pub, zip215);
|
|
4797
|
+
R = Point.fromHex(sig.slice(0, L), zip215);
|
|
4798
|
+
s = bytesToNumLE(sig.slice(L, L2));
|
|
4799
|
+
SB = G.multiply(s, false);
|
|
4800
|
+
hashable = concatBytes(R.toBytes(), A.toBytes(), msg);
|
|
4801
|
+
} catch (error) {
|
|
4802
|
+
}
|
|
4803
|
+
const finish = (hashed) => {
|
|
4804
|
+
if (SB == null)
|
|
4805
|
+
return false;
|
|
4806
|
+
if (!zip215 && A.isSmallOrder())
|
|
4807
|
+
return false;
|
|
4808
|
+
const k = modL_LE(hashed);
|
|
4809
|
+
const RkA = R.add(A.multiply(k, false));
|
|
4810
|
+
return RkA.add(SB.negate()).clearCofactor().is0();
|
|
4811
|
+
};
|
|
4812
|
+
return { hashable, finish };
|
|
4813
|
+
};
|
|
4814
|
+
var verify = (s, m, p, opts = veriOpts) => hashFinishS(_verify(s, m, p, opts));
|
|
4815
|
+
var etc = {
|
|
4816
|
+
sha512Async: async (...messages) => {
|
|
4817
|
+
const s = subtle();
|
|
4818
|
+
const m = concatBytes(...messages);
|
|
4819
|
+
return u8n(await s.digest("SHA-512", m.buffer));
|
|
4820
|
+
},
|
|
4821
|
+
sha512Sync: void 0,
|
|
4822
|
+
bytesToHex,
|
|
4823
|
+
hexToBytes,
|
|
4824
|
+
concatBytes,
|
|
4825
|
+
mod: M,
|
|
4826
|
+
invert,
|
|
4827
|
+
randomBytes: randomBytes2
|
|
4828
|
+
};
|
|
4829
|
+
var utils = {
|
|
4830
|
+
getExtendedPublicKeyAsync,
|
|
4831
|
+
getExtendedPublicKey,
|
|
4832
|
+
randomPrivateKey: () => randomBytes2(L),
|
|
4833
|
+
precompute: (w = 8, p = G) => {
|
|
4834
|
+
p.multiply(3n);
|
|
4835
|
+
w;
|
|
4836
|
+
return p;
|
|
4837
|
+
}
|
|
4838
|
+
// no-op
|
|
4839
|
+
};
|
|
4840
|
+
var W = 8;
|
|
4841
|
+
var scalarBits = 256;
|
|
4842
|
+
var pwindows = Math.ceil(scalarBits / W) + 1;
|
|
4843
|
+
var pwindowSize = 2 ** (W - 1);
|
|
4844
|
+
var precompute = () => {
|
|
4845
|
+
const points = [];
|
|
4846
|
+
let p = G;
|
|
4847
|
+
let b = p;
|
|
4848
|
+
for (let w = 0; w < pwindows; w++) {
|
|
4849
|
+
b = p;
|
|
4850
|
+
points.push(b);
|
|
4851
|
+
for (let i = 1; i < pwindowSize; i++) {
|
|
4852
|
+
b = b.add(p);
|
|
4853
|
+
points.push(b);
|
|
4854
|
+
}
|
|
4855
|
+
p = b.double();
|
|
4856
|
+
}
|
|
4857
|
+
return points;
|
|
4858
|
+
};
|
|
4859
|
+
var Gpows = void 0;
|
|
4860
|
+
var ctneg = (cnd, p) => {
|
|
4861
|
+
const n = p.negate();
|
|
4862
|
+
return cnd ? n : p;
|
|
4863
|
+
};
|
|
4864
|
+
var wNAF = (n) => {
|
|
4865
|
+
const comp = Gpows || (Gpows = precompute());
|
|
4866
|
+
let p = I;
|
|
4867
|
+
let f = G;
|
|
4868
|
+
const pow_2_w = 2 ** W;
|
|
4869
|
+
const maxNum = pow_2_w;
|
|
4870
|
+
const mask = big(pow_2_w - 1);
|
|
4871
|
+
const shiftBy = big(W);
|
|
4872
|
+
for (let w = 0; w < pwindows; w++) {
|
|
4873
|
+
let wbits = Number(n & mask);
|
|
4874
|
+
n >>= shiftBy;
|
|
4875
|
+
if (wbits > pwindowSize) {
|
|
4876
|
+
wbits -= maxNum;
|
|
4877
|
+
n += 1n;
|
|
4878
|
+
}
|
|
4879
|
+
const off = w * pwindowSize;
|
|
4880
|
+
const offF = off;
|
|
4881
|
+
const offP = off + Math.abs(wbits) - 1;
|
|
4882
|
+
const isEven = w % 2 !== 0;
|
|
4883
|
+
const isNeg = wbits < 0;
|
|
4884
|
+
if (wbits === 0) {
|
|
4885
|
+
f = f.add(ctneg(isEven, comp[offF]));
|
|
4886
|
+
} else {
|
|
4887
|
+
p = p.add(ctneg(isNeg, comp[offP]));
|
|
4888
|
+
}
|
|
4889
|
+
}
|
|
4890
|
+
return { p, f };
|
|
4891
|
+
};
|
|
4892
|
+
|
|
4893
|
+
// ../../packages/signature/src/base64url.ts
|
|
4894
|
+
function base64urlEncode(bytes) {
|
|
4895
|
+
const base64 = Buffer.from(bytes).toString("base64");
|
|
4896
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
4897
|
+
}
|
|
4898
|
+
function base64urlDecode(str) {
|
|
4899
|
+
const padded = str + "===".slice((str.length + 3) % 4);
|
|
4900
|
+
const base64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
4901
|
+
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
4902
|
+
}
|
|
4903
|
+
|
|
4904
|
+
// ../../packages/signature/src/ed25519.ts
|
|
4905
|
+
etc.sha512Sync = (...messages) => {
|
|
4906
|
+
const hash = createHash2("sha512");
|
|
4907
|
+
hash.update(etc.concatBytes(...messages));
|
|
4908
|
+
return new Uint8Array(hash.digest());
|
|
4909
|
+
};
|
|
4910
|
+
function generateKeypair(keyId) {
|
|
4911
|
+
const privateKey = utils.randomPrivateKey();
|
|
4912
|
+
const publicKey = getPublicKey(privateKey);
|
|
4913
|
+
return {
|
|
4914
|
+
privateKey,
|
|
4915
|
+
publicKey,
|
|
4916
|
+
keyId
|
|
4917
|
+
};
|
|
4918
|
+
}
|
|
4919
|
+
function signReceipt(unsignedReceipt, keypair) {
|
|
4920
|
+
const canonical = stableStringify(unsignedReceipt);
|
|
4921
|
+
const hash = sha256(canonical);
|
|
4922
|
+
const hashHex = hash.slice(7);
|
|
4923
|
+
const hashBytes = Buffer.from(hashHex, "hex");
|
|
4924
|
+
const signatureBytes = sign(hashBytes, keypair.privateKey);
|
|
4925
|
+
const signatureBase64url = base64urlEncode(signatureBytes);
|
|
4926
|
+
return {
|
|
4927
|
+
algorithm: "ed25519",
|
|
4928
|
+
key_id: keypair.keyId,
|
|
4929
|
+
value: signatureBase64url,
|
|
4930
|
+
signed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
4931
|
+
};
|
|
4932
|
+
}
|
|
4933
|
+
function verifyReceipt2(signedReceipt, publicKey) {
|
|
4934
|
+
try {
|
|
4935
|
+
const sig = signedReceipt.signature;
|
|
4936
|
+
if (!sig) {
|
|
4937
|
+
return { valid: false, error: "No signature field in receipt" };
|
|
4938
|
+
}
|
|
4939
|
+
if (sig.algorithm !== "ed25519") {
|
|
4940
|
+
return { valid: false, error: `Unsupported algorithm: ${sig.algorithm}` };
|
|
4941
|
+
}
|
|
4942
|
+
const publicKeyBytes = typeof publicKey === "string" ? base64urlDecode(publicKey) : publicKey;
|
|
4943
|
+
if (publicKeyBytes.length !== 32) {
|
|
4944
|
+
return { valid: false, error: "Invalid public key length (expected 32 bytes)" };
|
|
4945
|
+
}
|
|
4946
|
+
const signatureBytes = base64urlDecode(sig.value);
|
|
4947
|
+
if (signatureBytes.length !== 64) {
|
|
4948
|
+
return { valid: false, error: "Invalid signature length (expected 64 bytes)" };
|
|
4949
|
+
}
|
|
4950
|
+
const { signature, ...unsignedReceipt } = signedReceipt;
|
|
4951
|
+
const canonical = stableStringify(unsignedReceipt);
|
|
4952
|
+
const hash = sha256(canonical);
|
|
4953
|
+
const hashHex = hash.slice(7);
|
|
4954
|
+
const hashBytes = Buffer.from(hashHex, "hex");
|
|
4955
|
+
const valid = verify(signatureBytes, hashBytes, publicKeyBytes);
|
|
4956
|
+
return { valid, key_id: sig.key_id };
|
|
4957
|
+
} catch (error) {
|
|
4414
4958
|
return {
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
exitCode: EXIT.USAGE
|
|
4959
|
+
valid: false,
|
|
4960
|
+
error: error instanceof Error ? error.message : "Unknown verification error"
|
|
4418
4961
|
};
|
|
4419
4962
|
}
|
|
4963
|
+
}
|
|
4964
|
+
function exportKeypair(keypair) {
|
|
4965
|
+
return {
|
|
4966
|
+
key_id: keypair.keyId,
|
|
4967
|
+
algorithm: "ed25519",
|
|
4968
|
+
private_key: base64urlEncode(keypair.privateKey),
|
|
4969
|
+
public_key: base64urlEncode(keypair.publicKey)
|
|
4970
|
+
};
|
|
4971
|
+
}
|
|
4972
|
+
function importKeypair(json) {
|
|
4973
|
+
if (json.algorithm !== "ed25519") {
|
|
4974
|
+
throw new Error(`Unsupported algorithm: ${json.algorithm}`);
|
|
4975
|
+
}
|
|
4976
|
+
return {
|
|
4977
|
+
keyId: json.key_id,
|
|
4978
|
+
privateKey: base64urlDecode(json.private_key),
|
|
4979
|
+
publicKey: base64urlDecode(json.public_key)
|
|
4980
|
+
};
|
|
4981
|
+
}
|
|
4982
|
+
|
|
4983
|
+
// src/commands/receipt.ts
|
|
4984
|
+
function receiptCommand(args, deps) {
|
|
4985
|
+
const sub = args.positionals[0];
|
|
4986
|
+
switch (sub) {
|
|
4987
|
+
case "verify":
|
|
4988
|
+
return receiptVerify(args, deps);
|
|
4989
|
+
case "sign":
|
|
4990
|
+
return receiptSign(args, deps);
|
|
4991
|
+
case "keygen":
|
|
4992
|
+
return receiptKeygen(args, deps);
|
|
4993
|
+
default:
|
|
4994
|
+
return {
|
|
4995
|
+
stdout: "",
|
|
4996
|
+
stderr: [
|
|
4997
|
+
"Usage: calllint receipt <subcommand>",
|
|
4998
|
+
"",
|
|
4999
|
+
"Subcommands:",
|
|
5000
|
+
" verify <receipt.json> Validate receipt structure and signature",
|
|
5001
|
+
" sign <receipt.json> --key <file> Sign receipt locally (development only)",
|
|
5002
|
+
" keygen --out <file> Generate test keypair (development only)"
|
|
5003
|
+
].join("\n"),
|
|
5004
|
+
exitCode: EXIT.USAGE
|
|
5005
|
+
};
|
|
5006
|
+
}
|
|
5007
|
+
}
|
|
5008
|
+
function receiptVerify(args, deps) {
|
|
4420
5009
|
const file = args.positionals[1];
|
|
4421
5010
|
if (!file) {
|
|
4422
5011
|
return {
|
|
4423
5012
|
stdout: "",
|
|
4424
|
-
stderr: "Usage: calllint receipt verify <receipt.json>",
|
|
5013
|
+
stderr: "Usage: calllint receipt verify <receipt.json> [--public-key <keyfile>]",
|
|
4425
5014
|
exitCode: EXIT.USAGE
|
|
4426
5015
|
};
|
|
4427
5016
|
}
|
|
@@ -4449,31 +5038,224 @@ function receiptCommand(args, deps) {
|
|
|
4449
5038
|
return { stdout: "", stderr: `CallLint receipt: invalid
|
|
4450
5039
|
- ${msg}`, exitCode: 1 };
|
|
4451
5040
|
}
|
|
4452
|
-
const
|
|
5041
|
+
const structResult = verifyReceipt(parsed);
|
|
5042
|
+
if (!structResult.valid) {
|
|
5043
|
+
if (flagBool(args.flags, "json")) {
|
|
5044
|
+
return {
|
|
5045
|
+
stdout: JSON.stringify(structResult, null, 2),
|
|
5046
|
+
exitCode: 1
|
|
5047
|
+
};
|
|
5048
|
+
}
|
|
5049
|
+
const lines = ["CallLint receipt: invalid", ...structResult.errors.map((e) => ` - ${e}`)];
|
|
5050
|
+
return { stdout: "", stderr: lines.join("\n"), exitCode: 1 };
|
|
5051
|
+
}
|
|
5052
|
+
const receipt = parsed;
|
|
5053
|
+
if (receipt.signature) {
|
|
5054
|
+
return verifySignature(receipt, args, deps);
|
|
5055
|
+
}
|
|
5056
|
+
if (flagBool(args.flags, "json")) {
|
|
5057
|
+
return {
|
|
5058
|
+
stdout: JSON.stringify(
|
|
5059
|
+
{
|
|
5060
|
+
valid: true,
|
|
5061
|
+
receipt_id: receipt.receipt_id,
|
|
5062
|
+
schema_version: receipt.schema_version,
|
|
5063
|
+
verdict: receipt.verdict,
|
|
5064
|
+
signed: false
|
|
5065
|
+
},
|
|
5066
|
+
null,
|
|
5067
|
+
2
|
|
5068
|
+
),
|
|
5069
|
+
exitCode: EXIT.OK
|
|
5070
|
+
};
|
|
5071
|
+
}
|
|
5072
|
+
const stdout = [
|
|
5073
|
+
"\u2713 CallLint receipt: valid",
|
|
5074
|
+
` Receipt ID: ${receipt.receipt_id}`,
|
|
5075
|
+
` Schema: ${receipt.schema_version}`,
|
|
5076
|
+
` Verdict: ${receipt.verdict}`,
|
|
5077
|
+
` Signature: unsigned local receipt`
|
|
5078
|
+
].join("\n");
|
|
5079
|
+
return { stdout, exitCode: EXIT.OK };
|
|
5080
|
+
}
|
|
5081
|
+
function verifySignature(receipt, args, deps) {
|
|
5082
|
+
const sig = receipt.signature;
|
|
5083
|
+
const publicKeyUrl = sig.public_key_url || "https://calllint.com/.well-known/receipt-keys.json";
|
|
5084
|
+
const publicKeyFlag = args.flags["public-key"];
|
|
5085
|
+
if (!publicKeyFlag || typeof publicKeyFlag !== "string") {
|
|
5086
|
+
return {
|
|
5087
|
+
stdout: "",
|
|
5088
|
+
stderr: [
|
|
5089
|
+
"Receipt has a signature but no public key was provided.",
|
|
5090
|
+
` Signature key_id: ${sig.key_id}`,
|
|
5091
|
+
` Public key URL: ${publicKeyUrl}`,
|
|
5092
|
+
"",
|
|
5093
|
+
"Verify offline by passing the public key:",
|
|
5094
|
+
" calllint receipt verify <receipt.json> --public-key <keyfile>"
|
|
5095
|
+
].join("\n"),
|
|
5096
|
+
exitCode: EXIT.ERROR
|
|
5097
|
+
};
|
|
5098
|
+
}
|
|
5099
|
+
let publicKey;
|
|
5100
|
+
try {
|
|
5101
|
+
const keyJson = JSON.parse(readFileSync8(resolve4(deps.cwd, publicKeyFlag), "utf8"));
|
|
5102
|
+
publicKey = keyJson.public_key;
|
|
5103
|
+
if (typeof publicKey !== "string") {
|
|
5104
|
+
throw new Error("key file has no string 'public_key' field");
|
|
5105
|
+
}
|
|
5106
|
+
} catch (e) {
|
|
5107
|
+
return {
|
|
5108
|
+
stdout: "",
|
|
5109
|
+
stderr: `Could not read public key from ${publicKeyFlag}: ${e instanceof Error ? e.message : String(e)}`,
|
|
5110
|
+
exitCode: EXIT.ERROR
|
|
5111
|
+
};
|
|
5112
|
+
}
|
|
5113
|
+
const cryptoResult = verifyReceipt2(receipt, publicKey);
|
|
4453
5114
|
if (flagBool(args.flags, "json")) {
|
|
4454
5115
|
return {
|
|
4455
|
-
stdout: JSON.stringify(
|
|
4456
|
-
|
|
5116
|
+
stdout: JSON.stringify(
|
|
5117
|
+
{
|
|
5118
|
+
valid: cryptoResult.valid,
|
|
5119
|
+
receipt_id: receipt.receipt_id,
|
|
5120
|
+
schema_version: receipt.schema_version,
|
|
5121
|
+
verdict: receipt.verdict,
|
|
5122
|
+
signed: true,
|
|
5123
|
+
signature: {
|
|
5124
|
+
valid: cryptoResult.valid,
|
|
5125
|
+
key_id: sig.key_id,
|
|
5126
|
+
algorithm: sig.algorithm,
|
|
5127
|
+
signed_at: sig.signed_at,
|
|
5128
|
+
error: cryptoResult.error
|
|
5129
|
+
}
|
|
5130
|
+
},
|
|
5131
|
+
null,
|
|
5132
|
+
2
|
|
5133
|
+
),
|
|
5134
|
+
exitCode: cryptoResult.valid ? EXIT.OK : 1
|
|
4457
5135
|
};
|
|
4458
5136
|
}
|
|
4459
|
-
if (!
|
|
4460
|
-
const lines = [
|
|
5137
|
+
if (!cryptoResult.valid) {
|
|
5138
|
+
const lines = [
|
|
5139
|
+
"\u2717 CallLint receipt: signature INVALID",
|
|
5140
|
+
` Receipt ID: ${receipt.receipt_id}`,
|
|
5141
|
+
` Signature key: ${sig.key_id}`,
|
|
5142
|
+
` Error: ${cryptoResult.error || "signature verification failed"}`
|
|
5143
|
+
];
|
|
4461
5144
|
return { stdout: "", stderr: lines.join("\n"), exitCode: 1 };
|
|
4462
5145
|
}
|
|
4463
|
-
const r = parsed;
|
|
4464
|
-
const signature = r.signature ? "present (shape-only, not cryptographically verified)" : "unsigned local receipt";
|
|
4465
5146
|
const stdout = [
|
|
4466
|
-
"CallLint receipt: valid",
|
|
4467
|
-
`
|
|
4468
|
-
`
|
|
4469
|
-
`
|
|
4470
|
-
`
|
|
5147
|
+
"\u2713 CallLint receipt: valid",
|
|
5148
|
+
` Receipt ID: ${receipt.receipt_id}`,
|
|
5149
|
+
` Schema: ${receipt.schema_version}`,
|
|
5150
|
+
` Verdict: ${receipt.verdict}`,
|
|
5151
|
+
` Signature: valid (key: ${sig.key_id})`,
|
|
5152
|
+
` Signed at: ${sig.signed_at}`
|
|
4471
5153
|
].join("\n");
|
|
4472
5154
|
return { stdout, exitCode: EXIT.OK };
|
|
4473
5155
|
}
|
|
5156
|
+
function receiptSign(args, deps) {
|
|
5157
|
+
const receiptFile = args.positionals[1];
|
|
5158
|
+
const keyFile = args.flags["key"];
|
|
5159
|
+
const outFile = args.flags["out"] || receiptFile;
|
|
5160
|
+
if (!receiptFile || !keyFile || typeof keyFile !== "string") {
|
|
5161
|
+
return {
|
|
5162
|
+
stdout: "",
|
|
5163
|
+
stderr: [
|
|
5164
|
+
"Usage: calllint receipt sign <receipt.json> --key <keyfile> [--out <output>]",
|
|
5165
|
+
"",
|
|
5166
|
+
"Sign a receipt locally (development/testing only).",
|
|
5167
|
+
"Production signing requires cloud service: calllint scan --sign"
|
|
5168
|
+
].join("\n"),
|
|
5169
|
+
exitCode: EXIT.USAGE
|
|
5170
|
+
};
|
|
5171
|
+
}
|
|
5172
|
+
let unsignedReceipt;
|
|
5173
|
+
try {
|
|
5174
|
+
unsignedReceipt = JSON.parse(readFileSync8(resolve4(deps.cwd, receiptFile), "utf8"));
|
|
5175
|
+
} catch (e) {
|
|
5176
|
+
return {
|
|
5177
|
+
stdout: "",
|
|
5178
|
+
stderr: `Could not read receipt ${receiptFile}: ${e instanceof Error ? e.message : String(e)}`,
|
|
5179
|
+
exitCode: EXIT.ERROR
|
|
5180
|
+
};
|
|
5181
|
+
}
|
|
5182
|
+
if (unsignedReceipt.signature) {
|
|
5183
|
+
return {
|
|
5184
|
+
stdout: "",
|
|
5185
|
+
stderr: "Receipt already has a signature field. Cannot sign again.",
|
|
5186
|
+
exitCode: EXIT.ERROR
|
|
5187
|
+
};
|
|
5188
|
+
}
|
|
5189
|
+
let keypair;
|
|
5190
|
+
try {
|
|
5191
|
+
const keyJson = JSON.parse(readFileSync8(resolve4(deps.cwd, keyFile), "utf8"));
|
|
5192
|
+
keypair = importKeypair(keyJson);
|
|
5193
|
+
} catch (e) {
|
|
5194
|
+
return {
|
|
5195
|
+
stdout: "",
|
|
5196
|
+
stderr: `Could not read key file ${keyFile}: ${e instanceof Error ? e.message : String(e)}`,
|
|
5197
|
+
exitCode: EXIT.ERROR
|
|
5198
|
+
};
|
|
5199
|
+
}
|
|
5200
|
+
try {
|
|
5201
|
+
const signature = signReceipt(unsignedReceipt, keypair);
|
|
5202
|
+
const signedReceipt = { ...unsignedReceipt, signature };
|
|
5203
|
+
writeFileSync6(resolve4(deps.cwd, outFile), JSON.stringify(signedReceipt, null, 2) + "\n", "utf8");
|
|
5204
|
+
const stdout = [
|
|
5205
|
+
"\u2713 Receipt signed successfully",
|
|
5206
|
+
` Key ID: ${signature.key_id}`,
|
|
5207
|
+
` Algorithm: ${signature.algorithm}`,
|
|
5208
|
+
` Signed at: ${signature.signed_at}`,
|
|
5209
|
+
` Output: ${outFile}`
|
|
5210
|
+
].join("\n");
|
|
5211
|
+
return { stdout, exitCode: EXIT.OK };
|
|
5212
|
+
} catch (e) {
|
|
5213
|
+
return {
|
|
5214
|
+
stdout: "",
|
|
5215
|
+
stderr: `Signing failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
5216
|
+
exitCode: EXIT.ERROR
|
|
5217
|
+
};
|
|
5218
|
+
}
|
|
5219
|
+
}
|
|
5220
|
+
function receiptKeygen(args, deps) {
|
|
5221
|
+
const outFile = args.flags["out"];
|
|
5222
|
+
if (!outFile || typeof outFile !== "string") {
|
|
5223
|
+
return {
|
|
5224
|
+
stdout: "",
|
|
5225
|
+
stderr: [
|
|
5226
|
+
"Usage: calllint receipt keygen --out <file>",
|
|
5227
|
+
"",
|
|
5228
|
+
"Generate a test ed25519 keypair (development/testing only).",
|
|
5229
|
+
"Production keys are managed by cloud service."
|
|
5230
|
+
].join("\n"),
|
|
5231
|
+
exitCode: EXIT.USAGE
|
|
5232
|
+
};
|
|
5233
|
+
}
|
|
5234
|
+
const keyId = args.flags["key-id"] || "calllint-dev-2026-h2";
|
|
5235
|
+
try {
|
|
5236
|
+
const keypair = generateKeypair(keyId);
|
|
5237
|
+
const exported = exportKeypair(keypair);
|
|
5238
|
+
writeFileSync6(resolve4(deps.cwd, outFile), JSON.stringify(exported, null, 2) + "\n", "utf8");
|
|
5239
|
+
const stdout = [
|
|
5240
|
+
"\u2713 Keypair generated successfully",
|
|
5241
|
+
` Key ID: ${keyId}`,
|
|
5242
|
+
` Algorithm: ed25519`,
|
|
5243
|
+
` Output: ${outFile}`,
|
|
5244
|
+
"",
|
|
5245
|
+
"\u26A0\uFE0F Development/testing only. Do NOT use for production."
|
|
5246
|
+
].join("\n");
|
|
5247
|
+
return { stdout, exitCode: EXIT.OK };
|
|
5248
|
+
} catch (e) {
|
|
5249
|
+
return {
|
|
5250
|
+
stdout: "",
|
|
5251
|
+
stderr: `Keygen failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
5252
|
+
exitCode: EXIT.ERROR
|
|
5253
|
+
};
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
4474
5256
|
|
|
4475
5257
|
// src/commands/action.ts
|
|
4476
|
-
import { readFileSync as readFileSync9, writeFileSync as
|
|
5258
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
|
|
4477
5259
|
import { resolve as resolve5 } from "node:path";
|
|
4478
5260
|
|
|
4479
5261
|
// ../../packages/action-analyzer/src/types.ts
|
|
@@ -5016,15 +5798,15 @@ Expected: calllint.action.v0`,
|
|
|
5016
5798
|
exitCode: verdict === "SAFE" ? 0 : 1
|
|
5017
5799
|
};
|
|
5018
5800
|
} catch (error) {
|
|
5019
|
-
const
|
|
5801
|
+
const err2 = error;
|
|
5020
5802
|
let stderr = "";
|
|
5021
|
-
if (
|
|
5803
|
+
if (err2.code === "ENOENT") {
|
|
5022
5804
|
stderr = `Error: File not found: ${actionFile}`;
|
|
5023
5805
|
} else if (error instanceof SyntaxError) {
|
|
5024
5806
|
stderr = `Error: Invalid JSON in ${actionFile}
|
|
5025
5807
|
${error.message}`;
|
|
5026
5808
|
} else {
|
|
5027
|
-
stderr = `Error: ${
|
|
5809
|
+
stderr = `Error: ${err2.message}`;
|
|
5028
5810
|
}
|
|
5029
5811
|
return {
|
|
5030
5812
|
stdout: "",
|
|
@@ -5092,13 +5874,267 @@ function writeActionReceipt(descriptor, rawContent, actionReport, policy, args,
|
|
|
5092
5874
|
args.flags["receipt-out"] ?? "calllint-action-receipt.json"
|
|
5093
5875
|
);
|
|
5094
5876
|
try {
|
|
5095
|
-
|
|
5877
|
+
writeFileSync7(outPath, JSON.stringify(receipt, null, 2) + "\n", "utf8");
|
|
5096
5878
|
} catch (e) {
|
|
5097
5879
|
return `Could not write action receipt to ${outPath}: ${e instanceof Error ? e.message : String(e)}`;
|
|
5098
5880
|
}
|
|
5099
5881
|
return void 0;
|
|
5100
5882
|
}
|
|
5101
5883
|
|
|
5884
|
+
// src/commands/inbox.ts
|
|
5885
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "node:fs";
|
|
5886
|
+
import { resolve as resolve6 } from "node:path";
|
|
5887
|
+
function inboxCommand(args, deps) {
|
|
5888
|
+
const subcommand = args.positionals[0];
|
|
5889
|
+
if (!subcommand || subcommand === "help") {
|
|
5890
|
+
return {
|
|
5891
|
+
stdout: inboxHelp(),
|
|
5892
|
+
stderr: "",
|
|
5893
|
+
exitCode: 0
|
|
5894
|
+
};
|
|
5895
|
+
}
|
|
5896
|
+
if (subcommand === "inspect") {
|
|
5897
|
+
return inboxInspect(args, deps);
|
|
5898
|
+
}
|
|
5899
|
+
return {
|
|
5900
|
+
stdout: "",
|
|
5901
|
+
stderr: `Unknown inbox subcommand: ${subcommand}
|
|
5902
|
+
Run \`calllint inbox help\`.`,
|
|
5903
|
+
exitCode: 2
|
|
5904
|
+
};
|
|
5905
|
+
}
|
|
5906
|
+
function inboxInspect(args, deps) {
|
|
5907
|
+
const eventFile = args.positionals[1];
|
|
5908
|
+
if (!eventFile) {
|
|
5909
|
+
return {
|
|
5910
|
+
stdout: "",
|
|
5911
|
+
stderr: "Error: Missing event file\nUsage: calllint inbox inspect <normalized-event.json>",
|
|
5912
|
+
exitCode: 2
|
|
5913
|
+
};
|
|
5914
|
+
}
|
|
5915
|
+
try {
|
|
5916
|
+
const absolutePath = resolve6(deps.cwd, eventFile);
|
|
5917
|
+
const content = readFileSync10(absolutePath, "utf-8");
|
|
5918
|
+
const event = JSON.parse(content);
|
|
5919
|
+
if (event.schema_version !== "calllint.agent-inbox-event.v0") {
|
|
5920
|
+
return {
|
|
5921
|
+
stdout: "",
|
|
5922
|
+
stderr: `Error: Unsupported schema version: ${event.schema_version}
|
|
5923
|
+
Expected: calllint.agent-inbox-event.v0`,
|
|
5924
|
+
exitCode: 2
|
|
5925
|
+
};
|
|
5926
|
+
}
|
|
5927
|
+
if (!event.event_type || !event.timestamp || !event.source || !event.normalized_content) {
|
|
5928
|
+
return {
|
|
5929
|
+
stdout: "",
|
|
5930
|
+
stderr: "Error: Invalid inbox event - missing required fields (event_type, timestamp, source, normalized_content)",
|
|
5931
|
+
exitCode: 2
|
|
5932
|
+
};
|
|
5933
|
+
}
|
|
5934
|
+
const policyPath = args.flags["policy"];
|
|
5935
|
+
const policy = loadPolicyOrDefault(policyPath ? resolve6(deps.cwd, policyPath) : void 0);
|
|
5936
|
+
const actionCandidate = event.action_candidate;
|
|
5937
|
+
let verdict;
|
|
5938
|
+
let findings;
|
|
5939
|
+
let stdout = "";
|
|
5940
|
+
if (!actionCandidate) {
|
|
5941
|
+
verdict = "SAFE";
|
|
5942
|
+
findings = [];
|
|
5943
|
+
if (args.flags["json"]) {
|
|
5944
|
+
const report = {
|
|
5945
|
+
schema_version: "calllint.inbox-report.v0",
|
|
5946
|
+
verdict,
|
|
5947
|
+
findings: [],
|
|
5948
|
+
event: {
|
|
5949
|
+
event_type: event.event_type,
|
|
5950
|
+
provider: event.source.provider,
|
|
5951
|
+
account_id: event.source.account_id,
|
|
5952
|
+
timestamp: event.timestamp
|
|
5953
|
+
},
|
|
5954
|
+
action_candidate: null,
|
|
5955
|
+
scan_timestamp: deps.generatedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
5956
|
+
};
|
|
5957
|
+
stdout = JSON.stringify(report, null, 2);
|
|
5958
|
+
} else {
|
|
5959
|
+
stdout = `
|
|
5960
|
+
\u{1F4EC} Inbox Event: ${event.event_type} (${event.source.provider})
|
|
5961
|
+
Verdict: \u2705 SAFE
|
|
5962
|
+
Action Candidate: None (informational only)
|
|
5963
|
+
`;
|
|
5964
|
+
}
|
|
5965
|
+
} else {
|
|
5966
|
+
if (actionCandidate.schema_version !== "calllint.action.v0") {
|
|
5967
|
+
return {
|
|
5968
|
+
stdout: "",
|
|
5969
|
+
stderr: `Error: Unsupported action_candidate schema: ${actionCandidate.schema_version}
|
|
5970
|
+
Expected: calllint.action.v0`,
|
|
5971
|
+
exitCode: 2
|
|
5972
|
+
};
|
|
5973
|
+
}
|
|
5974
|
+
findings = analyzeAction(actionCandidate);
|
|
5975
|
+
verdict = computeActionVerdict2(findings, policy);
|
|
5976
|
+
if (args.flags["json"]) {
|
|
5977
|
+
const report = {
|
|
5978
|
+
schema_version: "calllint.inbox-report.v0",
|
|
5979
|
+
verdict,
|
|
5980
|
+
findings,
|
|
5981
|
+
event: {
|
|
5982
|
+
event_type: event.event_type,
|
|
5983
|
+
provider: event.source.provider,
|
|
5984
|
+
account_id: event.source.account_id,
|
|
5985
|
+
timestamp: event.timestamp
|
|
5986
|
+
},
|
|
5987
|
+
action_candidate: {
|
|
5988
|
+
kind: actionCandidate.kind,
|
|
5989
|
+
schema_version: actionCandidate.schema_version
|
|
5990
|
+
},
|
|
5991
|
+
scan_timestamp: deps.generatedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
5992
|
+
};
|
|
5993
|
+
stdout = JSON.stringify(report, null, 2);
|
|
5994
|
+
} else {
|
|
5995
|
+
const verdictEmoji = verdict === "SAFE" ? "\u2705" : verdict === "BLOCK" ? "\u26D4" : verdict === "REVIEW" ? "\u26A0\uFE0F" : "\u25C7";
|
|
5996
|
+
stdout = `
|
|
5997
|
+
\u{1F4EC} Inbox Event: ${event.event_type} (${event.source.provider})
|
|
5998
|
+
\u{1F50D} Action Candidate: ${actionCandidate.kind}
|
|
5999
|
+
Verdict: ${verdictEmoji} ${verdict}
|
|
6000
|
+
Findings: ${findings.length}
|
|
6001
|
+
`;
|
|
6002
|
+
if (findings.length > 0) {
|
|
6003
|
+
stdout += "\n";
|
|
6004
|
+
for (const f of findings) {
|
|
6005
|
+
stdout += ` \u2022 ${f.id}: ${f.impact}
|
|
6006
|
+
`;
|
|
6007
|
+
}
|
|
6008
|
+
}
|
|
6009
|
+
}
|
|
6010
|
+
}
|
|
6011
|
+
if (args.flags["receipt"]) {
|
|
6012
|
+
const receiptError = writeInboxReceipt(event, content, verdict, findings, policy, args, deps);
|
|
6013
|
+
if (receiptError) {
|
|
6014
|
+
return { stdout: "", stderr: receiptError, exitCode: 2 };
|
|
6015
|
+
}
|
|
6016
|
+
}
|
|
6017
|
+
return {
|
|
6018
|
+
stdout,
|
|
6019
|
+
stderr: "",
|
|
6020
|
+
exitCode: verdict === "SAFE" ? 0 : 1
|
|
6021
|
+
};
|
|
6022
|
+
} catch (error) {
|
|
6023
|
+
const err2 = error;
|
|
6024
|
+
let stderr = "";
|
|
6025
|
+
if (err2.code === "ENOENT") {
|
|
6026
|
+
stderr = `Error: File not found: ${eventFile}`;
|
|
6027
|
+
} else if (error instanceof SyntaxError) {
|
|
6028
|
+
stderr = `Error: Invalid JSON in ${eventFile}
|
|
6029
|
+
${error.message}`;
|
|
6030
|
+
} else {
|
|
6031
|
+
stderr = `Error: ${err2.message}`;
|
|
6032
|
+
}
|
|
6033
|
+
return {
|
|
6034
|
+
stdout: "",
|
|
6035
|
+
stderr,
|
|
6036
|
+
exitCode: 2
|
|
6037
|
+
};
|
|
6038
|
+
}
|
|
6039
|
+
}
|
|
6040
|
+
function computeActionVerdict2(findings, policy) {
|
|
6041
|
+
if (findings.length === 0) {
|
|
6042
|
+
return "SAFE";
|
|
6043
|
+
}
|
|
6044
|
+
const hasBlocker = findings.some((f) => f.blocker || f.severity === "critical");
|
|
6045
|
+
if (hasBlocker) {
|
|
6046
|
+
return "BLOCK";
|
|
6047
|
+
}
|
|
6048
|
+
const hasHigh = findings.some((f) => f.severity === "high");
|
|
6049
|
+
if (hasHigh) {
|
|
6050
|
+
return "REVIEW";
|
|
6051
|
+
}
|
|
6052
|
+
return "REVIEW";
|
|
6053
|
+
}
|
|
6054
|
+
function writeInboxReceipt(event, rawContent, verdict, findings, policy, args, deps) {
|
|
6055
|
+
try {
|
|
6056
|
+
const target = event.source.account_id ? `${event.source.provider}.${event.source.account_id}` : event.source.provider;
|
|
6057
|
+
const inboxReport = {
|
|
6058
|
+
schema_version: "calllint.inbox-report.v0",
|
|
6059
|
+
verdict,
|
|
6060
|
+
counts: {
|
|
6061
|
+
SAFE: verdict === "SAFE" ? 1 : 0,
|
|
6062
|
+
REVIEW: verdict === "REVIEW" ? 1 : 0,
|
|
6063
|
+
BLOCK: verdict === "BLOCK" ? 1 : 0,
|
|
6064
|
+
UNKNOWN: verdict === "UNKNOWN" ? 1 : 0
|
|
6065
|
+
},
|
|
6066
|
+
reports: [{ findings }]
|
|
6067
|
+
};
|
|
6068
|
+
const toolVersion = deps.toolVersion || "0.0.0-dev";
|
|
6069
|
+
const receipt = createReceipt(
|
|
6070
|
+
{
|
|
6071
|
+
toolVersion,
|
|
6072
|
+
subject: { type: "action", target },
|
|
6073
|
+
// Use 'action' type (inbox extends action)
|
|
6074
|
+
inputForHash: rawContent,
|
|
6075
|
+
effectivePolicyForHash: policy || { policy: "default" },
|
|
6076
|
+
scanReport: inboxReport,
|
|
6077
|
+
rulesetForHash: { tool: "calllint", version: toolVersion },
|
|
6078
|
+
networkUsed: false
|
|
6079
|
+
},
|
|
6080
|
+
deps.generatedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
6081
|
+
);
|
|
6082
|
+
const receiptOutPath = args.flags["receipt-out"] || "calllint-inbox-receipt.json";
|
|
6083
|
+
const absoluteReceiptPath = resolve6(deps.cwd, receiptOutPath);
|
|
6084
|
+
writeFileSync8(absoluteReceiptPath, JSON.stringify(receipt, null, 2) + "\n", "utf-8");
|
|
6085
|
+
return null;
|
|
6086
|
+
} catch (error) {
|
|
6087
|
+
const err2 = error;
|
|
6088
|
+
return `Error writing receipt: ${err2.message}`;
|
|
6089
|
+
}
|
|
6090
|
+
}
|
|
6091
|
+
function inboxHelp() {
|
|
6092
|
+
return `
|
|
6093
|
+
calllint inbox \u2014 Inspect normalized agent inbox events
|
|
6094
|
+
|
|
6095
|
+
USAGE
|
|
6096
|
+
calllint inbox inspect <normalized-event.json> [options]
|
|
6097
|
+
|
|
6098
|
+
DESCRIPTION
|
|
6099
|
+
Read a normalized agent inbox event (calllint.agent-inbox-event.v0),
|
|
6100
|
+
extract the optional action_candidate field, and analyze it using the
|
|
6101
|
+
R4 action analyzer. Returns SAFE/REVIEW/BLOCK/UNKNOWN verdict with
|
|
6102
|
+
evidence-backed findings.
|
|
6103
|
+
|
|
6104
|
+
This command is a composition layer: it does NOT fetch messages, poll
|
|
6105
|
+
providers, or implement OAuth. Caller provides pre-normalized events.
|
|
6106
|
+
|
|
6107
|
+
OPTIONS
|
|
6108
|
+
--receipt Write a receipt (calllint.receipt.v0) with subject.type="inbox"
|
|
6109
|
+
--receipt-out <path> Receipt output path (default: calllint-receipt.json)
|
|
6110
|
+
--policy <path> Custom policy file
|
|
6111
|
+
--json JSON output
|
|
6112
|
+
|
|
6113
|
+
EXIT CODES
|
|
6114
|
+
0 SAFE (no blockers observed)
|
|
6115
|
+
1 REVIEW / BLOCK / UNKNOWN
|
|
6116
|
+
2 Invalid input or schema violation
|
|
6117
|
+
|
|
6118
|
+
EXAMPLES
|
|
6119
|
+
# Inspect a normalized Gmail event with action candidate
|
|
6120
|
+
calllint inbox inspect gmail-reply.normalized.json
|
|
6121
|
+
|
|
6122
|
+
# Generate receipt for audit trail
|
|
6123
|
+
calllint inbox inspect resend-invoice.json --receipt
|
|
6124
|
+
|
|
6125
|
+
# JSON output for programmatic use
|
|
6126
|
+
calllint inbox inspect slack-mention.json --json
|
|
6127
|
+
|
|
6128
|
+
SEE ALSO
|
|
6129
|
+
docs/AGENT_INBOX_PREFLIGHT.md \u2014 Usage guide + worked examples
|
|
6130
|
+
docs/AGENT_INBOX_ADAPTER_CONTRACT.md \u2014 Provider \u2192 normalized transformation
|
|
6131
|
+
packages/fixtures/agent-inbox/ \u2014 6 providers \xD7 2 examples (reference)
|
|
6132
|
+
ADR 0031 \u2014 Agent Inbox Runtime design
|
|
6133
|
+
ADR 0030 \u2014 Agent Inbox Spec (v0.10.0 design)
|
|
6134
|
+
ADR 0029 \u2014 Unified External Action Preflight (R4)
|
|
6135
|
+
`;
|
|
6136
|
+
}
|
|
6137
|
+
|
|
5102
6138
|
// src/run.ts
|
|
5103
6139
|
function run(argv, deps) {
|
|
5104
6140
|
const args = parseArgs(argv);
|
|
@@ -5168,7 +6204,9 @@ function run(argv, deps) {
|
|
|
5168
6204
|
case "receipt":
|
|
5169
6205
|
return receiptCommand(args, { cwd: deps.cwd });
|
|
5170
6206
|
case "action":
|
|
5171
|
-
return actionCommand(args, { cwd: deps.cwd });
|
|
6207
|
+
return actionCommand(args, { cwd: deps.cwd, toolVersion: deps.toolVersion, generatedAt: deps.generatedAt });
|
|
6208
|
+
case "inbox":
|
|
6209
|
+
return inboxCommand(args, { cwd: deps.cwd, toolVersion: deps.toolVersion, generatedAt: deps.generatedAt });
|
|
5172
6210
|
case "gen-rule":
|
|
5173
6211
|
return genRuleCommand(args, { cwd: deps.cwd });
|
|
5174
6212
|
case "policy":
|
|
@@ -5442,13 +6480,13 @@ async function breathe(argv, deps = {}) {
|
|
|
5442
6480
|
}
|
|
5443
6481
|
|
|
5444
6482
|
// src/version.ts
|
|
5445
|
-
import { readFileSync as
|
|
6483
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
5446
6484
|
import { fileURLToPath } from "node:url";
|
|
5447
6485
|
import { dirname as dirname4, join as join11 } from "node:path";
|
|
5448
6486
|
function resolveToolVersion() {
|
|
5449
6487
|
try {
|
|
5450
6488
|
const pkgPath = join11(dirname4(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
5451
|
-
const pkg = JSON.parse(
|
|
6489
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
|
|
5452
6490
|
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : "unknown";
|
|
5453
6491
|
} catch {
|
|
5454
6492
|
return "unknown";
|
|
@@ -5458,7 +6496,7 @@ function resolveToolVersion() {
|
|
|
5458
6496
|
// src/index.ts
|
|
5459
6497
|
function readStdin() {
|
|
5460
6498
|
try {
|
|
5461
|
-
return
|
|
6499
|
+
return readFileSync12(0, "utf8");
|
|
5462
6500
|
} catch {
|
|
5463
6501
|
return "";
|
|
5464
6502
|
}
|
|
@@ -5479,8 +6517,8 @@ async function main() {
|
|
|
5479
6517
|
let clock;
|
|
5480
6518
|
try {
|
|
5481
6519
|
clock = resolveClock(argv, () => /* @__PURE__ */ new Date());
|
|
5482
|
-
} catch (
|
|
5483
|
-
process.stderr.write(`${
|
|
6520
|
+
} catch (err2) {
|
|
6521
|
+
process.stderr.write(`${err2 instanceof Error ? err2.message : String(err2)}
|
|
5484
6522
|
`);
|
|
5485
6523
|
process.exitCode = 2;
|
|
5486
6524
|
return;
|
|
@@ -5489,8 +6527,8 @@ async function main() {
|
|
|
5489
6527
|
let online;
|
|
5490
6528
|
try {
|
|
5491
6529
|
online = await computeOnlineEnrichment(argv, { fetchedAt: generatedAt });
|
|
5492
|
-
} catch (
|
|
5493
|
-
process.stderr.write(`--online failed: ${
|
|
6530
|
+
} catch (err2) {
|
|
6531
|
+
process.stderr.write(`--online failed: ${err2 instanceof Error ? err2.message : String(err2)}
|
|
5494
6532
|
`);
|
|
5495
6533
|
process.exitCode = 3;
|
|
5496
6534
|
return;
|
|
@@ -5515,3 +6553,8 @@ async function main() {
|
|
|
5515
6553
|
process.exitCode = result.exitCode;
|
|
5516
6554
|
}
|
|
5517
6555
|
void main();
|
|
6556
|
+
/*! Bundled license information:
|
|
6557
|
+
|
|
6558
|
+
@noble/ed25519/index.js:
|
|
6559
|
+
(*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) *)
|
|
6560
|
+
*/
|