feat: W1 batch — config, protocol, ring-buffer, origin, mock-pty (TDD)
Parallel module-builder subagents, disjoint file ownership, shared tree: - T4 src/config.ts — loadConfig+M1 origins (32 tests) - T5 src/protocol.ts — parse/serialize+SESSION_ID_RE+M7 (60 tests) - T6 src/session/ring-buffer.ts — byte-accurate, no UTF-8/ANSI split+M2 (15 tests) - T7 src/http/origin.ts — host+port match, undefined rejected (12 tests) - T3 test/helpers/mock-pty.ts — IPty double w/ emitData/emitExit (9 tests) Verified by orchestrator: tsc --noEmit clean; full suite 128 tests pass.
This commit is contained in:
53
src/http/origin.ts
Normal file
53
src/http/origin.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* src/http/origin.ts — Origin whitelist check (T7)
|
||||
*
|
||||
* Pure function, no side effects. Security-critical:
|
||||
* this is the CSWSH (Cross-Site WebSocket Hijacking) defence described in
|
||||
* TECH_DOC §7 and ARCHITECTURE §3.3.
|
||||
*
|
||||
* Strategy:
|
||||
* - Parse the incoming Origin as a URL.
|
||||
* - Compare scheme + hostname + port against every entry in the allowed list.
|
||||
* - Both host AND port must match (ARCHITECTURE §3.3: "host+port 双匹配").
|
||||
* - undefined Origin (non-browser clients: curl, scripts) → reject by default.
|
||||
* This is the single central policy point — no scattered `if` checks elsewhere.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns true iff `origin` exactly matches one of the `allowed` origins.
|
||||
*
|
||||
* Matching is scheme + hostname + port (all three must agree).
|
||||
* `undefined` → false (non-browser client; default-deny, no scattered if-chains).
|
||||
*/
|
||||
export function isOriginAllowed(
|
||||
origin: string | undefined,
|
||||
allowed: readonly string[],
|
||||
): boolean {
|
||||
// Central policy: undefined Origin is rejected. No other code path handles this.
|
||||
if (origin === undefined || origin === '') {
|
||||
return false
|
||||
}
|
||||
|
||||
let incomingUrl: URL
|
||||
try {
|
||||
incomingUrl = new URL(origin)
|
||||
} catch {
|
||||
// Malformed Origin value → reject.
|
||||
return false
|
||||
}
|
||||
|
||||
return allowed.some((entry) => {
|
||||
let allowedUrl: URL
|
||||
try {
|
||||
allowedUrl = new URL(entry)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
|
||||
return (
|
||||
incomingUrl.protocol === allowedUrl.protocol &&
|
||||
incomingUrl.hostname === allowedUrl.hostname &&
|
||||
incomingUrl.port === allowedUrl.port
|
||||
)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user