@atbash/sdk 0.3.25 → 0.4.0-dev.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 CHANGED
@@ -1,366 +1,144 @@
1
- # @atbash/sdk
1
+ # @atbash/sdk (Node)
2
2
 
3
- TypeScript SDK for Atbash the safety layer that evaluates AI agent actions against operator-defined policies before execution.
3
+ The Node.js Atbash SDK. One package, two layers:
4
4
 
5
- ## Installation
5
+ - **Rust core via NAPI-RS** — signing, key derivation, redaction, unicode
6
+ normalization, memory diff. Byte-identical to the TypeScript reference.
7
+ - **Judge / risk-engine HTTP client** — types generated from
8
+ [`spec/openapi.yaml`](../../spec/openapi.yaml), transport in
9
+ [src-ts/http/client.ts](src-ts/http/client.ts).
6
10
 
7
- ```bash
8
- npm install @atbash/sdk
9
- ```
10
-
11
- Requires Node.js 18+. Server-side only — private keys are used for local signing and must never be exposed to browsers.
12
-
13
- ## Quickstart
14
-
15
- ```ts
16
- import { loadAgent, judgeAction } from "@atbash/sdk";
17
-
18
- // 1. Load your agent identity using the private key you saved during
19
- // agent creation. loadAgent() validates the key and derives the
20
- // matching public key for you.
21
- const agent = loadAgent(process.env.ATBASH_AGENT_PRIVKEY!);
22
-
23
- // 2. Submit an action for judgment, before executing it.
24
- // The SDK signs the transaction locally and sends it to the judge API.
25
- // Private key stays on your machine — never sent over HTTP.
26
- // Pass orgName so the SDK auto-resolves the correct chain (public or private).
27
- const result = await judgeAction(
28
- "Transfer $50,000 to external wallet 0xabc",
29
- "Outbound AML check — new recipient, over threshold",
30
- agent,
31
- { orgName: "my_org" },
32
- );
33
-
34
- // 3. Enforce the verdict
35
- switch (result.verdict) {
36
- case "ALLOW":
37
- // Proceed with the action
38
- break;
39
- case "HOLD":
40
- // Held — operator must approve in the dashboard
41
- console.log("Held for review:", result.tool_call_id);
42
- break;
43
- case "BLOCK":
44
- // Refused — agent is auto-jailed
45
- throw new Error(`Blocked: ${result.reason}`);
46
- }
47
- ```
48
-
49
- Before this works, the agent must be onboarded at [atbash.ai](https://atbash.ai/) — assigned to an org with an active subscription and a policy pack attached.
50
-
51
- ### How it works
52
-
53
- `judgeAction()` performs a two-step flow:
54
-
55
- 1. **Sign locally** — signs the transaction using the agent's private key. The key never leaves your machine.
56
- 2. **Request verdict** — sends the signed payload to the Atbash judge API, which records it on the Chromia blockchain and returns a verdict.
57
-
58
-
59
- ### Don't have an agent yet?
60
-
61
- There are two ways to create an agent:
62
-
63
- 1. **Dashboard (recommended)** — create an agent at [atbash.ai/risk-engine/agents](https://atbash.ai/risk-engine/agents). The dashboard generates the keypair, assigns the agent to your org, and lets you attach a policy pack — all in one step.
64
-
65
- 2. **Programmatic** — generate a new keypair locally or bring an existing secp256k1 private key from another platform, then onboard it via the dashboard:
66
-
67
- ```ts
68
- import { generateKeyPair, loadAgent } from "@atbash/sdk";
69
-
70
- const { privKey } = generateKeyPair();
71
- console.log("Save this private key somewhere safe:", privKey);
72
- const agent = loadAgent(privKey);
73
- ```
74
-
75
- > **Note:** After generating a key programmatically, you must still onboard the agent at [atbash.ai/risk-engine/agents](https://atbash.ai/risk-engine/agents) — assign it to an org and attach a policy pack before `judgeAction` will work.
11
+ The high-level `Atbash` class composes the two, mirroring the TS reference at
12
+ [atbash-sdk/src/client.ts](../../atbash-sdk/src/client.ts) and the Python
13
+ surface at [bindings/python/python/atbash/client.py](../../bindings/python/python/atbash/client.py).
76
14
 
77
- ### Secret storage
15
+ ## Layout
78
16
 
79
- - Load the private key from an environment variable (`ATBASH_AGENT_KEY`) or a secret manager — never hardcode it.
80
- - Never commit `.env` files containing the key.
81
- - If a key leaks, stop using it and create a new agent in the [dashboard](https://atbash.ai/risk-engine/agents).
82
-
83
- ## Verdicts
84
-
85
- Every `judgeAction` call returns one of three verdicts:
86
-
87
- | Verdict | Meaning | What your code should do |
88
- |---------|---------|-------------------------|
89
- | `ALLOW` | Action is within policy | Proceed with execution |
90
- | `HOLD` | Requires operator review | Pause — poll `getJudgmentStatus` until resolved |
91
- | `BLOCK` | Violates a red line | Abort — agent is auto-jailed |
92
-
93
- > **NB:** If your org has **no active subscription**, the judge returns `"No verdict"` — actions are logged on-chain for the audit trail but not evaluated. Assign a subscription plan at [atbash.ai/risk-engine/settings](https://atbash.ai/risk-engine/settings) for active verdicts. All subscription plans (including Free) get full enforcement.
94
-
95
- ## API
96
-
97
- ### Judge
98
-
99
- ```ts
100
- judgeAction(
101
- action: string,
102
- context: string,
103
- auth: AgentAuth,
104
- opts?: JudgeOptions,
105
- ): Promise<JudgeResult>
106
17
  ```
107
-
108
- Submit an action for judgment before execution. Signs the transaction locally and sends it to the judge API for a verdict.
109
-
110
- ```ts
111
- interface AgentAuth {
112
- pubkey: string; // 66-char hex, secp256k1 compressed public key
113
- privkey: string; // 64-char hex private key (used for local signing only)
114
- }
115
-
116
- interface JudgeOptions {
117
- endpoint?: string; // API base URL (default: https://atbash.ai)
118
- timeout?: number; // Request timeout in ms
119
- provider?: string; // "openai" | "google" | "microsoft" | "custom"
120
- model?: string; // Model override (e.g. "gpt-4o-mini")
121
- toolName?: string; // Tool name for audit trail
122
- toolArgsJson?: string; // Tool arguments JSON for audit trail
123
- orgName?: string; // Org name — SDK auto-resolves the correct chain
124
- }
125
-
126
- interface JudgeResult {
127
- verdict: string; // "ALLOW", "HOLD", or "BLOCK"
128
- reason: string; // Human-readable explanation
129
- confidence: number; // 0–1
130
- provider: string; // Which provider evaluated the action
131
- latency_ms: number; // Inference time
132
- tool_call_id: string; // Unique ID for this judgment
133
- on_chain: boolean; // Whether the record was written on-chain
134
- }
18
+ bindings/node/
19
+ ├── src/lib.rs # NAPI-RS Rust bindings atbash.<triple>.node + index.{js,d.ts}
20
+ ├── index.js / index.d.ts # NAPI-generated native glue (gitignored)
21
+ ├── src-ts/ # TypeScript SDK surface
22
+ │ ├── index.ts # public exports (Atbash + native re-exports)
23
+ ├── client.ts # Atbash class (judge / log / queries)
24
+ │ ├── native.ts # typed loader for the NAPI addon
25
+ │ ├── http/ # schema.ts (generated) + thin fetch client
26
+ │ ├── types.ts, constants.ts, errors.ts, normalize.ts
27
+ │ └── ...
28
+ ├── dist/ # tsup output (gitignored) — what gets published
29
+ └── __test__/ # native parity + SDK (http / body-parity) tests
135
30
  ```
136
31
 
137
- ### Poll judgment status
32
+ ## Setup
138
33
 
139
- ```ts
140
- getJudgmentStatus(judgmentId: string, agentPubkey: string, opts?: ClientOpts): Promise<JudgmentStatus>
141
- ```
142
-
143
- Check whether a held action has been approved or rejected by an operator.
144
-
145
- ```ts
146
- interface JudgmentStatus {
147
- status: "pending" | "answered" | "error";
148
- verdict: string;
149
- reason: string;
150
- judgmentId: string;
151
- onChain?: boolean;
152
- cached?: boolean;
153
- responseTimeMs?: number;
154
- }
34
+ ```bash
35
+ cd bindings/node
36
+ npm install
155
37
  ```
156
38
 
157
- ### Agent identity
39
+ ## Build
158
40
 
159
- ```ts
160
- loadAgent(privkey: string): AgentAuth
161
- generateKeyPair(): { privKey: string; pubKey: string }
162
- derivePublicKey(privKeyHex: string): string
163
- isValidPrivateKey(hex: string): boolean
164
- toPubkeyHex(val: unknown): string
41
+ ```bash
42
+ npm run gen:http # regenerate src-ts/http/schema.ts from spec/openapi.yaml
43
+ npm run build:native # atbash.<triple>.node + index.{js,d.ts}
44
+ npm run build:ts # tsup → dist/{index.js,index.mjs,index.d.ts}
45
+ npm run build # native + ts
165
46
  ```
166
47
 
167
- `loadAgent(privkey)` is the canonical loader — pass in the private key from the dashboard, get back `{ pubkey, privkey }` ready for `judgeAction`. It accepts `0x`-prefixed, padded, or mixed-case input and throws on malformed keys. Use `generateKeyPair()` only for local development; for production, create agents in the dashboard so operators can attach policies.
168
-
169
- ### Operations
170
-
171
- Functions that sign transactions and write to the Chromia blockchain.
48
+ Native artifacts (`*.node`, `index.js`, `index.d.ts`) and `dist/` are
49
+ `.gitignore`d — build locally.
172
50
 
173
- | Function | Use case |
174
- |----------|----------|
175
- | `judgeAction(action, context, auth, opts?)` | Sign locally + request a verdict from the judge API |
51
+ ## Test
176
52
 
177
- ### Queries
178
-
179
- | Function | Use case |
180
- |----------|----------|
181
- | `checkAgentExists(pubkey, opts?)` | Check if an agent is onboarded before signing |
182
- | `getJudgmentStatus(judgmentId, agentPubkey, opts?)` | Poll whether a held action has been approved or rejected |
183
- | `getToolCalls(maxCount)` | List recent tool calls across all agents |
184
- | `getOrgToolCalls(orgName, maxCount)` | List tool calls for a specific org |
185
- | `getAgentToolCalls(pubkey, maxCount)` | List tool calls for a specific agent |
186
- | `getToolCallCount()` | Get total number of tool calls on-chain |
187
- | `getToolCallFull(toolCallId)` | Get full details of a single tool call (verdict, context, timing) |
188
- | `getOrgSubscription(orgName)` | Check an org's subscription plan, network, and active status |
189
- | `getAgentDetail(pubkey)` | Get agent metadata (org, status, creation date) |
190
- | `getAgentPolicy(pubkey)` | Check agent's policy pack and jail status |
191
- | `getPendingHeldActions(orgName, maxCount)` | List actions waiting for operator approval |
192
- | `getHeldActionReviews(orgName, maxCount)` | List completed operator reviews |
193
- | `getSafetyStats()` | Get chain-wide safety statistics (total judgments, verdicts, etc.) |
194
-
195
- ## Configuration
196
-
197
- You can pass configuration inline or use the built-in config module that reads from `~/.config/atbash/config.json`.
198
-
199
- ### Inline
200
-
201
- ```ts
202
- const result = await judgeAction(action, context, auth, {
203
- endpoint: "https://your-instance.example.com",
204
- provider: "openai",
205
- model: "gpt-4o",
206
- });
53
+ ```bash
54
+ npm test # node --test __test__/*.test.mjs
55
+ # native vector parity (91) + SDK http/body-parity (30) = 121 cases
207
56
  ```
208
57
 
209
- ### Persistent config
58
+ The SDK tests stand a real loopback HTTP server up (see
59
+ [__test__/_mock_server.mjs](__test__/_mock_server.mjs)) so `fetch` runs
60
+ end-to-end; signing is real through the NAPI core.
210
61
 
211
- Save configuration once — the SDK resolves values with priority: **flag > env var > config file**.
62
+ ## Usage
212
63
 
213
64
  ```ts
214
- import { saveUserConfig, resolve, loadAgent, judgeAction } from "@atbash/sdk";
65
+ import { Atbash } from "@atbash/sdk";
215
66
 
216
- // Save once
217
- saveUserConfig({
218
- agentKey: "9cd07a...",
219
- orgName: "my_org",
220
- provider: "openai",
221
- });
67
+ const client = new Atbash(privkey, { endpoint: "https://atbash.ai" });
222
68
 
223
- // Then use resolve() anywhere
224
- const agent = loadAgent(resolve("agentKey"));
225
- const result = await judgeAction("Transfer $500", "finance", agent, {
226
- orgName: resolve("orgName"),
227
- provider: resolve("provider"), // omit to use the on-chain ATBASH judge
69
+ // Sign + submit for judgement (verdict normalized to ALLOW/HOLD/BLOCK).
70
+ const verdict = await client.judgeAction("read_file", "reading config", {
71
+ toolName: "fs.read",
228
72
  });
229
- ```
230
-
231
- Config file location: `~/.config/atbash/config.json`
232
73
 
233
- | Function | Purpose |
234
- |----------|---------|
235
- | `saveUserConfig(config)` | Write config to disk |
236
- | `loadUserConfig()` | Read config from disk |
237
- | `resolve(key, flagValue?)` | Resolve a value: flag > env > file > `""` |
238
- | `getConfigPath()` | Returns the config file path |
239
-
240
- | Config key | Env var |
241
- |------------|--------|
242
- | `agentKey` | `ATBASH_AGENT_KEY` |
243
- | `orgName` | `ATBASH_ORG_NAME` |
244
- | `judgeEndpoint` | `ATBASH_ENDPOINT` |
245
- | `blockchainRid` | `ATBASH_BLOCKCHAIN_RID` |
246
- | `provider` | `ATBASH_PROVIDER` |
247
- | `providerModel` | `ATBASH_PROVIDER_MODEL` |
248
-
249
- > **Chain routing:** When you pass `orgName`, the SDK automatically connects to the correct chain for your org's subscription plan. You don't need to configure chain details manually.
250
-
251
- ## Secret redaction
252
-
253
- Before each `auditToolCall` signs anything, the SDK scans `args` and `context` for secret-shaped values (API keys, tokens, JWTs, PEM blocks, etc.) and replaces matches with `[REDACTED:<kind>]`. Redaction happens **before signing**, so secrets never reach the signed bytes, the request body, the on-chain log, or the prompt sent to the AI provider.
254
-
255
- When the redactor fires, you'll see a warning via the configured `logger`:
74
+ // Sign a tool call locally (server broadcasts to chain).
75
+ const logged = await client.logToolCall("write_file", "patching deps");
256
76
 
77
+ // Read-only queries.
78
+ const tier = await client.getOrgTierInfo("acme");
257
79
  ```
258
- [atbash] redacted secrets before judge call { tool: "exec", count: 2, kinds: ["anthropic", "generic_token"] }
259
- ```
260
-
261
- Common `kinds`:
262
-
263
- - `anthropic`, `openai`, `github`, `google`, `aws_access_key`, `stripe`, `slack`, `jwt`, `private_key_pem` — high-confidence vendor patterns; if you see these, a real secret was almost certainly in your input
264
- - `context_secret` — a value next to a label like `api_key=`, `token:`, `password=`, etc.
265
- - `generic_token` — long random-looking strings (32+ alphanumeric chars). Catches unknown-vendor secrets, but can also match UUIDs, content hashes, and other opaque identifiers. The judge sees `[REDACTED:generic_token]` instead of the original; for verdict purposes the shape of the action matters more than the exact ID, so this is generally safe — but worth knowing if you see it unexpectedly.
266
- - `base64` — long base64-encoded values; can match legitimate image/file data
267
-
268
- Redaction is silent at the consumer level — the SDK's caller still has the original arguments. Only what's sent to the judge (and persisted on chain via the verdict log) is scrubbed.
269
-
270
- ## High-level client
271
80
 
272
- For framework integrations, `createAtbashClient` wraps key loading, secret redaction, and verdict handling into a single `auditToolCall` method:
81
+ Crypto / redaction / memory primitives are also exported as plain functions
82
+ (byte-identical to the TS reference):
273
83
 
274
84
  ```ts
275
- import { createAtbashClient } from "@atbash/sdk";
85
+ import {
86
+ generateKeypair,
87
+ derivePublicKey,
88
+ signLogToolCall,
89
+ redactSecrets,
90
+ diffMemorySnapshots,
91
+ DEFAULT_BLOCKCHAIN_RID,
92
+ } from "@atbash/sdk";
276
93
 
277
- const atbash = createAtbashClient({
278
- orgName: "my_org",
279
- keyPair: { privKey: process.env.ATBASH_AGENT_KEY!, pubKey: "" },
280
- failClosed: true, // block on errors (default: true)
281
- });
282
-
283
- const decision = await atbash.auditToolCall({
284
- toolName: "send_email",
285
- args: { to: "user@example.com", subject: "Reset" },
286
- context: "Password reset flow",
287
- });
288
-
289
- if (!decision.allow) {
290
- console.log(`${decision.verdict}: ${decision.reason}`);
291
- }
292
- ```
293
-
294
- The client auto-resolves the correct chain from the org's subscription on the first call and caches the result. Secret redaction runs automatically before signing.
295
-
296
- ## Integration patterns
297
-
298
- ### Pre-execution gate
299
-
300
- ```ts
301
- async function safeExecute(action: string, context: string, execute: () => Promise<void>) {
302
- const result = await judgeAction(action, context, auth);
303
- if (result.verdict === "BLOCK") throw new Error(`Blocked: ${result.reason}`);
304
- if (result.verdict === "HOLD") throw new Error(`Held for review: ${result.tool_call_id}`);
305
- await execute();
306
- }
94
+ const kp = generateKeypair(); // { priv_key, pub_key }
95
+ const { redacted, found } = redactSecrets("token=sk-abc123…");
307
96
  ```
308
97
 
309
- ### Polling a held action
98
+ ## Config loading
310
99
 
311
- ```ts
312
- async function waitForApproval(toolCallId: string): Promise<string> {
313
- while (true) {
314
- const status = await getJudgmentStatus(toolCallId, auth.pubkey);
315
- if (status.status === "answered") return status.verdict;
316
- if (status.status === "error") throw new Error(status.reason);
317
- await new Promise((r) => setTimeout(r, 5000));
318
- }
319
- }
320
- ```
321
-
322
- ### Checking agent jail status
100
+ Construct from environment / config file / key file instead of an inline key.
101
+ Resolution precedence is flag `ATBASH_*` env → `~/.config/atbash/config.json`
102
+ (camelCase keys, shared with the Python SDK) → default.
323
103
 
324
104
  ```ts
325
- const policy = await getAgentPolicy(pubKey);
326
- if (policy.is_jailed) {
327
- console.error("Agent is jailed unjail via dashboard before retrying.");
328
- process.exit(1);
329
- }
105
+ import { Atbash } from "@atbash/sdk";
106
+
107
+ // Reads agentKey/judgeEndpoint/blockchainRid from env or config.json; falls
108
+ // back to the key file at ~/.config/atbash/guard-client-key.
109
+ const client = Atbash.fromConfig();
110
+
111
+ // Self-hosted judge: endpoint is validated and its response-signing pubkey
112
+ // becomes the default verifyPubKey for judgeAction.
113
+ const selfHosted = Atbash.fromConfig({
114
+ judge: {
115
+ policy: "self-hosted",
116
+ endpoint: "https://judge.internal",
117
+ verifyPubKey: "02…", // 66-hex compressed secp256k1
118
+ },
119
+ });
330
120
  ```
331
121
 
332
- ## Error handling
333
-
334
- The SDK throws standard `Error` objects. Known failure modes are enriched with a pointer to the relevant dashboard page, so the error message tells you where to fix the problem:
122
+ Lower-level helpers are also exported: `loadUserConfig`, `saveUserConfig`,
123
+ `resolve`, `validateJudgeEndpoint`, `resolveKeyPath`, `loadAgentFromFile`.
335
124
 
336
- ```
337
- API error 404: {"error":"Agent not registered..."}
338
- → Onboard the agent at https://atbash.ai/risk-engine/agents
339
- ```
125
+ ## One-call guard: `auditToolCall`
340
126
 
341
- | Error | Cause | Where to fix |
342
- |---|---|---|
343
- | `API error 404: Agent not registered` | Agent not onboarded | [atbash.ai/risk-engine/agents](https://atbash.ai/risk-engine/agents) |
344
- | `API error 400: Agent has no policy` | No policy attached to agent | [atbash.ai/risk-engine/agents](https://atbash.ai/risk-engine/agents) |
345
- | `Agent is jailed` | BLOCK verdict triggered auto-jail | [atbash.ai/risk-engine/agents](https://atbash.ai/risk-engine/agents) |
346
- | `Verdicts are disabled` | Org has no active subscription | [atbash.ai/risk-engine/settings](https://atbash.ai/risk-engine/settings) |
347
- | `API error 400: action is required` | Empty action string | Fix caller |
348
- | `API error 502: Incorrect API key` | Invalid provider API key | Check saved key at [atbash.ai/risk-engine/settings](https://atbash.ai/risk-engine/settings) |
127
+ Redact secrets sign judge allow/deny `Decision`, failing closed by
128
+ default (any error denies unless `failClosed: false`):
349
129
 
350
130
  ```ts
351
- try {
352
- const result = await judgeAction(action, context, auth);
353
- } catch (err) {
354
- if (err.message.includes("Agent not registered")) {
355
- // Point the user at https://atbash.ai/risk-engine/agents to onboard.
356
- }
357
- }
131
+ const client = Atbash.fromConfig({ logger: console });
132
+ const decision = await client.auditToolCall({
133
+ toolName: "shell.exec",
134
+ args: { cmd: "rm -rf /tmp/cache" },
135
+ context: "cleaning build cache",
136
+ });
137
+ if (!decision.allow) throw new Error(`${decision.verdict}: ${decision.reason}`);
358
138
  ```
359
139
 
360
- ## Dashboard
361
-
362
- Policy authoring, operator reviews, and agent management happen at [atbash.ai](https://atbash.ai/). The SDK is the programmatic interface; the dashboard is the operator interface.
363
-
364
- ## License
140
+ Secret-shaped values in `args`/`context` are redacted before signing, so they
141
+ never reach the signed bytes, the request, the on-chain log, or the LLM prompt.
365
142
 
366
- Proprietary all rights reserved. See [LICENSE](https://atbash.ai/license).
143
+ The raw native addon is also reachable at `@atbash/sdk/native` for advanced
144
+ callers who want the un-wrapped functions.
Binary file
Binary file
Binary file
Binary file