@elisym/sdk 0.25.3 → 0.25.4

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.d.cts CHANGED
@@ -801,13 +801,22 @@ declare class BlossomService {
801
801
  /** Delete a blob by sha256 (BUD-02). Blossom only - there is no fallback for deletes. */
802
802
  delete(identity: ElisymIdentity, sha256: string): Promise<void>;
803
803
  /**
804
- * Download a public blob (BUD-01 GET, no auth). Bounds memory on the ACTUAL streamed bytes (never
805
- * the declared Content-Length) and verifies the sha256 when `expectedSha256` is given. Browser-safe.
804
+ * Download a content-addressed blob from THIS Blossom server (BUD-01 GET, no auth). Bounds memory
805
+ * on the ACTUAL streamed bytes (never the declared Content-Length) and verifies the sha256 when
806
+ * `expectedSha256` is given. Browser-safe.
807
+ *
808
+ * SSRF guard: `url` typically arrives inside a remote counterparty's encrypted job envelope, so it
809
+ * is untrusted. elisym blobs are content-addressed on the single configured server (`seedBytes`
810
+ * refuses non-content-addressed fallbacks), so a legitimate URL is always `<serverUrl>/<sha256>`.
811
+ * The origin is pinned to `serverUrl` and redirects are refused, so a crafted url (or a 30x from
812
+ * the host) can't coerce a fetch to loopback, cloud-metadata, or internal addresses. Federation
813
+ * across Blossom servers would replace this single-origin pin with an explicit allowlist.
806
814
  */
807
815
  download(url: string, opts?: {
808
816
  maxBytes?: number;
809
817
  timeoutMs?: number;
810
818
  expectedSha256?: string;
819
+ signal?: AbortSignal;
811
820
  }): Promise<Uint8Array>;
812
821
  private uploadToBlossom;
813
822
  private authHeader;
@@ -1171,6 +1180,8 @@ interface BlossomBlobTransport {
1171
1180
  transport: BlossomTransport;
1172
1181
  senderPubkey: string;
1173
1182
  maxBytes?: number;
1183
+ /** Abort the in-flight download (e.g. job stop() / input-fetch budget). */
1184
+ signal?: AbortSignal;
1174
1185
  }): Promise<Uint8Array>;
1175
1186
  }
1176
1187
  declare function createBlossomTransport(opts: {
package/dist/index.d.ts CHANGED
@@ -801,13 +801,22 @@ declare class BlossomService {
801
801
  /** Delete a blob by sha256 (BUD-02). Blossom only - there is no fallback for deletes. */
802
802
  delete(identity: ElisymIdentity, sha256: string): Promise<void>;
803
803
  /**
804
- * Download a public blob (BUD-01 GET, no auth). Bounds memory on the ACTUAL streamed bytes (never
805
- * the declared Content-Length) and verifies the sha256 when `expectedSha256` is given. Browser-safe.
804
+ * Download a content-addressed blob from THIS Blossom server (BUD-01 GET, no auth). Bounds memory
805
+ * on the ACTUAL streamed bytes (never the declared Content-Length) and verifies the sha256 when
806
+ * `expectedSha256` is given. Browser-safe.
807
+ *
808
+ * SSRF guard: `url` typically arrives inside a remote counterparty's encrypted job envelope, so it
809
+ * is untrusted. elisym blobs are content-addressed on the single configured server (`seedBytes`
810
+ * refuses non-content-addressed fallbacks), so a legitimate URL is always `<serverUrl>/<sha256>`.
811
+ * The origin is pinned to `serverUrl` and redirects are refused, so a crafted url (or a 30x from
812
+ * the host) can't coerce a fetch to loopback, cloud-metadata, or internal addresses. Federation
813
+ * across Blossom servers would replace this single-origin pin with an explicit allowlist.
806
814
  */
807
815
  download(url: string, opts?: {
808
816
  maxBytes?: number;
809
817
  timeoutMs?: number;
810
818
  expectedSha256?: string;
819
+ signal?: AbortSignal;
811
820
  }): Promise<Uint8Array>;
812
821
  private uploadToBlossom;
813
822
  private authHeader;
@@ -1171,6 +1180,8 @@ interface BlossomBlobTransport {
1171
1180
  transport: BlossomTransport;
1172
1181
  senderPubkey: string;
1173
1182
  maxBytes?: number;
1183
+ /** Abort the in-flight download (e.g. job stop() / input-fetch budget). */
1184
+ signal?: AbortSignal;
1174
1185
  }): Promise<Uint8Array>;
1175
1186
  }
1176
1187
  declare function createBlossomTransport(opts: {
package/dist/index.js CHANGED
@@ -1262,18 +1262,38 @@ var BlossomService = class {
1262
1262
  }
1263
1263
  }
1264
1264
  /**
1265
- * Download a public blob (BUD-01 GET, no auth). Bounds memory on the ACTUAL streamed bytes (never
1266
- * the declared Content-Length) and verifies the sha256 when `expectedSha256` is given. Browser-safe.
1265
+ * Download a content-addressed blob from THIS Blossom server (BUD-01 GET, no auth). Bounds memory
1266
+ * on the ACTUAL streamed bytes (never the declared Content-Length) and verifies the sha256 when
1267
+ * `expectedSha256` is given. Browser-safe.
1268
+ *
1269
+ * SSRF guard: `url` typically arrives inside a remote counterparty's encrypted job envelope, so it
1270
+ * is untrusted. elisym blobs are content-addressed on the single configured server (`seedBytes`
1271
+ * refuses non-content-addressed fallbacks), so a legitimate URL is always `<serverUrl>/<sha256>`.
1272
+ * The origin is pinned to `serverUrl` and redirects are refused, so a crafted url (or a 30x from
1273
+ * the host) can't coerce a fetch to loopback, cloud-metadata, or internal addresses. Federation
1274
+ * across Blossom servers would replace this single-origin pin with an explicit allowlist.
1267
1275
  */
1268
1276
  async download(url, opts = {}) {
1277
+ if (new URL(url).origin !== new URL(this.serverUrl).origin) {
1278
+ throw new Error(`Refusing to download from a non-Blossom origin: ${url}`);
1279
+ }
1269
1280
  const maxBytes = opts.maxBytes ?? LIMITS.MAX_FILE_SIZE;
1270
1281
  const controller = new AbortController();
1271
1282
  const timer = setTimeout(
1272
1283
  () => controller.abort(),
1273
1284
  opts.timeoutMs ?? DEFAULTS.BLOSSOM_FETCH_TIMEOUT_MS
1274
1285
  );
1286
+ const externalSignal = opts.signal;
1287
+ const onExternalAbort = () => controller.abort();
1288
+ if (externalSignal !== void 0) {
1289
+ if (externalSignal.aborted) {
1290
+ controller.abort();
1291
+ } else {
1292
+ externalSignal.addEventListener("abort", onExternalAbort, { once: true });
1293
+ }
1294
+ }
1275
1295
  try {
1276
- const res = await fetch(url, { signal: controller.signal });
1296
+ const res = await fetch(url, { signal: controller.signal, redirect: "error" });
1277
1297
  if (!res.ok) {
1278
1298
  throw new Error(`Download failed: ${res.status} ${res.statusText}`);
1279
1299
  }
@@ -1315,6 +1335,9 @@ var BlossomService = class {
1315
1335
  return bytes;
1316
1336
  } finally {
1317
1337
  clearTimeout(timer);
1338
+ if (externalSignal !== void 0) {
1339
+ externalSignal.removeEventListener("abort", onExternalAbort);
1340
+ }
1318
1341
  }
1319
1342
  }
1320
1343
  async uploadToBlossom(identity, bytes, hashHex, mime) {
@@ -3712,11 +3735,12 @@ function createBlossomTransport(opts) {
3712
3735
  enc: { alg: "AES-256-GCM", iv, key: wrappedKey }
3713
3736
  };
3714
3737
  },
3715
- async fetchToBytes({ transport, senderPubkey, maxBytes }) {
3738
+ async fetchToBytes({ transport, senderPubkey, maxBytes, signal }) {
3716
3739
  const plaintextCap = maxBytes ?? LIMITS.MAX_BLOSSOM_ENCRYPTED_BYTES;
3717
3740
  const ciphertext = await blossom.download(transport.url, {
3718
3741
  maxBytes: plaintextCap + AES_GCM_TAG_BYTES,
3719
- expectedSha256: transport.sha256
3742
+ expectedSha256: transport.sha256,
3743
+ signal
3720
3744
  });
3721
3745
  return decryptBytesFromSender(
3722
3746
  ciphertext,