@cosmicdrift/kumiko-bundled-features 0.197.0 → 0.197.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-bundled-features",
3
- "version": "0.197.0",
3
+ "version": "0.197.1",
4
4
  "description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -125,12 +125,12 @@
125
125
  "./step-dispatcher": "./src/step-dispatcher/index.ts"
126
126
  },
127
127
  "dependencies": {
128
- "@cosmicdrift/kumiko-dispatcher-live": "0.197.0",
129
- "@cosmicdrift/kumiko-framework": "0.197.0",
130
- "@cosmicdrift/kumiko-headless": "0.197.0",
131
- "@cosmicdrift/kumiko-renderer": "0.197.0",
132
- "@cosmicdrift/kumiko-renderer-web": "0.197.0",
133
- "@cosmicdrift/kumiko-types": "0.197.0",
128
+ "@cosmicdrift/kumiko-dispatcher-live": "0.197.1",
129
+ "@cosmicdrift/kumiko-framework": "0.197.1",
130
+ "@cosmicdrift/kumiko-headless": "0.197.1",
131
+ "@cosmicdrift/kumiko-renderer": "0.197.1",
132
+ "@cosmicdrift/kumiko-renderer-web": "0.197.1",
133
+ "@cosmicdrift/kumiko-types": "0.197.1",
134
134
  "@mollie/api-client": "^4.5.0",
135
135
  "@node-rs/argon2": "^2.0.2",
136
136
  "@types/mailparser": "^3.4.6",
@@ -68,6 +68,9 @@ const widgetEntity = createEntity({
68
68
  full: { maxEdge: 4096, format: "webp" },
69
69
  plan: { maxEdge: 4096, format: "webp" },
70
70
  heroWide: { maxEdge: 3200, format: "webp" },
71
+ // No format — resize-without-reformat, the legitimate reason
72
+ // spec.format can be omitted (see #2021).
73
+ raw: { maxEdge: 160 },
71
74
  },
72
75
  }),
73
76
  plain: createImageField(),
@@ -133,10 +136,15 @@ async function uploadFile(
133
136
  asUser: typeof userA,
134
137
  attach: { entityType: string; entityId: string },
135
138
  fieldName = "img",
139
+ mimeType = "image/jpeg",
136
140
  ): Promise<string> {
137
141
  const token = await stack.jwt.sign(asUser);
138
142
  const fd = new FormData();
139
- fd.append("file", new File([Buffer.from([1, 2, 3])], "img.jpg", { type: "image/jpeg" }));
143
+ // Bun's server-side multipart parser derives File.type from the filename
144
+ // extension, not the declared part Content-Type — so the extension must
145
+ // match `mimeType` for the upload to actually carry it through.
146
+ const extension = mimeType.split("/")[1]?.split("+")[0] ?? "bin";
147
+ fd.append("file", new File([Buffer.from([1, 2, 3])], `img.${extension}`, { type: mimeType }));
140
148
  fd.append("entityType", attach.entityType);
141
149
  fd.append("entityId", attach.entityId);
142
150
  fd.append("fieldName", fieldName);
@@ -330,6 +338,25 @@ describe("GET /media/:fileRefId/:variant (anonymous, default-deny)", () => {
330
338
  expect(res.status).toBe(404);
331
339
  });
332
340
 
341
+ // #2021 — the "raw" variant has no `format`, so its mimeType comes from
342
+ // outputMimeType's fallback branch. `img` has no `accept`, so the source
343
+ // mimeType is exactly the client-controlled `file.type` from the upload —
344
+ // this proves the anonymous route no longer echoes it verbatim.
345
+ test("a variant with no spec.format never echoes a non-safe, client-controlled source mimeType", async () => {
346
+ const fileId = await uploadFile(
347
+ userA,
348
+ { entityType: "widget", entityId: PUBLIC_WIDGET_ID },
349
+ "img",
350
+ "image/svg+xml",
351
+ );
352
+
353
+ const res = await stack.app.request(`http://${HOST_A}/media/${fileId}/raw`);
354
+
355
+ expect(res.status).toBe(200);
356
+ expect(res.headers.get("Content-Type")).not.toBe("image/svg+xml");
357
+ expect(res.headers.get("Content-Type")).toBe("application/octet-stream");
358
+ });
359
+
333
360
  test("more than `limit` requests from the same IP within the window → 429 (Step-1 regression)", async () => {
334
361
  const fileId = await uploadFile(userA, { entityType: "widget", entityId: PUBLIC_WIDGET_ID });
335
362
  const xff = "203.0.113.9";