mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-07 17:28:23 +01:00
* Require the daemon secret and loopback Host/Origin on the design and iOS QA daemons (GST-1747) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * Name the changelog fragment after the branch (GST-1747) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes Review-Finding: p1/red-team#2 reader=red-team Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: refuse cross-site browser requests on the iOS QA loopback, show the expired page for a stale board secret, refuse requests without a Host (GST-1747) Review-Finding: v1/outside-voice#1 reader=outside-voice Review-Finding: v1/adversarial-native#2 reader=adversarial-native Review-Finding: v1/adversarial-native#5 reader=adversarial-native Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: show the secret segment in the documented design board URL (GST-1747) Review-Finding: v1/adversarial-native#1 reader=adversarial-native Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: keep the daemon token fixtures on one line so the credential scan reads them as code (GST-1747) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: cover the Sec-Fetch-Site refusal, the expired board page and the no-Host 403 in the changelog fragment (GST-1747) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
28 lines
1.2 KiB
TypeScript
28 lines
1.2 KiB
TypeScript
/**
|
|
* Request checks for daemons bound to a loopback port. Binding 127.0.0.1
|
|
* keeps other machines out but not other origins on this machine: a browser
|
|
* page can send requests to the port, and a page served under a hostname
|
|
* that resolves to 127.0.0.1 arrives with that hostname as Host. Serving
|
|
* only loopback Host values and same-origin (or absent) Origin values keeps
|
|
* the port to local clients that address it directly.
|
|
*/
|
|
|
|
const LOOPBACK_HOSTNAMES = ['127.0.0.1', 'localhost', '[::1]'];
|
|
|
|
function isLoopbackAuthority(authority: string, port: number): boolean {
|
|
const normalized = authority.toLowerCase();
|
|
return LOOPBACK_HOSTNAMES.some((hostname) => normalized === `${hostname}:${port}`);
|
|
}
|
|
|
|
/** Why a request to a loopback daemon on `port` must be refused, or null to serve it. */
|
|
export function loopbackRequestRefusal(
|
|
host: string | null | undefined,
|
|
origin: string | null | undefined,
|
|
port: number,
|
|
): 'host_not_loopback' | 'cross_origin' | null {
|
|
if (!host || !isLoopbackAuthority(host, port)) return 'host_not_loopback';
|
|
if (origin === null || origin === undefined) return null;
|
|
if (!origin.startsWith('http://') || !isLoopbackAuthority(origin.slice('http://'.length), port)) return 'cross_origin';
|
|
return null;
|
|
}
|