@verax-ai/body 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/approve-cli.d.ts +22 -1
- package/dist/approve-cli.js +209 -22
- package/dist/auth.d.ts +9 -1
- package/dist/auth.js +12 -2
- package/dist/body-log.d.ts +14 -0
- package/dist/body-log.js +61 -0
- package/dist/cli.d.ts +23 -1
- package/dist/cli.js +227 -94
- package/dist/config.d.ts +26 -0
- package/dist/config.js +47 -0
- package/dist/demo.d.ts +2 -0
- package/dist/demo.js +8 -2
- package/dist/desktop-parent.d.ts +19 -0
- package/dist/desktop-parent.js +44 -0
- package/dist/desktop.d.ts +140 -11
- package/dist/desktop.js +931 -103
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +94 -11
- package/dist/health-extras.d.ts +4 -0
- package/dist/health-extras.js +14 -0
- package/dist/init-local.d.ts +25 -1
- package/dist/init-local.js +151 -54
- package/dist/install.d.ts +745 -0
- package/dist/install.js +5738 -0
- package/dist/keys.d.ts +6 -0
- package/dist/keys.js +43 -20
- package/dist/main.js +11 -1
- package/dist/no-bypass-scan.js +3 -2
- package/dist/reconcile-cli.js +3 -1
- package/dist/server.d.ts +14 -1
- package/dist/server.js +125 -3
- package/dist/tools/audit.d.ts +5 -2
- package/dist/tools/audit.js +12 -2
- package/dist/tools/memory.js +41 -6
- package/dist/tools/message.js +38 -9
- package/dist/unlock.d.ts +5 -2
- package/dist/unlock.js +6 -1
- package/dist/verify-cli.js +135 -15
- package/dist/wiring.js +1 -1
- package/dist/witness.js +154 -24
- package/package.json +7 -3
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
2
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import path, { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { runApprove } from "./approve-cli.js";
|
|
6
|
+
import { attachBodyLog } from "./body-log.js";
|
|
6
7
|
import { runDemo } from "./demo.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { doctorBodyLog, doctorExit, runDoctor } from "./doctor.js";
|
|
9
|
+
import { clearVeraxEnv, envFileJwksMissing, loadEnvFile, runInitLocal } from "./init-local.js";
|
|
10
|
+
import { clearCliCodeCheckPassed, defaultElevated, directoryAccess, doctorStateTarget, elevatedCommandCodeRefusal, linuxNodeLabelCheck, linuxSelinuxCheck, liveInstalledChecks, markCliCodeCheckPassed, runInstall, runUninstall, SystemToolError, unreadableSentence } from "./install.js";
|
|
10
11
|
import { runHalt } from "./halt.js";
|
|
11
12
|
import { main } from "./main.js";
|
|
12
13
|
import { runOperator } from "./operator-cli.js";
|
|
@@ -19,13 +20,31 @@ const HELP = `verax - the body an agent asks before it acts, and the ledger it a
|
|
|
19
20
|
Usage: verax <command> [options]
|
|
20
21
|
|
|
21
22
|
(no command) serve MCP over Streamable HTTP at /mcp on VERAX_BIND (default 127.0.0.1:8787)
|
|
22
|
-
serve [--env-file <file>]
|
|
23
|
+
serve [--env-file <file>] [--log-file <file>]
|
|
24
|
+
same as serving, after loading KEY=VALUE lines from that file.
|
|
25
|
+
--log-file appends this process's stdout and stderr
|
|
23
26
|
init --local <stateDir> [--force] [--days N] [--port N]
|
|
24
27
|
write a loopback key, one agent token, and verax.env
|
|
28
|
+
install [--port N] [--days N] [--force]
|
|
29
|
+
copy this body to an administrator-owned directory and run it
|
|
30
|
+
as another account. Needs an elevated shell, and this program
|
|
31
|
+
must live in a directory only an administrator can change.
|
|
32
|
+
On Windows the agent token is under %ProgramData%\\Verax\\agent-token\\<SID>\\.
|
|
33
|
+
On Linux and macOS a process running as the invoking user writes ~/.verax/agent.token.
|
|
34
|
+
uninstall [--keep-state]
|
|
35
|
+
stop that body and remove its code. Needs an elevated shell
|
|
36
|
+
and the same administrator-owned copy.
|
|
25
37
|
doctor [--json] check the configuration this process would run with
|
|
26
38
|
demo [--keep] run a loopback body against a temporary ledger and print what it recorded
|
|
27
39
|
[--with-conarium] also fetch Conarium with npx, attach it as a child, and put a masked read through the gate
|
|
28
|
-
approve <args> approve a waiting request from this machine
|
|
40
|
+
approve <args> approve a waiting request from this machine.
|
|
41
|
+
On Windows the panel with a passkey is how a held call is approved.
|
|
42
|
+
An elevated CLI approve is a fallback from a separate administrator
|
|
43
|
+
account, not this account elevated:
|
|
44
|
+
"%ProgramFiles%\\verax-cli\\verax.cmd" approve.
|
|
45
|
+
Linux and macOS the root-owned Node, for example
|
|
46
|
+
sudo /opt/verax-node/<dir>/bin/node /opt/verax-cli/lib/node_modules/@verax-ai/body/dist/cli.js approve
|
|
47
|
+
or sudo /usr/bin/node with that same cli.js.
|
|
29
48
|
operator <args> enrol an operator and manage their passkeys
|
|
30
49
|
reconcile <args> compare the ledger against a statement
|
|
31
50
|
verify <stateDir> read a ledger back without a body: signatures, chain,
|
|
@@ -33,7 +52,6 @@ Usage: verax <command> [options]
|
|
|
33
52
|
witness <stateDir> run the witness alongside a body
|
|
34
53
|
halt <stateDir> stop the body from allowing anything further
|
|
35
54
|
unlock [--force] <stateDir> clear a stale ledger lock
|
|
36
|
-
desktop <args> open the local panel, joining the body that holds the ledger if one is up
|
|
37
55
|
|
|
38
56
|
--help, -h print this
|
|
39
57
|
--version, -v print the version
|
|
@@ -57,100 +75,215 @@ function version() {
|
|
|
57
75
|
}
|
|
58
76
|
return "unknown";
|
|
59
77
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
if (argv[0] === "demo") {
|
|
73
|
-
process.exit(await runDemo(argv, process.env, {
|
|
74
|
-
stdout: process.stdout,
|
|
75
|
-
stderr: process.stderr,
|
|
76
|
-
stdin: process.stdin,
|
|
77
|
-
isTTY: Boolean(process.stdin.isTTY),
|
|
78
|
-
}));
|
|
79
|
-
}
|
|
80
|
-
if (argv[0] === "approve") {
|
|
81
|
-
process.exit(await runApprove(argv));
|
|
82
|
-
}
|
|
83
|
-
if (argv[0] === "operator") {
|
|
84
|
-
process.exit(await runOperator(argv));
|
|
85
|
-
}
|
|
86
|
-
if (argv[0] === "halt") {
|
|
87
|
-
const stateDir = argv[1];
|
|
88
|
-
if (!stateDir) {
|
|
89
|
-
process.stderr.write("verax halt <stateDir>\n");
|
|
90
|
-
process.exit(78);
|
|
78
|
+
function isCliEntry() {
|
|
79
|
+
const entry = process.argv[1];
|
|
80
|
+
if (!entry)
|
|
81
|
+
return false;
|
|
82
|
+
// npm starts the CLI through a symbolic link (`bin/verax`, `node_modules/.bin/verax`): compare
|
|
83
|
+
// real paths, or a linked start runs nothing and exits 0.
|
|
84
|
+
try {
|
|
85
|
+
return realpathSync(path.resolve(entry)) === realpathSync(fileURLToPath(import.meta.url));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
91
89
|
}
|
|
92
|
-
process.exit(runHalt(stateDir));
|
|
93
|
-
}
|
|
94
|
-
if (argv[0] === "unlock") {
|
|
95
|
-
const force = argv.includes("--force");
|
|
96
|
-
const stateDir = argv.slice(1).find((a) => a !== "--force");
|
|
97
|
-
if (!stateDir) {
|
|
98
|
-
process.stderr.write("verax unlock [--force] <stateDir>\n");
|
|
99
|
-
process.exit(78);
|
|
100
|
-
}
|
|
101
|
-
process.exit(runUnlock(stateDir, (s) => process.stderr.write(s), { force }));
|
|
102
90
|
}
|
|
103
|
-
|
|
104
|
-
|
|
91
|
+
/**
|
|
92
|
+
* One gate for every command except `--help` and `--version`. An elevated
|
|
93
|
+
* process refuses code the invoking user can change before the command runs.
|
|
94
|
+
* Install and approve keep their own check for direct callers; the flag stops
|
|
95
|
+
* a second check in this process.
|
|
96
|
+
*/
|
|
97
|
+
export async function runCli(argv, hooks = {}) {
|
|
98
|
+
const stdout = hooks.stdout ?? process.stdout;
|
|
99
|
+
const stderr = hooks.stderr ?? process.stderr;
|
|
100
|
+
const platform = hooks.platform ?? process.platform;
|
|
101
|
+
const env = hooks.env ?? process.env;
|
|
102
|
+
if (argv[0] === "--help" || argv[0] === "-h" || argv[0] === "help") {
|
|
103
|
+
stdout.write(HELP);
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
if (argv[0] === "--version" || argv[0] === "-v") {
|
|
107
|
+
stdout.write(`${version()}\n`);
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
let isElevated = false;
|
|
111
|
+
try {
|
|
112
|
+
isElevated = hooks.elevated ? hooks.elevated() : defaultElevated(platform);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
if (err instanceof SystemToolError) {
|
|
116
|
+
stderr.write(`${err.message}\n`);
|
|
117
|
+
return 78;
|
|
118
|
+
}
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
121
|
+
if (isElevated) {
|
|
122
|
+
const refusal = elevatedCommandCodeRefusal(platform, env, hooks.codeProbe, {
|
|
123
|
+
execPathProbe: hooks.execPathProbe,
|
|
124
|
+
execArgv: hooks.execArgv,
|
|
125
|
+
});
|
|
126
|
+
if (refusal) {
|
|
127
|
+
stderr.write(refusal.endsWith("\n") ? refusal : `${refusal}\n`);
|
|
128
|
+
return 78;
|
|
129
|
+
}
|
|
130
|
+
markCliCodeCheckPassed();
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
if (isElevated && argv[0] === "demo" && argv.includes("--with-conarium")) {
|
|
134
|
+
stderr.write("refusing: demo --with-conarium fetches and runs code with npx; run it from a terminal that is not elevated\n");
|
|
135
|
+
return 78;
|
|
136
|
+
}
|
|
137
|
+
return await dispatchCli(argv, stdout, stderr, env);
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
clearCliCodeCheckPassed();
|
|
141
|
+
}
|
|
105
142
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
process.
|
|
112
|
-
process.
|
|
143
|
+
async function dispatchCli(argv, stdout, stderr, env) {
|
|
144
|
+
if (argv[0] === "demo") {
|
|
145
|
+
return runDemo(argv, env, {
|
|
146
|
+
stdout,
|
|
147
|
+
stderr,
|
|
148
|
+
stdin: process.stdin,
|
|
149
|
+
isTTY: Boolean(process.stdin.isTTY),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (argv[0] === "approve") {
|
|
153
|
+
return runApprove(argv);
|
|
154
|
+
}
|
|
155
|
+
if (argv[0] === "operator") {
|
|
156
|
+
return runOperator(argv);
|
|
157
|
+
}
|
|
158
|
+
if (argv[0] === "halt") {
|
|
159
|
+
const stateDir = argv[1];
|
|
160
|
+
if (!stateDir) {
|
|
161
|
+
stderr.write("verax halt <stateDir>\n");
|
|
162
|
+
return 78;
|
|
163
|
+
}
|
|
164
|
+
return runHalt(stateDir, (s) => stderr.write(s));
|
|
165
|
+
}
|
|
166
|
+
if (argv[0] === "unlock") {
|
|
167
|
+
const force = argv.includes("--force");
|
|
168
|
+
const stateDir = argv.slice(1).find((a) => a !== "--force");
|
|
169
|
+
if (!stateDir) {
|
|
170
|
+
stderr.write("verax unlock [--force] <stateDir>\n");
|
|
171
|
+
return 78;
|
|
113
172
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
173
|
+
return runUnlock(stateDir, (s) => stderr.write(s), { force });
|
|
174
|
+
}
|
|
175
|
+
if (argv[0] === "init") {
|
|
176
|
+
return runInitLocal(argv.slice(1));
|
|
177
|
+
}
|
|
178
|
+
if (argv[0] === "install") {
|
|
179
|
+
return runInstall(argv);
|
|
180
|
+
}
|
|
181
|
+
if (argv[0] === "uninstall") {
|
|
182
|
+
return runUninstall(argv);
|
|
183
|
+
}
|
|
184
|
+
if (argv[0] === "serve") {
|
|
185
|
+
const logFlag = argv.indexOf("--log-file");
|
|
186
|
+
if (logFlag !== -1) {
|
|
187
|
+
const logPath = argv[logFlag + 1];
|
|
188
|
+
if (!logPath || logPath.startsWith("-")) {
|
|
189
|
+
stderr.write("verax serve --log-file <file>\n");
|
|
190
|
+
return 78;
|
|
191
|
+
}
|
|
192
|
+
const attached = attachBodyLog(logPath);
|
|
193
|
+
if (!attached.ok) {
|
|
194
|
+
stderr.write(`${attached.reason}\n`);
|
|
195
|
+
return 78;
|
|
196
|
+
}
|
|
197
|
+
stderr.write("verax serve starting\n");
|
|
198
|
+
}
|
|
199
|
+
const fileFlag = argv.indexOf("--env-file");
|
|
200
|
+
if (fileFlag !== -1) {
|
|
201
|
+
const file = argv[fileFlag + 1];
|
|
202
|
+
if (!file || file.startsWith("-")) {
|
|
203
|
+
stderr.write("verax serve --env-file <file>\n");
|
|
204
|
+
return 78;
|
|
205
|
+
}
|
|
206
|
+
clearVeraxEnv(process.env);
|
|
207
|
+
const loaded = loadEnvFile(file);
|
|
208
|
+
if (!loaded.ok) {
|
|
209
|
+
stderr.write(`${loaded.reason}\n`);
|
|
210
|
+
return 78;
|
|
211
|
+
}
|
|
212
|
+
const missingJwks = envFileJwksMissing(process.env);
|
|
213
|
+
if (missingJwks) {
|
|
214
|
+
stderr.write(`${missingJwks}\n`);
|
|
215
|
+
return 78;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
await main();
|
|
219
|
+
// main() returns once the server is listening. The open socket keeps the
|
|
220
|
+
// process up; this promise stops the rest of the dispatcher from running.
|
|
221
|
+
await new Promise(() => { });
|
|
222
|
+
}
|
|
223
|
+
if (argv[0] === "doctor") {
|
|
224
|
+
const json = argv.includes("--json");
|
|
225
|
+
const target = doctorStateTarget(env);
|
|
226
|
+
if (target && directoryAccess(target) === "unreadable") {
|
|
227
|
+
stderr.write(`${unreadableSentence(target, "doctor")}\n`);
|
|
228
|
+
return 77;
|
|
229
|
+
}
|
|
230
|
+
const nodeLabel = process.platform === "linux" ? linuxNodeLabelCheck() : null;
|
|
231
|
+
const checks = [
|
|
232
|
+
...runDoctor(env, process.argv),
|
|
233
|
+
...liveInstalledChecks(),
|
|
234
|
+
...(process.platform === "linux" ? [linuxSelinuxCheck()] : []),
|
|
235
|
+
...(nodeLabel ? [nodeLabel] : []),
|
|
236
|
+
];
|
|
237
|
+
const bodyLog = target ? doctorBodyLog(target) : "";
|
|
238
|
+
if (json) {
|
|
239
|
+
stdout.write(`${JSON.stringify(target ? { checks, bodyLog } : { checks })}\n`);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
for (const c of checks) {
|
|
243
|
+
stdout.write(`${c.level}\t${c.id}\t${c.detail}\n`);
|
|
244
|
+
}
|
|
245
|
+
if (bodyLog !== "")
|
|
246
|
+
stdout.write(bodyLog);
|
|
247
|
+
}
|
|
248
|
+
return doctorExit(checks);
|
|
249
|
+
}
|
|
250
|
+
if (argv[0] === "reconcile") {
|
|
251
|
+
return runReconcile(argv, (s) => stderr.write(s));
|
|
252
|
+
}
|
|
253
|
+
if (argv[0] === "verify") {
|
|
254
|
+
return runVerify(argv.slice(1), (s) => stdout.write(`${s}\n`));
|
|
255
|
+
}
|
|
256
|
+
if (argv[0] === "desktop") {
|
|
257
|
+
stderr.write("desktop-not-in-0.4.0: the desktop panel ships in 0.4.1\n");
|
|
258
|
+
return 64;
|
|
259
|
+
}
|
|
260
|
+
if (argv[0] === "witness") {
|
|
261
|
+
const stateDir = argv[1];
|
|
262
|
+
if (!stateDir) {
|
|
263
|
+
stderr.write("verax witness <stateDir>\n");
|
|
264
|
+
return 78;
|
|
118
265
|
}
|
|
266
|
+
await runWitness(stateDir);
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
const head = argv[0];
|
|
270
|
+
if (head !== undefined && !head.startsWith("--")) {
|
|
271
|
+
stderr.write(`unknown command: ${head}\n`);
|
|
272
|
+
stderr.write(HELP);
|
|
273
|
+
return 64;
|
|
274
|
+
}
|
|
275
|
+
// `main()` reads `--inventory`. Any other leading flag is not a serve option.
|
|
276
|
+
if (head !== undefined && head.startsWith("--") && head !== "--inventory") {
|
|
277
|
+
stderr.write(`unknown option: ${head}\n`);
|
|
278
|
+
stderr.write(HELP);
|
|
279
|
+
return 64;
|
|
119
280
|
}
|
|
120
281
|
await main();
|
|
121
282
|
// main() returns once the server is listening. The open socket keeps the
|
|
122
|
-
// process up
|
|
283
|
+
// process up, the same way as `serve`.
|
|
123
284
|
await new Promise(() => { });
|
|
285
|
+
return 0;
|
|
124
286
|
}
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
const checks = runDoctor(process.env, process.argv);
|
|
128
|
-
if (json) {
|
|
129
|
-
process.stdout.write(`${JSON.stringify({ checks })}\n`);
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
for (const c of checks) {
|
|
133
|
-
process.stdout.write(`${c.level}\t${c.id}\t${c.detail}\n`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
process.exit(doctorExit(checks));
|
|
137
|
-
}
|
|
138
|
-
if (argv[0] === "reconcile") {
|
|
139
|
-
process.exit(runReconcile(argv));
|
|
140
|
-
}
|
|
141
|
-
if (argv[0] === "verify") {
|
|
142
|
-
process.exit(await runVerify(argv.slice(1)));
|
|
143
|
-
}
|
|
144
|
-
if (argv[0] === "desktop") {
|
|
145
|
-
process.exit(await desktopMain(argv));
|
|
146
|
-
}
|
|
147
|
-
if (argv[0] === "witness") {
|
|
148
|
-
const stateDir = argv[1];
|
|
149
|
-
if (!stateDir) {
|
|
150
|
-
process.stderr.write("verax witness <stateDir>\n");
|
|
151
|
-
process.exit(78);
|
|
152
|
-
}
|
|
153
|
-
await runWitness(stateDir);
|
|
154
|
-
process.exit(0);
|
|
287
|
+
if (isCliEntry()) {
|
|
288
|
+
process.exit(await runCli(process.argv.slice(2)));
|
|
155
289
|
}
|
|
156
|
-
await main();
|
package/dist/config.d.ts
CHANGED
|
@@ -13,6 +13,14 @@ export type BodyConfig = {
|
|
|
13
13
|
* Loopback binds only.
|
|
14
14
|
*/
|
|
15
15
|
jwksFile?: string | null;
|
|
16
|
+
/**
|
|
17
|
+
* JWKS captured once at desktop start. Verification uses this set and does
|
|
18
|
+
* not refetch `jwksUrl`. Null when the body should use the remote set.
|
|
19
|
+
* Not a substitute for `jwksFile` (that mode refuses operator scopes).
|
|
20
|
+
*/
|
|
21
|
+
jwksPin?: {
|
|
22
|
+
keys: Record<string, unknown>[];
|
|
23
|
+
} | null;
|
|
16
24
|
/** Path to an inventory JSON file. Missing file is absence, not a fault. */
|
|
17
25
|
inventoryFile?: string | null;
|
|
18
26
|
/**
|
|
@@ -22,7 +30,25 @@ export type BodyConfig = {
|
|
|
22
30
|
* no downstream; a named file that cannot be attached stops the body.
|
|
23
31
|
*/
|
|
24
32
|
downstreamFile?: string | null;
|
|
33
|
+
/** Origins allowed to call this HTTP server. Empty means no browser origin. */
|
|
34
|
+
allowedOrigins?: readonly string[];
|
|
35
|
+
/**
|
|
36
|
+
* Bytes one tenant may keep under `memory/`, summed across stored versions.
|
|
37
|
+
* Default is 1 MiB. `VERAX_MEMORY_QUOTA_BYTES` overrides it.
|
|
38
|
+
*/
|
|
39
|
+
memoryQuotaBytes?: number;
|
|
40
|
+
/**
|
|
41
|
+
* Bytes one tenant may append to `outbox.jsonl`.
|
|
42
|
+
* Default is 1 MiB. `VERAX_MESSAGE_QUOTA_BYTES` overrides it.
|
|
43
|
+
*/
|
|
44
|
+
messageQuotaBytes?: number;
|
|
25
45
|
};
|
|
46
|
+
/** Per-tenant `memory.put` cap. The HTTP body cap does not reset this total. */
|
|
47
|
+
export declare const DEFAULT_MEMORY_QUOTA_BYTES: number;
|
|
48
|
+
/** Per-tenant `message.send` cap. The HTTP body cap does not reset this total. */
|
|
49
|
+
export declare const DEFAULT_MESSAGE_QUOTA_BYTES: number;
|
|
50
|
+
export declare function memoryQuotaBytes(env?: NodeJS.ProcessEnv): number;
|
|
51
|
+
export declare function messageQuotaBytes(env?: NodeJS.ProcessEnv): number;
|
|
26
52
|
export type ConfigResult = {
|
|
27
53
|
ok: true;
|
|
28
54
|
value: BodyConfig;
|
package/dist/config.js
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
export const EX_CONFIG = 78;
|
|
3
|
+
/** Per-tenant `memory.put` cap. The HTTP body cap does not reset this total. */
|
|
4
|
+
export const DEFAULT_MEMORY_QUOTA_BYTES = 1024 * 1024;
|
|
5
|
+
/** Per-tenant `message.send` cap. The HTTP body cap does not reset this total. */
|
|
6
|
+
export const DEFAULT_MESSAGE_QUOTA_BYTES = 1024 * 1024;
|
|
7
|
+
function positiveIntQuota(raw, fallback) {
|
|
8
|
+
const text = raw?.trim() ?? "";
|
|
9
|
+
if (text === "")
|
|
10
|
+
return fallback;
|
|
11
|
+
const n = Number(text);
|
|
12
|
+
if (!Number.isInteger(n) || n <= 0)
|
|
13
|
+
return fallback;
|
|
14
|
+
return n;
|
|
15
|
+
}
|
|
16
|
+
export function memoryQuotaBytes(env = process.env) {
|
|
17
|
+
return positiveIntQuota(env.VERAX_MEMORY_QUOTA_BYTES, DEFAULT_MEMORY_QUOTA_BYTES);
|
|
18
|
+
}
|
|
19
|
+
export function messageQuotaBytes(env = process.env) {
|
|
20
|
+
return positiveIntQuota(env.VERAX_MESSAGE_QUOTA_BYTES, DEFAULT_MESSAGE_QUOTA_BYTES);
|
|
21
|
+
}
|
|
3
22
|
const LOOPBACK = new Set(["127.0.0.1", "::1", "localhost"]);
|
|
4
23
|
function parseBind(raw) {
|
|
5
24
|
const text = raw && raw.trim() !== "" ? raw.trim() : "127.0.0.1:8787";
|
|
@@ -15,6 +34,19 @@ export function isLoopbackHost(host) {
|
|
|
15
34
|
const stripped = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
|
16
35
|
return LOOPBACK.has(stripped);
|
|
17
36
|
}
|
|
37
|
+
function jwksPinParses(raw) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(raw);
|
|
40
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.keys) || parsed.keys.length === 0)
|
|
41
|
+
return null;
|
|
42
|
+
if (!parsed.keys.every((key) => key !== null && typeof key === "object" && !Array.isArray(key)))
|
|
43
|
+
return null;
|
|
44
|
+
return { keys: parsed.keys };
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
18
50
|
function jwksFileParses(path) {
|
|
19
51
|
try {
|
|
20
52
|
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
@@ -28,7 +60,15 @@ export function loadConfig(env) {
|
|
|
28
60
|
const issuer = env.VERAX_ISSUER?.trim() ?? "";
|
|
29
61
|
const jwksUrl = env.VERAX_JWKS_URL?.trim() ?? "";
|
|
30
62
|
const jwksFile = env.VERAX_JWKS_FILE?.trim() ?? "";
|
|
63
|
+
const jwksPinRaw = env.VERAX_JWKS_PIN?.trim() ?? "";
|
|
31
64
|
const audience = env.VERAX_AUDIENCE?.trim() ?? "";
|
|
65
|
+
let jwksPin = null;
|
|
66
|
+
if (jwksPinRaw !== "") {
|
|
67
|
+
jwksPin = jwksPinParses(jwksPinRaw);
|
|
68
|
+
if (!jwksPin) {
|
|
69
|
+
return { ok: false, code: EX_CONFIG, reason: "VERAX_JWKS_PIN is unparsable" };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
32
72
|
if (jwksUrl !== "" && jwksFile !== "") {
|
|
33
73
|
return {
|
|
34
74
|
ok: false,
|
|
@@ -82,6 +122,7 @@ export function loadConfig(env) {
|
|
|
82
122
|
issuer,
|
|
83
123
|
jwksUrl,
|
|
84
124
|
jwksFile: jwksFile === "" ? null : jwksFile,
|
|
125
|
+
jwksPin,
|
|
85
126
|
audience,
|
|
86
127
|
stateDir,
|
|
87
128
|
bindHost: bind.host,
|
|
@@ -90,6 +131,12 @@ export function loadConfig(env) {
|
|
|
90
131
|
tlsTerminated,
|
|
91
132
|
inventoryFile: inventoryRaw === "" ? null : inventoryRaw,
|
|
92
133
|
downstreamFile: downstreamRaw === "" ? null : downstreamRaw,
|
|
134
|
+
allowedOrigins: (env.VERAX_ALLOWED_ORIGINS ?? "")
|
|
135
|
+
.split(",")
|
|
136
|
+
.map((item) => item.trim())
|
|
137
|
+
.filter((item) => item !== ""),
|
|
138
|
+
memoryQuotaBytes: memoryQuotaBytes(env),
|
|
139
|
+
messageQuotaBytes: messageQuotaBytes(env),
|
|
93
140
|
},
|
|
94
141
|
};
|
|
95
142
|
}
|
package/dist/demo.d.ts
CHANGED
|
@@ -14,5 +14,7 @@ export type DemoOpts = {
|
|
|
14
14
|
command: string;
|
|
15
15
|
args: string[];
|
|
16
16
|
};
|
|
17
|
+
/** mkdtemp prefix. Tests pass a unique one so parallel files do not share verax-demo-. */
|
|
18
|
+
statePrefix?: string;
|
|
17
19
|
};
|
|
18
20
|
export declare function runDemo(argv: string[], env: NodeJS.ProcessEnv, io: DemoIo, opts?: DemoOpts): Promise<number>;
|
package/dist/demo.js
CHANGED
|
@@ -229,6 +229,8 @@ async function readAnswer(stdin) {
|
|
|
229
229
|
resolve(value);
|
|
230
230
|
};
|
|
231
231
|
const onData = (chunk) => {
|
|
232
|
+
if (typeof stdin.pause === "function")
|
|
233
|
+
stdin.pause();
|
|
232
234
|
done(String(chunk).split(/\r?\n/)[0] ?? "");
|
|
233
235
|
};
|
|
234
236
|
const onEnd = () => done("");
|
|
@@ -289,7 +291,8 @@ export async function runDemo(argv, env, io, opts) {
|
|
|
289
291
|
process.once("SIGINT", onStop);
|
|
290
292
|
process.once("SIGTERM", onStop);
|
|
291
293
|
try {
|
|
292
|
-
|
|
294
|
+
const statePrefix = opts?.statePrefix && opts.statePrefix.length > 0 ? opts.statePrefix : "verax-demo-";
|
|
295
|
+
stateDir = mkdtempSync(join(tmpdir(), statePrefix));
|
|
293
296
|
const policyFile = writeDemoPolicy(stateDir, withConarium);
|
|
294
297
|
const { privateKey, publicKey } = await generateKeyPair("ES256");
|
|
295
298
|
const jwk = { ...(await exportJWK(publicKey)), alg: "ES256", use: "sig", kid: KID };
|
|
@@ -369,7 +372,10 @@ export async function runDemo(argv, env, io, opts) {
|
|
|
369
372
|
writeOut("Approve this payment as the operator on this machine? [y/N]\n");
|
|
370
373
|
const answer = await readAnswer(io.stdin);
|
|
371
374
|
if (answer.trim().toLowerCase() === "y") {
|
|
372
|
-
const code = await runApprove(["approve", stateDir, held.ref],
|
|
375
|
+
const code = await runApprove(["approve", stateDir, held.ref], writeErr, () => undefined, {
|
|
376
|
+
isTTY: io.isTTY,
|
|
377
|
+
ask: async () => readAnswer(io.stdin),
|
|
378
|
+
}, { elevated: () => false });
|
|
373
379
|
if (code !== 0)
|
|
374
380
|
throw new Error("demo: approve failed");
|
|
375
381
|
approved = true;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Env the desktop launcher sets on the issuer and the body. Absent outside that launch. */
|
|
2
|
+
export declare const DESKTOP_PARENT_PID = "VERAX_DESKTOP_PARENT_PID";
|
|
3
|
+
export type DesktopParentWatch = {
|
|
4
|
+
env?: NodeJS.ProcessEnv;
|
|
5
|
+
/** True when `pid` is still a process. Default is `process.kill(pid, 0)`. */
|
|
6
|
+
pidAlive?: (pid: number) => boolean;
|
|
7
|
+
exit?: (code: number) => void;
|
|
8
|
+
schedule?: (fn: () => void, ms: number) => {
|
|
9
|
+
unref?: () => void;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
/** `process.kill(pid, 0)` — EPERM means the process exists. */
|
|
13
|
+
export declare function desktopParentAlive(pid: number): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* When `VERAX_DESKTOP_PARENT_PID` is set, exit once that pid is gone.
|
|
16
|
+
* Polls every 2s. No-op when the variable is unset, so a normal body or issuer
|
|
17
|
+
* start does not watch anything.
|
|
18
|
+
*/
|
|
19
|
+
export declare function watchDesktopParent(hooks?: DesktopParentWatch): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Env the desktop launcher sets on the issuer and the body. Absent outside that launch. */
|
|
2
|
+
export const DESKTOP_PARENT_PID = "VERAX_DESKTOP_PARENT_PID";
|
|
3
|
+
/** `process.kill(pid, 0)` — EPERM means the process exists. */
|
|
4
|
+
export function desktopParentAlive(pid) {
|
|
5
|
+
try {
|
|
6
|
+
process.kill(pid, 0);
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
return err.code === "EPERM";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* When `VERAX_DESKTOP_PARENT_PID` is set, exit once that pid is gone.
|
|
15
|
+
* Polls every 2s. No-op when the variable is unset, so a normal body or issuer
|
|
16
|
+
* start does not watch anything.
|
|
17
|
+
*/
|
|
18
|
+
export function watchDesktopParent(hooks = {}) {
|
|
19
|
+
const env = hooks.env ?? process.env;
|
|
20
|
+
const raw = env[DESKTOP_PARENT_PID]?.trim() ?? "";
|
|
21
|
+
if (raw === "")
|
|
22
|
+
return;
|
|
23
|
+
const exit = hooks.exit ?? ((code) => process.exit(code));
|
|
24
|
+
const pid = Number(raw);
|
|
25
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
26
|
+
exit(1);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const alive = hooks.pidAlive ?? desktopParentAlive;
|
|
30
|
+
if (!alive(pid)) {
|
|
31
|
+
exit(1);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const schedule = hooks.schedule ??
|
|
35
|
+
((fn, ms) => {
|
|
36
|
+
const timer = setInterval(fn, ms);
|
|
37
|
+
timer.unref();
|
|
38
|
+
return timer;
|
|
39
|
+
});
|
|
40
|
+
schedule(() => {
|
|
41
|
+
if (!alive(pid))
|
|
42
|
+
exit(1);
|
|
43
|
+
}, 2_000);
|
|
44
|
+
}
|