Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ concurrency:
jobs:
quality_linux:
name: quality_linux
if: false
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
Expand Down Expand Up @@ -175,9 +176,6 @@ jobs:
fail-fast: false
matrix:
include:
- platform: linux
runner: ubuntu-24.04
turbo_concurrency: "50%"
- platform: windows
runner: windows-2022
turbo_concurrency: "1"
Expand Down Expand Up @@ -494,6 +492,7 @@ jobs:

e2e_linux:
name: e2e_linux
if: false
needs: quality_linux
runs-on: ubuntu-24.04
timeout-minutes: 30
Expand Down Expand Up @@ -672,11 +671,11 @@ jobs:
steps:
- name: Require every quality dependency to succeed
run: |
test "${{ needs.quality_linux.result }}" = success
test "${{ needs.quality_linux.result }}" = skipped
test "${{ needs.quality_windows.result }}" = success
test "${{ needs.unit.result }}" = success
test "${{ needs.browser_windows.result }}" = success
test "${{ needs.e2e_linux.result }}" = success
test "${{ needs.e2e_linux.result }}" = skipped
test "${{ needs.e2e_windows.result }}" = success
test "${{ needs.macos_arm64.result }}" = skipped

Expand Down
18 changes: 12 additions & 6 deletions apps/server/src/nodeHttpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ServeError } from "effect/unstable/http/HttpServerError";
import { WebSocket, WebSocketServer } from "ws";

import type { ServerConfigShape } from "./config";
import { isLoopbackHost } from "./startupAccess";
import {
classifyWsMessage,
makeWsMessageAdmission,
Expand All @@ -18,18 +19,23 @@ import {
export const MAX_WEBSOCKET_MESSAGE_BYTES = 2 * 1024 * 1024;

/**
* A configured public URL declares HTTPS reverse-proxy mode. The socket peer
* is then the shared proxy, not a client identity, so pre-auth transport keeps
* only the global cap and post-auth session admission owns client isolation.
* No forwarded request header is consulted. Direct modes retain peer limits.
* Private desktop renderer traffic shares one loopback peer identity while
* bootstrap and feature sockets reconnect together, so its pre-auth peer
* bucket is disabled while the global connection cap, origin/auth checks, and
* per-connection message limits remain in force. Public URL proxy mode
* preserves its existing shared-peer handling. Every direct web or non-loopback
* deployment retains peer throttling, and no forwarded request header is
* consulted.
*/
export function wsTransportAdmissionOptionsForServerConfig(
config: Pick<ServerConfigShape, "publicUrl">,
config: Pick<ServerConfigShape, "publicUrl"> & Partial<Pick<ServerConfigShape, "mode" | "host">>,
overrides: WsTransportAdmissionOptions = {},
): WsTransportAdmissionOptions {
const isPrivateDesktopLoopback =
config.mode === "desktop" && config.publicUrl === undefined && isLoopbackHost(config.host);
Comment thread
slashdevcorpse marked this conversation as resolved.
return {
...overrides,
connectionPeerRateLimitEnabled: config.publicUrl === undefined,
connectionPeerRateLimitEnabled: config.publicUrl === undefined && !isPrivateDesktopLoopback,
};
}

Expand Down
128 changes: 108 additions & 20 deletions apps/server/src/wsTransportAdmission.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,37 +74,125 @@ describe("WebSocket transport admission", () => {
expect(admission.acquireConnection("127.0.0.1").admitted).toBe(true);
});

it("keeps publicUrl proxy traffic out of shared peer buckets while preserving the global cap", () => {
const options = wsTransportAdmissionOptionsForServerConfig(
{ publicUrl: new URL("https://synara.example.test/") },
{
maxConcurrentConnections: 11,
connectionBurstPerPeer: 2,
connectionRatePerMinutePerPeer: 2,
},
it.each([
["implicit loopback", undefined],
["localhost", "localhost"],
["IPv4 loopback", "127.0.0.1"],
["IPv6 loopback", "::1"],
["bracketed IPv6 loopback", "[::1]"],
])("disables the peer bucket for private desktop %s", (_label, host) => {
expect(
wsTransportAdmissionOptionsForServerConfig({
mode: "desktop",
host,
publicUrl: undefined,
}).connectionPeerRateLimitEnabled,
).toBe(false);
});

it.each([
[
"desktop IPv4 wildcard bind",
{ mode: "desktop" as const, host: "0.0.0.0", publicUrl: undefined },
],
["desktop IPv6 wildcard bind", { mode: "desktop" as const, host: "::", publicUrl: undefined }],
[
"desktop bracketed IPv6 wildcard bind",
{ mode: "desktop" as const, host: "[::]", publicUrl: undefined },
],
["desktop remote bind", { mode: "desktop" as const, host: "192.0.2.10", publicUrl: undefined }],
["direct web loopback", { mode: "web" as const, host: "127.0.0.1", publicUrl: undefined }],
])("retains pre-auth peer throttling for %s", (_label, config) => {
expect(wsTransportAdmissionOptionsForServerConfig(config).connectionPeerRateLimitEnabled).toBe(
true,
);
expect(options.connectionPeerRateLimitEnabled).toBe(false);
const admission = makeWsTransportAdmission(options);
});

// Two five-socket page loads plus one reconnect all arrive from the same
// proxy socket address. Direct mode would reject the third connection.
for (let index = 0; index < 11; index += 1) {
expect(admission.acquireConnection("127.0.0.1").admitted).toBe(true);
it("allows repeated private desktop bootstrap and feature cycles while retaining the global cap", () => {
const admission = makeWsTransportAdmission(
wsTransportAdmissionOptionsForServerConfig(
{ mode: "desktop", host: "127.0.0.1", publicUrl: undefined },
{
maxConcurrentConnections: 2,
connectionBurstPerPeer: 1,
connectionRatePerMinutePerPeer: 1,
},
),
);

for (let index = 0; index < 20; index += 1) {
const bootstrap = admission.acquireConnection("127.0.0.1");
expect(bootstrap.admitted).toBe(true);
if (!bootstrap.admitted) throw new Error("Expected desktop bootstrap to be admitted");
admission.releaseConnection(bootstrap.lease);

const feature = admission.acquireConnection("127.0.0.1");
expect(feature.admitted).toBe(true);
if (!feature.admitted) throw new Error("Expected desktop feature socket to be admitted");
admission.releaseConnection(feature.lease);
}
expect(admission.snapshot()).toEqual({ activeConnections: 11, trackedPeers: 0 });
expect(admission.snapshot()).toEqual({ activeConnections: 0, trackedPeers: 0 });

const first = admission.acquireConnection("127.0.0.1");
const second = admission.acquireConnection("127.0.0.1");
expect(first.admitted).toBe(true);
expect(second.admitted).toBe(true);
expect(admission.acquireConnection("127.0.0.1")).toMatchObject({
admitted: false,
reason: "global-capacity",
});
});

it("keeps peer limiting enabled when no publicUrl proxy is configured", () => {
expect(
wsTransportAdmissionOptionsForServerConfig({ publicUrl: undefined })
.connectionPeerRateLimitEnabled,
).toBe(true);
it.each([
["desktop non-loopback", { mode: "desktop" as const, host: "0.0.0.0", publicUrl: undefined }],
["direct web", { mode: "web" as const, host: "127.0.0.1", publicUrl: undefined }],
])("rate-limits repeated connections in %s mode", (_label, config) => {
const admission = makeWsTransportAdmission(
wsTransportAdmissionOptionsForServerConfig(config, {
now: () => 0,
maxConcurrentConnections: 20,
connectionBurstPerPeer: 2,
connectionRatePerMinutePerPeer: 2,
}),
);
const first = admission.acquireConnection("203.0.113.8");
const second = admission.acquireConnection("203.0.113.8");
expect(first.admitted).toBe(true);
expect(second.admitted).toBe(true);
expect(admission.acquireConnection("203.0.113.8")).toMatchObject({
admitted: false,
reason: "peer-rate",
retryAfterMs: 30_000,
Comment thread
slashdevcorpse marked this conversation as resolved.
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});

it.each(["desktop", "web"] as const)(
"preserves publicUrl proxy peer handling and the global cap in %s mode",
(mode) => {
const admission = makeWsTransportAdmission(
wsTransportAdmissionOptionsForServerConfig(
{
mode,
host: "127.0.0.1",
publicUrl: new URL("https://synara.example.test/"),
},
{
maxConcurrentConnections: 2,
connectionBurstPerPeer: 1,
connectionRatePerMinutePerPeer: 1,
},
),
);
expect(admission.acquireConnection("127.0.0.1").admitted).toBe(true);
expect(admission.acquireConnection("127.0.0.1").admitted).toBe(true);
expect(admission.snapshot()).toEqual({ activeConnections: 2, trackedPeers: 0 });
expect(admission.acquireConnection("127.0.0.1")).toMatchObject({
admitted: false,
reason: "global-capacity",
});
},
);

it("bounds inbound messages with a sustained token bucket", () => {
let nowMs = 0;
const admission = makeWsMessageAdmission({
Expand Down
46 changes: 38 additions & 8 deletions scripts/lib/workflow-contracts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ permissions:
contents: read
jobs:
quality_linux:
if: false
runs-on: ubuntu-24.04
steps:
- uses: ${pinnedCheckout}
Expand Down Expand Up @@ -249,9 +250,6 @@ jobs:
fail-fast: false
matrix:
include:
- platform: linux
runner: ubuntu-24.04
turbo_concurrency: "50%"
- platform: windows
runner: windows-2022
turbo_concurrency: "1"
Expand Down Expand Up @@ -317,6 +315,7 @@ ${windowsStartupSmokeStep}
retention-days: 1
e2e_linux:
name: e2e_linux
if: false
needs: quality_linux
runs-on: ubuntu-24.04
timeout-minutes: 30
Expand Down Expand Up @@ -379,11 +378,11 @@ ${macosStartupSmokeStep}
timeout-minutes: 5
steps:
- run: |
test "\${{ needs.quality_linux.result }}" = success
test "\${{ needs.quality_linux.result }}" = skipped
test "\${{ needs.quality_windows.result }}" = success
test "\${{ needs.unit.result }}" = success
test "\${{ needs.browser_windows.result }}" = success
test "\${{ needs.e2e_linux.result }}" = success
test "\${{ needs.e2e_linux.result }}" = skipped
test "\${{ needs.e2e_windows.result }}" = success
test "\${{ needs.macos_arm64.result }}" = skipped
release_smoke:
Expand Down Expand Up @@ -534,6 +533,34 @@ describe("workflow contracts", () => {
expect(validateMergifyConfiguration(mergifyConfiguration)).toEqual([]);
});

it("keeps only the three backlogged Linux CI lanes from executing", () => {
expect(
ciErrors(
ciWorkflow.replace(" quality_linux:\n if: false", " quality_linux:\n if: true"),
),
).toContain(
"quality_linux backlog policy requires if: false and continue-on-error to be unset or false",
);

expect(
ciErrors(
ciWorkflow.replace(
" e2e_linux:\n name: e2e_linux\n if: false",
" e2e_linux:\n name: e2e_linux",
),
),
).toContain("e2e_linux must remain disabled while Linux CI is backlogged");

expect(
ciErrors(
ciWorkflow.replace(
" include:\n - platform: windows",
' include:\n - platform: linux\n runner: ubuntu-24.04\n turbo_concurrency: "50%"\n - platform: windows',
),
),
).toContain("unit matrix must contain the exact required platforms");
});

it("keeps stable browser tests blocking and only registry-backed quarantine runs nonblocking", () => {
const stableNonblocking = ciWorkflow.replace(
" - name: Browser test (stable)\n run: bun run --cwd apps/web test:browser:stable",
Expand Down Expand Up @@ -724,7 +751,10 @@ describe("workflow contracts", () => {
const enabled = validFiles();
enabled.set(
".github/workflows/ci.yml",
ciWorkflow.replace(" if: false", " if: ${{ github.event_name == 'push' }}"),
ciWorkflow.replace(
" macos_arm64:\n if: false",
" macos_arm64:\n if: ${{ github.event_name == 'push' }}",
),
);
expect(validateWorkflowContracts(enabled, policy()).join("\n")).toContain(
"macos_arm64 must remain disabled while macOS CI is backlogged",
Expand Down Expand Up @@ -858,7 +888,7 @@ describe("workflow contracts", () => {
);
});

it("locks the complete bounded unit matrix and required quality aggregate", () => {
it("locks the bounded Windows unit matrix and required quality aggregate", () => {
const failFast = validFiles();
failFast.set(
".github/workflows/ci.yml",
Expand All @@ -877,7 +907,7 @@ describe("workflow contracts", () => {
),
);
expect(validateWorkflowContracts(concurrentWindows, policy()).join("\n")).toContain(
"unit matrix entry 2 has drifted",
"unit matrix entry 1 has drifted",
);

const detachedConcurrency = validFiles();
Expand Down
Loading