@treeship/verify 0.9.2
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 +143 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.js +89 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# @treeship/verify
|
|
2
|
+
|
|
3
|
+
Zero-dependency cryptographic verification for [Treeship](https://treeship.dev) Session Receipts and Agent Certificates. Runs anywhere WebAssembly and `fetch` are available.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @treeship/verify
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The only dependency is `@treeship/core-wasm` (the compiled Rust core, under 170 KB gzipped). No transitive dependency on `@treeship/sdk`, so you can ship this to an edge worker, browser dashboard, or audit tool without pulling the subprocess code path in at all.
|
|
12
|
+
|
|
13
|
+
## API
|
|
14
|
+
|
|
15
|
+
Three functions. Each accepts a parsed object, a JSON string, or a URL.
|
|
16
|
+
|
|
17
|
+
### `verifyReceipt(target)`
|
|
18
|
+
|
|
19
|
+
Runs the JSON-level checks a Treeship Session Receipt carries: Merkle root recomputation, inclusion proof verification, leaf-count parity, timeline ordering, chain linkage.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { verifyReceipt } from '@treeship/verify';
|
|
23
|
+
|
|
24
|
+
const result = await verifyReceipt('https://treeship.dev/receipt/ssn_abc');
|
|
25
|
+
|
|
26
|
+
if (result.outcome === 'pass') {
|
|
27
|
+
console.log(`session ${result.session.id} verified`);
|
|
28
|
+
} else {
|
|
29
|
+
console.log(`verification failed:`, result.checks.filter(c => c.status === 'fail'));
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### `verifyCertificate(target, now?)`
|
|
34
|
+
|
|
35
|
+
Verifies the Ed25519 signature on an Agent Certificate against the public key embedded in the certificate. With `now` supplied (Date or RFC 3339 string), also classifies the validity window.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { verifyCertificate } from '@treeship/verify';
|
|
39
|
+
|
|
40
|
+
const result = await verifyCertificate('./researcher.agent/certificate.json', new Date());
|
|
41
|
+
|
|
42
|
+
if (result.outcome === 'pass' && result.validity === 'valid') {
|
|
43
|
+
console.log(`certificate valid for ${result.certificate.agent_name}`);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### `crossVerify(receipt, certificate, now?)`
|
|
48
|
+
|
|
49
|
+
Answers three questions: do the receipt and certificate reference the same ship, was the certificate valid at `now`, was every tool the session called authorized by the certificate. The `ok` field is the roll-up.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { crossVerify } from '@treeship/verify';
|
|
53
|
+
|
|
54
|
+
const result = await crossVerify(
|
|
55
|
+
'https://treeship.dev/receipt/ssn_abc',
|
|
56
|
+
'https://example.com/researcher.agent.json',
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (result.ok) {
|
|
60
|
+
console.log('complete trust loop verified');
|
|
61
|
+
} else {
|
|
62
|
+
console.log('ship_id_status:', result.ship_id_status);
|
|
63
|
+
console.log('unauthorized:', result.unauthorized_tool_calls);
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Runtime compatibility
|
|
68
|
+
|
|
69
|
+
| Runtime | Supported |
|
|
70
|
+
|---------|-----------|
|
|
71
|
+
| Node.js 18+ | yes |
|
|
72
|
+
| Node.js 20+ | yes |
|
|
73
|
+
| Deno | yes |
|
|
74
|
+
| Browser (bundler) | yes |
|
|
75
|
+
| Vercel Edge | yes |
|
|
76
|
+
| Cloudflare Workers | yes |
|
|
77
|
+
| AWS Lambda (Node) | yes |
|
|
78
|
+
|
|
79
|
+
The package ships `@treeship/core-wasm` with `--target bundler`. Runtimes that need a different WASM wrapper (plain Node without a bundler) can still consume the core via the subprocess-backed `@treeship/sdk`.
|
|
80
|
+
|
|
81
|
+
## Examples
|
|
82
|
+
|
|
83
|
+
### Vercel Edge Function
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
import { verifyReceipt } from '@treeship/verify';
|
|
87
|
+
|
|
88
|
+
export const config = { runtime: 'edge' };
|
|
89
|
+
|
|
90
|
+
export default async function handler(req: Request) {
|
|
91
|
+
const { url } = await req.json();
|
|
92
|
+
const result = await verifyReceipt(url);
|
|
93
|
+
return Response.json(result);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Cloudflare Worker
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { verifyReceipt } from '@treeship/verify';
|
|
101
|
+
|
|
102
|
+
export default {
|
|
103
|
+
async fetch(request: Request): Promise<Response> {
|
|
104
|
+
const { url } = await request.json();
|
|
105
|
+
const result = await verifyReceipt(url);
|
|
106
|
+
return Response.json(result);
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### AWS Lambda (Node runtime)
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { verifyReceipt } from '@treeship/verify';
|
|
115
|
+
|
|
116
|
+
export const handler = async (event: { body: string }) => {
|
|
117
|
+
const { url } = JSON.parse(event.body);
|
|
118
|
+
const result = await verifyReceipt(url);
|
|
119
|
+
return { statusCode: 200, body: JSON.stringify(result) };
|
|
120
|
+
};
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Browser dashboard
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { verifyReceipt } from '@treeship/verify';
|
|
127
|
+
|
|
128
|
+
const file = await fileInput.files[0];
|
|
129
|
+
const text = await file.text();
|
|
130
|
+
const result = await verifyReceipt(text);
|
|
131
|
+
|
|
132
|
+
renderChecks(result.checks);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## What this package is NOT
|
|
136
|
+
|
|
137
|
+
- **Not an attestation SDK.** For signing artifacts, session management, Hub push/pull, or agent registration, use [`@treeship/sdk`](../sdk-ts/) which shells out to the `treeship` CLI.
|
|
138
|
+
- **Not a trust anchor.** The embedded Ed25519 signature on an Agent Certificate is verified against the certificate's own public key. Chaining a certificate to a trusted issuer is the caller's responsibility.
|
|
139
|
+
- **Not a drop-in for local-chain verification.** Some signature verification needs the original envelope bytes, which a URL-fetched receipt does not carry. Use `treeship verify <artifact-id>` on the CLI for that.
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
Apache-2.0
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/** Accepted input shapes across all exported functions. */
|
|
2
|
+
export type VerifyTarget = string | URL | Record<string, unknown>;
|
|
3
|
+
export interface VerifyCheck {
|
|
4
|
+
step: string;
|
|
5
|
+
status: 'pass' | 'fail' | 'warn';
|
|
6
|
+
detail: string;
|
|
7
|
+
}
|
|
8
|
+
export interface VerifyReceiptResult {
|
|
9
|
+
outcome: 'pass' | 'fail' | 'error';
|
|
10
|
+
checks: VerifyCheck[];
|
|
11
|
+
session: {
|
|
12
|
+
id: string;
|
|
13
|
+
ship_id?: string;
|
|
14
|
+
schema_version?: string;
|
|
15
|
+
agent: string;
|
|
16
|
+
duration_ms?: number;
|
|
17
|
+
actions: number;
|
|
18
|
+
};
|
|
19
|
+
error_code?: string;
|
|
20
|
+
message?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface VerifyCertificateResult {
|
|
23
|
+
outcome: 'pass' | 'fail' | 'error';
|
|
24
|
+
signature_valid: boolean;
|
|
25
|
+
validity: 'valid' | 'expired' | 'not_yet_valid' | 'not_checked';
|
|
26
|
+
certificate: {
|
|
27
|
+
ship_id: string;
|
|
28
|
+
agent_name: string;
|
|
29
|
+
issued_at: string;
|
|
30
|
+
valid_until: string;
|
|
31
|
+
schema_version?: string;
|
|
32
|
+
};
|
|
33
|
+
error_code?: string;
|
|
34
|
+
message?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface CrossVerifyResult {
|
|
37
|
+
outcome: 'pass' | 'fail' | 'error';
|
|
38
|
+
ok: boolean;
|
|
39
|
+
ship_id_status: 'match' | 'mismatch' | 'unknown';
|
|
40
|
+
certificate_status: 'valid' | 'expired' | 'not_yet_valid';
|
|
41
|
+
certificate_signature_valid: boolean;
|
|
42
|
+
authorized_tool_calls: string[];
|
|
43
|
+
unauthorized_tool_calls: string[];
|
|
44
|
+
authorized_tools_never_called: string[];
|
|
45
|
+
error_code?: string;
|
|
46
|
+
message?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Verify a Treeship Session Receipt. Runs the checks derivable from the
|
|
50
|
+
* receipt JSON alone (Merkle root recomputation, inclusion proofs, leaf
|
|
51
|
+
* count, timeline ordering, chain linkage). Signature verification on
|
|
52
|
+
* individual envelopes requires the original envelope bytes and is out of
|
|
53
|
+
* scope for URL-fetched receipts; use the `treeship verify` CLI for that.
|
|
54
|
+
*
|
|
55
|
+
* Accepts:
|
|
56
|
+
* - a parsed receipt object (best for callers that already have the JSON)
|
|
57
|
+
* - a JSON string
|
|
58
|
+
* - a URL string (fetched with the runtime's global fetch)
|
|
59
|
+
* - a URL object
|
|
60
|
+
*/
|
|
61
|
+
export declare function verifyReceipt(target: VerifyTarget): Promise<VerifyReceiptResult>;
|
|
62
|
+
/**
|
|
63
|
+
* Verify an Agent Certificate. Checks the embedded Ed25519 signature
|
|
64
|
+
* against the certificate's embedded public key, then optionally
|
|
65
|
+
* classifies the validity window relative to `now`.
|
|
66
|
+
*
|
|
67
|
+
* Omit `now` (or pass `undefined`) to defer validity classification
|
|
68
|
+
* (signature-only). Pass a `Date` or RFC 3339 string to check expiry.
|
|
69
|
+
*/
|
|
70
|
+
export declare function verifyCertificate(target: VerifyTarget, now?: Date | string): Promise<VerifyCertificateResult>;
|
|
71
|
+
/**
|
|
72
|
+
* Cross-verify a Session Receipt against an Agent Certificate. Answers
|
|
73
|
+
* three questions in one call: do the receipt and certificate reference
|
|
74
|
+
* the same ship? Was the certificate valid at `now`? Was every tool the
|
|
75
|
+
* session called authorized by the certificate?
|
|
76
|
+
*
|
|
77
|
+
* The `ok` field is the roll-up: true iff all three checks pass. Defaults
|
|
78
|
+
* `now` to `Date.now()` if omitted.
|
|
79
|
+
*/
|
|
80
|
+
export declare function crossVerify(receipt: VerifyTarget, certificate: VerifyTarget, now?: Date | string): Promise<CrossVerifyResult>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// @treeship/verify -- zero-dependency cryptographic verification.
|
|
2
|
+
//
|
|
3
|
+
// Install this package alone to verify Treeship Session Receipts and Agent
|
|
4
|
+
// Certificates in any runtime with WebAssembly and fetch. It is deliberately
|
|
5
|
+
// tiny: the only dependency is @treeship/core-wasm (the compiled Rust core,
|
|
6
|
+
// ~170 KB gzipped). There is no transitive dependency on @treeship/sdk,
|
|
7
|
+
// so shipping this to an edge worker, browser dashboard, or Witness doesn't
|
|
8
|
+
// pull the subprocess code path in at all.
|
|
9
|
+
//
|
|
10
|
+
// Same rules `treeship verify` applies from the CLI, same result shape.
|
|
11
|
+
// If a new schema version lands in core, this package picks it up via
|
|
12
|
+
// core-wasm without an API change here.
|
|
13
|
+
let wasmBindings = null;
|
|
14
|
+
async function loadWasm() {
|
|
15
|
+
if (wasmBindings)
|
|
16
|
+
return wasmBindings;
|
|
17
|
+
const mod = (await import('@treeship/core-wasm'));
|
|
18
|
+
wasmBindings = mod;
|
|
19
|
+
return mod;
|
|
20
|
+
}
|
|
21
|
+
async function normalizeToJson(target) {
|
|
22
|
+
if (typeof target === 'object' && !(target instanceof URL)) {
|
|
23
|
+
return JSON.stringify(target);
|
|
24
|
+
}
|
|
25
|
+
const raw = target instanceof URL ? target.toString() : target;
|
|
26
|
+
if (raw.startsWith('http://') || raw.startsWith('https://')) {
|
|
27
|
+
// Accept both the Hub JSON API path and the human-readable mirror.
|
|
28
|
+
const apiUrl = raw.replace('/receipt/', '/v1/receipt/');
|
|
29
|
+
const res = await fetch(apiUrl, { headers: { accept: 'application/json' } });
|
|
30
|
+
if (!res.ok)
|
|
31
|
+
throw new Error(`fetch ${apiUrl} returned HTTP ${res.status}`);
|
|
32
|
+
return await res.text();
|
|
33
|
+
}
|
|
34
|
+
return raw;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Verify a Treeship Session Receipt. Runs the checks derivable from the
|
|
38
|
+
* receipt JSON alone (Merkle root recomputation, inclusion proofs, leaf
|
|
39
|
+
* count, timeline ordering, chain linkage). Signature verification on
|
|
40
|
+
* individual envelopes requires the original envelope bytes and is out of
|
|
41
|
+
* scope for URL-fetched receipts; use the `treeship verify` CLI for that.
|
|
42
|
+
*
|
|
43
|
+
* Accepts:
|
|
44
|
+
* - a parsed receipt object (best for callers that already have the JSON)
|
|
45
|
+
* - a JSON string
|
|
46
|
+
* - a URL string (fetched with the runtime's global fetch)
|
|
47
|
+
* - a URL object
|
|
48
|
+
*/
|
|
49
|
+
export async function verifyReceipt(target) {
|
|
50
|
+
const json = await normalizeToJson(target);
|
|
51
|
+
const wasm = await loadWasm();
|
|
52
|
+
return JSON.parse(wasm.verify_receipt(json));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Verify an Agent Certificate. Checks the embedded Ed25519 signature
|
|
56
|
+
* against the certificate's embedded public key, then optionally
|
|
57
|
+
* classifies the validity window relative to `now`.
|
|
58
|
+
*
|
|
59
|
+
* Omit `now` (or pass `undefined`) to defer validity classification
|
|
60
|
+
* (signature-only). Pass a `Date` or RFC 3339 string to check expiry.
|
|
61
|
+
*/
|
|
62
|
+
export async function verifyCertificate(target, now) {
|
|
63
|
+
const json = await normalizeToJson(target);
|
|
64
|
+
const nowStr = now === undefined ? '' : now instanceof Date ? now.toISOString() : now;
|
|
65
|
+
const wasm = await loadWasm();
|
|
66
|
+
return JSON.parse(wasm.verify_certificate(json, nowStr));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Cross-verify a Session Receipt against an Agent Certificate. Answers
|
|
70
|
+
* three questions in one call: do the receipt and certificate reference
|
|
71
|
+
* the same ship? Was the certificate valid at `now`? Was every tool the
|
|
72
|
+
* session called authorized by the certificate?
|
|
73
|
+
*
|
|
74
|
+
* The `ok` field is the roll-up: true iff all three checks pass. Defaults
|
|
75
|
+
* `now` to `Date.now()` if omitted.
|
|
76
|
+
*/
|
|
77
|
+
export async function crossVerify(receipt, certificate, now) {
|
|
78
|
+
const [receiptJson, certJson] = await Promise.all([
|
|
79
|
+
normalizeToJson(receipt),
|
|
80
|
+
normalizeToJson(certificate),
|
|
81
|
+
]);
|
|
82
|
+
const nowStr = now === undefined
|
|
83
|
+
? new Date().toISOString()
|
|
84
|
+
: now instanceof Date
|
|
85
|
+
? now.toISOString()
|
|
86
|
+
: now;
|
|
87
|
+
const wasm = await loadWasm();
|
|
88
|
+
return JSON.parse(wasm.cross_verify(receiptJson, certJson, nowStr));
|
|
89
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@treeship/verify",
|
|
3
|
+
"version": "0.9.2",
|
|
4
|
+
"description": "Zero-dependency cryptographic verification for Treeship receipts and certificates. Runs anywhere WASM runs: Node, browser, Vercel Edge, Cloudflare Workers, AWS Lambda.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/zerkerlabs/treeship",
|
|
9
|
+
"directory": "packages/verify-js"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://treeship.dev",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"treeship",
|
|
14
|
+
"verification",
|
|
15
|
+
"verify",
|
|
16
|
+
"attestation",
|
|
17
|
+
"receipts",
|
|
18
|
+
"certificate",
|
|
19
|
+
"cross-verification",
|
|
20
|
+
"wasm",
|
|
21
|
+
"edge",
|
|
22
|
+
"serverless"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "dist/index.js",
|
|
26
|
+
"types": "dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"sideEffects": false,
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc",
|
|
40
|
+
"test": "vitest run"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@treeship/core-wasm": "0.9.2"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^25.5.0",
|
|
47
|
+
"typescript": "^5.7.0",
|
|
48
|
+
"vitest": "^3.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|