dsh-cliproxyapi-pro 1.5.0 → 1.6.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.
package/lib/index.js CHANGED
@@ -1695,18 +1695,33 @@ function admitRequest(ctx, req, res, expectedMethod) {
1695
1695
  return true;
1696
1696
  }
1697
1697
 
1698
- /** Update writes are browser-only: require an explicit, exact same Origin. */
1698
+ /** Same authority, using the scheme the Origin itself declares (host comparison only). */
1699
+ function sameAuthorityOrigin(origin, host) {
1700
+ let originUrl;
1701
+ try { originUrl = new URL(origin); } catch { return false; }
1702
+ if (originUrl.protocol !== "http:" && originUrl.protocol !== "https:") return false;
1703
+ if (originUrl.username || originUrl.password) return false;
1704
+ try {
1705
+ return originUrl.host === new URL(`${originUrl.protocol}//${host}`).host;
1706
+ } catch {
1707
+ return false;
1708
+ }
1709
+ }
1710
+
1711
+ /**
1712
+ * Update writes need an authenticated browser request. The host connection
1713
+ * service already enforces the Host/Origin fence and its session cookie, so
1714
+ * this only narrows it: when a browser sends an Origin it must belong to this
1715
+ * listener. A carrier that omits Origin (the desktop shell proxies API calls
1716
+ * without it) is allowed, because the session cookie is still required and an
1717
+ * unauthenticated caller is rejected before reaching the route.
1718
+ */
1699
1719
  function admitUpdateRequest(ctx, req, res, method) {
1700
1720
  if (!admitRequest(ctx, req, res, method)) return false;
1701
1721
  if (method === "GET") return true;
1702
- const protocol = req.socket?.encrypted ? "https:" : "http:";
1703
- try {
1704
- const origin = new URL(req.headers.origin);
1705
- const target = new URL(`${protocol}//${req.headers.host}`);
1706
- if (!req.headers.host || origin.origin !== target.origin || req.headers.origin !== origin.origin || origin.username || origin.password) {
1707
- throw new Error("Origin mismatch");
1708
- }
1709
- } catch {
1722
+ const host = req.headers.host;
1723
+ const origin = req.headers.origin;
1724
+ if (host === undefined || origin !== undefined && !sameAuthorityOrigin(origin, host)) {
1710
1725
  sendJson(res, 403, { ok: false, errorCode: "forbidden-origin", error: "Same-origin browser request required" });
1711
1726
  return false;
1712
1727
  }
package/lib/updater.js CHANGED
@@ -13,8 +13,9 @@ export const UPDATE_REGISTRIES = Object.freeze({
13
13
  export const UPDATE_TAGS = Object.freeze({ stable: "latest", beta: "beta", dev: "dev" });
14
14
  export const UPDATE_INTERVAL_MS = 6 * 60 * 60 * 1000;
15
15
  export const DEFAULT_UPDATE_SETTINGS = Object.freeze({
16
- autoCheck: true, autoDownload: false, channel: "stable", region: "auto",
16
+ autoCheck: true, autoDownload: false, autoInstall: false, channel: "stable", region: "auto",
17
17
  });
18
+ const BOOLEAN_SETTINGS = ["autoCheck", "autoDownload", "autoInstall"];
18
19
  const MAX_METADATA_BYTES = 512 * 1024;
19
20
  const MAX_PACKAGE_BYTES = 8 * 1024 * 1024;
20
21
  const MAX_UNPACKED_BYTES = 16 * 1024 * 1024;
@@ -29,6 +30,8 @@ export function normalizeUpdateSettings(raw = {}) {
29
30
  return {
30
31
  autoCheck: typeof raw?.autoCheck === "boolean" ? raw.autoCheck : true,
31
32
  autoDownload: raw?.autoDownload === true,
33
+ // Consumed by the Web client, which delegates installation to dshmarket.
34
+ autoInstall: raw?.autoInstall === true,
32
35
  channel: Object.hasOwn(UPDATE_TAGS, raw?.channel ?? "") ? raw.channel : "stable",
33
36
  region: ["auto", "global", "cn"].includes(raw?.region) ? raw.region : "auto",
34
37
  };
@@ -38,7 +41,7 @@ export function loadUpdateSettings(file) {
38
41
  try {
39
42
  const raw = JSON.parse(readFileSync(file, "utf8"));
40
43
  if (!raw || typeof raw !== "object" || Array.isArray(raw) || raw.version !== 1 ||
41
- ["autoCheck", "autoDownload"].some((key) => raw[key] !== undefined && typeof raw[key] !== "boolean") ||
44
+ BOOLEAN_SETTINGS.some((key) => raw[key] !== undefined && typeof raw[key] !== "boolean") ||
42
45
  raw.channel !== undefined && !Object.hasOwn(UPDATE_TAGS, raw.channel) ||
43
46
  raw.region !== undefined && !["auto", "global", "cn"].includes(raw.region)) fail("invalid-settings");
44
47
  return normalizeUpdateSettings(raw);
@@ -340,7 +343,7 @@ export function createPluginUpdater(deps) {
340
343
  if (!patch || typeof patch !== "object" || Array.isArray(patch)) fail("invalid-settings");
341
344
  if (Object.keys(patch).some((key) => !Object.hasOwn(DEFAULT_UPDATE_SETTINGS, key))) fail("invalid-settings");
342
345
  if (Object.hasOwn(patch, "channel") && !Object.hasOwn(UPDATE_TAGS, patch.channel) || Object.hasOwn(patch, "region") && !["auto", "global", "cn"].includes(patch.region)) fail("invalid-settings");
343
- for (const key of ["autoCheck", "autoDownload"]) if (Object.hasOwn(patch, key) && typeof patch[key] !== "boolean") fail("invalid-settings");
346
+ for (const key of BOOLEAN_SETTINGS) if (Object.hasOwn(patch, key) && typeof patch[key] !== "boolean") fail("invalid-settings");
344
347
  const next = normalizeUpdateSettings({ ...settings, ...patch });
345
348
  saving = true;
346
349
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-cliproxyapi-pro",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Auto-discovery CLIProxyAPI provider and management UI for DeepSeek Harness — multiple accounts, dynamic model sync, capability detection, and visual settings.",
5
5
  "keywords": [
6
6
  "dsh",