@tom2012/cc-web 2026.5.20-b → 2026.5.24-b

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 (35) hide show
  1. package/README.md +1 -1
  2. package/backend/dist/__tests__/browser-proxy.test.d.ts +2 -0
  3. package/backend/dist/__tests__/browser-proxy.test.d.ts.map +1 -0
  4. package/backend/dist/__tests__/browser-proxy.test.js +340 -0
  5. package/backend/dist/__tests__/browser-proxy.test.js.map +1 -0
  6. package/backend/dist/index.d.ts.map +1 -1
  7. package/backend/dist/index.js +5 -0
  8. package/backend/dist/index.js.map +1 -1
  9. package/backend/dist/notify-service.d.ts +1 -0
  10. package/backend/dist/notify-service.d.ts.map +1 -1
  11. package/backend/dist/notify-service.js +1 -0
  12. package/backend/dist/notify-service.js.map +1 -1
  13. package/backend/dist/routes/browser-proxy.d.ts +43 -0
  14. package/backend/dist/routes/browser-proxy.d.ts.map +1 -0
  15. package/backend/dist/routes/browser-proxy.js +400 -0
  16. package/backend/dist/routes/browser-proxy.js.map +1 -0
  17. package/frontend/dist/assets/{ChatOverlay-C6C_KmoQ.js → ChatOverlay-Cm7M9uTo.js} +1 -1
  18. package/frontend/dist/assets/{GraphPreview-3Jt6HVoR.js → GraphPreview-Bu-zdWO0.js} +1 -1
  19. package/frontend/dist/assets/{MobilePage-Ccfw-PQN.js → MobilePage-DHSd8DBY.js} +3 -3
  20. package/frontend/dist/assets/{OfficePreview-CcNfdvDk.js → OfficePreview-CKOQ-aQd.js} +2 -2
  21. package/frontend/dist/assets/{PdfPreview-C_TVMGcZ.js → PdfPreview-G8fpvo6_.js} +1 -1
  22. package/frontend/dist/assets/{ProjectPage-DdDicE9q.js → ProjectPage-D8O-uvJ3.js} +5 -5
  23. package/frontend/dist/assets/SettingsPage-a3v0GKkG.js +13 -0
  24. package/frontend/dist/assets/{SkillHubPage-qYjj_wYZ.js → SkillHubPage-BcohQzQq.js} +2 -2
  25. package/frontend/dist/assets/{chevron-down-UeSwXtvj.js → chevron-down-BjD1KDpf.js} +1 -1
  26. package/frontend/dist/assets/{index-BoEETvjx.js → index-BxSzFzfM.js} +1 -1
  27. package/frontend/dist/assets/{index-Bf5BlYFK.js → index-Fi22OrSt.js} +3 -3
  28. package/frontend/dist/assets/{index-e0TiJwv-.js → index-dlR56s5t.js} +1 -1
  29. package/frontend/dist/assets/{jszip.min-PNrxBYIs.js → jszip.min-j8-Zud6N.js} +1 -1
  30. package/frontend/dist/assets/{search-DY3wMq7s.js → search-CfhpkpXs.js} +1 -1
  31. package/frontend/dist/assets/select-nwQBXqLw.js +13 -0
  32. package/frontend/dist/index.html +1 -1
  33. package/package.json +1 -1
  34. package/frontend/dist/assets/SettingsPage-GCr7_tnl.js +0 -13
  35. package/frontend/dist/assets/select-CYI-GI6f.js +0 -13
@@ -0,0 +1,43 @@
1
+ import { Router } from 'express';
2
+ declare const router: Router;
3
+ interface ParsedHostport {
4
+ host: string;
5
+ port: number;
6
+ }
7
+ export declare function parseHostport(raw: string): ParsedHostport | null;
8
+ /**
9
+ * Allowlist check on a literal IPv4/IPv6 address. Unlike notify-service's
10
+ * isPrivateAddress (which is a deny-list for outbound webhooks and uses
11
+ * loose startsWith on the *hostname*), this is strictly numeric and refuses
12
+ * the cloud-metadata link-local addresses even though they're RFC1918.
13
+ */
14
+ export declare function isAllowedProxyIp(addr: string): boolean;
15
+ /**
16
+ * Resolve hostname to a single IP (or accept literal IP) and confirm it's
17
+ * in the allowed private range. Returns the resolved IP to pin against
18
+ * DNS-rebinding TOCTOU: callers fetch by IP, not by hostname.
19
+ */
20
+ export declare function resolveAllowedTarget(host: string): Promise<string | null>;
21
+ /**
22
+ * Strip `_bp_tok=<jwt>` from a subPath string ("/foo/bar?x=1&_bp_tok=...&y=2#z")
23
+ * so the upstream never sees the session token. Idempotent: missing token
24
+ * is a no-op. Handles token in any query position and an empty resulting
25
+ * query (leaves no orphan "?").
26
+ */
27
+ export declare function stripTokenFromSubPath(subPath: string): string;
28
+ /**
29
+ * Rewrite root-relative absolute paths (src/href/action="/...") through
30
+ * this proxy. Also strip any upstream `<base href="...">` so the original
31
+ * declaration cannot redirect relative resolution back out to the bare
32
+ * ccweb origin. Each rewritten URL carries the session token so the iframe
33
+ * can fetch it without relying on cookies that the sandbox would drop.
34
+ * Protocol-relative and relative paths are untouched — relative URLs are
35
+ * resolved by the browser against the iframe URL (which already has the
36
+ * token in its query), and the browser preserves that query on the resolved
37
+ * URL? No — relative resolution does NOT inherit query. So relative paths
38
+ * within the page will appear without the token. They will fail. v0 limit.
39
+ */
40
+ export declare function rewriteHtml(html: string, prefix: string, token: string): string;
41
+ export declare function rewriteLocationHeader(loc: string, parsed: ParsedHostport, mountPrefix: string, token: string): string;
42
+ export default router;
43
+ //# sourceMappingURL=browser-proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-proxy.d.ts","sourceRoot":"","sources":["../../src/routes/browser-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAA0B,MAAM,SAAS,CAAC;AAUzD,QAAA,MAAM,MAAM,EAAE,MAAiB,CAAC;AAoDhC,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAShE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAetD;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAa/E;AAWD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAa7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAU/E;AAED,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GACZ,MAAM,CAoBR;AAgLD,eAAe,MAAM,CAAC"}
@@ -0,0 +1,400 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.parseHostport = parseHostport;
37
+ exports.isAllowedProxyIp = isAllowedProxyIp;
38
+ exports.resolveAllowedTarget = resolveAllowedTarget;
39
+ exports.stripTokenFromSubPath = stripTokenFromSubPath;
40
+ exports.rewriteHtml = rewriteHtml;
41
+ exports.rewriteLocationHeader = rewriteLocationHeader;
42
+ const express_1 = require("express");
43
+ const dns = __importStar(require("dns/promises"));
44
+ const net = __importStar(require("net"));
45
+ const jwt = __importStar(require("jsonwebtoken"));
46
+ const auth_1 = require("../auth");
47
+ const config_1 = require("../config");
48
+ const logger_1 = require("../logger");
49
+ const log = (0, logger_1.modLogger)('browser-proxy');
50
+ const router = (0, express_1.Router)();
51
+ const BLOCKED_PORTS = new Set([
52
+ 22, 23, 25, 110, 143, 465, 587, 993, 995,
53
+ 2049, 3389, 5432, 5984, 6379, 9200, 11211, 27017,
54
+ ]);
55
+ const STRIPPED_RESPONSE_HEADERS = new Set([
56
+ 'x-frame-options',
57
+ 'frame-options',
58
+ 'content-security-policy',
59
+ 'content-security-policy-report-only',
60
+ 'cross-origin-opener-policy',
61
+ 'cross-origin-embedder-policy',
62
+ 'cross-origin-resource-policy',
63
+ 'set-cookie',
64
+ 'content-length',
65
+ 'transfer-encoding',
66
+ 'content-encoding',
67
+ // P1 (codex review): upstream must not be able to clobber ccweb's
68
+ // own cookies/storage or register service workers on the daemon origin.
69
+ 'clear-site-data',
70
+ 'service-worker-allowed',
71
+ 'permissions-policy',
72
+ 'feature-policy',
73
+ ]);
74
+ // CSP sandbox without allow-same-origin forces an opaque origin even for
75
+ // same-origin iframes — proxied page JS cannot reach ccweb's localStorage
76
+ // / IndexedDB / cookies. Pair with the `<iframe sandbox>` attribute on the
77
+ // frontend so a direct GET to /api/browser-proxy/... in another tab is
78
+ // also opaque.
79
+ const PROXY_HTML_CSP = "sandbox allow-scripts allow-forms allow-popups";
80
+ const MAX_PROXY_SIZE = 16 * 1024 * 1024;
81
+ const UPSTREAM_TIMEOUT_MS = 15000;
82
+ // Cloud metadata services + IPv4 link-local that should NEVER be reachable.
83
+ // 169.254.169.254 = AWS/GCP/Azure metadata. 169.254.170.2 = ECS task role.
84
+ const BLOCKED_IPS = new Set(['169.254.169.254', '169.254.170.2']);
85
+ const BLOCKED_HOSTS = new Set(['metadata.google.internal', 'metadata']);
86
+ // Session token presented as ?_bp_tok=<jwt> on every proxy GET. Cookies
87
+ // would have been cleaner but a sandboxed iframe (no allow-same-origin)
88
+ // is treated as opaque origin → SameSite=Lax cookies are NOT sent on
89
+ // subresource requests originating inside the iframe. The query-param
90
+ // approach trades access-log exposure for working sandboxed iframes.
91
+ // Token is stripped before the URL is forwarded upstream so it never
92
+ // reaches the proxied site.
93
+ const TOKEN_QUERY_PARAM = '_bp_tok';
94
+ const SESSION_MAX_AGE_SEC = 60 * 60;
95
+ function parseHostport(raw) {
96
+ // v0: http only. IPv6 literals out of scope (path-segment ambiguity).
97
+ const m = raw.match(/^([a-zA-Z0-9.\-_]+):(\d{1,5})$/);
98
+ if (!m)
99
+ return null;
100
+ const host = m[1].toLowerCase();
101
+ const port = Number(m[2]);
102
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
103
+ return null;
104
+ if (BLOCKED_PORTS.has(port))
105
+ return null;
106
+ return { host, port };
107
+ }
108
+ /**
109
+ * Allowlist check on a literal IPv4/IPv6 address. Unlike notify-service's
110
+ * isPrivateAddress (which is a deny-list for outbound webhooks and uses
111
+ * loose startsWith on the *hostname*), this is strictly numeric and refuses
112
+ * the cloud-metadata link-local addresses even though they're RFC1918.
113
+ */
114
+ function isAllowedProxyIp(addr) {
115
+ const a = addr.replace(/^\[|\]$/g, '').toLowerCase();
116
+ const mapped = a.match(/^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/);
117
+ if (mapped)
118
+ return isAllowedProxyIp(mapped[1]);
119
+ // Reject anything that isn't a real IP — without this `10.evil.com`
120
+ // would match the `/^10\./` prefix below and slip through.
121
+ if (!net.isIP(a))
122
+ return false;
123
+ if (BLOCKED_IPS.has(a))
124
+ return false;
125
+ if (a === '0.0.0.0' || a === '::1')
126
+ return true;
127
+ if (/^127\./.test(a))
128
+ return true;
129
+ if (/^10\./.test(a))
130
+ return true;
131
+ if (/^192\.168\./.test(a))
132
+ return true;
133
+ if (/^172\.(1[6-9]|2\d|3[01])\./.test(a))
134
+ return true;
135
+ if (/^fc/.test(a) || /^fd/.test(a))
136
+ return true; // ULA v6
137
+ return false;
138
+ }
139
+ /**
140
+ * Resolve hostname to a single IP (or accept literal IP) and confirm it's
141
+ * in the allowed private range. Returns the resolved IP to pin against
142
+ * DNS-rebinding TOCTOU: callers fetch by IP, not by hostname.
143
+ */
144
+ async function resolveAllowedTarget(host) {
145
+ const h = host.replace(/\.$/, '').toLowerCase();
146
+ if (BLOCKED_HOSTS.has(h))
147
+ return null;
148
+ if (h === 'localhost')
149
+ return '127.0.0.1';
150
+ if (net.isIP(h))
151
+ return isAllowedProxyIp(h) ? h : null;
152
+ try {
153
+ const records = await dns.lookup(h, { all: true, verbatim: true });
154
+ if (records.length === 0)
155
+ return null;
156
+ if (!records.every((r) => isAllowedProxyIp(r.address)))
157
+ return null;
158
+ return records[0].address;
159
+ }
160
+ catch {
161
+ return null;
162
+ }
163
+ }
164
+ function appendToken(url, token) {
165
+ // Preserve fragment, insert token into query string.
166
+ const hashIdx = url.indexOf('#');
167
+ const hash = hashIdx >= 0 ? url.slice(hashIdx) : '';
168
+ const beforeHash = hashIdx >= 0 ? url.slice(0, hashIdx) : url;
169
+ const sep = beforeHash.includes('?') ? '&' : '?';
170
+ return `${beforeHash}${sep}${TOKEN_QUERY_PARAM}=${encodeURIComponent(token)}${hash}`;
171
+ }
172
+ /**
173
+ * Strip `_bp_tok=<jwt>` from a subPath string ("/foo/bar?x=1&_bp_tok=...&y=2#z")
174
+ * so the upstream never sees the session token. Idempotent: missing token
175
+ * is a no-op. Handles token in any query position and an empty resulting
176
+ * query (leaves no orphan "?").
177
+ */
178
+ function stripTokenFromSubPath(subPath) {
179
+ const hashIdx = subPath.indexOf('#');
180
+ const hash = hashIdx >= 0 ? subPath.slice(hashIdx) : '';
181
+ const beforeHash = hashIdx >= 0 ? subPath.slice(0, hashIdx) : subPath;
182
+ const qIdx = beforeHash.indexOf('?');
183
+ if (qIdx < 0)
184
+ return subPath;
185
+ const pathPart = beforeHash.slice(0, qIdx);
186
+ const queryPart = beforeHash.slice(qIdx + 1);
187
+ const kept = queryPart
188
+ .split('&')
189
+ .filter((kv) => kv !== '' && !kv.startsWith(`${TOKEN_QUERY_PARAM}=`) && kv !== TOKEN_QUERY_PARAM);
190
+ const newQuery = kept.length > 0 ? `?${kept.join('&')}` : '';
191
+ return `${pathPart}${newQuery}${hash}`;
192
+ }
193
+ /**
194
+ * Rewrite root-relative absolute paths (src/href/action="/...") through
195
+ * this proxy. Also strip any upstream `<base href="...">` so the original
196
+ * declaration cannot redirect relative resolution back out to the bare
197
+ * ccweb origin. Each rewritten URL carries the session token so the iframe
198
+ * can fetch it without relying on cookies that the sandbox would drop.
199
+ * Protocol-relative and relative paths are untouched — relative URLs are
200
+ * resolved by the browser against the iframe URL (which already has the
201
+ * token in its query), and the browser preserves that query on the resolved
202
+ * URL? No — relative resolution does NOT inherit query. So relative paths
203
+ * within the page will appear without the token. They will fail. v0 limit.
204
+ */
205
+ function rewriteHtml(html, prefix, token) {
206
+ return html
207
+ .replace(/<base\b[^>]*>/gi, '')
208
+ .replace(/\b(src|href|action)\s*=\s*(["'])\/(?!\/)([^"']*)\2/gi, (_, attr, quote, rest) => {
209
+ const rewritten = appendToken(`${prefix}/${rest}`, token);
210
+ return `${attr}=${quote}${rewritten}${quote}`;
211
+ });
212
+ }
213
+ function rewriteLocationHeader(loc, parsed, mountPrefix, token) {
214
+ try {
215
+ const absUrl = new URL(loc);
216
+ // Same scheme + same host + same port → safe to rewrite. Different
217
+ // scheme on the same host (e.g. http page issuing https:// redirect)
218
+ // is treated as cross-target and left untouched.
219
+ const expectedProto = 'http:';
220
+ const sameProto = absUrl.protocol === expectedProto;
221
+ const sameHost = absUrl.hostname.toLowerCase() === parsed.host;
222
+ const samePort = Number(absUrl.port || 80) === parsed.port;
223
+ if (sameProto && sameHost && samePort) {
224
+ return appendToken(`${mountPrefix}${absUrl.pathname}${absUrl.search}${absUrl.hash}`, token);
225
+ }
226
+ return loc;
227
+ }
228
+ catch {
229
+ if (loc.startsWith('/') && !loc.startsWith('//')) {
230
+ return appendToken(`${mountPrefix}${loc}`, token);
231
+ }
232
+ return loc;
233
+ }
234
+ }
235
+ function hasValidSessionToken(req) {
236
+ const token = req.query[TOKEN_QUERY_PARAM];
237
+ if (typeof token !== 'string' || !token)
238
+ return false;
239
+ try {
240
+ const config = (0, config_1.getConfig)();
241
+ const decoded = jwt.verify(token, config.jwtSecret);
242
+ return decoded.typ === 'browser-proxy' && typeof decoded.username === 'string';
243
+ }
244
+ catch {
245
+ return false;
246
+ }
247
+ }
248
+ /**
249
+ * Explicit-Bearer admin gate. Deliberately does NOT fall back to localhost
250
+ * auto-auth: any same-machine browser can blind-CSRF /_session if we trust
251
+ * the socket address, which would let a malicious page mint cookies and
252
+ * then drive arbitrary GETs through /api/browser-proxy/. Caller must
253
+ * present a real admin JWT (the frontend already has one via
254
+ * /api/auth/local-token or /api/auth/login).
255
+ */
256
+ function requireBearerAdmin(req, res, next) {
257
+ const authHeader = req.headers['authorization'];
258
+ if (!authHeader?.startsWith('Bearer ')) {
259
+ res.status(401).json({ error: 'Bearer token required' });
260
+ return;
261
+ }
262
+ const user = (0, auth_1.verifyToken)(authHeader.slice(7));
263
+ if (!user || !(0, config_1.isAdminUser)(user.username)) {
264
+ res.status(403).json({ error: 'Admin only' });
265
+ return;
266
+ }
267
+ req.user = user;
268
+ next();
269
+ }
270
+ /**
271
+ * Issues a short-lived JWT that the frontend attaches as ?_bp_tok=... to
272
+ * the iframe src. We deliberately do NOT use a cookie here — sandboxed
273
+ * iframes (no allow-same-origin) are treated as opaque origins, and Lax
274
+ * cookies are not sent on subresource requests originating inside such
275
+ * iframes. The query-param token is the only way to authenticate iframe
276
+ * subresource fetches without relaxing the sandbox.
277
+ */
278
+ router.post('/_session', requireBearerAdmin, (req, res) => {
279
+ const config = (0, config_1.getConfig)();
280
+ const token = jwt.sign({ username: req.user?.username, typ: 'browser-proxy' }, config.jwtSecret, { expiresIn: `${SESSION_MAX_AGE_SEC}s` });
281
+ res.json({ token, maxAge: SESSION_MAX_AGE_SEC });
282
+ });
283
+ async function handle(req, res) {
284
+ // Trust ONLY the query-param session token. Route mounted without
285
+ // authMiddleware so localhost auto-auth / any CSRF-able header cannot
286
+ // reach the proxy. Token can only be issued by POST /_session which
287
+ // requires explicit Bearer admin.
288
+ if (!hasValidSessionToken(req)) {
289
+ res.status(403).send('Browser proxy session required — call POST /_session first');
290
+ return;
291
+ }
292
+ const sessionToken = req.query[TOKEN_QUERY_PARAM];
293
+ const hostportRaw = req.params['hostport'];
294
+ if (!hostportRaw) {
295
+ res.status(400).send('hostport required');
296
+ return;
297
+ }
298
+ const parsed = parseHostport(hostportRaw);
299
+ if (!parsed) {
300
+ res.status(400).send('Invalid hostport (expected host:port, http only, ports 1-65535 except blocked)');
301
+ return;
302
+ }
303
+ const resolvedIp = await resolveAllowedTarget(parsed.host);
304
+ if (!resolvedIp) {
305
+ res.status(403).send('Target not allowed — must resolve to RFC1918 / loopback (excluding cloud-metadata)');
306
+ return;
307
+ }
308
+ // req.url is mount-relative. Strip hostport segment + the session-token
309
+ // query param before forwarding to upstream (token must NEVER leak to
310
+ // the proxied site).
311
+ const afterMount = req.url.replace(/^\/+/, '');
312
+ const slashIdx = afterMount.indexOf('/');
313
+ const rawSubPath = slashIdx >= 0 ? afterMount.slice(slashIdx) : '/';
314
+ const subPath = stripTokenFromSubPath(rawSubPath);
315
+ // Fetch by resolved IP — pins the connection to the address we just
316
+ // validated, so DNS rebinding between validation and fetch is harmless.
317
+ const targetUrl = `http://${resolvedIp}:${parsed.port}${subPath}`;
318
+ const mountPrefix = `/api/browser-proxy/${hostportRaw}`;
319
+ try {
320
+ const upstream = await fetch(targetUrl, {
321
+ method: 'GET',
322
+ redirect: 'manual',
323
+ signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS),
324
+ headers: {
325
+ // Send original hostname as Host so vhost-aware servers route correctly.
326
+ 'Host': `${parsed.host}:${parsed.port}`,
327
+ 'User-Agent': req.headers['user-agent'] || 'ccweb-browser-proxy',
328
+ 'Accept': req.headers['accept'] || '*/*',
329
+ 'Accept-Language': req.headers['accept-language'] || 'en',
330
+ },
331
+ });
332
+ const declaredLen = Number(upstream.headers.get('content-length') || '0');
333
+ if (declaredLen > MAX_PROXY_SIZE) {
334
+ res.status(413).send('Upstream response too large for proxy');
335
+ return;
336
+ }
337
+ res.status(upstream.status);
338
+ upstream.headers.forEach((value, name) => {
339
+ const lower = name.toLowerCase();
340
+ if (STRIPPED_RESPONSE_HEADERS.has(lower))
341
+ return;
342
+ if (lower === 'location') {
343
+ res.setHeader('Location', rewriteLocationHeader(value, parsed, mountPrefix, sessionToken));
344
+ return;
345
+ }
346
+ res.setHeader(name, value);
347
+ });
348
+ const contentType = upstream.headers.get('content-type') || '';
349
+ const isHtml = contentType.includes('text/html');
350
+ // Stream-aware cap: read in chunks, abort if we exceed limit before
351
+ // the whole body lands in memory. Compatible with chunked responses
352
+ // that don't declare Content-Length.
353
+ const chunks = [];
354
+ let total = 0;
355
+ if (upstream.body) {
356
+ const reader = upstream.body.getReader();
357
+ while (true) {
358
+ const { done, value } = await reader.read();
359
+ if (done)
360
+ break;
361
+ total += value.byteLength;
362
+ if (total > MAX_PROXY_SIZE) {
363
+ reader.cancel().catch(() => { });
364
+ if (!res.headersSent)
365
+ res.status(413);
366
+ res.end();
367
+ return;
368
+ }
369
+ chunks.push(value);
370
+ }
371
+ }
372
+ const buf = Buffer.concat(chunks.map((c) => Buffer.from(c)));
373
+ if (isHtml) {
374
+ res.setHeader('Content-Security-Policy', PROXY_HTML_CSP);
375
+ const rewritten = rewriteHtml(buf.toString('utf-8'), mountPrefix, sessionToken);
376
+ const out = Buffer.from(rewritten, 'utf-8');
377
+ res.setHeader('Content-Length', String(out.byteLength));
378
+ res.end(out);
379
+ }
380
+ else {
381
+ res.setHeader('Content-Length', String(buf.byteLength));
382
+ res.end(buf);
383
+ }
384
+ }
385
+ catch (err) {
386
+ const name = err?.name;
387
+ const reason = name === 'TimeoutError' ? 'Upstream timeout' : 'Upstream fetch failed';
388
+ log.warn({ err, targetUrl }, 'browser-proxy upstream error');
389
+ if (!res.headersSent) {
390
+ res.status(502).send(reason);
391
+ }
392
+ else if (!res.writableEnded) {
393
+ res.end();
394
+ }
395
+ }
396
+ }
397
+ router.get('/:hostport', handle);
398
+ router.get('/:hostport/*', handle);
399
+ exports.default = router;
400
+ //# sourceMappingURL=browser-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-proxy.js","sourceRoot":"","sources":["../../src/routes/browser-proxy.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import{bH as e,bI as t,r as n,bJ as r,c as a,h as i,bK as o,aX as s,j as l,R as c,d as u,U as d,bL as p,t as m,b2 as g,B as h,A as f,q as b,bM as y,a as E,a8 as v,aa as S,a6 as w,a5 as k,a9 as x,a4 as T,bk as A,aG as _,bz as I,bA as N,bB as R,i as C,bN as O,bO as L,s as D,bP as M,bQ as P,bC as F,bR as B,bE as U,bF as z,S as $,l as G,p as j}from"./index-Bf5BlYFK.js";import{C as H}from"./chevron-down-UeSwXtvj.js";import{a as q,C as V,u as W,c as Y}from"./index-BoEETvjx.js";function K(){!e.current&&t();const[a]=n.useState(r.current);return a}
1
+ import{bH as e,bI as t,r as n,bJ as r,c as a,h as i,bK as o,aX as s,j as l,R as c,d as u,U as d,bL as p,t as m,b2 as g,B as h,A as f,q as b,bM as y,a as E,a8 as v,aa as S,a6 as w,a5 as k,a9 as x,a4 as T,bk as A,aH as _,bz as I,bA as N,bB as R,i as C,bN as O,bO as L,s as D,bP as M,bQ as P,bC as F,bR as B,bE as U,bF as z,S as $,l as G,p as j}from"./index-Fi22OrSt.js";import{C as H}from"./chevron-down-BjD1KDpf.js";import{a as q,C as V,u as W,c as Y}from"./index-BxSzFzfM.js";function K(){!e.current&&t();const[a]=n.useState(r.current);return a}
2
2
  /**
3
3
  * @license lucide-react v0.309.0 - ISC
4
4
  *
@@ -1,4 +1,4 @@
1
- import{c as e,r as t,b as n,j as i,U as r,B as o,m as a}from"./index-Bf5BlYFK.js";import{Z as l,a as s}from"./ProjectPage-DdDicE9q.js";import"./purify.es-CgRAQgUo.js";import"./ChatOverlay-C6C_KmoQ.js";import"./chevron-down-UeSwXtvj.js";import"./index-BoEETvjx.js";import"./select-CYI-GI6f.js";import"./search-DY3wMq7s.js";
1
+ import{c as e,r as t,b as n,j as i,U as r,B as o,m as a}from"./index-Fi22OrSt.js";import{Z as l,a as s}from"./ProjectPage-D8O-uvJ3.js";import"./purify.es-CgRAQgUo.js";import"./ChatOverlay-Cm7M9uTo.js";import"./chevron-down-BjD1KDpf.js";import"./index-BxSzFzfM.js";import"./select-nwQBXqLw.js";import"./search-CfhpkpXs.js";
2
2
  /**
3
3
  * @license lucide-react v0.309.0 - ISC
4
4
  *