@antseed/cli 0.1.177 → 0.1.179

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 (39) hide show
  1. package/README.md +3 -0
  2. package/dist/cli/commands/apps/index.d.ts +42 -0
  3. package/dist/cli/commands/apps/index.d.ts.map +1 -0
  4. package/dist/cli/commands/apps/index.js +224 -0
  5. package/dist/cli/commands/apps/index.js.map +1 -0
  6. package/dist/cli/commands/apps/index.test.d.ts +2 -0
  7. package/dist/cli/commands/apps/index.test.d.ts.map +1 -0
  8. package/dist/cli/commands/apps/index.test.js +206 -0
  9. package/dist/cli/commands/apps/index.test.js.map +1 -0
  10. package/dist/cli/commands/buyer/deposit.d.ts.map +1 -1
  11. package/dist/cli/commands/buyer/deposit.js +9 -1
  12. package/dist/cli/commands/buyer/deposit.js.map +1 -1
  13. package/dist/cli/index.js +2 -0
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/gateway/gateway.test.js +31 -0
  16. package/dist/gateway/gateway.test.js.map +1 -1
  17. package/dist/gateway/server.d.ts.map +1 -1
  18. package/dist/gateway/server.js +4 -1
  19. package/dist/gateway/server.js.map +1 -1
  20. package/dist/proxy/buyer-proxy.d.ts.map +1 -1
  21. package/dist/proxy/buyer-proxy.js +34 -14
  22. package/dist/proxy/buyer-proxy.js.map +1 -1
  23. package/dist/proxy/buyer-proxy.test.js +96 -0
  24. package/dist/proxy/buyer-proxy.test.js.map +1 -1
  25. package/dist/proxy/credential-headers.d.ts +17 -0
  26. package/dist/proxy/credential-headers.d.ts.map +1 -0
  27. package/dist/proxy/credential-headers.js +29 -0
  28. package/dist/proxy/credential-headers.js.map +1 -0
  29. package/dist/proxy/deposit-watcher.d.ts +19 -0
  30. package/dist/proxy/deposit-watcher.d.ts.map +1 -1
  31. package/dist/proxy/deposit-watcher.js +44 -8
  32. package/dist/proxy/deposit-watcher.js.map +1 -1
  33. package/dist/proxy/deposit-watcher.test.js +27 -4
  34. package/dist/proxy/deposit-watcher.test.js.map +1 -1
  35. package/dist/system-proxy/proxy-server.js +2 -2
  36. package/dist/system-proxy/proxy-server.js.map +1 -1
  37. package/dist/system-proxy/proxy-server.test.js +40 -0
  38. package/dist/system-proxy/proxy-server.test.js.map +1 -1
  39. package/package.json +7 -6
@@ -309,6 +309,25 @@ test('BuyerProxy accepts a custom background refresh interval', () => {
309
309
  });
310
310
  assert.equal(proxy._bgRefreshIntervalMs, 15_000);
311
311
  });
312
+ test('BuyerProxy rewrites upstream 413 responses into a clear request size error', () => {
313
+ const proxy = new BuyerProxy({
314
+ port: 0,
315
+ dataDir: '/tmp/antseed-test',
316
+ node: { router: null },
317
+ });
318
+ const upstream = {
319
+ requestId: 'req-413',
320
+ statusCode: 413,
321
+ headers: { 'content-type': 'text/html' },
322
+ body: Buffer.from('<html><title>413 Request Entity Too Large</title></html>'),
323
+ };
324
+ const rewritten = proxy._withFriendlyUploadLimitError(upstream, 37 * 1024 * 1024, 'opus-5.5');
325
+ const { error } = JSON.parse(Buffer.from(rewritten.body).toString('utf-8'));
326
+ assert.equal(rewritten.headers['content-type'], 'application/json');
327
+ assert.equal(error.code, 'request_too_large');
328
+ assert.match(error.message, /Request body for opus-5\.5 is 37 MiB/);
329
+ assert.match(error.message, /Images and large attachments/);
330
+ });
312
331
  test('BuyerProxy reloads model routing preferences from config', async (t) => {
313
332
  const dataDir = await mkdtemp(join(tmpdir(), 'antseed-routing-config-'));
314
333
  const configPath = join(dataDir, 'config.json');
@@ -784,6 +803,29 @@ test('conversation routing keeps the actual peer as a soft preference and fails
784
803
  assert.equal(route.statusCode, 200);
785
804
  assert.equal(JSON.parse(route.body).conversation.lastModel, `${rankedFirst.peerId}@Kimi K3`);
786
805
  });
806
+ test('BuyerProxy does not forward the Expect header to the seller', async () => {
807
+ const peer = makePeer('c', ['openai']);
808
+ peer.providerServiceApiProtocols = {
809
+ openai: { services: { 'kimi-k3': ['openai-chat-completions'] } },
810
+ };
811
+ const proxy = makeBuyerProxyWithPeers([peer], [peer], permissiveRouter());
812
+ let forwardedHeaders;
813
+ proxy._node.sendRequest = async (_peer, request) => {
814
+ forwardedHeaders = request.headers;
815
+ return {
816
+ requestId: request.requestId,
817
+ statusCode: 200,
818
+ headers: { 'content-type': 'application/json' },
819
+ body: Buffer.from('{}'),
820
+ };
821
+ };
822
+ await invokeProxy(proxy, makeProxyRequest({
823
+ headers: { expect: '100-continue' },
824
+ body: { model: 'kimi-k3', messages: [{ role: 'user', content: 'hello' }] },
825
+ }));
826
+ assert.ok(forwardedHeaders);
827
+ assert.equal(forwardedHeaders['expect'], undefined);
828
+ });
787
829
  test('model-only request applies a cached-input pricing reputation penalty', async () => {
788
830
  const priced = makePeer('a', ['openai']);
789
831
  priced.reputationScore = 75;
@@ -3384,6 +3426,60 @@ test('getSweepReceipt returns cached relayer receipts case-insensitively', () =>
3384
3426
  assert.equal(proxy.getSweepReceipt(nonce.toLowerCase()), receipt);
3385
3427
  assert.equal(proxy.getSweepReceipt('0x' + '00'.repeat(32)), null);
3386
3428
  });
3429
+ // Issue #1104: a client's own provider credentials must never reach a seller.
3430
+ // Sellers authenticate their upstream with their own keys; the buyer pays
3431
+ // on-chain. A malicious seller would otherwise read the raw key off the wire.
3432
+ const CLIENT_CREDENTIAL_HEADERS = {
3433
+ authorization: 'Bearer sk-real',
3434
+ 'proxy-authorization': 'Basic cHJveHk6c2VjcmV0',
3435
+ 'x-api-key': 'sk-ant-real',
3436
+ 'x-goog-api-key': 'AIza-real',
3437
+ 'api-key': 'azure-real',
3438
+ cookie: 'session=real',
3439
+ };
3440
+ test('buyer proxy does not forward client credential headers to the seller (buffered)', async () => {
3441
+ const peer = makePeer('e', ['openai']);
3442
+ const proxy = makeBuyerProxyWithPeers([peer], [peer], permissiveRouter());
3443
+ let seen = null;
3444
+ proxy._node.sendRequest = async (_peer, request) => {
3445
+ seen = request.headers;
3446
+ return { requestId: request.requestId, statusCode: 200, headers: { 'content-type': 'application/json' }, body: Buffer.from('{}') };
3447
+ };
3448
+ const res = await invokeProxy(proxy, makeProxyRequest({
3449
+ headers: {
3450
+ ...CLIENT_CREDENTIAL_HEADERS,
3451
+ 'x-antseed-pin-peer': peer.peerId,
3452
+ 'anthropic-version': '2023-06-01',
3453
+ 'x-antseed-spending-auth': 'signed-auth-payload',
3454
+ },
3455
+ }));
3456
+ assert.equal(res.statusCode, 200);
3457
+ assert.ok(seen, 'seller received a request');
3458
+ const leaked = Object.keys(CLIENT_CREDENTIAL_HEADERS).filter((name) => seen[name] !== undefined);
3459
+ assert.deepEqual(leaked, [], `credential headers reached the seller: ${leaked.join(', ')}`);
3460
+ // Non-credential client headers still pass through.
3461
+ assert.equal(seen['anthropic-version'], '2023-06-01');
3462
+ assert.equal(seen['content-type'], 'application/json');
3463
+ // AntSeed's own payment-auth header is not a client credential.
3464
+ assert.equal(seen['x-antseed-spending-auth'], 'signed-auth-payload');
3465
+ });
3466
+ test('buyer proxy does not forward client credential headers to the seller (streaming)', async () => {
3467
+ const peer = makePeer('f', ['openai']);
3468
+ const proxy = makeBuyerProxyWithPeers([peer], [peer], permissiveRouter());
3469
+ let seen = null;
3470
+ proxy._node.sendRequestStream = async (_peer, request) => {
3471
+ seen = request.headers;
3472
+ return { requestId: request.requestId, statusCode: 200, headers: { 'content-type': 'application/json' }, body: Buffer.from('{}') };
3473
+ };
3474
+ proxy._node.sendRequest = proxy._node.sendRequestStream;
3475
+ await invokeProxy(proxy, makeProxyRequest({
3476
+ headers: { ...CLIENT_CREDENTIAL_HEADERS, accept: 'text/event-stream', 'x-antseed-pin-peer': peer.peerId },
3477
+ body: { model: 'gpt-4o', stream: true, messages: [] },
3478
+ }));
3479
+ assert.ok(seen, 'seller received a request');
3480
+ const leaked = Object.keys(CLIENT_CREDENTIAL_HEADERS).filter((name) => seen[name] !== undefined);
3481
+ assert.deepEqual(leaked, [], `credential headers reached the seller: ${leaked.join(', ')}`);
3482
+ });
3387
3483
  test('switching a chat route while a request is in flight is not reverted when that request completes', async () => {
3388
3484
  const dir = await mkdtemp(join(tmpdir(), 'antseed-buyer-route-race-'));
3389
3485
  try {