@bounded-sh/server 0.0.55 → 0.0.57

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/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { init as init$1, convertRemainingAccounts, buildSetDocumentsTransaction, normalizeEvmAddress, genAuthNonce, getConfig, genSiweMessage, createSessionWithSignature, assertCanonicalBoundedRuntimeWriteCalldata, live as live$1, set as set$1, setMany as setMany$1, setFile as setFile$1, get as get$1, getMany as getMany$1, getFiles as getFiles$1, search as search$1, runQuery as runQuery$1, runQueryMany as runQueryMany$1, runExpression as runExpression$1, runExpressionMany as runExpressionMany$1, count as count$1, aggregate as aggregate$1, queryAggregate as queryAggregate$1, subscribe as subscribe$1, functions as functions$1, ServerSessionManager, getWebhookKeysUrl } from '@bounded-sh/core';
1
+ import { init as init$1, convertRemainingAccounts, buildSetDocumentsTransaction, normalizeEvmAddress, genAuthNonce, getConfig, genSiweMessage, createSessionWithSignature, assertCanonicalBoundedRuntimeWriteCalldata, live as live$1, set as set$1, setMany as setMany$1, setFile as setFile$1, get as get$1, getMany as getMany$1, getFiles as getFiles$1, search as search$1, runQuery as runQuery$1, runQueryMany as runQueryMany$1, runExpression as runExpression$1, runExpressionMany as runExpressionMany$1, count as count$1, aggregate as aggregate$1, queryAggregate as queryAggregate$1, subscribe as subscribe$1, functions as functions$1, ServerSessionManager, getWebhookKeysUrl, getConfiguredAppId } from '@bounded-sh/core';
2
2
  export { BoundedDeclineError, DECLINE_EVENT_NAME, FunctionInvokeError, InsufficientBalanceError, LiveIntentError, deriveDecline, getConfig, getWebhookKeysUrl } from '@bounded-sh/core';
3
3
  import { Connection, VersionedTransaction, ComputeBudgetProgram, SystemProgram, Keypair } from '@solana/web3.js';
4
4
  import bs58 from 'bs58';
@@ -54,8 +54,8 @@ async function getIdToken() {
54
54
  }
55
55
 
56
56
  const PRESIGNED_BLOCKHASH_ERROR = 'Server signedTransaction blockhash is stale or expired';
57
- const BOUNDED_PROGRAM_MAINNET = 'poof4b5pk1L9tmThvBmaABjcyjfhFGbMbQP5BXk2QZp';
58
- const BOUNDED_PROGRAM_DEVNET = 'taro6CvKqwrYrDc16ufYgzQ2NZcyyVKStffbtudrhRu';
57
+ const BOUNDED_PROGRAM_MAINNET = 'openTv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6F';
58
+ const BOUNDED_PROGRAM_DEVNET = 'openTv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6F';
59
59
  const COMPUTE_BUDGET_PROGRAM = 'ComputeBudget111111111111111111111111111111';
60
60
  const SYSTEM_PROGRAM_ID = '11111111111111111111111111111111';
61
61
  const SUPPORTED_SOLANA_NETWORKS = ['solana_devnet', 'solana_mainnet'];
@@ -790,17 +790,23 @@ class EvmKeypairProvider {
790
790
  }
791
791
  }
792
792
 
793
- var __rest = (undefined && undefined.__rest) || function (s, e) {
794
- var t = {};
795
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
796
- t[p] = s[p];
797
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
798
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
799
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
800
- t[p[i]] = s[p[i]];
801
- }
802
- return t;
803
- };
793
+ // #023: strip caller-supplied auth headers so an operation runs as the isolated wallet, not
794
+ // as whatever headers were forwarded in. HTTP header names are case-insensitive on the wire,
795
+ // so a fixed `Authorization`/`authorization` destructure let a mixed-case `AUTHORIZATION`
796
+ // survive and ride along. Drop every header whose lowercased name is authorization (plus
797
+ // proxy-authorization / cookie, which would likewise leak a caller identity into the session).
798
+ const STRIP_HEADER_NAMES = new Set(["authorization", "proxy-authorization", "cookie"]);
799
+ function stripCallerAuthHeaders(headers) {
800
+ if (!headers)
801
+ return undefined;
802
+ const rest = {};
803
+ for (const [name, value] of Object.entries(headers)) {
804
+ if (!STRIP_HEADER_NAMES.has(name.toLowerCase()))
805
+ rest[name] = value;
806
+ }
807
+ return Object.keys(rest).length > 0 ? rest : undefined;
808
+ }
809
+
804
810
  /* ------------------------------------------------------------------ */
805
811
  /* Keypair parsing */
806
812
  /* ------------------------------------------------------------------ */
@@ -848,7 +854,11 @@ class WalletClient {
848
854
  this.evmTransactionPolicy = evmTransactionPolicy;
849
855
  this.live = {
850
856
  intent: (roomPath, intent, opts = {}) => live$1.intent(roomPath, intent, this.mergeLiveIntentOptions(opts)),
851
- status: (roomPath, opts = {}) => live$1.status(roomPath, Object.assign(Object.assign({}, opts), { headers: this.sanitizeHeaders(opts.headers) })),
857
+ // Route status through the SAME auth merge intent uses so a server-keypair
858
+ // agent's session token rides the request. Without it the agent sends the
859
+ // public app id only and the server returns the slim anonymous shape rather
860
+ // than the full diagnostics the authenticated agent is entitled to (#102).
861
+ status: (roomPath, opts = {}) => live$1.status(roomPath, this.mergeLiveStatusOptions(opts)),
852
862
  // Agent-as-player view stream: subscribe to THIS wallet's per-player view
853
863
  // under the wallet's own session (per-subscription overrides — no global
854
864
  // auth needed). The view id defaults to this wallet's @user.id (== its
@@ -888,12 +898,9 @@ class WalletClient {
888
898
  this.sessionManager.clearSession();
889
899
  }, _walletAddress: this.address }, (this.evmTransactionPolicy ? { _evmTransactionPolicy: this.evmTransactionPolicy } : {}));
890
900
  }
891
- /** Strip Authorization from caller headers to prevent overriding wallet token */
901
+ /** #023: strip caller Authorization (any casing) so operations run as the isolated wallet. */
892
902
  sanitizeHeaders(headers) {
893
- if (!headers)
894
- return undefined;
895
- const _a = headers, { Authorization, authorization } = _a, rest = __rest(_a, ["Authorization", "authorization"]);
896
- return Object.keys(rest).length > 0 ? rest : undefined;
903
+ return stripCallerAuthHeaders(headers);
897
904
  }
898
905
  mergeSetOverrides(opts) {
899
906
  var _a;
@@ -910,6 +917,12 @@ class WalletClient {
910
917
  const merged = Object.assign(Object.assign({}, opts), { headers: this.sanitizeHeaders(opts === null || opts === void 0 ? void 0 : opts.headers), _overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts._overrides), { headers: this.sanitizeHeaders((_a = opts === null || opts === void 0 ? void 0 : opts._overrides) === null || _a === void 0 ? void 0 : _a.headers), _getAuthHeaders: ovr._getAuthHeaders, _clearAuth: ovr._clearAuth, _walletAddress: ovr._walletAddress }) });
911
918
  return merged;
912
919
  }
920
+ mergeLiveStatusOptions(opts) {
921
+ var _a;
922
+ const ovr = this.buildOverrides();
923
+ const merged = Object.assign(Object.assign({}, opts), { headers: this.sanitizeHeaders(opts === null || opts === void 0 ? void 0 : opts.headers), _overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts._overrides), { headers: this.sanitizeHeaders((_a = opts === null || opts === void 0 ? void 0 : opts._overrides) === null || _a === void 0 ? void 0 : _a.headers), _getAuthHeaders: ovr._getAuthHeaders, _clearAuth: ovr._clearAuth, _walletAddress: ovr._walletAddress }) });
924
+ return merged;
925
+ }
913
926
  /* ---- Data operations ---- */
914
927
  async set(path, document, options) {
915
928
  return set$1(path, document, this.mergeSetOverrides(options));
@@ -1324,8 +1337,29 @@ async function verifyWebhook(rawBody, headers, opts = {}) {
1324
1337
  if (Math.abs(nowSeconds - payload.timestamp) > maxSkew) {
1325
1338
  throw new WebhookVerificationError(`Webhook timestamp outside allowed skew of ${maxSkew}s (delta ${Math.abs(nowSeconds - payload.timestamp)}s).`);
1326
1339
  }
1327
- if (opts.expectedAppId !== undefined && payload.appId !== opts.expectedAppId) {
1328
- throw new WebhookVerificationError(`Webhook appId "${payload.appId}" does not match expected appId "${opts.expectedAppId}".`);
1340
+ // #072: bind the delivery to an app by DEFAULT. The shared platform signing key means a
1341
+ // valid signature proves "from Bounded", not "from your app"; payload.appId is the ONLY
1342
+ // per-app separation. Previously the bind ran only when expectedAppId was passed, so the
1343
+ // secure check was opt-in and a malicious app owner could point their app's webhook at a
1344
+ // victim receiver. Now: an explicit expectedAppId is authoritative; otherwise bind to the
1345
+ // init()-time app id; a multi-app receiver must set allowAnyAppId to accept any app; and
1346
+ // when no app id is resolvable we FAIL CLOSED rather than silently trust any app.
1347
+ if (opts.expectedAppId !== undefined) {
1348
+ if (payload.appId !== opts.expectedAppId) {
1349
+ throw new WebhookVerificationError(`Webhook appId "${payload.appId}" does not match expected appId "${opts.expectedAppId}".`);
1350
+ }
1351
+ }
1352
+ else if (!opts.allowAnyAppId) {
1353
+ const configuredAppId = getConfiguredAppId();
1354
+ if (!configuredAppId) {
1355
+ throw new WebhookVerificationError("verifyWebhook cannot determine which app this webhook is for: no expectedAppId was " +
1356
+ "passed and the SDK has no init()-time app id. Pass expectedAppId, or set " +
1357
+ "allowAnyAppId: true to intentionally accept webhooks from any app.");
1358
+ }
1359
+ if (payload.appId !== configuredAppId) {
1360
+ throw new WebhookVerificationError(`Webhook appId "${payload.appId}" does not match the configured app id "${configuredAppId}". ` +
1361
+ "Pass allowAnyAppId: true to accept webhooks from any app.");
1362
+ }
1329
1363
  }
1330
1364
  // Replay cache: reject a previously-seen signed delivery within the skew
1331
1365
  // window. The worker's payload.id is the document id/path, so using it alone