Merge remote-tracking branch 'origin/doc/host-client-group-readmes' into feat/directory-picker

This commit is contained in:
creatixchu
2026-07-28 17:10:00 +08:00
11 changed files with 76 additions and 61 deletions

View File

@@ -8,13 +8,15 @@ function request(headers: Record<string, string | undefined>): { headers: Record
}
describe('isTrustedApiRequest', () => {
it('accepts every request without browser markers — curl, tests, native clients, on any Host', () => {
// No Origin and no sec-fetch-site → the sender is the principal itself
// (it forges Host freely anyway); this is the LAN-serving shape a Host
// fence must not break.
for (const host of ['127.0.0.1:3080', '192.168.1.5:3080', 'harness.example', undefined]) {
expect(isTrustedApiRequest(request(host === undefined ? {} : { host }), [])).toBe(true)
}
it('holds markerless requests to the same Host fence — a plain-HTTP browser read carries no markers', () => {
// Over plain HTTP a browser attaches neither Origin nor Fetch-Metadata to
// reads (EventSource, images, navigations), so a rebound-origin GET is
// markerless and its response readable: no marker shortcut may exist.
expect(isTrustedApiRequest(request({ host: '127.0.0.1:3080' }), [])).toBe(true)
expect(isTrustedApiRequest(request({ host: '192.168.1.5:3080' }), ['192.168.1.5'])).toBe(true)
expect(isTrustedApiRequest(request({ host: '192.168.1.5:3080' }), [])).toBe(false)
expect(isTrustedApiRequest(request({ host: 'harness.example' }), [])).toBe(false)
expect(isTrustedApiRequest(request({}), [])).toBe(false)
})
it('accepts loopback Hosts in every spelling, with and without ports, for browser requests', () => {
@@ -79,6 +81,12 @@ describe('isTrustedApiRequest', () => {
for (const entry of ['harness.internal:3080 ', ' harness.internal', 'harness.internal:30\t80']) {
expect(() => { assertTrustedAuthority(entry) }).toThrow(/not a bare host\[:port\] authority/)
}
// WHATWG parsing would silently rewrite these — a dangling colon or
// zero-padded port would broaden an intended exact-port grant to every
// port, and non-canonical host spellings would not read back as written.
for (const entry of ['harness.internal:', '[::1]:', 'harness.internal:0080', '0x7f.0.0.1', '[0:0:0:0:0:0:0:1]']) {
expect(() => { assertTrustedAuthority(entry) }).toThrow(/not a bare host\[:port\] authority/)
}
})
it('never lets stray whitespace broaden an exact-port entry to every port', () => {

View File

@@ -98,14 +98,14 @@ describe('connection node half', () => {
})
it('passes loopback and declared-authority requests through to the bridge', async () => {
const { routes, dispose } = await mounted({ trustedHosts: ['harness.example:3080'] })
const { routes, dispose } = await mounted({ trustedHosts: ['harness.example:3080', '192.168.1.5'] })
// Loopback, no browser markers (curl shape): the fence passes; the carrier
// answers 404 for a GET unary path — proof the bridge ran.
const loopback = fakeResponse()
await routes[0]!.handler(fakeRequest({ host: '127.0.0.1:3080' }), loopback.response)
expect(loopback.state.status).toBe(404)
// Undeclared LAN authority, no browser markers: the `--host 0.0.0.0` curl
// shape must reach the bridge even with an empty-by-default trust list.
// LAN authority declared as a port-less IP literal — the shape the CLI
// derives for `--host 0.0.0.0` — passes markerless curl on any port.
const lan = fakeResponse()
await routes[0]!.handler(fakeRequest({ host: '192.168.1.5:3080' }), lan.response)
expect(lan.state.status).toBe(404)