@dshn/agent 0.3.1 → 0.3.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.
Files changed (2) hide show
  1. package/lib/index.js +24 -6
  2. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -4624,7 +4624,15 @@ var SHIM_BODY = String.raw`
4624
4624
  let resolveReady
4625
4625
  const ready = new Promise((r) => { resolveReady = r })
4626
4626
  const hexToBytes = (hx) => { const a = new Uint8Array(hx.length / 2); for (let i = 0; i < a.length; i++) a[i] = parseInt(hx.substr(i * 2, 2), 16); return a }
4627
- const isApi = (url) => { try { const u = new URL(url, location.href); return u.origin === location.origin && u.pathname.startsWith('/api') } catch { return false } }
4627
+ // Same host as the page, over http(s) OR ws(s). A wss: URL has the origin
4628
+ // "wss://host", which never equals the page's "https://host", so comparing
4629
+ // origins would leave every event socket undecrypted.
4630
+ const isApi = (url) => {
4631
+ try {
4632
+ const u = new URL(url, location.href)
4633
+ return u.host === location.host && /^(https?|wss?):$/.test(u.protocol) && u.pathname.startsWith('/api')
4634
+ } catch { return false }
4635
+ }
4628
4636
 
4629
4637
  async function deriveKey(password, saltHex) {
4630
4638
  const base = await crypto.subtle.importKey('raw', enc.encode(password), 'PBKDF2', false, ['deriveKey'])
@@ -4704,7 +4712,9 @@ var SHIM_BODY = String.raw`
4704
4712
  : data instanceof Blob ? new Uint8Array(await data.arrayBuffer())
4705
4713
  : new Uint8Array(await new Blob([data]).arrayBuffer())
4706
4714
  const opened = await openBytes(key, raw)
4707
- data = opened[0] === 0 ? new TextDecoder().decode(opened.subarray(1)) : opened.subarray(1).buffer
4715
+ // slice, not subarray: subarray(1).buffer is the WHOLE decrypted
4716
+ // buffer, type byte included, and would hand the app one extra byte.
4717
+ data = opened[0] === 0 ? new TextDecoder().decode(opened.subarray(1)) : opened.slice(1).buffer
4708
4718
  } catch { return } // drop messages we can't decrypt
4709
4719
  }
4710
4720
  }
@@ -4838,6 +4848,13 @@ function e2eBootstrapTag(info) {
4838
4848
  const json = JSON.stringify({ salt: info.salt, device: info.device }).replace(/</g, "\\u003c");
4839
4849
  return `<script>(function (__dshnInfo) {${SHIM_BODY}})(${json})</script>`;
4840
4850
  }
4851
+ function credentialManifestLinks(html) {
4852
+ return html.replace(/<link\b[^>]*>/gi, (tag) => {
4853
+ if (!/\brel\s*=\s*["']?manifest\b/i.test(tag) || /\bcrossorigin\b/i.test(tag))
4854
+ return tag;
4855
+ return tag.replace(/\s*\/?>$/, (end) => ` crossorigin="use-credentials"${end.trim() === "/>" ? " />" : ">"}`);
4856
+ });
4857
+ }
4841
4858
  function injectE2EBootstrap(html, info) {
4842
4859
  const tag = e2eBootstrapTag(info);
4843
4860
  const head = /<head(\s[^>]*)?>/i.exec(html);
@@ -5615,16 +5632,17 @@ var AgentTunnel = class {
5615
5632
  return;
5616
5633
  }
5617
5634
  const wantsDocument = method === "GET" && headers.some(([k, v]) => k.toLowerCase() === "accept" && v.includes("text/html"));
5618
- const injectBootstrap = this.e2eKey !== null && wantsDocument;
5619
- if (injectBootstrap)
5635
+ if (wantsDocument)
5620
5636
  outHeaders["accept-encoding"] = "identity";
5621
5637
  const req = http.request({ host: this.config.localHost, port: this.localPort(), method, path, headers: outHeaders }, (res) => {
5622
5638
  const contentType = String(res.headers["content-type"] ?? "");
5623
- if (injectBootstrap && this.e2eKey !== null && res.statusCode === 200 && /^text\/html\b/i.test(contentType)) {
5639
+ if (wantsDocument && res.statusCode === 200 && /^text\/html\b/i.test(contentType)) {
5624
5640
  const chunks = [];
5625
5641
  res.on("data", (c) => chunks.push(c));
5626
5642
  res.on("end", () => {
5627
- const html = injectE2EBootstrap(Buffer.concat(chunks).toString("utf8"), { salt: this.e2eSalt, device: this.deviceId });
5643
+ let html = credentialManifestLinks(Buffer.concat(chunks).toString("utf8"));
5644
+ if (this.e2eKey !== null)
5645
+ html = injectE2EBootstrap(html, { salt: this.e2eSalt, device: this.deviceId });
5628
5646
  const body = Buffer.from(html, "utf8");
5629
5647
  const resHeaders = filterHeaders(headerListFromRaw(res.rawHeaders), /* @__PURE__ */ new Set([...HOP_BY_HOP, "content-length", "content-encoding"]));
5630
5648
  resHeaders.push(["content-length", String(body.length)]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dshn/agent",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Forward a local dsh web service to the public internet over ds.hn (bundled).",
5
5
  "keywords": [
6
6
  "dsh",