@varnir/agent-server 0.1.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.
Files changed (55) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +282 -0
  3. package/dist/bearer.d.ts +5 -0
  4. package/dist/bearer.d.ts.map +1 -0
  5. package/dist/bearer.js +12 -0
  6. package/dist/bearer.js.map +1 -0
  7. package/dist/config.d.ts +36 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +151 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/guard.d.ts +28 -0
  12. package/dist/guard.d.ts.map +1 -0
  13. package/dist/guard.js +56 -0
  14. package/dist/guard.js.map +1 -0
  15. package/dist/index.d.ts +3 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +79 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/key-match.d.ts +2 -0
  20. package/dist/key-match.d.ts.map +1 -0
  21. package/dist/key-match.js +33 -0
  22. package/dist/key-match.js.map +1 -0
  23. package/dist/log.d.ts +8 -0
  24. package/dist/log.d.ts.map +1 -0
  25. package/dist/log.js +30 -0
  26. package/dist/log.js.map +1 -0
  27. package/dist/mcp-http.d.ts +58 -0
  28. package/dist/mcp-http.d.ts.map +1 -0
  29. package/dist/mcp-http.js +121 -0
  30. package/dist/mcp-http.js.map +1 -0
  31. package/dist/mcp-stdio.d.ts +11 -0
  32. package/dist/mcp-stdio.d.ts.map +1 -0
  33. package/dist/mcp-stdio.js +14 -0
  34. package/dist/mcp-stdio.js.map +1 -0
  35. package/dist/mcp.d.ts +10 -0
  36. package/dist/mcp.d.ts.map +1 -0
  37. package/dist/mcp.js +18 -0
  38. package/dist/mcp.js.map +1 -0
  39. package/dist/rest-methods.d.ts +31 -0
  40. package/dist/rest-methods.d.ts.map +1 -0
  41. package/dist/rest-methods.js +66 -0
  42. package/dist/rest-methods.js.map +1 -0
  43. package/dist/rest.d.ts +17 -0
  44. package/dist/rest.d.ts.map +1 -0
  45. package/dist/rest.js +84 -0
  46. package/dist/rest.js.map +1 -0
  47. package/dist/tool-error.d.ts +31 -0
  48. package/dist/tool-error.d.ts.map +1 -0
  49. package/dist/tool-error.js +66 -0
  50. package/dist/tool-error.js.map +1 -0
  51. package/dist/tools.d.ts +31 -0
  52. package/dist/tools.d.ts.map +1 -0
  53. package/dist/tools.js +223 -0
  54. package/dist/tools.js.map +1 -0
  55. package/package.json +47 -0
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License (MIT)
2
+ Copyright (c) 2026 Adam Walker
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,282 @@
1
+ # @varnir/agent-server
2
+
3
+ A self-hosted server that lets your own AI assistant (Claude, Gemini, ChatGPT,
4
+ or anything else that speaks MCP or plain HTTP) trade and move funds on
5
+ Varnir, bounded by an on-ledger spending policy attached to the key you give
6
+ it. Varnir never holds the key - it lives in your environment, on your host,
7
+ under your control.
8
+
9
+ It exposes thirteen tools over MCP - seven reads (identity, balances, tokens,
10
+ the order book, transaction history, a transfer's settlement receipt, the
11
+ key's own spend policy) and six writes (post an order, fill an order, place a
12
+ market order, cancel an order, send to an L1 address, transfer to another
13
+ Varnir identity on L2). The same
14
+ operations are also reachable over a plain REST API, off by default - see
15
+ "The REST SecureAPI" below. Writes are refused outright unless the key this
16
+ server signs with actually carries a spend policy; see "Before you start".
17
+
18
+ ## Before you start
19
+
20
+ **Mint a policy-scoped agent key in the scanner's keys card, with the spend
21
+ policy you want the assistant bound by, before you put anything in
22
+ `VARNIR_PRIVATE_KEY`.**
23
+
24
+ Do not hand this server your identity's owner key. It will not do what you
25
+ expect: at boot, the server reads its own key back off the ledger and checks
26
+ what it actually is before allowing any write. If that key turns out to be
27
+
28
+ - the identity's **owner** key (no spend policy exists to bound it),
29
+ - a key with **no spend policy** attached, or
30
+ - a key with the **`readonly`** or **`offledger`** role (the money contracts
31
+ refuse these write access regardless of any policy attached to them),
32
+
33
+ the server starts anyway, but **read-only**: every write tool and every write
34
+ REST method is refused before it ever reaches the ledger, with a message
35
+ explaining why. This is deliberate - the entire security argument for this
36
+ server is that the on-ledger policy is the hard bound on what a connected
37
+ assistant can spend, and that argument only holds when the key actually
38
+ carries one. There is an escape hatch (`VARNIR_ALLOW_UNSCOPED_KEY=i-understand`
39
+ in `.env.example`) for operators who understand they are removing that bound;
40
+ do not reach for it to make an error message go away.
41
+
42
+ Once you have the key, set `VARNIR_PRIVATE_KEY`, `VARNIR_IDENTITY`, and
43
+ `VARNIR_NETWORK` (e.g. `testnet`).
44
+
45
+ ## Install and run
46
+
47
+ Run these from the repo root:
48
+
49
+ ```bash
50
+ pnpm install
51
+ pnpm --filter @varnir/agent-server build
52
+ ```
53
+
54
+ Then, with the environment set (see `.env.example`), still from the repo root:
55
+
56
+ ```bash
57
+ node apps/agent-server/dist/index.js
58
+ ```
59
+
60
+ The server logs which mode it started in (`writes`, `read-only`, or it exits
61
+ with `refuse` if the key isn't even attached to `VARNIR_IDENTITY`) before it
62
+ starts serving anything.
63
+
64
+ ## Deploying to your own cloud account
65
+
66
+ You can also run this server in your own Google Cloud, Azure, or AWS
67
+ account instead of on a local machine - it is still your account and your
68
+ key either way; Varnir runs none of this infrastructure and never sees the
69
+ key. See `deploy/README.md` for one-click deploy buttons, the ARM/
70
+ CloudFormation templates, and `Dockerfile` for the container image (built
71
+ from the published npm package, not this repo's source).
72
+
73
+ ## Connecting a client
74
+
75
+ By default the server speaks MCP over stdio (`VARNIR_MCP_STDIO=on`), which is
76
+ how a client that can launch a local process connects. Copy the block for
77
+ your client and fill in the real values - `VARNIR_PRIVATE_KEY` and
78
+ `VARNIR_IDENTITY` particularly should come from wherever you actually keep
79
+ them, not pasted in plaintext if you can avoid it. Replace
80
+ `/absolute/path/to/apps/agent-server/dist/index.js` with the real absolute
81
+ path to the built entry point on your machine (the package's `bin` name is
82
+ `varnir-agent-server` if you install it globally instead of pointing at
83
+ `dist/index.js` directly).
84
+
85
+ ### Claude Code
86
+
87
+ ```bash
88
+ claude mcp add --transport stdio varnir \
89
+ --env VARNIR_PRIVATE_KEY=... --env VARNIR_IDENTITY=... --env VARNIR_NETWORK=testnet \
90
+ -- node /absolute/path/to/apps/agent-server/dist/index.js
91
+ ```
92
+
93
+ ### Claude Desktop
94
+
95
+ Add to `claude_desktop_config.json`:
96
+
97
+ ```json
98
+ {
99
+ "mcpServers": {
100
+ "varnir": {
101
+ "command": "node",
102
+ "args": ["/absolute/path/to/apps/agent-server/dist/index.js"],
103
+ "env": {
104
+ "VARNIR_PRIVATE_KEY": "...",
105
+ "VARNIR_IDENTITY": "...",
106
+ "VARNIR_NETWORK": "testnet"
107
+ }
108
+ }
109
+ }
110
+ }
111
+ ```
112
+
113
+ ### Gemini CLI
114
+
115
+ This recipe substitutes `$VARNIR_PRIVATE_KEY` and `$VARNIR_IDENTITY` from the
116
+ shell environment, so `export` both in the shell you launch `gemini` from
117
+ first - an unset variable resolves to an empty string here rather than
118
+ erroring, which just pushes the failure to server boot instead of catching it
119
+ here.
120
+
121
+ Add to `~/.gemini/settings.json`:
122
+
123
+ ```json
124
+ {
125
+ "mcpServers": {
126
+ "varnir": {
127
+ "command": "node",
128
+ "args": ["/absolute/path/to/apps/agent-server/dist/index.js"],
129
+ "env": {
130
+ "VARNIR_PRIVATE_KEY": "$VARNIR_PRIVATE_KEY",
131
+ "VARNIR_IDENTITY": "$VARNIR_IDENTITY",
132
+ "VARNIR_NETWORK": "testnet"
133
+ },
134
+ "timeout": 30000
135
+ }
136
+ }
137
+ }
138
+ ```
139
+
140
+ ### ChatGPT (Streamable HTTP, over a tunnel)
141
+
142
+ ChatGPT cannot launch a local process - it needs a publicly reachable HTTPS
143
+ URL, so this recipe uses the MCP HTTP transport (`POST /mcp`) behind a tunnel
144
+ instead of stdio. `VARNIR_MCP_HTTP_TOKEN` (at least 32 characters) is now
145
+ **mandatory whenever `VARNIR_MCP_HTTP=on`, even on loopback** - without it,
146
+ any other local user or process on the same host could reach the full write
147
+ set with no authentication at all. A remote bind additionally requires
148
+ `VARNIR_MCP_HTTP_ALLOW_REMOTE=i-understand` - `cloudflared` puts you on a
149
+ public hostname even though the process itself still listens on loopback, so
150
+ in practice you need the token either way. Without the token set, the server
151
+ refuses to start with `VARNIR_MCP_HTTP=on` at all.
152
+
153
+ Run this from the repo root, same as "Install and run" above:
154
+
155
+ ```bash
156
+ VARNIR_PRIVATE_KEY=... VARNIR_IDENTITY=... VARNIR_NETWORK=testnet \
157
+ VARNIR_MCP_HTTP=on VARNIR_MCP_HTTP_TOKEN=<a-random-string-32-chars-or-longer> \
158
+ VARNIR_MCP_HTTP_ALLOW_REMOTE=i-understand \
159
+ node apps/agent-server/dist/index.js
160
+ ```
161
+
162
+ Expose it:
163
+
164
+ ```bash
165
+ cloudflared tunnel --url http://localhost:8787
166
+ ```
167
+
168
+ Then in ChatGPT: Settings -> Connectors -> Advanced -> Developer mode -> Add
169
+ custom connector, URL `<tunnel-url>/mcp`, bearer token
170
+ `<the token you set above>`.
171
+
172
+ ## The REST SecureAPI
173
+
174
+ The SDK's methods are also reachable over plain HTTP, for integrating
175
+ software that can only call HTTP endpoints rather than speak MCP. It is off
176
+ by default (`VARNIR_HTTP_API=off`) and every gate below is enforced in
177
+ `src/config.ts` before the process will even start with it on.
178
+
179
+ | Variable | Default | Required | Notes |
180
+ |---|---|---|---|
181
+ | `VARNIR_HTTP_API` | off | No | Enable with `on`/`true`/`1` |
182
+ | `VARNIR_HTTP_API_TOKEN` | - | Yes, when enabled | No default, minimum 32 characters |
183
+ | `VARNIR_HTTP_API_HOST` | `127.0.0.1` | No | Loopback by default |
184
+ | `VARNIR_HTTP_API_PORT` | 8788 | No | Integer [1, 65535] |
185
+ | `VARNIR_HTTP_API_WRITES` | off | No | Reads only until set `on` |
186
+ | `VARNIR_HTTP_API_ALLOW_REMOTE` | - | Conditional | Exactly `i-understand`; required for a non-loopback host |
187
+
188
+ **Turning this on with writes enabled and a non-loopback bind puts a
189
+ money-moving endpoint on the network.** Anyone who can reach that host and
190
+ port and holds the bearer token can move funds within whatever the signing
191
+ key's on-ledger policy allows - the policy still bounds them, but the network
192
+ exposure itself is real and irreversible the moment someone finds the port.
193
+ Keep it on loopback with writes off unless you have a specific, considered
194
+ reason to do otherwise, and prefer a tunnel with its own auth in front of it
195
+ over binding a public interface directly.
196
+
197
+ Every write method is also refused if the server's own key came up
198
+ read-only at boot (see "Before you start"), on top of the `VARNIR_HTTP_API_WRITES`
199
+ gate - two independent checks, not one.
200
+
201
+ One method needs calling out specifically: **`getWallet` is classified as a
202
+ write**, even though the name and its use elsewhere as a read look
203
+ innocuous. When this identity has no wallet yet on a given chain, `getWallet`
204
+ falls through to claiming a pregenerated L1 wallet from the pool and signing
205
+ a `Varnir.AssignOwner` transaction to establish ownership - a real
206
+ ledger-mutating write, on its first call for that chain. `send` calls
207
+ `getWallet` internally for the same reason. Both are refused with
208
+ `VARNIR_HTTP_API_WRITES=off` or in read-only mode, exactly like any other
209
+ write.
210
+
211
+ Example, once you have `VARNIR_HTTP_API=on` and a token set:
212
+
213
+ ```bash
214
+ curl -s -X POST http://127.0.0.1:8788/v1/getBalances \
215
+ -H "Authorization: Bearer $VARNIR_HTTP_API_TOKEN" \
216
+ -H "Content-Type: application/json" \
217
+ -d '{}'
218
+ ```
219
+
220
+ `GET /v1/methods` (also bearer-authenticated) lists every method the server
221
+ will accept and whether it is a write. `GET /v1/health` is unauthenticated and
222
+ carries no identity or key data - just whether the process is up and whether
223
+ it is running read-only.
224
+
225
+ ## Security
226
+
227
+ The signing key lives in your environment - your host is the trust boundary,
228
+ not Varnir. Whoever can read that environment (or the process's memory) can
229
+ sign whatever the key's on-ledger policy allows. The on-ledger spending
230
+ policy - per-transaction ceiling, rolling-period cap, recipient allowlist,
231
+ co-sign threshold - is the real bound on a connected assistant, not this
232
+ server; this server is defence in depth around that bound (see `src/guard.ts`
233
+ for exactly what it checks and why). To revoke access, remove the key from
234
+ the identity in the scanner - that is the authoritative shutoff, independent
235
+ of whether this process is still running.
236
+
237
+ Reads are ungated by design: `get_balances`, `list_transactions`,
238
+ `get_identity`, and the equivalent REST methods, do not check the guard mode
239
+ and are always answerable once the server is running at all. Anything that
240
+ can talk to this server - over stdio, MCP HTTP, or the REST API - can see
241
+ your balances and transaction history, whether or not it can move anything.
242
+ Do not treat "read-only mode" as "safe to expose"; treat it as "cannot sign."
243
+
244
+ ## What it will not do
245
+
246
+ - **No key management on any surface.** There is no tool and no REST method
247
+ to add a key, remove a key, change an approval threshold, or register or
248
+ revoke an API key - `addKey`, `removeKey`, `setApprovalThreshold`,
249
+ `registerApiKey` and `revokeApiKey` are absent from `REST_METHODS` on
250
+ purpose and must stay absent.
251
+ - **One deliberate, narrower exception:** a `verifier` or `dual` key (both
252
+ stake 80) that has NOT itself raised a transfer proposal can still hit the
253
+ ledger's co-sign band on that proposal - the contract treats that as a
254
+ genuine co-sign confirmation and releases it without re-checking that
255
+ key's own spend policy, because the policy was already checked when the
256
+ proposal was raised. `confirmTransfer` and `listPendingApprovals` would let
257
+ a connected assistant enumerate and release someone else's held proposal at
258
+ the proposal's amount, ignoring its own per-tx ceiling, period cap and
259
+ allowlist - the one way this server's design lets a key widen its own
260
+ effective authority. Both are absent from `REST_METHODS` for exactly this
261
+ reason and must stay absent; neither has an MCP tool, so nothing regresses.
262
+ Aside from this, nothing reachable from a connected assistant can widen
263
+ that assistant's own authority.
264
+ - **No remaining-allowance figure.** `get_spend_policy` reports the
265
+ *configured limits* on this key - per-transaction ceiling, rolling-period
266
+ cap, allowlist, co-sign threshold - not how much of the period's cap is
267
+ left. Remaining allowance against a rolling-period cap is not readable
268
+ through the SDK yet.
269
+ - **`get_transfer` cannot tell "still settling" apart from "held for a
270
+ co-sign that will never settle on its own"**, given only a umid. That
271
+ distinction genuinely exists only in `send`'s/`transfer_on_ledger`'s own
272
+ return at submission time (`awaiting_cosign` plus the `proposal`) - looking
273
+ the umid up afterwards, which is all `get_transfer` does, cannot recover it.
274
+ Telling them apart from a umid alone would need reading this identity's
275
+ pending-approvals list, which is deliberately not wired into any MCP tool
276
+ (see the point above) - so `get_transfer` says so plainly rather than
277
+ guessing, and a `not_settled` result should be read as "no receipt yet,"
278
+ not "still settling."
279
+ - **No writes behind an owner, policy-less, `readonly` or `offledger` key.**
280
+ The server starts read-only in all four cases rather than silently running
281
+ with unbounded or contract-refused authority; see "Before you start" for
282
+ what each one means and how to fix it.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Timing-safe bearer token comparison to prevent timing-based token guessing attacks.
3
+ */
4
+ export declare function tokenMatches(presented: string, expected: string): boolean;
5
+ //# sourceMappingURL=bearer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bearer.d.ts","sourceRoot":"","sources":["../src/bearer.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAKzE"}
package/dist/bearer.js ADDED
@@ -0,0 +1,12 @@
1
+ import { timingSafeEqual } from 'node:crypto';
2
+ /**
3
+ * Timing-safe bearer token comparison to prevent timing-based token guessing attacks.
4
+ */
5
+ export function tokenMatches(presented, expected) {
6
+ const a = Buffer.from(presented);
7
+ const b = Buffer.from(expected);
8
+ if (a.length !== b.length)
9
+ return false;
10
+ return timingSafeEqual(a, b);
11
+ }
12
+ //# sourceMappingURL=bearer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bearer.js","sourceRoot":"","sources":["../src/bearer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,eAAe,EAAC,MAAM,aAAa,CAAC;AAE5C;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,SAAiB,EAAE,QAAgB;IAC9D,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACjC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { redact } from './log.js';
2
+ export { redact };
3
+ /** A configuration problem that must stop the process before it serves anything. */
4
+ export declare class ConfigError extends Error {
5
+ constructor(message: string);
6
+ }
7
+ export interface ServerConfig {
8
+ privateKeyHex: string;
9
+ identity: string;
10
+ network: string;
11
+ /** True only for the exact phrase `i-understand`. See the guard in guard.ts. */
12
+ allowUnscopedKey: boolean;
13
+ mcpStdio: boolean;
14
+ mcpHttp: {
15
+ enabled: false;
16
+ } | {
17
+ enabled: true;
18
+ port: number;
19
+ host: string;
20
+ token: string;
21
+ remote: boolean;
22
+ };
23
+ restApi: {
24
+ enabled: false;
25
+ } | {
26
+ enabled: true;
27
+ host: string;
28
+ port: number;
29
+ token: string;
30
+ writes: boolean;
31
+ remote: boolean;
32
+ };
33
+ }
34
+ type Env = Record<string, string | undefined>;
35
+ export declare function loadConfig(env: Env): ServerConfig;
36
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,MAAM,EAAC,MAAM,UAAU,CAAC;AAEhC,OAAO,EAAC,MAAM,EAAC,CAAC;AAEhB,oFAAoF;AACpF,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM;CAK5B;AAED,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,gFAAgF;IAChF,gBAAgB,EAAE,OAAO,CAAC;IAC1B,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE;QAAC,OAAO,EAAE,KAAK,CAAA;KAAC,GAAG;QAAC,OAAO,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAC,CAAC;IACxG,OAAO,EACH;QAAC,OAAO,EAAE,KAAK,CAAA;KAAC,GAChB;QAAC,OAAO,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAC,CAAC;CAClG;AAED,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AA8D9C,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,GAAG,YAAY,CAkGjD"}
package/dist/config.js ADDED
@@ -0,0 +1,151 @@
1
+ import { keyTypeOf } from '@varnir/chain-client';
2
+ import { redact } from './log.js';
3
+ export { redact };
4
+ /** A configuration problem that must stop the process before it serves anything. */
5
+ export class ConfigError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ Object.setPrototypeOf(this, new.target.prototype);
9
+ this.name = 'ConfigError';
10
+ }
11
+ }
12
+ const LOOPBACK = new Set(['127.0.0.1', 'localhost', '::1']);
13
+ const MIN_TOKEN_LEN = 32;
14
+ function required(env, name) {
15
+ const v = env[name]?.trim();
16
+ if (!v) {
17
+ throw new ConfigError(`${name} is required. Set it in the environment (a secret manager should supply it) and start again.`);
18
+ }
19
+ return v;
20
+ }
21
+ /** `on`/`true`/`1` are on; anything else (including unset) is off. */
22
+ function flag(env, name, fallback = false) {
23
+ const v = env[name]?.trim().toLowerCase();
24
+ if (v === undefined || v === '')
25
+ return fallback;
26
+ return v === 'on' || v === 'true' || v === '1';
27
+ }
28
+ function port(env, name, fallback) {
29
+ const v = env[name]?.trim();
30
+ if (!v)
31
+ return fallback;
32
+ const n = Number(v);
33
+ if (!Number.isInteger(n) || n < 1 || n > 65535) {
34
+ throw new ConfigError(`${name} must be an integer port in [1, 65535]; got ${JSON.stringify(v)}.`);
35
+ }
36
+ return n;
37
+ }
38
+ /**
39
+ * Precedence: VARNIR_MCP_HTTP_PORT -> PORT -> 8787.
40
+ *
41
+ * Google Cloud Run and Azure Container Apps both inject a `PORT` environment
42
+ * variable at container start and require the process to listen on exactly
43
+ * that port - a container bound elsewhere is health-checked as down and
44
+ * killed, regardless of what it's actually doing. This server's own knob is
45
+ * VARNIR_MCP_HTTP_PORT (default 8787), which predates any cloud deployment
46
+ * target and must keep working unchanged for stdio/local/self-hosted users
47
+ * who set it deliberately. Falling back to PORT only when
48
+ * VARNIR_MCP_HTTP_PORT is unset lets the same image boot correctly on either
49
+ * platform without extra configuration, while an operator who explicitly
50
+ * sets VARNIR_MCP_HTTP_PORT (e.g. to run two instances behind one host)
51
+ * still gets exactly that value.
52
+ *
53
+ * The REST API's port (VARNIR_HTTP_API_PORT, default 8788) deliberately does
54
+ * NOT get this same PORT fallback: a container only has one port that is
55
+ * "the platform's" port (the one Cloud Run/ACA route traffic to and health
56
+ * check), and the MCP HTTP server is that surface for a cloud deployment -
57
+ * the REST API is off by default and, when enabled at all, is a secondary,
58
+ * self-hosted-oriented surface that isn't part of the one-click cloud path.
59
+ * Wiring PORT to REST's port too would silently break MCP whenever only one
60
+ * of the two servers is meant to claim the platform's assigned port.
61
+ */
62
+ function mcpHttpPort(env) {
63
+ if (env.VARNIR_MCP_HTTP_PORT?.trim())
64
+ return port(env, 'VARNIR_MCP_HTTP_PORT', 8787);
65
+ if (env.PORT?.trim())
66
+ return port(env, 'PORT', 8787);
67
+ return 8787;
68
+ }
69
+ export function loadConfig(env) {
70
+ const privateKeyHex = required(env, 'VARNIR_PRIVATE_KEY');
71
+ // Validate key format based on key type
72
+ const keyType = keyTypeOf(privateKeyHex);
73
+ if (keyType === 'secp256k1') {
74
+ // secp256k1: must be hex
75
+ if (!/^(0x)?[0-9a-fA-F]+$/.test(privateKeyHex)) {
76
+ // Deliberately does not echo the value.
77
+ throw new ConfigError('VARNIR_PRIVATE_KEY must be a hex string.');
78
+ }
79
+ }
80
+ else {
81
+ // Post-quantum: must be <type>:<secretKey>:<publicKey>
82
+ const parts = privateKeyHex.split(':');
83
+ // Two-part format is a public key (<type>:<publicKey>), not a private
84
+ // key - but only when that second part is actually present. `<type>:`
85
+ // alone (parts[1] === '') isn't a public key either; it's simply a
86
+ // malformed private key, and must fall through to the generic format
87
+ // error below rather than be misdiagnosed as "here is a public key."
88
+ if (parts.length === 2 && parts[1]) {
89
+ throw new ConfigError(`VARNIR_PRIVATE_KEY appears to be a public key (${keyType}). A private key is required.`);
90
+ }
91
+ if (parts.length !== 3 || !parts[1] || !parts[2]) {
92
+ // Deliberately does not echo the value.
93
+ throw new ConfigError(`VARNIR_PRIVATE_KEY must be a valid ${keyType} private key in the format <type>:<secretKey>:<publicKey>.`);
94
+ }
95
+ }
96
+ const identity = required(env, 'VARNIR_IDENTITY');
97
+ const network = required(env, 'VARNIR_NETWORK');
98
+ const allowUnscopedKey = env.VARNIR_ALLOW_UNSCOPED_KEY?.trim() === 'i-understand';
99
+ const mcpStdio = flag(env, 'VARNIR_MCP_STDIO', true);
100
+ let mcpHttp = { enabled: false };
101
+ if (flag(env, 'VARNIR_MCP_HTTP')) {
102
+ const host = env.VARNIR_MCP_HTTP_HOST?.trim() || '127.0.0.1';
103
+ const remote = !LOOPBACK.has(host);
104
+ if (remote && env.VARNIR_MCP_HTTP_ALLOW_REMOTE?.trim() !== 'i-understand') {
105
+ throw new ConfigError(`VARNIR_MCP_HTTP_HOST is ${host}, which is not loopback. Binding the MCP HTTP server to a reachable address exposes a money-moving endpoint to the network. Set VARNIR_MCP_HTTP_ALLOW_REMOTE=i-understand if that is genuinely what you want.`);
106
+ }
107
+ // I4: mandatory even on loopback. VARNIR_MCP_HTTP=on alone used to put the
108
+ // full write set on 127.0.0.1 with NO authentication - any other local
109
+ // user or process on a shared host could move funds. REST already
110
+ // requires a token unconditionally; MCP HTTP now matches it.
111
+ const token = env.VARNIR_MCP_HTTP_TOKEN?.trim();
112
+ if (!token) {
113
+ throw new ConfigError('VARNIR_MCP_HTTP is on but VARNIR_MCP_HTTP_TOKEN is not set. The MCP HTTP server moves money; it will not start without a token, even on loopback - anything else on the host could otherwise reach it unauthenticated.');
114
+ }
115
+ if (token.length < MIN_TOKEN_LEN) {
116
+ throw new ConfigError(`VARNIR_MCP_HTTP_TOKEN must be at least ${MIN_TOKEN_LEN} characters. A weak token on a money-moving endpoint is a misconfiguration, not a preference.`);
117
+ }
118
+ mcpHttp = {
119
+ enabled: true,
120
+ port: mcpHttpPort(env),
121
+ host,
122
+ token,
123
+ remote,
124
+ };
125
+ }
126
+ let restApi = { enabled: false };
127
+ if (flag(env, 'VARNIR_HTTP_API')) {
128
+ const token = env.VARNIR_HTTP_API_TOKEN?.trim();
129
+ if (!token) {
130
+ throw new ConfigError('VARNIR_HTTP_API is on but VARNIR_HTTP_API_TOKEN is not set. The REST API moves money; it will not start without a token. There is no default and none is generated for you.');
131
+ }
132
+ if (token.length < MIN_TOKEN_LEN) {
133
+ throw new ConfigError(`VARNIR_HTTP_API_TOKEN must be at least ${MIN_TOKEN_LEN} characters. A weak token on a money-moving endpoint is a misconfiguration, not a preference.`);
134
+ }
135
+ const host = env.VARNIR_HTTP_API_HOST?.trim() || '127.0.0.1';
136
+ const remote = !LOOPBACK.has(host);
137
+ if (remote && env.VARNIR_HTTP_API_ALLOW_REMOTE?.trim() !== 'i-understand') {
138
+ throw new ConfigError(`VARNIR_HTTP_API_HOST is ${host}, which is not loopback. Binding the REST API to a reachable address exposes a money-moving endpoint to the network. Set VARNIR_HTTP_API_ALLOW_REMOTE=i-understand if that is genuinely what you want.`);
139
+ }
140
+ restApi = {
141
+ enabled: true,
142
+ host,
143
+ port: port(env, 'VARNIR_HTTP_API_PORT', 8788),
144
+ token,
145
+ writes: flag(env, 'VARNIR_HTTP_API_WRITES'),
146
+ remote,
147
+ };
148
+ }
149
+ return { privateKeyHex, identity, network, allowUnscopedKey, mcpStdio, mcpHttp, restApi };
150
+ }
151
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { VarnirLedgerKey } from '@varnir/chain-client';
2
+ /**
3
+ * The whole security argument for this server is that the on-ledger spending
4
+ * policy is the hard bound on what a connected assistant can do. That argument
5
+ * holds ONLY if the key in VARNIR_PRIVATE_KEY is a policy-scoped agent key.
6
+ *
7
+ * Nothing stops an operator pasting their owner key into the environment, and
8
+ * the result would appear to work perfectly while having unbounded authority
9
+ * over the identity. So at boot we read our own key off the ledger and check
10
+ * what it actually is.
11
+ *
12
+ * This is defence in depth, not the bound itself - the contracts remain the
13
+ * real enforcement. Its value is catching the single most likely operator
14
+ * mistake before it can cost money.
15
+ */
16
+ export type GuardMode = 'writes' | 'read-only' | 'refuse';
17
+ export interface KeyClassification {
18
+ mode: GuardMode;
19
+ reason: string;
20
+ }
21
+ /**
22
+ * `onWirePublicKey` MUST be the UNTAGGED on-wire value - i.e.
23
+ * `onWirePublicKey(privateKeyHex).publicKey`, NOT `publicKeyForPrivateKey()`,
24
+ * which tags PQ keys as `falcon-512:<pk>` and would never match what
25
+ * `listLedgerKeys()` reports. See key-match.ts.
26
+ */
27
+ export declare function classifyKey(keys: VarnirLedgerKey[], onWirePublicKey: string, allowUnscoped: boolean): KeyClassification;
28
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,sBAAsB,CAAC;AAG1D;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,eAAe,EAAE,EACvB,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,OAAO,GACrB,iBAAiB,CA4DnB"}
package/dist/guard.js ADDED
@@ -0,0 +1,56 @@
1
+ import { samePublicKey } from './key-match.js';
2
+ /**
3
+ * `onWirePublicKey` MUST be the UNTAGGED on-wire value - i.e.
4
+ * `onWirePublicKey(privateKeyHex).publicKey`, NOT `publicKeyForPrivateKey()`,
5
+ * which tags PQ keys as `falcon-512:<pk>` and would never match what
6
+ * `listLedgerKeys()` reports. See key-match.ts.
7
+ */
8
+ export function classifyKey(keys, onWirePublicKey, allowUnscoped) {
9
+ const mine = keys.find(k => samePublicKey(k.publicKey, onWirePublicKey));
10
+ if (!mine) {
11
+ // Not a degradation: this key could not sign anything on this identity at
12
+ // all, so every write would fail later anyway. Fail loudly and early.
13
+ return {
14
+ mode: 'refuse',
15
+ reason: 'This key is not attached to VARNIR_IDENTITY on the ledger. Check that VARNIR_PRIVATE_KEY and VARNIR_IDENTITY belong together, and that the key has not been revoked.',
16
+ };
17
+ }
18
+ if (mine.isOwner || mine.role === 'owner') {
19
+ if (allowUnscoped) {
20
+ return { mode: 'writes', reason: 'Running with the identity OWNER key because VARNIR_ALLOW_UNSCOPED_KEY is set.' };
21
+ }
22
+ return {
23
+ mode: 'read-only',
24
+ reason: 'This is the identity OWNER key, which carries no spending policy - an assistant holding it would have unbounded authority over your funds. Starting read-only. Mint a policy-scoped agent key in the scanner and use that instead, or set VARNIR_ALLOW_UNSCOPED_KEY=i-understand.',
25
+ };
26
+ }
27
+ if (mine.role === 'readonly') {
28
+ return {
29
+ mode: 'read-only',
30
+ reason: 'This key is a read-only key by ledger design - it carries a 9000 stake that gates read-only access, and the money contracts refuse it write access regardless of any spend policy. A spend policy on this key cannot grant write ability. Mint a proposer or dual key carrying a spend policy in the scanner instead.',
31
+ };
32
+ }
33
+ if (mine.role === 'offledger') {
34
+ return {
35
+ mode: 'read-only',
36
+ reason: 'This is an off-ledger key - the money contracts refuse it any fund movement regardless of any spend policy. A spend policy on this key is inert and will not grant write ability. Mint a proposer or dual key carrying a spend policy in the scanner instead.',
37
+ };
38
+ }
39
+ if (mine.role === 'verifier') {
40
+ return {
41
+ mode: 'read-only',
42
+ reason: 'This is a verifier key - the money contracts refuse it outright ("A verifier key cannot raise transfers") regardless of any spend policy. A spend policy on this key cannot grant write ability. Mint a proposer or dual key carrying a spend policy in the scanner instead.',
43
+ };
44
+ }
45
+ if (!mine.policy) {
46
+ if (allowUnscoped) {
47
+ return { mode: 'writes', reason: 'Running with a key that carries no spend policy because VARNIR_ALLOW_UNSCOPED_KEY is set.' };
48
+ }
49
+ return {
50
+ mode: 'read-only',
51
+ reason: 'This key carries no on-ledger spend policy, so nothing would bound what a connected assistant could spend. Starting read-only. Add a spend policy to the key in the scanner, or set VARNIR_ALLOW_UNSCOPED_KEY=i-understand.',
52
+ };
53
+ }
54
+ return { mode: 'writes', reason: 'Scoped agent key with an on-ledger spend policy.' };
55
+ }
56
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,aAAa,EAAC,MAAM,gBAAgB,CAAC;AAuB7C;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,IAAuB,EACvB,eAAuB,EACvB,aAAsB;IAEtB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC;IAEzE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,0EAA0E;QAC1E,sEAAsE;QACtE,OAAO;YACL,IAAI,EAAE,QAAQ;YACd,MAAM,EACJ,sKAAsK;SACzK,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC1C,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,EAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,+EAA+E,EAAC,CAAC;QACnH,CAAC;QACD,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,MAAM,EACJ,mRAAmR;SACtR,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC7B,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,MAAM,EACJ,uTAAuT;SAC1T,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAC9B,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,MAAM,EACJ,+PAA+P;SAClQ,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC7B,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,MAAM,EACJ,8QAA8Q;SACjR,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QACjB,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,EAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,2FAA2F,EAAC,CAAC;QAC/H,CAAC;QACD,OAAO;YACL,IAAI,EAAE,WAAW;YACjB,MAAM,EACJ,6NAA6N;SAChO,CAAC;IACJ,CAAC;IAED,OAAO,EAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,kDAAkD,EAAC,CAAC;AACtF,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}