@qilin-agent/client-connection 3.1.3 → 3.1.5

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/README.i18n.yaml CHANGED
@@ -5,33 +5,33 @@
5
5
  /:
6
6
  en: 13b131457d03a224
7
7
  zh: 541bd85b21259ff3
8
- /qilin-client-connection:
9
- en: bfbb88dd96c1e4cb
10
- zh: 37988d4aa00fa2a5
11
- /qilin-client-connection/summary:
8
+ /qilin-agent-client-connection:
9
+ en: eec5c8484d97c773
10
+ zh: 86905b0501d26e73
11
+ /qilin-agent-client-connection/summary:
12
12
  en: a83b5e9fd1968062
13
13
  zh: ac68628e185a4bff
14
- /qilin-client-connection/table-of-contents:
14
+ /qilin-agent-client-connection/table-of-contents:
15
15
  en: 18792bf2fb4f8030
16
16
  zh: 84c63acc7e0099ae
17
- /qilin-client-connection/use-this-package:
17
+ /qilin-agent-client-connection/use-this-package:
18
18
  en: ef04350968741ba3
19
19
  zh: abcded86153ddd9b
20
- /qilin-client-connection/browser-authentication-and-request-trust:
20
+ /qilin-agent-client-connection/browser-authentication-and-request-trust:
21
21
  en: 73a9f2fd2c2d559e
22
22
  zh: 4b629185fecee159
23
- /qilin-client-connection/connection-generation:
24
- en: 9ab2b92f7be612e3
25
- zh: e2f6994b29f4a9cc
26
- /qilin-client-connection/model-experience:
23
+ /qilin-agent-client-connection/connection-generation:
24
+ en: d260c2ac53a86c8f
25
+ zh: 3f1de43f27afd85e
26
+ /qilin-agent-client-connection/model-experience:
27
27
  en: 88bbc33fe9ae9df8
28
28
  zh: bfbab6d51b4abd40
29
- /qilin-client-connection/model-experience/kv-cache-effect:
29
+ /qilin-agent-client-connection/model-experience/kv-cache-effect:
30
30
  en: ca75c51c89c2c9b0
31
31
  zh: de54f3467ef3b9be
32
- /qilin-client-connection/known-limitations-and-deferred-work:
32
+ /qilin-agent-client-connection/known-limitations-and-deferred-work:
33
33
  en: 21d90520c2e5d4b9
34
34
  zh: c9ae88395b272977
35
- /qilin-client-connection/known-limitations-and-deferred-work/dev-note:
35
+ /qilin-agent-client-connection/known-limitations-and-deferred-work/dev-note:
36
36
  en: e67f633dc6fe7773
37
37
  zh: e88aeb617ffb74ea
package/lib/index.js CHANGED
@@ -128,23 +128,27 @@ function isLoopbackHostname(hostname) {
128
128
  * the attacker's domain while the socket reaches this server) and cross-site
129
129
  * requests fired from a malicious page. The Host fence binds every request,
130
130
  * browser-looking or not: over plain HTTP a browser attaches neither Origin
131
- * nor Fetch-Metadata to reads (images and navigations — those
132
- * headers go only to trustworthy destinations), so an unmarked request may
133
- * still be a rebound browser read and Host is the one header rebinding cannot
134
- * forge. Non-browser and remote clients pass the same fence via loopback,
135
- * deployment-derived LAN IP literals, or a declared `trustedHosts` authority.
136
- * Network reachability and authentication stay out of scope: binding policy
137
- * belongs to the webserver config, and this fence is not an auth layer.
131
+ * nor Fetch-Metadata to reads (images and navigations — those headers go only
132
+ * to trustworthy destinations), so an unmarked request may still be a rebound
133
+ * browser read and Host is the one header rebinding cannot forge. Non-browser
134
+ * and remote clients pass the same fence via loopback, the listener's bind IP
135
+ * literal, or a declared `trustedHosts` authority. Binding policy belongs to
136
+ * the webserver config; this fence never establishes identity.
138
137
  */
139
138
  function header$1(headers, name) {
140
139
  if (headers instanceof Headers) return headers.get(name) ?? void 0;
141
140
  const value = headers[name];
142
141
  return typeof value === "string" ? value : void 0;
143
142
  }
144
- /** Normalized URL of a Host-header authority (hostname lowercased, default port stripped, IPv6 bracketed), or undefined when unparsable. */
145
- function parseAuthority(authority) {
143
+ /**
144
+ * Normalized URL of a bare authority (hostname lowercased, default port
145
+ * stripped, IPv6 bracketed), or undefined when unparsable.
146
+ * @param authority - `host` or `host:port` text.
147
+ * @param scheme - special scheme to parse under; its default port is the one stripped.
148
+ */
149
+ function parseAuthority(authority, scheme = "http:") {
146
150
  try {
147
- return new URL(`http://${authority}`);
151
+ return new URL(`${scheme}//${authority}`);
148
152
  } catch {
149
153
  return;
150
154
  }
@@ -176,43 +180,74 @@ function assertTrustedAuthority(entry) {
176
180
  * shapes like `host:port ` as port-less.
177
181
  */
178
182
  function canonicalAuthority(entry, entryUrl) {
179
- const port = entryUrl.port !== "" ? entryUrl.port : new URL(`https://${entry}`).port;
183
+ const port = entryUrl.port !== "" ? entryUrl.port : new URL(`${entryUrl.protocol === "http:" ? "https:" : "http:"}//${entry}`).port;
180
184
  return port === "" ? entryUrl.hostname : `${entryUrl.hostname}:${port}`;
181
185
  }
182
186
  /**
183
187
  * Whether the request authority matches a `trustedHosts` entry. An entry with
184
188
  * an explicit port matches that exact authority; a port-less entry matches the
185
- * hostname on any port (the shape the CLI derives for IP-literal LAN serving,
186
- * where the bound port may be OS-assigned). Both sides compare through WHATWG
187
- * normalization, so case and a redundant `:80` never decide trust.
189
+ * hostname on any port. Both sides compare through WHATWG normalization,
190
+ * so case and a redundant scheme-default port never decide trust.
188
191
  */
189
- function isTrustedAuthority(hostUrl, trustedHosts) {
192
+ function isTrustedAuthority(hostUrl, trustedHosts, protocol) {
190
193
  return trustedHosts.some((entry) => {
191
- const entryUrl = parseAuthority(entry);
194
+ const entryUrl = parseAuthority(entry, protocol);
192
195
  if (entryUrl === void 0) return false;
193
196
  return canonicalAuthority(entry, entryUrl) === entryUrl.hostname ? entryUrl.hostname === hostUrl.hostname : entryUrl.host === hostUrl.host;
194
197
  });
195
198
  }
196
199
  /**
200
+ * Whether a parsed authority names the deployment's own bind address. A browser
201
+ * dialing that literal sends it as Host, and accepting it grants nothing
202
+ * further: the literal is no rebinding target, the Origin and cross-site checks
203
+ * still apply, and it stays outside `trustedHosts`. Matching ignores the port,
204
+ * like a port-less entry, and any `%zone` id (an interface selector, not
205
+ * address text).
206
+ */
207
+ function isBindAddressAuthority(hostUrl, bindHost) {
208
+ if (bindHost === void 0) return false;
209
+ const zoneAt = bindHost.indexOf("%");
210
+ const address = zoneAt === -1 ? bindHost : bindHost.slice(0, zoneAt);
211
+ const bindUrl = parseAuthority(address.includes(":") ? `[${address}]` : address);
212
+ return bindUrl !== void 0 && bindUrl.hostname === hostUrl.hostname;
213
+ }
214
+ /**
197
215
  * Decide whether one /api request may reach the RPC bridge.
198
216
  * @param request - Node HTTP or Fetch request facts (headers).
199
217
  * @param trustedHosts - non-loopback authorities this deployment serves: exact `host:port`, or port-less `host` matching any port.
200
- * @returns true when the Host is ours (loopback or trusted) and any attached browser markers are same-origin.
218
+ * @param bindHost - the listener's own bind IP literal, accepted on any port independently of `trustedHosts`.
219
+ * @param protocol - listener protocol for default Host ports; HTTP when no Web carrier is mounted.
220
+ * @returns true when the Host is ours (loopback, the bind address, or trusted) and any attached browser markers are same-origin.
201
221
  */
202
- function isTrustedApiRequest(request, trustedHosts) {
222
+ function isTrustedApiRequest(request, trustedHosts, bindHost, protocol = "http:") {
203
223
  const host = header$1(request.headers, "host");
204
224
  if (host === void 0) return false;
205
- const hostUrl = parseAuthority(host);
225
+ const hostUrl = parseAuthority(host, protocol);
206
226
  if (hostUrl === void 0) return false;
207
- if (!isLoopbackHostname(hostUrl.hostname) && !isTrustedAuthority(hostUrl, trustedHosts)) return false;
227
+ if (!isLoopbackHostname(hostUrl.hostname) && !isTrustedAuthority(hostUrl, trustedHosts, protocol) && !isBindAddressAuthority(hostUrl, bindHost)) return false;
208
228
  if (header$1(request.headers, "sec-fetch-site") === "cross-site") return false;
209
229
  const origin = header$1(request.headers, "origin");
210
230
  if (origin === void 0) return true;
231
+ return isSameAuthorityOrigin(origin, host);
232
+ }
233
+ /**
234
+ * Compare Host under the Origin's HTTP(S) scheme so default ports normalize
235
+ * consistently. The transport may remain HTTP behind an HTTPS proxy.
236
+ * @param origin - verbatim Origin header value.
237
+ * @param host - verbatim Host header value already accepted by the Host fence.
238
+ * @returns true only when the Origin is this listener's own `http(s)` authority.
239
+ */
240
+ function isSameAuthorityOrigin(origin, host) {
241
+ let originUrl;
211
242
  try {
212
- return new URL(origin).host === hostUrl.host;
243
+ originUrl = new URL(origin);
213
244
  } catch {
214
245
  return false;
215
246
  }
247
+ const scheme = originUrl.protocol === "https:" ? "https:" : originUrl.protocol === "http:" ? "http:" : void 0;
248
+ if (scheme === void 0) return false;
249
+ const hostUrl = parseAuthority(host, scheme);
250
+ return hostUrl !== void 0 && originUrl.host === hostUrl.host;
216
251
  }
217
252
  //#endregion
218
253
  //#region lib/types/web-entry.js
@@ -667,7 +702,8 @@ var HostConnectionService = class extends Service {
667
702
  * cookie minted by the launch-token exchange.
668
703
  */
669
704
  requestRejection(request) {
670
- if (!isTrustedApiRequest(request, this.trustedHosts)) return 403;
705
+ const carrier = this.ctx.get("webServer");
706
+ if (!isTrustedApiRequest(request, this.trustedHosts, carrier?.host, carrier?.protocol ?? "http:")) return 403;
671
707
  const authority = this.sessionAuthority;
672
708
  if (authority === void 0) return this.browserAuth.isAuthenticated(request) ? void 0 : 401;
673
709
  if (authority.isPublicApiRequest(request)) return void 0;
@@ -4,13 +4,12 @@
4
4
  * the attacker's domain while the socket reaches this server) and cross-site
5
5
  * requests fired from a malicious page. The Host fence binds every request,
6
6
  * browser-looking or not: over plain HTTP a browser attaches neither Origin
7
- * nor Fetch-Metadata to reads (images and navigations — those
8
- * headers go only to trustworthy destinations), so an unmarked request may
9
- * still be a rebound browser read and Host is the one header rebinding cannot
10
- * forge. Non-browser and remote clients pass the same fence via loopback,
11
- * deployment-derived LAN IP literals, or a declared `trustedHosts` authority.
12
- * Network reachability and authentication stay out of scope: binding policy
13
- * belongs to the webserver config, and this fence is not an auth layer.
7
+ * nor Fetch-Metadata to reads (images and navigations — those headers go only
8
+ * to trustworthy destinations), so an unmarked request may still be a rebound
9
+ * browser read and Host is the one header rebinding cannot forge. Non-browser
10
+ * and remote clients pass the same fence via loopback, the listener's bind IP
11
+ * literal, or a declared `trustedHosts` authority. Binding policy belongs to
12
+ * the webserver config; this fence never establishes identity.
14
13
  */
15
14
  import type { ConnectionTrustRequest } from './rpc.ts';
16
15
  /**
@@ -28,11 +27,19 @@ import type { ConnectionTrustRequest } from './rpc.ts';
28
27
  * @param entry - the configured value, verbatim.
29
28
  */
30
29
  export declare function assertTrustedAuthority(entry: string): void;
30
+ /**
31
+ * Classify an authority already accepted by {@link assertTrustedAuthority}.
32
+ * @param entry - the configured authority, verbatim.
33
+ * @returns true unless the entry parses as a loopback hostname.
34
+ */
35
+ export declare function isRemoteAuthority(entry: string): boolean;
31
36
  /**
32
37
  * Decide whether one /api request may reach the RPC bridge.
33
38
  * @param request - Node HTTP or Fetch request facts (headers).
34
39
  * @param trustedHosts - non-loopback authorities this deployment serves: exact `host:port`, or port-less `host` matching any port.
35
- * @returns true when the Host is ours (loopback or trusted) and any attached browser markers are same-origin.
40
+ * @param bindHost - the listener's own bind IP literal, accepted on any port independently of `trustedHosts`.
41
+ * @param protocol - listener protocol for default Host ports; HTTP when no Web carrier is mounted.
42
+ * @returns true when the Host is ours (loopback, the bind address, or trusted) and any attached browser markers are same-origin.
36
43
  */
37
- export declare function isTrustedApiRequest(request: ConnectionTrustRequest, trustedHosts: readonly string[]): boolean;
44
+ export declare function isTrustedApiRequest(request: ConnectionTrustRequest, trustedHosts: readonly string[], bindHost?: string, protocol?: 'http:' | 'https:'): boolean;
38
45
  //# sourceMappingURL=api-request-trust.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@qilin-agent/client-connection",
3
3
  "description": "Authenticated RPC transport and generation lifecycle",
4
- "version": "3.1.3",
4
+ "version": "3.1.5",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -38,7 +38,7 @@
38
38
  "license": "MIT",
39
39
  "dependencies": {
40
40
  "zod": "^4.4.3",
41
- "@qilin-agent/credentials": "3.1.3",
41
+ "@qilin-agent/credentials": "3.1.5",
42
42
  "@qilin-agent/schemastery": "~3.18.3"
43
43
  },
44
44
  "files": [
@@ -47,22 +47,22 @@
47
47
  "lib/types/**/*.d.ts"
48
48
  ],
49
49
  "peerDependencies": {
50
- "@qilin-agent/scope": "3.1.3",
51
- "@qilin-agent/kylin": "~4.0.3"
50
+ "@qilin-agent/kylin": "~4.0.3",
51
+ "@qilin-agent/scope": "3.1.5"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@qilin-agent/kylin": "~4.0.3",
55
- "@qilin-agent/attachment": "3.1.3",
56
- "@qilin-agent/brand": "3.1.3",
57
- "@qilin-agent/host-webserver": "3.1.3",
58
- "@qilin-agent/llm": "3.1.3",
59
- "@qilin-agent/scope": "3.1.3",
60
- "@qilin-agent/session": "3.1.3",
61
- "@qilin-agent/typert-protocol": "3.1.3",
62
- "@qilin-agent/tool-todo": "3.1.3",
63
- "@qilin-agent/util-values": "3.1.3",
64
- "@qilin-agent/commands": "3.1.3",
65
- "@qilin-agent/settings": "3.1.3",
66
- "@qilin-agent/host-directory-picker": "3.1.3"
55
+ "@qilin-agent/brand": "3.1.5",
56
+ "@qilin-agent/attachment": "3.1.5",
57
+ "@qilin-agent/host-webserver": "3.1.5",
58
+ "@qilin-agent/llm": "3.1.5",
59
+ "@qilin-agent/session": "3.1.5",
60
+ "@qilin-agent/typert-protocol": "3.1.5",
61
+ "@qilin-agent/scope": "3.1.5",
62
+ "@qilin-agent/tool-todo": "3.1.5",
63
+ "@qilin-agent/commands": "3.1.5",
64
+ "@qilin-agent/util-values": "3.1.5",
65
+ "@qilin-agent/settings": "3.1.5",
66
+ "@qilin-agent/host-directory-picker": "3.1.5"
67
67
  }
68
68
  }