@schlessera/brain-ui-server 0.12.1 → 0.13.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/dist/app.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAWA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAoB1E,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAqBD,wBAAgB,SAAS,CAAC,OAAO,GAAE,gBAAqB;;;EAyFvD"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAYA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAoB1E,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAqBD,wBAAgB,SAAS,CAAC,OAAO,GAAE,gBAAqB;;;EA6FvD"}
package/dist/app.js CHANGED
@@ -8,6 +8,7 @@ import { brainRoutes } from "./routes/brain.js";
8
8
  import { sessionRoutes } from "./routes/sessions.js";
9
9
  import { voiceRoutes } from "./routes/voice.js";
10
10
  import { filesRoutes } from "./routes/files.js";
11
+ import { shareRoutes, shareTargetFallbackRoutes } from "./routes/share.js";
11
12
  import { createRenderRoutes } from "./routes/render.js";
12
13
  import { providerRoutes } from "./routes/providers.js";
13
14
  import { modelRoutes } from "./routes/models.js";
@@ -74,6 +75,9 @@ export function createApp(options = {}) {
74
75
  // /api/status is intentionally NOT here — it leaks the git SHA, cron errors,
75
76
  // and a session oracle, so it lives behind the guard below.
76
77
  app.route("/api", healthRoutes);
78
+ // Not under /api, and not behind the guard: this is where a system share
79
+ // lands when no service worker was around to intercept it. See the route.
80
+ app.route("/", shareTargetFallbackRoutes);
77
81
  app.route("/api", authRoutes(authMode, { passwordDisabled: passwordLoginDisabled }));
78
82
  app.route("/api", passkeyPublicRoutes(authMode));
79
83
  // Auth guard for every other /api/* route. The probe below is intentionally
@@ -89,6 +93,7 @@ export function createApp(options = {}) {
89
93
  app.route("/api", sessionRoutes);
90
94
  app.route("/api", voiceRoutes);
91
95
  app.route("/api", filesRoutes);
96
+ app.route("/api", shareRoutes);
92
97
  app.route("/api", createRenderRoutes(options.renderer));
93
98
  app.route("/api", providerRoutes);
94
99
  app.route("/api", modelRoutes);
package/dist/app.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -14,5 +14,6 @@ export { getBackends, getBackendsInfo } from "./agent/backend.js";
14
14
  export { resolveAuthMode, type AuthMode } from "./middleware/auth.js";
15
15
  export { WsHost, type WsHostOptions, defaultWsHost, configureWsHost, handleClientMessage, resetForTests, resolveTurnTarget, createSessionCatalog, type SessionCatalog, } from "./ws/handler.js";
16
16
  export * as brainClient from "./brain/client.js";
17
+ export { pruneShareStaging, shareStagingRoot } from "./share/staging.js";
17
18
  export { buildKeyterms, writeCache } from "./voice/keyterm-builder.js";
18
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAG9E,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAI7D,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGtE,OAAO,EACL,MAAM,EACN,KAAK,aAAa,EAClB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,GACpB,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAGjD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAG9E,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAI7D,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGtE,OAAO,EACL,MAAM,EACN,KAAK,aAAa,EAClB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,oBAAoB,EACpB,KAAK,cAAc,GACpB,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAIjD,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGzE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC"}
package/dist/index.js CHANGED
@@ -20,6 +20,9 @@ export { WsHost, defaultWsHost, configureWsHost, handleClientMessage, resetForTe
20
20
  // Brain repo access (spawned CLI wrapper) — useful for embedders that add
21
21
  // their own routes on top.
22
22
  export * as brainClient from "./brain/client.js";
23
+ // Share staging: a deployment can sweep expired staging dirs at boot; the
24
+ // intake route also sweeps opportunistically on every share.
25
+ export { pruneShareStaging, shareStagingRoot } from "./share/staging.js";
23
26
  // Voice keyterm cache rebuild (used by deployments after `brain sync`).
24
27
  export { buildKeyterms, writeCache } from "./voice/keyterm-builder.js";
25
28
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAA2C,MAAM,UAAU,CAAC;AAE9E,+CAA+C;AAC/C,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7D,4EAA4E;AAC5E,+BAA+B;AAC/B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAiB,MAAM,sBAAsB,CAAC;AAEtE,+CAA+C;AAC/C,OAAO,EACL,MAAM,EAEN,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,oBAAoB,GAErB,MAAM,iBAAiB,CAAC;AAEzB,0EAA0E;AAC1E,2BAA2B;AAC3B,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAEjD,wEAAwE;AACxE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAA2C,MAAM,UAAU,CAAC;AAE9E,+CAA+C;AAC/C,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7D,4EAA4E;AAC5E,+BAA+B;AAC/B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAiB,MAAM,sBAAsB,CAAC;AAEtE,+CAA+C;AAC/C,OAAO,EACL,MAAM,EAEN,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,oBAAoB,GAErB,MAAM,iBAAiB,CAAC;AAEzB,0EAA0E;AAC1E,2BAA2B;AAC3B,OAAO,KAAK,WAAW,MAAM,mBAAmB,CAAC;AAEjD,0EAA0E;AAC1E,6DAA6D;AAC7D,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEzE,wEAAwE;AACxE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { Context } from "hono";
2
+ /**
3
+ * Same-origin enforcement for requests a browser sends without a preflight.
4
+ *
5
+ * Every other state-changing route here reads `application/json`, which is not
6
+ * a CORS-simple content type and so forces a preflight the attacker's origin
7
+ * fails — CSRF-safe by accident. A multipart upload has no such protection: it
8
+ * is a simple request, and under `AUTH_MODE=tailscale` the credential is the
9
+ * source IP rather than a cookie, so no SameSite flag stands in the way either.
10
+ * Any page opened on the tailnet could otherwise POST into the brain.
11
+ *
12
+ * The check is free because the routes that use it are only ever called by the
13
+ * app itself, same-origin. Same shape as the WebSocket upgrade's origin guard.
14
+ */
15
+ export declare function allowedOriginList(): string[];
16
+ export declare function isSameOriginRequest(c: Context, allowed?: string[]): boolean;
17
+ //# sourceMappingURL=origin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origin.d.ts","sourceRoot":"","sources":["../../src/middleware/origin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAK5C;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,WAAsB,GAAG,OAAO,CAgBtF"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Same-origin enforcement for requests a browser sends without a preflight.
3
+ *
4
+ * Every other state-changing route here reads `application/json`, which is not
5
+ * a CORS-simple content type and so forces a preflight the attacker's origin
6
+ * fails — CSRF-safe by accident. A multipart upload has no such protection: it
7
+ * is a simple request, and under `AUTH_MODE=tailscale` the credential is the
8
+ * source IP rather than a cookie, so no SameSite flag stands in the way either.
9
+ * Any page opened on the tailnet could otherwise POST into the brain.
10
+ *
11
+ * The check is free because the routes that use it are only ever called by the
12
+ * app itself, same-origin. Same shape as the WebSocket upgrade's origin guard.
13
+ */
14
+ export function allowedOriginList() {
15
+ return (process.env.ALLOWED_ORIGINS ?? "")
16
+ .split(",")
17
+ .map((origin) => origin.trim())
18
+ .filter(Boolean);
19
+ }
20
+ export function isSameOriginRequest(c, allowed = allowedOriginList()) {
21
+ // Chromium and Firefox send this, and it is not settable by script.
22
+ const site = c.req.header("sec-fetch-site");
23
+ if (site)
24
+ return site === "same-origin" || site === "none";
25
+ // Older browsers: fall back to Origin, and to the configured allowlist when
26
+ // the deployment is split across origins.
27
+ const origin = c.req.header("origin");
28
+ if (!origin)
29
+ return true; // A non-browser client; still gated by auth.
30
+ if (allowed.length > 0)
31
+ return allowed.includes(origin);
32
+ try {
33
+ const host = c.req.header("host");
34
+ return !!host && new URL(origin).host === host;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ //# sourceMappingURL=origin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origin.js","sourceRoot":"","sources":["../../src/middleware/origin.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE,CAAC;SACvC,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;SAC9B,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,CAAU,EAAE,OAAO,GAAG,iBAAiB,EAAE;IAC3E,oEAAoE;IACpE,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAC5C,IAAI,IAAI;QAAE,OAAO,IAAI,KAAK,aAAa,IAAI,IAAI,KAAK,MAAM,CAAC;IAE3D,4EAA4E;IAC5E,0CAA0C;IAC1C,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC,CAAC,6CAA6C;IACvE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAClC,OAAO,CAAC,CAAC,IAAI,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Answer a share that reached the SERVER — which means no service worker was
3
+ * there to intercept it.
4
+ *
5
+ * That happens: the worker is evicted, storage was cleared, or the app was
6
+ * installed before the worker activated. Android bakes the share target's
7
+ * intent filters into the WebAPK at install time, so the share sheet keeps
8
+ * offering the app regardless, and the POST lands here. Without this route the
9
+ * user gets a bare 404 inside the app window (the SPA fallback is GET-only).
10
+ *
11
+ * The body is deliberately not read: the payload cannot be recovered from here
12
+ * anyway. Landing in the app is still the best outcome, because loading the app
13
+ * re-registers the worker and the next share works.
14
+ *
15
+ * Registered BEFORE the auth guard, and it must stay there: a share navigation
16
+ * is cross-site, so the SameSite=Strict session cookie is absent by
17
+ * construction and an authenticated version of this route could never fire.
18
+ */
19
+ export declare const shareTargetFallbackRoutes: import("hono/hono-base").HonoBase<import("hono/types").BlankEnv, {
20
+ "/share-target": {
21
+ $post: {
22
+ input: {};
23
+ output: undefined;
24
+ outputFormat: "redirect";
25
+ status: 303;
26
+ };
27
+ };
28
+ }, "/", "/share-target">;
29
+ export declare const shareRoutes: import("hono/hono-base").HonoBase<import("hono/types").BlankEnv, {
30
+ "/share": {
31
+ $post: {
32
+ input: {};
33
+ output: {
34
+ error: string;
35
+ };
36
+ outputFormat: "json";
37
+ status: 403;
38
+ } | {
39
+ input: {};
40
+ output: {
41
+ error: string;
42
+ };
43
+ outputFormat: "json";
44
+ status: 503;
45
+ } | {
46
+ input: {};
47
+ output: {
48
+ error: string;
49
+ limit: number;
50
+ };
51
+ outputFormat: "json";
52
+ status: 413;
53
+ } | {
54
+ input: {};
55
+ output: {
56
+ error: string;
57
+ };
58
+ outputFormat: "json";
59
+ status: 400;
60
+ } | {
61
+ input: {};
62
+ output: {
63
+ id: string;
64
+ dir: string;
65
+ receivedAt: number;
66
+ title?: string | undefined;
67
+ text?: string | undefined;
68
+ url?: string | undefined;
69
+ files: {
70
+ name: string;
71
+ path: string;
72
+ mediaType: string;
73
+ bytes: number;
74
+ }[];
75
+ skipped?: string[] | undefined;
76
+ };
77
+ outputFormat: "json";
78
+ status: 201;
79
+ } | {
80
+ input: {};
81
+ output: {
82
+ error: string;
83
+ };
84
+ outputFormat: "json";
85
+ status: 500;
86
+ };
87
+ };
88
+ }, "/", "/share">;
89
+ //# sourceMappingURL=share.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share.d.ts","sourceRoot":"","sources":["../../src/routes/share.ts"],"names":[],"mappings":"AA8DA;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;wBAErC,CAAC;AA8BF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiEtB,CAAC"}
@@ -0,0 +1,158 @@
1
+ import { Hono } from "hono";
2
+ import { SHARE_MAX_CONCURRENT_INTAKE, SHARE_MAX_TOTAL_BYTES, } from "@schlessera/brain-ui-sdk/protocol";
3
+ import { isSameOriginRequest } from "../middleware/origin.js";
4
+ import { EmptyShareError, ShareTooLargeError, pruneShareStaging, stageShare, } from "../share/staging.js";
5
+ /**
6
+ * Slack over the total cap for multipart framing (boundaries, part headers, and
7
+ * the title/text/url fields).
8
+ */
9
+ const BODY_SLACK_BYTES = 1_000_000;
10
+ const HARD_BODY_LIMIT = SHARE_MAX_TOTAL_BYTES + BODY_SLACK_BYTES;
11
+ /**
12
+ * Read the body, refusing to buffer more than `limit` bytes.
13
+ *
14
+ * `content-length` is only a hint — HTTP/2 and chunked transfer encoding omit
15
+ * it entirely, and a client is free to lie — so the cap has to be counted off
16
+ * the stream itself. Calling `formData()` first would hand an unbounded body to
17
+ * the multipart parser and let an authenticated caller exhaust memory well
18
+ * inside the advertised limits.
19
+ *
20
+ * Returns null when the body exceeds the limit.
21
+ */
22
+ async function readCappedBody(request, limit) {
23
+ const stream = request.body;
24
+ if (!stream)
25
+ return new Blob([]);
26
+ const reader = stream.getReader();
27
+ const chunks = [];
28
+ let total = 0;
29
+ try {
30
+ for (;;) {
31
+ const { done, value } = await reader.read();
32
+ if (done)
33
+ break;
34
+ if (!value)
35
+ continue;
36
+ total += value.byteLength;
37
+ if (total > limit)
38
+ return null;
39
+ // Copy out of the reader's view: the underlying buffer may be reused.
40
+ chunks.push(value.slice().buffer);
41
+ }
42
+ }
43
+ finally {
44
+ reader.releaseLock();
45
+ // Nothing more is wanted from an over-limit body; let the peer find out.
46
+ if (total > limit)
47
+ await stream.cancel().catch(() => { });
48
+ }
49
+ // Hand the chunks to the parser as they are. Concatenating them into one
50
+ // buffer first would copy the whole payload a second time, for nothing.
51
+ return new Blob(chunks);
52
+ }
53
+ /**
54
+ * Answer a share that reached the SERVER — which means no service worker was
55
+ * there to intercept it.
56
+ *
57
+ * That happens: the worker is evicted, storage was cleared, or the app was
58
+ * installed before the worker activated. Android bakes the share target's
59
+ * intent filters into the WebAPK at install time, so the share sheet keeps
60
+ * offering the app regardless, and the POST lands here. Without this route the
61
+ * user gets a bare 404 inside the app window (the SPA fallback is GET-only).
62
+ *
63
+ * The body is deliberately not read: the payload cannot be recovered from here
64
+ * anyway. Landing in the app is still the best outcome, because loading the app
65
+ * re-registers the worker and the next share works.
66
+ *
67
+ * Registered BEFORE the auth guard, and it must stay there: a share navigation
68
+ * is cross-site, so the SameSite=Strict session cookie is absent by
69
+ * construction and an authenticated version of this route could never fire.
70
+ */
71
+ export const shareTargetFallbackRoutes = new Hono().post("/share-target", (c) => c.redirect("/?share_error=no_worker", 303));
72
+ /**
73
+ * In-flight intakes. Each one holds its whole payload in memory while the
74
+ * multipart parser runs, so without a bound the per-share cap multiplies by
75
+ * however many clients ask at once — on a box that also runs headless Chrome
76
+ * for the renderer.
77
+ */
78
+ let inFlight = 0;
79
+ /** At most one sweep per interval: the intake path should not stat the inbox on every upload. */
80
+ let lastPrune = 0;
81
+ const PRUNE_INTERVAL_MS = 10 * 60 * 1000;
82
+ function maybePrune() {
83
+ const now = Date.now();
84
+ if (now - lastPrune < PRUNE_INTERVAL_MS)
85
+ return;
86
+ lastPrune = now;
87
+ // Deliberately not awaited: pruning is housekeeping, and the client is
88
+ // waiting on the staging result, not on it.
89
+ void pruneShareStaging().catch((err) => {
90
+ console.error("[share] prune failed:", err);
91
+ });
92
+ }
93
+ function firstString(form, name) {
94
+ const value = form.get(name);
95
+ return typeof value === "string" && value.trim() ? value : undefined;
96
+ }
97
+ export const shareRoutes = new Hono().post("/share", async (c) => {
98
+ // See middleware/origin.ts: a multipart POST is a CORS-simple request, so it
99
+ // reaches this route with no preflight, and in tailscale mode the credential
100
+ // is the source IP. This route is only ever called by the app itself.
101
+ if (!isSameOriginRequest(c)) {
102
+ return c.json({ error: "cross_origin_rejected" }, 403);
103
+ }
104
+ if (inFlight >= SHARE_MAX_CONCURRENT_INTAKE) {
105
+ return c.json({ error: "busy" }, 503);
106
+ }
107
+ const tooLarge = () => c.json({ error: "share_too_large", limit: SHARE_MAX_TOTAL_BYTES }, 413);
108
+ // Cheap early-out for a client that declares its size honestly; the streamed
109
+ // count below is what actually enforces the cap.
110
+ const declaredLength = Number(c.req.header("content-length") ?? "0");
111
+ if (Number.isFinite(declaredLength) && declaredLength > HARD_BODY_LIMIT) {
112
+ return tooLarge();
113
+ }
114
+ const raw = await readCappedBody(c.req.raw, HARD_BODY_LIMIT);
115
+ if (raw === null)
116
+ return tooLarge();
117
+ let form;
118
+ try {
119
+ const contentType = c.req.header("content-type");
120
+ form = await new Response(raw, {
121
+ headers: contentType ? { "content-type": contentType } : {},
122
+ }).formData();
123
+ }
124
+ catch {
125
+ // A truncated upload or a malformed multipart body. The share is gone
126
+ // either way; the client re-offers it from its own copy.
127
+ return c.json({ error: "invalid_form" }, 400);
128
+ }
129
+ // Empty parts are what an app sends when it has nothing to attach.
130
+ const files = form
131
+ .getAll("files")
132
+ .filter((value) => value instanceof File && value.size > 0);
133
+ inFlight += 1;
134
+ try {
135
+ const result = await stageShare({
136
+ title: firstString(form, "title"),
137
+ text: firstString(form, "text"),
138
+ url: firstString(form, "url"),
139
+ files,
140
+ });
141
+ maybePrune();
142
+ return c.json(result, 201);
143
+ }
144
+ catch (err) {
145
+ if (err instanceof EmptyShareError) {
146
+ return c.json({ error: "empty_share" }, 400);
147
+ }
148
+ if (err instanceof ShareTooLargeError) {
149
+ return c.json({ error: err.reason, limit: err.limit }, 413);
150
+ }
151
+ console.error("[share]", err);
152
+ return c.json({ error: "share_failed" }, 500);
153
+ }
154
+ finally {
155
+ inFlight -= 1;
156
+ }
157
+ });
158
+ //# sourceMappingURL=share.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"share.js","sourceRoot":"","sources":["../../src/routes/share.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import { type ShareIntakeResult } from "@schlessera/brain-ui-sdk/protocol";
2
+ /**
3
+ * Staging for an incoming system share.
4
+ *
5
+ * Everything a share carries is attacker-influenced: file names come from
6
+ * whichever app invoked the share sheet, and the text is whatever page the user
7
+ * was looking at. So the payload lands in a directory whose name the SERVER
8
+ * mints, under a dot-directory that is gitignored and hidden from the file
9
+ * browser, and nothing in it is ever trusted to be a path.
10
+ *
11
+ * The consumer is an LLM agent with file and shell tools, which sets the bar
12
+ * for "sanitized": a name is not safe merely because the filesystem accepts it.
13
+ * It also has to be safe unquoted in a shell, and free of characters that are
14
+ * invisible to the human reviewing what the agent is about to do.
15
+ */
16
+ export declare class EmptyShareError extends Error {
17
+ constructor();
18
+ }
19
+ export type ShareLimitReason = "too_many_files" | "file_too_large" | "share_too_large" | "text_too_large" | "inbox_full";
20
+ export declare class ShareTooLargeError extends Error {
21
+ /** Machine-readable reason, returned to the client verbatim. */
22
+ reason: ShareLimitReason;
23
+ limit: number;
24
+ constructor(
25
+ /** Machine-readable reason, returned to the client verbatim. */
26
+ reason: ShareLimitReason, limit: number);
27
+ }
28
+ export interface ShareInput {
29
+ title?: string | undefined;
30
+ text?: string | undefined;
31
+ url?: string | undefined;
32
+ files: File[];
33
+ }
34
+ /** Absolute staging root. Read per call, so a test can move BRAIN_PATH. */
35
+ export declare function shareStagingRoot(): string;
36
+ /**
37
+ * Reduce whatever the sharing app called a file to a plain name that cannot
38
+ * escape its directory, hide itself, or do anything in a shell.
39
+ *
40
+ * Path separators are dropped rather than replaced: a name is a name, and the
41
+ * only directory a staged file may land in is the one this module just made.
42
+ */
43
+ export declare function sanitizeFileName(raw: string, mediaType: string): string;
44
+ /**
45
+ * Write one share to its own staging directory and return the manifest.
46
+ *
47
+ * Staged into `.<id>.partial` and renamed into place only once `meta.json` is
48
+ * written. The rename is atomic within a filesystem, so the agent can never
49
+ * observe a share that is missing files or a manifest — not even if the process
50
+ * is killed mid-write, which `try/catch` cleanup cannot cover.
51
+ */
52
+ export declare function stageShare(input: ShareInput): Promise<ShareIntakeResult>;
53
+ /**
54
+ * Remove staged shares older than the TTL, and partial directories orphaned by
55
+ * a crash mid-write.
56
+ *
57
+ * Called opportunistically on each intake (debounced) and exported so a
58
+ * deployment can sweep at boot too — scheduling belongs to the container
59
+ * crontab, and this sweep is cheap and bounded.
60
+ */
61
+ export declare function pruneShareStaging(now?: number): Promise<number>;
62
+ //# sourceMappingURL=staging.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staging.d.ts","sourceRoot":"","sources":["../../src/share/staging.ts"],"names":[],"mappings":"AAEA,OAAO,EASL,KAAK,iBAAiB,EAEvB,MAAM,mCAAmC,CAAC;AAG3C;;;;;;;;;;;;;GAaG;AAEH,qBAAa,eAAgB,SAAQ,KAAK;;CAKzC;AAED,MAAM,MAAM,gBAAgB,GACxB,gBAAgB,GAChB,gBAAgB,GAChB,iBAAiB,GACjB,gBAAgB,GAChB,YAAY,CAAC;AAEjB,qBAAa,kBAAmB,SAAQ,KAAK;IAEzC,gEAAgE;IACzD,MAAM,EAAE,gBAAgB;IACxB,KAAK,EAAE,MAAM;;IAFpB,gEAAgE;IACzD,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,MAAM;CAKvB;AAgED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AA+BD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAyBvE;AAyED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgH9E;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,SAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA8BzE"}