@cat-factory/server 0.288.0 → 0.290.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.
Files changed (41) hide show
  1. package/dist/agents/containerAgentBody.d.ts.map +1 -1
  2. package/dist/agents/containerAgentBody.js +50 -18
  3. package/dist/agents/containerAgentBody.js.map +1 -1
  4. package/dist/app.d.ts.map +1 -1
  5. package/dist/app.js +6 -0
  6. package/dist/app.js.map +1 -1
  7. package/dist/modules/artifacts/HarnessArtifactController.d.ts.map +1 -1
  8. package/dist/modules/artifacts/HarnessArtifactController.js +2 -38
  9. package/dist/modules/artifacts/HarnessArtifactController.js.map +1 -1
  10. package/dist/modules/artifacts/HarnessAssetController.d.ts +12 -0
  11. package/dist/modules/artifacts/HarnessAssetController.d.ts.map +1 -0
  12. package/dist/modules/artifacts/HarnessAssetController.js +181 -0
  13. package/dist/modules/artifacts/HarnessAssetController.js.map +1 -0
  14. package/dist/modules/artifacts/assetArtifacts.d.ts +50 -0
  15. package/dist/modules/artifacts/assetArtifacts.d.ts.map +1 -0
  16. package/dist/modules/artifacts/assetArtifacts.js +104 -0
  17. package/dist/modules/artifacts/assetArtifacts.js.map +1 -0
  18. package/dist/modules/artifacts/harnessSession.d.ts +22 -0
  19. package/dist/modules/artifacts/harnessSession.d.ts.map +1 -0
  20. package/dist/modules/artifacts/harnessSession.js +44 -0
  21. package/dist/modules/artifacts/harnessSession.js.map +1 -0
  22. package/dist/modules/artifacts/imageArtifacts.d.ts +5 -0
  23. package/dist/modules/artifacts/imageArtifacts.d.ts.map +1 -1
  24. package/dist/modules/artifacts/imageArtifacts.js +7 -6
  25. package/dist/modules/artifacts/imageArtifacts.js.map +1 -1
  26. package/dist/modules/publicApi/openapiDocument.generated.d.ts +1 -1
  27. package/dist/modules/publicApi/openapiDocument.generated.d.ts.map +1 -1
  28. package/dist/modules/publicApi/openapiDocument.generated.js +1 -1
  29. package/dist/modules/publicApi/openapiDocument.generated.js.map +1 -1
  30. package/dist/modules/publicApi/publicApiAdmission.d.ts +56 -13
  31. package/dist/modules/publicApi/publicApiAdmission.d.ts.map +1 -1
  32. package/dist/modules/publicApi/publicApiAdmission.js +44 -12
  33. package/dist/modules/publicApi/publicApiAdmission.js.map +1 -1
  34. package/dist/modules/publicApi/taskTypeFields.d.ts.map +1 -1
  35. package/dist/modules/publicApi/taskTypeFields.js +1 -0
  36. package/dist/modules/publicApi/taskTypeFields.js.map +1 -1
  37. package/dist/persistence/mappers.d.ts +31 -0
  38. package/dist/persistence/mappers.d.ts.map +1 -1
  39. package/dist/persistence/mappers.js +26 -1
  40. package/dist/persistence/mappers.js.map +1 -1
  41. package/package.json +7 -7
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HarnessAssetController.js","sourceRoot":"","sources":["../../../src/modules/artifacts/HarnessAssetController.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * A hard ceiling on a single stored asset. Larger than a screenshot's because a 3D scene or a
3
+ * short video legitimately is, and still bounded: a run that needs to deliver more than this per
4
+ * file is delivering through the org's own object store, which is the other half of the feature.
5
+ *
6
+ * The number is sized by the TIGHTEST runtime the route serves, not by what a generator might
7
+ * like to send. The `BinaryArtifactStore` port takes bytes (`store({ blob })`), so an ingest
8
+ * necessarily materialises the whole file, and it does so TWICE at peak: once as the multipart
9
+ * body the form parser holds, once as the `arrayBuffer()` read off the part. The Worker facade
10
+ * runs that inside a workerd isolate with a fixed memory ceiling of 128 MB shared with everything
11
+ * else the invocation holds, so a per-file limit anywhere near it does not answer 413, it kills
12
+ * the isolate mid-upload, and a Node-only test cannot see the difference. 24 MiB keeps the peak
13
+ * (~50 MiB with framing) well inside that budget on every facade.
14
+ *
15
+ * Raising it is therefore not a config decision: it needs the port to take a STREAM, and every
16
+ * blob backend behind it (R2 / S3 / a Postgres `bytea` row / the filesystem) to accept one.
17
+ */
18
+ export declare const MAX_ASSET_BYTES: number;
19
+ /**
20
+ * The coarse pre-buffer ceiling on the whole multipart request, with slack for the framing. See
21
+ * the image sibling for why the exact per-file check still runs after parsing.
22
+ */
23
+ export declare const MAX_ASSET_REQUEST_BYTES: number;
24
+ /**
25
+ * Cap on how many assets ONE run may store. Generous (a step generating a full icon set legitimately
26
+ * stores dozens) and finite, so a looping or compromised container cannot fill the store before the
27
+ * job's own watchdog stops it. The same shape as the screenshot cap, with its own number because the
28
+ * two answer different questions.
29
+ */
30
+ export declare const MAX_ASSETS_PER_RUN = 200;
31
+ /**
32
+ * Canonicalise an uploaded asset's declared content type, or `null` when this store does not
33
+ * accept it (the caller answers 415).
34
+ *
35
+ * Unlike the image sibling there is no default for a TYPELESS upload: a screenshot is always a
36
+ * PNG so guessing one is safe, while an asset with no declared type could be anything and storing
37
+ * it as a guess would mislabel the row that a reader later downloads by. An upload that declares
38
+ * nothing is refused, which the agent can see and correct.
39
+ *
40
+ * The normalised value is then held to `mediaTypeSchema`, the platform's ONE definition of what a
41
+ * media type may look like, before any allow-list is consulted. `normalizeMediaType` answers
42
+ * "what did they mean by this", not "is this a media type at all": it drops parameters and lower-
43
+ * cases, and returns whatever sat either side of the first slash. This value is MODEL-AUTHORED
44
+ * and the only caller who bounds it is this one, since it lands on a persisted column and comes
45
+ * back out on a public wire field (`GET /api/v1/runs/{runId}/artifacts`), so an unbounded subtype
46
+ * would be stored and served verbatim. The schema is the same one a generator's declared formats
47
+ * pass, which is also what keeps a stored `contentType` comparable to a step's requirement.
48
+ */
49
+ export declare function normalizeAssetContentType(raw: string | null | undefined): string | null;
50
+ //# sourceMappingURL=assetArtifacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assetArtifacts.d.ts","sourceRoot":"","sources":["../../../src/modules/artifacts/assetArtifacts.ts"],"names":[],"mappings":"AAiDA;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,eAAe,QAAmB,CAAA;AAE/C;;;GAGG;AACH,eAAO,MAAM,uBAAuB,QAAgC,CAAA;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,MAAM,CAAA;AAErC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAOvF"}
@@ -0,0 +1,104 @@
1
+ import * as v from 'valibot';
2
+ import { mediaTypeSchema, normalizeMediaType } from '@cat-factory/contracts';
3
+ // ---------------------------------------------------------------------------
4
+ // What the platform's own ASSET storage accepts, and why the rule is not the image allow-list
5
+ // beside it.
6
+ //
7
+ // A screenshot is always a PNG, so its ingest can name four types and refuse the rest. A
8
+ // generated asset is whatever the step asked for: a sprite sheet, a GLB an artist opens in
9
+ // Blender, a sound effect, a rendered PDF. Holding those to an image allow-list would make the
10
+ // Media task type an image-only feature with nothing saying so.
11
+ //
12
+ // The rule is therefore a TOP-LEVEL type gate plus a small set of container types, and what makes
13
+ // that safe is the SERVE path rather than this one: `blobResponseHeaders` puts everything outside
14
+ // the inline-image list (contracts' `INLINE_IMAGE_MEDIA_TYPES`) behind `application/octet-stream`
15
+ // + `Content-Disposition: attachment` + `nosniff`, so a stored asset can never come back as active
16
+ // content whatever it declared. This gate is about storing something legible, not about containing
17
+ // an XSS the serve path already contains.
18
+ //
19
+ // Two exclusions are still made HERE, at the write boundary, and both are the same judgement the
20
+ // image allow-list makes: an SVG is an image by media type and a script host in practice, and a
21
+ // markup/text document is not an asset anyone generates through this seam. Refusing them where
22
+ // the upload happens means the agent is TOLD (a 415 it can report), where refusing them at serve
23
+ // time would be a stored artifact nobody can open and nothing to say why.
24
+ // ---------------------------------------------------------------------------
25
+ /** Top-level media types a generated asset may carry. */
26
+ const ASSET_TOP_LEVEL_TYPES = new Set(['image', 'audio', 'video', 'model']);
27
+ /**
28
+ * The `application/*` subtypes accepted beside them: the containers real generators emit that
29
+ * have no top-level type of their own. Named individually because `application/*` is where
30
+ * everything else on the internet also lives.
31
+ */
32
+ const ASSET_APPLICATION_TYPES = new Set([
33
+ // A 3D container: glTF-binary is `model/gltf-binary`, but several vendors emit the older
34
+ // `application/octet-stream` for a `.glb`, and a zipped asset bundle is legitimately this too.
35
+ 'application/octet-stream',
36
+ 'application/zip',
37
+ // A rendered document deliverable (a spec sheet, a print-ready layout).
38
+ 'application/pdf',
39
+ ]);
40
+ /**
41
+ * An image media type that is refused despite being one: SVG is markup with script in it, and the
42
+ * platform's image allow-list has excluded it since it existed for exactly that reason.
43
+ */
44
+ const REFUSED_IMAGE_SUBTYPES = new Set(['image/svg+xml']);
45
+ /**
46
+ * A hard ceiling on a single stored asset. Larger than a screenshot's because a 3D scene or a
47
+ * short video legitimately is, and still bounded: a run that needs to deliver more than this per
48
+ * file is delivering through the org's own object store, which is the other half of the feature.
49
+ *
50
+ * The number is sized by the TIGHTEST runtime the route serves, not by what a generator might
51
+ * like to send. The `BinaryArtifactStore` port takes bytes (`store({ blob })`), so an ingest
52
+ * necessarily materialises the whole file, and it does so TWICE at peak: once as the multipart
53
+ * body the form parser holds, once as the `arrayBuffer()` read off the part. The Worker facade
54
+ * runs that inside a workerd isolate with a fixed memory ceiling of 128 MB shared with everything
55
+ * else the invocation holds, so a per-file limit anywhere near it does not answer 413, it kills
56
+ * the isolate mid-upload, and a Node-only test cannot see the difference. 24 MiB keeps the peak
57
+ * (~50 MiB with framing) well inside that budget on every facade.
58
+ *
59
+ * Raising it is therefore not a config decision: it needs the port to take a STREAM, and every
60
+ * blob backend behind it (R2 / S3 / a Postgres `bytea` row / the filesystem) to accept one.
61
+ */
62
+ export const MAX_ASSET_BYTES = 24 * 1024 * 1024;
63
+ /**
64
+ * The coarse pre-buffer ceiling on the whole multipart request, with slack for the framing. See
65
+ * the image sibling for why the exact per-file check still runs after parsing.
66
+ */
67
+ export const MAX_ASSET_REQUEST_BYTES = MAX_ASSET_BYTES + 1024 * 1024;
68
+ /**
69
+ * Cap on how many assets ONE run may store. Generous (a step generating a full icon set legitimately
70
+ * stores dozens) and finite, so a looping or compromised container cannot fill the store before the
71
+ * job's own watchdog stops it. The same shape as the screenshot cap, with its own number because the
72
+ * two answer different questions.
73
+ */
74
+ export const MAX_ASSETS_PER_RUN = 200;
75
+ /**
76
+ * Canonicalise an uploaded asset's declared content type, or `null` when this store does not
77
+ * accept it (the caller answers 415).
78
+ *
79
+ * Unlike the image sibling there is no default for a TYPELESS upload: a screenshot is always a
80
+ * PNG so guessing one is safe, while an asset with no declared type could be anything and storing
81
+ * it as a guess would mislabel the row that a reader later downloads by. An upload that declares
82
+ * nothing is refused, which the agent can see and correct.
83
+ *
84
+ * The normalised value is then held to `mediaTypeSchema`, the platform's ONE definition of what a
85
+ * media type may look like, before any allow-list is consulted. `normalizeMediaType` answers
86
+ * "what did they mean by this", not "is this a media type at all": it drops parameters and lower-
87
+ * cases, and returns whatever sat either side of the first slash. This value is MODEL-AUTHORED
88
+ * and the only caller who bounds it is this one, since it lands on a persisted column and comes
89
+ * back out on a public wire field (`GET /api/v1/runs/{runId}/artifacts`), so an unbounded subtype
90
+ * would be stored and served verbatim. The schema is the same one a generator's declared formats
91
+ * pass, which is also what keeps a stored `contentType` comparable to a step's requirement.
92
+ */
93
+ export function normalizeAssetContentType(raw) {
94
+ const type = normalizeMediaType(raw ?? '');
95
+ if (!type || !v.is(mediaTypeSchema, type))
96
+ return null;
97
+ if (REFUSED_IMAGE_SUBTYPES.has(type))
98
+ return null;
99
+ const top = type.split('/')[0];
100
+ if (ASSET_TOP_LEVEL_TYPES.has(top))
101
+ return type;
102
+ return ASSET_APPLICATION_TYPES.has(type) ? type : null;
103
+ }
104
+ //# sourceMappingURL=assetArtifacts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assetArtifacts.js","sourceRoot":"","sources":["../../../src/modules/artifacts/assetArtifacts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAC5B,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAE5E,8EAA8E;AAC9E,8FAA8F;AAC9F,aAAa;AACb,EAAE;AACF,yFAAyF;AACzF,2FAA2F;AAC3F,+FAA+F;AAC/F,gEAAgE;AAChE,EAAE;AACF,kGAAkG;AAClG,kGAAkG;AAClG,kGAAkG;AAClG,mGAAmG;AACnG,mGAAmG;AACnG,0CAA0C;AAC1C,EAAE;AACF,iGAAiG;AACjG,gGAAgG;AAChG,+FAA+F;AAC/F,iGAAiG;AACjG,0EAA0E;AAC1E,8EAA8E;AAE9E,yDAAyD;AACzD,MAAM,qBAAqB,GAAwB,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAA;AAEhG;;;;GAIG;AACH,MAAM,uBAAuB,GAAwB,IAAI,GAAG,CAAC;IAC3D,yFAAyF;IACzF,+FAA+F;IAC/F,0BAA0B;IAC1B,iBAAiB;IACjB,wEAAwE;IACxE,iBAAiB;CAClB,CAAC,CAAA;AAEF;;;GAGG;AACH,MAAM,sBAAsB,GAAwB,IAAI,GAAG,CAAC,CAAC,eAAe,CAAC,CAAC,CAAA;AAE9E;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;AAE/C;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,eAAe,GAAG,IAAI,GAAG,IAAI,CAAA;AAEpE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAA;AAErC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAA8B;IACtE,MAAM,IAAI,GAAG,kBAAkB,CAAC,GAAG,IAAI,EAAE,CAAC,CAAA;IAC1C,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,eAAe,EAAE,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACtD,IAAI,sBAAsB,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACjD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAA;IAC/B,IAAI,qBAAqB,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IAC/C,OAAO,uBAAuB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;AACxD,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { Context } from 'hono';
2
+ import type { ContainerSession } from '../../containers/ContainerSessionService.js';
3
+ import type { AppEnv } from '../../http/env.js';
4
+ import type { BinaryArtifactStore } from '@cat-factory/kernel';
5
+ /**
6
+ * Verify the container session token on a harness request and resolve the store its run may use.
7
+ *
8
+ * ONE helper for every direction of the seam (the tester's screenshot ingest, the reference
9
+ * download beside it, and the asset ingest a media step stores through), so they can never end up
10
+ * disagreeing about what authenticates a container: the SAME short-lived, workspace- and
11
+ * execution-pinned token the agent already holds for the LLM proxy, and a store resolved from the
12
+ * token's workspace rather than from anything the request says.
13
+ *
14
+ * It lives in its own module rather than on one of the controllers because the SECOND caller is
15
+ * what makes the rule load-bearing: a per-controller copy is how one entry point ends up trusting
16
+ * a workspace id off the request body while its sibling does not.
17
+ */
18
+ export declare function requireHarnessSession(c: Context<AppEnv>, scope: string): Promise<{
19
+ session: ContainerSession;
20
+ store: BinaryArtifactStore;
21
+ }>;
22
+ //# sourceMappingURL=harnessSession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harnessSession.d.ts","sourceRoot":"","sources":["../../../src/modules/artifacts/harnessSession.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAGnC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6CAA6C,CAAA;AACnF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAE/C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAG9D;;;;;;;;;;;;GAYG;AACH,wBAAsB,qBAAqB,CACzC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,EAClB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,OAAO,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,mBAAmB,CAAA;CAAE,CAAC,CAyBpE"}
@@ -0,0 +1,44 @@
1
+ import { bearerToken } from '../../auth/middleware.js';
2
+ import { ContainerSessionService } from '../../containers/ContainerSessionService.js';
3
+ import { logger } from '../../observability/logger.js';
4
+ import { UnauthorizedError, UnavailableError } from '@cat-factory/kernel';
5
+ /**
6
+ * Verify the container session token on a harness request and resolve the store its run may use.
7
+ *
8
+ * ONE helper for every direction of the seam (the tester's screenshot ingest, the reference
9
+ * download beside it, and the asset ingest a media step stores through), so they can never end up
10
+ * disagreeing about what authenticates a container: the SAME short-lived, workspace- and
11
+ * execution-pinned token the agent already holds for the LLM proxy, and a store resolved from the
12
+ * token's workspace rather than from anything the request says.
13
+ *
14
+ * It lives in its own module rather than on one of the controllers because the SECOND caller is
15
+ * what makes the rule load-bearing: a per-controller copy is how one entry point ends up trusting
16
+ * a workspace id off the request body while its sibling does not.
17
+ */
18
+ export async function requireHarnessSession(c, scope) {
19
+ const container = c.get('container');
20
+ const resolveStore = container.resolveBinaryArtifactStore;
21
+ if (!resolveStore) {
22
+ throw new UnavailableError('Artifact storage not configured');
23
+ }
24
+ const secret = container.config.auth.sessionSecret;
25
+ if (!secret) {
26
+ logger.error('harness artifacts: session secret not configured', { scope });
27
+ throw new UnavailableError('Artifact ingest not configured');
28
+ }
29
+ const sessions = new ContainerSessionService({ secret });
30
+ const session = await sessions.verify(bearerToken(c));
31
+ if (!session) {
32
+ logger.warn('harness artifacts: invalid or expired session token', { scope });
33
+ throw new UnauthorizedError('Invalid or expired token');
34
+ }
35
+ // The store is the run's ACCOUNT's configured backend, resolved from the token's workspace
36
+ // (never the request), so a container only ever reaches its own account's storage. Null means the
37
+ // account configured no storage.
38
+ const store = await resolveStore(session.workspaceId);
39
+ if (!store) {
40
+ throw new UnavailableError('Artifact storage not configured');
41
+ }
42
+ return { session, store };
43
+ }
44
+ //# sourceMappingURL=harnessSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harnessSession.js","sourceRoot":"","sources":["../../../src/modules/artifacts/harnessSession.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,uBAAuB,EAAE,MAAM,6CAA6C,CAAA;AAGrF,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAA;AAEtD,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAEzE;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,CAAkB,EAClB,KAAa;IAEb,MAAM,SAAS,GAAG,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;IACpC,MAAM,YAAY,GAAG,SAAS,CAAC,0BAA0B,CAAA;IACzD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,gBAAgB,CAAC,iCAAiC,CAAC,CAAA;IAC/D,CAAC;IACD,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAA;IAClD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,CAAC,KAAK,CAAC,kDAAkD,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;QAC3E,MAAM,IAAI,gBAAgB,CAAC,gCAAgC,CAAC,CAAA;IAC9D,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;IACxD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;IACrD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,qDAAqD,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;QAC7E,MAAM,IAAI,iBAAiB,CAAC,0BAA0B,CAAC,CAAA;IACzD,CAAC;IACD,2FAA2F;IAC3F,kGAAkG;IAClG,iCAAiC;IACjC,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IACrD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,gBAAgB,CAAC,iCAAiC,CAAC,CAAA;IAC/D,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;AAC3B,CAAC"}
@@ -3,6 +3,11 @@
3
3
  * designs are uploaded images; anything else (HTML, SVG — which can carry script and would
4
4
  * execute when served inline same-origin) is rejected at the write boundary so the blob
5
5
  * endpoint can never become a stored-XSS vector. SVG is deliberately excluded.
6
+ *
7
+ * The MEMBERS come from `@cat-factory/contracts`, because the SPA has to make the same
8
+ * judgement about the same bytes (does this row render as a picture, or as a download?) and two
9
+ * copies of it disagree silently in both directions. What stays here is the second USE of that
10
+ * list (the upload gate), which is this package's alone.
6
11
  */
7
12
  export declare const ALLOWED_IMAGE_CONTENT_TYPES: ReadonlySet<string>;
8
13
  /** A hard ceiling on a single artifact's bytes, enforced at every ingest point. */
@@ -1 +1 @@
1
- {"version":3,"file":"imageArtifacts.d.ts","sourceRoot":"","sources":["../../../src/modules/artifacts/imageArtifacts.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,EAAE,WAAW,CAAC,MAAM,CAK1D,CAAA;AAEF,mFAAmF;AACnF,eAAO,MAAM,gBAAgB,QAAmB,CAAA;AAEhD;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,QAAiC,CAAA;AAE/D;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAIzF;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAIvF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBrF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,QAAQ,CAE5D"}
1
+ {"version":3,"file":"imageArtifacts.d.ts","sourceRoot":"","sources":["../../../src/modules/artifacts/imageArtifacts.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,EAAE,WAAW,CAAC,MAAM,CAAqC,CAAA;AAEjG,mFAAmF;AACnF,eAAO,MAAM,gBAAgB,QAAmB,CAAA;AAEhD;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,QAAiC,CAAA;AAE/D;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAIzF;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAIvF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBrF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,QAAQ,CAE5D"}
@@ -1,3 +1,4 @@
1
+ import { INLINE_IMAGE_MEDIA_TYPES } from '@cat-factory/contracts';
1
2
  // Shared validation + safe-serving helpers for binary-artifact images (UI screenshots +
2
3
  // reference design images). Used by BOTH the workspace-scoped upload/serve controller and
3
4
  // the container-token-authed harness ingest route, so the security posture can't drift
@@ -7,13 +8,13 @@
7
8
  * designs are uploaded images; anything else (HTML, SVG — which can carry script and would
8
9
  * execute when served inline same-origin) is rejected at the write boundary so the blob
9
10
  * endpoint can never become a stored-XSS vector. SVG is deliberately excluded.
11
+ *
12
+ * The MEMBERS come from `@cat-factory/contracts`, because the SPA has to make the same
13
+ * judgement about the same bytes (does this row render as a picture, or as a download?) and two
14
+ * copies of it disagree silently in both directions. What stays here is the second USE of that
15
+ * list (the upload gate), which is this package's alone.
10
16
  */
11
- export const ALLOWED_IMAGE_CONTENT_TYPES = new Set([
12
- 'image/png',
13
- 'image/jpeg',
14
- 'image/webp',
15
- 'image/gif',
16
- ]);
17
+ export const ALLOWED_IMAGE_CONTENT_TYPES = new Set(INLINE_IMAGE_MEDIA_TYPES);
17
18
  /** A hard ceiling on a single artifact's bytes, enforced at every ingest point. */
18
19
  export const MAX_UPLOAD_BYTES = 16 * 1024 * 1024;
19
20
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"imageArtifacts.js","sourceRoot":"","sources":["../../../src/modules/artifacts/imageArtifacts.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,0FAA0F;AAC1F,uFAAuF;AACvF,gCAAgC;AAEhC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAwB,IAAI,GAAG,CAAC;IACtE,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,WAAW;CACZ,CAAC,CAAA;AAEF,mFAAmF;AACnF,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;AAEhD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAAA;AAE/D;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,aAAwC;IAC9E,IAAI,CAAC,aAAa;QAAE,OAAO,KAAK,CAAA;IAChC,MAAM,CAAC,GAAG,MAAM,CAAC,aAAa,CAAC,CAAA;IAC/B,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAA;AACpD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAA8B;IACtE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IACrB,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACvD,OAAO,2BAA2B,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;AAC5D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,iBAAyB;IAC3D,MAAM,IAAI,GAAG,yBAAyB,CAAC,iBAAiB,CAAC,CAAA;IACzD,OAAO,IAAI;QACT,CAAC,CAAC;YACE,cAAc,EAAE,IAAI;YACpB,qBAAqB,EAAE,QAAQ;YAC/B,wBAAwB,EAAE,SAAS;YACnC,eAAe,EAAE,wBAAwB;SAC1C;QACH,CAAC,CAAC;YACE,6EAA6E;YAC7E,cAAc,EAAE,0BAA0B;YAC1C,qBAAqB,EAAE,YAAY;YACnC,wBAAwB,EAAE,SAAS;YACnC,eAAe,EAAE,wBAAwB;SAC1C,CAAA;AACP,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAiB;IAChD,OAAO,KAA4B,CAAA;AACrC,CAAC"}
1
+ {"version":3,"file":"imageArtifacts.js","sourceRoot":"","sources":["../../../src/modules/artifacts/imageArtifacts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAA;AAEjE,wFAAwF;AACxF,0FAA0F;AAC1F,uFAAuF;AACvF,gCAAgC;AAEhC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAwB,IAAI,GAAG,CAAC,wBAAwB,CAAC,CAAA;AAEjG,mFAAmF;AACnF,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;AAEhD;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAAA;AAE/D;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,aAAwC;IAC9E,IAAI,CAAC,aAAa;QAAE,OAAO,KAAK,CAAA;IAChC,MAAM,CAAC,GAAG,MAAM,CAAC,aAAa,CAAC,CAAA;IAC/B,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAA;AACpD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAA8B;IACtE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAA;IACrB,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACvD,OAAO,2BAA2B,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;AAC5D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,iBAAyB;IAC3D,MAAM,IAAI,GAAG,yBAAyB,CAAC,iBAAiB,CAAC,CAAA;IACzD,OAAO,IAAI;QACT,CAAC,CAAC;YACE,cAAc,EAAE,IAAI;YACpB,qBAAqB,EAAE,QAAQ;YAC/B,wBAAwB,EAAE,SAAS;YACnC,eAAe,EAAE,wBAAwB;SAC1C;QACH,CAAC,CAAC;YACE,6EAA6E;YAC7E,cAAc,EAAE,0BAA0B;YAC1C,qBAAqB,EAAE,YAAY;YACnC,wBAAwB,EAAE,SAAS;YACnC,eAAe,EAAE,wBAAwB;SAC1C,CAAA;AACP,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAiB;IAChD,OAAO,KAA4B,CAAA;AACrC,CAAC"}