@7h3/protocol 0.5.6 → 0.6.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/bin/7h3.js CHANGED
@@ -405,8 +405,81 @@ async function cmdKeysServe(argv) {
405
405
  });
406
406
  }
407
407
  // ─── 7h3 add ───────────────────────────────────────────────────────────────────
408
- const ADD_FRAMEWORKS = ['cloudflare-worker', 'nextjs', 'express', 'hono', 'fastify', 'claude-code', 'opencode', 'codex', 'grok'];
408
+ const ADD_FRAMEWORKS = ['webmcp', 'cloudflare-worker', 'nextjs', 'express', 'hono', 'fastify', 'claude-code', 'opencode', 'codex', 'grok'];
409
409
  const FRAMEWORK_SNIPPETS = {
410
+ webmcp: (sender) => `// 7h3 — signed, capability-scoped WebMCP tools
411
+ // Install: npm install @7h3/protocol-webmcp @7h3/protocol
412
+ //
413
+ // WebMCP requires a secure context (HTTPS) and tools must be registered in the
414
+ // TOP-LEVEL page — tools inside an iframe are not discoverable by agents.
415
+
416
+ import { guard, isWebMcpSupported } from '@7h3/protocol-webmcp'
417
+ import { generateEd25519KeypairBase64Url } from '@7h3/protocol'
418
+
419
+ if (isWebMcpSupported()) {
420
+ // Per-session key: fine for signing this visitor's grants and receipts. The
421
+ // manifest is signed separately, at deploy time, by a key the browser never sees.
422
+ const { publicKey, privateKey } = await generateEd25519KeypairBase64Url()
423
+
424
+ const g = guard({
425
+ origin: ${JSON.stringify(sender)},
426
+ privateKey,
427
+ publicKey,
428
+ onConfirm: async (tool, input) =>
429
+ window.confirm(\`Allow \${tool.name}?\\n\\n\${JSON.stringify(input, null, 2)}\`),
430
+ })
431
+
432
+ // An unguarded read: no scope, so no grant is required.
433
+ await g.registerTool({
434
+ name: 'search_items',
435
+ description: 'Search the catalog',
436
+ inputSchema: {
437
+ type: 'object',
438
+ properties: { query: { type: 'string' } },
439
+ required: ['query'],
440
+ additionalProperties: false,
441
+ },
442
+ annotations: { readOnlyHint: true },
443
+ execute: async ({ query }) => searchItems(String(query)),
444
+ })
445
+
446
+ // A guarded write. \`scope\` gates it behind a capability; \`limit\` is a ceiling
447
+ // this site will never exceed, whatever a grant says.
448
+ await g.registerTool({
449
+ name: 'place_order',
450
+ description: 'Place an order for the current cart',
451
+ inputSchema: {
452
+ type: 'object',
453
+ properties: { cartId: { type: 'string' }, amountCents: { type: 'number' } },
454
+ required: ['cartId', 'amountCents'],
455
+ additionalProperties: false,
456
+ },
457
+ annotations: { destructiveHint: true },
458
+ scope: 'orders/place',
459
+ limit: { field: 'amountCents', max: 500_00 },
460
+ confirm: true,
461
+ execute: async ({ cartId }) => placeOrder(String(cartId)),
462
+ })
463
+
464
+ // Wire this to a consent control in your own UI — never grant automatically.
465
+ // The token is held page-side, so it never passes through the agent.
466
+ document.querySelector('#allow-agent')?.addEventListener('click', async () => {
467
+ await g.grant({
468
+ subject: 'browser-agent',
469
+ scopes: ['orders/place'],
470
+ caps: { amountCents: 100_00 }, // bound inside the signed token
471
+ ttlMs: 10 * 60_000, // authority lapses on its own
472
+ })
473
+ })
474
+
475
+ // Every call — allowed and refused — lands on a hash-chained signed log.
476
+ g.on((event) => {
477
+ if (event.type === 'call') {
478
+ console.log(event.receipt.outcome, event.receipt.tool, event.receipt.reason ?? '')
479
+ }
480
+ })
481
+ }
482
+ `,
410
483
  'cloudflare-worker': (sender) => `// cloudflare/src/worker.ts — 7h3 Gateway Worker
411
484
  // Install: npm install @7h3/protocol
412
485
  // See: cloudflare/DEPLOY.md for full setup
package/cborCodec.d.ts CHANGED
@@ -21,11 +21,24 @@ export declare class CborEncoder {
21
21
  private _encodeHeadBytes;
22
22
  private _concat;
23
23
  }
24
+ /**
25
+ * Maximum nesting depth accepted while decoding.
26
+ *
27
+ * RFC 8949 §10 calls this out explicitly: a decoder that recurses per nesting
28
+ * level turns a handful of attacker bytes into a stack overflow. `0x81` is
29
+ * "array of 1", so 50 KB of repeated `0x81` nests 50 000 deep and blows the
30
+ * stack — and CBOR arrives straight off the wire through the HTTP binding.
31
+ * 64 is far beyond any real envelope, which nests a handful of levels at most.
32
+ */
33
+ export declare const MAX_CBOR_DEPTH = 64;
24
34
  export declare class CborDecoder {
25
35
  private data;
26
36
  private offset;
37
+ private depth;
27
38
  decode(data: Uint8Array): unknown;
39
+ /** Depth-counting wrapper around the recursive decode body. */
28
40
  private _decode;
41
+ private _decodeItem;
29
42
  private _decodeUint;
30
43
  private _readByte;
31
44
  private _readBytes;