locadot 1.6.0-beta.2 → 1.6.0-beta.3

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/CHANGELOG.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
- The version in `package.json` is 1.6.0-beta.2 (published under the `beta` tag). The owner decides the bump. **2.0.0** is suggested because of the
5
+ The version in `package.json` is 1.6.0-beta.3 (published under the `beta` tag). The owner decides the bump. **2.0.0** is suggested because of the
6
6
  breaking items below.
7
7
 
8
8
  ### Breaking
@@ -13,6 +13,7 @@ breaking items below.
13
13
  - Bare `localhost` is reserved for the dashboard and can't be mapped.
14
14
 
15
15
  ### Added
16
+ - `--cors` per mapping (CLI, API and dashboard). `Origin`/`Referer` are sent as the target's own origin, CORS preflights are answered locally, and any origin may call the domain with credentials. (ENH-12)
16
17
  - A control panel at `https://localhost` with a dark theme. You can add, edit and remove mappings, toggle CA trust and start-at-boot, see root/admin and privileged-port status,
17
18
  tail and clear logs, and stop the proxy. (FEAT-05)
18
19
  - A token-protected JSON API for scripts and AI agents (`POST/PUT/DELETE /api/hosts`, `/api/startup`, `/api/trust`, `/api/logs`, `/api/proxy/stop`),
package/README.md CHANGED
@@ -54,6 +54,7 @@ Options for `add` / `update`:
54
54
  | `-p, --port <port>` | Shorthand for `--target http://localhost:<port>`. |
55
55
  | `-t, --target <url>` | Any http(s) upstream. A bare `host:port` means `http://host:port`. |
56
56
  | `-k, --insecure` | Don't verify the TLS certificate of an `https` target (self-signed upstreams). |
57
+ | `--cors` / `--no-cors` | Bypass CORS for this domain. The upstream gets `Origin`/`Referer` as its own origin, preflights are answered locally, any origin may read responses (with credentials), and cookies become `SameSite=None` over HTTPS. URLs hard-coded in pages still go to the real domain. |
57
58
  | `--no-start` | Save the mapping without starting the proxy. |
58
59
 
59
60
  ### Proxy
@@ -93,7 +94,7 @@ are answered by locadot itself, not proxied. The page is a dark control panel:
93
94
 
94
95
  - **System:** whether the proxy runs as root/admin, whether ports below 1024 can be bound (with the Linux sysctl fix when they
95
96
  can't), CA trust with an on/off toggle, start-at-boot with an on/off toggle, ports, bind addresses and state dir. It also has a **Stop proxy** button.
96
- - **Proxies:** add a mapping (host + port / host:port / URL, and an optional "insecure TLS" box), edit a target inline, or remove one. The table shows
97
+ - **Proxies:** add a mapping (host + port / host:port / URL, and optional "Insecure TLS" and "Bypass CORS" boxes), edit a target inline, or remove one. The table shows
97
98
  each source → destination with up/down, status, latency, hits, errors, last access and average ms. It refreshes every 5 s.
98
99
  - **Logs:** a live tail with refresh and clear.
99
100
  - **CLI / API snippets** you can copy.
@@ -113,8 +114,8 @@ each time the proxy starts.
113
114
  | `GET /api/status` | | Proxy info, uptime, host count, and `system` (`platform`, `isRoot`, `canBindPrivileged`, `unprivilegedPortStart`, `caTrusted`, `startup`, `stateDir`). |
114
115
  | `GET /api/hosts` | | Every mapping with urls, probe (`up`, `status`, `ms`) and stats. |
115
116
  | `GET /api/logs?lines=200` | | `{ lines: [...] }` |
116
- | `POST /api/hosts` | `{ "host": "app.localhost", "target": "3000", "insecure": false }` | Add a mapping (201; 409 if it exists). |
117
- | `PUT /api/hosts/:host` | `{ "target": "https://example.com", "insecure": false }` | Change a mapping (404 if unknown). |
117
+ | `POST /api/hosts` | `{ "host": "app.localhost", "target": "3000", "insecure": false, "cors": false }` | Add a mapping (201; 409 if it exists). |
118
+ | `PUT /api/hosts/:host` | `{ "target": "https://example.com", "insecure": false, "cors": true }` | Change a mapping (404 if unknown). |
118
119
  | `DELETE /api/hosts/:host` | | Remove a mapping. |
119
120
  | `POST /api/startup` | `{ "enabled": true }` | Start at boot on/off. |
120
121
  | `POST /api/trust` | `{ "trusted": true }` | Trust or untrust the CA. |
@@ -165,7 +165,7 @@ async function route(req, url, ctx) {
165
165
  const body = await readJson(req);
166
166
  try {
167
167
  if (path === "/api/hosts") {
168
- const { host, entry } = await hosts_1.default.add({ host: body.host, target: body.target, insecure: optionalBool(body.insecure, "insecure") });
168
+ const { host, entry } = await hosts_1.default.add({ host: body.host, target: body.target, insecure: optionalBool(body.insecure, "insecure"), cors: optionalBool(body.cors, "cors") });
169
169
  ctx.reload();
170
170
  logger_1.default.info(`➕ dashboard: ${host} → ${entry.target}`);
171
171
  return { status: 201, body: { ok: true, host, ...entry, url: hostUrl(host, ctx) } };
@@ -178,7 +178,7 @@ async function route(req, url, ctx) {
178
178
  logger_1.default.info(`🗑️ dashboard: removed ${host}`);
179
179
  return { status: 200, body: { ok: true, host } };
180
180
  }
181
- const updated = await hosts_1.default.update({ host, target: body.target, insecure: optionalBool(body.insecure, "insecure") });
181
+ const updated = await hosts_1.default.update({ host, target: body.target, insecure: optionalBool(body.insecure, "insecure"), cors: optionalBool(body.cors, "cors") });
182
182
  ctx.reload();
183
183
  logger_1.default.info(`✏️ dashboard: ${updated.host} → ${updated.entry.target}`);
184
184
  return { status: 200, body: { ok: true, host: updated.host, ...updated.entry, url: hostUrl(updated.host, ctx) } };
@@ -42,6 +42,7 @@ async function buildHosts(ctx) {
42
42
  host,
43
43
  target: entry.target,
44
44
  insecure: !!entry.insecure,
45
+ cors: !!entry.cors,
45
46
  createdAt: entry.createdAt,
46
47
  updatedAt: entry.updatedAt,
47
48
  urls: hostUrls(host, httpPort, httpsPort),
@@ -305,6 +305,9 @@ tr:last-child td { border-bottom: none; }
305
305
  width: 100%;
306
306
  min-width: 160px;
307
307
  }
308
+ .badges { display: flex; flex-wrap: wrap; gap: 4px; }
309
+ .badge-cors { color: var(--accent-cyan); border-color: var(--accent-cyan); background: rgba(34, 211, 238, 0.1); }
310
+ td .inline-check + .inline-check { margin-top: 4px; }
308
311
  .inline-check { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); }
309
312
  .dim { color: var(--muted-dim); }
310
313
 
@@ -449,6 +452,7 @@ const clientJs = `
449
452
  var addHostInput = document.getElementById("add-host");
450
453
  var addTargetInput = document.getElementById("add-target");
451
454
  var addInsecureInput = document.getElementById("add-insecure");
455
+ var addCorsInput = document.getElementById("add-cors");
452
456
  var addSubmitBtn = document.getElementById("add-submit");
453
457
  var addErrorEl = document.getElementById("add-error");
454
458
 
@@ -757,9 +761,10 @@ const clientJs = `
757
761
  }
758
762
  if (hostVal.indexOf(".") === -1) hostVal = hostVal + ".localhost";
759
763
  var insecureVal = !!addInsecureInput.checked;
764
+ var corsVal = !!addCorsInput.checked;
760
765
  addSubmitBtn.disabled = true;
761
766
  addSubmitBtn.classList.add("busy");
762
- apiFetch("/api/hosts", { method: "POST", body: JSON.stringify({ host: hostVal, target: targetVal, insecure: insecureVal }) })
767
+ apiFetch("/api/hosts", { method: "POST", body: JSON.stringify({ host: hostVal, target: targetVal, insecure: insecureVal, cors: corsVal }) })
763
768
  .then(function () {
764
769
  addForm.reset();
765
770
  showToast("Added " + hostVal, "success");
@@ -822,17 +827,24 @@ const clientJs = `
822
827
  tr.appendChild(makeCell("\\u2192", "arrow"));
823
828
  tr.appendChild(makeCell(row.target, "mono"));
824
829
 
825
- var insecureTd = document.createElement("td");
826
- if (row.insecure) {
827
- var badge = document.createElement("span");
828
- badge.className = "badge";
829
- badge.textContent = "insecure";
830
- insecureTd.appendChild(badge);
830
+ var optionsTd = document.createElement("td");
831
+ var flags = [];
832
+ if (row.insecure) flags.push(["insecure", "Upstream TLS certificate is not verified"]);
833
+ if (row.cors) flags.push(["cors", "Origin/Referer rewritten to the target; any origin may call this host"]);
834
+ if (flags.length) {
835
+ optionsTd.className = "badges";
836
+ flags.forEach(function (flag) {
837
+ var badge = document.createElement("span");
838
+ badge.className = "badge" + (flag[0] === "cors" ? " badge-cors" : "");
839
+ badge.textContent = flag[0];
840
+ badge.title = flag[1];
841
+ optionsTd.appendChild(badge);
842
+ });
831
843
  } else {
832
- insecureTd.className = "dim";
833
- insecureTd.textContent = "\\u2014";
844
+ optionsTd.className = "dim";
845
+ optionsTd.textContent = "\\u2014";
834
846
  }
835
- tr.appendChild(insecureTd);
847
+ tr.appendChild(optionsTd);
836
848
 
837
849
  var statusTd = document.createElement("td");
838
850
  statusTd.appendChild(statusPill(row));
@@ -899,6 +911,14 @@ const clientJs = `
899
911
  insecureLabel.appendChild(insecureInput);
900
912
  insecureLabel.appendChild(document.createTextNode("insecure"));
901
913
  insecureTd.appendChild(insecureLabel);
914
+ var corsLabel = document.createElement("label");
915
+ corsLabel.className = "inline-check";
916
+ var corsInput = document.createElement("input");
917
+ corsInput.type = "checkbox";
918
+ corsInput.checked = !!row.cors;
919
+ corsLabel.appendChild(corsInput);
920
+ corsLabel.appendChild(document.createTextNode("cors"));
921
+ insecureTd.appendChild(corsLabel);
902
922
  tr.appendChild(insecureTd);
903
923
 
904
924
  tr.appendChild(makeCell("\\u2014", "dim"));
@@ -927,7 +947,7 @@ const clientJs = `
927
947
  cancelBtn.disabled = true;
928
948
  apiFetch("/api/hosts/" + encodeURIComponent(row.host), {
929
949
  method: "PUT",
930
- body: JSON.stringify({ target: newTarget, insecure: !!insecureInput.checked })
950
+ body: JSON.stringify({ target: newTarget, insecure: !!insecureInput.checked, cors: !!corsInput.checked })
931
951
  })
932
952
  .then(function () {
933
953
  editingHost = null;
@@ -1134,6 +1154,10 @@ function renderPage(nonce, token) {
1134
1154
  <input type="checkbox" id="add-insecure" name="insecure">
1135
1155
  <label for="add-insecure">Insecure TLS</label>
1136
1156
  </div>
1157
+ <div class="field field-checkbox" title="Send Origin/Referer as the target's own and let any origin call this host">
1158
+ <input type="checkbox" id="add-cors" name="cors">
1159
+ <label for="add-cors">Bypass CORS</label>
1160
+ </div>
1137
1161
  <button type="submit" id="add-submit" class="btn btn-primary">Add proxy</button>
1138
1162
  </div>
1139
1163
  <div id="add-error" class="field-error" role="alert" hidden></div>
@@ -1153,7 +1177,7 @@ function renderPage(nonce, token) {
1153
1177
  <th scope="col">Host</th>
1154
1178
  <th scope="col" class="visually-hidden">Flow</th>
1155
1179
  <th scope="col">Target</th>
1156
- <th scope="col">Insecure</th>
1180
+ <th scope="col">Options</th>
1157
1181
  <th scope="col">Status</th>
1158
1182
  <th scope="col">Hits</th>
1159
1183
  <th scope="col">Errors</th>
package/dist/index.js CHANGED
@@ -30,6 +30,8 @@ const destinationOptions = (command) => command
30
30
  .option("-p, --port <port>", "Local port to forward to (shorthand for --target http://localhost:<port>)")
31
31
  .option("-t, --target <url>", "Any upstream: 3000, 127.0.0.1:8080, http://192.168.1.5:8080, https://google.com")
32
32
  .option("-k, --insecure", "Don't verify the TLS certificate of an https target")
33
+ .option("--cors", "Send Origin/Referer as the target's own and let any origin call this domain")
34
+ .option("--no-cors", "Turn --cors off again (update)")
33
35
  .option("--no-start", "Only save the mapping; don't start the proxy");
34
36
  program
35
37
  .name("locadot")
@@ -90,7 +90,7 @@ const warnIfDown = async (entry) => {
90
90
  };
91
91
  class Commands {
92
92
  static async add(options) {
93
- const { host, entry } = await hosts_1.default.add({ host: options.host, target: resolveTarget(options), insecure: options.insecure });
93
+ const { host, entry } = await hosts_1.default.add({ host: options.host, target: resolveTarget(options), insecure: options.insecure, cors: options.cors });
94
94
  const { target } = entry;
95
95
  await ensureRunning(options);
96
96
  print(`✅ ${(0, exports.urlFor)(host)} → ${target}`);
@@ -100,7 +100,7 @@ class Commands {
100
100
  await warnIfDown(entry);
101
101
  }
102
102
  static async update(options) {
103
- const { host, entry } = await hosts_1.default.update({ host: options.host, target: resolveTarget(options), insecure: options.insecure });
103
+ const { host, entry } = await hosts_1.default.update({ host: options.host, target: resolveTarget(options), insecure: options.insecure, cors: options.cors });
104
104
  const { target } = entry;
105
105
  await ensureRunning(options);
106
106
  print(`✅ Updated ${(0, exports.urlFor)(host)} → ${target}`);
@@ -122,7 +122,7 @@ class Commands {
122
122
  }
123
123
  const width = Math.max(...hosts.map(([host]) => (0, exports.urlFor)(host).length));
124
124
  for (const [host, entry] of hosts) {
125
- print(`${(0, exports.urlFor)(host).padEnd(width)} → ${entry.target}${entry.insecure ? " (insecure)" : ""}`);
125
+ print(`${(0, exports.urlFor)(host).padEnd(width)} → ${entry.target}${entry.insecure ? " (insecure)" : ""}${entry.cors ? " (cors)" : ""}`);
126
126
  }
127
127
  print(`☑️ Total: ${hosts.length}.`);
128
128
  }
package/dist/lib/hosts.js CHANGED
@@ -69,7 +69,7 @@ class HostOps {
69
69
  if (existing) {
70
70
  throw new ConflictError(`❌ ${host} is already mapped to ${existing.target}. Use \`locadot update --host ${host} ...\` instead.`);
71
71
  }
72
- registry.hosts[host] = { target, insecure: input.insecure || undefined, createdAt: now, updatedAt: now };
72
+ registry.hosts[host] = { target, insecure: input.insecure || undefined, cors: input.cors || undefined, createdAt: now, updatedAt: now };
73
73
  return registry.hosts[host];
74
74
  });
75
75
  return { host, entry };
@@ -82,7 +82,8 @@ class HostOps {
82
82
  if (!existing)
83
83
  throw new NotFoundError(`${constants_1.default.proxyInfo.hostNotFound} (${host})`);
84
84
  const insecure = input.insecure ?? existing.insecure;
85
- registry.hosts[host] = { ...existing, target, insecure: insecure || undefined, updatedAt: new Date().toISOString() };
85
+ const cors = input.cors ?? existing.cors;
86
+ registry.hosts[host] = { ...existing, target, insecure: insecure || undefined, cors: cors || undefined, updatedAt: new Date().toISOString() };
86
87
  return registry.hosts[host];
87
88
  });
88
89
  return { host, entry };
package/dist/lib/http.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.hostOf = void 0;
6
+ exports.applyCors = exports.hostOf = void 0;
7
7
  const template_1 = require("../constants/template");
8
8
  const constants_1 = __importDefault(require("../constants"));
9
9
  const logger_1 = __importDefault(require("../utils/logger"));
@@ -34,8 +34,54 @@ const proxyOptions = (req, entry) => ({
34
34
  hostRewrite: req.headers.host,
35
35
  protocolRewrite: isTls(req) ? "https" : "http",
36
36
  cookieDomainRewrite: { "*": "" },
37
- headers: { "X-Original-Host": req.headers.host || "" },
37
+ headers: { "X-Original-Host": req.headers.host || "", ...(entry.cors ? sameOriginHeaders(req, entry) : {}) },
38
38
  });
39
+ // --cors: the upstream should see a request from its own site, so origin/CSRF checks pass.
40
+ const sameOriginHeaders = (req, entry) => {
41
+ const origin = new URL(entry.target).origin;
42
+ const headers = {};
43
+ if (req.headers.origin)
44
+ headers.Origin = origin;
45
+ if (req.headers.referer) {
46
+ try {
47
+ const referer = new URL(req.headers.referer);
48
+ headers.Referer = origin + referer.pathname + referer.search;
49
+ }
50
+ catch {
51
+ headers.Referer = origin + "/";
52
+ }
53
+ }
54
+ return headers;
55
+ };
56
+ const isPreflight = (req) => req.method === "OPTIONS" && !!req.headers.origin && !!req.headers["access-control-request-method"];
57
+ /** Headers that let the calling page read the response, credentials included. */
58
+ const allowOrigin = (req) => req.headers.origin
59
+ ? { "Access-Control-Allow-Origin": req.headers.origin, "Access-Control-Allow-Credentials": "true", Vary: "Origin" }
60
+ : { "Access-Control-Allow-Origin": "*" };
61
+ /**
62
+ * Rewrites an upstream response for a --cors host: our CORS headers replace the upstream's,
63
+ * and cookies become SameSite=None so a page on another origin can send them back.
64
+ */
65
+ const applyCors = (req, headers) => {
66
+ for (const name of Object.keys(headers)) {
67
+ if (name.startsWith("access-control-"))
68
+ delete headers[name];
69
+ }
70
+ const exposed = Object.keys(headers).filter((name) => name !== "set-cookie" && name !== "vary");
71
+ headers["access-control-allow-origin"] = req.headers.origin || "*";
72
+ if (req.headers.origin) {
73
+ headers["access-control-allow-credentials"] = "true";
74
+ const vary = String(headers.vary || "");
75
+ if (!/(^|,)\s*(origin|\*)\s*(,|$)/i.test(vary))
76
+ headers.vary = vary ? `${vary}, Origin` : "Origin";
77
+ }
78
+ if (exposed.length)
79
+ headers["access-control-expose-headers"] = exposed.join(", ");
80
+ if (isTls(req) && headers["set-cookie"]) {
81
+ headers["set-cookie"] = headers["set-cookie"].map((cookie) => cookie.replace(/;\s*samesite=[^;]*/gi, "").replace(/;\s*secure\b(?!=)/gi, "") + "; SameSite=None; Secure");
82
+ }
83
+ };
84
+ exports.applyCors = applyCors;
39
85
  const record = (stats, host, status, ms, error) => {
40
86
  const current = stats.get(host) || { hits: 0, errors: 0 };
41
87
  current.hits += 1;
@@ -60,6 +106,20 @@ class HttpModule {
60
106
  res.end((0, template_1.proxyNotFound)(host, dashboardUrl(req)));
61
107
  return;
62
108
  }
109
+ // Answered here: upstreams often reject OPTIONS or don't send the headers the browser wants.
110
+ if (entry.cors && isPreflight(req)) {
111
+ const requested = req.headers["access-control-request-headers"];
112
+ res.writeHead(204, {
113
+ ...allowOrigin(req),
114
+ "Access-Control-Allow-Methods": String(req.headers["access-control-request-method"]),
115
+ ...(requested ? { "Access-Control-Allow-Headers": String(requested) } : {}),
116
+ ...(req.headers["access-control-request-private-network"] ? { "Access-Control-Allow-Private-Network": "true" } : {}),
117
+ "Access-Control-Max-Age": "600",
118
+ "Content-Length": "0",
119
+ });
120
+ res.end();
121
+ return;
122
+ }
63
123
  const started = Date.now();
64
124
  res.once("finish", () => {
65
125
  record(ctx.stats, host, res.statusCode, Date.now() - started, res.statusCode >= 500);
@@ -70,7 +130,8 @@ class HttpModule {
70
130
  res.destroy();
71
131
  return;
72
132
  }
73
- res.writeHead(502, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
133
+ // With --cors the page should see a 502, not a CORS error.
134
+ res.writeHead(502, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store", ...(entry.cors ? allowOrigin(req) : {}) });
74
135
  res.end((0, template_1.upstreamDown)(host, entry.target, err?.code || err?.message || "error", dashboardUrl(req)));
75
136
  });
76
137
  }
package/dist/server.js CHANGED
@@ -1,4 +1,37 @@
1
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
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -12,7 +45,7 @@ const http_proxy_1 = __importDefault(require("http-proxy"));
12
45
  const constants_1 = __importDefault(require("./constants"));
13
46
  const registry_1 = __importDefault(require("./lib/registry"));
14
47
  const locadot_file_1 = __importDefault(require("./lib/locadot-file"));
15
- const http_2 = __importDefault(require("./lib/http"));
48
+ const http_2 = __importStar(require("./lib/http"));
16
49
  const localhost_1 = __importDefault(require("./lib/localhost"));
17
50
  const file_1 = __importDefault(require("./utils/file"));
18
51
  const logger_1 = __importDefault(require("./utils/logger"));
@@ -68,7 +101,7 @@ async function startCentralProxy() {
68
101
  // Hop-by-hop headers describe the upstream connection, not ours. Apache sends
69
102
  // `Connection: Upgrade, close` + `Upgrade: h2`, which made us close the browser's
70
103
  // socket after every response (ERR_TOO_MANY_RETRIES on asset-heavy pages).
71
- proxy.on("proxyRes", (proxyRes) => {
104
+ proxy.on("proxyRes", (proxyRes, req) => {
72
105
  if (proxyRes.statusCode === 101)
73
106
  return;
74
107
  const listed = String(proxyRes.headers.connection || "")
@@ -78,6 +111,8 @@ async function startCentralProxy() {
78
111
  for (const name of [...listed, "connection", "keep-alive", "upgrade", "proxy-connection"]) {
79
112
  delete proxyRes.headers[name];
80
113
  }
114
+ if (registry.hosts[(0, http_2.hostOf)(req)]?.cors)
115
+ (0, http_2.applyCors)(req, proxyRes.headers);
81
116
  });
82
117
  const info = {
83
118
  pid: process.pid,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "locadot",
3
- "version": "1.6.0-beta.2",
3
+ "version": "1.6.0-beta.3",
4
4
  "description": "Secure your local development environment with HTTPS and custom domains like dev.localhost.",
5
5
  "homepage": "https://www.npmjs.com/package/locadot",
6
6
  "main": "dist/index.js",