calllint 0.9.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1259 -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,603 @@ 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
|
+
// ../../node_modules/.pnpm/@noble+ed25519@2.3.0/node_modules/@noble/ed25519/index.js
|
|
4413
|
+
var ed25519_CURVE = {
|
|
4414
|
+
p: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedn,
|
|
4415
|
+
n: 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3edn,
|
|
4416
|
+
h: 8n,
|
|
4417
|
+
a: 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffecn,
|
|
4418
|
+
d: 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3n,
|
|
4419
|
+
Gx: 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51an,
|
|
4420
|
+
Gy: 0x6666666666666666666666666666666666666666666666666666666666666658n
|
|
4421
|
+
};
|
|
4422
|
+
var { p: P, n: N, Gx, Gy, a: _a, d: _d } = ed25519_CURVE;
|
|
4423
|
+
var h = 8n;
|
|
4424
|
+
var L = 32;
|
|
4425
|
+
var L2 = 64;
|
|
4426
|
+
var err = (m = "") => {
|
|
4427
|
+
throw new Error(m);
|
|
4428
|
+
};
|
|
4429
|
+
var isBig = (n) => typeof n === "bigint";
|
|
4430
|
+
var isStr = (s) => typeof s === "string";
|
|
4431
|
+
var isBytes = (a) => a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
4432
|
+
var abytes = (a, l) => !isBytes(a) || typeof l === "number" && l > 0 && a.length !== l ? err("Uint8Array expected") : a;
|
|
4433
|
+
var u8n = (len) => new Uint8Array(len);
|
|
4434
|
+
var u8fr = (buf) => Uint8Array.from(buf);
|
|
4435
|
+
var padh = (n, pad) => n.toString(16).padStart(pad, "0");
|
|
4436
|
+
var bytesToHex = (b) => Array.from(abytes(b)).map((e) => padh(e, 2)).join("");
|
|
4437
|
+
var C = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
|
|
4438
|
+
var _ch = (ch) => {
|
|
4439
|
+
if (ch >= C._0 && ch <= C._9)
|
|
4440
|
+
return ch - C._0;
|
|
4441
|
+
if (ch >= C.A && ch <= C.F)
|
|
4442
|
+
return ch - (C.A - 10);
|
|
4443
|
+
if (ch >= C.a && ch <= C.f)
|
|
4444
|
+
return ch - (C.a - 10);
|
|
4445
|
+
return;
|
|
4446
|
+
};
|
|
4447
|
+
var hexToBytes = (hex) => {
|
|
4448
|
+
const e = "hex invalid";
|
|
4449
|
+
if (!isStr(hex))
|
|
4450
|
+
return err(e);
|
|
4451
|
+
const hl = hex.length;
|
|
4452
|
+
const al = hl / 2;
|
|
4453
|
+
if (hl % 2)
|
|
4454
|
+
return err(e);
|
|
4455
|
+
const array = u8n(al);
|
|
4456
|
+
for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) {
|
|
4457
|
+
const n1 = _ch(hex.charCodeAt(hi));
|
|
4458
|
+
const n2 = _ch(hex.charCodeAt(hi + 1));
|
|
4459
|
+
if (n1 === void 0 || n2 === void 0)
|
|
4460
|
+
return err(e);
|
|
4461
|
+
array[ai] = n1 * 16 + n2;
|
|
4462
|
+
}
|
|
4463
|
+
return array;
|
|
4464
|
+
};
|
|
4465
|
+
var toU8 = (a, len) => abytes(isStr(a) ? hexToBytes(a) : u8fr(abytes(a)), len);
|
|
4466
|
+
var cr = () => globalThis?.crypto;
|
|
4467
|
+
var subtle = () => cr()?.subtle ?? err("crypto.subtle must be defined");
|
|
4468
|
+
var concatBytes = (...arrs) => {
|
|
4469
|
+
const r = u8n(arrs.reduce((sum, a) => sum + abytes(a).length, 0));
|
|
4470
|
+
let pad = 0;
|
|
4471
|
+
arrs.forEach((a) => {
|
|
4472
|
+
r.set(a, pad);
|
|
4473
|
+
pad += a.length;
|
|
4474
|
+
});
|
|
4475
|
+
return r;
|
|
4476
|
+
};
|
|
4477
|
+
var randomBytes2 = (len = L) => {
|
|
4478
|
+
const c = cr();
|
|
4479
|
+
return c.getRandomValues(u8n(len));
|
|
4480
|
+
};
|
|
4481
|
+
var big = BigInt;
|
|
4482
|
+
var arange = (n, min, max, msg = "bad number: out of range") => isBig(n) && min <= n && n < max ? n : err(msg);
|
|
4483
|
+
var M = (a, b = P) => {
|
|
4484
|
+
const r = a % b;
|
|
4485
|
+
return r >= 0n ? r : b + r;
|
|
4486
|
+
};
|
|
4487
|
+
var modN = (a) => M(a, N);
|
|
4488
|
+
var invert = (num, md) => {
|
|
4489
|
+
if (num === 0n || md <= 0n)
|
|
4490
|
+
err("no inverse n=" + num + " mod=" + md);
|
|
4491
|
+
let a = M(num, md), b = md, x = 0n, y = 1n, u = 1n, v = 0n;
|
|
4492
|
+
while (a !== 0n) {
|
|
4493
|
+
const q = b / a, r = b % a;
|
|
4494
|
+
const m = x - u * q, n = y - v * q;
|
|
4495
|
+
b = a, a = r, x = u, y = v, u = m, v = n;
|
|
4496
|
+
}
|
|
4497
|
+
return b === 1n ? M(x, md) : err("no inverse");
|
|
4498
|
+
};
|
|
4499
|
+
var callHash = (name) => {
|
|
4500
|
+
const fn = etc[name];
|
|
4501
|
+
if (typeof fn !== "function")
|
|
4502
|
+
err("hashes." + name + " not set");
|
|
4503
|
+
return fn;
|
|
4504
|
+
};
|
|
4505
|
+
var apoint = (p) => p instanceof Point ? p : err("Point expected");
|
|
4506
|
+
var B256 = 2n ** 256n;
|
|
4507
|
+
var Point = class _Point {
|
|
4508
|
+
static BASE;
|
|
4509
|
+
static ZERO;
|
|
4510
|
+
ex;
|
|
4511
|
+
ey;
|
|
4512
|
+
ez;
|
|
4513
|
+
et;
|
|
4514
|
+
constructor(ex, ey, ez, et) {
|
|
4515
|
+
const max = B256;
|
|
4516
|
+
this.ex = arange(ex, 0n, max);
|
|
4517
|
+
this.ey = arange(ey, 0n, max);
|
|
4518
|
+
this.ez = arange(ez, 1n, max);
|
|
4519
|
+
this.et = arange(et, 0n, max);
|
|
4520
|
+
Object.freeze(this);
|
|
4521
|
+
}
|
|
4522
|
+
static fromAffine(p) {
|
|
4523
|
+
return new _Point(p.x, p.y, 1n, M(p.x * p.y));
|
|
4524
|
+
}
|
|
4525
|
+
/** RFC8032 5.1.3: Uint8Array to Point. */
|
|
4526
|
+
static fromBytes(hex, zip215 = false) {
|
|
4527
|
+
const d = _d;
|
|
4528
|
+
const normed = u8fr(abytes(hex, L));
|
|
4529
|
+
const lastByte = hex[31];
|
|
4530
|
+
normed[31] = lastByte & ~128;
|
|
4531
|
+
const y = bytesToNumLE(normed);
|
|
4532
|
+
const max = zip215 ? B256 : P;
|
|
4533
|
+
arange(y, 0n, max);
|
|
4534
|
+
const y2 = M(y * y);
|
|
4535
|
+
const u = M(y2 - 1n);
|
|
4536
|
+
const v = M(d * y2 + 1n);
|
|
4537
|
+
let { isValid, value: x } = uvRatio(u, v);
|
|
4538
|
+
if (!isValid)
|
|
4539
|
+
err("bad point: y not sqrt");
|
|
4540
|
+
const isXOdd = (x & 1n) === 1n;
|
|
4541
|
+
const isLastByteOdd = (lastByte & 128) !== 0;
|
|
4542
|
+
if (!zip215 && x === 0n && isLastByteOdd)
|
|
4543
|
+
err("bad point: x==0, isLastByteOdd");
|
|
4544
|
+
if (isLastByteOdd !== isXOdd)
|
|
4545
|
+
x = M(-x);
|
|
4546
|
+
return new _Point(x, y, 1n, M(x * y));
|
|
4547
|
+
}
|
|
4548
|
+
/** Checks if the point is valid and on-curve. */
|
|
4549
|
+
assertValidity() {
|
|
4550
|
+
const a = _a;
|
|
4551
|
+
const d = _d;
|
|
4552
|
+
const p = this;
|
|
4553
|
+
if (p.is0())
|
|
4554
|
+
throw new Error("bad point: ZERO");
|
|
4555
|
+
const { ex: X, ey: Y, ez: Z, et: T } = p;
|
|
4556
|
+
const X2 = M(X * X);
|
|
4557
|
+
const Y2 = M(Y * Y);
|
|
4558
|
+
const Z2 = M(Z * Z);
|
|
4559
|
+
const Z4 = M(Z2 * Z2);
|
|
4560
|
+
const aX2 = M(X2 * a);
|
|
4561
|
+
const left = M(Z2 * M(aX2 + Y2));
|
|
4562
|
+
const right = M(Z4 + M(d * M(X2 * Y2)));
|
|
4563
|
+
if (left !== right)
|
|
4564
|
+
throw new Error("bad point: equation left != right (1)");
|
|
4565
|
+
const XY = M(X * Y);
|
|
4566
|
+
const ZT = M(Z * T);
|
|
4567
|
+
if (XY !== ZT)
|
|
4568
|
+
throw new Error("bad point: equation left != right (2)");
|
|
4569
|
+
return this;
|
|
4570
|
+
}
|
|
4571
|
+
/** Equality check: compare points P&Q. */
|
|
4572
|
+
equals(other) {
|
|
4573
|
+
const { ex: X1, ey: Y1, ez: Z1 } = this;
|
|
4574
|
+
const { ex: X2, ey: Y2, ez: Z2 } = apoint(other);
|
|
4575
|
+
const X1Z2 = M(X1 * Z2);
|
|
4576
|
+
const X2Z1 = M(X2 * Z1);
|
|
4577
|
+
const Y1Z2 = M(Y1 * Z2);
|
|
4578
|
+
const Y2Z1 = M(Y2 * Z1);
|
|
4579
|
+
return X1Z2 === X2Z1 && Y1Z2 === Y2Z1;
|
|
4580
|
+
}
|
|
4581
|
+
is0() {
|
|
4582
|
+
return this.equals(I);
|
|
4583
|
+
}
|
|
4584
|
+
/** Flip point over y coordinate. */
|
|
4585
|
+
negate() {
|
|
4586
|
+
return new _Point(M(-this.ex), this.ey, this.ez, M(-this.et));
|
|
4587
|
+
}
|
|
4588
|
+
/** Point doubling. Complete formula. Cost: `4M + 4S + 1*a + 6add + 1*2`. */
|
|
4589
|
+
double() {
|
|
4590
|
+
const { ex: X1, ey: Y1, ez: Z1 } = this;
|
|
4591
|
+
const a = _a;
|
|
4592
|
+
const A = M(X1 * X1);
|
|
4593
|
+
const B = M(Y1 * Y1);
|
|
4594
|
+
const C2 = M(2n * M(Z1 * Z1));
|
|
4595
|
+
const D = M(a * A);
|
|
4596
|
+
const x1y1 = X1 + Y1;
|
|
4597
|
+
const E = M(M(x1y1 * x1y1) - A - B);
|
|
4598
|
+
const G2 = D + B;
|
|
4599
|
+
const F = G2 - C2;
|
|
4600
|
+
const H = D - B;
|
|
4601
|
+
const X3 = M(E * F);
|
|
4602
|
+
const Y3 = M(G2 * H);
|
|
4603
|
+
const T3 = M(E * H);
|
|
4604
|
+
const Z3 = M(F * G2);
|
|
4605
|
+
return new _Point(X3, Y3, Z3, T3);
|
|
4606
|
+
}
|
|
4607
|
+
/** Point addition. Complete formula. Cost: `8M + 1*k + 8add + 1*2`. */
|
|
4608
|
+
add(other) {
|
|
4609
|
+
const { ex: X1, ey: Y1, ez: Z1, et: T1 } = this;
|
|
4610
|
+
const { ex: X2, ey: Y2, ez: Z2, et: T2 } = apoint(other);
|
|
4611
|
+
const a = _a;
|
|
4612
|
+
const d = _d;
|
|
4613
|
+
const A = M(X1 * X2);
|
|
4614
|
+
const B = M(Y1 * Y2);
|
|
4615
|
+
const C2 = M(T1 * d * T2);
|
|
4616
|
+
const D = M(Z1 * Z2);
|
|
4617
|
+
const E = M((X1 + Y1) * (X2 + Y2) - A - B);
|
|
4618
|
+
const F = M(D - C2);
|
|
4619
|
+
const G2 = M(D + C2);
|
|
4620
|
+
const H = M(B - a * A);
|
|
4621
|
+
const X3 = M(E * F);
|
|
4622
|
+
const Y3 = M(G2 * H);
|
|
4623
|
+
const T3 = M(E * H);
|
|
4624
|
+
const Z3 = M(F * G2);
|
|
4625
|
+
return new _Point(X3, Y3, Z3, T3);
|
|
4626
|
+
}
|
|
4627
|
+
/**
|
|
4628
|
+
* Point-by-scalar multiplication. Scalar must be in range 1 <= n < CURVE.n.
|
|
4629
|
+
* Uses {@link wNAF} for base point.
|
|
4630
|
+
* Uses fake point to mitigate side-channel leakage.
|
|
4631
|
+
* @param n scalar by which point is multiplied
|
|
4632
|
+
* @param safe safe mode guards against timing attacks; unsafe mode is faster
|
|
4633
|
+
*/
|
|
4634
|
+
multiply(n, safe = true) {
|
|
4635
|
+
if (!safe && (n === 0n || this.is0()))
|
|
4636
|
+
return I;
|
|
4637
|
+
arange(n, 1n, N);
|
|
4638
|
+
if (n === 1n)
|
|
4639
|
+
return this;
|
|
4640
|
+
if (this.equals(G))
|
|
4641
|
+
return wNAF(n).p;
|
|
4642
|
+
let p = I;
|
|
4643
|
+
let f = G;
|
|
4644
|
+
for (let d = this; n > 0n; d = d.double(), n >>= 1n) {
|
|
4645
|
+
if (n & 1n)
|
|
4646
|
+
p = p.add(d);
|
|
4647
|
+
else if (safe)
|
|
4648
|
+
f = f.add(d);
|
|
4649
|
+
}
|
|
4650
|
+
return p;
|
|
4651
|
+
}
|
|
4652
|
+
/** Convert point to 2d xy affine point. (X, Y, Z) ∋ (x=X/Z, y=Y/Z) */
|
|
4653
|
+
toAffine() {
|
|
4654
|
+
const { ex: x, ey: y, ez: z } = this;
|
|
4655
|
+
if (this.equals(I))
|
|
4656
|
+
return { x: 0n, y: 1n };
|
|
4657
|
+
const iz = invert(z, P);
|
|
4658
|
+
if (M(z * iz) !== 1n)
|
|
4659
|
+
err("invalid inverse");
|
|
4660
|
+
return { x: M(x * iz), y: M(y * iz) };
|
|
4661
|
+
}
|
|
4662
|
+
toBytes() {
|
|
4663
|
+
const { x, y } = this.assertValidity().toAffine();
|
|
4664
|
+
const b = numTo32bLE(y);
|
|
4665
|
+
b[31] |= x & 1n ? 128 : 0;
|
|
4666
|
+
return b;
|
|
4667
|
+
}
|
|
4668
|
+
toHex() {
|
|
4669
|
+
return bytesToHex(this.toBytes());
|
|
4670
|
+
}
|
|
4671
|
+
// encode to hex string
|
|
4672
|
+
clearCofactor() {
|
|
4673
|
+
return this.multiply(big(h), false);
|
|
4674
|
+
}
|
|
4675
|
+
isSmallOrder() {
|
|
4676
|
+
return this.clearCofactor().is0();
|
|
4677
|
+
}
|
|
4678
|
+
isTorsionFree() {
|
|
4679
|
+
let p = this.multiply(N / 2n, false).double();
|
|
4680
|
+
if (N % 2n)
|
|
4681
|
+
p = p.add(this);
|
|
4682
|
+
return p.is0();
|
|
4683
|
+
}
|
|
4684
|
+
static fromHex(hex, zip215) {
|
|
4685
|
+
return _Point.fromBytes(toU8(hex), zip215);
|
|
4686
|
+
}
|
|
4687
|
+
get x() {
|
|
4688
|
+
return this.toAffine().x;
|
|
4689
|
+
}
|
|
4690
|
+
get y() {
|
|
4691
|
+
return this.toAffine().y;
|
|
4692
|
+
}
|
|
4693
|
+
toRawBytes() {
|
|
4694
|
+
return this.toBytes();
|
|
4695
|
+
}
|
|
4696
|
+
};
|
|
4697
|
+
var G = new Point(Gx, Gy, 1n, M(Gx * Gy));
|
|
4698
|
+
var I = new Point(0n, 1n, 1n, 0n);
|
|
4699
|
+
Point.BASE = G;
|
|
4700
|
+
Point.ZERO = I;
|
|
4701
|
+
var numTo32bLE = (num) => hexToBytes(padh(arange(num, 0n, B256), L2)).reverse();
|
|
4702
|
+
var bytesToNumLE = (b) => big("0x" + bytesToHex(u8fr(abytes(b)).reverse()));
|
|
4703
|
+
var pow2 = (x, power) => {
|
|
4704
|
+
let r = x;
|
|
4705
|
+
while (power-- > 0n) {
|
|
4706
|
+
r *= r;
|
|
4707
|
+
r %= P;
|
|
4708
|
+
}
|
|
4709
|
+
return r;
|
|
4710
|
+
};
|
|
4711
|
+
var pow_2_252_3 = (x) => {
|
|
4712
|
+
const x2 = x * x % P;
|
|
4713
|
+
const b2 = x2 * x % P;
|
|
4714
|
+
const b4 = pow2(b2, 2n) * b2 % P;
|
|
4715
|
+
const b5 = pow2(b4, 1n) * x % P;
|
|
4716
|
+
const b10 = pow2(b5, 5n) * b5 % P;
|
|
4717
|
+
const b20 = pow2(b10, 10n) * b10 % P;
|
|
4718
|
+
const b40 = pow2(b20, 20n) * b20 % P;
|
|
4719
|
+
const b80 = pow2(b40, 40n) * b40 % P;
|
|
4720
|
+
const b160 = pow2(b80, 80n) * b80 % P;
|
|
4721
|
+
const b240 = pow2(b160, 80n) * b80 % P;
|
|
4722
|
+
const b250 = pow2(b240, 10n) * b10 % P;
|
|
4723
|
+
const pow_p_5_8 = pow2(b250, 2n) * x % P;
|
|
4724
|
+
return { pow_p_5_8, b2 };
|
|
4725
|
+
};
|
|
4726
|
+
var RM1 = 0x2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0n;
|
|
4727
|
+
var uvRatio = (u, v) => {
|
|
4728
|
+
const v3 = M(v * v * v);
|
|
4729
|
+
const v7 = M(v3 * v3 * v);
|
|
4730
|
+
const pow = pow_2_252_3(u * v7).pow_p_5_8;
|
|
4731
|
+
let x = M(u * v3 * pow);
|
|
4732
|
+
const vx2 = M(v * x * x);
|
|
4733
|
+
const root1 = x;
|
|
4734
|
+
const root2 = M(x * RM1);
|
|
4735
|
+
const useRoot1 = vx2 === u;
|
|
4736
|
+
const useRoot2 = vx2 === M(-u);
|
|
4737
|
+
const noRoot = vx2 === M(-u * RM1);
|
|
4738
|
+
if (useRoot1)
|
|
4739
|
+
x = root1;
|
|
4740
|
+
if (useRoot2 || noRoot)
|
|
4741
|
+
x = root2;
|
|
4742
|
+
if ((M(x) & 1n) === 1n)
|
|
4743
|
+
x = M(-x);
|
|
4744
|
+
return { isValid: useRoot1 || useRoot2, value: x };
|
|
4745
|
+
};
|
|
4746
|
+
var modL_LE = (hash) => modN(bytesToNumLE(hash));
|
|
4747
|
+
var sha512a = (...m) => etc.sha512Async(...m);
|
|
4748
|
+
var sha512s = (...m) => callHash("sha512Sync")(...m);
|
|
4749
|
+
var hash2extK = (hashed) => {
|
|
4750
|
+
const head = hashed.slice(0, L);
|
|
4751
|
+
head[0] &= 248;
|
|
4752
|
+
head[31] &= 127;
|
|
4753
|
+
head[31] |= 64;
|
|
4754
|
+
const prefix = hashed.slice(L, L2);
|
|
4755
|
+
const scalar = modL_LE(head);
|
|
4756
|
+
const point = G.multiply(scalar);
|
|
4757
|
+
const pointBytes = point.toBytes();
|
|
4758
|
+
return { head, prefix, scalar, point, pointBytes };
|
|
4759
|
+
};
|
|
4760
|
+
var getExtendedPublicKeyAsync = (priv) => sha512a(toU8(priv, L)).then(hash2extK);
|
|
4761
|
+
var getExtendedPublicKey = (priv) => hash2extK(sha512s(toU8(priv, L)));
|
|
4762
|
+
var getPublicKeyAsync = (priv) => getExtendedPublicKeyAsync(priv).then((p) => p.pointBytes);
|
|
4763
|
+
var hashFinishA = (res) => sha512a(res.hashable).then(res.finish);
|
|
4764
|
+
var _sign = (e, rBytes, msg) => {
|
|
4765
|
+
const { pointBytes: P2, scalar: s } = e;
|
|
4766
|
+
const r = modL_LE(rBytes);
|
|
4767
|
+
const R = G.multiply(r).toBytes();
|
|
4768
|
+
const hashable = concatBytes(R, P2, msg);
|
|
4769
|
+
const finish = (hashed) => {
|
|
4770
|
+
const S = modN(r + modL_LE(hashed) * s);
|
|
4771
|
+
return abytes(concatBytes(R, numTo32bLE(S)), L2);
|
|
4772
|
+
};
|
|
4773
|
+
return { hashable, finish };
|
|
4774
|
+
};
|
|
4775
|
+
var signAsync = async (msg, privKey) => {
|
|
4776
|
+
const m = toU8(msg);
|
|
4777
|
+
const e = await getExtendedPublicKeyAsync(privKey);
|
|
4778
|
+
const rBytes = await sha512a(e.prefix, m);
|
|
4779
|
+
return hashFinishA(_sign(e, rBytes, m));
|
|
4780
|
+
};
|
|
4781
|
+
var veriOpts = { zip215: true };
|
|
4782
|
+
var _verify = (sig, msg, pub, opts = veriOpts) => {
|
|
4783
|
+
sig = toU8(sig, L2);
|
|
4784
|
+
msg = toU8(msg);
|
|
4785
|
+
pub = toU8(pub, L);
|
|
4786
|
+
const { zip215 } = opts;
|
|
4787
|
+
let A;
|
|
4788
|
+
let R;
|
|
4789
|
+
let s;
|
|
4790
|
+
let SB;
|
|
4791
|
+
let hashable = Uint8Array.of();
|
|
4792
|
+
try {
|
|
4793
|
+
A = Point.fromHex(pub, zip215);
|
|
4794
|
+
R = Point.fromHex(sig.slice(0, L), zip215);
|
|
4795
|
+
s = bytesToNumLE(sig.slice(L, L2));
|
|
4796
|
+
SB = G.multiply(s, false);
|
|
4797
|
+
hashable = concatBytes(R.toBytes(), A.toBytes(), msg);
|
|
4798
|
+
} catch (error) {
|
|
4799
|
+
}
|
|
4800
|
+
const finish = (hashed) => {
|
|
4801
|
+
if (SB == null)
|
|
4802
|
+
return false;
|
|
4803
|
+
if (!zip215 && A.isSmallOrder())
|
|
4804
|
+
return false;
|
|
4805
|
+
const k = modL_LE(hashed);
|
|
4806
|
+
const RkA = R.add(A.multiply(k, false));
|
|
4807
|
+
return RkA.add(SB.negate()).clearCofactor().is0();
|
|
4808
|
+
};
|
|
4809
|
+
return { hashable, finish };
|
|
4810
|
+
};
|
|
4811
|
+
var verifyAsync = async (s, m, p, opts = veriOpts) => hashFinishA(_verify(s, m, p, opts));
|
|
4812
|
+
var etc = {
|
|
4813
|
+
sha512Async: async (...messages) => {
|
|
4814
|
+
const s = subtle();
|
|
4815
|
+
const m = concatBytes(...messages);
|
|
4816
|
+
return u8n(await s.digest("SHA-512", m.buffer));
|
|
4817
|
+
},
|
|
4818
|
+
sha512Sync: void 0,
|
|
4819
|
+
bytesToHex,
|
|
4820
|
+
hexToBytes,
|
|
4821
|
+
concatBytes,
|
|
4822
|
+
mod: M,
|
|
4823
|
+
invert,
|
|
4824
|
+
randomBytes: randomBytes2
|
|
4825
|
+
};
|
|
4826
|
+
var utils = {
|
|
4827
|
+
getExtendedPublicKeyAsync,
|
|
4828
|
+
getExtendedPublicKey,
|
|
4829
|
+
randomPrivateKey: () => randomBytes2(L),
|
|
4830
|
+
precompute: (w = 8, p = G) => {
|
|
4831
|
+
p.multiply(3n);
|
|
4832
|
+
w;
|
|
4833
|
+
return p;
|
|
4834
|
+
}
|
|
4835
|
+
// no-op
|
|
4836
|
+
};
|
|
4837
|
+
var W = 8;
|
|
4838
|
+
var scalarBits = 256;
|
|
4839
|
+
var pwindows = Math.ceil(scalarBits / W) + 1;
|
|
4840
|
+
var pwindowSize = 2 ** (W - 1);
|
|
4841
|
+
var precompute = () => {
|
|
4842
|
+
const points = [];
|
|
4843
|
+
let p = G;
|
|
4844
|
+
let b = p;
|
|
4845
|
+
for (let w = 0; w < pwindows; w++) {
|
|
4846
|
+
b = p;
|
|
4847
|
+
points.push(b);
|
|
4848
|
+
for (let i = 1; i < pwindowSize; i++) {
|
|
4849
|
+
b = b.add(p);
|
|
4850
|
+
points.push(b);
|
|
4851
|
+
}
|
|
4852
|
+
p = b.double();
|
|
4853
|
+
}
|
|
4854
|
+
return points;
|
|
4855
|
+
};
|
|
4856
|
+
var Gpows = void 0;
|
|
4857
|
+
var ctneg = (cnd, p) => {
|
|
4858
|
+
const n = p.negate();
|
|
4859
|
+
return cnd ? n : p;
|
|
4860
|
+
};
|
|
4861
|
+
var wNAF = (n) => {
|
|
4862
|
+
const comp = Gpows || (Gpows = precompute());
|
|
4863
|
+
let p = I;
|
|
4864
|
+
let f = G;
|
|
4865
|
+
const pow_2_w = 2 ** W;
|
|
4866
|
+
const maxNum = pow_2_w;
|
|
4867
|
+
const mask = big(pow_2_w - 1);
|
|
4868
|
+
const shiftBy = big(W);
|
|
4869
|
+
for (let w = 0; w < pwindows; w++) {
|
|
4870
|
+
let wbits = Number(n & mask);
|
|
4871
|
+
n >>= shiftBy;
|
|
4872
|
+
if (wbits > pwindowSize) {
|
|
4873
|
+
wbits -= maxNum;
|
|
4874
|
+
n += 1n;
|
|
4875
|
+
}
|
|
4876
|
+
const off = w * pwindowSize;
|
|
4877
|
+
const offF = off;
|
|
4878
|
+
const offP = off + Math.abs(wbits) - 1;
|
|
4879
|
+
const isEven = w % 2 !== 0;
|
|
4880
|
+
const isNeg = wbits < 0;
|
|
4881
|
+
if (wbits === 0) {
|
|
4882
|
+
f = f.add(ctneg(isEven, comp[offF]));
|
|
4883
|
+
} else {
|
|
4884
|
+
p = p.add(ctneg(isNeg, comp[offP]));
|
|
4885
|
+
}
|
|
4886
|
+
}
|
|
4887
|
+
return { p, f };
|
|
4888
|
+
};
|
|
4889
|
+
|
|
4890
|
+
// ../../packages/signature/src/base64url.ts
|
|
4891
|
+
function base64urlEncode(bytes) {
|
|
4892
|
+
const base64 = Buffer.from(bytes).toString("base64");
|
|
4893
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
4894
|
+
}
|
|
4895
|
+
function base64urlDecode(str) {
|
|
4896
|
+
const padded = str + "===".slice((str.length + 3) % 4);
|
|
4897
|
+
const base64 = padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
4898
|
+
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
4899
|
+
}
|
|
4900
|
+
|
|
4901
|
+
// ../../packages/signature/src/ed25519.ts
|
|
4902
|
+
async function generateKeypair(keyId) {
|
|
4903
|
+
const privateKey = utils.randomPrivateKey();
|
|
4904
|
+
const publicKey = await getPublicKeyAsync(privateKey);
|
|
4905
|
+
return {
|
|
4906
|
+
privateKey,
|
|
4907
|
+
publicKey,
|
|
4908
|
+
keyId
|
|
4909
|
+
};
|
|
4910
|
+
}
|
|
4911
|
+
async function signReceipt(unsignedReceipt, keypair) {
|
|
4912
|
+
const canonical = stableStringify(unsignedReceipt);
|
|
4913
|
+
const hash = sha256(canonical);
|
|
4914
|
+
const hashHex = hash.slice(7);
|
|
4915
|
+
const hashBytes = Buffer.from(hashHex, "hex");
|
|
4916
|
+
const signatureBytes = await signAsync(hashBytes, keypair.privateKey);
|
|
4917
|
+
const signatureBase64url = base64urlEncode(signatureBytes);
|
|
4918
|
+
return {
|
|
4919
|
+
algorithm: "ed25519",
|
|
4920
|
+
key_id: keypair.keyId,
|
|
4921
|
+
value: signatureBase64url,
|
|
4922
|
+
signed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
4923
|
+
};
|
|
4924
|
+
}
|
|
4925
|
+
async function verifyReceipt2(signedReceipt, publicKey) {
|
|
4926
|
+
try {
|
|
4927
|
+
const sig = signedReceipt.signature;
|
|
4928
|
+
if (!sig) {
|
|
4929
|
+
return { valid: false, error: "No signature field in receipt" };
|
|
4930
|
+
}
|
|
4931
|
+
if (sig.algorithm !== "ed25519") {
|
|
4932
|
+
return { valid: false, error: `Unsupported algorithm: ${sig.algorithm}` };
|
|
4933
|
+
}
|
|
4934
|
+
const publicKeyBytes = typeof publicKey === "string" ? base64urlDecode(publicKey) : publicKey;
|
|
4935
|
+
if (publicKeyBytes.length !== 32) {
|
|
4936
|
+
return { valid: false, error: "Invalid public key length (expected 32 bytes)" };
|
|
4937
|
+
}
|
|
4938
|
+
const signatureBytes = base64urlDecode(sig.value);
|
|
4939
|
+
if (signatureBytes.length !== 64) {
|
|
4940
|
+
return { valid: false, error: "Invalid signature length (expected 64 bytes)" };
|
|
4941
|
+
}
|
|
4942
|
+
const { signature, ...unsignedReceipt } = signedReceipt;
|
|
4943
|
+
const canonical = stableStringify(unsignedReceipt);
|
|
4944
|
+
const hash = sha256(canonical);
|
|
4945
|
+
const hashHex = hash.slice(7);
|
|
4946
|
+
const hashBytes = Buffer.from(hashHex, "hex");
|
|
4947
|
+
const valid = await verifyAsync(signatureBytes, hashBytes, publicKeyBytes);
|
|
4948
|
+
return { valid, key_id: sig.key_id };
|
|
4949
|
+
} catch (error) {
|
|
4414
4950
|
return {
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
exitCode: EXIT.USAGE
|
|
4951
|
+
valid: false,
|
|
4952
|
+
error: error instanceof Error ? error.message : "Unknown verification error"
|
|
4418
4953
|
};
|
|
4419
4954
|
}
|
|
4955
|
+
}
|
|
4956
|
+
function exportKeypair(keypair) {
|
|
4957
|
+
return {
|
|
4958
|
+
key_id: keypair.keyId,
|
|
4959
|
+
algorithm: "ed25519",
|
|
4960
|
+
private_key: base64urlEncode(keypair.privateKey),
|
|
4961
|
+
public_key: base64urlEncode(keypair.publicKey)
|
|
4962
|
+
};
|
|
4963
|
+
}
|
|
4964
|
+
function importKeypair(json) {
|
|
4965
|
+
if (json.algorithm !== "ed25519") {
|
|
4966
|
+
throw new Error(`Unsupported algorithm: ${json.algorithm}`);
|
|
4967
|
+
}
|
|
4968
|
+
return {
|
|
4969
|
+
keyId: json.key_id,
|
|
4970
|
+
privateKey: base64urlDecode(json.private_key),
|
|
4971
|
+
publicKey: base64urlDecode(json.public_key)
|
|
4972
|
+
};
|
|
4973
|
+
}
|
|
4974
|
+
|
|
4975
|
+
// src/commands/receipt.ts
|
|
4976
|
+
function receiptCommand(args, deps) {
|
|
4977
|
+
const sub = args.positionals[0];
|
|
4978
|
+
switch (sub) {
|
|
4979
|
+
case "verify":
|
|
4980
|
+
return receiptVerify(args, deps);
|
|
4981
|
+
case "sign":
|
|
4982
|
+
return receiptSign(args, deps);
|
|
4983
|
+
case "keygen":
|
|
4984
|
+
return receiptKeygen(args, deps);
|
|
4985
|
+
default:
|
|
4986
|
+
return {
|
|
4987
|
+
stdout: "",
|
|
4988
|
+
stderr: [
|
|
4989
|
+
"Usage: calllint receipt <subcommand>",
|
|
4990
|
+
"",
|
|
4991
|
+
"Subcommands:",
|
|
4992
|
+
" verify <receipt.json> Validate receipt structure and signature",
|
|
4993
|
+
" sign <receipt.json> --key <file> Sign receipt locally (development only)",
|
|
4994
|
+
" keygen --out <file> Generate test keypair (development only)"
|
|
4995
|
+
].join("\n"),
|
|
4996
|
+
exitCode: EXIT.USAGE
|
|
4997
|
+
};
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
5000
|
+
function receiptVerify(args, deps) {
|
|
4420
5001
|
const file = args.positionals[1];
|
|
4421
5002
|
if (!file) {
|
|
4422
5003
|
return {
|
|
4423
5004
|
stdout: "",
|
|
4424
|
-
stderr: "Usage: calllint receipt verify <receipt.json>",
|
|
5005
|
+
stderr: "Usage: calllint receipt verify <receipt.json> [--public-key <keyfile>]",
|
|
4425
5006
|
exitCode: EXIT.USAGE
|
|
4426
5007
|
};
|
|
4427
5008
|
}
|
|
@@ -4449,31 +5030,361 @@ function receiptCommand(args, deps) {
|
|
|
4449
5030
|
return { stdout: "", stderr: `CallLint receipt: invalid
|
|
4450
5031
|
- ${msg}`, exitCode: 1 };
|
|
4451
5032
|
}
|
|
4452
|
-
const
|
|
5033
|
+
const structResult = verifyReceipt(parsed);
|
|
5034
|
+
if (!structResult.valid) {
|
|
5035
|
+
if (flagBool(args.flags, "json")) {
|
|
5036
|
+
return {
|
|
5037
|
+
stdout: JSON.stringify(structResult, null, 2),
|
|
5038
|
+
exitCode: 1
|
|
5039
|
+
};
|
|
5040
|
+
}
|
|
5041
|
+
const lines = ["CallLint receipt: invalid", ...structResult.errors.map((e) => ` - ${e}`)];
|
|
5042
|
+
return { stdout: "", stderr: lines.join("\n"), exitCode: 1 };
|
|
5043
|
+
}
|
|
5044
|
+
const receipt = parsed;
|
|
5045
|
+
if (receipt.signature) {
|
|
5046
|
+
let cryptoResult = null;
|
|
5047
|
+
let errorMsg = null;
|
|
5048
|
+
try {
|
|
5049
|
+
const promise = verifySignature(receipt, args);
|
|
5050
|
+
let done = false;
|
|
5051
|
+
promise.then(
|
|
5052
|
+
(r) => {
|
|
5053
|
+
cryptoResult = r;
|
|
5054
|
+
done = true;
|
|
5055
|
+
},
|
|
5056
|
+
(e) => {
|
|
5057
|
+
errorMsg = e instanceof Error ? e.message : String(e);
|
|
5058
|
+
done = true;
|
|
5059
|
+
}
|
|
5060
|
+
);
|
|
5061
|
+
const start = Date.now();
|
|
5062
|
+
while (!done && Date.now() - start < 5e3) {
|
|
5063
|
+
}
|
|
5064
|
+
if (!done) {
|
|
5065
|
+
return {
|
|
5066
|
+
stdout: "",
|
|
5067
|
+
stderr: "Signature verification timed out",
|
|
5068
|
+
exitCode: EXIT.ERROR
|
|
5069
|
+
};
|
|
5070
|
+
}
|
|
5071
|
+
if (errorMsg) {
|
|
5072
|
+
return {
|
|
5073
|
+
stdout: "",
|
|
5074
|
+
stderr: `Signature verification failed: ${errorMsg}`,
|
|
5075
|
+
exitCode: EXIT.ERROR
|
|
5076
|
+
};
|
|
5077
|
+
}
|
|
5078
|
+
if (!cryptoResult) {
|
|
5079
|
+
return {
|
|
5080
|
+
stdout: "",
|
|
5081
|
+
stderr: "Signature verification failed (no result)",
|
|
5082
|
+
exitCode: EXIT.ERROR
|
|
5083
|
+
};
|
|
5084
|
+
}
|
|
5085
|
+
return formatVerifyResult(receipt, cryptoResult, args);
|
|
5086
|
+
} catch (e) {
|
|
5087
|
+
return {
|
|
5088
|
+
stdout: "",
|
|
5089
|
+
stderr: `Signature verification error: ${e instanceof Error ? e.message : String(e)}`,
|
|
5090
|
+
exitCode: EXIT.ERROR
|
|
5091
|
+
};
|
|
5092
|
+
}
|
|
5093
|
+
}
|
|
4453
5094
|
if (flagBool(args.flags, "json")) {
|
|
4454
5095
|
return {
|
|
4455
|
-
stdout: JSON.stringify(
|
|
4456
|
-
|
|
5096
|
+
stdout: JSON.stringify(
|
|
5097
|
+
{
|
|
5098
|
+
valid: true,
|
|
5099
|
+
receipt_id: receipt.receipt_id,
|
|
5100
|
+
schema_version: receipt.schema_version,
|
|
5101
|
+
verdict: receipt.verdict,
|
|
5102
|
+
signed: false
|
|
5103
|
+
},
|
|
5104
|
+
null,
|
|
5105
|
+
2
|
|
5106
|
+
),
|
|
5107
|
+
exitCode: EXIT.OK
|
|
4457
5108
|
};
|
|
4458
5109
|
}
|
|
4459
|
-
|
|
4460
|
-
|
|
5110
|
+
const stdout = [
|
|
5111
|
+
"\u2713 CallLint receipt: valid",
|
|
5112
|
+
` Receipt ID: ${receipt.receipt_id}`,
|
|
5113
|
+
` Schema: ${receipt.schema_version}`,
|
|
5114
|
+
` Verdict: ${receipt.verdict}`,
|
|
5115
|
+
` Signature: unsigned local receipt`
|
|
5116
|
+
].join("\n");
|
|
5117
|
+
return { stdout, exitCode: EXIT.OK };
|
|
5118
|
+
}
|
|
5119
|
+
async function verifySignature(receipt, args) {
|
|
5120
|
+
const sig = receipt.signature;
|
|
5121
|
+
const publicKeyUrl = sig.public_key_url || "https://calllint.com/.well-known/receipt-keys.json";
|
|
5122
|
+
let publicKey;
|
|
5123
|
+
const publicKeyFlag = args.flags["public-key"];
|
|
5124
|
+
if (publicKeyFlag && typeof publicKeyFlag === "string") {
|
|
5125
|
+
try {
|
|
5126
|
+
const keyJson = JSON.parse(readFileSync8(publicKeyFlag, "utf8"));
|
|
5127
|
+
publicKey = keyJson.public_key;
|
|
5128
|
+
} catch (e) {
|
|
5129
|
+
throw new Error(
|
|
5130
|
+
`Could not read public key from ${publicKeyFlag}: ${e instanceof Error ? e.message : String(e)}`
|
|
5131
|
+
);
|
|
5132
|
+
}
|
|
5133
|
+
} else {
|
|
5134
|
+
throw new Error(
|
|
5135
|
+
`Receipt has signature but public key not available.
|
|
5136
|
+
Signature key_id: ${sig.key_id}
|
|
5137
|
+
Public key URL: ${publicKeyUrl}
|
|
5138
|
+
|
|
5139
|
+
To verify locally, provide public key with --public-key <keyfile>
|
|
5140
|
+
Example: calllint receipt verify receipt.json --public-key dev-key.json`
|
|
5141
|
+
);
|
|
5142
|
+
}
|
|
5143
|
+
return await verifyReceipt2(receipt, publicKey);
|
|
5144
|
+
}
|
|
5145
|
+
function formatVerifyResult(receipt, cryptoResult, args) {
|
|
5146
|
+
const sig = receipt.signature;
|
|
5147
|
+
if (flagBool(args.flags, "json")) {
|
|
5148
|
+
return {
|
|
5149
|
+
stdout: JSON.stringify(
|
|
5150
|
+
{
|
|
5151
|
+
valid: cryptoResult.valid,
|
|
5152
|
+
receipt_id: receipt.receipt_id,
|
|
5153
|
+
schema_version: receipt.schema_version,
|
|
5154
|
+
verdict: receipt.verdict,
|
|
5155
|
+
signed: true,
|
|
5156
|
+
signature: {
|
|
5157
|
+
valid: cryptoResult.valid,
|
|
5158
|
+
key_id: sig.key_id,
|
|
5159
|
+
algorithm: sig.algorithm,
|
|
5160
|
+
signed_at: sig.signed_at,
|
|
5161
|
+
error: cryptoResult.error
|
|
5162
|
+
}
|
|
5163
|
+
},
|
|
5164
|
+
null,
|
|
5165
|
+
2
|
|
5166
|
+
),
|
|
5167
|
+
exitCode: cryptoResult.valid ? EXIT.OK : 1
|
|
5168
|
+
};
|
|
5169
|
+
}
|
|
5170
|
+
if (!cryptoResult.valid) {
|
|
5171
|
+
const lines = [
|
|
5172
|
+
"\u2717 CallLint receipt: signature INVALID",
|
|
5173
|
+
` Receipt ID: ${receipt.receipt_id}`,
|
|
5174
|
+
` Signature key: ${sig.key_id}`,
|
|
5175
|
+
` Error: ${cryptoResult.error || "signature verification failed"}`
|
|
5176
|
+
];
|
|
4461
5177
|
return { stdout: "", stderr: lines.join("\n"), exitCode: 1 };
|
|
4462
5178
|
}
|
|
4463
|
-
const r = parsed;
|
|
4464
|
-
const signature = r.signature ? "present (shape-only, not cryptographically verified)" : "unsigned local receipt";
|
|
4465
5179
|
const stdout = [
|
|
4466
|
-
"CallLint receipt: valid",
|
|
4467
|
-
`
|
|
4468
|
-
`
|
|
4469
|
-
`
|
|
4470
|
-
`
|
|
5180
|
+
"\u2713 CallLint receipt: valid",
|
|
5181
|
+
` Receipt ID: ${receipt.receipt_id}`,
|
|
5182
|
+
` Schema: ${receipt.schema_version}`,
|
|
5183
|
+
` Verdict: ${receipt.verdict}`,
|
|
5184
|
+
` Signature: valid (key: ${sig.key_id})`,
|
|
5185
|
+
` Signed at: ${sig.signed_at}`
|
|
4471
5186
|
].join("\n");
|
|
4472
5187
|
return { stdout, exitCode: EXIT.OK };
|
|
4473
5188
|
}
|
|
5189
|
+
function receiptSign(args, deps) {
|
|
5190
|
+
const receiptFile = args.positionals[1];
|
|
5191
|
+
const keyFile = args.flags["key"];
|
|
5192
|
+
const outFile = args.flags["out"] || receiptFile;
|
|
5193
|
+
if (!receiptFile || !keyFile || typeof keyFile !== "string") {
|
|
5194
|
+
return {
|
|
5195
|
+
stdout: "",
|
|
5196
|
+
stderr: [
|
|
5197
|
+
"Usage: calllint receipt sign <receipt.json> --key <keyfile> [--out <output>]",
|
|
5198
|
+
"",
|
|
5199
|
+
"Sign a receipt locally (development/testing only).",
|
|
5200
|
+
"Production signing requires cloud service: calllint scan --sign"
|
|
5201
|
+
].join("\n"),
|
|
5202
|
+
exitCode: EXIT.USAGE
|
|
5203
|
+
};
|
|
5204
|
+
}
|
|
5205
|
+
let receiptRaw;
|
|
5206
|
+
try {
|
|
5207
|
+
receiptRaw = readFileSync8(resolve4(deps.cwd, receiptFile), "utf8");
|
|
5208
|
+
} catch (e) {
|
|
5209
|
+
return {
|
|
5210
|
+
stdout: "",
|
|
5211
|
+
stderr: `Could not read receipt ${receiptFile}: ${e instanceof Error ? e.message : String(e)}`,
|
|
5212
|
+
exitCode: EXIT.ERROR
|
|
5213
|
+
};
|
|
5214
|
+
}
|
|
5215
|
+
let unsignedReceipt;
|
|
5216
|
+
try {
|
|
5217
|
+
unsignedReceipt = JSON.parse(receiptRaw);
|
|
5218
|
+
} catch (e) {
|
|
5219
|
+
return {
|
|
5220
|
+
stdout: "",
|
|
5221
|
+
stderr: `Receipt is not valid JSON: ${e instanceof Error ? e.message : String(e)}`,
|
|
5222
|
+
exitCode: EXIT.ERROR
|
|
5223
|
+
};
|
|
5224
|
+
}
|
|
5225
|
+
if (unsignedReceipt.signature) {
|
|
5226
|
+
return {
|
|
5227
|
+
stdout: "",
|
|
5228
|
+
stderr: "Receipt already has a signature field. Cannot sign again.",
|
|
5229
|
+
exitCode: EXIT.ERROR
|
|
5230
|
+
};
|
|
5231
|
+
}
|
|
5232
|
+
let keyJson;
|
|
5233
|
+
try {
|
|
5234
|
+
const keyRaw = readFileSync8(resolve4(deps.cwd, keyFile), "utf8");
|
|
5235
|
+
keyJson = JSON.parse(keyRaw);
|
|
5236
|
+
} catch (e) {
|
|
5237
|
+
return {
|
|
5238
|
+
stdout: "",
|
|
5239
|
+
stderr: `Could not read key file ${keyFile}: ${e instanceof Error ? e.message : String(e)}`,
|
|
5240
|
+
exitCode: EXIT.ERROR
|
|
5241
|
+
};
|
|
5242
|
+
}
|
|
5243
|
+
let keypair;
|
|
5244
|
+
try {
|
|
5245
|
+
keypair = importKeypair(keyJson);
|
|
5246
|
+
} catch (e) {
|
|
5247
|
+
return {
|
|
5248
|
+
stdout: "",
|
|
5249
|
+
stderr: `Invalid keypair format: ${e instanceof Error ? e.message : String(e)}`,
|
|
5250
|
+
exitCode: EXIT.ERROR
|
|
5251
|
+
};
|
|
5252
|
+
}
|
|
5253
|
+
let signature = null;
|
|
5254
|
+
let errorMsg = null;
|
|
5255
|
+
const promise = signReceipt(unsignedReceipt, keypair);
|
|
5256
|
+
let done = false;
|
|
5257
|
+
promise.then(
|
|
5258
|
+
(sig) => {
|
|
5259
|
+
signature = sig;
|
|
5260
|
+
done = true;
|
|
5261
|
+
},
|
|
5262
|
+
(e) => {
|
|
5263
|
+
errorMsg = e instanceof Error ? e.message : String(e);
|
|
5264
|
+
done = true;
|
|
5265
|
+
}
|
|
5266
|
+
);
|
|
5267
|
+
const start = Date.now();
|
|
5268
|
+
while (!done && Date.now() - start < 5e3) {
|
|
5269
|
+
}
|
|
5270
|
+
if (!done) {
|
|
5271
|
+
return {
|
|
5272
|
+
stdout: "",
|
|
5273
|
+
stderr: "Signing timed out",
|
|
5274
|
+
exitCode: EXIT.ERROR
|
|
5275
|
+
};
|
|
5276
|
+
}
|
|
5277
|
+
if (errorMsg) {
|
|
5278
|
+
return {
|
|
5279
|
+
stdout: "",
|
|
5280
|
+
stderr: `Signing failed: ${errorMsg}`,
|
|
5281
|
+
exitCode: EXIT.ERROR
|
|
5282
|
+
};
|
|
5283
|
+
}
|
|
5284
|
+
if (!signature) {
|
|
5285
|
+
return {
|
|
5286
|
+
stdout: "",
|
|
5287
|
+
stderr: "Signing failed (no result)",
|
|
5288
|
+
exitCode: EXIT.ERROR
|
|
5289
|
+
};
|
|
5290
|
+
}
|
|
5291
|
+
try {
|
|
5292
|
+
const signedReceipt = { ...unsignedReceipt, signature };
|
|
5293
|
+
const outPath = resolve4(deps.cwd, outFile);
|
|
5294
|
+
writeFileSync6(outPath, JSON.stringify(signedReceipt, null, 2) + "\n", "utf8");
|
|
5295
|
+
const stdout = [
|
|
5296
|
+
"\u2713 Receipt signed successfully",
|
|
5297
|
+
` Key ID: ${signature.key_id}`,
|
|
5298
|
+
` Algorithm: ${signature.algorithm}`,
|
|
5299
|
+
` Signed at: ${signature.signed_at}`,
|
|
5300
|
+
` Output: ${outFile}`
|
|
5301
|
+
].join("\n");
|
|
5302
|
+
return { stdout, exitCode: EXIT.OK };
|
|
5303
|
+
} catch (e) {
|
|
5304
|
+
return {
|
|
5305
|
+
stdout: "",
|
|
5306
|
+
stderr: `Failed to write signed receipt: ${e instanceof Error ? e.message : String(e)}`,
|
|
5307
|
+
exitCode: EXIT.ERROR
|
|
5308
|
+
};
|
|
5309
|
+
}
|
|
5310
|
+
}
|
|
5311
|
+
function receiptKeygen(args, deps) {
|
|
5312
|
+
const outFile = args.flags["out"];
|
|
5313
|
+
if (!outFile || typeof outFile !== "string") {
|
|
5314
|
+
return {
|
|
5315
|
+
stdout: "",
|
|
5316
|
+
stderr: [
|
|
5317
|
+
"Usage: calllint receipt keygen --out <file>",
|
|
5318
|
+
"",
|
|
5319
|
+
"Generate a test ed25519 keypair (development/testing only).",
|
|
5320
|
+
"Production keys are managed by cloud service."
|
|
5321
|
+
].join("\n"),
|
|
5322
|
+
exitCode: EXIT.USAGE
|
|
5323
|
+
};
|
|
5324
|
+
}
|
|
5325
|
+
const keyId = args.flags["key-id"] || "calllint-dev-2026-h2";
|
|
5326
|
+
let keypair = null;
|
|
5327
|
+
let errorMsg = null;
|
|
5328
|
+
const promise = generateKeypair(keyId);
|
|
5329
|
+
let done = false;
|
|
5330
|
+
promise.then(
|
|
5331
|
+
(kp) => {
|
|
5332
|
+
keypair = kp;
|
|
5333
|
+
done = true;
|
|
5334
|
+
},
|
|
5335
|
+
(e) => {
|
|
5336
|
+
errorMsg = e instanceof Error ? e.message : String(e);
|
|
5337
|
+
done = true;
|
|
5338
|
+
}
|
|
5339
|
+
);
|
|
5340
|
+
const start = Date.now();
|
|
5341
|
+
while (!done && Date.now() - start < 5e3) {
|
|
5342
|
+
}
|
|
5343
|
+
if (!done) {
|
|
5344
|
+
return {
|
|
5345
|
+
stdout: "",
|
|
5346
|
+
stderr: "Keygen timed out",
|
|
5347
|
+
exitCode: EXIT.ERROR
|
|
5348
|
+
};
|
|
5349
|
+
}
|
|
5350
|
+
if (errorMsg) {
|
|
5351
|
+
return {
|
|
5352
|
+
stdout: "",
|
|
5353
|
+
stderr: `Keygen failed: ${errorMsg}`,
|
|
5354
|
+
exitCode: EXIT.ERROR
|
|
5355
|
+
};
|
|
5356
|
+
}
|
|
5357
|
+
if (!keypair) {
|
|
5358
|
+
return {
|
|
5359
|
+
stdout: "",
|
|
5360
|
+
stderr: "Keygen failed (no result)",
|
|
5361
|
+
exitCode: EXIT.ERROR
|
|
5362
|
+
};
|
|
5363
|
+
}
|
|
5364
|
+
try {
|
|
5365
|
+
const exported = exportKeypair(keypair);
|
|
5366
|
+
const outPath = resolve4(deps.cwd, outFile);
|
|
5367
|
+
writeFileSync6(outPath, JSON.stringify(exported, null, 2) + "\n", "utf8");
|
|
5368
|
+
const stdout = [
|
|
5369
|
+
"\u2713 Keypair generated successfully",
|
|
5370
|
+
` Key ID: ${keyId}`,
|
|
5371
|
+
` Algorithm: ed25519`,
|
|
5372
|
+
` Output: ${outFile}`,
|
|
5373
|
+
"",
|
|
5374
|
+
"\u26A0\uFE0F Development/testing only. Do NOT use for production."
|
|
5375
|
+
].join("\n");
|
|
5376
|
+
return { stdout, exitCode: EXIT.OK };
|
|
5377
|
+
} catch (e) {
|
|
5378
|
+
return {
|
|
5379
|
+
stdout: "",
|
|
5380
|
+
stderr: `Failed to write keypair: ${e instanceof Error ? e.message : String(e)}`,
|
|
5381
|
+
exitCode: EXIT.ERROR
|
|
5382
|
+
};
|
|
5383
|
+
}
|
|
5384
|
+
}
|
|
4474
5385
|
|
|
4475
5386
|
// src/commands/action.ts
|
|
4476
|
-
import { readFileSync as readFileSync9, writeFileSync as
|
|
5387
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
|
|
4477
5388
|
import { resolve as resolve5 } from "node:path";
|
|
4478
5389
|
|
|
4479
5390
|
// ../../packages/action-analyzer/src/types.ts
|
|
@@ -5016,15 +5927,15 @@ Expected: calllint.action.v0`,
|
|
|
5016
5927
|
exitCode: verdict === "SAFE" ? 0 : 1
|
|
5017
5928
|
};
|
|
5018
5929
|
} catch (error) {
|
|
5019
|
-
const
|
|
5930
|
+
const err2 = error;
|
|
5020
5931
|
let stderr = "";
|
|
5021
|
-
if (
|
|
5932
|
+
if (err2.code === "ENOENT") {
|
|
5022
5933
|
stderr = `Error: File not found: ${actionFile}`;
|
|
5023
5934
|
} else if (error instanceof SyntaxError) {
|
|
5024
5935
|
stderr = `Error: Invalid JSON in ${actionFile}
|
|
5025
5936
|
${error.message}`;
|
|
5026
5937
|
} else {
|
|
5027
|
-
stderr = `Error: ${
|
|
5938
|
+
stderr = `Error: ${err2.message}`;
|
|
5028
5939
|
}
|
|
5029
5940
|
return {
|
|
5030
5941
|
stdout: "",
|
|
@@ -5092,13 +6003,267 @@ function writeActionReceipt(descriptor, rawContent, actionReport, policy, args,
|
|
|
5092
6003
|
args.flags["receipt-out"] ?? "calllint-action-receipt.json"
|
|
5093
6004
|
);
|
|
5094
6005
|
try {
|
|
5095
|
-
|
|
6006
|
+
writeFileSync7(outPath, JSON.stringify(receipt, null, 2) + "\n", "utf8");
|
|
5096
6007
|
} catch (e) {
|
|
5097
6008
|
return `Could not write action receipt to ${outPath}: ${e instanceof Error ? e.message : String(e)}`;
|
|
5098
6009
|
}
|
|
5099
6010
|
return void 0;
|
|
5100
6011
|
}
|
|
5101
6012
|
|
|
6013
|
+
// src/commands/inbox.ts
|
|
6014
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "node:fs";
|
|
6015
|
+
import { resolve as resolve6 } from "node:path";
|
|
6016
|
+
function inboxCommand(args, deps) {
|
|
6017
|
+
const subcommand = args.positionals[0];
|
|
6018
|
+
if (!subcommand || subcommand === "help") {
|
|
6019
|
+
return {
|
|
6020
|
+
stdout: inboxHelp(),
|
|
6021
|
+
stderr: "",
|
|
6022
|
+
exitCode: 0
|
|
6023
|
+
};
|
|
6024
|
+
}
|
|
6025
|
+
if (subcommand === "inspect") {
|
|
6026
|
+
return inboxInspect(args, deps);
|
|
6027
|
+
}
|
|
6028
|
+
return {
|
|
6029
|
+
stdout: "",
|
|
6030
|
+
stderr: `Unknown inbox subcommand: ${subcommand}
|
|
6031
|
+
Run \`calllint inbox help\`.`,
|
|
6032
|
+
exitCode: 2
|
|
6033
|
+
};
|
|
6034
|
+
}
|
|
6035
|
+
function inboxInspect(args, deps) {
|
|
6036
|
+
const eventFile = args.positionals[1];
|
|
6037
|
+
if (!eventFile) {
|
|
6038
|
+
return {
|
|
6039
|
+
stdout: "",
|
|
6040
|
+
stderr: "Error: Missing event file\nUsage: calllint inbox inspect <normalized-event.json>",
|
|
6041
|
+
exitCode: 2
|
|
6042
|
+
};
|
|
6043
|
+
}
|
|
6044
|
+
try {
|
|
6045
|
+
const absolutePath = resolve6(deps.cwd, eventFile);
|
|
6046
|
+
const content = readFileSync10(absolutePath, "utf-8");
|
|
6047
|
+
const event = JSON.parse(content);
|
|
6048
|
+
if (event.schema_version !== "calllint.agent-inbox-event.v0") {
|
|
6049
|
+
return {
|
|
6050
|
+
stdout: "",
|
|
6051
|
+
stderr: `Error: Unsupported schema version: ${event.schema_version}
|
|
6052
|
+
Expected: calllint.agent-inbox-event.v0`,
|
|
6053
|
+
exitCode: 2
|
|
6054
|
+
};
|
|
6055
|
+
}
|
|
6056
|
+
if (!event.event_type || !event.timestamp || !event.source || !event.normalized_content) {
|
|
6057
|
+
return {
|
|
6058
|
+
stdout: "",
|
|
6059
|
+
stderr: "Error: Invalid inbox event - missing required fields (event_type, timestamp, source, normalized_content)",
|
|
6060
|
+
exitCode: 2
|
|
6061
|
+
};
|
|
6062
|
+
}
|
|
6063
|
+
const policyPath = args.flags["policy"];
|
|
6064
|
+
const policy = loadPolicyOrDefault(policyPath ? resolve6(deps.cwd, policyPath) : void 0);
|
|
6065
|
+
const actionCandidate = event.action_candidate;
|
|
6066
|
+
let verdict;
|
|
6067
|
+
let findings;
|
|
6068
|
+
let stdout = "";
|
|
6069
|
+
if (!actionCandidate) {
|
|
6070
|
+
verdict = "SAFE";
|
|
6071
|
+
findings = [];
|
|
6072
|
+
if (args.flags["json"]) {
|
|
6073
|
+
const report = {
|
|
6074
|
+
schema_version: "calllint.inbox-report.v0",
|
|
6075
|
+
verdict,
|
|
6076
|
+
findings: [],
|
|
6077
|
+
event: {
|
|
6078
|
+
event_type: event.event_type,
|
|
6079
|
+
provider: event.source.provider,
|
|
6080
|
+
account_id: event.source.account_id,
|
|
6081
|
+
timestamp: event.timestamp
|
|
6082
|
+
},
|
|
6083
|
+
action_candidate: null,
|
|
6084
|
+
scan_timestamp: deps.generatedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
6085
|
+
};
|
|
6086
|
+
stdout = JSON.stringify(report, null, 2);
|
|
6087
|
+
} else {
|
|
6088
|
+
stdout = `
|
|
6089
|
+
\u{1F4EC} Inbox Event: ${event.event_type} (${event.source.provider})
|
|
6090
|
+
Verdict: \u2705 SAFE
|
|
6091
|
+
Action Candidate: None (informational only)
|
|
6092
|
+
`;
|
|
6093
|
+
}
|
|
6094
|
+
} else {
|
|
6095
|
+
if (actionCandidate.schema_version !== "calllint.action.v0") {
|
|
6096
|
+
return {
|
|
6097
|
+
stdout: "",
|
|
6098
|
+
stderr: `Error: Unsupported action_candidate schema: ${actionCandidate.schema_version}
|
|
6099
|
+
Expected: calllint.action.v0`,
|
|
6100
|
+
exitCode: 2
|
|
6101
|
+
};
|
|
6102
|
+
}
|
|
6103
|
+
findings = analyzeAction(actionCandidate);
|
|
6104
|
+
verdict = computeActionVerdict2(findings, policy);
|
|
6105
|
+
if (args.flags["json"]) {
|
|
6106
|
+
const report = {
|
|
6107
|
+
schema_version: "calllint.inbox-report.v0",
|
|
6108
|
+
verdict,
|
|
6109
|
+
findings,
|
|
6110
|
+
event: {
|
|
6111
|
+
event_type: event.event_type,
|
|
6112
|
+
provider: event.source.provider,
|
|
6113
|
+
account_id: event.source.account_id,
|
|
6114
|
+
timestamp: event.timestamp
|
|
6115
|
+
},
|
|
6116
|
+
action_candidate: {
|
|
6117
|
+
kind: actionCandidate.kind,
|
|
6118
|
+
schema_version: actionCandidate.schema_version
|
|
6119
|
+
},
|
|
6120
|
+
scan_timestamp: deps.generatedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
6121
|
+
};
|
|
6122
|
+
stdout = JSON.stringify(report, null, 2);
|
|
6123
|
+
} else {
|
|
6124
|
+
const verdictEmoji = verdict === "SAFE" ? "\u2705" : verdict === "BLOCK" ? "\u26D4" : verdict === "REVIEW" ? "\u26A0\uFE0F" : "\u25C7";
|
|
6125
|
+
stdout = `
|
|
6126
|
+
\u{1F4EC} Inbox Event: ${event.event_type} (${event.source.provider})
|
|
6127
|
+
\u{1F50D} Action Candidate: ${actionCandidate.kind}
|
|
6128
|
+
Verdict: ${verdictEmoji} ${verdict}
|
|
6129
|
+
Findings: ${findings.length}
|
|
6130
|
+
`;
|
|
6131
|
+
if (findings.length > 0) {
|
|
6132
|
+
stdout += "\n";
|
|
6133
|
+
for (const f of findings) {
|
|
6134
|
+
stdout += ` \u2022 ${f.id}: ${f.impact}
|
|
6135
|
+
`;
|
|
6136
|
+
}
|
|
6137
|
+
}
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
if (args.flags["receipt"]) {
|
|
6141
|
+
const receiptError = writeInboxReceipt(event, content, verdict, findings, policy, args, deps);
|
|
6142
|
+
if (receiptError) {
|
|
6143
|
+
return { stdout: "", stderr: receiptError, exitCode: 2 };
|
|
6144
|
+
}
|
|
6145
|
+
}
|
|
6146
|
+
return {
|
|
6147
|
+
stdout,
|
|
6148
|
+
stderr: "",
|
|
6149
|
+
exitCode: verdict === "SAFE" ? 0 : 1
|
|
6150
|
+
};
|
|
6151
|
+
} catch (error) {
|
|
6152
|
+
const err2 = error;
|
|
6153
|
+
let stderr = "";
|
|
6154
|
+
if (err2.code === "ENOENT") {
|
|
6155
|
+
stderr = `Error: File not found: ${eventFile}`;
|
|
6156
|
+
} else if (error instanceof SyntaxError) {
|
|
6157
|
+
stderr = `Error: Invalid JSON in ${eventFile}
|
|
6158
|
+
${error.message}`;
|
|
6159
|
+
} else {
|
|
6160
|
+
stderr = `Error: ${err2.message}`;
|
|
6161
|
+
}
|
|
6162
|
+
return {
|
|
6163
|
+
stdout: "",
|
|
6164
|
+
stderr,
|
|
6165
|
+
exitCode: 2
|
|
6166
|
+
};
|
|
6167
|
+
}
|
|
6168
|
+
}
|
|
6169
|
+
function computeActionVerdict2(findings, policy) {
|
|
6170
|
+
if (findings.length === 0) {
|
|
6171
|
+
return "SAFE";
|
|
6172
|
+
}
|
|
6173
|
+
const hasBlocker = findings.some((f) => f.blocker || f.severity === "critical");
|
|
6174
|
+
if (hasBlocker) {
|
|
6175
|
+
return "BLOCK";
|
|
6176
|
+
}
|
|
6177
|
+
const hasHigh = findings.some((f) => f.severity === "high");
|
|
6178
|
+
if (hasHigh) {
|
|
6179
|
+
return "REVIEW";
|
|
6180
|
+
}
|
|
6181
|
+
return "REVIEW";
|
|
6182
|
+
}
|
|
6183
|
+
function writeInboxReceipt(event, rawContent, verdict, findings, policy, args, deps) {
|
|
6184
|
+
try {
|
|
6185
|
+
const target = event.source.account_id ? `${event.source.provider}.${event.source.account_id}` : event.source.provider;
|
|
6186
|
+
const inboxReport = {
|
|
6187
|
+
schema_version: "calllint.inbox-report.v0",
|
|
6188
|
+
verdict,
|
|
6189
|
+
counts: {
|
|
6190
|
+
SAFE: verdict === "SAFE" ? 1 : 0,
|
|
6191
|
+
REVIEW: verdict === "REVIEW" ? 1 : 0,
|
|
6192
|
+
BLOCK: verdict === "BLOCK" ? 1 : 0,
|
|
6193
|
+
UNKNOWN: verdict === "UNKNOWN" ? 1 : 0
|
|
6194
|
+
},
|
|
6195
|
+
reports: [{ findings }]
|
|
6196
|
+
};
|
|
6197
|
+
const toolVersion = deps.toolVersion || "0.0.0-dev";
|
|
6198
|
+
const receipt = createReceipt(
|
|
6199
|
+
{
|
|
6200
|
+
toolVersion,
|
|
6201
|
+
subject: { type: "action", target },
|
|
6202
|
+
// Use 'action' type (inbox extends action)
|
|
6203
|
+
inputForHash: rawContent,
|
|
6204
|
+
effectivePolicyForHash: policy || { policy: "default" },
|
|
6205
|
+
scanReport: inboxReport,
|
|
6206
|
+
rulesetForHash: { tool: "calllint", version: toolVersion },
|
|
6207
|
+
networkUsed: false
|
|
6208
|
+
},
|
|
6209
|
+
deps.generatedAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
6210
|
+
);
|
|
6211
|
+
const receiptOutPath = args.flags["receipt-out"] || "calllint-inbox-receipt.json";
|
|
6212
|
+
const absoluteReceiptPath = resolve6(deps.cwd, receiptOutPath);
|
|
6213
|
+
writeFileSync8(absoluteReceiptPath, JSON.stringify(receipt, null, 2) + "\n", "utf-8");
|
|
6214
|
+
return null;
|
|
6215
|
+
} catch (error) {
|
|
6216
|
+
const err2 = error;
|
|
6217
|
+
return `Error writing receipt: ${err2.message}`;
|
|
6218
|
+
}
|
|
6219
|
+
}
|
|
6220
|
+
function inboxHelp() {
|
|
6221
|
+
return `
|
|
6222
|
+
calllint inbox \u2014 Inspect normalized agent inbox events
|
|
6223
|
+
|
|
6224
|
+
USAGE
|
|
6225
|
+
calllint inbox inspect <normalized-event.json> [options]
|
|
6226
|
+
|
|
6227
|
+
DESCRIPTION
|
|
6228
|
+
Read a normalized agent inbox event (calllint.agent-inbox-event.v0),
|
|
6229
|
+
extract the optional action_candidate field, and analyze it using the
|
|
6230
|
+
R4 action analyzer. Returns SAFE/REVIEW/BLOCK/UNKNOWN verdict with
|
|
6231
|
+
evidence-backed findings.
|
|
6232
|
+
|
|
6233
|
+
This command is a composition layer: it does NOT fetch messages, poll
|
|
6234
|
+
providers, or implement OAuth. Caller provides pre-normalized events.
|
|
6235
|
+
|
|
6236
|
+
OPTIONS
|
|
6237
|
+
--receipt Write a receipt (calllint.receipt.v0) with subject.type="inbox"
|
|
6238
|
+
--receipt-out <path> Receipt output path (default: calllint-receipt.json)
|
|
6239
|
+
--policy <path> Custom policy file
|
|
6240
|
+
--json JSON output
|
|
6241
|
+
|
|
6242
|
+
EXIT CODES
|
|
6243
|
+
0 SAFE (no blockers observed)
|
|
6244
|
+
1 REVIEW / BLOCK / UNKNOWN
|
|
6245
|
+
2 Invalid input or schema violation
|
|
6246
|
+
|
|
6247
|
+
EXAMPLES
|
|
6248
|
+
# Inspect a normalized Gmail event with action candidate
|
|
6249
|
+
calllint inbox inspect gmail-reply.normalized.json
|
|
6250
|
+
|
|
6251
|
+
# Generate receipt for audit trail
|
|
6252
|
+
calllint inbox inspect resend-invoice.json --receipt
|
|
6253
|
+
|
|
6254
|
+
# JSON output for programmatic use
|
|
6255
|
+
calllint inbox inspect slack-mention.json --json
|
|
6256
|
+
|
|
6257
|
+
SEE ALSO
|
|
6258
|
+
docs/AGENT_INBOX_PREFLIGHT.md \u2014 Usage guide + worked examples
|
|
6259
|
+
docs/AGENT_INBOX_ADAPTER_CONTRACT.md \u2014 Provider \u2192 normalized transformation
|
|
6260
|
+
packages/fixtures/agent-inbox/ \u2014 6 providers \xD7 2 examples (reference)
|
|
6261
|
+
ADR 0031 \u2014 Agent Inbox Runtime design
|
|
6262
|
+
ADR 0030 \u2014 Agent Inbox Spec (v0.10.0 design)
|
|
6263
|
+
ADR 0029 \u2014 Unified External Action Preflight (R4)
|
|
6264
|
+
`;
|
|
6265
|
+
}
|
|
6266
|
+
|
|
5102
6267
|
// src/run.ts
|
|
5103
6268
|
function run(argv, deps) {
|
|
5104
6269
|
const args = parseArgs(argv);
|
|
@@ -5168,7 +6333,9 @@ function run(argv, deps) {
|
|
|
5168
6333
|
case "receipt":
|
|
5169
6334
|
return receiptCommand(args, { cwd: deps.cwd });
|
|
5170
6335
|
case "action":
|
|
5171
|
-
return actionCommand(args, { cwd: deps.cwd });
|
|
6336
|
+
return actionCommand(args, { cwd: deps.cwd, toolVersion: deps.toolVersion, generatedAt: deps.generatedAt });
|
|
6337
|
+
case "inbox":
|
|
6338
|
+
return inboxCommand(args, { cwd: deps.cwd, toolVersion: deps.toolVersion, generatedAt: deps.generatedAt });
|
|
5172
6339
|
case "gen-rule":
|
|
5173
6340
|
return genRuleCommand(args, { cwd: deps.cwd });
|
|
5174
6341
|
case "policy":
|
|
@@ -5442,13 +6609,13 @@ async function breathe(argv, deps = {}) {
|
|
|
5442
6609
|
}
|
|
5443
6610
|
|
|
5444
6611
|
// src/version.ts
|
|
5445
|
-
import { readFileSync as
|
|
6612
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
5446
6613
|
import { fileURLToPath } from "node:url";
|
|
5447
6614
|
import { dirname as dirname4, join as join11 } from "node:path";
|
|
5448
6615
|
function resolveToolVersion() {
|
|
5449
6616
|
try {
|
|
5450
6617
|
const pkgPath = join11(dirname4(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
5451
|
-
const pkg = JSON.parse(
|
|
6618
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
|
|
5452
6619
|
return typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : "unknown";
|
|
5453
6620
|
} catch {
|
|
5454
6621
|
return "unknown";
|
|
@@ -5458,7 +6625,7 @@ function resolveToolVersion() {
|
|
|
5458
6625
|
// src/index.ts
|
|
5459
6626
|
function readStdin() {
|
|
5460
6627
|
try {
|
|
5461
|
-
return
|
|
6628
|
+
return readFileSync12(0, "utf8");
|
|
5462
6629
|
} catch {
|
|
5463
6630
|
return "";
|
|
5464
6631
|
}
|
|
@@ -5479,8 +6646,8 @@ async function main() {
|
|
|
5479
6646
|
let clock;
|
|
5480
6647
|
try {
|
|
5481
6648
|
clock = resolveClock(argv, () => /* @__PURE__ */ new Date());
|
|
5482
|
-
} catch (
|
|
5483
|
-
process.stderr.write(`${
|
|
6649
|
+
} catch (err2) {
|
|
6650
|
+
process.stderr.write(`${err2 instanceof Error ? err2.message : String(err2)}
|
|
5484
6651
|
`);
|
|
5485
6652
|
process.exitCode = 2;
|
|
5486
6653
|
return;
|
|
@@ -5489,8 +6656,8 @@ async function main() {
|
|
|
5489
6656
|
let online;
|
|
5490
6657
|
try {
|
|
5491
6658
|
online = await computeOnlineEnrichment(argv, { fetchedAt: generatedAt });
|
|
5492
|
-
} catch (
|
|
5493
|
-
process.stderr.write(`--online failed: ${
|
|
6659
|
+
} catch (err2) {
|
|
6660
|
+
process.stderr.write(`--online failed: ${err2 instanceof Error ? err2.message : String(err2)}
|
|
5494
6661
|
`);
|
|
5495
6662
|
process.exitCode = 3;
|
|
5496
6663
|
return;
|
|
@@ -5515,3 +6682,8 @@ async function main() {
|
|
|
5515
6682
|
process.exitCode = result.exitCode;
|
|
5516
6683
|
}
|
|
5517
6684
|
void main();
|
|
6685
|
+
/*! Bundled license information:
|
|
6686
|
+
|
|
6687
|
+
@noble/ed25519/index.js:
|
|
6688
|
+
(*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) *)
|
|
6689
|
+
*/
|