@kangentic/protocol 0.11.0 → 0.11.1
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/dist/index.cjs +26 -6
- package/dist/index.mjs +26 -6
- package/dist/pairing/relay-address.d.ts +12 -6
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5833,14 +5833,34 @@ function parseCapabilityRequestPayload(verb, payload) {
|
|
|
5833
5833
|
|
|
5834
5834
|
// src/pairing/relay-address.ts
|
|
5835
5835
|
var MAX_RELAY_ADDRESS_LENGTH = 512;
|
|
5836
|
-
var
|
|
5836
|
+
var LOOPBACK_WS_HOSTS = ["localhost", "127.0.0.1", "[::1]"];
|
|
5837
|
+
var PLAINTEXT_SCHEME = "ws://";
|
|
5838
|
+
function hostWithoutPort(authority) {
|
|
5839
|
+
if (authority.startsWith("[")) {
|
|
5840
|
+
const closingBracket = authority.indexOf("]");
|
|
5841
|
+
if (closingBracket === -1) return null;
|
|
5842
|
+
const afterLiteral = authority.slice(closingBracket + 1);
|
|
5843
|
+
if (afterLiteral !== "" && !/^:[0-9]+$/.test(afterLiteral)) return null;
|
|
5844
|
+
return authority.slice(0, closingBracket + 1);
|
|
5845
|
+
}
|
|
5846
|
+
const portSeparator = authority.lastIndexOf(":");
|
|
5847
|
+
if (portSeparator === -1) return authority;
|
|
5848
|
+
const port = authority.slice(portSeparator + 1);
|
|
5849
|
+
return /^[0-9]+$/.test(port) ? authority.slice(0, portSeparator) : authority;
|
|
5850
|
+
}
|
|
5851
|
+
function plaintextRelayHost(relayAddress) {
|
|
5852
|
+
if (!relayAddress.startsWith(PLAINTEXT_SCHEME)) return null;
|
|
5853
|
+
const afterScheme = relayAddress.slice(PLAINTEXT_SCHEME.length);
|
|
5854
|
+
const authorityEnd = afterScheme.search(/[/?#]/);
|
|
5855
|
+
const authority = authorityEnd === -1 ? afterScheme : afterScheme.slice(0, authorityEnd);
|
|
5856
|
+
if (authority.includes("@")) return null;
|
|
5857
|
+
const host = hostWithoutPort(authority);
|
|
5858
|
+
return host === null ? null : host.toLowerCase();
|
|
5859
|
+
}
|
|
5837
5860
|
function isSecureRelayAddress(relayAddress) {
|
|
5838
5861
|
if (relayAddress.startsWith("wss://")) return true;
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
const boundaryChar = relayAddress.charAt(prefix.length);
|
|
5842
|
-
return boundaryChar === "" || boundaryChar === ":" || boundaryChar === "/" || boundaryChar === "?" || boundaryChar === "#";
|
|
5843
|
-
});
|
|
5862
|
+
const host = plaintextRelayHost(relayAddress);
|
|
5863
|
+
return host !== null && LOOPBACK_WS_HOSTS.includes(host);
|
|
5844
5864
|
}
|
|
5845
5865
|
|
|
5846
5866
|
// src/pairing/qr.ts
|
package/dist/index.mjs
CHANGED
|
@@ -5716,14 +5716,34 @@ function parseCapabilityRequestPayload(verb, payload) {
|
|
|
5716
5716
|
|
|
5717
5717
|
// src/pairing/relay-address.ts
|
|
5718
5718
|
var MAX_RELAY_ADDRESS_LENGTH = 512;
|
|
5719
|
-
var
|
|
5719
|
+
var LOOPBACK_WS_HOSTS = ["localhost", "127.0.0.1", "[::1]"];
|
|
5720
|
+
var PLAINTEXT_SCHEME = "ws://";
|
|
5721
|
+
function hostWithoutPort(authority) {
|
|
5722
|
+
if (authority.startsWith("[")) {
|
|
5723
|
+
const closingBracket = authority.indexOf("]");
|
|
5724
|
+
if (closingBracket === -1) return null;
|
|
5725
|
+
const afterLiteral = authority.slice(closingBracket + 1);
|
|
5726
|
+
if (afterLiteral !== "" && !/^:[0-9]+$/.test(afterLiteral)) return null;
|
|
5727
|
+
return authority.slice(0, closingBracket + 1);
|
|
5728
|
+
}
|
|
5729
|
+
const portSeparator = authority.lastIndexOf(":");
|
|
5730
|
+
if (portSeparator === -1) return authority;
|
|
5731
|
+
const port = authority.slice(portSeparator + 1);
|
|
5732
|
+
return /^[0-9]+$/.test(port) ? authority.slice(0, portSeparator) : authority;
|
|
5733
|
+
}
|
|
5734
|
+
function plaintextRelayHost(relayAddress) {
|
|
5735
|
+
if (!relayAddress.startsWith(PLAINTEXT_SCHEME)) return null;
|
|
5736
|
+
const afterScheme = relayAddress.slice(PLAINTEXT_SCHEME.length);
|
|
5737
|
+
const authorityEnd = afterScheme.search(/[/?#]/);
|
|
5738
|
+
const authority = authorityEnd === -1 ? afterScheme : afterScheme.slice(0, authorityEnd);
|
|
5739
|
+
if (authority.includes("@")) return null;
|
|
5740
|
+
const host = hostWithoutPort(authority);
|
|
5741
|
+
return host === null ? null : host.toLowerCase();
|
|
5742
|
+
}
|
|
5720
5743
|
function isSecureRelayAddress(relayAddress) {
|
|
5721
5744
|
if (relayAddress.startsWith("wss://")) return true;
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
const boundaryChar = relayAddress.charAt(prefix.length);
|
|
5725
|
-
return boundaryChar === "" || boundaryChar === ":" || boundaryChar === "/" || boundaryChar === "?" || boundaryChar === "#";
|
|
5726
|
-
});
|
|
5745
|
+
const host = plaintextRelayHost(relayAddress);
|
|
5746
|
+
return host !== null && LOOPBACK_WS_HOSTS.includes(host);
|
|
5727
5747
|
}
|
|
5728
5748
|
|
|
5729
5749
|
// src/pairing/qr.ts
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Relay address rules
|
|
3
|
-
*
|
|
2
|
+
* Relay address rules, mirrored by the mobile app's src/pairing/qr.ts (which
|
|
3
|
+
* hardened these rules first; this module now matches it). This module
|
|
4
|
+
* imports nothing on purpose: it is
|
|
4
5
|
* deep-imported by the desktop's src/shared/relay.ts, which the RENDERER
|
|
5
6
|
* bundles, and pulling in the rest of this package (crypto/primitives ->
|
|
6
7
|
* @noble/*) would drag Noise crypto into that bundle for no reason. Keep it
|
|
@@ -10,9 +11,14 @@ export declare const MAX_RELAY_ADDRESS_LENGTH = 512;
|
|
|
10
11
|
/**
|
|
11
12
|
* True if the phone's pairing QR scanner will accept this relay address.
|
|
12
13
|
* wss:// is always secure; ws:// is accepted only for loopback, since the
|
|
13
|
-
* pairing token doubles as the Noise PSK and is dialed verbatim as
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* pairing token doubles as the Noise PSK and is dialed verbatim as `?slot=`.
|
|
15
|
+
*
|
|
16
|
+
* Parses the AUTHORITY rather than prefix-matching. Prefix matching accepted
|
|
17
|
+
* `ws://127.0.0.1:8080@evil.test`: everything before an '@' is userinfo, so
|
|
18
|
+
* that address dials evil.test while looking like loopback, putting the PSK
|
|
19
|
+
* on the wire in cleartext to an attacker-chosen host - and the pairing then
|
|
20
|
+
* persists that host to the trust anchor for every later session. Authority
|
|
21
|
+
* parsing also subsumes the old boundary check, since `localhost.evil.com`
|
|
22
|
+
* is simply a different host.
|
|
17
23
|
*/
|
|
18
24
|
export declare function isSecureRelayAddress(relayAddress: string): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kangentic/protocol",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Kangentic mobile bridge wire protocol: framing, Noise-based pairing and session handshakes, signed device roster, and capability verbs shared between desktop and mobile.",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|