From 13846b66afb2c609b0b7f4476e6e843c5f9f8423 Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 14:29:03 -0700 Subject: [PATCH 01/35] docs(ssh): record the port-forwards design, amending the relay spec's non-goal --- .../2026-10-15-ssh-port-forwards-design.md | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md diff --git a/docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md b/docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md new file mode 100644 index 000000000..34aba8a09 --- /dev/null +++ b/docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md @@ -0,0 +1,178 @@ +# SSH Port Forwards + +Status: design, for review. Dated 2026-10-15. +Branch: `feat/ssh-port-forwards`, cut from `origin/main`. This deliberately amends a +written non-goal: the relay spec (`2026-10-08-ssh-agent-relay-design.md` §1) listed +port forwarding out of scope, and the launcher refused it at three layers. The prior +specs stay as the record of why; this one records what changed and why it is sound. + +## 1. Problem and goal + +An SSH pane gives the user a shell on the destination D, dialed from the connecting +machine B. Plenty of services sit behind D: a database on D's private network, an +admin API, a staging box only D can see. Today reaching them means hand-running +tunnel commands inside the pane, and nothing in Subshell knows they exist. + +This feature makes port forwarding a first-class part of an SSH connection. At +launch the user may declare forward rows, and the connection carries them for its +lifetime. All three OpenSSH kinds ship: local (`-L`), dynamic/SOCKS (`-D`), and +remote (`-R`). SSH presets can store rows, so "this host always forwards the DB" +is a preset, not a ritual. Forwards bind to loopback by default; letting them bind +wider is a per-machine owner decision. + +## 2. What this changes, stated precisely + +The refusal this feature inverts is real and was reasoned, so the inversion has to +name what exactly stays and what goes. + +What goes: a config file the connecting account holds can no longer be the reason +nothing forwards. What stays: **a config file can never be the reason something +does.** The resolver keeps refusing `LocalForward`/`RemoteForward`/`DynamicForward` +found by `ssh -G`, by name, unchanged and pinned by test; the frozen snapshot keeps +`forwards` and `tunnels` as literal-null forbidden members; `Tunnel` (the tun +device) stays `no`, and `ForwardAgent`/`ForwardX11` toward D stay `no`. The M1 +argument, "missing a forward would be the silent-widening defect," was always about +*ambient* directives the user never consciously approved. What ships here is the +opposite kind of thing: explicit user-declared rows, visible in the launch form, +saved in presets, audited at launch. + +The widening is bounded by what the connection already grants. An approved `-L` or +`-D` gives B less than the shell on D that this very connection hands the user (from +inside the pane you can `socat` the same path by hand). An approved `-R` asks D to +bind a port, and D's sshd decides under `GatewayPorts`/`PermitRemoteOpen` exactly as +it would for any client; we relay the request and own the asking, nothing more. The +accounting goes into `docs/security.md` §SSH surfaces as a paragraph, not a patch-on- +a-refusal: config-derived forwards are still refused by name, and the new rows enter +through a different door. + +## 3. Terminology + +B, D, J keep the ssh-anywhere spec's meanings (connecting machine, destination, jump +hop). A **forward row** is one declared tunnel: a `kind`, a **listen** end (`bindHost` ++ `listenPort`, bound by B for local/dynamic, by D for remote), and for local/remote +a **target** (`targetHost` + `targetPort`, reached through D for local, through B for +remote). Dynamic rows have no target: the SSH channel decides destinations at runtime, +which is what makes a SOCKS proxy useful and is exactly the reach a shell on D already +has. + +## 4. Row grammar + +New protocol module `packages/subshell-protocol/src/ssh-forwards.ts`, pure and +Metro-safe like its neighbors: + +- `SshForwardWire`: `kind` (`local` | `remote` | `dynamic`), `listenPort` (integer + 1..65535, always explicit; no ephemeral/auto ports), optional `bindHost` + (canonical host/IPv4/IPv6, bracketed IPv6 kept, option-like leading `-` refused + the way the snapshot grammar refuses it), and `targetHost`/`targetPort` required + for local/remote and forbidden for dynamic. +- `parseSshForwardRows()`: the closed-shape rebuild discipline of + `parseSshConnectionSnapshot`. Any bad row refuses the whole set (there is no + "drop the bad one" in an approval list). Cap at 16 rows. Duplicate listen ends + refused: same `bindHost` (canonically compared) and `listenPort` twice is a bug, + not a configuration. + +Rows ride the pane's ssh launch payload **beside** the snapshot, never inside it. +The snapshot is the resolver's output and its grammar says config-derived forwards +do not survive it; approved rows are user input from a different door, and keeping +the channels distinct is what lets the old refusal stay honest. + +## 5. Rendering + +`renderSshConfigContents` grows an options object, `{ relay?, forwards? }`. With +approved rows it emits a second config block, `Host `, +**after** the `Host *` policy block, carrying `LocalForward` / `RemoteForward` / +`DynamicForward` lines composed only from re-parsed rows. + +Two facts about OpenSSH make the placement the whole design. First, +`ClearAllForwardings` clears forwardings "specified in configuration files **or on +the command line**" at the point it is applied, so approved rows cannot ride argv +where the policy block would wipe them; they must be added after the clear, inside +the same file, which is also why the rows stay out of `sshOptionTokens` (those are +the destination-scoped facts the policy must not fight). Second, ProxyJump children +re-read the `-F` file matching their own host, so they hit `Host *` (policy, +cleared) and never match the exact final-host pattern: a forward cannot leak onto a +hop's connection. + +Byte stability is kept (same approval renders the same file), so the run's recorded +config byte-check and spec-22's sweep keep working. `ExitOnForwardFailure` stays at +ssh's default: a busy port prints ssh's own warning into the pane and the session +carries on. The forward is auxiliary to the terminal, not its precondition, and the +form says so. + +## 6. Trust and gates + +- **Loopback is the default and the floor.** An omitted `bindHost` binds ssh's own + loopback, same as typing `127.0.0.1`. Loopback binds on B are reachable by B's + other OS users; that is §0's accepted posture verbatim, the same one the node's + loopback dashboard already carries, and `security.md` says so in one sentence. +- **Wide binds are a machine decision.** A row naming a non-loopback bind host is + refused at launch unless the connecting node's new `sshWideForwards` setting is on: + owner-only (admin on `local`), cookie-only, default off, audited + `node.ssh_wide_forwards.update`, presented on the node's SSH card beside the + allow-SSH switch. Enforcement is plane-side only, deliberately: the node already + gates the whole SSH subsystem, the render trusts the approved rows the launch + carries, and a plane lying about the flag is the control-plane-compromise case §0 + accepts. A wide bind turns B into a router for whatever can reach that port; the + setting is the owner saying their network is trusted enough for that, per machine. +- **-R binds are the destination's policy**, not ours; the copy names it. +- **Auth modes are untouched.** A forward is an option on the one already-approved + connection. Password mode and the one-shot local-key path gain nothing new: rows + never multiply connections, and the local-key signer still signs exactly once. +- **Older agents are refused, never downgraded.** A launch carrying rows toward an + agent predating the grammar is refused by name with an "update this machine" + remedy. Silently stripping a forward is precisely the silent-widening failure the + resolver's refusal was written against; a dropped forward is a lie about what the + user approved. + +## 7. Surfaces + +- **Launch form**: a "Port forwards" group inside the existing `ssh-advanced-options` + disclosure. Per row: kind, listen port, optional bind host (only offered when the + selected machine reports wide binds on), target host and port (hidden for dynamic), + plus one detail-role line explaining what the kind does and where the port lives. + The forward dies with the pane, and the copy says so. +- **Presets**: rows persist beside destination/auth/key choices (JSON column on + `ssh_presets`), validated at save through the same parser, applied to the form like + every other preset choice. A preset with rows is still unaudited CRUD, per the + presets precedent; the launch itself audits. +- **Node overview**: the SSH card grows the wide-forwards toggle, owner-only. +- The pane itself gains no new live surface in this round; see §10. + +## 8. Audit + +`ssh.launch` metadata grows a bounded summary of the approved rows (kind, canonical +listen end, canonical target for local/remote). These are the same class of fact the +row already records (it names the destination), and they answer "which machine is +forwarding what" for a security review without a new event family. The wide-forwards +flip writes its own row in the nodes family. + +## 9. Tests + +- Protocol: accept/refuse census on `parseSshForwardRows` (both IPv6 spellings, port + bounds, dynamic target rules, kind closedness, cap, dupes). +- Resolver: the config-forward refusal pins stay green (this change must not touch + them; a test guards that a config `LocalForward` still fails resolution). +- Render: byte snapshots (policy-first ordering, the forward block, relay and + forwards coexisting, the exact-Host pattern not matching the hop's host). +- Plane: launch route gate matrix (loopback vs wide bind x node setting off/on), + min-protocol refusal, duplicate refusal, audit summary shape; preset save + validation and round-trip. +- Node: launch re-validation of rows (a hostile list cannot render), config + byte-check still passes. +- Web: draft round-trip, bind-host gating on the node flag, preset editor rows. +- E2E: extend spec 22's real-sshd pattern: launch with a `-L` row and curl the bound + loopback port for the fixture's bytes; a `-D` leg through `curl --socks5-hostname`; + a `-R` loopback-bind leg; the wide-bind refusal before the toggle and acceptance + after. Assert the recorded config carries the forward block and the `Host *` clear. + +## 10. Non-goals and follow-ups + +- Not managed-tunnel objects: no start/stop apart from the pane, no live tunnel + list, no health. If forwards outgrow the pane, that's a new spec. +- No ephemeral/auto ports (a row without a port has no honest audit story). +- No way to use a forwarded port from the browser or the desktop app's machine; + forwards terminate on B, where the ssh process lives. +- No pane-header "active forwards" display yet; the launch audit and the pane's ssh + config are the record. +- `ForwardAgent`, `ForwardX11`, and `Tunnel` toward D stay refused: this feature is + TCP port forwarding, and nothing here opens those doors. From a1e711885c96ea0ce9de2c1bbbbe2ea0ffba34cb Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 14:38:31 -0700 Subject: [PATCH 02/35] feat(ssh): forward-row grammar in the protocol package --- .../2026-10-15-ssh-port-forwards-design.md | 66 ++++--- .../src/__tests__/ssh-forwards.test.ts | 154 +++++++++++++++++ packages/subshell-protocol/src/index.ts | 11 ++ packages/subshell-protocol/src/ssh-config.ts | 19 +- .../subshell-protocol/src/ssh-forwards.ts | 162 ++++++++++++++++++ 5 files changed, 383 insertions(+), 29 deletions(-) create mode 100644 packages/subshell-protocol/src/__tests__/ssh-forwards.test.ts create mode 100644 packages/subshell-protocol/src/ssh-forwards.ts diff --git a/docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md b/docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md index 34aba8a09..e36e46385 100644 --- a/docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md +++ b/docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md @@ -78,20 +78,36 @@ the channels distinct is what lets the old refusal stay honest. ## 5. Rendering -`renderSshConfigContents` grows an options object, `{ relay?, forwards? }`. With -approved rows it emits a second config block, `Host `, -**after** the `Host *` policy block, carrying `LocalForward` / `RemoteForward` / -`DynamicForward` lines composed only from re-parsed rows. - -Two facts about OpenSSH make the placement the whole design. First, -`ClearAllForwardings` clears forwardings "specified in configuration files **or on -the command line**" at the point it is applied, so approved rows cannot ride argv -where the policy block would wipe them; they must be added after the clear, inside -the same file, which is also why the rows stay out of `sshOptionTokens` (those are -the destination-scoped facts the policy must not fight). Second, ProxyJump children -re-read the `-F` file matching their own host, so they hit `Host *` (policy, -cleared) and never match the exact final-host pattern: a forward cannot leak onto a -hop's connection. +`renderSshConfigContents` grows a third argument, the approved row list (the +relay option stays where it is). Rendering is the plane's job end to end - the +launch frame still carries only `{ configPath, fileContent }` - so no wire +grammar changes and no agent update is required: the rows shape the bytes the +node already writes verbatim. When rows are present the policy block **drops +its `ClearAllForwardings yes` line** and a second block appears: `Host `, carrying `LocalForward` / `RemoteForward` / `DynamicForward` +lines composed only from re-parsed rows. Omitted bind hosts render explicitly as +`127.0.0.1:port` for local/dynamic; a remote row with no bind host rides D's own +policy spelling. Without rows, the file is byte-identical to what renders today, +clear included. + +That omission is measured, not assumed (OpenSSH 10.2p1, real sshd, observable +listener): `ClearAllForwardings` applies at the END of the whole config load, not +where it is written, so forwardings survive it neither from a later block nor from +the command line. Placement "after the clear" was the first design and the probe +buried it. What makes omitting the clear honest is where forwardings can otherwise +come from on this connection: `-F` replaces the user file outright, so the only +channels are our rendered lines and the root-owned system file - the same +"inside the OS trust boundary, root's to decide" class the renderer's header +already names. The connecting account's own config never reaches a Subshell +launch, which is what the clear was really guarding. + +The exact-Host block still matters for the other reason: ProxyJump children re-read +the `-F` file matching their own host, so they inherit the `Host *` policy (without +the now-absent clear, which they never needed for confinement: `ForwardAgent`/ +`ForwardX11 no` and friends are first-obtained keywords) and never match the final +host's pattern. A forward cannot leak onto a hop's connection. One grammar corner: +a launch whose ProxyJump names the destination host itself is refused with forwards +(the pattern would match both processes); the route refuses it by name. Byte stability is kept (same approval renders the same file), so the run's recorded config byte-check and spec-22's sweep keep working. `ExitOnForwardFailure` stays at @@ -118,11 +134,14 @@ form says so. - **Auth modes are untouched.** A forward is an option on the one already-approved connection. Password mode and the one-shot local-key path gain nothing new: rows never multiply connections, and the local-key signer still signs exactly once. -- **Older agents are refused, never downgraded.** A launch carrying rows toward an - agent predating the grammar is refused by name with an "update this machine" - remedy. Silently stripping a forward is precisely the silent-widening failure the - resolver's refusal was written against; a dropped forward is a lie about what the - user approved. +- **No agent update is required, and none is silently assumed.** The rows shape + the rendered config, and the launch frame already carries the rendered bytes; + every agent that writes a Subshell ssh config writes a forwarding one. What + must never happen is a plane rendering a launch WITHOUT a row the user + approved: compose re-validates the row list at the same boundary it + re-validates the snapshot, and a grammar refusal throws rather than dropping + a row (silently stripping a forward is the silent-widening failure the + resolver's refusal was written against, seen from the other side). ## 7. Surfaces @@ -155,10 +174,11 @@ flip writes its own row in the nodes family. - Render: byte snapshots (policy-first ordering, the forward block, relay and forwards coexisting, the exact-Host pattern not matching the hop's host). - Plane: launch route gate matrix (loopback vs wide bind x node setting off/on), - min-protocol refusal, duplicate refusal, audit summary shape; preset save - validation and round-trip. -- Node: launch re-validation of rows (a hostile list cannot render), config - byte-check still passes. + duplicate refusal, jump-names-destination refusal, audit summary shape; + preset save validation and round-trip. +- Node: unchanged launch path (the frame still carries configPath + content + bytes; the path byte-check and the config dir sweep must still pass for a + launch that forwards). - Web: draft round-trip, bind-host gating on the node flag, preset editor rows. - E2E: extend spec 22's real-sshd pattern: launch with a `-L` row and curl the bound loopback port for the fixture's bytes; a `-D` leg through `curl --socks5-hostname`; diff --git a/packages/subshell-protocol/src/__tests__/ssh-forwards.test.ts b/packages/subshell-protocol/src/__tests__/ssh-forwards.test.ts new file mode 100644 index 000000000..95d393613 --- /dev/null +++ b/packages/subshell-protocol/src/__tests__/ssh-forwards.test.ts @@ -0,0 +1,154 @@ +import { describe, expect, it } from "bun:test"; +import { + isLoopbackForwardBind, + isSshForwardKind, + parseSshForwardRows, + SSH_FORWARD_KINDS, + SSH_MAX_FORWARD_ROWS, + type SshForwardWire, +} from "../ssh-forwards.js"; + +/** + * The forward-row grammar (spec 2026-10-15 §4). These refusals are the + * shape half of the security story - the node re-runs this parser on every + * launch that names forwards, so what renders can never be wider than what + * this grammar can express. Policy (the wide-bind gate) is deliberately NOT + * tested here: this module answers "is this a row", never "is this row + * allowed on this machine". + */ + +function makeRow(overrides: Partial = {}): SshForwardWire { + return { + kind: "local", + listenPort: 5432, + bindHost: null, + targetHost: "db.internal", + targetPort: 5432, + ...overrides, + }; +} + +describe("parseSshForwardRows", () => { + it("round-trips one row of each kind", () => { + const rows: SshForwardWire[] = [ + makeRow(), + makeRow({ kind: "remote", listenPort: 9090, bindHost: null, targetHost: "node.internal", targetPort: 3000 }), + makeRow({ kind: "dynamic", listenPort: 1080, targetHost: null, targetPort: null }), + ]; + expect(parseSshForwardRows(structuredClone(rows))).toEqual(rows); + }); + + it("treats null and absent as no forwards, and the empty list round-trips", () => { + expect(parseSshForwardRows(null)).toEqual([]); + expect(parseSshForwardRows(undefined)).toEqual([]); + expect(parseSshForwardRows([])).toEqual([]); + }); + + it("accepts a bound row: loopback v4, bracketed v6, and a named host", () => { + for (const bindHost of ["127.0.0.1", "[::1]", "localhost", "10.0.0.5", "fwd.internal"]) { + expect(parseSshForwardRows([makeRow({ bindHost })])).toEqual([makeRow({ bindHost })]); + } + }); + + it("refuses a bare (unbracketed) IPv6 bind: the host grammar wants brackets, same as every other host field", () => { + expect(parseSshForwardRows([makeRow({ bindHost: "::1" })])).toBeNull(); + }); + + it("refuses an option-like leading dash on any host member", () => { + expect(parseSshForwardRows([makeRow({ bindHost: "-oProxyCommand=evil" })])).toBeNull(); + expect(parseSshForwardRows([makeRow({ targetHost: "-lroot" })])).toBeNull(); + }); + + it("refuses a target baked with a port (the grammar keeps port its own field)", () => { + expect(parseSshForwardRows([makeRow({ targetHost: "db.internal:5432" })])).toBeNull(); + }); + + it("refuses an unknown kind and a wrong-case kind (enums are enums)", () => { + expect(parseSshForwardRows([makeRow({ kind: "tunnel" as SshForwardWire["kind"] })])).toBeNull(); + expect(parseSshForwardRows([{ ...makeRow(), kind: "Local" }])).toBeNull(); + }); + + it("refuses ports outside 1..65535 and non-integers", () => { + for (const listenPort of [0, 65536, -1, 80.5, Number.NaN, "80"]) { + expect(parseSshForwardRows([makeRow({ listenPort: listenPort as unknown as number })])).toBeNull(); + } + for (const targetPort of [0, 65536, 1.5]) { + expect(parseSshForwardRows([makeRow({ targetPort: targetPort as unknown as number })])).toBeNull(); + } + }); + + it("refuses a dynamic row that carries a target (a contradiction, not a hint)", () => { + expect(parseSshForwardRows([makeRow({ kind: "dynamic", targetHost: "x.example" })])).toBeNull(); + expect(parseSshForwardRows([makeRow({ kind: "dynamic", targetPort: 80 })])).toBeNull(); + }); + + it("refuses local/remote rows missing a target member", () => { + expect(parseSshForwardRows([{ kind: "local", listenPort: 5432, bindHost: null }])).toBeNull(); + expect(parseSshForwardRows([{ kind: "remote", listenPort: 9090, bindHost: null, targetHost: "h" }])).toBeNull(); + }); + + it("refuses an absent or non-null bindHost member: presence is required, null is the default spelling", () => { + expect(parseSshForwardRows([{ kind: "local", listenPort: 5432, targetHost: "h", targetPort: 1 }])).toBeNull(); + expect(parseSshForwardRows([makeRow({ bindHost: "" })])).toBeNull(); + }); + + it("refuses a row with an unknown key rather than dropping it", () => { + expect(parseSshForwardRows([{ ...makeRow(), extra: true }])).toBeNull(); + }); + + it("refuses a duplicate listen end, case-insensitively on the name", () => { + expect(parseSshForwardRows([makeRow(), makeRow({ targetHost: "other.example" })])).toBeNull(); + expect(parseSshForwardRows([makeRow({ bindHost: "LOCALHOST" }), makeRow({ bindHost: "localhost" })])).toBeNull(); + }); + + it("accepts two rows on one port under different literal names (the bind race is ssh's warning, not this grammar's)", () => { + const rows = [makeRow({ bindHost: "localhost" }), makeRow({ bindHost: "127.0.0.1" })]; + expect(parseSshForwardRows(rows)).toEqual(rows); + }); + + it("refuses a non-array list", () => { + expect(parseSshForwardRows({ 0: makeRow() })).toBeNull(); + expect(parseSshForwardRows("local")).toBeNull(); + expect(parseSshForwardRows([null, undefined, 42, []])).toBeNull(); + }); + + it("refuses over the cap rather than truncating", () => { + const over = Array.from({ length: SSH_MAX_FORWARD_ROWS + 1 }, (_, i) => makeRow({ listenPort: i + 1 })); + expect(parseSshForwardRows(over)).toBeNull(); + const atCap = over.slice(0, SSH_MAX_FORWARD_ROWS); + expect(parseSshForwardRows(atCap)).toEqual(atCap); + }); + + it("returns rebuilt rows, never the input objects (stray-array mutation cannot ride along)", () => { + const input = [makeRow()]; + const parsed = parseSshForwardRows(input) ?? []; + expect(parsed).toEqual(input); + expect(parsed[0]).not.toBe(input[0]); + }); +}); + +describe("isLoopbackForwardBind", () => { + const loopback = [null, "localhost", "LOCALHOST", "Localhost", "[::1]", "127.0.0.1", "127.9.9.9", "127.0.0.0"]; + for (const bindHost of loopback) { + it(`confines ${bindHost ?? "(omitted)"}`, () => { + expect(isLoopbackForwardBind(makeRow({ bindHost }))).toBe(true); + }); + } + const wide = ["0.0.0.0", "::", "[::]", "10.0.0.5", "example.com", "[::ffff:127.0.0.1]", "mybox.internal"]; + for (const bindHost of wide) { + it(`names ${bindHost} wide (fails closed: the test is the written name, not a DNS answer)`, () => { + expect(isLoopbackForwardBind(makeRow({ bindHost }))).toBe(false); + }); + } +}); + +describe("SSH_FORWARD_KINDS / isSshForwardKind", () => { + it("the runtime list matches the type census", () => { + expect([...SSH_FORWARD_KINDS].sort()).toEqual(["dynamic", "local", "remote"]); + }); + it("accepts exactly the named kinds, case-sensitively", () => { + for (const kind of SSH_FORWARD_KINDS) expect(isSshForwardKind(kind)).toBe(true); + expect(isSshForwardKind("LOCAL")).toBe(false); + expect(isSshForwardKind(undefined)).toBe(false); + }); +}); diff --git a/packages/subshell-protocol/src/index.ts b/packages/subshell-protocol/src/index.ts index a8f058ab8..c090ebc54 100644 --- a/packages/subshell-protocol/src/index.ts +++ b/packages/subshell-protocol/src/index.ts @@ -249,6 +249,8 @@ export { type ViewerCapacity, } from "./shared-geometry.js"; export { + isSshHostName, + isSshPort, parseSshConnectionSnapshot, SSH_FORBIDDEN_SNAPSHOT_FIELDS, type SshConnectionSnapshotWire, @@ -260,6 +262,15 @@ export { SSH_ERROR_DESCRIPTIONS, type SshErrorCode, } from "./ssh-errors.js"; +export { + isLoopbackForwardBind, + isSshForwardKind, + parseSshForwardRows, + SSH_FORWARD_KINDS, + SSH_MAX_FORWARD_ROWS, + type SshForwardKind, + type SshForwardWire, +} from "./ssh-forwards.js"; export { isSshFingerprint, isSshFingerprints, diff --git a/packages/subshell-protocol/src/ssh-config.ts b/packages/subshell-protocol/src/ssh-config.ts index 4ce8a511b..9710af426 100644 --- a/packages/subshell-protocol/src/ssh-config.ts +++ b/packages/subshell-protocol/src/ssh-config.ts @@ -163,8 +163,12 @@ function isWireName(value: unknown): value is string { ); } -/** A destination hostname (or bracketed IPv6), by grammar not by hygiene: see {@link HOST_RE}. */ -function isHostName(value: unknown): value is string { +/** + * A destination hostname (or bracketed IPv6), by grammar not by hygiene: see {@link HOST_RE}. + * Exported because the forward-row grammar validates host-valued fields under + * exactly the same rule; one definition, not two that can drift. + */ +export function isSshHostName(value: unknown): value is string { return isStr(value) && value.length <= SSH_NAME_MAX_CHARS && HOST_RE.test(value); } @@ -178,8 +182,11 @@ function isAbsPosixPath(value: unknown): value is string { return isStr(value) && value.startsWith("/") && value.length <= SSH_PATH_MAX_CHARS && !/\p{Cc}/u.test(value); } -/** A resolved port: a positive integer inside the legal range. */ -function isPort(value: unknown): value is number { +/** + * A resolved port: a positive integer inside the legal range. Exported for the + * forward-row grammar, which bounds listen and target ports by the same rule. + */ +export function isSshPort(value: unknown): value is number { return isInt(value) && (value as number) >= 1 && (value as number) <= 65535; } @@ -189,7 +196,7 @@ function isPathListWithin(value: unknown, cap: number): value is string[] { } function isHop(value: unknown): value is SshHopWire { - if (!isRecord(value) || !isHostName(value.host) || !isPort(value.port)) return false; + if (!isRecord(value) || !isSshHostName(value.host) || !isSshPort(value.port)) return false; if (!("user" in value)) return false; return value.user === null || isUserName(value.user); } @@ -213,7 +220,7 @@ export function parseSshConnectionSnapshot(value: unknown): SshConnectionSnapsho for (const field of SSH_FORBIDDEN_SNAPSHOT_FIELDS) { if (value[field] != null) return null; } - if (!isWireName(value.alias) || !isHostName(value.host) || !isPort(value.port)) return null; + if (!isWireName(value.alias) || !isSshHostName(value.host) || !isSshPort(value.port)) return null; if (!("user" in value) || !(value.user === null || isUserName(value.user))) return null; if (!isPathListWithin(value.identityFiles, SSH_MAX_IDENTITY_REFS)) return null; if (!isPathListWithin(value.certificateFiles, SSH_MAX_CERT_REFS)) return null; diff --git a/packages/subshell-protocol/src/ssh-forwards.ts b/packages/subshell-protocol/src/ssh-forwards.ts new file mode 100644 index 000000000..f95256040 --- /dev/null +++ b/packages/subshell-protocol/src/ssh-forwards.ts @@ -0,0 +1,162 @@ +/** + * The user-declared port-forward row (docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md). + * + * Read the sibling `ssh-config.ts` first, because the two grammars draw the + * line the security posture depends on: the SNAPSHOT's forbidden `forwards` + * member stays literal null, so a forward found in the connecting account's + * own ssh config still fails resolution by name. What lives HERE is the other + * door - a row the human typed into the launch form (or a preset saved from + * it), approved on its face, rendered into the per-connection config's exact + * destination block. Same rebuild discipline as `parseSshConnectionSnapshot`: + * any bad row refuses the whole list, because half a list of approved + * forwards is a lie about what the user approved. + * + * Shape is the grammar's job; policy is the plane's. `bindHost` may name ANY + * canonical host here, including `0.0.0.0`: whether a wide bind is ALLOWED is + * the connecting node's `sshWideForwards` decision, checked at the launch + * route against the machine's setting, never here. This module only knows + * what a row IS, so the node can re-validate the shape it was handed without + * holding the plane's policy. + * + * Imports no `node:` builtin; Metro-safe barrel like its neighbors. + */ + +import { isRecord, isStr } from "./guards.js"; +import { isSshHostName, isSshPort } from "./ssh-config.js"; + +/** The three OpenSSH forward kinds: `-L`, `-R`, `-D`. */ +export const SSH_FORWARD_KINDS = ["local", "remote", "dynamic"] as const; + +/** One member of {@link SSH_FORWARD_KINDS}. */ +export type SshForwardKind = (typeof SSH_FORWARD_KINDS)[number]; + +/** True when `value` is one of the named kinds (case-sensitive: the wire spells enums, never prose). */ +export function isSshForwardKind(value: unknown): value is SshForwardKind { + return isStr(value) && (SSH_FORWARD_KINDS as readonly string[]).includes(value); +} + +/** + * One approved forward. The listen end is where the port binds: on the + * connecting machine B for `local`/`dynamic`, on the destination D for + * `remote`. The target is what traffic reaches: a host D can see for + * `local`, a host B can see for `remote`, nothing at all for `dynamic` + * (the SOCKS channel picks its own destinations at runtime, which is the + * whole feature and exactly the reach a shell on D already grants). + * + * A `type`, not an interface: rows ride inside JSON command members, and a + * type has an implicit index signature the command builders rely on. + */ +export type SshForwardWire = { + /** Which way traffic flows; see {@link SshForwardKind}. */ + kind: SshForwardKind; + /** Listen port, 1..65535. Always explicit: an ephemeral/auto port has no honest audit story (spec §10). */ + listenPort: number; + /** + * Listen bind host; null means "the stack's loopback default" (rendered + * explicitly for local/dynamic; for remote, D's own `GatewayPorts` policy + * decides, which is D's to make - spec §6). Grammar-valid host otherwise; + * the WIDE question is the plane's gate, not this parser's. + */ + bindHost: string | null; + /** Target host; required for local/remote, null only for dynamic. */ + targetHost: string | null; + /** Target port; follows the same kind rule. */ + targetPort: number | null; +}; + +/** + * Rows one connection may carry. Generous for a real dev host (a database, a + * cache, an admin API, and room to spare) and small enough that the audit + * summary and the rendered block stay reviewable. Over the cap is a refusal, + * never a truncation. + */ +export const SSH_MAX_FORWARD_ROWS = 16; + +/** The keys one row may spell; anything else means the caller hand-built a shape this grammar does not have. */ +const FORWARD_ROW_KEYS: readonly (keyof SshForwardWire)[] = [ + "kind", + "listenPort", + "bindHost", + "targetHost", + "targetPort", +]; + +/** + * Validate and rebuild one row. Returns the narrowed row or null; the caller + * never sees the input object (rebuilt output, stray keys die with the input, + * same discipline as the snapshot parser). + */ +function parseSshForwardRow(value: unknown): SshForwardWire | null { + if (!isRecord(value)) return null; + for (const key of Object.keys(value)) { + if (!(FORWARD_ROW_KEYS as readonly string[]).includes(key)) return null; + } + if (!isSshForwardKind(value.kind) || !isSshPort(value.listenPort)) return null; + if (!("bindHost" in value)) return null; + if (value.bindHost !== null && !isSshHostName(value.bindHost)) return null; + const needsTarget = value.kind === "local" || value.kind === "remote"; + if (needsTarget) { + if (!("targetHost" in value) || !isSshHostName(value.targetHost)) return null; + if (!("targetPort" in value) || !isSshPort(value.targetPort)) return null; + } else { + // A dynamic row carrying a target is a contradiction, not a hint: refuse. + if ((value.targetHost ?? null) !== null || (value.targetPort ?? null) !== null) return null; + } + return { + kind: value.kind, + listenPort: value.listenPort, + bindHost: value.bindHost === undefined ? null : (value.bindHost as string | null), + targetHost: needsTarget ? (value.targetHost as string) : null, + targetPort: needsTarget ? (value.targetPort as number) : null, + }; +} + +/** + * Validate a whole forward list: each row through {@link parseSshForwardRow}, + * then the LIST rules. A duplicate listen end (same host case-insensitively, + * same port) refuses the list: two rows competing for one bind is a bug the + * user should hear about, not a race ssh silently half-wins. The cap is a + * refusal too, never a trim. + * + * @param value - candidate rows (parsed JSON; unknown provenance), or null/undefined for "no forwards" + * @returns the narrowed rows (possibly empty), or null when malformed or over cap + */ +export function parseSshForwardRows(value: unknown): SshForwardWire[] | null { + if (value === null || value === undefined) return []; + if (!Array.isArray(value) || value.length > SSH_MAX_FORWARD_ROWS) return null; + const rows: SshForwardWire[] = []; + const seen = new Set(); + for (const item of value) { + const row = parseSshForwardRow(item); + if (row === null) return null; + // Casefold only for the dupe key: hostnames match case-insensitively, and + // a "LOCALHOST" beside "localhost" is one bind, not two. + const key = `${(row.bindHost ?? "").toLowerCase()}|${row.listenPort}`; + if (seen.has(key)) return null; + seen.add(key); + rows.push(row); + } + return rows; +} + +/** + * True when this row's listen end is confined to loopback: an omitted + * `bindHost` (which renders as loopback) or a named loopback address + * (`127.0.0.0/8`, `::1`, `localhost`). Exported for the plane's wide-bind + * gate: anything answering false requires the connecting machine's + * `sshWideForwards` setting. A hostname that merely HAPPENS to resolve to + * loopback ("mybox.internal") is not loopback here - the test is the name + * the user wrote, because that name is what the rendered config carries and + * what DNS can later point elsewhere. + */ +export function isLoopbackForwardBind(row: SshForwardWire): boolean { + if (row.bindHost === null) return true; + const host = row.bindHost.toLowerCase(); + // The grammar admits IPv6 only bracketed (HOST_RE), so `[::1]` is the + // spelling a row can actually carry. + if (host === "localhost" || host === "[::1]") return true; + // IPv4 127.0.0.0/8: dotted-quad only (the grammar's host shape admits the + // other spellings, but nobody renders those here and refusing them as + // "wide" fails closed, which is the safe direction). + return /^127(\.\d{1,3}){3}$/.test(host); +} From e91cba8960d5f5571e8bb251df13b94454fe7069 Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 14:48:44 -0700 Subject: [PATCH 03/35] feat(ssh): render approved forward rows into the exact-destination config block --- .../api/src/services/ssh-launch.service.ts | 11 ++ .../src/ssh/__tests__/ssh-render.test.ts | 131 ++++++++++++++++++ packages/pane-runtime/src/ssh/ssh-render.ts | 83 ++++++++++- 3 files changed, 223 insertions(+), 2 deletions(-) diff --git a/apps/server/api/src/services/ssh-launch.service.ts b/apps/server/api/src/services/ssh-launch.service.ts index 731435195..01a2f591e 100644 --- a/apps/server/api/src/services/ssh-launch.service.ts +++ b/apps/server/api/src/services/ssh-launch.service.ts @@ -21,6 +21,7 @@ import { parseSshConnectionSnapshot, SSH_NAME_MAX_CHARS, type SshConnectionSnapshotWire, + type SshForwardWire, } from "@internal/subshell-protocol"; import { loadNodeGate } from "@/api/nodes/node-gate.js"; import { SUBSHELL_SERVER_DATA_DIR } from "@/constants.js"; @@ -142,6 +143,15 @@ export function composeSshLaunch(args: { */ hostPinPath?: string; authentication?: "password"; + /** + * User-declared port-forward rows (docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md). + * The launch route has already gate-checked them (wide binds against the + * machine's setting); the renderer re-validates the SHAPE here as the last + * station before the bytes, same impossible-state posture as the snapshot + * line below - a refusal that dropped rows silently would tell the user a + * forward exists when nothing binds it. + */ + forwards?: SshForwardWire[]; }): { configPath: string; fileContent: string; @@ -154,6 +164,7 @@ export function composeSshLaunch(args: { let fileContent = renderSshConfigContents( approved, args.hostPinPath === undefined ? undefined : { hostPinPath: args.hostPinPath }, + args.forwards, ); if (args.authentication === "password") { if (args.hostPinPath) throw new Error("Password authentication cannot use a key relay."); diff --git a/packages/pane-runtime/src/ssh/__tests__/ssh-render.test.ts b/packages/pane-runtime/src/ssh/__tests__/ssh-render.test.ts index 906f5d0a5..1da86f815 100644 --- a/packages/pane-runtime/src/ssh/__tests__/ssh-render.test.ts +++ b/packages/pane-runtime/src/ssh/__tests__/ssh-render.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "bun:test"; import { readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; +import type { SshForwardWire } from "@internal/subshell-protocol"; import { buildAgentSocketPath, buildSshConfigPath, @@ -367,6 +368,136 @@ describe("renderSshConfigContents relay mode (spec 2026-10-08 §9)", () => { }); }); +describe("renderSshConfigContents port forwards (spec 2026-10-15)", () => { + const localRow: SshForwardWire = { + kind: "local", + listenPort: 5432, + bindHost: null, + targetHost: "db.internal", + targetPort: 5432, + }; + const wideRow: SshForwardWire = { + kind: "local", + listenPort: 8080, + bindHost: "10.0.0.5", + targetHost: "api.internal", + targetPort: 80, + }; + const remoteRow: SshForwardWire = { + kind: "remote", + listenPort: 9090, + bindHost: null, + targetHost: "node.internal", + targetPort: 3000, + }; + const dynamicRow: SshForwardWire = { + kind: "dynamic", + listenPort: 1080, + bindHost: null, + targetHost: null, + targetPort: null, + }; + + it("absent or empty rows render byte-identical to the rowless posture, clear included", () => { + const snap = baseSnapshot(); + expect(renderSshConfigContents(snap, undefined, [])).toBe(renderSshConfigContents(snap)); + expect(renderSshConfigContents(snap, undefined, [localRow])).not.toBe(renderSshConfigContents(snap)); + }); + + it("renders each kind as a line under the exact destination Host, loopback binds explicit", () => { + const out = renderSshConfigContents(baseSnapshot(), undefined, [localRow, wideRow, remoteRow, dynamicRow]); + expect(out).toContain("Host app-02.example.com"); + expect(out).toContain(" LocalForward 127.0.0.1:5432 db.internal:5432"); + expect(out).toContain(" LocalForward 10.0.0.5:8080 api.internal:80"); + // A remote row with no bind host stays bare: D's own GatewayPorts policy + // is the authority there, and inventing a name we did not approve would + // be the opposite lie of the one the renderer refuses elsewhere. + expect(out).toContain(" RemoteForward 9090 node.internal:3000"); + expect(out).toContain(" DynamicForward 127.0.0.1:1080"); + }); + + it("the row block comes AFTER the Host * policy section, under its own header", () => { + const out = renderSshConfigContents(baseSnapshot(), undefined, [localRow]); + const policy = out.indexOf("Host *"); + const block = out.indexOf("Host app-02.example.com"); + expect(policy).toBeGreaterThanOrEqual(0); + expect(block).toBeGreaterThan(policy); + // No LocalForward byte exists BEFORE the exact-Host header: the rows are + // under their own section, not smuggled into the policy one (the whole + // jump-child isolation argument rests on this shape). + expect(out.indexOf("LocalForward")).toBeGreaterThan(block); + }); + + it("drops ONLY the ClearAllForwardings line; every other confinement stands", () => { + const out = renderSshConfigContents(baseSnapshot(), undefined, [localRow]); + expect(out).not.toContain("ClearAllForwardings"); + for (const [key, value] of [ + ["ForwardAgent", "no"], + ["ForwardX11", "no"], + ["Tunnel", "no"], + ["PermitRemoteOpen", "none"], + ["PermitLocalCommand", "no"], + ["EscapeChar", "none"], + ["ControlMaster", "no"], + ] as [string, string][]) { + expect(out).toContain(` ${key} ${value}`); + } + }); + + it("coexists with relay mode: pinned trust bytes AND the row block", () => { + const out = renderSshConfigContents(baseSnapshot(), { hostPinPath: "/data/ssh/s1/known_hosts" }, [dynamicRow]); + expect(out).toContain(" StrictHostKeyChecking yes"); + expect(out).toContain(" UserKnownHostsFile /data/ssh/s1/known_hosts"); + expect(out).toContain(" DynamicForward 127.0.0.1:1080"); + expect(out).not.toContain("ClearAllForwardings"); + }); + + it("refuses the whole render on a malformed row (no partial forward list, ever)", () => { + expect(() => renderSshConfigContents(baseSnapshot(), undefined, [localRow, { kind: "tunnel" } as never])).toThrow( + /grammar re-validation/, + ); + }); + + it("refuses rows when a ProxyJump hop names the destination host (case-insensitively)", () => { + const snap = baseSnapshot({ + host: "dup.example.com", + proxyJumps: [{ host: "DUP.example.com", user: null, port: 22 }], + }); + expect(() => renderSshConfigContents(snap, undefined, [localRow])).toThrow(/ProxyJump names the destination/); + // Rowless, the same snapshot still renders: the guard belongs to forwards only. + expect(renderSshConfigContents(snap)).toContain("Host *"); + }); + + it.skipIf(!Bun.which("ssh"))("real OpenSSH parses a rendered forward config without complaint", async () => { + const root = tempRoot("ssh-render-forwards-"); + try { + const path = join(root, "config"); + writeFileSync( + path, + renderSshConfigContents( + baseSnapshot({ identityFiles: [], certificateFiles: [], knownHostsFiles: [] }), + undefined, + [localRow, wideRow, remoteRow, dynamicRow], + ), + ); + // -G evaluates the config for a host without connecting. The destination + // host's pass must exit clean; ssh would warn on a bad keyword or value. + const child = Bun.spawn([Bun.which("ssh") as string, "-G", "-F", path, "app-02.example.com"], { + stdout: "pipe", + stderr: "pipe", + }); + const stderr = await new Response(child.stderr).text(); + expect(await child.exited).toBe(0); + // OpenSSH 10.2 answers -G on a non-tty stdin with a pseudo-terminal + // NOTICE regardless of the config; the honest check is that no CONFIG + // complaint rides along (a bad keyword or value names itself). + expect(stderr).not.toMatch(/bad configuration|unknown|invalid|overlong/i); + } finally { + cleanup(root); + } + }); +}); + describe("buildSshConfigPath", () => { it("composes the derived path from the two facts each side already holds", () => { expect(buildSshConfigPath("/data", "s1")).toBe("/data/ssh/s1/config"); diff --git a/packages/pane-runtime/src/ssh/ssh-render.ts b/packages/pane-runtime/src/ssh/ssh-render.ts index 5a4939b25..f95c9f7ab 100644 --- a/packages/pane-runtime/src/ssh/ssh-render.ts +++ b/packages/pane-runtime/src/ssh/ssh-render.ts @@ -1,7 +1,9 @@ import { parseSshConnectionSnapshot, + parseSshForwardRows, SSH_PATH_MAX_CHARS, type SshConnectionSnapshotWire, + type SshForwardWire, type SshHopWire, } from "@internal/subshell-protocol"; @@ -75,6 +77,9 @@ const MANDATORY_POLICY: readonly [string, string][] = [ ["ForwardX11", "no"], ["ForwardX11Trusted", "no"], // no local/remote/dynamic forwards or tunnels, whatever any config said + // (the ONE conditional policy line: omitted when the launch carries + // approved forward rows, which this clear would wipe anyway - see the + // render function's doc for the measured OpenSSH fact) ["ClearAllForwardings", "yes"], ["Tunnel", "no"], ["PermitRemoteOpen", "none"], @@ -146,6 +151,28 @@ export interface SshRelayRenderOption { /** The two spellings of the ONE host-key-checking line: the M1 posture and the relay block. */ const HOST_KEY_CHECK: Record<"direct" | "relay", string> = { direct: "accept-new", relay: "yes" }; +/** The ssh_config keyword each approved forward kind renders to (the row's kind is grammar-closed, so this lookup is total). */ +const FORWARD_KEYWORD: Record = { + local: "LocalForward", + remote: "RemoteForward", + dynamic: "DynamicForward", +}; + +/** + * One approved forward as one config line, under the exact-destination `Host` + * block. The listen spelling carries no whitespace risk (the row grammar + * validates every host member), so no quoting seam is needed here; a null + * `bindHost` renders ssh's own loopback default EXPLICITLY for local/dynamic + * (127.0.0.1 is the name the audit and the reviewer both read), and stays + * spelled bare for remote, where D's `GatewayPorts` policy is the authority + * and adding a name we did not approve would be a lie of the opposite kind. + */ +function forwardLine(row: SshForwardWire): string { + const listen = row.bindHost === null ? (row.kind === "remote" ? "" : "127.0.0.1:") : `${row.bindHost}:`; + const head = `${FORWARD_KEYWORD[row.kind]} ${listen}${row.listenPort}`; + return ` ${row.kind === "dynamic" ? head : `${head} ${row.targetHost}:${row.targetPort}`}`; +} + /** * Render the runtime ssh_config FILE for one snapshot: the policy above, * every hop included, plus the snapshot's trust refs and identity refs under @@ -167,9 +194,46 @@ const HOST_KEY_CHECK: Record<"direct" | "relay", string> = { direct: "accept-new * B's `/etc/ssh/ssh_known_hosts` must not authenticate D either. Absent the * option (every M1 launch) the bytes are the accept-new posture * byte-for-byte, untouched: the direct branch never names the global file. + * + * User-declared port forwards (the third argument, spec 2026-10-15) change two + * bytes-worth of policy and nothing else: the `ClearAllForwardings yes` line + * is dropped, and an exact-destination `Host` block carrying the approved + * rows is appended. The drop is measured, not assumed (OpenSSH 10.2p1, real + * sshd, observable listener): `ClearAllForwardings` applies at the END of the + * whole config load, not where it is written, so forwardings survive it + * neither from a later block nor from the command line; keeping the line + * would silently undo the rows, and silently dropping a forward is precisely + * the lie the resolver's refusal-by-name exists to prevent. Omitting it is + * honest because the forward channels left are our own lines and the + * root-owned system file - `-F` replaces the user config outright, and the + * system file is the "root's to decide" class this file's header already + * names. The rows enter under the EXACT destination host because ProxyJump + * children re-read this file matching their own host: they get the `Host *` + * policy and never the row block, so a forward cannot leak onto a hop. + * Absent or empty rows, every byte is what renders today, clear included. */ -export function renderSshConfigContents(snapshot: SshConnectionSnapshotWire, relay?: SshRelayRenderOption): string { +export function renderSshConfigContents( + snapshot: SshConnectionSnapshotWire, + relay?: SshRelayRenderOption, + forwards?: SshForwardWire[], +): string { assertRenderable(snapshot); + // Rows are re-validated HERE, at the last station before bytes a machine + // writes: a refused row THROWS rather than dropping, because half an + // approved forward list is a lie about what the user approved (the same + // no-partial-approval discipline as the snapshot parse). + let rows: SshForwardWire[] = []; + if (forwards !== undefined) { + const parsed = parseSshForwardRows(forwards); + if (parsed === null) throw new Error("forward rows failed grammar re-validation"); + rows = parsed; + } + if (rows.length > 0 && snapshot.proxyJumps.some((h) => h.host.toLowerCase() === snapshot.host.toLowerCase())) { + // The exact-`Host` pattern would match the hop child too, handing a + // forward to the wrong connection. The route refuses this launch for the + // user first; this is the impossible-state guard beneath it. + throw new Error("port forwards cannot ride a launch whose ProxyJump names the destination host"); + } if (relay !== undefined) { // The same path discipline `sshOptionTokens` applies to the config path: // this value is interpolated into a file ssh reads verbatim, and a @@ -192,7 +256,12 @@ export function renderSshConfigContents(snapshot: SshConnectionSnapshotWire, rel ] : []), "Host *", - ...MANDATORY_POLICY.map(([key, value]) => ` ${key} ${key === "StrictHostKeyChecking" ? checking : value}`), + ...MANDATORY_POLICY.filter( + // The clear wipes approved rows too, measured (see the function doc): + // a launch with approved forwards drops the line, every other key of + // the policy stays, and so does the line for every rowless launch. + ([key]) => !(rows.length > 0 && key === "ClearAllForwardings"), + ).map(([key, value]) => ` ${key} ${key === "StrictHostKeyChecking" ? checking : value}`), // Relay: the pinned file is the ONE trust source - the snapshot's own // refs must not ride beside it, and neither may the system-wide DB // (`yes` consults GlobalKnownHostsFile too, so it is /dev/null'd; one @@ -209,6 +278,16 @@ export function renderSshConfigContents(snapshot: SshConnectionSnapshotWire, rel ...snapshot.identityFiles.map((f) => ` IdentityFile ${configPathValue(f)}`), ...snapshot.certificateFiles.map((f) => ` CertificateFile ${configPathValue(f)}`), ]), + // User-declared forwards (spec 2026-10-15): a separate exact-Host section, + // never inside the `Host *` policy - the pattern is the whole point, it + // marks the final connection and only the final one. + ...(rows.length > 0 + ? [ + "# User-declared port forwards, approved at launch (spec 2026-10-15).", + `Host ${snapshot.host}`, + ...rows.map(forwardLine), + ] + : []), "", ]; return lines.join("\n"); From 46e67db194873627a299b4c93ef84cf4d52b8dbe Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 15:05:55 -0700 Subject: [PATCH 04/35] feat(ssh): launch accepts forward rows; per-node wide-bind setting --- .../set-node-ssh-wide-forwards.route.test.ts | 144 +++++++++++++++ apps/server/api/src/api/nodes/node-view.ts | 5 + .../api/nodes/set-node-ssh-enabled.route.ts | 48 ++++- .../src/api/ssh/__tests__/ssh-routes.test.ts | 166 ++++++++++++++++++ apps/server/api/src/api/ssh/launch.route.ts | 56 +++++- apps/server/api/src/db/migrate.ts | 2 + .../migrations/0055-node-ssh-wide-forwards.ts | 18 ++ .../0055-node-ssh-wide-forwards.test.ts | 72 ++++++++ .../src/db/repositories/nodes.repository.ts | 25 +++ .../server/api/src/db/types/nodes.db-types.ts | 8 + .../nodes/__tests__/remote-launcher.test.ts | 1 + .../api/src/services/nodes/ssh-enabled.ts | 26 +++ .../api/src/services/ssh-launch.service.ts | 75 +++++++- packages/backend-errors/src/error-codes.ts | 12 ++ 14 files changed, 651 insertions(+), 7 deletions(-) create mode 100644 apps/server/api/src/api/nodes/__tests__/set-node-ssh-wide-forwards.route.test.ts create mode 100644 apps/server/api/src/db/migrations/0055-node-ssh-wide-forwards.ts create mode 100644 apps/server/api/src/db/migrations/__tests__/0055-node-ssh-wide-forwards.test.ts diff --git a/apps/server/api/src/api/nodes/__tests__/set-node-ssh-wide-forwards.route.test.ts b/apps/server/api/src/api/nodes/__tests__/set-node-ssh-wide-forwards.route.test.ts new file mode 100644 index 000000000..3cdd54793 --- /dev/null +++ b/apps/server/api/src/api/nodes/__tests__/set-node-ssh-wide-forwards.route.test.ts @@ -0,0 +1,144 @@ +import { afterAll, beforeAll, describe, expect, it } from "bun:test"; +import { hashPassword } from "better-auth/crypto"; +import { nodesRoutes } from "@/api/nodes/index.js"; +import { db } from "@/db/index.js"; +import { NodeSharesRepository } from "@/db/repositories/node-shares.repository.js"; +import { UsersRepository } from "@/db/repositories/users.repository.js"; +import { LOCAL_NODE_ID } from "@/db/types/nodes.db-types.js"; +import { ensureLocalNode } from "@/services/nodes/seed-local.js"; +import { deleteUserByEmailOrId, setupAuthTables, signIn } from "../../__tests__/helpers/auth-tables.js"; + +/** + * `PUT /api/nodes/:id/ssh-wide-forwards` (spec 2026-10-15 §6) — the per-node + * wide-bind allowance for SSH port forwards, opt-in and off by default, + * cloned from the SSH switch down to the gate: owner (`canManage`, admin on + * `local`), deliberately not the `edit` grantee's door. + * + * The route performs no write and records no audit itself — + * `setNodeSshWideForwards` is the one change-site — so this file asserts the + * ROUTE wired the acting human into that row. + */ +describe("node ssh-wide-forwards route", () => { + const pw = "ssh-wide-1"; + const ownerEmail = `wf-owner-${crypto.randomUUID()}@subshell.local`; + const editorEmail = `wf-editor-${crypto.randomUUID()}@subshell.local`; + const otherEmail = `wf-other-${crypto.randomUUID()}@subshell.local`; + const adminEmail = `wf-admin-${crypto.randomUUID()}@subshell.local`; + let ownerId: string; + let editorId: string; + let ownerCookie: string; + let editorCookie: string; + let otherCookie: string; + let adminCookie: string; + const NODE = `n-wf-${crypto.randomUUID()}`; + + async function mkUser(email: string, role: "user" | "admin" = "user"): Promise { + return await new UsersRepository(db).createUser({ + email, + name: email, + passwordHash: await hashPassword(pw), + role, + }); + } + + async function wfAudit(): Promise> { + const rows = await db + .selectFrom("auditEvents") + .select(["id", "actorUserId", "metadataJson"]) + .where("targetId", "=", NODE) + .where("action", "=", "node.ssh_wide_forwards.update") + .execute(); + return new Map(rows.map((r) => [r.id, { actorUserId: r.actorUserId, metadataJson: r.metadataJson }])); + } + + function put(nodeId: string, cookie: string, on: boolean) { + return nodesRoutes.fetch( + new Request(`http://localhost:3080/api/nodes/${nodeId}/ssh-wide-forwards`, { + method: "PUT", + headers: { "content-type": "application/json", cookie: `better-auth.session_token=${cookie}` }, + body: JSON.stringify({ on }), + }), + ); + } + + beforeAll(async () => { + await setupAuthTables(); + ownerId = await mkUser(ownerEmail); + editorId = await mkUser(editorEmail); + await mkUser(otherEmail); + await mkUser(adminEmail, "admin"); + ownerCookie = await signIn(ownerEmail, pw); + editorCookie = await signIn(editorEmail, pw); + otherCookie = await signIn(otherEmail, pw); + adminCookie = await signIn(adminEmail, pw); + await ensureLocalNode(db); + + const now = new Date().toISOString(); + await db + .insertInto("nodes") + .values({ + id: NODE, + ownerUserId: ownerId, + name: NODE, + kind: "agent", + status: "offline", + createdAt: now, + updatedAt: now, + } as never) + .execute(); + await new NodeSharesRepository(db).replaceForNode(NODE, [{ granteeUserId: editorId, permission: "edit" }], ownerId); + }); + + afterAll(async () => { + await db.deleteFrom("nodes").where("id", "=", NODE).execute(); + await db + .updateTable("nodes") + .set({ sshWideForwards: 0 } as never) + .where("id", "=", LOCAL_NODE_ID) + .execute(); + for (const email of [ownerEmail, editorEmail, otherEmail, adminEmail]) await deleteUserByEmailOrId(email); + }); + + it("defaults off: a fresh node's view carries false", async () => { + const res = await put(NODE, ownerCookie, false); + expect(res.status).toBe(200); + expect(((await res.json()) as { sshWideForwards: boolean }).sshWideForwards).toBe(false); + }); + + it("the owner turns it on, and the answer is the node view carrying the flag", async () => { + const res = await put(NODE, ownerCookie, true); + expect(res.status).toBe(200); + expect(((await res.json()) as { sshWideForwards: boolean }).sshWideForwards).toBe(true); + }); + + it("names the acting human and carries ONLY {on} in the audit row", async () => { + await put(NODE, ownerCookie, false); + const before = await wfAudit(); + + expect((await put(NODE, ownerCookie, true)).status).toBe(200); + const added = [...(await wfAudit())].filter(([id]) => !before.has(id)); + expect(added).toHaveLength(1); + expect(added[0][1].actorUserId).toBe(ownerId); + expect(added[0][1].metadataJson).toBe('{"on":true}'); + }); + + it("an edit grantee is refused — it is not their machine to widen", async () => { + expect((await put(NODE, editorCookie, true)).status).toBe(403); + }); + + it("a stranger gets 404, never 403 — an invisible node stays invisible", async () => { + expect((await put(NODE, otherCookie, true)).status).toBe(404); + }); + + it("answers 404 for an unknown node", async () => { + expect((await put(`n-missing-${crypto.randomUUID()}`, ownerCookie, true)).status).toBe(404); + }); + + it("an admin is refused on a node they do not own, and allowed on `local`", async () => { + expect((await put(NODE, adminCookie, true)).status).toBe(403); + expect((await put(LOCAL_NODE_ID, otherCookie, true)).status).toBe(403); + const res = await put(LOCAL_NODE_ID, adminCookie, true); + expect(res.status).toBe(200); + expect(((await res.json()) as { sshWideForwards: boolean }).sshWideForwards).toBe(true); + }); +}); diff --git a/apps/server/api/src/api/nodes/node-view.ts b/apps/server/api/src/api/nodes/node-view.ts index a57663fbf..8d2d6dede 100644 --- a/apps/server/api/src/api/nodes/node-view.ts +++ b/apps/server/api/src/api/nodes/node-view.ts @@ -160,6 +160,10 @@ export const NodeViewSchema = t.Object({ description: "Whether this machine may be used for Subshell SSH (outbound ssh and serving its keys). Opt-in per node, off by default on every row including the control-plane host; the launch gate refuses SSH here while false, whoever asks", }), + sshWideForwards: t.Boolean({ + description: + "Whether SSH port forwards on this machine may bind non-loopback addresses. Off by default: forwards bind loopback only, and widening turns the machine into a router for whatever network reaches the bound port. The owner's decision, refused at launch while false", + }), held: t.Nullable( t.Object({ reason: t.Union([t.Literal("below-floor"), t.Literal("protocol-mismatch"), t.Literal("encryption-required")], { @@ -450,6 +454,7 @@ function nodeViewBase( maintenanceAt: row.maintenanceAt, maintenanceSource: row.maintenanceSource, sshEnabled: row.sshEnabled === 1, + sshWideForwards: row.sshWideForwards === 1, // Read from the LIVE registry rather than the row, for the same reason // `runtime` is: being held is a fact about a socket that exists right now, // and a column would go stale the moment the process ends. It is NOT diff --git a/apps/server/api/src/api/nodes/set-node-ssh-enabled.route.ts b/apps/server/api/src/api/nodes/set-node-ssh-enabled.route.ts index 3e23d5435..2422eb414 100644 --- a/apps/server/api/src/api/nodes/set-node-ssh-enabled.route.ts +++ b/apps/server/api/src/api/nodes/set-node-ssh-enabled.route.ts @@ -7,7 +7,14 @@ import { db } from "@/db/index.js"; import { NodesRepository } from "@/db/repositories/nodes.repository.js"; import { apiErrorBody } from "@/lib/api-error.js"; import { apiModels } from "@/schema/index.js"; -import { setNodeSshEnabled } from "@/services/nodes/ssh-enabled.js"; +import { setNodeSshEnabled, setNodeSshWideForwards } from "@/services/nodes/ssh-enabled.js"; + +const SshWideForwardsBodySchema = t.Object({ + on: t.Boolean({ + description: + "Allow SSH port forwards on this machine to bind non-loopback addresses. Off by default: forwards bind loopback only. Owner only; admin on the control-plane host.", + }), +}); const SshEnabledBodySchema = t.Object({ on: t.Boolean({ @@ -79,4 +86,43 @@ export const setNodeSshEnabledRoute = new Elysia() "Allow or refuse Subshell SSH on a node (owner only; admin on the control-plane host). Off by default; enabling lets the plane dial out of and serve keys from this machine.", }, }, + ) + // The wide-bind allowance rides THIS instance rather than a module of its + // own for a measured reason: a second mounted route module of this shape + // tips the Elysia `App` inference into TS2589 (the depth ceiling already + // recorded for new route files; `auth-rate-limit.route.ts` is the fold + // precedent). The acts are one family anyway, owner-only SSH flags on the + // machine's row, each written at exactly one change-site. + .put( + "/:id/ssh-wide-forwards", + async ({ params, body, user, actor, status }) => { + requireCookieActor(actor, "Node SSH wide-forwards settings are restricted to browser sessions"); + const gate = await loadNodeGate(user.id, params.id); + if (!gate) { + return status(404, apiErrorBody({ code: BackendErrorCodes.NOT_FOUND_ERROR, message: "Node not found" })); + } + if (!gate.canManage) throw new ForbiddenError(); + + await setNodeSshWideForwards({ nodeId: gate.row.id, on: body.on, actorUserId: user.id }); + // The same re-read rule as the switch above: the view renders the row. + const row = (await new NodesRepository(db).findById(gate.row.id)) ?? gate.row; + return await toNodeView(row, gate.access, gate.isAdmin, gate.granted); + }, + { + params: t.Object({ id: t.String({ description: "Node id" }) }), + body: SshWideForwardsBodySchema, + response: { + 200: NodeViewSchema, + 400: "ApiErrorResponse", + 401: "ApiErrorResponse", + 403: "ApiErrorResponse", + 404: "ApiErrorResponse", + }, + detail: { + operationId: "setNodeSshWideForwards", + tags: ["nodes"], + description: + "Allow or refuse non-loopback binds for SSH port forwards on a node (owner only; admin on the control-plane host). Off by default: forwards bind loopback only, and enabling lets launches on this machine bind addresses other machines can reach. Turning it off refuses future wide binds; already-running forwards ride the connections approved when they started.", + }, + }, ); diff --git a/apps/server/api/src/api/ssh/__tests__/ssh-routes.test.ts b/apps/server/api/src/api/ssh/__tests__/ssh-routes.test.ts index 7bfad2fd7..738636c67 100644 --- a/apps/server/api/src/api/ssh/__tests__/ssh-routes.test.ts +++ b/apps/server/api/src/api/ssh/__tests__/ssh-routes.test.ts @@ -543,6 +543,172 @@ describe("POST /api/ssh/launch", () => { }); }); +describe("POST /api/ssh/launch with port forwards (spec 2026-10-15)", () => { + const localRow = { kind: "local", listenPort: 5432, bindHost: null, targetHost: "db.internal", targetPort: 5432 }; + const wideRow = { kind: "local", listenPort: 8080, bindHost: "10.0.0.5", targetHost: "api.internal", targetPort: 80 }; + + /** A node whose owner allowed wide binds; the flag is restored by the caller. */ + async function wideOn(nodeId: string): Promise { + await nodes.setSshWideForwards(nodeId, { on: true }); + } + async function wideOff(nodeId: string): Promise { + await nodes.setSshWideForwards(nodeId, { on: false }); + } + + function forwardingScript() { + return attachScriptedNode(NODE_ON, { + ssh_resolve_config: () => ({ accepted: true, snapshot: ACCEPTED }), + launch: ok, + stat_dir: statDirEcho, + }); + } + + it("a malformed row list is a 400 before the machine is asked anything", async () => { + const scripted = silentScript(NODE_ON); + try { + // dynamic-with-target: a contradiction the grammar refuses whole. + const res = await launch( + { node: NODE_ON, destination: "work", forwards: [{ ...localRow, kind: "dynamic" }] }, + ownerCookie, + ); + expect(res.status).toBe(400); + expect(((await res.json()) as { code: string }).code).toBe(BackendErrorCodes.CONFIG_INVALID); + expect(scripted.cmdTypes()).toEqual([]); + } finally { + scripted.detach(); + } + }); + + it("a duplicate listen end is refused (two rows, one bind: a bug, not a configuration)", async () => { + const scripted = silentScript(NODE_ON); + try { + const res = await launch( + { node: NODE_ON, destination: "work", forwards: [localRow, { ...localRow, targetHost: "other.internal" }] }, + ownerCookie, + ); + expect(res.status).toBe(400); + expect(scripted.cmdTypes()).toEqual([]); + } finally { + scripted.detach(); + } + }); + + it("a wide bind is refused before any resolve while the machine's setting is off", async () => { + const scripted = silentScript(NODE_ON); + try { + const res = await launch({ node: NODE_ON, destination: "work", forwards: [wideRow] }, ownerCookie); + expect(res.status).toBe(403); + expect(((await res.json()) as { code: string }).code).toBe(BackendErrorCodes.SSH_FORWARD_BIND_DENIED); + expect(scripted.cmdTypes()).toEqual([]); + } finally { + scripted.detach(); + } + }); + + it("a loopback forward launches, renders into the exact-destination block, and reads in the audit", async () => { + const scripted = forwardingScript(); + const beforeIds = new Set((await auditRows()).map((r) => r.id)); + try { + const res = await launch({ node: NODE_ON, destination: "work", forwards: [localRow] }, ownerCookie); + expect(res.status).toBe(201); + const { subshell } = (await res.json()) as { subshell: { id: string } }; + createdSubshellIds.push(subshell.id); + + const cmd = scripted.cmdsOf("launch")[0] as { + ssh?: { fileContent: string }; + argv?: string[]; + preset?: { flags?: string[] }; + }; + expect(cmd.ssh?.fileContent).toBe(renderSshConfigContents(ACCEPTED as never, undefined, [localRow] as never)); + expect(cmd.ssh?.fileContent).toContain(" LocalForward 127.0.0.1:5432 db.internal:5432"); + expect(cmd.ssh?.fileContent).not.toContain("ClearAllForwardings"); + // The forward rows must NOT ride the option argv (the policy file would + // clear them there; the config block is the one honest channel). + const flags = cmd.preset?.flags ?? []; + expect(flags.join(" ")).not.toMatch(/LocalForward|-L /); + + const added = (await auditRows()).filter((r) => !beforeIds.has(r.id)); + expect(added).toHaveLength(1); + expect(added[0].metadata).toEqual({ + nodeId: NODE_ON, + destination: CANON, + subshellId: subshell.id, + forwards: [{ kind: "local", listen: "127.0.0.1:5432", target: "db.internal:5432" }], + }); + } finally { + scripted.detach(); + } + }); + + it("a wide bind launches once the owner allowed it, and the row carries the written address", async () => { + const scripted = forwardingScript(); + await wideOn(NODE_ON); + try { + const res = await launch({ node: NODE_ON, destination: "work", forwards: [localRow, wideRow] }, ownerCookie); + expect(res.status).toBe(201); + const { subshell } = (await res.json()) as { subshell: { id: string } }; + createdSubshellIds.push(subshell.id); + const cmd = scripted.cmdsOf("launch")[0] as { ssh?: { fileContent: string } }; + expect(cmd.ssh?.fileContent).toContain(" LocalForward 10.0.0.5:8080 api.internal:80"); + } finally { + await wideOff(NODE_ON); + scripted.detach(); + } + }); + + it("forwards on a connection whose jump names the destination itself are refused after resolve", async () => { + const snap = snapshot({ proxyJumps: [{ host: "EXAMPLE.test", user: null, port: 22 }] }); + const scripted = attachScriptedNode(NODE_ON, { + ssh_resolve_config: () => ({ accepted: true, snapshot: snap }), + launch: ok, + stat_dir: statDirEcho, + }); + try { + const res = await launch({ node: NODE_ON, destination: "work", forwards: [localRow] }, ownerCookie); + expect(res.status).toBe(400); + expect(((await res.json()) as { message: string }).message).toContain("jump hop is the destination itself"); + expect(scripted.countOf("launch")).toBe(0); + } finally { + scripted.detach(); + } + }); + + it("a dynamic row launches with no target, and the audit says so", async () => { + const scripted = forwardingScript(); + const beforeIds = new Set((await auditRows()).map((r) => r.id)); + const dynamic = { kind: "dynamic", listenPort: 1080, bindHost: null, targetHost: null, targetPort: null }; + try { + const res = await launch({ node: NODE_ON, destination: "work", forwards: [dynamic] }, ownerCookie); + expect(res.status).toBe(201); + const { subshell } = (await res.json()) as { subshell: { id: string } }; + createdSubshellIds.push(subshell.id); + const cmd = scripted.cmdsOf("launch")[0] as { ssh?: { fileContent: string } }; + expect(cmd.ssh?.fileContent).toContain(" DynamicForward 127.0.0.1:1080"); + const added = (await auditRows()).filter((r) => !beforeIds.has(r.id)); + expect(added[0].metadata).toMatchObject({ + forwards: [{ kind: "dynamic", listen: "127.0.0.1:1080" }], + }); + } finally { + scripted.detach(); + } + }); + + it("a remote row with no bind host renders bare (D's policy spells it)", async () => { + const scripted = forwardingScript(); + const remote = { kind: "remote", listenPort: 9090, bindHost: null, targetHost: "dev.local", targetPort: 3000 }; + try { + const res = await launch({ node: NODE_ON, destination: "work", forwards: [remote] }, ownerCookie); + expect(res.status).toBe(201); + const { subshell } = (await res.json()) as { subshell: { id: string } }; + createdSubshellIds.push(subshell.id); + const cmd = scripted.cmdsOf("launch")[0] as { ssh?: { fileContent: string } }; + expect(cmd.ssh?.fileContent).toContain(" RemoteForward 9090 dev.local:3000"); + } finally { + scripted.detach(); + } + }); +}); + describe("/api/ssh/recent-destinations", () => { it("GET lists only the caller's recent destinations", async () => { // Owner A saves; owner B (a second human with their own node) must see diff --git a/apps/server/api/src/api/ssh/launch.route.ts b/apps/server/api/src/api/ssh/launch.route.ts index becb58df8..9a90cd3f3 100644 --- a/apps/server/api/src/api/ssh/launch.route.ts +++ b/apps/server/api/src/api/ssh/launch.route.ts @@ -1,4 +1,9 @@ -import { normalizeLabel, SSH_ROSTER_MAX_IDENTITIES } from "@internal/subshell-protocol"; +import { + normalizeLabel, + SSH_MAX_FORWARD_ROWS, + SSH_NAME_MAX_CHARS, + SSH_ROSTER_MAX_IDENTITIES, +} from "@internal/subshell-protocol"; import { Elysia, t } from "elysia"; import { authGuard, requireCookieActor } from "@/api/auth-guard.js"; import { SubshellSchema } from "@/api/models.js"; @@ -12,6 +17,45 @@ import { contextPlugin } from "@/plugins/context.plugin.js"; import { apiModels } from "@/schema/index.js"; import { sshLaunch } from "@/services/ssh-launch.service.js"; +/** + * One forward row's ENVELOPE. The shape lives here so the OpenAPI docs read + * honestly, and deliberately NOT the detailed grammar: `parseSshForwardRows` + * in the protocol package is the single authority (host forms, kind rules, + * duplicate listens), because the renderer re-runs that parser at the last + * station before bytes and a second grammar here would be two that drift. + */ +const SshForwardRowSchema = t.Object({ + kind: t.Union([t.Literal("local"), t.Literal("remote"), t.Literal("dynamic")], { + description: + "local: bind a port on the connecting machine that reaches a host the destination can see; remote: ask the destination to bind a port that reaches a host the connecting machine can see; dynamic: a SOCKS proxy on the connecting machine", + }), + listenPort: t.Numeric({ + minimum: 1, + maximum: 65535, + description: "Port to bind (always explicit; no auto ports). Integer-ness belongs to the protocol parser", + }), + bindHost: t.Nullable( + t.String({ + minLength: 1, + maxLength: SSH_NAME_MAX_CHARS, + description: + "Address to bind (hostname, IPv4, or bracketed IPv6). Null means loopback for local/dynamic, and the destination's own policy for remote", + }), + ), + targetHost: t.Optional( + t.Nullable( + t.String({ + minLength: 1, + maxLength: SSH_NAME_MAX_CHARS, + description: "Host to reach through the connection; required for local/remote, null for dynamic", + }), + ), + ), + targetPort: t.Optional( + t.Nullable(t.Numeric({ minimum: 1, maximum: 65535, description: "Port on the target host; same kind rule" })), + ), +}); + /** * `POST /api/ssh/launch` `{node, destination, name?}` — open an interactive * SSH pane to one destination (spec 2026-10-07 §5): gate (current launch access and readiness, before @@ -47,6 +91,9 @@ export const sshLaunchRoute = new Elysia() fingerprints: body.fingerprints, authentication: body.authentication, localKeyId: body.localKeyId, + // The protocol parser owns the detail (shape, kind rules, dupes); + // this handler only carries the envelope through. + forwards: body.forwards, sessionId: session?.id, subshells: ctx.services.subshells, }); @@ -81,6 +128,13 @@ export const sshLaunchRoute = new Elysia() "RELAY MODE: the key home machine whose agent signs for this connection; absent uses the connecting machine's own keys", }), ), + forwards: t.Optional( + t.Array(SshForwardRowSchema, { + maxItems: SSH_MAX_FORWARD_ROWS, + description: + "User-declared port forwards this connection carries (spec 2026-10-15). They live and die with the pane; a non-loopback bind host needs the machine's wide-forwards setting on", + }), + ), }), response: { 201: t.Object({ subshell: SubshellSchema }), diff --git a/apps/server/api/src/db/migrate.ts b/apps/server/api/src/db/migrate.ts index 772fb930f..212cd4302 100644 --- a/apps/server/api/src/db/migrate.ts +++ b/apps/server/api/src/db/migrate.ts @@ -53,6 +53,7 @@ import * as sshGrantsMigration from "@/db/migrations/0050-ssh-grants.js"; import * as sshRequestDestinationMigration from "@/db/migrations/0051-ssh-request-destination.js"; import * as subshellKeyHomeMigration from "@/db/migrations/0052-subshell-key-home.js"; import * as sshPresetsMigration from "@/db/migrations/0054-ssh-presets.js"; +import * as sshWideForwardsMigration from "@/db/migrations/0055-node-ssh-wide-forwards.js"; import * as dropSshGrantsMigration from "./migrations/0053-drop-ssh-grants.js"; /** @@ -163,6 +164,7 @@ export async function runMigrations(): Promise { "0052-subshell-key-home": subshellKeyHomeMigration, "0053-drop-ssh-grants": dropSshGrantsMigration, "0054-ssh-presets": sshPresetsMigration, + "0055-node-ssh-wide-forwards": sshWideForwardsMigration, }; }, }, diff --git a/apps/server/api/src/db/migrations/0055-node-ssh-wide-forwards.ts b/apps/server/api/src/db/migrations/0055-node-ssh-wide-forwards.ts new file mode 100644 index 000000000..145c5bf4c --- /dev/null +++ b/apps/server/api/src/db/migrations/0055-node-ssh-wide-forwards.ts @@ -0,0 +1,18 @@ +import type { Kysely } from "kysely"; + +// Adds the per-node wide-bind allowance for SSH port forwards +// (docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md §6). Default +// 0 keeps the whole fleet loopback-only after an upgrade: a forward that binds +// a non-loopback address turns the machine into a router for whatever network +// reaches that port, and only the machine's owner decides that is acceptable +// there. Same default-0 doctrine as the SSH capability gate (0047). +export async function up(db: Kysely): Promise { + await db.schema + .alterTable("nodes") + .addColumn("ssh_wide_forwards", "integer", (col) => col.notNull().defaultTo(0)) + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema.alterTable("nodes").dropColumn("ssh_wide_forwards").execute(); +} diff --git a/apps/server/api/src/db/migrations/__tests__/0055-node-ssh-wide-forwards.test.ts b/apps/server/api/src/db/migrations/__tests__/0055-node-ssh-wide-forwards.test.ts new file mode 100644 index 000000000..d4e41bf7c --- /dev/null +++ b/apps/server/api/src/db/migrations/__tests__/0055-node-ssh-wide-forwards.test.ts @@ -0,0 +1,72 @@ +import { afterAll, beforeAll, describe, expect, it } from "bun:test"; +import { Kysely, sql } from "kysely"; +import { BunSqliteDialect } from "kysely-bun-sqlite-dialect"; +import * as initMigration from "@/db/migrations/0001-init.js"; +import * as remoteOpsMigration from "@/db/migrations/0003-remote-ops.js"; +import * as profileDefaultFlagMigration from "@/db/migrations/0010-profile-default-flag.js"; +import * as nodesMigration from "@/db/migrations/0017-nodes.js"; +import * as sshEnabledMigration from "@/db/migrations/0047-node-ssh-enabled.js"; +import * as wideForwardsMigration from "@/db/migrations/0055-node-ssh-wide-forwards.js"; +import { openSqliteDatabase } from "@/db/open-database.js"; + +interface MigrationDatabase { + [table: string]: Record; + nodes: Record; +} + +/** + * The wide-bind allowance column (spec 2026-10-15 §6). Default 0 is the + * upgrade story: every machine, the control-plane host included, stays + * loopback-only until its owner widens it. + */ +describe("migration 0055-node-ssh-wide-forwards", () => { + let dbFile: string; + let db: Kysely; + + beforeAll(async () => { + dbFile = `/tmp/subshell-0055-${Math.random().toString(36).slice(2)}.db`; + db = new Kysely({ dialect: new BunSqliteDialect({ database: openSqliteDatabase(dbFile) }) }); + await initMigration.up(db); + await remoteOpsMigration.up(db); + await profileDefaultFlagMigration.up(db); + await nodesMigration.up(db); + await sshEnabledMigration.up(db); + // Written BEFORE the column exists - the upgrade case this test is about. + await db + .insertInto("nodes") + .values({ + id: "pre-existing", + owner_user_id: "u1", + name: "pre-existing", + kind: "agent", + created_at: "2026-10-15T00:00:00.000Z", + updated_at: "2026-10-15T00:00:00.000Z", + }) + .execute(); + await wideForwardsMigration.up(db); + }); + + afterAll(async () => { + await db.destroy(); + }); + + it("adds the column", async () => { + const cols = await sql<{ name: string }>`SELECT name FROM pragma_table_info('nodes')`.execute(db); + expect(cols.rows.map((c) => c.name)).toContain("ssh_wide_forwards"); + }); + + it("rows written before the migration read 0 (loopback stays the floor after an upgrade)", async () => { + const row = await db + .selectFrom("nodes") + .select("ssh_wide_forwards") + .where("id", "=", "pre-existing") + .executeTakeFirst(); + expect(row?.ssh_wide_forwards).toBe(0); + }); + + it("down removes the column", async () => { + await wideForwardsMigration.down(db); + const cols = await sql<{ name: string }>`SELECT name FROM pragma_table_info('nodes')`.execute(db); + expect(cols.rows.map((c) => c.name)).not.toContain("ssh_wide_forwards"); + }); +}); diff --git a/apps/server/api/src/db/repositories/nodes.repository.ts b/apps/server/api/src/db/repositories/nodes.repository.ts index 3fd95ca53..566df61a6 100644 --- a/apps/server/api/src/db/repositories/nodes.repository.ts +++ b/apps/server/api/src/db/repositories/nodes.repository.ts @@ -60,6 +60,7 @@ export class NodesRepository extends BaseRepository { // and the gate is opt-in per machine (spec 4.3); mirror it the same way. sshEnabled: input.sshEnabled ?? 0, sshEnabledAt: input.sshEnabledAt ?? null, + sshWideForwards: 0, createdAt: input.createdAt ?? now, updatedAt: now, }) @@ -275,6 +276,30 @@ export class NodesRepository extends BaseRepository { .executeTakeFirst(); } + /** + * Write the SSH wide-forwards allowance (spec 2026-10-15 §6). No stamp + * column: unlike {@link setSshEnabled} there is no node-side mirror to + * reconcile - the flag is read plane-side at launch, when the rendered + * config is composed, and nothing travels to the machine. + */ + async setSshWideForwards( + id: string, + state: { + /** True = forwards on this machine may bind non-loopback addresses. */ + on: boolean; + }, + ): Promise { + return await this.db + .updateTable("nodes") + .set({ + sshWideForwards: state.on ? 1 : 0, + updatedAt: new Date().toISOString(), + }) + .where("id", "=", id) + .returningAll() + .executeTakeFirst(); + } + /** * The unfinished subshells on one node, as FULL rows. * diff --git a/apps/server/api/src/db/types/nodes.db-types.ts b/apps/server/api/src/db/types/nodes.db-types.ts index 74400a0a8..c45aa5f03 100644 --- a/apps/server/api/src/db/types/nodes.db-types.ts +++ b/apps/server/api/src/db/types/nodes.db-types.ts @@ -101,6 +101,14 @@ export interface NodeTable { * maintenance. */ sshEnabledAt: string | null; + /** + * 1 = this machine's owner allows SSH port forwards to bind NON-loopback + * addresses (docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md + * §6). Default 0: forwards bind loopback only. A wide bind turns the machine + * into a router for whatever network reaches that port, so the decision is + * per machine and belongs to the owner, never to a connection's author. + */ + sshWideForwards: number; /** * JSON mirror of the ssh-fingerprint block from this node's last runtime * report (spec 2026-10-08 §4.5), so the trust card survives an offline node diff --git a/apps/server/api/src/services/nodes/__tests__/remote-launcher.test.ts b/apps/server/api/src/services/nodes/__tests__/remote-launcher.test.ts index a8cb3413a..e650f61e8 100644 --- a/apps/server/api/src/services/nodes/__tests__/remote-launcher.test.ts +++ b/apps/server/api/src/services/nodes/__tests__/remote-launcher.test.ts @@ -174,6 +174,7 @@ function nodeRow(over: Partial = {}): NodeTable { maintenanceAt: null, maintenanceSource: null, sshEnabled: 0, + sshWideForwards: 0, sshEnabledAt: null, // The 0049 mirror slot: NULL until the runtime-report task fills it. sshFingerprint: null, diff --git a/apps/server/api/src/services/nodes/ssh-enabled.ts b/apps/server/api/src/services/nodes/ssh-enabled.ts index d461513a3..69afbca9e 100644 --- a/apps/server/api/src/services/nodes/ssh-enabled.ts +++ b/apps/server/api/src/services/nodes/ssh-enabled.ts @@ -48,6 +48,32 @@ export async function setNodeSshEnabled(write: SshEnabledWrite): Promise { pushSetSshEnabledBestEffort(write.nodeId, { on: write.on, changedAt: write.changedAt }); } +/** + * Flip one node's wide-bind allowance for SSH port forwards (spec 2026-10-15 + * §6): row and audit in one function, same one-change-site posture as + * {@link setNodeSshEnabled}. The differences are all in what does NOT happen: + * no stamp, no push, no mirror, and no relay cut - the flag is read + * plane-side when a launch composes its config, so turning it off refuses + * every FUTURE wide bind while already-running forwards ride the connection + * that was approved when they started (the same honesty the SSH switch + * carries about already-authenticated sessions). + */ +export async function setNodeSshWideForwards(write: { + nodeId: string; + on: boolean; + actorUserId: string; +}): Promise { + const { repos } = getRequestlessContext(); + await repos.nodes.setSshWideForwards(write.nodeId, { on: write.on }); + await audit({ + actorUserId: write.actorUserId, + action: "node.ssh_wide_forwards.update", + targetType: "node", + targetId: write.nodeId, + metadataJson: JSON.stringify({ on: write.on }), + }); +} + /** The node-row fields {@link decideSshEnabled} reads — nothing else is relevant. */ type SshRow = Pick; diff --git a/apps/server/api/src/services/ssh-launch.service.ts b/apps/server/api/src/services/ssh-launch.service.ts index 01a2f591e..cb26b4835 100644 --- a/apps/server/api/src/services/ssh-launch.service.ts +++ b/apps/server/api/src/services/ssh-launch.service.ts @@ -14,11 +14,13 @@ import { sshOptionTokens, } from "@internal/pane-runtime"; import { + isLoopbackForwardBind, type NodeSshAgentIdentity, type NodeSshAliasListResult, type NodeSshResolveOutcomeWire, parseNodeSshAliasList, parseSshConnectionSnapshot, + parseSshForwardRows, SSH_NAME_MAX_CHARS, type SshConnectionSnapshotWire, type SshForwardWire, @@ -410,6 +412,13 @@ export async function sshLaunch(args: { authentication?: "password"; /** One-use preparation owned by this exact cookie session. */ localKeyId?: string; + /** + * User-declared port forwards (spec 2026-10-15). `unknown` ON PURPOSE: the + * route's t-schema is an envelope, and `parseSshForwardRows` here is the + * authority - the same parser the renderer re-runs at the last station, so + * what composes can never be wider than what the grammar can express. + */ + forwards?: unknown; sessionId?: string; /** The REQUEST-scoped subshells service (the create path is its own). */ subshells: Pick; @@ -431,6 +440,27 @@ export async function sshLaunch(args: { if (!isWireSafeSshDestination(args.destination)) { return codedRefusal(400, BackendErrorCodes.ALIAS_UNSAFE, UNSAFE_DESTINATION_COPY); } + // Port forwards (spec 2026-10-15): parse first (the grammar owns shape), + // then the machine's wide-bind gate (the owner's setting owns policy). Both + // BEFORE any resolve, so a refused list never dials a machine. + let forwards: SshForwardWire[] = []; + if (args.forwards !== undefined) { + const parsed = parseSshForwardRows(args.forwards); + if (parsed === null) + return codedRefusal( + 400, + BackendErrorCodes.CONFIG_INVALID, + "That port-forward list isn't valid. Each forward needs a kind (local, remote, or dynamic), a listen port from 1 to 65535, and a target host and port except for dynamic. Two forwards can't bind the same port.", + ); + forwards = parsed; + } + if (forwards.length > 0 && forwards.some((f) => !isLoopbackForwardBind(f)) && row.sshWideForwards !== 1) { + return codedRefusal( + 403, + BackendErrorCodes.SSH_FORWARD_BIND_DENIED, + "This machine only allows port forwards that bind its own loopback address. Change the bind address, or its owner can allow wide forwards in the machine's SSH settings.", + ); + } if (args.fingerprints !== undefined && args.keyHomeNodeId === undefined) { return codedRefusal( 400, @@ -454,6 +484,16 @@ export async function sshLaunch(args: { const snapshot = resolved.value.snapshot; // parseNodeSshResolveOutcome already narrowed + rebuilt it const destinationProblem = sshExpectedDestinationProblem(snapshot, args.expectedDestination); if (destinationProblem) return codedRefusal(400, BackendErrorCodes.CONFIG_INVALID, destinationProblem); + if (forwards.length > 0 && snapshot.proxyJumps.some((h) => h.host.toLowerCase() === snapshot.host.toLowerCase())) { + // The forward block renders under the exact destination host; a hop named + // the same would match the child too and hand it a forward (the renderer + // throws on this as the guard beneath; the human hears it here first). + return codedRefusal( + 400, + BackendErrorCodes.CONFIG_INVALID, + "Port forwards can't ride a connection whose jump hop is the destination itself. Remove the jump or the forwards.", + ); + } if (args.keyHomeNodeId !== undefined) { const refusal = relaySnapshotRefusal(snapshot); if (refusal) return refusal; @@ -510,6 +550,10 @@ export async function sshLaunch(args: { // signed relay-open just wrote beside it; a direct launch renders the M1 // accept-new bytes untouched. ...(relay || localKey ? { hostPinPath: buildSshKnownHostsPath(dataDir, subshellId) } : {}), + // Approved rows (spec 2026-10-15): the compose re-validates and renders + // them into the exact-destination block; rowless launches keep today's + // bytes, ClearAllForwardings included. + ...(forwards.length > 0 ? { forwards } : {}), }); // Relay mode RE-POINTS the scoped exception (spec §5.2): the socket the // pane's ssh authenticates through is B's proxy the broker just verified, @@ -581,11 +625,32 @@ export async function sshLaunch(args: { targetType: "node", targetId: row.id, // Only session ids and destination metadata enter the success audit. - metadataJson: JSON.stringify( - relay - ? { nodeId: row.id, destination, subshellId: created.id, relayRef: relay.ref } - : { nodeId: row.id, destination, subshellId: created.id }, - ), + // The forward summary (spec 2026-10-15 §8) is the same class of fact: + // canonical names and ports the user approved, no config text and no key + // material, bounded by the row cap. + metadataJson: JSON.stringify({ + nodeId: row.id, + destination, + subshellId: created.id, + ...(relay ? { relayRef: relay.ref } : {}), + ...(forwards.length > 0 + ? { + forwards: forwards.map((f) => ({ + kind: f.kind, + // What the user wrote; "127.0.0.1:port" when they wrote loopback + // implicitly, and a bare port for a remote row, whose bind is + // the destination's policy to spell. + listen: + f.bindHost === null + ? f.kind === "remote" + ? String(f.listenPort) + : `127.0.0.1:${f.listenPort}` + : `${f.bindHost}:${f.listenPort}`, + ...(f.kind === "dynamic" ? {} : { target: `${String(f.targetHost)}:${String(f.targetPort)}` }), + })), + } + : {}), + }), }); return ok({ subshellId: created.id }); } diff --git a/packages/backend-errors/src/error-codes.ts b/packages/backend-errors/src/error-codes.ts index 795defe0c..956582153 100644 --- a/packages/backend-errors/src/error-codes.ts +++ b/packages/backend-errors/src/error-codes.ts @@ -289,6 +289,14 @@ export enum BackendErrorCodes { * nowhere in it, and the interactive pane is untouched. */ SSH_UPGRADE_FAILED = "SSH_UPGRADE_FAILED", + /** + * A launch asked for a port forward that binds a non-loopback address on a + * machine whose owner has not allowed wide binds (spec 2026-10-15 §6): the + * forward would turn the machine into a router for whatever network reaches + * that port. Refused before anything composes; the remedy the message names + * is the machine's SSH setting, or a loopback bind. + */ + SSH_FORWARD_BIND_DENIED = "SSH_FORWARD_BIND_DENIED", } /** @@ -591,4 +599,8 @@ export const BackendErrorCodeDefs = { message: "Subshell setup didn't finish", statusCode: 409, }, + [BackendErrorCodes.SSH_FORWARD_BIND_DENIED]: { + message: "This machine only allows port forwards that bind its own loopback address", + statusCode: 403, + }, }; From 65a13ad5fc83e2b86cc33ceff36b404be9d0befb Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 15:16:21 -0700 Subject: [PATCH 05/35] feat(ssh): SSH presets store declared port forwards, validated at save --- apps/server/api/src/api/ssh-presets.ts | 22 +++- .../src/api/ssh/__tests__/ssh-routes.test.ts | 105 ++++++++++++++++++ apps/server/api/src/api/ssh/launch.route.ts | 47 +------- apps/server/api/src/api/ssh/ssh-views.ts | 69 ++++++++++++ apps/server/api/src/db/migrate.ts | 2 + .../db/migrations/0056-ssh-preset-forwards.ts | 16 +++ .../0056-ssh-preset-forwards.test.ts | 47 ++++++++ .../api/src/db/types/ssh-presets.db-types.ts | 8 ++ .../api/src/services/ssh-presets.service.ts | 19 ++++ 9 files changed, 288 insertions(+), 47 deletions(-) create mode 100644 apps/server/api/src/db/migrations/0056-ssh-preset-forwards.ts create mode 100644 apps/server/api/src/db/migrations/__tests__/0056-ssh-preset-forwards.test.ts diff --git a/apps/server/api/src/api/ssh-presets.ts b/apps/server/api/src/api/ssh-presets.ts index 946f4e78c..97df223cc 100644 --- a/apps/server/api/src/api/ssh-presets.ts +++ b/apps/server/api/src/api/ssh-presets.ts @@ -1,6 +1,11 @@ -import { SSH_FINGERPRINT_PATTERN, SSH_MAX_SELECTED_FINGERPRINTS } from "@internal/subshell-protocol"; +import type { SshForwardWire } from "@internal/subshell-protocol"; +import { + SSH_FINGERPRINT_PATTERN, + SSH_MAX_FORWARD_ROWS, + SSH_MAX_SELECTED_FINGERPRINTS, +} from "@internal/subshell-protocol"; import { t } from "elysia"; -import { SshExpectedDestinationField } from "@/api/ssh/ssh-views.js"; +import { SshExpectedDestinationField, SshForwardRowInputSchema } from "@/api/ssh/ssh-views.js"; import type { SshPresetTable } from "@/db/types/ssh-presets.db-types.js"; const SshPresetFields = { @@ -49,6 +54,15 @@ const SshPresetFields = { }), ), ), + forwards: t.Optional( + t.Nullable( + t.Array(SshForwardRowInputSchema, { + maxItems: SSH_MAX_FORWARD_ROWS, + description: + "Port forwards this preset carries to every launch; validated by the protocol grammar at save, and a non-loopback bind is still refused at launch until the connecting machine allows it. Omitted on update keeps the stored list, null clears it", + }), + ), + ), }; export const CreateSshPresetSchema = t.Object( { @@ -77,6 +91,9 @@ export const SshPresetSchema = t.Object({ fingerprints: t.Array(t.String({ description: "Selected public key fingerprint" }), { description: "Remote key selection, empty for other authentication modes", }), + forwards: t.Array(SshForwardRowInputSchema, { + description: "Declared port forwards, empty when the preset stores none", + }), createdAt: t.String({ description: "Creation timestamp" }), updatedAt: t.String({ description: "Last update timestamp" }), }); @@ -86,5 +103,6 @@ export function sshPresetView(row: SshPresetTable) { ...row, kind: "ssh" as const, fingerprints: row.fingerprints === null ? [] : (JSON.parse(row.fingerprints) as string[]), + forwards: row.forwards === null ? [] : (JSON.parse(row.forwards) as SshForwardWire[]), }; } diff --git a/apps/server/api/src/api/ssh/__tests__/ssh-routes.test.ts b/apps/server/api/src/api/ssh/__tests__/ssh-routes.test.ts index 738636c67..3c60be0e7 100644 --- a/apps/server/api/src/api/ssh/__tests__/ssh-routes.test.ts +++ b/apps/server/api/src/api/ssh/__tests__/ssh-routes.test.ts @@ -709,6 +709,111 @@ describe("POST /api/ssh/launch with port forwards (spec 2026-10-15)", () => { }); }); +describe("SSH preset forwards (spec 2026-10-15 §7)", () => { + const fwd = { kind: "local", listenPort: 5432, bindHost: null, targetHost: "db.internal", targetPort: 5432 }; + const dyn = { kind: "dynamic", listenPort: 1080, bindHost: null, targetHost: null, targetPort: null }; + const presetBody = (name: string, extra: Record = {}) => ({ + kind: "ssh", + name, + nodeId: NODE_ON, + destination: "work", + authentication: "connecting", + fingerprints: [], + ...extra, + }); + + it("a saved list round-trips through the view and the row", async () => { + const scripted = silentScript(NODE_ON); + try { + const created = await sshFetch("/api/presets", { + method: "POST", + cookie: ownerCookie, + body: presetBody("Forward preset", { forwards: [fwd, dyn] }), + }); + expect(created.status).toBe(200); + const view = (await created.json()) as { id: string; forwards: unknown[] }; + expect(view.forwards).toEqual([fwd, dyn]); + const row = await db.selectFrom("sshPresets").select("forwards").where("id", "=", view.id).executeTakeFirst(); + expect(JSON.parse(String(row?.forwards))).toEqual([fwd, dyn]); + await sshFetch(`/api/presets/${view.id}`, { method: "DELETE", cookie: ownerCookie }); + } finally { + scripted.detach(); + } + }); + + it("omission keeps the stored list, null clears it, an empty list clears it", async () => { + const scripted = silentScript(NODE_ON); + try { + const created = await sshFetch("/api/presets", { + method: "POST", + cookie: ownerCookie, + body: presetBody("Forward keeps", { forwards: [fwd] }), + }); + const { id } = (await created.json()) as { id: string }; + const put = (extra: Record) => + sshFetch(`/api/presets/${id}`, { + method: "PUT", + cookie: ownerCookie, + body: { + name: "Forward keeps", + nodeId: NODE_ON, + destination: CANON, + authentication: "connecting", + fingerprints: [], + ...extra, + }, + }); + const kept = await put({}); + expect(kept.status).toBe(200); + expect(((await kept.json()) as { forwards: unknown[] }).forwards).toEqual([fwd]); + const cleared = await put({ forwards: null }); + expect(((await cleared.json()) as { forwards: unknown[] }).forwards).toEqual([]); + const row = await db.selectFrom("sshPresets").select("forwards").where("id", "=", id).executeTakeFirst(); + expect(row?.forwards).toBeNull(); + await sshFetch(`/api/presets/${id}`, { method: "DELETE", cookie: ownerCookie }); + } finally { + scripted.detach(); + } + }); + + it("a malformed list is refused at save, before anything writes", async () => { + const scripted = silentScript(NODE_ON); + try { + const res = await sshFetch("/api/presets", { + method: "POST", + cookie: ownerCookie, + body: presetBody("Bad forwards", { forwards: [{ kind: "tunnel", listenPort: 1, bindHost: null }] }), + }); + expect(res.status).toBe(400); + const rows = await db.selectFrom("sshPresets").select("id").where("name", "=", "Bad forwards").execute(); + expect(rows).toHaveLength(0); + } finally { + scripted.detach(); + } + }); + + it("a wide-bind row SAVES fine and refuses at launch until the machine allows it", async () => { + const scripted = silentScript(NODE_ON); + try { + const wide = { kind: "remote", listenPort: 9090, bindHost: "0.0.0.0", targetHost: "dev.local", targetPort: 3000 }; + const created = await sshFetch("/api/presets", { + method: "POST", + cookie: ownerCookie, + body: presetBody("Wide preset", { forwards: [wide] }), + }); + expect(created.status).toBe(200); + const { id } = (await created.json()) as { id: string }; + // The save stores the wish; the launch enforces the machine's answer. + const res = await launch({ node: NODE_ON, destination: "work", forwards: [wide] }, ownerCookie); + expect(res.status).toBe(403); + expect(((await res.json()) as { code: string }).code).toBe(BackendErrorCodes.SSH_FORWARD_BIND_DENIED); + await sshFetch(`/api/presets/${id}`, { method: "DELETE", cookie: ownerCookie }); + } finally { + scripted.detach(); + } + }); +}); + describe("/api/ssh/recent-destinations", () => { it("GET lists only the caller's recent destinations", async () => { // Owner A saves; owner B (a second human with their own node) must see diff --git a/apps/server/api/src/api/ssh/launch.route.ts b/apps/server/api/src/api/ssh/launch.route.ts index 9a90cd3f3..05bf8b348 100644 --- a/apps/server/api/src/api/ssh/launch.route.ts +++ b/apps/server/api/src/api/ssh/launch.route.ts @@ -1,15 +1,11 @@ -import { - normalizeLabel, - SSH_MAX_FORWARD_ROWS, - SSH_NAME_MAX_CHARS, - SSH_ROSTER_MAX_IDENTITIES, -} from "@internal/subshell-protocol"; +import { normalizeLabel, SSH_MAX_FORWARD_ROWS, SSH_ROSTER_MAX_IDENTITIES } from "@internal/subshell-protocol"; import { Elysia, t } from "elysia"; import { authGuard, requireCookieActor } from "@/api/auth-guard.js"; import { SubshellSchema } from "@/api/models.js"; import { SshDestinationField, SshExpectedDestinationField, + SshForwardRowSchema, SshResolveRefusalViewSchema, throwCodedRefusal, } from "@/api/ssh/ssh-views.js"; @@ -17,45 +13,6 @@ import { contextPlugin } from "@/plugins/context.plugin.js"; import { apiModels } from "@/schema/index.js"; import { sshLaunch } from "@/services/ssh-launch.service.js"; -/** - * One forward row's ENVELOPE. The shape lives here so the OpenAPI docs read - * honestly, and deliberately NOT the detailed grammar: `parseSshForwardRows` - * in the protocol package is the single authority (host forms, kind rules, - * duplicate listens), because the renderer re-runs that parser at the last - * station before bytes and a second grammar here would be two that drift. - */ -const SshForwardRowSchema = t.Object({ - kind: t.Union([t.Literal("local"), t.Literal("remote"), t.Literal("dynamic")], { - description: - "local: bind a port on the connecting machine that reaches a host the destination can see; remote: ask the destination to bind a port that reaches a host the connecting machine can see; dynamic: a SOCKS proxy on the connecting machine", - }), - listenPort: t.Numeric({ - minimum: 1, - maximum: 65535, - description: "Port to bind (always explicit; no auto ports). Integer-ness belongs to the protocol parser", - }), - bindHost: t.Nullable( - t.String({ - minLength: 1, - maxLength: SSH_NAME_MAX_CHARS, - description: - "Address to bind (hostname, IPv4, or bracketed IPv6). Null means loopback for local/dynamic, and the destination's own policy for remote", - }), - ), - targetHost: t.Optional( - t.Nullable( - t.String({ - minLength: 1, - maxLength: SSH_NAME_MAX_CHARS, - description: "Host to reach through the connection; required for local/remote, null for dynamic", - }), - ), - ), - targetPort: t.Optional( - t.Nullable(t.Numeric({ minimum: 1, maximum: 65535, description: "Port on the target host; same kind rule" })), - ), -}); - /** * `POST /api/ssh/launch` `{node, destination, name?}` — open an interactive * SSH pane to one destination (spec 2026-10-07 §5): gate (current launch access and readiness, before diff --git a/apps/server/api/src/api/ssh/ssh-views.ts b/apps/server/api/src/api/ssh/ssh-views.ts index d02f7db2c..bb0b504ce 100644 --- a/apps/server/api/src/api/ssh/ssh-views.ts +++ b/apps/server/api/src/api/ssh/ssh-views.ts @@ -10,6 +10,75 @@ import type { SshRefusal } from "@/services/ssh-launch.service.js"; * drift. */ +/** + * One user-declared port-forward row's ENVELOPE (spec 2026-10-15), shared by + * the launch body and the preset save because they carry the same thing. + * Deliberately NOT the detailed grammar: `parseSshForwardRows` in the protocol + * package is the single authority (host forms, kind rules, duplicate listens), + * and the renderer re-runs that parser at the last station before bytes - a + * second grammar in a schema here would be two that drift. + */ +export const SshForwardRowSchema = t.Object({ + kind: t.Union([t.Literal("local"), t.Literal("remote"), t.Literal("dynamic")], { + description: + "local: bind a port on the connecting machine that reaches a host the destination can see; remote: ask the destination to bind a port that reaches a host the connecting machine can see; dynamic: a SOCKS proxy on the connecting machine", + }), + listenPort: t.Number({ + minimum: 1, + maximum: 65535, + description: "Port to bind (always explicit; no auto ports). Integer-ness belongs to the protocol parser", + }), + bindHost: t.Nullable( + t.String({ + minLength: 1, + maxLength: 253, + description: + "Address to bind (hostname, IPv4, or bracketed IPv6). Null means loopback for local/dynamic, and the destination's own policy for remote", + }), + ), + targetHost: t.Nullable( + t.String({ + minLength: 1, + maxLength: 253, + description: "Host to reach through the connection; required for local/remote, null for dynamic", + }), + ), + targetPort: t.Nullable( + t.Number({ minimum: 1, maximum: 65535, description: "Port on the target host; same kind rule" }), + ), +}); + +/** + * The same forward row as the preset routes carry it, deliberately FLATTER + * than {@link SshForwardRowSchema}: nested literal unions inside the + * agent-or-ssh body/response union push the `presets.route.ts` handlers past + * Elysia's inference depth (measured the day `forwards` arrived; the launch + * route, which never unions with agent bodies, keeps the expressive kind + * census). The protocol parser enforces the kind set either way, so the + * pattern here is documentation, not the enforcement. + */ +export const SshForwardRowInputSchema = t.Object({ + kind: t.String({ pattern: "^(local|remote|dynamic)$", description: "local (-L), remote (-R), or dynamic (-D)" }), + listenPort: t.Number({ minimum: 1, maximum: 65535, description: "Port to bind (always explicit; no auto ports)" }), + bindHost: t.Nullable( + t.String({ + minLength: 1, + maxLength: 253, + description: "Address to bind; null means loopback for local/dynamic, the destination's policy for remote", + }), + ), + targetHost: t.Nullable( + t.String({ + minLength: 1, + maxLength: 253, + description: "Host to reach through the connection; required for local/remote, null for dynamic", + }), + ), + targetPort: t.Nullable( + t.Number({ minimum: 1, maximum: 65535, description: "Port on the target host; same kind rule" }), + ), +}); + /** One ProxyJump hop as the frozen snapshot spells it. */ export const SshHopViewSchema = t.Object({ host: t.String({ description: "Hop hostname (bracketed IPv6 literal kept as given)" }), diff --git a/apps/server/api/src/db/migrate.ts b/apps/server/api/src/db/migrate.ts index 212cd4302..a5d4ad7e0 100644 --- a/apps/server/api/src/db/migrate.ts +++ b/apps/server/api/src/db/migrate.ts @@ -54,6 +54,7 @@ import * as sshRequestDestinationMigration from "@/db/migrations/0051-ssh-reques import * as subshellKeyHomeMigration from "@/db/migrations/0052-subshell-key-home.js"; import * as sshPresetsMigration from "@/db/migrations/0054-ssh-presets.js"; import * as sshWideForwardsMigration from "@/db/migrations/0055-node-ssh-wide-forwards.js"; +import * as sshPresetForwardsMigration from "@/db/migrations/0056-ssh-preset-forwards.js"; import * as dropSshGrantsMigration from "./migrations/0053-drop-ssh-grants.js"; /** @@ -165,6 +166,7 @@ export async function runMigrations(): Promise { "0053-drop-ssh-grants": dropSshGrantsMigration, "0054-ssh-presets": sshPresetsMigration, "0055-node-ssh-wide-forwards": sshWideForwardsMigration, + "0056-ssh-preset-forwards": sshPresetForwardsMigration, }; }, }, diff --git a/apps/server/api/src/db/migrations/0056-ssh-preset-forwards.ts b/apps/server/api/src/db/migrations/0056-ssh-preset-forwards.ts new file mode 100644 index 000000000..c80d36ab5 --- /dev/null +++ b/apps/server/api/src/db/migrations/0056-ssh-preset-forwards.ts @@ -0,0 +1,16 @@ +import type { Kysely } from "kysely"; + +// SSH presets carry their owner's declared port forwards (spec 2026-10-15 §7): +// a JSON array of rows the protocol grammar validated AT SAVE, or NULL for +// "no forwards" (the pre-column spelling and the clear-the-list spelling +// agree). The rows are connection choices, not credentials, so they follow +// the same storage rules as destination and authentication - and a wide-bind +// row remains a WIDE refusal at launch until the connecting machine allows +// it, exactly as it would for a typed list. +export async function up(db: Kysely): Promise { + await db.schema.alterTable("ssh_presets").addColumn("forwards", "text").execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema.alterTable("ssh_presets").dropColumn("forwards").execute(); +} diff --git a/apps/server/api/src/db/migrations/__tests__/0056-ssh-preset-forwards.test.ts b/apps/server/api/src/db/migrations/__tests__/0056-ssh-preset-forwards.test.ts new file mode 100644 index 000000000..2b22eb6ed --- /dev/null +++ b/apps/server/api/src/db/migrations/__tests__/0056-ssh-preset-forwards.test.ts @@ -0,0 +1,47 @@ +import { expect, test } from "bun:test"; +import { Kysely, sql } from "kysely"; +import { BunSqliteDialect } from "kysely-bun-sqlite-dialect"; +import * as presetsMigration from "@/db/migrations/0054-ssh-presets.js"; +import * as presetForwardsMigration from "@/db/migrations/0056-ssh-preset-forwards.js"; +import { openSqliteDatabase } from "@/db/open-database.js"; + +/** + * The preset forwards column (spec 2026-10-15 §7): nullable JSON, so every + * preset written before the migration simply has none, and NULL is the same + * reading as the explicit clear. + */ +test("SSH preset forwards column arrives nullable and leaves cleanly", async () => { + const db = new Kysely({ dialect: new BunSqliteDialect({ database: openSqliteDatabase(":memory:") }) }); + try { + await sql`CREATE TABLE user (id TEXT PRIMARY KEY)`.execute(db); + await sql`CREATE TABLE nodes (id TEXT PRIMARY KEY)`.execute(db); + await sql`CREATE TABLE presets (id TEXT PRIMARY KEY, name TEXT)`.execute(db); + await sql`CREATE TABLE ssh_saved_hosts (id TEXT PRIMARY KEY, owner_user_id TEXT, destination TEXT, alias TEXT, node_id TEXT, saved_at TEXT, last_connect_at TEXT)`.execute( + db, + ); + await sql`INSERT INTO user VALUES ('u')`.execute(db); + await sql`INSERT INTO nodes VALUES ('n')`.execute(db); + await presetsMigration.up(db); + // A preset written BEFORE 0056, the upgrade case. + await sql`INSERT INTO ssh_presets VALUES ('p', 'u', 'Work', NULL, 'n', 'host:22', NULL, NULL, 'connecting', NULL, NULL, 'now', 'now')`.execute( + db, + ); + + await presetForwardsMigration.up(db); + const cols = await sql<{ + name: string; + notnull: number; + }>`SELECT name, "notnull" FROM pragma_table_info('ssh_presets')`.execute(db); + const fwd = cols.rows.find((c) => c.name === "forwards"); + expect(fwd).toBeDefined(); + expect(fwd?.notnull).toBe(0); + const row = await sql<{ forwards: string | null }>`SELECT forwards FROM ssh_presets WHERE id='p'`.execute(db); + expect(row.rows[0]?.forwards).toBeNull(); + + await presetForwardsMigration.down(db); + const after = await sql<{ name: string }>`SELECT name FROM pragma_table_info('ssh_presets')`.execute(db); + expect(after.rows.map((c) => c.name)).not.toContain("forwards"); + } finally { + await db.destroy(); + } +}); diff --git a/apps/server/api/src/db/types/ssh-presets.db-types.ts b/apps/server/api/src/db/types/ssh-presets.db-types.ts index bbaf43cae..41bd45bd7 100644 --- a/apps/server/api/src/db/types/ssh-presets.db-types.ts +++ b/apps/server/api/src/db/types/ssh-presets.db-types.ts @@ -33,6 +33,14 @@ export interface SshPresetTable { keyHome: string | null; /** JSON-encoded array of selected `SHA256:` public-key fingerprints (remote only); null otherwise */ fingerprints: string | null; + /** + * JSON-encoded array of user-declared port-forward rows (spec 2026-10-15 + * §7), validated by the protocol grammar at save; null means no forwards. + * Connection choices, not credentials - the same class as destination and + * authentication, and a wide-bind row stored here is still refused at + * launch until the connecting machine allows it. + */ + forwards: string | null; /** ISO 8601 creation timestamp (DB default) */ createdAt: string; /** ISO 8601 last-update timestamp */ diff --git a/apps/server/api/src/services/ssh-presets.service.ts b/apps/server/api/src/services/ssh-presets.service.ts index 2de0f2db9..a6ad89185 100644 --- a/apps/server/api/src/services/ssh-presets.service.ts +++ b/apps/server/api/src/services/ssh-presets.service.ts @@ -1,3 +1,4 @@ +import { parseSshForwardRows } from "@internal/subshell-protocol"; import type { Static } from "elysia"; import { loadNodeGate } from "@/api/nodes/node-gate.js"; import { throwCodedRefusal } from "@/api/ssh/ssh-views.js"; @@ -49,6 +50,21 @@ export async function saveSshPreset( throw new SshPresetError("bad_request", "Choose a different node to provide the SSH key"); await validateNodeAccess(userId, body.keyHome); } + // Declared port forwards (spec 2026-10-15 §7): saved through the protocol's + // own parser, so no row the launch renderer would refuse can ride a stored + // preset. Whether a WIDE bind is allowed is a connect-time decision, not a + // save-time one: a preset stores a wish, and the launch route checks the + // connecting machine's flag every time it is honored. + let forwardsJson: string | null = null; + if (body.forwards !== undefined && body.forwards !== null) { + const parsed = parseSshForwardRows(body.forwards); + if (parsed === null) + throw new SshPresetError( + "bad_request", + "That port-forward list isn't valid. Each forward needs a kind (local, remote, or dynamic), a listen port from 1 to 65535, and a target host and port except for dynamic. Two forwards can't bind the same port.", + ); + if (parsed.length > 0) forwardsJson = JSON.stringify(parsed); + } let destination = body.destination; // PUT omits a field it is not deciding about, so an absent alias keeps the // stored one (null clears it), the same rule the agent PUT body follows. @@ -105,6 +121,9 @@ export async function saveSshPreset( keyHome: body.authentication === "remote" ? (body.keyHome ?? null) : null, fingerprints: body.authentication === "remote" && body.fingerprints != null ? JSON.stringify(body.fingerprints) : null, + // PUT convention, same as `description`: omitted keeps what is stored, + // null (or an emptied list) clears it. + forwards: body.forwards === undefined ? (existing?.forwards ?? null) : forwardsJson, createdAt: existing?.createdAt ?? now, updatedAt: now, }; From 63f49a46a056c89ac8abdc98061b03ca33c3ffe1 Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 15:42:21 -0700 Subject: [PATCH 06/35] feat(ssh): port-forward rows in the launch form, presets, and the node SSH card --- .../__tests__/new-subshell-form.test.tsx | 1 + .../components/__tests__/node-row.test.tsx | 1 + .../__tests__/subshell-card.test.tsx | 1 + .../connect/__tests__/connect-panel.test.tsx | 48 +++++ .../src/components/connect/connect-panel.tsx | 20 ++- .../components/connect/ssh-forward-fields.tsx | 170 ++++++++++++++++++ .../web/src/components/connect/ssh-refusal.ts | 10 ++ .../components/connect/ssh-session-draft.ts | 6 + .../web/src/components/connect/ssh-wizard.tsx | 10 ++ .../__tests__/node-harness-card.test.tsx | 1 + .../nodes/__tests__/node-list-row.test.tsx | 1 + .../nodes/__tests__/node-section-nav.test.tsx | 1 + .../nodes/__tests__/node-ssh-card.test.tsx | 26 ++- .../nodes/__tests__/node-table.test.tsx | 1 + .../nodes/__tests__/node-update-card.test.tsx | 1 + .../src/components/nodes/node-ssh-card.tsx | 29 ++- .../__tests__/ssh-preset-editor.test.tsx | 27 +++ .../ssh/__tests__/trust-section.test.tsx | 1 + .../__tests__/new-subshell-form.test.tsx | 1 + .../__tests__/no-launch-targets.test.tsx | 1 + .../__tests__/ssh-launch-integration.test.tsx | 2 + .../__tests__/use-node-harnesses.test.tsx | 1 + .../src/hooks/__tests__/use-nodes.test.tsx | 1 + apps/server/web/src/hooks/use-ssh.ts | 19 ++ .../src/lib/__tests__/ssh-forwards.test.ts | 93 ++++++++++ .../web/src/lib/__tests__/ssh-preset.test.ts | 2 + .../src/lib/__tests__/subshell-compat.test.ts | 1 + apps/server/web/src/lib/ssh-forwards.ts | 110 ++++++++++++ apps/server/web/src/lib/ssh-preset.ts | 3 + apps/server/web/src/lib/ssh.ts | 22 +++ .../routes/__tests__/nodes-detail.test.tsx | 1 + .../__tests__/nodes-page-filters.test.tsx | 2 + .../routes/__tests__/nodes-page-gate.test.tsx | 1 + .../src/routes/__tests__/nodes-tabs.test.tsx | 1 + apps/server/web/src/types/preset.ts | 14 +- .../src/__tests__/node-allowed-dirs.test.tsx | 1 + .../__tests__/node-maintenance-card.test.tsx | 1 + .../src/__tests__/node-runtime-card.test.tsx | 1 + .../src/__tests__/node-service-card.test.tsx | 1 + .../src/__tests__/ssh-trust-card.test.tsx | 1 + packages/node-admin/src/types/node.ts | 8 + 41 files changed, 638 insertions(+), 5 deletions(-) create mode 100644 apps/server/web/src/components/connect/ssh-forward-fields.tsx create mode 100644 apps/server/web/src/lib/__tests__/ssh-forwards.test.ts create mode 100644 apps/server/web/src/lib/ssh-forwards.ts diff --git a/apps/server/web/src/components/__tests__/new-subshell-form.test.tsx b/apps/server/web/src/components/__tests__/new-subshell-form.test.tsx index 65c4b1c9a..8c6a6004b 100644 --- a/apps/server/web/src/components/__tests__/new-subshell-form.test.tsx +++ b/apps/server/web/src/components/__tests__/new-subshell-form.test.tsx @@ -56,6 +56,7 @@ function node(overrides: Partial): Node { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, ...overrides, }; diff --git a/apps/server/web/src/components/__tests__/node-row.test.tsx b/apps/server/web/src/components/__tests__/node-row.test.tsx index f30afc40d..f173c5928 100644 --- a/apps/server/web/src/components/__tests__/node-row.test.tsx +++ b/apps/server/web/src/components/__tests__/node-row.test.tsx @@ -30,6 +30,7 @@ const BASE: Node = { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, }; diff --git a/apps/server/web/src/components/__tests__/subshell-card.test.tsx b/apps/server/web/src/components/__tests__/subshell-card.test.tsx index 15e7da1fc..2db9036cd 100644 --- a/apps/server/web/src/components/__tests__/subshell-card.test.tsx +++ b/apps/server/web/src/components/__tests__/subshell-card.test.tsx @@ -47,6 +47,7 @@ function agent(overrides: Partial = {}): Node { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, ...overrides, }; diff --git a/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx b/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx index b595db3b1..a5d73403c 100644 --- a/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx +++ b/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx @@ -50,6 +50,7 @@ function node(overrides: Partial): Node { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, ...overrides, }; @@ -763,3 +764,50 @@ it("a delayed URL preset cannot overwrite an edited draft during or after a pend restore(); } }); + +it("carries declared port forwards in the launch body, and holds Start until the row is valid (spec 2026-10-15)", async () => { + const { calls, restore } = await renderPanel({ + nodes: [HOST_A], + launch: () => json({ subshell: { id: "pane-fwd" } }, 201), + }); + try { + fireEvent.change(destinationInput(), { target: { value: "build.example" } }); + await settle(); + fireEvent.click(screen.getByRole("button", { name: "Advanced SSH options" })); + fireEvent.click(screen.getByRole("button", { name: "Add port forward" })); + await settle(); + // Loopback-only machine: no bind field offered, and the reason is on screen. + expect(screen.queryByLabelText(/Bind address/)).toBeNull(); + expect(screen.getByText(/loopback/i)).toBeTruthy(); + fireEvent.change(screen.getByLabelText(/Local port/), { target: { value: "5432" } }); + await settle(); + // Half a row: the listen port alone is not a launchable forward. + expect((screen.getByRole("button", { name: /^Start SSH subshell$/ }) as HTMLButtonElement).disabled).toBe(true); + fireEvent.change(screen.getByLabelText(/Target host/), { target: { value: "db.internal" } }); + fireEvent.change(screen.getByLabelText(/Target port/), { target: { value: "5432" } }); + await settle(); + await clickConnect(); + await waitFor(() => expect(calls.some((call) => call.url === "/api/ssh/launch")).toBe(true)); + expect(calls.find((call) => call.url === "/api/ssh/launch")?.body).toMatchObject({ + node: "a1", + destination: "build.example", + forwards: [{ kind: "local", listenPort: 5432, bindHost: null, targetHost: "db.internal", targetPort: 5432 }], + }); + } finally { + restore(); + } +}); + +it("offers the bind address on a machine whose owner allows wide binds", async () => { + const HOST_WIDE = node({ id: "w1", name: "router box", sshEnabled: true, sshWideForwards: true }); + const { restore } = await renderPanel({ nodes: [HOST_WIDE] }); + try { + fireEvent.click(screen.getByRole("button", { name: "Advanced SSH options" })); + fireEvent.click(screen.getByRole("button", { name: "Add port forward" })); + await settle(); + expect(screen.getByLabelText(/Bind address/)).toBeTruthy(); + expect(screen.queryByText(/owner can allow other bind addresses/i)).toBeNull(); + } finally { + restore(); + } +}); diff --git a/apps/server/web/src/components/connect/connect-panel.tsx b/apps/server/web/src/components/connect/connect-panel.tsx index 8c1d725bd..dfe4f8644 100644 --- a/apps/server/web/src/components/connect/connect-panel.tsx +++ b/apps/server/web/src/components/connect/connect-panel.tsx @@ -7,6 +7,7 @@ import { useBrowserKeySelection } from "@/components/connect/browser-key-fields" import { DestinationField } from "@/components/connect/destination-field"; import { buildDestinationOptions } from "@/components/connect/destination-options"; import { NoSshTargets } from "@/components/connect/no-ssh-targets"; +import { SshForwardFields } from "@/components/connect/ssh-forward-fields"; import { SshKeyStep } from "@/components/connect/ssh-key-step"; import { SaveSshPresetFields, SshPresetPicker } from "@/components/connect/ssh-preset-controls"; import { type SshRefusalCopy, sshLaunchRefusal } from "@/components/connect/ssh-refusal"; @@ -41,6 +42,7 @@ import { fieldErrorToned, makeForm, useSubmitDisabled } from "@/lib/form"; import { nodeOptionLabel } from "@/lib/node-label"; import { REQUIREMENT_CAPTION_CLASS } from "@/lib/requirement-tone"; import { sshCanEnableOnStart } from "@/lib/ssh"; +import { sshForwardRowsToWire } from "@/lib/ssh-forwards"; import { sshDestinationProblem } from "@/lib/ssh-input-validation"; import { launchWithLocalKey } from "@/lib/ssh-local-key"; import { sshDraftFromPreset, sshPresetPayload } from "@/lib/ssh-preset"; @@ -232,6 +234,7 @@ export function ConnectPanel({ const selectionError = sshKeySelectionProblem(fingerprints, roster.data?.identities); const rosterReady = !keyHome || (!roster.isPending && !roster.isError && selectionError === null); const destination = sshDestination(draft); + const forwardRows = sshForwardRowsToWire(draft.forwards); // A committed row's label is the field's text; while that echo stands the // list is UNFILTERED (the whole ledger again), because the echo is display, // not a search. Any keystroke releases the pick, and text becomes query. @@ -344,6 +347,7 @@ export function ConnectPanel({ destination, ...(expectedDestination ? { expectedDestination } : {}), ...(keyHome ? { keyHome, fingerprints } : {}), + ...(forwardRows.length > 0 ? { forwards: forwardRows } : {}), }; const created = draft.authentication === "local-key" @@ -381,6 +385,7 @@ export function ConnectPanel({ destination, ...(expectedDestination ? { expectedDestination } : {}), ...(keyHome ? { keyHome, fingerprints } : {}), + ...(forwardRows.length > 0 ? { forwards: forwardRows } : {}), }, launch.mutateAsync, ); @@ -566,14 +571,17 @@ export function ConnectPanel({
- diff --git a/apps/server/web/src/components/connect/ssh-forward-fields.tsx b/apps/server/web/src/components/connect/ssh-forward-fields.tsx new file mode 100644 index 000000000..1ac65d6a7 --- /dev/null +++ b/apps/server/web/src/components/connect/ssh-forward-fields.tsx @@ -0,0 +1,170 @@ +import { Button, Input, Label } from "@internal/node-admin"; +import { SSH_MAX_FORWARD_ROWS } from "@internal/subshell-protocol"; +import { Plus, X } from "lucide-react"; +import { Field, FieldLabel } from "@/components/ui/field"; +import { RequiredMark } from "@/components/ui/required-mark"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { EMPTY_SSH_FORWARD_ROW, type SshForwardDraftRow, sshForwardRowProblems } from "@/lib/ssh-forwards"; + +/** + * The port-forwards group (spec 2026-10-15 §7): every row a person declared + * for this connection, with its kind, ports, and (only on a machine whose + * owner allowed wide binds) its bind address. Rows are form state until + * Start; the launch carries them and the rendered config makes them real, + * and they die with the pane. + */ + +const KIND_LABELS = [ + { value: "local", label: "Local (-L)" }, + { value: "remote", label: "Remote (-R)" }, + { value: "dynamic", label: "Dynamic SOCKS (-D)" }, +] as const; + +function kindLabel(kind: SshForwardDraftRow["kind"]): string { + return KIND_LABELS.find((option) => option.value === kind)?.label ?? kind; +} + +export function SshForwardFields({ + rows, + allowWideBind, + busy, + onChange, +}: { + rows: SshForwardDraftRow[]; + /** The selected machine's wide-bind allowance; false hides the bind field. */ + allowWideBind: boolean; + busy: boolean; + onChange: (rows: SshForwardDraftRow[]) => void; +}) { + const patchRow = (index: number, value: Partial) => + onChange(rows.map((row, i) => (i === index ? { ...row, ...value } : row))); + return ( +
+ Port forwards +

+ Each forward opens a listening port for as long as this connection lives. A local or dynamic forward binds it on + the connecting machine, so programs there reach a host the destination can see; a remote one asks the + destination to bind a port that reaches a host the connecting machine can see. +

+ {rows.map((row, index) => { + const problem = sshForwardRowProblems(row); + const needsTarget = row.kind !== "dynamic"; + const idPrefix = `ssh-fwd-${String(index)}`; + return ( +
+
+ + Kind + + + + + patchRow(index, { listenPort: event.target.value })} + /> + + {needsTarget && ( + <> + + + patchRow(index, { targetHost: event.target.value })} + /> + + + + patchRow(index, { targetPort: event.target.value })} + /> + + + )} + {(allowWideBind || row.bindHost !== "") && ( + + + patchRow(index, { bindHost: event.target.value })} + /> + + )} + +
+ {problem && ( +

+ {problem} +

+ )} +
+ ); + })} + {!allowWideBind && ( +

+ Forwards bind this machine's own loopback address. Its owner can allow other bind addresses on the node's SSH + settings. +

+ )} + +
+ ); +} diff --git a/apps/server/web/src/components/connect/ssh-refusal.ts b/apps/server/web/src/components/connect/ssh-refusal.ts index dd5fbd377..c85a96de2 100644 --- a/apps/server/web/src/components/connect/ssh-refusal.ts +++ b/apps/server/web/src/components/connect/ssh-refusal.ts @@ -85,6 +85,16 @@ export function sshLaunchRefusal( // ALIAS_UNSAFE: the token was refused before the machine was asked. return { text: "Enter a valid SSH destination, such as deploy@example.com:22.", field: "destination" }; case 403: { + if (err.code === "SSH_FORWARD_BIND_DENIED") { + // A wide bind needs the machine's owner to allow it (spec 2026-10-15 + // §6): the server's own sentence names both remedies, so pass it + // through like the CONFIG_INVALID arm does. + const message = + err.body && typeof err.body === "object" && "message" in err.body && typeof err.body.message === "string" + ? err.body.message + : err.message; + return { text: message, field: "machine" }; + } if (err.code === "SSH_GATE_OFF" && machine !== null && !machine.sshEnabled) { // The gate's off-door. Naming WHO flips it follows the row's kind: // `local` is an admin's setting, every other machine its owner's diff --git a/apps/server/web/src/components/connect/ssh-session-draft.ts b/apps/server/web/src/components/connect/ssh-session-draft.ts index 6a06b181a..ccfd553f2 100644 --- a/apps/server/web/src/components/connect/ssh-session-draft.ts +++ b/apps/server/web/src/components/connect/ssh-session-draft.ts @@ -1,5 +1,6 @@ import type { DestinationCandidate } from "@/components/connect/destination-options"; import { type SshAgentIdentity, sshSelectionError } from "@/lib/ssh"; +import { type SshForwardDraftRow, sshForwardListProblems } from "@/lib/ssh-forwards"; import { sshDestinationProblem, sshPresetNameProblem } from "@/lib/ssh-input-validation"; export type SshInitialChoices = { @@ -30,6 +31,8 @@ export interface SshSessionDraft { presetApplied?: boolean; saveAsPreset?: boolean; presetName?: string; + /** Declared port forwards as FORM rows (strings; untyped rows are ignored). Spec 2026-10-15. */ + forwards: SshForwardDraftRow[]; } export function sshSessionDraft(initial?: SshInitialChoices): SshSessionDraft { @@ -43,6 +46,7 @@ export function sshSessionDraft(initial?: SshInitialChoices): SshSessionDraft { typed: initial?.destination ?? "", }, initialPresetId: initial?.preset, + forwards: [], }; } @@ -72,6 +76,8 @@ export function sshDraftProblems(draft: SshSessionDraft): Record if (!draft.nodeId) return { node: "Choose a node to connect from first." }; const destinationProblem = sshDestinationProblem(sshDestination(draft)); if (destinationProblem) return { destination: destinationProblem }; + const forwardProblem = sshForwardListProblems(draft.forwards); + if (forwardProblem) return { forwards: forwardProblem }; const error = sshKeySourceProblem(draft) || (draft.keyHome ? sshKeySelectionProblem(draft.selections[draft.keyHome] ?? []) : null); diff --git a/apps/server/web/src/components/connect/ssh-wizard.tsx b/apps/server/web/src/components/connect/ssh-wizard.tsx index c49d89073..0049b0ee4 100644 --- a/apps/server/web/src/components/connect/ssh-wizard.tsx +++ b/apps/server/web/src/components/connect/ssh-wizard.tsx @@ -3,6 +3,7 @@ import { CircleAlert } from "lucide-react"; import { useEffect, useRef } from "react"; import { BrowserKeyFields, type BrowserKeySelection } from "@/components/connect/browser-key-fields"; import { SshDestinationStep } from "@/components/connect/ssh-destination-step"; +import { SshForwardFields } from "@/components/connect/ssh-forward-fields"; import { SshKeyStep } from "@/components/connect/ssh-key-step"; import { SshMachineSetup, SshMachineStep } from "@/components/connect/ssh-machine-step"; import { SaveSshPresetFields } from "@/components/connect/ssh-preset-controls"; @@ -19,6 +20,7 @@ import { useSshNodeRoster } from "@/hooks/use-ssh"; import { isDesktop } from "@/lib/desktop"; import { makeForm, useSubmitDisabled } from "@/lib/form"; import { type SshMachineReadiness, sshCanEnableOnStart } from "@/lib/ssh"; +import { sshForwardListProblems } from "@/lib/ssh-forwards"; import { sshDestinationProblem, sshPresetNameProblem } from "@/lib/ssh-input-validation"; export interface SshWizardProps { @@ -92,6 +94,8 @@ export function SshWizard({ if (!value.nodeId) return { draft: "Choose a node." }; const destinationProblem = sshDestinationProblem(sshDestination(value)); if (destinationProblem) return { draft: destinationProblem }; + const forwardProblem = sshForwardListProblems(value.forwards); + if (forwardProblem) return { draft: forwardProblem }; if (remote) { const error = !value.keyHome ? "Choose a node to supply keys." : sshKeySourceProblem(value); if (error) return { draft: error }; @@ -331,6 +335,12 @@ export function SshWizard({ + patch({ forwards })} + /> {!presetEditor && } {refusal && ( diff --git a/apps/server/web/src/components/nodes/__tests__/node-harness-card.test.tsx b/apps/server/web/src/components/nodes/__tests__/node-harness-card.test.tsx index 83a0ccf64..312720f05 100644 --- a/apps/server/web/src/components/nodes/__tests__/node-harness-card.test.tsx +++ b/apps/server/web/src/components/nodes/__tests__/node-harness-card.test.tsx @@ -105,6 +105,7 @@ function view(over: Partial): NodeDetail { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, ...over, }; diff --git a/apps/server/web/src/components/nodes/__tests__/node-list-row.test.tsx b/apps/server/web/src/components/nodes/__tests__/node-list-row.test.tsx index 465af89b7..26927df0c 100644 --- a/apps/server/web/src/components/nodes/__tests__/node-list-row.test.tsx +++ b/apps/server/web/src/components/nodes/__tests__/node-list-row.test.tsx @@ -36,6 +36,7 @@ function node(overrides: Partial = {}): Node { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, ...overrides, }; diff --git a/apps/server/web/src/components/nodes/__tests__/node-section-nav.test.tsx b/apps/server/web/src/components/nodes/__tests__/node-section-nav.test.tsx index b6101c03d..1ddac617a 100644 --- a/apps/server/web/src/components/nodes/__tests__/node-section-nav.test.tsx +++ b/apps/server/web/src/components/nodes/__tests__/node-section-nav.test.tsx @@ -35,6 +35,7 @@ function navNode(overrides: Partial = {}): NodeDetail { canManage: true, canRetire: true, sshEnabled: false, + sshWideForwards: false, canLaunch: true, allowedDirs: [], capabilities: [], diff --git a/apps/server/web/src/components/nodes/__tests__/node-ssh-card.test.tsx b/apps/server/web/src/components/nodes/__tests__/node-ssh-card.test.tsx index 5f69e2727..3b7fe0cc0 100644 --- a/apps/server/web/src/components/nodes/__tests__/node-ssh-card.test.tsx +++ b/apps/server/web/src/components/nodes/__tests__/node-ssh-card.test.tsx @@ -28,6 +28,7 @@ function node(over: Partial = {}): NodeDetail { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, ...over, }; @@ -54,7 +55,7 @@ for (const enabled of [false, true]) { return new Response("{}"); }) as unknown as typeof fetch; mount(node({ sshEnabled: enabled })); - expect(screen.getByRole("switch").getAttribute("aria-checked")).toBe(String(enabled)); + expect(screen.getByRole("switch", { name: "Allow SSH" }).getAttribute("aria-checked")).toBe(String(enabled)); fireEvent.click(screen.getByRole("switch", { name: "Allow SSH" })); await waitFor(() => expect(calls).toHaveLength(1)); expect(calls[0].url).toContain("/api/nodes/node1/ssh-enabled"); @@ -62,6 +63,29 @@ for (const enabled of [false, true]) { expect(JSON.parse(String(calls[0].init?.body))).toEqual({ on: !enabled }); }); } +it("offers the wide-forwards switch beside the SSH switch, and writes its own endpoint", async () => { + const calls: { url: string; init?: RequestInit }[] = []; + globalThis.fetch = (async (url, init) => { + calls.push({ url: String(url), init }); + return new Response("{}"); + }) as unknown as typeof fetch; + mount(node({ sshEnabled: true })); + const wide = screen.getByRole("switch", { name: "Allow wide port forwards" }); + expect(wide.getAttribute("aria-checked")).toBe("false"); + fireEvent.click(wide); + await waitFor(() => expect(calls.some((c) => c.url.includes("/api/nodes/node1/ssh-wide-forwards"))).toBe(true)); + const call = calls.find((c) => c.url.includes("/ssh-wide-forwards")); + expect(JSON.parse(String(call?.init?.body))).toEqual({ on: true }); +}); + +it("hides the wide-forwards switch while SSH itself is off, and says who owns it to others", () => { + mount(node({ sshEnabled: false })); + expect(screen.queryByRole("switch", { name: "Allow wide port forwards" })).toBeNull(); + mount(node({ sshEnabled: true, access: "edit", canManage: false })); + const wide = screen.getByRole("switch", { name: "Allow wide port forwards" }); + expect(wide.hasAttribute("data-disabled")).toBe(true); +}); + it("shows configuration to edit viewers without offering changes", () => { mount(node({ access: "edit", canManage: false })); expect(screen.getByRole("switch").hasAttribute("data-disabled")).toBe(true); diff --git a/apps/server/web/src/components/nodes/__tests__/node-table.test.tsx b/apps/server/web/src/components/nodes/__tests__/node-table.test.tsx index 7b61365d6..41feda532 100644 --- a/apps/server/web/src/components/nodes/__tests__/node-table.test.tsx +++ b/apps/server/web/src/components/nodes/__tests__/node-table.test.tsx @@ -30,6 +30,7 @@ function node(overrides: Partial = {}): Node { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, ...overrides, }; diff --git a/apps/server/web/src/components/nodes/__tests__/node-update-card.test.tsx b/apps/server/web/src/components/nodes/__tests__/node-update-card.test.tsx index bc64345fa..b820699bf 100644 --- a/apps/server/web/src/components/nodes/__tests__/node-update-card.test.tsx +++ b/apps/server/web/src/components/nodes/__tests__/node-update-card.test.tsx @@ -36,6 +36,7 @@ function node(over: Partial = {}): NodeDetail { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, ...over, }; diff --git a/apps/server/web/src/components/nodes/node-ssh-card.tsx b/apps/server/web/src/components/nodes/node-ssh-card.tsx index 1c122507a..0018777de 100644 --- a/apps/server/web/src/components/nodes/node-ssh-card.tsx +++ b/apps/server/web/src/components/nodes/node-ssh-card.tsx @@ -9,11 +9,12 @@ import { Switch, } from "@internal/node-admin"; import type { ReactNode } from "react"; -import { useSetSshEnabled } from "@/hooks/use-ssh"; +import { useSetSshEnabled, useSetSshWideForwards } from "@/hooks/use-ssh"; /** The node's configuration permission is supplied by the same server gate as the update route. */ export function NodeSshCard({ node, children }: { node: NodeDetail; children?: ReactNode }) { const update = useSetSshEnabled(); + const wide = useSetSshWideForwards(); return ( @@ -47,6 +48,32 @@ export function NodeSshCard({ node, children }: { node: NodeDetail; children?: R {errMessage(update.error, "Couldn't update the SSH setting.")}

)} + {node.sshEnabled && ( +
+
+ wide.mutate({ id: node.id, on })} + /> + +
+

+ {node.sshWideForwards + ? "Port forwards can bind an address other machines can reach, so this machine routes their traffic into whatever the connection reaches. Turning this off refuses future wide binds; running forwards keep going until their pane closes." + : "Port forwards bind this machine's own loopback address only, so just programs on this machine can use them. Turn this on to let a forward bind an address other machines can reach."} +

+ {!node.canManage && ( +

Only this node’s owner can change this setting.

+ )} + {wide.isError && ( +

+ {errMessage(wide.error, "Couldn't update the port-forward setting.")} +

+ )} +
+ )} {children}
diff --git a/apps/server/web/src/components/presets/__tests__/ssh-preset-editor.test.tsx b/apps/server/web/src/components/presets/__tests__/ssh-preset-editor.test.tsx index be3a6ce26..3ed160d04 100644 --- a/apps/server/web/src/components/presets/__tests__/ssh-preset-editor.test.tsx +++ b/apps/server/web/src/components/presets/__tests__/ssh-preset-editor.test.tsx @@ -32,6 +32,7 @@ const preset: SshPreset = { authentication: "local-key", keyHome: null, fingerprints: [], + forwards: [], createdAt: "t", updatedAt: "t", }; @@ -112,10 +113,36 @@ it("saves the local-key radio choice without requiring a file or approval", asyn authentication: "local-key", keyHome: null, fingerprints: [], + forwards: [], }, }, ]); }); + +it("saves a declared port forward alongside the connection choices", async () => { + const { writes, closed } = await setup(); + await waitFor(() => expect(disabled()).toBe(false)); + fireEvent.click(screen.getByRole("button", { name: "Add port forward" })); + fireEvent.change(screen.getByLabelText(/Local port/), { target: { value: "5432" } }); + fireEvent.change(screen.getByLabelText(/Target host/), { target: { value: "db.internal" } }); + fireEvent.change(screen.getByLabelText(/Target port/), { target: { value: "5432" } }); + await waitFor(() => expect(disabled()).toBe(false)); + fireEvent.click(screen.getByRole("button", { name: "Save preset" })); + await waitFor(() => expect(closed()).toBe(true)); + expect(writes[0]?.body.forwards).toEqual([ + { kind: "local", listenPort: 5432, bindHost: null, targetHost: "db.internal", targetPort: 5432 }, + ]); +}); + +it("an incomplete forward row holds Save", async () => { + const { writes } = await setup(); + await waitFor(() => expect(disabled()).toBe(false)); + fireEvent.click(screen.getByRole("button", { name: "Add port forward" })); + fireEvent.change(screen.getByLabelText(/Local port/), { target: { value: "5432" } }); + fireEvent.change(screen.getByLabelText(/Target port/), { target: { value: "5432" } }); + await waitFor(() => expect(disabled()).toBe(true)); + expect(writes).toHaveLength(0); +}); it("allows unrelated edits while the saved connecting node is offline", async () => { const { writes } = await setup(false); fireEvent.change(screen.getByLabelText("Name"), { target: { value: "Renamed" } }); diff --git a/apps/server/web/src/components/ssh/__tests__/trust-section.test.tsx b/apps/server/web/src/components/ssh/__tests__/trust-section.test.tsx index 3169523d2..48aafe65e 100644 --- a/apps/server/web/src/components/ssh/__tests__/trust-section.test.tsx +++ b/apps/server/web/src/components/ssh/__tests__/trust-section.test.tsx @@ -48,6 +48,7 @@ function node(over: Partial = {}): NodeDetail { maintenanceAt: null, maintenanceSource: null, sshEnabled: true, + sshWideForwards: false, held: null, sshTrust: { own: { signing: FP_OWN_SIGN, encryption: FP_OWN_ENC }, diff --git a/apps/server/web/src/components/subshell-picker/__tests__/new-subshell-form.test.tsx b/apps/server/web/src/components/subshell-picker/__tests__/new-subshell-form.test.tsx index e2bef6da1..b64ce062d 100644 --- a/apps/server/web/src/components/subshell-picker/__tests__/new-subshell-form.test.tsx +++ b/apps/server/web/src/components/subshell-picker/__tests__/new-subshell-form.test.tsx @@ -194,6 +194,7 @@ const BOX = { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, }; diff --git a/apps/server/web/src/components/subshell-picker/__tests__/no-launch-targets.test.tsx b/apps/server/web/src/components/subshell-picker/__tests__/no-launch-targets.test.tsx index 60f367fac..b3e354cbe 100644 --- a/apps/server/web/src/components/subshell-picker/__tests__/no-launch-targets.test.tsx +++ b/apps/server/web/src/components/subshell-picker/__tests__/no-launch-targets.test.tsx @@ -57,6 +57,7 @@ function node(overrides: Partial): Node { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, ...overrides, }; diff --git a/apps/server/web/src/components/subshell-picker/__tests__/ssh-launch-integration.test.tsx b/apps/server/web/src/components/subshell-picker/__tests__/ssh-launch-integration.test.tsx index 0716166bc..ec693a2e4 100644 --- a/apps/server/web/src/components/subshell-picker/__tests__/ssh-launch-integration.test.tsx +++ b/apps/server/web/src/components/subshell-picker/__tests__/ssh-launch-integration.test.tsx @@ -39,6 +39,7 @@ function api() { canManage: true, access: "owner", sshEnabled: true, + sshWideForwards: false, maintenance: false, harnesses: [], allowedDirs: [], @@ -70,6 +71,7 @@ function api() { name: "Server", kind: "local", sshEnabled: true, + sshWideForwards: false, canLaunch: true, canManage: true, status: "online", diff --git a/apps/server/web/src/hooks/__tests__/use-node-harnesses.test.tsx b/apps/server/web/src/hooks/__tests__/use-node-harnesses.test.tsx index 181047db7..82d5c30df 100644 --- a/apps/server/web/src/hooks/__tests__/use-node-harnesses.test.tsx +++ b/apps/server/web/src/hooks/__tests__/use-node-harnesses.test.tsx @@ -39,6 +39,7 @@ const NODE: NodeDetail = { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, }; diff --git a/apps/server/web/src/hooks/__tests__/use-nodes.test.tsx b/apps/server/web/src/hooks/__tests__/use-nodes.test.tsx index eac2e457e..9afc42fad 100644 --- a/apps/server/web/src/hooks/__tests__/use-nodes.test.tsx +++ b/apps/server/web/src/hooks/__tests__/use-nodes.test.tsx @@ -41,6 +41,7 @@ const NODE: Node = { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, }; diff --git a/apps/server/web/src/hooks/use-ssh.ts b/apps/server/web/src/hooks/use-ssh.ts index c3dd59ca4..ae24a5ad2 100644 --- a/apps/server/web/src/hooks/use-ssh.ts +++ b/apps/server/web/src/hooks/use-ssh.ts @@ -182,3 +182,22 @@ export function useSetSshEnabled() { }, }); } + +/** Flip one machine's wide-bind allowance for SSH port forwards (spec 2026-10-15 §6); owner-only, like the SSH switch. */ +export function useSetSshWideForwards() { + const client = useQueryClient(); + return useMutation({ + mutationFn: ({ id, on }: { id: string; on: boolean }) => + apiFetch(`/api/nodes/${encodeURIComponent(id)}/ssh-wide-forwards`, { + method: "PUT", + body: JSON.stringify({ on }), + }), + onSuccess: async (_data, { id }) => { + await Promise.all([ + client.invalidateQueries({ queryKey: ["ssh-readiness"] }), + client.invalidateQueries({ queryKey: [...NODE_QUERY_KEY, id] }), + client.invalidateQueries({ queryKey: NODES_QUERY_KEY }), + ]); + }, + }); +} diff --git a/apps/server/web/src/lib/__tests__/ssh-forwards.test.ts b/apps/server/web/src/lib/__tests__/ssh-forwards.test.ts new file mode 100644 index 000000000..995414d09 --- /dev/null +++ b/apps/server/web/src/lib/__tests__/ssh-forwards.test.ts @@ -0,0 +1,93 @@ +import { describe, expect, it } from "bun:test"; +import { + EMPTY_SSH_FORWARD_ROW, + type SshForwardDraftRow, + sshForwardListProblems, + sshForwardRowProblems, + sshForwardRowsFromWire, + sshForwardRowsToWire, +} from "@/lib/ssh-forwards"; + +/** The form-side mirror of the launch-time refusals (spec 2026-10-15). */ +function row(over: Partial = {}): SshForwardDraftRow { + return { kind: "local", listenPort: "5432", bindHost: "", targetHost: "db.internal", targetPort: "5432", ...over }; +} + +describe("sshForwardRowProblems", () => { + it("accepts a complete local row and a dynamic row with no target", () => { + expect(sshForwardRowProblems(row())).toBeNull(); + expect(sshForwardRowProblems(row({ kind: "dynamic", targetHost: "", targetPort: "" }))).toBeNull(); + }); + + it("refuses ports outside the range and non-numbers", () => { + for (const listenPort of ["", "0", "65536", "abc", "1.5", "-1"]) { + expect(sshForwardRowProblems(row({ listenPort }))).toMatch(/listen port/i); + } + expect(sshForwardRowProblems(row({ targetPort: "0" }))).toMatch(/target port/i); + }); + + it("refuses a local or remote row with no target host", () => { + expect(sshForwardRowProblems(row({ targetHost: "" }))).toMatch(/host to reach/i); + expect(sshForwardRowProblems(row({ kind: "remote", targetHost: "" }))).toMatch(/host to reach/i); + // dynamic carries no target at all, so blank is correct there. + expect(sshForwardRowProblems(row({ kind: "dynamic", targetHost: "", targetPort: "" }))).toBeNull(); + }); + + it("refuses option-like or space-bearing bind hosts, and accepts blanks", () => { + expect(sshForwardRowProblems(row({ bindHost: "-oProxyCommand=x" }))).toMatch(/bind host/i); + expect(sshForwardRowProblems(row({ bindHost: "two words" }))).toMatch(/bind host/i); + expect(sshForwardRowProblems(row({ bindHost: "" }))).toBeNull(); + expect(sshForwardRowProblems(row({ bindHost: "10.0.0.5" }))).toBeNull(); + expect(sshForwardRowProblems(row({ bindHost: "[::1]" }))).toBeNull(); + }); +}); + +describe("sshForwardListProblems", () => { + it("an all-empty list (including untouched blank rows) is ready", () => { + expect(sshForwardListProblems([])).toBeNull(); + expect(sshForwardListProblems([EMPTY_SSH_FORWARD_ROW, { ...EMPTY_SSH_FORWARD_ROW }])).toBeNull(); + }); + + it("refuses two rows binding the same port, case-insensitively on the name", () => { + expect(sshForwardListProblems([row(), row({ targetHost: "other.internal" })])).toMatch(/same port/i); + expect(sshForwardListProblems([row({ bindHost: "LOCALHOST" }), row({ bindHost: "localhost" })])).toMatch( + /same port/i, + ); + // Different literal names on one port is a bind race ssh reports itself. + expect(sshForwardListProblems([row({ bindHost: "localhost" }), row({ bindHost: "127.0.0.1" })])).toBeNull(); + }); + + it("refuses over the cap", () => { + const many = Array.from({ length: 17 }, (_, i) => row({ listenPort: String(i + 1) })); + expect(sshForwardListProblems(many)).toMatch(/at most 16/i); + }); + + it("reports the first row's problem before list rules", () => { + expect(sshForwardListProblems([row({ listenPort: "" }), row()])).toMatch(/listen port/i); + }); +}); + +describe("sshForwardRowsToWire", () => { + it("drops empty rows, parses numbers, and nulls the blanks", () => { + expect( + sshForwardRowsToWire([ + row({ listenPort: " 5432 ", targetHost: " db.internal " }), + EMPTY_SSH_FORWARD_ROW, + row({ kind: "dynamic", listenPort: "1080", targetHost: "", targetPort: "" }), + ]), + ).toEqual([ + { kind: "local", listenPort: 5432, bindHost: null, targetHost: "db.internal", targetPort: 5432 }, + { kind: "dynamic", listenPort: 1080, bindHost: null, targetHost: null, targetPort: null }, + ]); + }); +}); + +describe("sshForwardRowsFromWire", () => { + it("round-trips wire rows into form strings and back", () => { + const wire = [ + { kind: "remote" as const, listenPort: 9090, bindHost: "0.0.0.0", targetHost: "dev.local", targetPort: 3000 }, + ]; + expect(sshForwardRowsToWire(sshForwardRowsFromWire(wire))).toEqual(wire); + expect(sshForwardRowsFromWire(undefined)).toEqual([]); + }); +}); diff --git a/apps/server/web/src/lib/__tests__/ssh-preset.test.ts b/apps/server/web/src/lib/__tests__/ssh-preset.test.ts index 1fcc354e6..fb7101222 100644 --- a/apps/server/web/src/lib/__tests__/ssh-preset.test.ts +++ b/apps/server/web/src/lib/__tests__/ssh-preset.test.ts @@ -13,6 +13,7 @@ const preset: SshPreset = { authentication: "remote", keyHome: "keys", fingerprints: ["SHA256:one"], + forwards: [], createdAt: "t", updatedAt: "t", }; @@ -47,6 +48,7 @@ it("projects the local-key preference without key, credential, or stale remote s authentication: "local-key", keyHome: null, fingerprints: [], + forwards: [], }); }); it.each(["connecting", "remote", "local-key", "password"] as const)( diff --git a/apps/server/web/src/lib/__tests__/subshell-compat.test.ts b/apps/server/web/src/lib/__tests__/subshell-compat.test.ts index a6baaaaae..b9d565370 100644 --- a/apps/server/web/src/lib/__tests__/subshell-compat.test.ts +++ b/apps/server/web/src/lib/__tests__/subshell-compat.test.ts @@ -33,6 +33,7 @@ function node(overrides: Partial): Node { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, ...overrides, }; diff --git a/apps/server/web/src/lib/ssh-forwards.ts b/apps/server/web/src/lib/ssh-forwards.ts new file mode 100644 index 000000000..559c51622 --- /dev/null +++ b/apps/server/web/src/lib/ssh-forwards.ts @@ -0,0 +1,110 @@ +import { SSH_MAX_FORWARD_ROWS } from "@internal/subshell-protocol"; +import type { SshForwardRow } from "@/lib/ssh"; + +/** + * Port-forward rows as the FORM holds them: every value a string, because a + * half-typed port is normal keystrokes and NaN is not. The pure checks here + * mirror the server's refusals (kind rules, port bounds, host hygiene, + * duplicate binds) so a person sees the problem beside the field instead of + * after pressing Start; the protocol grammar on the server stays the + * authority, and nothing here needs to be a perfect copy of it. + */ +export type SshForwardDraftRow = { + kind: "local" | "remote" | "dynamic"; + listenPort: string; + bindHost: string; + targetHost: string; + targetPort: string; +}; + +export const EMPTY_SSH_FORWARD_ROW: SshForwardDraftRow = { + kind: "local", + listenPort: "", + bindHost: "", + targetHost: "", + targetPort: "", +}; + +/** True when a row holds nothing but its default kind: an empty row is not a forward. */ +export function sshForwardRowEmpty(row: SshForwardDraftRow): boolean { + return ( + row.listenPort.trim() === "" && + row.bindHost.trim() === "" && + row.targetHost.trim() === "" && + row.targetPort.trim() === "" + ); +} + +const PORT_RE = /^\d{1,5}$/; + +function isPortText(value: string): boolean { + if (!PORT_RE.test(value.trim())) return false; + const n = Number(value.trim()); + return n >= 1 && n <= 65535; +} + +/** The host hygiene the wire grammar also refuses: no option-like start, no whitespace or control characters, bounded length. */ +function isHostText(value: string): boolean { + const host = value.trim(); + return host.length > 0 && host.length <= 253 && !host.startsWith("-") && !/[\s\p{Cc}]/u.test(host); +} + +/** The first problem with one row, in the voice of the field it belongs to. */ +export function sshForwardRowProblems(row: SshForwardDraftRow): string | null { + if (!isPortText(row.listenPort)) return "Enter a listen port from 1 to 65535."; + if (row.bindHost.trim() !== "" && !isHostText(row.bindHost)) + return "Enter the bind host as a plain name or address, or leave it blank for loopback."; + if (row.kind !== "dynamic") { + if (!isHostText(row.targetHost)) return "Enter the host to reach through the connection."; + if (!isPortText(row.targetPort)) return "Enter a target port from 1 to 65535."; + } + return null; +} + +/** + * The whole-list answer: per-row problems first, then the LIST rules + * (duplicate binds, the cap). Returns null when the list is ready to launch; + * empty rows (the untyped remainder of "Add forward") do not count against + * anything. + */ +export function sshForwardListProblems(rows: readonly SshForwardDraftRow[]): string | null { + const filled = rows.filter((row) => !sshForwardRowEmpty(row)); + if (filled.length === 0) return null; + if (filled.length > SSH_MAX_FORWARD_ROWS) + return `A connection carries at most ${String(SSH_MAX_FORWARD_ROWS)} port forwards.`; + for (const row of filled) { + const problem = sshForwardRowProblems(row); + if (problem) return problem; + } + const listens = new Set(); + for (const row of filled) { + const key = `${row.bindHost.trim().toLowerCase()}|${row.listenPort.trim()}`; + if (listens.has(key)) return "Two forwards can't bind the same port. Remove one or change a port."; + listens.add(key); + } + return null; +} + +/** The list as the wire wants it (empty rows dropped, numbers parsed, blanks null). Call only when {@link sshForwardListProblems} says the list is ready. */ +export function sshForwardRowsToWire(rows: readonly SshForwardDraftRow[]): SshForwardRow[] { + return rows + .filter((row) => !sshForwardRowEmpty(row)) + .map((row) => ({ + kind: row.kind, + listenPort: Number(row.listenPort.trim()), + bindHost: row.bindHost.trim() === "" ? null : row.bindHost.trim(), + targetHost: row.kind === "dynamic" || row.targetHost.trim() === "" ? null : row.targetHost.trim(), + targetPort: row.kind === "dynamic" || row.targetPort.trim() === "" ? null : Number(row.targetPort.trim()), + })); +} + +/** A stored preset's rows back into form spelling. */ +export function sshForwardRowsFromWire(rows: readonly SshForwardRow[] | undefined): SshForwardDraftRow[] { + return (rows ?? []).map((row) => ({ + kind: row.kind, + listenPort: String(row.listenPort), + bindHost: row.bindHost ?? "", + targetHost: row.targetHost ?? "", + targetPort: row.targetPort === null ? "" : String(row.targetPort), + })); +} diff --git a/apps/server/web/src/lib/ssh-preset.ts b/apps/server/web/src/lib/ssh-preset.ts index 7f3e8abd6..7fd90a6e9 100644 --- a/apps/server/web/src/lib/ssh-preset.ts +++ b/apps/server/web/src/lib/ssh-preset.ts @@ -1,4 +1,5 @@ import { type SshSessionDraft, sshDestination, sshSessionDraft } from "@/components/connect/ssh-session-draft"; +import { sshForwardRowsFromWire, sshForwardRowsToWire } from "@/lib/ssh-forwards"; import type { SshPreset, SshPresetPayload } from "@/types/preset"; export const SSH_AUTHENTICATION_LABELS = { @@ -19,6 +20,7 @@ export function sshDraftFromPreset(preset: SshPreset): SshSessionDraft { keyHome: preset.authentication === "remote" ? (preset.keyHome ?? "") : "", selections: preset.authentication === "remote" && preset.keyHome ? { [preset.keyHome]: [...preset.fingerprints] } : {}, + forwards: sshForwardRowsFromWire(preset.forwards), }; } @@ -37,5 +39,6 @@ export function sshPresetPayload(draft: SshSessionDraft, name: string, descripti authentication, keyHome: authentication === "remote" ? draft.keyHome : null, fingerprints: authentication === "remote" ? (draft.selections[draft.keyHome] ?? []) : [], + forwards: sshForwardRowsToWire(draft.forwards), }; } diff --git a/apps/server/web/src/lib/ssh.ts b/apps/server/web/src/lib/ssh.ts index 4845c22f5..18a835af0 100644 --- a/apps/server/web/src/lib/ssh.ts +++ b/apps/server/web/src/lib/ssh.ts @@ -126,6 +126,26 @@ export interface SshAliasesView { truncated: boolean; } +/** + * One declared port-forward row, mirroring the server's `SshForwardWire` + * (docs/security.md's SSH section; spec 2026-10-15). Hosts are canonical + * names; nulls mean the omitted spelling (loopback for local/dynamic, the + * destination's policy for remote). Checked against the route schema in + * `apps/server/api/src/api/ssh/launch.route.ts`. + */ +export interface SshForwardRow { + /** local (-L, binds on the connecting machine), remote (-R, binds on the destination), dynamic (-D SOCKS) */ + kind: "local" | "remote" | "dynamic"; + /** Port to bind, 1..65535, always explicit */ + listenPort: number; + /** Listen bind host; null is the omitted default */ + bindHost: string | null; + /** Target host through the connection; null only for dynamic */ + targetHost: string | null; + /** Target port; null only for dynamic */ + targetPort: number | null; +} + /** `POST /api/ssh/launch` body: name is the pane's display name, optional. */ export interface SshLaunchRequest { /** One-use native signing preparation, never key material. */ @@ -142,6 +162,8 @@ export interface SshLaunchRequest { expectedDestination?: string; /** Pane display name */ name?: string; + /** User-declared port forwards this connection carries (spec 2026-10-15); omitted launches none */ + forwards?: SshForwardRow[]; } /** diff --git a/apps/server/web/src/routes/__tests__/nodes-detail.test.tsx b/apps/server/web/src/routes/__tests__/nodes-detail.test.tsx index 59adefa8b..185991655 100644 --- a/apps/server/web/src/routes/__tests__/nodes-detail.test.tsx +++ b/apps/server/web/src/routes/__tests__/nodes-detail.test.tsx @@ -40,6 +40,7 @@ function enrolledNode(overrides: Partial = {}): NodeDetail { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, ...overrides, }; diff --git a/apps/server/web/src/routes/__tests__/nodes-page-filters.test.tsx b/apps/server/web/src/routes/__tests__/nodes-page-filters.test.tsx index 6997faf9e..327afab75 100644 --- a/apps/server/web/src/routes/__tests__/nodes-page-filters.test.tsx +++ b/apps/server/web/src/routes/__tests__/nodes-page-filters.test.tsx @@ -33,6 +33,7 @@ const NODES: Node[] = [ canRetire: true, canLaunch: true, sshEnabled: false, + sshWideForwards: false, allowedDirs: [], capabilities: [], harnesses: [], @@ -58,6 +59,7 @@ const NODES: Node[] = [ canRetire: true, canLaunch: true, sshEnabled: false, + sshWideForwards: false, allowedDirs: [], capabilities: [], harnesses: [], diff --git a/apps/server/web/src/routes/__tests__/nodes-page-gate.test.tsx b/apps/server/web/src/routes/__tests__/nodes-page-gate.test.tsx index 84df1c763..c28ad7742 100644 --- a/apps/server/web/src/routes/__tests__/nodes-page-gate.test.tsx +++ b/apps/server/web/src/routes/__tests__/nodes-page-gate.test.tsx @@ -142,6 +142,7 @@ describe("/nodes add-node gating", () => { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, }, ], diff --git a/apps/server/web/src/routes/__tests__/nodes-tabs.test.tsx b/apps/server/web/src/routes/__tests__/nodes-tabs.test.tsx index ddb6938c4..d51078527 100644 --- a/apps/server/web/src/routes/__tests__/nodes-tabs.test.tsx +++ b/apps/server/web/src/routes/__tests__/nodes-tabs.test.tsx @@ -29,6 +29,7 @@ const NODE = { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, }; diff --git a/apps/server/web/src/types/preset.ts b/apps/server/web/src/types/preset.ts index c2a856ac6..52e1f6da4 100644 --- a/apps/server/web/src/types/preset.ts +++ b/apps/server/web/src/types/preset.ts @@ -4,6 +4,8 @@ * (apps/server/api/src/api/models.ts): it carries the fields the frontend uses * and omits `userId`. */ +import type { SshForwardRow } from "@/lib/ssh"; + export interface AgentPreset { kind: "agent"; id: string; @@ -58,6 +60,8 @@ export interface SshPreset { keyHome: string | null; /** Selected public-key fingerprints (remote mode only); private keys stay on their machine */ fingerprints: string[]; + /** Declared port forwards this preset carries to every launch; empty when it stores none (spec 2026-10-15) */ + forwards: SshForwardRow[]; /** ISO 8601 creation timestamp */ createdAt: string; /** ISO 8601 update timestamp */ @@ -66,5 +70,13 @@ export interface SshPreset { export type SshPresetPayload = Pick< SshPreset, - "name" | "description" | "destination" | "alias" | "resolutionAlias" | "authentication" | "keyHome" | "fingerprints" + | "name" + | "description" + | "destination" + | "alias" + | "resolutionAlias" + | "authentication" + | "keyHome" + | "fingerprints" + | "forwards" > & { kind: "ssh"; nodeId: string; expectedDestination?: string }; diff --git a/packages/node-admin/src/__tests__/node-allowed-dirs.test.tsx b/packages/node-admin/src/__tests__/node-allowed-dirs.test.tsx index eaea6ebfd..c1f4f021b 100644 --- a/packages/node-admin/src/__tests__/node-allowed-dirs.test.tsx +++ b/packages/node-admin/src/__tests__/node-allowed-dirs.test.tsx @@ -34,6 +34,7 @@ function node(over: Partial = {}): Node { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, ...over, }; diff --git a/packages/node-admin/src/__tests__/node-maintenance-card.test.tsx b/packages/node-admin/src/__tests__/node-maintenance-card.test.tsx index 9c91ec548..ac297b26b 100644 --- a/packages/node-admin/src/__tests__/node-maintenance-card.test.tsx +++ b/packages/node-admin/src/__tests__/node-maintenance-card.test.tsx @@ -41,6 +41,7 @@ function node(overrides: Partial = {}): NodeDetail { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, ...overrides, }; diff --git a/packages/node-admin/src/__tests__/node-runtime-card.test.tsx b/packages/node-admin/src/__tests__/node-runtime-card.test.tsx index 03310153a..5db4ec467 100644 --- a/packages/node-admin/src/__tests__/node-runtime-card.test.tsx +++ b/packages/node-admin/src/__tests__/node-runtime-card.test.tsx @@ -34,6 +34,7 @@ const base: NodeDetail = { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, }; diff --git a/packages/node-admin/src/__tests__/node-service-card.test.tsx b/packages/node-admin/src/__tests__/node-service-card.test.tsx index 1c983a6c5..7f70e0b35 100644 --- a/packages/node-admin/src/__tests__/node-service-card.test.tsx +++ b/packages/node-admin/src/__tests__/node-service-card.test.tsx @@ -49,6 +49,7 @@ const base: NodeDetail = { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, }; diff --git a/packages/node-admin/src/__tests__/ssh-trust-card.test.tsx b/packages/node-admin/src/__tests__/ssh-trust-card.test.tsx index 8bc8f846a..eaec6357b 100644 --- a/packages/node-admin/src/__tests__/ssh-trust-card.test.tsx +++ b/packages/node-admin/src/__tests__/ssh-trust-card.test.tsx @@ -44,6 +44,7 @@ function node(over: Partial = {}): NodeDetail { maintenanceAt: null, maintenanceSource: null, sshEnabled: false, + sshWideForwards: false, held: null, ...over, }; diff --git a/packages/node-admin/src/types/node.ts b/packages/node-admin/src/types/node.ts index 01aa16e32..d71a38dff 100644 --- a/packages/node-admin/src/types/node.ts +++ b/packages/node-admin/src/types/node.ts @@ -177,6 +177,14 @@ export interface Node { * machine visible but disabled, never hidden. */ sshEnabled: boolean; + /** + * Whether SSH port forwards on this machine may bind non-loopback + * addresses (spec 2026-10-15 §6). Off by default: forwards bind loopback + * only, and the launch refuses a wide bind while this is false. The + * launch form reads it to decide whether to offer the bind-host field at + * all. + */ + sshWideForwards: boolean; /** * This node is connected but REFUSED — held open for exactly one * command and offline for every other purpose (spec 2026-09-15 §5.3). From 0e22197218a1423511c8c195f82f84eab33a3afb Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 16:59:22 -0700 Subject: [PATCH 07/35] fix(ssh): move the backup era strip set with the migration pin The backup engine refuses a snapshot carrying migrations it does not know, and the era fixture strips the ledger tail that the pin names: advancing the pin without moving the strip set leaves an intermediate state where the restored snapshot keeps the newer DDL under a re-applied older name. One commit is the fixture's own rule. Co-Authored-By: Claude --- .../src/routes/__tests__/status-page.test.tsx | 1 + .../__tests__/backup-cli-subprocess.test.ts | 26 ++++++++++++------- .../api/src/services/backups/database.ts | 2 +- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/apps/node/web/src/routes/__tests__/status-page.test.tsx b/apps/node/web/src/routes/__tests__/status-page.test.tsx index c820da5af..f99f07cc6 100644 --- a/apps/node/web/src/routes/__tests__/status-page.test.tsx +++ b/apps/node/web/src/routes/__tests__/status-page.test.tsx @@ -50,6 +50,7 @@ function localView(): NodeDetail { maintenanceAt: null, maintenanceSource: null, sshEnabled: true, + sshWideForwards: false, held: null, serverUrl: "http://127.0.0.1:3080", // No `runtime`: the page then skips the Runtime/Service cards, and this diff --git a/apps/server/api/src/commands/__tests__/backup-cli-subprocess.test.ts b/apps/server/api/src/commands/__tests__/backup-cli-subprocess.test.ts index 0192304eb..ac33cc75c 100644 --- a/apps/server/api/src/commands/__tests__/backup-cli-subprocess.test.ts +++ b/apps/server/api/src/commands/__tests__/backup-cli-subprocess.test.ts @@ -392,21 +392,21 @@ test("real entry: available older upgrade snapshot restores and migrates before // are stripped (rows AND their DDL) so the snapshot ends where an install of // that era would; removing one from the middle while a later row stands // above it is exactly the non-contiguous history kysely refuses to boot. - // WHY 0047+0048+0049+0050+0051+0052 together: this era's six migrations are - // the tail of the registered ledger, so the prefix runs contiguously through - // 0046 and boot re-applies all six. Advancing LATEST_BACKUP_MIGRATION (0052) - // means the strip set moves with it in the same commit. + // WHY 0047..0056 together: this era's ten migrations are the tail of the + // registered ledger, so the prefix runs contiguously through 0046 and boot + // re-applies all ten. Advancing LATEST_BACKUP_MIGRATION (0056) means the + // strip set moves with it in the same commit. const migrationRows = db .query( - "SELECT * FROM kysely_migration WHERE name IN ('0047-node-ssh-enabled','0048-ssh-launch-and-saved-hosts','0049-ssh-relay-identity','0050-ssh-grants','0051-ssh-request-destination','0052-subshell-key-home','0053-drop-ssh-grants','0054-ssh-presets') ORDER BY name", + "SELECT * FROM kysely_migration WHERE name IN ('0047-node-ssh-enabled','0048-ssh-launch-and-saved-hosts','0049-ssh-relay-identity','0050-ssh-grants','0051-ssh-request-destination','0052-subshell-key-home','0053-drop-ssh-grants','0054-ssh-presets','0055-node-ssh-wide-forwards','0056-ssh-preset-forwards') ORDER BY name", ) .all() as { name: string; timestamp: string }[]; db.exec( - "ALTER TABLE nodes DROP COLUMN ssh_enabled_at; ALTER TABLE nodes DROP COLUMN ssh_enabled; DROP TABLE ssh_presets; DROP TABLE ssh_recent_destinations; ALTER TABLE subshells DROP COLUMN ssh; ALTER TABLE identities DROP COLUMN signing_public_key; ALTER TABLE nodes DROP COLUMN ssh_fingerprint; ALTER TABLE subshells DROP COLUMN key_home_node_id; DROP TABLE ssh_host_pins; DELETE FROM kysely_migration WHERE name IN ('0047-node-ssh-enabled','0048-ssh-launch-and-saved-hosts','0049-ssh-relay-identity','0050-ssh-grants','0051-ssh-request-destination','0052-subshell-key-home','0053-drop-ssh-grants','0054-ssh-presets'); CREATE TABLE migration_restore_probe(value TEXT); INSERT INTO migration_restore_probe VALUES ('older snapshot');", + "ALTER TABLE nodes DROP COLUMN ssh_wide_forwards; ALTER TABLE nodes DROP COLUMN ssh_enabled_at; ALTER TABLE nodes DROP COLUMN ssh_enabled; DROP TABLE ssh_presets; DROP TABLE ssh_recent_destinations; ALTER TABLE subshells DROP COLUMN ssh; ALTER TABLE identities DROP COLUMN signing_public_key; ALTER TABLE nodes DROP COLUMN ssh_fingerprint; ALTER TABLE subshells DROP COLUMN key_home_node_id; DROP TABLE ssh_host_pins; DELETE FROM kysely_migration WHERE name IN ('0047-node-ssh-enabled','0048-ssh-launch-and-saved-hosts','0049-ssh-relay-identity','0050-ssh-grants','0051-ssh-request-destination','0052-subshell-key-home','0053-drop-ssh-grants','0054-ssh-presets','0055-node-ssh-wide-forwards','0056-ssh-preset-forwards'); CREATE TABLE migration_restore_probe(value TEXT); INSERT INTO migration_restore_probe VALUES ('older snapshot');", ); db.query("VACUUM INTO ?").run(snapshot); db.exec( - "ALTER TABLE nodes ADD COLUMN ssh_enabled integer NOT NULL DEFAULT 0; ALTER TABLE nodes ADD COLUMN ssh_enabled_at text; CREATE TABLE ssh_recent_destinations(id TEXT PRIMARY KEY, owner_user_id TEXT NOT NULL REFERENCES user(id), destination TEXT NOT NULL, alias TEXT, node_id TEXT NOT NULL, last_connect_at TEXT NOT NULL); CREATE TABLE ssh_presets(id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES user(id), name TEXT NOT NULL, description TEXT, node_id TEXT REFERENCES nodes(id) ON DELETE SET NULL, destination TEXT NOT NULL, alias TEXT, authentication TEXT NOT NULL, key_home TEXT REFERENCES nodes(id) ON DELETE SET NULL, fingerprints TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL); ALTER TABLE subshells ADD COLUMN ssh text; ALTER TABLE identities ADD COLUMN signing_public_key text; ALTER TABLE nodes ADD COLUMN ssh_fingerprint text; ALTER TABLE subshells ADD COLUMN key_home_node_id text; CREATE TABLE ssh_host_pins(id TEXT PRIMARY KEY, owner_user_id TEXT NOT NULL REFERENCES user(id), destination TEXT NOT NULL, host_key TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL); UPDATE migration_restore_probe SET value='newer destination';", + "ALTER TABLE nodes ADD COLUMN ssh_enabled integer NOT NULL DEFAULT 0; ALTER TABLE nodes ADD COLUMN ssh_enabled_at text; CREATE TABLE ssh_recent_destinations(id TEXT PRIMARY KEY, owner_user_id TEXT NOT NULL REFERENCES user(id), destination TEXT NOT NULL, alias TEXT, node_id TEXT NOT NULL, last_connect_at TEXT NOT NULL); CREATE TABLE ssh_presets(id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES user(id), name TEXT NOT NULL, description TEXT, node_id TEXT REFERENCES nodes(id) ON DELETE SET NULL, destination TEXT NOT NULL, alias TEXT, authentication TEXT NOT NULL, key_home TEXT REFERENCES nodes(id) ON DELETE SET NULL, fingerprints TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL); ALTER TABLE ssh_presets ADD COLUMN forwards text; ALTER TABLE nodes ADD COLUMN ssh_wide_forwards integer NOT NULL DEFAULT 0; ALTER TABLE subshells ADD COLUMN ssh text; ALTER TABLE identities ADD COLUMN signing_public_key text; ALTER TABLE nodes ADD COLUMN ssh_fingerprint text; ALTER TABLE subshells ADD COLUMN key_home_node_id text; CREATE TABLE ssh_host_pins(id TEXT PRIMARY KEY, owner_user_id TEXT NOT NULL REFERENCES user(id), destination TEXT NOT NULL, host_key TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL); UPDATE migration_restore_probe SET value='newer destination';", ); for (const row of migrationRows) db.query("INSERT INTO kysely_migration(name,timestamp) VALUES (?,?)").run(row.name, row.timestamp); @@ -420,7 +420,7 @@ test("real entry: available older upgrade snapshot restores and migrates before expect( offline .query( - "SELECT name FROM kysely_migration WHERE name IN ('0047-node-ssh-enabled','0048-ssh-launch-and-saved-hosts','0049-ssh-relay-identity','0050-ssh-grants','0051-ssh-request-destination','0052-subshell-key-home','0053-drop-ssh-grants','0054-ssh-presets')", + "SELECT name FROM kysely_migration WHERE name IN ('0047-node-ssh-enabled','0048-ssh-launch-and-saved-hosts','0049-ssh-relay-identity','0050-ssh-grants','0051-ssh-request-destination','0052-subshell-key-home','0053-drop-ssh-grants','0054-ssh-presets','0055-node-ssh-wide-forwards','0056-ssh-preset-forwards')", ) .all(), ).toEqual([]); @@ -430,7 +430,7 @@ test("real entry: available older upgrade snapshot restores and migrates before expect( migrated .query( - "SELECT name FROM kysely_migration WHERE name IN ('0047-node-ssh-enabled','0048-ssh-launch-and-saved-hosts','0049-ssh-relay-identity','0050-ssh-grants','0051-ssh-request-destination','0052-subshell-key-home','0053-drop-ssh-grants','0054-ssh-presets') ORDER BY name", + "SELECT name FROM kysely_migration WHERE name IN ('0047-node-ssh-enabled','0048-ssh-launch-and-saved-hosts','0049-ssh-relay-identity','0050-ssh-grants','0051-ssh-request-destination','0052-subshell-key-home','0053-drop-ssh-grants','0054-ssh-presets','0055-node-ssh-wide-forwards','0056-ssh-preset-forwards') ORDER BY name", ) .all(), ).toEqual([ @@ -442,6 +442,8 @@ test("real entry: available older upgrade snapshot restores and migrates before { name: "0052-subshell-key-home" }, { name: "0053-drop-ssh-grants" }, { name: "0054-ssh-presets" }, + { name: "0055-node-ssh-wide-forwards" }, + { name: "0056-ssh-preset-forwards" }, ]); expect(migrated.query("SELECT value FROM migration_restore_probe").get()).toEqual({ value: "older snapshot" }); expect( @@ -464,6 +466,12 @@ test("real entry: available older upgrade snapshot restores and migrates before expect(migrated.query("SELECT name FROM sqlite_schema WHERE name='ssh_host_pins'").get()).toEqual({ name: "ssh_host_pins", }); + expect(migrated.query("SELECT name FROM pragma_table_info('nodes') WHERE name='ssh_wide_forwards'").get()).toEqual({ + name: "ssh_wide_forwards", + }); + expect(migrated.query("SELECT name FROM pragma_table_info('ssh_presets') WHERE name='forwards'").get()).toEqual({ + name: "forwards", + }); for (const removedTable of ["ssh_key_grants", "ssh_grant_requests"]) { expect(migrated.query("SELECT name FROM sqlite_schema WHERE name=?").get(removedTable)).toBeNull(); } diff --git a/apps/server/api/src/services/backups/database.ts b/apps/server/api/src/services/backups/database.ts index dc9229979..b96f2e69a 100644 --- a/apps/server/api/src/services/backups/database.ts +++ b/apps/server/api/src/services/backups/database.ts @@ -11,7 +11,7 @@ import { assertSafeHostPath } from "./paths.js"; import type { BackupAdmin } from "./types.js"; /** Latest supported application migration; its registration is pinned by the engine tests. */ -export const LATEST_BACKUP_MIGRATION = "0054-ssh-presets"; +export const LATEST_BACKUP_MIGRATION = "0056-ssh-preset-forwards"; /** Snapshot a live SQLite database using VACUUM INTO, including committed WAL state. */ export async function snapshotDatabase(sourcePath: string, destinationPath: string): Promise { From a4dd3566c677ab8df108d77c125c035f14a4d375 Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 16:59:35 -0700 Subject: [PATCH 08/35] docs(ssh): port forwards in the guides, the security accounting, and changesets Co-Authored-By: Claude --- .changeset/document-ssh-port-forwards.md | 5 ++++ .changeset/ssh-port-forwards.md | 5 ++++ .claude/rules/security-context.md | 4 +-- apps/docs/content/docs/guides/ssh.mdx | 23 ++++++++++++++- apps/docs/content/docs/nodes/manage.mdx | 2 ++ apps/docs/content/docs/reference/ports.mdx | 3 ++ docs/security.md | 33 ++++++++++++++++++++-- 7 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 .changeset/document-ssh-port-forwards.md create mode 100644 .changeset/ssh-port-forwards.md diff --git a/.changeset/document-ssh-port-forwards.md b/.changeset/document-ssh-port-forwards.md new file mode 100644 index 000000000..4fdee620f --- /dev/null +++ b/.changeset/document-ssh-port-forwards.md @@ -0,0 +1,5 @@ +--- +"@internal/docs": minor +--- + +Document SSH port forwards: the kinds and the wide-forwards node switch in the SSH guide, the toggle on the node management page, and forwarded ports in the ports reference. diff --git a/.changeset/ssh-port-forwards.md b/.changeset/ssh-port-forwards.md new file mode 100644 index 000000000..9734574c7 --- /dev/null +++ b/.changeset/ssh-port-forwards.md @@ -0,0 +1,5 @@ +--- +"@internal/server": minor +--- + +SSH connections can carry port forwards you declare: local (`-L`), remote (`-R`), and dynamic SOCKS (`-D`) rows in the connection form and in SSH presets. Forwards bind the connecting node's loopback address unless its owner turns on **Allow wide port forwards** on the node's SSH card, and a remote forward's bind answers to the destination's own SSH server policy. A forward opens with the subshell and closes when it ends. Forwards that came from the connecting account's SSH configuration files are still refused by name; only rows declared at launch open a port. diff --git a/.claude/rules/security-context.md b/.claude/rules/security-context.md index 95db9a510..3483d3fcd 100644 --- a/.claude/rules/security-context.md +++ b/.claude/rules/security-context.md @@ -124,7 +124,7 @@ for the route inventory and trust limits. `/api/ssh/*` requires a browser sessio SSH terminal input additionally requires the pane owner and refuses all bearer actors. The node SSH switch is owner-only (admin for the built-in node), including implicit enablement on launch. It controls outgoing SSH and agent-key supply, -not incoming access. Relays require access to both nodes; the UI requires key selection, while the +not incoming access. Port forwards are declared per connection, never config-derived (that refusal stands): loopback binds by default, and a non-loopback bind needs the node's owner-only wide-forwards switch (`node.ssh_wide_forwards.update`); remote-forward binds answer to the destination's sshd policy. Relays require access to both nodes; the UI requires key selection, while the cookie API can select the current roster when selection is omitted. Private keys stay at the source. Bounded configuration scanning does not prevent all local execution during `ssh -G`; trust the connecting account’s SSH configuration. First-use destination confirmation cannot replace @@ -186,7 +186,7 @@ Per-app mechanics (command lists and shapes, assistant screens, the reset path, Read via `GET /api/audit?limit=50` (admin). Rules: auth-family metadata carries method and ids, NEVER tokens/emails/IPs/UAs; the user-management family names its subject by email by convention (§3). Sign-in rows are SUCCESS only (failures write nothing so the trail can't be spam-DoSed); `auth.sign_out` is one row per session ended; trusted-origin probes write no row. -Auth `auth.sign_in` / `auth.sign_out` · users/keys `user.create|role_change|password_reset|disabled_change|approve|reject`, `system-key.create|enable|disable|delete`, `emergency_login.rewrite_credential`, `setup_key.create` (no metadata) / `setup_key.revoke`, `settings.update` · auth providers `auth_provider.create|update|delete` (fields + issuer, never the secret) · subshells `subshell.create|terminate|restart|preset_switch|delete` · transfers `transfer.create|complete` (node ids, paths, byte and entry counts, sync flag, outcome; never contents) · nodes `node.enroll|delete|rename|key_rotate|reregister_key|reregister|allowed_dirs.update|config.update|maintenance.update|ssh_enabled.update|ssh_identity.register (signing-key bootstrap over the link, ids + via only, never the key)|ssh_machine_pin.repair (verified peer replacement; ids only)|ssh_identity.repair (admin-verified server disk identity; metadata null)|ssh_host_pin.create|ssh_host_pin.delete (canonical destination and public host fingerprint only)|ssh_relay.open|ssh_relay.close (plane rows, actorUserId null, named reason word, fingerprint COUNT only)|ssh_upgrade.run (the "Set up Subshell here" act, Task 14: pane/B/A ids and relay ref, canonical destination, exec id, outcome + service-chosen stage word; the minted setup key and ALL installer output appear in NO row and NO refusal copy)|logging.update|update|update.unknown|shares_set|local_share_changed|service`, and the SSH launcher's `ssh.launch` `{nodeId, destination, subshellId, relayRef on a relay launch}` (success only, written after the pane spawned; the canonical `host:port`, never config or key material; SSH preset CRUD unaudited, the prompts precedent) · server `server.config.update|restart|logging.update|autostart.update|update|supervision.request` · plugins/networks/installers `plugin.install|enable|disable|uninstall|unpublish`, `network.configure|install|join|publish|unpublish|leave`, `agent.install`, `tmux.install`. Per-event metadata detail: §10. +Auth `auth.sign_in` / `auth.sign_out` · users/keys `user.create|role_change|password_reset|disabled_change|approve|reject`, `system-key.create|enable|disable|delete`, `emergency_login.rewrite_credential`, `setup_key.create` (no metadata) / `setup_key.revoke`, `settings.update` · auth providers `auth_provider.create|update|delete` (fields + issuer, never the secret) · subshells `subshell.create|terminate|restart|preset_switch|delete` · transfers `transfer.create|complete` (node ids, paths, byte and entry counts, sync flag, outcome; never contents) · nodes `node.enroll|delete|rename|key_rotate|reregister_key|reregister|allowed_dirs.update|config.update|maintenance.update|ssh_enabled.update|ssh_wide_forwards.update|ssh_identity.register (signing-key bootstrap over the link, ids + via only, never the key)|ssh_machine_pin.repair (verified peer replacement; ids only)|ssh_identity.repair (admin-verified server disk identity; metadata null)|ssh_host_pin.create|ssh_host_pin.delete (canonical destination and public host fingerprint only)|ssh_relay.open|ssh_relay.close (plane rows, actorUserId null, named reason word, fingerprint COUNT only)|ssh_upgrade.run (the "Set up Subshell here" act, Task 14: pane/B/A ids and relay ref, canonical destination, exec id, outcome + service-chosen stage word; the minted setup key and ALL installer output appear in NO row and NO refusal copy)|logging.update|update|update.unknown|shares_set|local_share_changed|service`, and the SSH launcher's `ssh.launch` `{nodeId, destination, subshellId, relayRef on a relay launch, bounded forwards summary}` (success only, written after the pane spawned; the canonical `host:port` and the declared rows' ports and hosts, never config or key material; SSH preset CRUD unaudited, the prompts precedent) · server `server.config.update|restart|logging.update|autostart.update|update|supervision.request` · plugins/networks/installers `plugin.install|enable|disable|uninstall|unpublish`, `network.configure|install|join|publish|unpublish|leave`, `agent.install`, `tmux.install`. Per-event metadata detail: §10. ## Deliberate non-validation & accepted risks diff --git a/apps/docs/content/docs/guides/ssh.mdx b/apps/docs/content/docs/guides/ssh.mdx index f26be3349..c0ae50743 100644 --- a/apps/docs/content/docs/guides/ssh.mdx +++ b/apps/docs/content/docs/guides/ssh.mdx @@ -1,6 +1,6 @@ --- title: "Connect over SSH" -description: "Open an SSH terminal through a node, choose keys or a password, verify unfamiliar destinations, and reuse connection choices as SSH presets." +description: "Open an SSH terminal through a node, choose keys or a password, verify unfamiliar destinations, forward ports through the connection, and reuse connection choices as SSH presets." --- Open an SSH terminal on another machine through one of your Subshell nodes. @@ -116,6 +116,27 @@ Presets never store your private key, key file, passphrase, native approval, or If you select **Save as preset** while launching and the connection fails, the saved preset remains available. Retrying the same choices doesn't create another copy. Previously remembered destinations aren't converted into named presets; create presets for the connections you want to reuse. +## Port forwards + +Some services, like a database or an admin API, live behind the destination and have no other route. A connection can open a port that reaches them: in the connection form, choose **Advanced SSH options**, then **Add port forward**. Every forward belongs to that connection, and the port closes when the subshell ends. + +| Kind | Where the port opens | What it does | +| --- | --- | --- | +| **Local** (`-L`) | The connecting node | Programs on that node reach a host the destination can see. | +| **Remote** (`-R`) | The destination | The destination reaches a host the connecting node can see. The destination's own SSH server configuration decides whether and how it binds the port. | +| **Dynamic SOCKS** (`-D`) | The connecting node | Tools configured to use the SOCKS proxy pick their own destinations at runtime and reach them through the connection. | + +Fill in a **Local port** (or **Remote port** for a remote forward), and for local and remote kinds the **Target host** and **Target port** you want to reach. A local or dynamic forward can also name a **Bind address**; left blank, the port binds the node's loopback address, so only programs on that node use it. + +A non-loopback bind lets other machines route through the port into whatever the connection reaches. That's a decision about the machine, not the connection, so it stays off until the node's owner turns on **Allow wide port forwards** on the node's SSH card. A launch that asks for a wide bind is refused until then; turning the setting off again refuses future wide binds while forwards that are already running keep going until their subshell closes. + +If the port you asked for is already taken on the node, the destination's shell still opens, and the terminal prints SSH's warning that the forward didn't bind. + +A preset carries forwards beside its other connection choices, so connecting from the preset fills the same rows; you can still edit or remove them before you start. The forward rows themselves are not stored anywhere else, and every launch records them in the audit trail with the connection. + +> [!note] +> A forward extends the reach your SSH login already grants; it doesn't add a new credential or a new trust. Forwards end with the subshell: to reopen a port after a disconnect, start a new SSH subshell. + ## Make the destination a node As the SSH subshell's owner, open its **⋯** menu and choose **Set up Subshell here…** when available. Review the confirmation before running the installer: it installs Subshell on the destination and enrolls it as a node you own. Your SSH subshell keeps running. diff --git a/apps/docs/content/docs/nodes/manage.mdx b/apps/docs/content/docs/nodes/manage.mdx index cc9459a27..a1ffcce16 100644 --- a/apps/docs/content/docs/nodes/manage.mdx +++ b/apps/docs/content/docs/nodes/manage.mdx @@ -27,6 +27,8 @@ Only the node’s owner can change this setting. An administrator manages it for You can also enable it when starting an SSH subshell: the connection form explains the change before you submit. See [Connect over SSH](/guides/ssh). +With SSH allowed, the card gains **Allow wide port forwards**. While it's off, port forwards opened from this node bind the node's own loopback address, so only programs on that node can use them. Turning it on lets a launch bind a port other machines can reach, which routes their traffic through that connection. It's off by default and sits behind the same owner-only gate as **Allow SSH**; turning it off affects new launches, while forwards already running keep going until their subshell closes. + ## Service and logs Where you have management access, review the service and log sections. A node may be online from a terminal process even when no service is installed. diff --git a/apps/docs/content/docs/reference/ports.mdx b/apps/docs/content/docs/reference/ports.mdx index f8b4662ff..23c2ada83 100644 --- a/apps/docs/content/docs/reference/ports.mdx +++ b/apps/docs/content/docs/reference/ports.mdx @@ -14,6 +14,7 @@ Identify the network listeners and connection paths used by Subshell. | Browser terminal stream | WebSocket through the control plane. | | HTTPS proxy or network publication | Port selected by that frontend, forwarding to the server. | | Node management dashboard | Loopback-only local listener; no login. Other local users can reach it. | +| SSH port forward | A port and address chosen at launch, bound on the connecting node (local and dynamic kinds) or by the destination's SSH server (remote kind). Subshell fixes none of them. | The server serves its API and production web interface on one port. Browsers don't need a route to every execution node. @@ -23,6 +24,8 @@ Allow the server port only on the networks intended to reach it. A wildcard bind Enrolled nodes connect outward. Avoid exposing their local management dashboard as a public endpoint. +SSH port forwards follow the same shape: a forward that binds loopback adds no listener other machines can reach. A wide bind (allowed only when the node's owner turns on **Allow wide port forwards**) does, so firewall choices on that node's network apply to it. See [Connect over SSH](/guides/ssh#port-forwards). + ## Development The server API defaults to 3080 and the web development server to 5174. The documentation development server uses 3400; the marketing site uses 3401. The isolated end-to-end stack uses 3199. diff --git a/docs/security.md b/docs/security.md index e124688d7..46a612b55 100644 --- a/docs/security.md +++ b/docs/security.md @@ -1220,7 +1220,7 @@ connecting node's credentials. Restart still requires a new SSH connection. The connection form is **New subshell → SSH terminal**. **Settings → Presets → SSH** manages saved connection choices; it has no host-key editor. SSH presets retain -the resolved destination, original SSH resolution token, and authentication choice. +the resolved destination, original SSH resolution token, authentication choice, and declared port forwards. The token preserves alias-scoped routing and key settings; an optional `expectedDestination` constraint checks the resolved username, host, and port before save/clone, local-key preparation, trust discovery/acceptance, or launch @@ -1238,6 +1238,7 @@ keys. It does not configure `sshd` or grant incoming SSH access. | `/api/ssh/readiness`, `/aliases`, `/resolve`, `/identities` | Browser session required. Node discovery and operations follow the caller's node access and the applicable readiness gates; alias/configuration data and public agent identities are not anonymous information. | | `POST /api/ssh/launch` | Browser session and launch access to the connecting node; relays also require launch access to the key source. Both participants must pass SSH and readiness checks. | | `PUT /api/nodes/:id/ssh-enabled` | Browser session and node owner, or administrator for the built-in node. An edit share alone cannot enable SSH. The setting can be changed while a node is offline and is reconciled on reconnect. | +| `PUT /api/nodes/:id/ssh-wide-forwards` | Browser session and node owner, or administrator for the built-in node. Governs whether new SSH launches from this node may bind port forwards beyond loopback; an edit share cannot change it. | | `/api/ssh/recent-destinations` and `/preferences` | Browser session, scoped to the caller. Recent suggestions and the default connecting node are separate from destination trust. | | `/api/presets` with SSH kind | Browser session, scoped to the caller. Saving resolves the destination through the existing node gate and checks access to any key node; it does not enable SSH or approve trust. Launch rechecks access, readiness, fingerprints, and trust. Deleting a preset neither removes its host pin nor closes a subshell. | | `/api/ssh/verify-destination` and `/verify-destination/accept` | Browser session, current access checks, and the one-use confirmation binding described above. These are destination network probes and trust writes, not login attempts. | @@ -1283,6 +1284,32 @@ account’s configuration as executable trusted input, not passive data. Remote- one canonical destination. These restrictions do not sandbox that OS account or make its OpenSSH binary and configuration untrusted-code-safe. +**Port forwards are declared, never ambient.** Every forward that came from the +connecting account's own SSH configuration stays refused by name: the resolver +rejects `LocalForward`, `RemoteForward`, and `DynamicForward` found by `ssh -G` +before the snapshot freezes, and the snapshot keeps them as forbidden members. +Forwards that exist come from rows the user declared at launch (or from the +preset storing them): an explicit kind, port, and target, visible in the form +and summarized in the launch audit. A local or dynamic forward binds the +connecting node's loopback address by default, which that node's other OS users +can reach: §0's accepted posture verbatim, the same class the node's loopback +dashboard already carries. Binding anything wider requires the node's +**Allow wide port forwards** switch (owner-only, admin on the built-in node, +default off, audited `node.ssh_wide_forwards.update`), and a launch asking for +a wide bind on a node without it is refused; enforcement sits plane-side +because the node already gates the whole SSH subsystem, and a plane lying +about the flag is the control-plane-compromise case §0 accepts. A remote +forward binds on the destination under its sshd's own `GatewayPorts` and +`PermitRemoteOpen` policy, like any SSH client's request; Subshell relays it +and owns the asking. When approved rows exist the rendered config omits its +`ClearAllForwardings yes` line (measured on OpenSSH: the keyword applies at the +end of the whole config load, so rows cannot survive it from anywhere), and +that omission is honest because the pane's `-F` file replaces the connecting +account's configuration outright, leaving only our rendered lines and the +root-owned system file as forward sources. `Tunnel`, `ForwardAgent`, and +`ForwardX11` toward the destination stay refused: this opens TCP port +forwarding and nothing else. + **Setup progress is not a durable job.** Completed results remain for one hour in the running server process. Closing the dialog does not cancel the installer; a server restart loses progress tracking. Check the destination and node roster @@ -2572,6 +2599,7 @@ Audit events are written, grouped by family: - **Nodes**: `node.enroll`, `node.delete`, `node.rename`, `node.key_rotate`, `node.reregister_key`, `node.reregister`, `node.allowed_dirs.update`, `node.config.update`, `node.maintenance.update`, `node.ssh_enabled.update`, + `node.ssh_wide_forwards.update` (the boolean itself; which node, who flipped it), `node.ssh_identity.register` (spec 2026-10-08 §4.3: the signing-key bootstrap lands when the link's answer fills the EMPTY slot, `via: "link"` and nothing else in the metadata - never the key value; the idempotent @@ -2607,7 +2635,8 @@ Audit events are written, grouped by family: verb shares its action with the rest of the service surface). The SSH launcher's act lands in this family too as `ssh.launch` (spec 2026-10-07 §12): metadata `{ nodeId, destination, subshellId }` plus `relayRef` for a - relay launch, naming which machine + relay launch and a bounded `forwards` summary (per approved row: kind, canonical + listen end, and target for local/remote) when the launch carries rows, naming which machine dialed out to which canonical `host:port` and the pane that came of it, and never a config value, path list, or key material (§13). It is written AFTER the pane spawned, success only, the same posture as `subshell.create`, and From bd9bde2d203a7d1f7a7948c5388b7a2622a23ff0 Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 17:00:05 -0700 Subject: [PATCH 09/35] fix(ssh): canonical duplicate-bind check, per-row kind help, and the e2e forward legs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round-two review of spec 2026-10-15: a null bind on a local or dynamic row renders exactly what a written 127.0.0.1 renders, so the grammar's duplicate rule now sees them as one bind (mirrored in the form); the loopback test now bounds each 127/8 octet; each forward row carries its own kind explanation where the port lives (spec §7); the preset schema's clear sentence names the editor's real empty-list path, with a test for the branch; and the spec §9 E2E legs ship - real -L, -D, and -R listeners, the wide-bind door before and after the switch, and the rendered file byte-checked without the clear. Co-Authored-By: Claude --- apps/server/api/src/api/ssh-presets.ts | 2 +- .../src/api/ssh/__tests__/ssh-routes.test.ts | 4 + .../components/connect/ssh-forward-fields.tsx | 12 +- .../src/lib/__tests__/ssh-forwards.test.ts | 14 + apps/server/web/src/lib/ssh-forwards.ts | 6 +- e2e/ports.ts | 10 + e2e/tests/26-ssh-forwarding.spec.ts | 443 ++++++++++++++++++ .../src/__tests__/ssh-forwards.test.ts | 34 +- .../subshell-protocol/src/ssh-forwards.ts | 20 +- 9 files changed, 535 insertions(+), 10 deletions(-) create mode 100644 e2e/tests/26-ssh-forwarding.spec.ts diff --git a/apps/server/api/src/api/ssh-presets.ts b/apps/server/api/src/api/ssh-presets.ts index 97df223cc..f693f35ad 100644 --- a/apps/server/api/src/api/ssh-presets.ts +++ b/apps/server/api/src/api/ssh-presets.ts @@ -59,7 +59,7 @@ const SshPresetFields = { t.Array(SshForwardRowInputSchema, { maxItems: SSH_MAX_FORWARD_ROWS, description: - "Port forwards this preset carries to every launch; validated by the protocol grammar at save, and a non-loopback bind is still refused at launch until the connecting machine allows it. Omitted on update keeps the stored list, null clears it", + "Port forwards this preset carries to every launch; validated by the protocol grammar at save, and a non-loopback bind is still refused at launch until the connecting machine allows it. Omitted on update keeps the stored list; null or an empty list clears it", }), ), ), diff --git a/apps/server/api/src/api/ssh/__tests__/ssh-routes.test.ts b/apps/server/api/src/api/ssh/__tests__/ssh-routes.test.ts index 3c60be0e7..2cfe208f9 100644 --- a/apps/server/api/src/api/ssh/__tests__/ssh-routes.test.ts +++ b/apps/server/api/src/api/ssh/__tests__/ssh-routes.test.ts @@ -766,6 +766,10 @@ describe("SSH preset forwards (spec 2026-10-15 §7)", () => { const kept = await put({}); expect(kept.status).toBe(200); expect(((await kept.json()) as { forwards: unknown[] }).forwards).toEqual([fwd]); + // The editor's real clear path: deleting every row sends []. An + // "empty keeps" refactor here would silently break that button. + const emptied = await put({ forwards: [] }); + expect(((await emptied.json()) as { forwards: unknown[] }).forwards).toEqual([]); const cleared = await put({ forwards: null }); expect(((await cleared.json()) as { forwards: unknown[] }).forwards).toEqual([]); const row = await db.selectFrom("sshPresets").select("forwards").where("id", "=", id).executeTakeFirst(); diff --git a/apps/server/web/src/components/connect/ssh-forward-fields.tsx b/apps/server/web/src/components/connect/ssh-forward-fields.tsx index 1ac65d6a7..36d6cd7ff 100644 --- a/apps/server/web/src/components/connect/ssh-forward-fields.tsx +++ b/apps/server/web/src/components/connect/ssh-forward-fields.tsx @@ -24,6 +24,15 @@ function kindLabel(kind: SshForwardDraftRow["kind"]): string { return KIND_LABELS.find((option) => option.value === kind)?.label ?? kind; } +/** What the chosen kind does and where its port lives, kept beside the row it explains (spec §7). */ +const KIND_HELP: Record = { + local: "The port opens on this machine, and programs here reach the target through the destination.", + remote: + "The destination opens the port and reaches the target through this machine; its own SSH server decides how it binds.", + dynamic: + "A SOCKS proxy port opens on this machine; tools that use it pick their own targets and reach them through the destination.", +}; + export function SshForwardFields({ rows, allowWideBind, @@ -136,6 +145,7 @@ export function SshForwardFields({
+

{KIND_HELP[row.kind]}

{problem && (

{problem} @@ -146,7 +156,7 @@ export function SshForwardFields({ })} {!allowWideBind && (

- Forwards bind this machine's own loopback address. Its owner can allow other bind addresses on the node's SSH + Forwards bind this machine's own loopback address. Its owner can allow other bind addresses in the node's SSH settings.

)} diff --git a/apps/server/web/src/lib/__tests__/ssh-forwards.test.ts b/apps/server/web/src/lib/__tests__/ssh-forwards.test.ts index 995414d09..1e152ef3a 100644 --- a/apps/server/web/src/lib/__tests__/ssh-forwards.test.ts +++ b/apps/server/web/src/lib/__tests__/ssh-forwards.test.ts @@ -57,6 +57,20 @@ describe("sshForwardListProblems", () => { expect(sshForwardListProblems([row({ bindHost: "localhost" }), row({ bindHost: "127.0.0.1" })])).toBeNull(); }); + it("refuses a blank bind beside an explicit 127.0.0.1, and keeps the remote pair distinct", () => { + // A blank local bind renders as 127.0.0.1, so the rows are one listener twice. + expect(sshForwardListProblems([row(), row({ bindHost: "127.0.0.1", targetHost: "other.internal" })])).toMatch( + /same port/i, + ); + // A blank remote bind rides the destination's own policy spelling. + expect( + sshForwardListProblems([ + row({ kind: "remote", bindHost: "" }), + row({ kind: "remote", bindHost: "127.0.0.1", targetHost: "other.internal" }), + ]), + ).toBeNull(); + }); + it("refuses over the cap", () => { const many = Array.from({ length: 17 }, (_, i) => row({ listenPort: String(i + 1) })); expect(sshForwardListProblems(many)).toMatch(/at most 16/i); diff --git a/apps/server/web/src/lib/ssh-forwards.ts b/apps/server/web/src/lib/ssh-forwards.ts index 559c51622..f5fcbaed7 100644 --- a/apps/server/web/src/lib/ssh-forwards.ts +++ b/apps/server/web/src/lib/ssh-forwards.ts @@ -78,7 +78,11 @@ export function sshForwardListProblems(rows: readonly SshForwardDraftRow[]): str } const listens = new Set(); for (const row of filled) { - const key = `${row.bindHost.trim().toLowerCase()}|${row.listenPort.trim()}`; + // Same canonical as the protocol's dupe key: a blank bind on a local or + // dynamic row renders as `127.0.0.1`, so it IS that explicit bind twice; + // a remote blank rides the destination's own policy spelling. + const bind = row.bindHost.trim() === "" && row.kind !== "remote" ? "127.0.0.1" : row.bindHost.trim(); + const key = `${bind.toLowerCase()}|${row.listenPort.trim()}`; if (listens.has(key)) return "Two forwards can't bind the same port. Remove one or change a port."; listens.add(key); } diff --git a/e2e/ports.ts b/e2e/ports.ts index eb0d20e4e..ea97e0905 100644 --- a/e2e/ports.ts +++ b/e2e/ports.ts @@ -67,6 +67,16 @@ export const PORTS = { * artifact dir the "Set up Subshell here" install fetches from. */ relay: envPort("E2E_PORT_RELAY", 3203), + /* + * `forwards` belongs to spec 26 (the port-forwards legs of spec 2026-10-15 + * §9): a FOURTH standalone ssh-tier backend for the same HOME-override + * reason as `ssh` and `connect`, and a fresh slot rather than a reuse - + * the ssh specs keep their instances alive across their own retries, so + * parallel ssh backends must never collide on one port. Its forwards are + * the spec's own scratch listeners, not fixed ports: the spec binds what + * `freePort()` names. + */ + forwards: envPort("E2E_PORT_FORWARDS", 3204), } as const; export const BASE_URL = `http://127.0.0.1:${PORTS.backend}`; diff --git a/e2e/tests/26-ssh-forwarding.spec.ts b/e2e/tests/26-ssh-forwarding.spec.ts new file mode 100644 index 000000000..a354b9e71 --- /dev/null +++ b/e2e/tests/26-ssh-forwarding.spec.ts @@ -0,0 +1,443 @@ +import { spawn, spawnSync } from "node:child_process"; +import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs"; +import { createServer } from "node:http"; +import { tmpdir } from "node:os"; +import path from "node:path"; +import { type APIRequestContext, expect, type PlaywrightWorkerArgs, test } from "@playwright/test"; +import { missingSshBins, SSH_FIXTURE_ALIAS, type SshFixture, startSshFixture } from "../fixtures/sshd"; +import { PORTS } from "../ports"; +import { shortTmuxBase } from "../stack"; + +/** + * The port-forwards legs of spec 2026-10-15 §9: a REAL sshd, a REAL launch, + * and listeners this spec curls. Everything unit-level already answers the + * question "does the rendered file contain the forward lines?" - including a + * real `ssh -G` parse. What only a bound port can answer is the fact the + * whole render design rests on: that with `ClearAllForwardings` dropped from + * the policy block, ssh really binds the approved listens and traffic really + * flows, and that a row nobody approved binds nothing. + * + * The three kinds each get a leg on ONE launch: + * - `-L`: curl the loopback port on the connecting side and read the marker + * the target server answers with (if the clear survived, or the block + * never matched, the port stays unbound and curl fails), + * - `-D`: `curl --socks5-hostname` THROUGH the SOCKS listener fetches the + * same marker (a proxy that never bound refuses the connect), + * - `-R`: curl the listen port sshd opened on the destination side; the + * bytes come back through the tunnel to the target server, proving the + * remote forward was accepted and bound (the fixture sshd keeps the + * OpenSSH default `GatewayPorts no`, so its bind is loopback, which is + * exactly what a null `bindHost` means to ride - spec §6). + * + * Then the wide-bind gate as a door, not a copy: the same kind of row naming + * `0.0.0.0` is refused (403 `SSH_FORWARD_BIND_DENIED`) before the node's + * setting exists, and after the owner flips it the launch is accepted, the + * rendered file spells the wide bind, and the port answers. The audit row's + * bounded forwards summary is checked beside the bytes it describes. + * + * B/D are the same host here (the fixture sshd runs unprivileged beside the + * backend), which the legs tolerate: they assert a LISTEN exists that only + * the approved forward could have created, and that fact does not depend on + * two machines. The binary gate is spec 22's: no ssh binaries, skip LOUDLY. + */ + +const ROOT = path.join(import.meta.dirname, "..", ".."); +const BACKEND_DIR = path.join(ROOT, "apps", "server", "api"); +const ORIGIN = `http://127.0.0.1:${PORTS.forwards}`; + +const READY_TIMEOUT = 60_000; +const SPAWN_TIMEOUT = 60_000; + +/** The marker the target server answers with; every leg expects exactly this. */ +const MARKER = "FORWARD-MARKER-OK\n"; + +const missing = missingSshBins(); +test.skip( + missing.length > 0 || spawnSync("curl", ["--version"], { encoding: "utf8" }).status !== 0, + `spec 26 needs real ssh binaries${missing.length > 0 ? ` (missing: ${missing.join(", ")})` : ""} and curl for the SOCKS leg`, +); + +const sleep = (ms: number): Promise => new Promise((r) => setTimeout(r, ms)); + +/** Polls `check` until true or the deadline; on timeout throws the label's current text. */ +async function pollUntil( + label: string | (() => string), + check: () => Promise | boolean, + timeoutMs = SPAWN_TIMEOUT, +): Promise { + const deadline = Date.now() + timeoutMs; + for (;;) { + if (await check()) return; + if (Date.now() > deadline) throw new Error(typeof label === "string" ? label : label()); + await sleep(500); + } +} + +/** A loopback ephemeral port, freed the instant it is named (the spec's own copy of the fixture's trick). */ +async function freePort(): Promise { + const srv = createServer(); + await new Promise((resolve) => srv.listen(0, "127.0.0.1", resolve)); + const { port } = srv.address() as { port: number }; + await new Promise((resolve) => srv.close(() => resolve())); + return port; +} + +/** GET as a yes/no bytes answer; connection refused (an unbound forward) is just "not yet". */ +async function fetchText(url: string): Promise { + try { + const res = await fetch(url, { signal: AbortSignal.timeout(5_000) }); + return res.ok ? await res.text() : null; + } catch { + return null; + } +} + +/** + * Fetch through a SOCKS5 listener (remote-resolving, the way `-D` is meant to + * be used). ASYNC on purpose: the marker server lives in this worker process, + * and a synchronous curl would park the event loop the server needs to + * answer - the forward would stall looking like a refusal to bind. + */ +async function socksFetch(proxyPort: number, url: string): Promise { + return await new Promise((resolve) => { + const child = spawn("curl", ["-s", "--max-time", "10", "--socks5-hostname", `127.0.0.1:${String(proxyPort)}`, url]); + let out = ""; + child.stdout.on("data", (chunk: Buffer) => { + out += chunk.toString(); + }); + child.on("error", () => resolve(null)); + child.on("close", (code) => resolve(code === 0 ? out : null)); + }); +} + +interface ForwardInstance { + child: ReturnType; + dir: string; + dataDir: string; + tmuxBase: string; +} + +/** Boots the spec's OWN backend on `PORTS.forwards`, HOME + agent socket in its env (spec 22's reason). */ +async function startForwardBackend(fixtureHome: string, sshAuthSock: string): Promise { + stopForwardBackend(); + const dir = mkdtempSync(path.join(tmpdir(), "subshell-e2e-fwd-")); + const dataDir = path.join(dir, "data"); + const tmuxBase = shortTmuxBase(); + mkdirSync(tmuxBase, { recursive: true }); + + const env: Record = { + ...process.env, + NODE_ENV: "development", + SUBSHELL_TEST_MODE: "false", + SUBSHELL_SERVER_CONFIG_DIR: dir, + SERVER_PORT: String(PORTS.forwards), + HOST: "127.0.0.1", + DATABASE_PATH: path.join(dir, "subshell.db"), + SUBSHELL_SERVER_DATA_DIR: dataDir, + APP_BASE_URL: ORIGIN, + BETTER_AUTH_SECRET: "e2e-secret-not-used-outside-tests-0000000000", + SUBSHELL_PLUGIN_REGISTRY_URL: `http://127.0.0.1:${PORTS.fakeRegistry}`, + SUBSHELL_RELEASE_URL: "", + TMUX_TMPDIR: tmuxBase, + HOME: fixtureHome, + SSH_AUTH_SOCK: sshAuthSock, + }; + delete env.TMUX; + delete env.TMUX_PANE; + + const child = spawn("bun", ["run", "src/index.ts"], { + cwd: BACKEND_DIR, + detached: true, + stdio: process.env.E2E_VERBOSE ? "inherit" : "ignore", + env, + }); + child.unref(); + const inst: ForwardInstance = { child, dir, dataDir, tmuxBase }; + instance = inst; + + const deadline = Date.now() + READY_TIMEOUT; + for (;;) { + try { + const res = await fetch(`${ORIGIN}/api/setup/status`); + if (res.ok) { + const body = (await res.json()) as { needsSetup: boolean }; + if (!body.needsSetup) throw new Error("[e2e] forwarding instance: port already set up (leaked run)"); + return inst; + } + } catch (err) { + if ((err as Error).message.includes("port already set up")) throw err as Error; + // Not listening yet. + } + if (Date.now() > deadline) throw new Error(`[e2e] forwarding instance did not become ready at ${ORIGIN}`); + await sleep(300); + } +} + +let instance: ForwardInstance | undefined; + +function stopForwardBackend(): void { + const inst = instance; + if (!inst) return; + instance = undefined; + try { + if (inst.child.pid) process.kill(-inst.child.pid, "SIGTERM"); + } catch { + // Already gone. + } + for (const uidDir of readdirSafe(inst.tmuxBase)) { + for (const socket of readdirSafe(path.join(inst.tmuxBase, uidDir))) { + try { + spawnSync("tmux", ["-S", path.join(inst.tmuxBase, uidDir, socket), "kill-server"]); + } catch { + // Best-effort per socket. + } + } + } + rmSync(inst.dir, { recursive: true, force: true }); + rmSync(path.dirname(inst.tmuxBase), { recursive: true, force: true }); +} + +function readdirSafe(dir: string): string[] { + try { + return readdirSync(dir); + } catch { + return []; + } +} + +const ADMIN = { name: "Fwd Admin", email: "fwd-admin@subshell.test", password: "e2e-fwd-admin-pass-1" } as const; + +function freshCtx(playwright: PlaywrightWorkerArgs["playwright"]): Promise { + return playwright.request.newContext({ baseURL: ORIGIN, storageState: { cookies: [], origins: [] } }); +} + +test.describe("ssh port forwards (spec 2026-10-15)", () => { + test.describe.configure({ mode: "serial" }); + + const leaks: string[] = []; + const specTeardown: (() => Promise | void)[] = []; + let fixture: SshFixture | undefined; + let fixtureRoot: string | undefined; + let agentPid: number | undefined; + let adminRef: APIRequestContext | undefined; + let targetPort = 0; + const livePaneIds: string[] = []; + + test.beforeAll(async ({ playwright }) => { + fixtureRoot = mkdtempSync(path.join(tmpdir(), "subshell-e2e-fwdtier-")); + fixture = await startSshFixture(fixtureRoot); + + const agentOut = spawnSync("ssh-agent", ["-s"], { encoding: "utf8" }); + if (agentOut.status !== 0) throw new Error(`ssh-agent failed: ${agentOut.stderr}`); + const agentEnv = Object.fromEntries( + ["SSH_AUTH_SOCK", "SSH_AGENT_PID"].map((k) => [k, agentOut.stdout.match(new RegExp(`${k}=([^;]+)`))?.[1] ?? ""]), + ); + agentPid = Number(agentEnv.SSH_AGENT_PID); + if (!agentEnv.SSH_AUTH_SOCK || !Number.isInteger(agentPid) || agentPid <= 0) { + throw new Error(`ssh-agent output unparseable: ${agentOut.stdout.slice(0, 200)}`); + } + const add = spawnSync("ssh-add", [fixture.trustedKeyPath], { + env: { ...process.env, SSH_AUTH_SOCK: agentEnv.SSH_AUTH_SOCK }, + encoding: "utf8", + }); + if (add.status !== 0) throw new Error(`ssh-add failed: ${add.stderr}`); + + await startForwardBackend(fixture.sshConfigHome, agentEnv.SSH_AUTH_SOCK); + adminRef = await freshCtx(playwright); + const signUp = await adminRef.post("/api/auth/sign-up/email", { data: ADMIN }); + expect(signUp.ok(), await signUp.text()).toBe(true); + const enable = await adminRef.put("/api/nodes/local/ssh-enabled", { data: { on: true } }); + expect(enable.ok(), await enable.text()).toBe(true); + // The marker server every leg reads its bytes back from. It listens on + // its own port; a leg's listen port proves the FORWARD bound a listener, + // because only the tunnel connects that listener to this server. + targetPort = await freePort(); + const target = createServer((_req, res) => { + res.setHeader("content-type", "text/plain"); + res.end(MARKER); + }); + target.listen(targetPort, "127.0.0.1"); + specTeardown.push(() => new Promise((resolve) => target.close(() => resolve()))); + }); + + test.afterAll(async () => { + for (const [label, id] of [...livePaneIds].reverse().entries()) { + try { + if (adminRef) { + await adminRef.post(`/api/subshells/${id}/terminate`); + await adminRef.delete(`/api/subshells/${id}`); + } + } catch (err) { + leaks.push(`cleanup row ${label}: ${String(err)}`); + } + } + for (const close of specTeardown.reverse()) { + try { + await close(); + } catch (err) { + leaks.push(`teardown: ${String(err)}`); + } + } + try { + await adminRef?.dispose(); + } catch (err) { + leaks.push(`admin ctx: ${String(err)}`); + } + if (agentPid !== undefined) { + try { + process.kill(agentPid, "SIGTERM"); + } catch { + // Already gone. + } + } + try { + await fixture?.stop(); + } catch (err) { + leaks.push(`sshd fixture stop: ${String(err)}`); + } + try { + stopForwardBackend(); + } catch (err) { + leaks.push(`backend stop: ${String(err)}`); + } + if (fixtureRoot !== undefined) rmSync(fixtureRoot, { recursive: true, force: true }); + if (leaks.length > 0) console.error(`[26-ssh-forwarding] cleanup problems: ${leaks.join("; ")}`); + }); + + test("an approved -L, -D, and -R bind real listeners; the rendered config carries the block without the clear; the audit names the rows", async () => { + test.setTimeout(240_000); + const admin = adminRef; + if (!admin || !fixture) throw new Error("beforeAll did not run"); + const fx = fixture; // Local const: the poll closures below cannot narrow the outer `let`. + const inst = instance; + if (!inst) throw new Error("no backend instance"); + + const localListen = await freePort(); + const socksListen = await freePort(); + const remoteListen = await freePort(); + const launch = await admin.post("/api/ssh/launch", { + data: { + node: "local", + destination: SSH_FIXTURE_ALIAS, + name: "e2e-fwd-three-kinds", + forwards: [ + { kind: "local", listenPort: localListen, bindHost: null, targetHost: "127.0.0.1", targetPort }, + { kind: "dynamic", listenPort: socksListen, bindHost: null, targetHost: null, targetPort: null }, + { kind: "remote", listenPort: remoteListen, bindHost: null, targetHost: "127.0.0.1", targetPort }, + ], + }, + }); + expect(launch.status(), await launch.text()).toBe(201); + const subshellId = ((await launch.json()) as { subshell: { id: string } }).subshell.id; + livePaneIds.push(subshellId); + + await pollUntil( + `ssh pane ${subshellId} never reached running+alive`, + async () => { + const res = await admin.get(`/api/subshells/${subshellId}`); + if (!res.ok()) return false; + const view = (await res.json()) as { status: string; alive: boolean }; + return view.status === "running" && view.alive === true; + }, + READY_TIMEOUT, + ); + + // The rendered config: the exact-destination block spells all three + // lines, the `Host *` policy stands (Tunnel no etc.), and the clear line + // the rows made obsolete is GONE - the whole §5 argument in one file. + const configPath = path.join(inst.dataDir, "ssh", subshellId, "config"); + await pollUntil(`the rendered ssh config never appeared at ${configPath}`, () => existsSync(configPath)); + const config = readFileSync(configPath, "utf8"); + expect(config).toContain("Tunnel no"); + expect(config).not.toContain("ClearAllForwardings"); + expect(config).toContain(`LocalForward 127.0.0.1:${String(localListen)} 127.0.0.1:${String(targetPort)}`); + expect(config).toContain(`DynamicForward 127.0.0.1:${String(socksListen)}`); + expect(config).toContain(`RemoteForward ${String(remoteListen)} 127.0.0.1:${String(targetPort)}`); + expect(config.indexOf("Host *")).toBeLessThan(config.indexOf("# User-declared port forwards")); + expect(config).toContain("# User-declared port forwards, approved at launch (spec 2026-10-15)."); + + // The sshd shell must be up before the legs read bytes: forwards bind + // only after authentication completes ("Starting session" as spec 22's + // truth source). + await pollUntil( + () => `sshd never reported a shell for ${subshellId}`, + () => /Starting session: shell/.test(readFileSync(fx.logPath, "utf8")), + READY_TIMEOUT, + ); + + // -L: the connecting side's listener answers with the target's bytes. + await pollUntil( + () => `-L forward on 127.0.0.1:${String(localListen)} never answered`, + async () => (await fetchText(`http://127.0.0.1:${String(localListen)}/`)) === MARKER, + ); + // -D: through the SOCKS listener, remote-resolved like any SOCKS client. + await pollUntil( + () => `-D SOCKS listener on 127.0.0.1:${String(socksListen)} never proxied`, + async () => (await socksFetch(socksListen, `http://127.0.0.1:${String(targetPort)}/`)) === MARKER, + ); + // -R: sshd's own listener on the destination side; the bytes come back + // through the tunnel to the target server. + await pollUntil( + () => `-R forward on the destination never answered on ${String(remoteListen)}`, + async () => (await fetchText(`http://127.0.0.1:${String(remoteListen)}/`)) === MARKER, + ); + + // The audit row names what the pane opened, bounded and secret-free: + // kind + canonical listen per row, target for the two kinds that have one. + const audit = await admin.get("/api/audit?limit=50"); + expect(audit.ok(), await audit.text()).toBe(true); + const rows = (await audit.json()) as { action: string; metadata: unknown }[]; + const launchRow = rows.find((r) => r.action === "ssh.launch"); + expect(launchRow).toBeDefined(); + expect(launchRow?.metadata).toMatchObject({ + nodeId: "local", + forwards: [ + { kind: "local", listen: `127.0.0.1:${String(localListen)}`, target: `127.0.0.1:${String(targetPort)}` }, + { kind: "dynamic", listen: `127.0.0.1:${String(socksListen)}` }, + { kind: "remote", listen: String(remoteListen), target: `127.0.0.1:${String(targetPort)}` }, + ], + }); + }); + + test("a wide bind is refused before the switch and bound after it", async () => { + test.setTimeout(240_000); + const admin = adminRef; + if (!admin) throw new Error("beforeAll did not run"); + const inst = instance; + if (!inst) throw new Error("no backend instance"); + + const wideListen = await freePort(); + const rows = [{ kind: "local", listenPort: wideListen, bindHost: "0.0.0.0", targetHost: "127.0.0.1", targetPort }]; + + // Before: the refusal is pre-launch and names itself; nothing was dialed. + const denied = await admin.post("/api/ssh/launch", { + data: { node: "local", destination: SSH_FIXTURE_ALIAS, name: "e2e-fwd-wide-denied", forwards: rows }, + }); + expect(denied.status()).toBe(403); + expect(((await denied.json()) as { code: string }).code).toBe("SSH_FORWARD_BIND_DENIED"); + + // The owner flips the machine's own switch. + const flipped = await admin.put("/api/nodes/local/ssh-wide-forwards", { data: { on: true } }); + expect(flipped.ok(), await flipped.text()).toBe(true); + expect(((await flipped.json()) as { sshWideForwards: boolean }).sshWideForwards).toBe(true); + + // After: the same row launches, the file spells the wide bind, and the + // listener answers. + const launch = await admin.post("/api/ssh/launch", { + data: { node: "local", destination: SSH_FIXTURE_ALIAS, name: "e2e-fwd-wide-accepted", forwards: rows }, + }); + expect(launch.status(), await launch.text()).toBe(201); + const subshellId = ((await launch.json()) as { subshell: { id: string } }).subshell.id; + livePaneIds.push(subshellId); + + const configPath = path.join(inst.dataDir, "ssh", subshellId, "config"); + await pollUntil(`the wide-bind config never appeared at ${configPath}`, () => existsSync(configPath)); + expect(readFileSync(configPath, "utf8")).toContain( + `LocalForward 0.0.0.0:${String(wideListen)} 127.0.0.1:${String(targetPort)}`, + ); + await pollUntil( + () => `wide forward on ${String(wideListen)} never answered`, + async () => (await fetchText(`http://127.0.0.1:${String(wideListen)}/`)) === MARKER, + ); + }); +}); diff --git a/packages/subshell-protocol/src/__tests__/ssh-forwards.test.ts b/packages/subshell-protocol/src/__tests__/ssh-forwards.test.ts index 95d393613..bbda4c620 100644 --- a/packages/subshell-protocol/src/__tests__/ssh-forwards.test.ts +++ b/packages/subshell-protocol/src/__tests__/ssh-forwards.test.ts @@ -106,6 +106,26 @@ describe("parseSshForwardRows", () => { expect(parseSshForwardRows(rows)).toEqual(rows); }); + it("refuses a null bind beside an explicit 127.0.0.1 on the same port: the two render one listener twice", () => { + expect( + parseSshForwardRows([makeRow(), makeRow({ bindHost: "127.0.0.1", targetHost: "other.example" })]), + ).toBeNull(); + expect( + parseSshForwardRows([ + makeRow({ kind: "dynamic", listenPort: 1080, targetHost: null, targetPort: null }), + makeRow({ kind: "dynamic", listenPort: 1080, bindHost: "127.0.0.1", targetHost: null, targetPort: null }), + ]), + ).toBeNull(); + }); + + it("keeps a remote null bind distinct from a named one (D's GatewayPorts spelling is its own listen spec)", () => { + const rows = [ + makeRow({ kind: "remote", listenPort: 9090, targetHost: "a.internal", targetPort: 1 }), + makeRow({ kind: "remote", listenPort: 9090, bindHost: "127.0.0.1", targetHost: "b.internal", targetPort: 2 }), + ]; + expect(parseSshForwardRows(rows)).toEqual(rows); + }); + it("refuses a non-array list", () => { expect(parseSshForwardRows({ 0: makeRow() })).toBeNull(); expect(parseSshForwardRows("local")).toBeNull(); @@ -134,7 +154,19 @@ describe("isLoopbackForwardBind", () => { expect(isLoopbackForwardBind(makeRow({ bindHost }))).toBe(true); }); } - const wide = ["0.0.0.0", "::", "[::]", "10.0.0.5", "example.com", "[::ffff:127.0.0.1]", "mybox.internal"]; + const wide = [ + "0.0.0.0", + "::", + "[::]", + "10.0.0.5", + "example.com", + "[::ffff:127.0.0.1]", + "mybox.internal", + // Not a bindable quad: classifying it loopback would skip the wide gate + // on a name ssh simply refuses to bind. + "127.999.999.999", + "127", + ]; for (const bindHost of wide) { it(`names ${bindHost} wide (fails closed: the test is the written name, not a DNS answer)`, () => { expect(isLoopbackForwardBind(makeRow({ bindHost }))).toBe(false); diff --git a/packages/subshell-protocol/src/ssh-forwards.ts b/packages/subshell-protocol/src/ssh-forwards.ts index f95256040..ce89d94a7 100644 --- a/packages/subshell-protocol/src/ssh-forwards.ts +++ b/packages/subshell-protocol/src/ssh-forwards.ts @@ -130,8 +130,15 @@ export function parseSshForwardRows(value: unknown): SshForwardWire[] | null { const row = parseSshForwardRow(item); if (row === null) return null; // Casefold only for the dupe key: hostnames match case-insensitively, and - // a "LOCALHOST" beside "localhost" is one bind, not two. - const key = `${(row.bindHost ?? "").toLowerCase()}|${row.listenPort}`; + // a "LOCALHOST" beside "localhost" is one bind, not two. A null bind on a + // local/dynamic row is canonically ssh's loopback, which is what the + // renderer spells as `127.0.0.1`, so it DUPLICATES a written `127.0.0.1` + // rather than riding beside it; two different literal names on one port + // ("localhost" vs "127.0.0.1") stay a configuration ssh reports itself. + // A remote null renders bare (D's GatewayPorts policy picks the address), + // which is a different listen spec from any named bind, so it keys empty. + const bindKey = (row.kind === "remote" ? (row.bindHost ?? "") : (row.bindHost ?? "127.0.0.1")).toLowerCase(); + const key = `${bindKey}|${row.listenPort}`; if (seen.has(key)) return null; seen.add(key); rows.push(row); @@ -155,8 +162,9 @@ export function isLoopbackForwardBind(row: SshForwardWire): boolean { // The grammar admits IPv6 only bracketed (HOST_RE), so `[::1]` is the // spelling a row can actually carry. if (host === "localhost" || host === "[::1]") return true; - // IPv4 127.0.0.0/8: dotted-quad only (the grammar's host shape admits the - // other spellings, but nobody renders those here and refusing them as - // "wide" fails closed, which is the safe direction). - return /^127(\.\d{1,3}){3}$/.test(host); + // IPv4 127.0.0.0/8: dotted-quad with each octet 0-255 (the grammar's host + // shape admits other spellings, and a 127.999.999.999 is not a bindable + // address - classifying it "wide" fails closed, which is the safe side). + const OCTET = "(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)"; + return new RegExp(`^127(\\.${OCTET}){3}$`).test(host); } From ad2c295c901fa66695a4c082a6ecaf84263ad265 Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 17:17:37 -0700 Subject: [PATCH 10/35] refactor(ssh): per-side duplicate binds, one refusal sentence, sharper row copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three review nits worth shipping: the duplicate-listen rule keys the listen end per SIDE (a port a local/dynamic row binds on the connecting machine beside a remote row's port on the destination is two listeners), the form keys ports numerically so a written-twice port is caught inline, the launch and preset-save 400s share one sentence exported beside the grammar, and the row help names the two ends instead of trusting a bare "this machine" to read right on a remote-node launch. The spec's E2E test line now matches what §5 ships: with approved rows, the Host * clear is gone. Co-Authored-By: Claude --- .../api/src/services/ssh-launch.service.ts | 8 +--- .../api/src/services/ssh-presets.service.ts | 8 +--- .../components/connect/ssh-forward-fields.tsx | 12 ++++-- .../src/lib/__tests__/ssh-forwards.test.ts | 12 ++++++ apps/server/web/src/lib/ssh-forwards.ts | 16 +++++--- .../2026-10-15-ssh-port-forwards-design.md | 11 ++++-- .../src/__tests__/ssh-forwards.test.ts | 8 ++++ packages/subshell-protocol/src/index.ts | 1 + .../subshell-protocol/src/ssh-forwards.ts | 39 ++++++++++++------- 9 files changed, 77 insertions(+), 38 deletions(-) diff --git a/apps/server/api/src/services/ssh-launch.service.ts b/apps/server/api/src/services/ssh-launch.service.ts index cb26b4835..442a7bfb1 100644 --- a/apps/server/api/src/services/ssh-launch.service.ts +++ b/apps/server/api/src/services/ssh-launch.service.ts @@ -21,6 +21,7 @@ import { parseNodeSshAliasList, parseSshConnectionSnapshot, parseSshForwardRows, + SSH_FORWARD_LIST_REFUSAL, SSH_NAME_MAX_CHARS, type SshConnectionSnapshotWire, type SshForwardWire, @@ -446,12 +447,7 @@ export async function sshLaunch(args: { let forwards: SshForwardWire[] = []; if (args.forwards !== undefined) { const parsed = parseSshForwardRows(args.forwards); - if (parsed === null) - return codedRefusal( - 400, - BackendErrorCodes.CONFIG_INVALID, - "That port-forward list isn't valid. Each forward needs a kind (local, remote, or dynamic), a listen port from 1 to 65535, and a target host and port except for dynamic. Two forwards can't bind the same port.", - ); + if (parsed === null) return codedRefusal(400, BackendErrorCodes.CONFIG_INVALID, SSH_FORWARD_LIST_REFUSAL); forwards = parsed; } if (forwards.length > 0 && forwards.some((f) => !isLoopbackForwardBind(f)) && row.sshWideForwards !== 1) { diff --git a/apps/server/api/src/services/ssh-presets.service.ts b/apps/server/api/src/services/ssh-presets.service.ts index a6ad89185..babee6248 100644 --- a/apps/server/api/src/services/ssh-presets.service.ts +++ b/apps/server/api/src/services/ssh-presets.service.ts @@ -1,4 +1,4 @@ -import { parseSshForwardRows } from "@internal/subshell-protocol"; +import { parseSshForwardRows, SSH_FORWARD_LIST_REFUSAL } from "@internal/subshell-protocol"; import type { Static } from "elysia"; import { loadNodeGate } from "@/api/nodes/node-gate.js"; import { throwCodedRefusal } from "@/api/ssh/ssh-views.js"; @@ -58,11 +58,7 @@ export async function saveSshPreset( let forwardsJson: string | null = null; if (body.forwards !== undefined && body.forwards !== null) { const parsed = parseSshForwardRows(body.forwards); - if (parsed === null) - throw new SshPresetError( - "bad_request", - "That port-forward list isn't valid. Each forward needs a kind (local, remote, or dynamic), a listen port from 1 to 65535, and a target host and port except for dynamic. Two forwards can't bind the same port.", - ); + if (parsed === null) throw new SshPresetError("bad_request", SSH_FORWARD_LIST_REFUSAL); if (parsed.length > 0) forwardsJson = JSON.stringify(parsed); } let destination = body.destination; diff --git a/apps/server/web/src/components/connect/ssh-forward-fields.tsx b/apps/server/web/src/components/connect/ssh-forward-fields.tsx index 36d6cd7ff..a8d22b4d5 100644 --- a/apps/server/web/src/components/connect/ssh-forward-fields.tsx +++ b/apps/server/web/src/components/connect/ssh-forward-fields.tsx @@ -24,13 +24,17 @@ function kindLabel(kind: SshForwardDraftRow["kind"]): string { return KIND_LABELS.find((option) => option.value === kind)?.label ?? kind; } -/** What the chosen kind does and where its port lives, kept beside the row it explains (spec §7). */ +/** + * What the chosen kind does and where its port lives, kept beside the row it + * explains (spec §7). "The connecting machine" and "the destination" name the + * two ends: on a remote-node launch "this machine" would read as the browser. + */ const KIND_HELP: Record = { - local: "The port opens on this machine, and programs here reach the target through the destination.", + local: "The port opens on the connecting machine, and programs there reach the target through the destination.", remote: - "The destination opens the port and reaches the target through this machine; its own SSH server decides how it binds.", + "The destination opens the port and reaches the target through the connecting machine; its own SSH server decides how it binds.", dynamic: - "A SOCKS proxy port opens on this machine; tools that use it pick their own targets and reach them through the destination.", + "A SOCKS proxy port opens on the connecting machine; tools that use it pick their own targets and reach them through the destination.", }; export function SshForwardFields({ diff --git a/apps/server/web/src/lib/__tests__/ssh-forwards.test.ts b/apps/server/web/src/lib/__tests__/ssh-forwards.test.ts index 1e152ef3a..468b40dc6 100644 --- a/apps/server/web/src/lib/__tests__/ssh-forwards.test.ts +++ b/apps/server/web/src/lib/__tests__/ssh-forwards.test.ts @@ -69,6 +69,18 @@ describe("sshForwardListProblems", () => { row({ kind: "remote", bindHost: "127.0.0.1", targetHost: "other.internal" }), ]), ).toBeNull(); + // The same port on the two sides is two listeners, not a dupe. + expect( + sshForwardListProblems([ + row({ listenPort: "8080", targetPort: "8080" }), + row({ kind: "remote", listenPort: "8080", targetPort: "3000" }), + ]), + ).toBeNull(); + // The port keys numerically, so "05432" beside "5432" is one listener + // here rather than a form-only near-miss the server then 400s. + expect( + sshForwardListProblems([row({ listenPort: "05432" }), row({ listenPort: "5432", targetHost: "other.internal" })]), + ).toMatch(/same port/i); }); it("refuses over the cap", () => { diff --git a/apps/server/web/src/lib/ssh-forwards.ts b/apps/server/web/src/lib/ssh-forwards.ts index f5fcbaed7..1156103cc 100644 --- a/apps/server/web/src/lib/ssh-forwards.ts +++ b/apps/server/web/src/lib/ssh-forwards.ts @@ -78,12 +78,18 @@ export function sshForwardListProblems(rows: readonly SshForwardDraftRow[]): str } const listens = new Set(); for (const row of filled) { - // Same canonical as the protocol's dupe key: a blank bind on a local or - // dynamic row renders as `127.0.0.1`, so it IS that explicit bind twice; - // a remote blank rides the destination's own policy spelling. + // Same key as the protocol's dupe rule: per SIDE (a local/dynamic row + // binds the connecting machine, a remote row the destination, so one + // port on each side is two listeners), with the same canonical bind (a + // blank on a local or dynamic row renders as `127.0.0.1`, so it IS that + // explicit bind twice; a remote blank rides the destination's policy + // spelling) and the port as a number, so "05432" beside "5432" is one + // listener and not a form-only near-miss the server then refuses. + const side = row.kind === "remote" ? "D" : "B"; const bind = row.bindHost.trim() === "" && row.kind !== "remote" ? "127.0.0.1" : row.bindHost.trim(); - const key = `${bind.toLowerCase()}|${row.listenPort.trim()}`; - if (listens.has(key)) return "Two forwards can't bind the same port. Remove one or change a port."; + const key = `${side}|${bind.toLowerCase()}|${String(Number(row.listenPort.trim()))}`; + if (listens.has(key)) + return "Two forwards can't bind the same port on the same machine. Remove one or change a port."; listens.add(key); } return null; diff --git a/docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md b/docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md index e36e46385..6c8bce491 100644 --- a/docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md +++ b/docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md @@ -180,10 +180,13 @@ flip writes its own row in the nodes family. bytes; the path byte-check and the config dir sweep must still pass for a launch that forwards). - Web: draft round-trip, bind-host gating on the node flag, preset editor rows. -- E2E: extend spec 22's real-sshd pattern: launch with a `-L` row and curl the bound - loopback port for the fixture's bytes; a `-D` leg through `curl --socks5-hostname`; - a `-R` loopback-bind leg; the wide-bind refusal before the toggle and acceptance - after. Assert the recorded config carries the forward block and the `Host *` clear. +- E2E (shipped: `e2e/tests/26-ssh-forwarding.spec.ts`): extend spec 22's real-sshd + pattern: launch with a `-L` row and curl the bound loopback port for the fixture's + bytes; a `-D` leg through `curl --socks5-hostname`; a `-R` loopback-bind leg; the + wide-bind refusal before the toggle and acceptance after. Assert the recorded config + carries the forward block under the exact-`Host` section and that the rowless + `Host *` clear is GONE (§5: the clear is dropped when rows are approved, so asserting + its presence would assert the opposite of what ships). ## 10. Non-goals and follow-ups diff --git a/packages/subshell-protocol/src/__tests__/ssh-forwards.test.ts b/packages/subshell-protocol/src/__tests__/ssh-forwards.test.ts index bbda4c620..00303a51b 100644 --- a/packages/subshell-protocol/src/__tests__/ssh-forwards.test.ts +++ b/packages/subshell-protocol/src/__tests__/ssh-forwards.test.ts @@ -118,6 +118,14 @@ describe("parseSshForwardRows", () => { ).toBeNull(); }); + it("accepts one port spelled on both sides: a B bind and a D bind are two listeners", () => { + const rows = [ + makeRow({ listenPort: 8080 }), + makeRow({ kind: "remote", listenPort: 8080, bindHost: "127.0.0.1", targetHost: "dev.local", targetPort: 3000 }), + ]; + expect(parseSshForwardRows(rows)).toEqual(rows); + }); + it("keeps a remote null bind distinct from a named one (D's GatewayPorts spelling is its own listen spec)", () => { const rows = [ makeRow({ kind: "remote", listenPort: 9090, targetHost: "a.internal", targetPort: 1 }), diff --git a/packages/subshell-protocol/src/index.ts b/packages/subshell-protocol/src/index.ts index c090ebc54..bbbe27d74 100644 --- a/packages/subshell-protocol/src/index.ts +++ b/packages/subshell-protocol/src/index.ts @@ -267,6 +267,7 @@ export { isSshForwardKind, parseSshForwardRows, SSH_FORWARD_KINDS, + SSH_FORWARD_LIST_REFUSAL, SSH_MAX_FORWARD_ROWS, type SshForwardKind, type SshForwardWire, diff --git a/packages/subshell-protocol/src/ssh-forwards.ts b/packages/subshell-protocol/src/ssh-forwards.ts index ce89d94a7..7fad3c6fe 100644 --- a/packages/subshell-protocol/src/ssh-forwards.ts +++ b/packages/subshell-protocol/src/ssh-forwards.ts @@ -72,6 +72,14 @@ export type SshForwardWire = { */ export const SSH_MAX_FORWARD_ROWS = 16; +/** + * The sentence every station says when {@link parseSshForwardRows} refuses a + * list. Shared so the launch's 400 and the preset save's 400 cannot drift; + * the web form mirrors the same rules in its own per-field copy. + */ +export const SSH_FORWARD_LIST_REFUSAL = + "That port-forward list isn't valid. Each forward needs a kind (local, remote, or dynamic), a listen port from 1 to 65535, and a target host and port except for dynamic. Two forwards can't bind the same port on the same machine."; + /** The keys one row may spell; anything else means the caller hand-built a shape this grammar does not have. */ const FORWARD_ROW_KEYS: readonly (keyof SshForwardWire)[] = [ "kind", @@ -113,10 +121,12 @@ function parseSshForwardRow(value: unknown): SshForwardWire | null { /** * Validate a whole forward list: each row through {@link parseSshForwardRow}, - * then the LIST rules. A duplicate listen end (same host case-insensitively, - * same port) refuses the list: two rows competing for one bind is a bug the - * user should hear about, not a race ssh silently half-wins. The cap is a - * refusal too, never a trim. + * then the LIST rules. A duplicate listen end refuses the list: two rows + * competing for one bind on one machine is a bug the user should hear about, + * not a race ssh silently half-wins. The listen end is keyed per SIDE: local + * and dynamic rows bind on the connecting machine, remote rows on the + * destination, so the same port spelled once on each side is two listeners, + * not a dupe. The cap is a refusal too, never a trim. * * @param value - candidate rows (parsed JSON; unknown provenance), or null/undefined for "no forwards" * @returns the narrowed rows (possibly empty), or null when malformed or over cap @@ -129,16 +139,19 @@ export function parseSshForwardRows(value: unknown): SshForwardWire[] | null { for (const item of value) { const row = parseSshForwardRow(item); if (row === null) return null; - // Casefold only for the dupe key: hostnames match case-insensitively, and - // a "LOCALHOST" beside "localhost" is one bind, not two. A null bind on a - // local/dynamic row is canonically ssh's loopback, which is what the - // renderer spells as `127.0.0.1`, so it DUPLICATES a written `127.0.0.1` - // rather than riding beside it; two different literal names on one port - // ("localhost" vs "127.0.0.1") stay a configuration ssh reports itself. - // A remote null renders bare (D's GatewayPorts policy picks the address), - // which is a different listen spec from any named bind, so it keys empty. + // The listen end is per SIDE: a local/dynamic row binds B, a remote row + // binds D, so one port on each side is two listeners. Casefold only the + // name: hostnames match case-insensitively, and "LOCALHOST" beside + // "localhost" is one bind, not two. A null bind on a local/dynamic row is + // canonically ssh's loopback, which is what the renderer spells as + // `127.0.0.1`, so it DUPLICATES a written `127.0.0.1` rather than riding + // beside it; two different literal names on one port ("localhost" vs + // "127.0.0.1") stay a configuration ssh reports itself. A remote null + // renders bare (D's GatewayPorts policy picks the address), which is a + // different listen spec from any named bind, so it keys empty. + const side = row.kind === "remote" ? "D" : "B"; const bindKey = (row.kind === "remote" ? (row.bindHost ?? "") : (row.bindHost ?? "127.0.0.1")).toLowerCase(); - const key = `${bindKey}|${row.listenPort}`; + const key = `${side}|${bindKey}|${row.listenPort}`; if (seen.has(key)) return null; seen.add(key); rows.push(row); From c73aaf562eb26de563127b856a1efe5d6e3f2f3d Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 17:21:40 -0700 Subject: [PATCH 11/35] =?UTF-8?q?docs(ssh):=20unpack=20the=20=C2=A79=20tes?= =?UTF-8?q?t=20bullet=20after=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "the rowless Host * clear" named the render the clear belongs to in one compressed word; the sentence now says which line disappears and when. Co-Authored-By: Claude --- .../specs/2026-10-15-ssh-port-forwards-design.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md b/docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md index 6c8bce491..7da8b0119 100644 --- a/docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md +++ b/docs/superpowers/specs/2026-10-15-ssh-port-forwards-design.md @@ -184,9 +184,10 @@ flip writes its own row in the nodes family. pattern: launch with a `-L` row and curl the bound loopback port for the fixture's bytes; a `-D` leg through `curl --socks5-hostname`; a `-R` loopback-bind leg; the wide-bind refusal before the toggle and acceptance after. Assert the recorded config - carries the forward block under the exact-`Host` section and that the rowless - `Host *` clear is GONE (§5: the clear is dropped when rows are approved, so asserting - its presence would assert the opposite of what ships). + carries the forward block under the exact-`Host` section and that the `Host *` + policy's `ClearAllForwardings` line - present in a rowless render - is GONE + (§5: rows approved means the clear dropped, so asserting its presence would + assert the opposite of what ships). ## 10. Non-goals and follow-ups From b3ef68b32bcb2cae0a6c13340dff0b3a0a086009 Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 18:14:17 -0700 Subject: [PATCH 12/35] feat(ssh): radio the forward kind and split its validation tone gold The kind becomes a three-way radio with the chosen kind's explanation under the row (the checked radio points AT it for screen readers), replacing the one-paragraph description of all three. A row's validation sentence now follows the ruling 2026-09-30 split: an empty required field shows gold ("nothing typed yet"), a typed value that fails the rule stays red. Co-Authored-By: Claude --- .../components/connect/ssh-forward-fields.tsx | 62 ++++++++++--------- .../src/lib/__tests__/ssh-forwards.test.ts | 35 +++++++++++ apps/server/web/src/lib/ssh-forwards.ts | 31 ++++++++-- 3 files changed, 93 insertions(+), 35 deletions(-) diff --git a/apps/server/web/src/components/connect/ssh-forward-fields.tsx b/apps/server/web/src/components/connect/ssh-forward-fields.tsx index a8d22b4d5..a9a91eaa1 100644 --- a/apps/server/web/src/components/connect/ssh-forward-fields.tsx +++ b/apps/server/web/src/components/connect/ssh-forward-fields.tsx @@ -1,10 +1,11 @@ -import { Button, Input, Label } from "@internal/node-admin"; +import { Button, cn, Input, Label } from "@internal/node-admin"; import { SSH_MAX_FORWARD_ROWS } from "@internal/subshell-protocol"; import { Plus, X } from "lucide-react"; import { Field, FieldLabel } from "@/components/ui/field"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; import { RequiredMark } from "@/components/ui/required-mark"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; -import { EMPTY_SSH_FORWARD_ROW, type SshForwardDraftRow, sshForwardRowProblems } from "@/lib/ssh-forwards"; +import { REQUIREMENT_CAPTION_CLASS } from "@/lib/requirement-tone"; +import { EMPTY_SSH_FORWARD_ROW, type SshForwardDraftRow, sshForwardRowProblem } from "@/lib/ssh-forwards"; /** * The port-forwards group (spec 2026-10-15 §7): every row a person declared @@ -20,10 +21,6 @@ const KIND_LABELS = [ { value: "dynamic", label: "Dynamic SOCKS (-D)" }, ] as const; -function kindLabel(kind: SshForwardDraftRow["kind"]): string { - return KIND_LABELS.find((option) => option.value === kind)?.label ?? kind; -} - /** * What the chosen kind does and where its port lives, kept beside the row it * explains (spec §7). "The connecting machine" and "the destination" name the @@ -55,36 +52,40 @@ export function SshForwardFields({
Port forwards

- Each forward opens a listening port for as long as this connection lives. A local or dynamic forward binds it on - the connecting machine, so programs there reach a host the destination can see; a remote one asks the - destination to bind a port that reaches a host the connecting machine can see. + Each forward opens a listening port for as long as this connection lives, and the port closes when the subshell + ends.

{rows.map((row, index) => { - const problem = sshForwardRowProblems(row); + const problem = sshForwardRowProblem(row); const needsTarget = row.kind !== "dynamic"; const idPrefix = `ssh-fwd-${String(index)}`; + const helpId = `${idPrefix}-kind-help`; return (
- - Kind - + + + ))} +
-

{KIND_HELP[row.kind]}

+ {/* One sentence per chosen kind, kept below the row it explains; the checked radio points here. */} +

+ {KIND_HELP[row.kind]} +

{problem && ( -

- {problem} +

+ {problem.text}

)}
diff --git a/apps/server/web/src/lib/__tests__/ssh-forwards.test.ts b/apps/server/web/src/lib/__tests__/ssh-forwards.test.ts index 468b40dc6..8efd268d3 100644 --- a/apps/server/web/src/lib/__tests__/ssh-forwards.test.ts +++ b/apps/server/web/src/lib/__tests__/ssh-forwards.test.ts @@ -3,6 +3,7 @@ import { EMPTY_SSH_FORWARD_ROW, type SshForwardDraftRow, sshForwardListProblems, + sshForwardRowProblem, sshForwardRowProblems, sshForwardRowsFromWire, sshForwardRowsToWire, @@ -42,6 +43,40 @@ describe("sshForwardRowProblems", () => { }); }); +describe("sshForwardRowProblem tone", () => { + it("calls an empty required field a gap and a typed wrong value a hard error", () => { + expect(sshForwardRowProblem(row({ listenPort: "" }))).toEqual({ + text: "Enter a listen port from 1 to 65535.", + gap: true, + }); + expect(sshForwardRowProblem(row({ listenPort: "abc" }))).toEqual({ + text: "Enter a listen port from 1 to 65535.", + gap: false, + }); + expect(sshForwardRowProblem(row({ listenPort: "1.5" }))).toEqual({ + text: "Enter a listen port from 1 to 65535.", + gap: false, + }); + // An empty target host is the same fact as an empty listen port: nothing typed yet. + expect(sshForwardRowProblem(row({ targetHost: "" }))).toEqual({ + text: "Enter the host to reach through the connection.", + gap: true, + }); + // A malformed bind host is always a typed value that is wrong. + expect(sshForwardRowProblem(row({ bindHost: "-oProxyCommand=x" }))).toEqual({ + text: "Enter the bind host as a plain name or address, or leave it blank for loopback.", + gap: false, + }); + expect(sshForwardRowProblem(row())).toBeNull(); + }); + + it("keeps the text-only voice in step with the toned one", () => { + const r = row({ listenPort: "" }); + expect(sshForwardRowProblems(r)).toBe(sshForwardRowProblem(r)?.text ?? null); + expect(sshForwardRowProblems(row())).toBeNull(); + }); +}); + describe("sshForwardListProblems", () => { it("an all-empty list (including untouched blank rows) is ready", () => { expect(sshForwardListProblems([])).toBeNull(); diff --git a/apps/server/web/src/lib/ssh-forwards.ts b/apps/server/web/src/lib/ssh-forwards.ts index 1156103cc..9ce68eb6a 100644 --- a/apps/server/web/src/lib/ssh-forwards.ts +++ b/apps/server/web/src/lib/ssh-forwards.ts @@ -49,18 +49,37 @@ function isHostText(value: string): boolean { return host.length > 0 && host.length <= 253 && !host.startsWith("-") && !/[\s\p{Cc}]/u.test(host); } -/** The first problem with one row, in the voice of the field it belongs to. */ -export function sshForwardRowProblems(row: SshForwardDraftRow): string | null { - if (!isPortText(row.listenPort)) return "Enter a listen port from 1 to 65535."; +/** A row problem and its tone: `gap` means "nothing typed yet" (gold), else the typed value is wrong (red). */ +export type SshForwardRowProblem = { text: string; gap: boolean }; + +/** + * The first problem with one row, in the voice of the field it belongs to, + * with the gold/red split the form substrate uses (ruling 2026-09-30): an + * empty required field is a requirement not yet filled, a typed value that + * fails the same rule is a hard error. + */ +export function sshForwardRowProblem(row: SshForwardDraftRow): SshForwardRowProblem | null { + if (!isPortText(row.listenPort)) + return { text: "Enter a listen port from 1 to 65535.", gap: row.listenPort.trim() === "" }; if (row.bindHost.trim() !== "" && !isHostText(row.bindHost)) - return "Enter the bind host as a plain name or address, or leave it blank for loopback."; + return { + text: "Enter the bind host as a plain name or address, or leave it blank for loopback.", + gap: false, + }; if (row.kind !== "dynamic") { - if (!isHostText(row.targetHost)) return "Enter the host to reach through the connection."; - if (!isPortText(row.targetPort)) return "Enter a target port from 1 to 65535."; + if (!isHostText(row.targetHost)) + return { text: "Enter the host to reach through the connection.", gap: row.targetHost.trim() === "" }; + if (!isPortText(row.targetPort)) + return { text: "Enter a target port from 1 to 65535.", gap: row.targetPort.trim() === "" }; } return null; } +/** The first problem with one row, as plain text (the gating voice; the component reads {@link sshForwardRowProblem} for the tone). */ +export function sshForwardRowProblems(row: SshForwardDraftRow): string | null { + return sshForwardRowProblem(row)?.text ?? null; +} + /** * The whole-list answer: per-row problems first, then the LIST rules * (duplicate binds, the cap). Returns null when the list is ready to launch; From 2567b99aef15b8cc026a0fec6b14d046096e2a67 Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 18:20:21 -0700 Subject: [PATCH 13/35] feat(ssh): nest the forward fields under the chosen kind, like Authentication Drop the "Kind" heading: the radio group is labelled "Forward kind" for assistive tech and stands on its own. The chosen option now carries its caption and its port and target fields in the column beside the radio - the same shape as the Authentication choice, where picking "Use another node's keys" reveals the key controls beneath it. Co-Authored-By: Claude --- .../components/connect/ssh-forward-fields.tsx | 202 +++++++++--------- 1 file changed, 104 insertions(+), 98 deletions(-) diff --git a/apps/server/web/src/components/connect/ssh-forward-fields.tsx b/apps/server/web/src/components/connect/ssh-forward-fields.tsx index a9a91eaa1..10ba2d11e 100644 --- a/apps/server/web/src/components/connect/ssh-forward-fields.tsx +++ b/apps/server/web/src/components/connect/ssh-forward-fields.tsx @@ -57,109 +57,115 @@ export function SshForwardFields({

{rows.map((row, index) => { const problem = sshForwardRowProblem(row); - const needsTarget = row.kind !== "dynamic"; const idPrefix = `ssh-fwd-${String(index)}`; const helpId = `${idPrefix}-kind-help`; return ( -
-
- - - Kind - - patchRow(index, { kind: (value ?? "local") as SshForwardDraftRow["kind"] })} - > - {KIND_LABELS.map((option) => ( - - - - {option.label} - - - ))} - - - - - patchRow(index, { listenPort: event.target.value })} - /> - - {needsTarget && ( - <> - - - patchRow(index, { targetHost: event.target.value })} - /> - - - - patchRow(index, { targetPort: event.target.value })} - /> - - - )} - {(allowWideBind || row.bindHost !== "") && ( - - - patchRow(index, { bindHost: event.target.value })} + patchRow(index, { kind: (value ?? "local") as SshForwardDraftRow["kind"] })} + > + {/* The Authentication field's shape: the chosen option carries its + caption and its controls in the column beside the radio, and the + other options stay one line each. */} + {KIND_LABELS.map((option) => { + const selected = row.kind === option.value; + return ( + + +
+ + {option.label} + + {selected && ( + <> + + {KIND_HELP[option.value]} + +
+ + + patchRow(index, { listenPort: event.target.value })} + /> + + {option.value !== "dynamic" && ( + <> + + + patchRow(index, { targetHost: event.target.value })} + /> + + + + patchRow(index, { targetPort: event.target.value })} + /> + + + )} + {(allowWideBind || row.bindHost !== "") && ( + + + patchRow(index, { bindHost: event.target.value })} + /> + + )} + +
+ {problem && ( +

+ {problem.text} +

+ )} + + )} +
- )} - -
- {/* One sentence per chosen kind, kept below the row it explains; the checked radio points here. */} -

- {KIND_HELP[row.kind]} -

- {problem && ( -

- {problem.text} -

- )} -
+ ); + })} + ); })} {!allowWideBind && ( From 2a593d64d3feb878063d3f0a033d8bd99c190d27 Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 18:29:31 -0700 Subject: [PATCH 14/35] feat(ssh): one-line route diagrams under each kind caption The prose names the ends; a mono line traces one request's path with the example values from the field placeholders (5432, db.internal), so the direction is legible at a glance without reading the sentence first. Co-Authored-By: Claude --- .../components/connect/ssh-forward-fields.tsx | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/apps/server/web/src/components/connect/ssh-forward-fields.tsx b/apps/server/web/src/components/connect/ssh-forward-fields.tsx index 10ba2d11e..caeacce96 100644 --- a/apps/server/web/src/components/connect/ssh-forward-fields.tsx +++ b/apps/server/web/src/components/connect/ssh-forward-fields.tsx @@ -23,15 +23,25 @@ const KIND_LABELS = [ /** * What the chosen kind does and where its port lives, kept beside the row it - * explains (spec §7). "The connecting machine" and "the destination" name the - * two ends: on a remote-node launch "this machine" would read as the browser. + * explains (spec §7). The ends are named ("the machine you connect from", + * "the destination") because on a remote-node launch a bare "this machine" + * would read as the browser. The diagram line traces one request's route with + * the example values from the placeholders, so the prose names the ends and + * the arrows show the direction. */ const KIND_HELP: Record = { - local: "The port opens on the connecting machine, and programs there reach the target through the destination.", + local: + "Opens a port on the machine you connect from. Anything you send to that port reaches the target through the destination.", remote: - "The destination opens the port and reaches the target through the connecting machine; its own SSH server decides how it binds.", + "Opens a port on the destination. Anything sent to that port reaches the target on the machine you connect from. The destination's own SSH settings decide whether the port may bind.", dynamic: - "A SOCKS proxy port opens on the connecting machine; tools that use it pick their own targets and reach them through the destination.", + "Opens a SOCKS proxy port on the machine you connect from. Tools pointed at it choose their own targets, each reached through the destination.", +}; + +const KIND_DIAGRAM: Record = { + local: "program → 5432 on the connecting machine → destination → db.internal:5432", + remote: "program on the destination → 9090 there → connecting machine → app.local:3000", + dynamic: "SOCKS client → 1080 on the connecting machine → destination → any host it asks for", }; export function SshForwardFields({ @@ -87,6 +97,9 @@ export function SshForwardFields({ {KIND_HELP[option.value]} + + {KIND_DIAGRAM[option.value]} +
); From 23ce0aa327ce925c0c053eda0ee83f9d5320afe0 Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 19:24:51 -0700 Subject: [PATCH 18/35] feat(ssh): drop the loopback note from the forward fields The standing sentence about loopback binds and the owner's wide-forwards setting sat under every open editor and every list. The bind field's absence already says loopback-only to anyone who looks for the field, and the node's SSH settings card carries the full explanation where the switch actually lives. --- .../src/components/connect/__tests__/connect-panel.test.tsx | 4 +--- .../web/src/components/connect/ssh-forward-fields.tsx | 6 ------ 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx b/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx index 732b46a6b..3c2b56cae 100644 --- a/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx +++ b/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx @@ -779,9 +779,8 @@ it("carries added port forwards in the launch body, and holds Add until the edit expect(screen.queryByLabelText(/Local port/) === null).toBe(true); fireEvent.click(screen.getByRole("button", { name: "Add port forward" })); await settle(); - // Loopback-only machine: no bind field offered, and the reason is on screen. + // Loopback-only machine: no bind field offered. expect(screen.queryByLabelText(/Bind address/)).toBeNull(); - expect(screen.getByText(/loopback/i)).toBeTruthy(); // Disabled state rides aria-disabled, not the attribute: the node-admin // Button stays focusable while it explains why Add is held (Base UI). const addDisabled = () => { @@ -895,7 +894,6 @@ it("offers the bind address on a machine whose owner allows wide binds", async ( fireEvent.click(screen.getByRole("button", { name: "Add port forward" })); await settle(); expect(screen.getByLabelText(/Bind address/)).toBeTruthy(); - expect(screen.queryByText(/owner can allow other bind addresses/i)).toBeNull(); } finally { restore(); } diff --git a/apps/server/web/src/components/connect/ssh-forward-fields.tsx b/apps/server/web/src/components/connect/ssh-forward-fields.tsx index c51cc3f98..18254278d 100644 --- a/apps/server/web/src/components/connect/ssh-forward-fields.tsx +++ b/apps/server/web/src/components/connect/ssh-forward-fields.tsx @@ -236,12 +236,6 @@ export function SshForwardFields({ )} - {!allowWideBind && (composing || rows.length > 0) && ( -

- Forwards bind this machine's own loopback address. Its owner can allow other bind addresses in the node's SSH - settings. -

- )} {rows.length > 0 && ( // The declared forwards as the dialog's other lists read (the sharing // dialog's grants are the worked example): one line each, the control From 6680473e8b9ef6ae4a16eba89b49066b01bc2a88 Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 19:29:01 -0700 Subject: [PATCH 19/35] feat(ssh): say what the wide-forwards switch refuses The off-state sentence described loopback in the abstract and left the reader guessing whether turning it on moved every forward to 0.0.0.0. It states the rule and the permission instead: forwards listen on 127.0.0.1 while off; on lets a forward name another address. --- apps/server/web/src/components/nodes/node-ssh-card.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/web/src/components/nodes/node-ssh-card.tsx b/apps/server/web/src/components/nodes/node-ssh-card.tsx index 0018777de..9ab9b8e0b 100644 --- a/apps/server/web/src/components/nodes/node-ssh-card.tsx +++ b/apps/server/web/src/components/nodes/node-ssh-card.tsx @@ -62,7 +62,7 @@ export function NodeSshCard({ node, children }: { node: NodeDetail; children?: R

{node.sshWideForwards ? "Port forwards can bind an address other machines can reach, so this machine routes their traffic into whatever the connection reaches. Turning this off refuses future wide binds; running forwards keep going until their pane closes." - : "Port forwards bind this machine's own loopback address only, so just programs on this machine can use them. Turn this on to let a forward bind an address other machines can reach."} + : "Port forwards here listen on 127.0.0.1 only. Turn this on to let a forward bind another address, such as 0.0.0.0 for every interface."}

{!node.canManage && (

Only this node’s owner can change this setting.

From 8c2862fa354e7f2eb5c9d3540cc304fdb9ca37ba Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 19:30:49 -0700 Subject: [PATCH 20/35] feat(ssh): state the loopback rule in the forward section When the selected machine has not allowed wide binds, the section says up front that its forwards listen on 127.0.0.1 only and where the owner can change that, in the same words the node's SSH card uses. The bind field's absence was the only signal, and it reads as a missing feature rather than a rule. --- .../src/components/connect/__tests__/connect-panel.test.tsx | 4 ++++ .../web/src/components/connect/ssh-forward-fields.tsx | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx b/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx index 3c2b56cae..dd5e3f913 100644 --- a/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx +++ b/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx @@ -777,6 +777,8 @@ it("carries added port forwards in the launch body, and holds Add until the edit await settle(); // Nobody declared a forward: the section is one button, not a form. expect(screen.queryByLabelText(/Local port/) === null).toBe(true); + // The machine's bind rule is stated up front, not discovered at launch. + expect(screen.getByText(/127\.0\.0\.1 only/)).toBeTruthy(); fireEvent.click(screen.getByRole("button", { name: "Add port forward" })); await settle(); // Loopback-only machine: no bind field offered. @@ -894,6 +896,8 @@ it("offers the bind address on a machine whose owner allows wide binds", async ( fireEvent.click(screen.getByRole("button", { name: "Add port forward" })); await settle(); expect(screen.getByLabelText(/Bind address/)).toBeTruthy(); + // The owner already allowed wide binds: no loopback-only sentence. + expect(screen.queryByText(/127\.0\.0\.1 only/) === null).toBe(true); } finally { restore(); } diff --git a/apps/server/web/src/components/connect/ssh-forward-fields.tsx b/apps/server/web/src/components/connect/ssh-forward-fields.tsx index 18254278d..50a35c4d6 100644 --- a/apps/server/web/src/components/connect/ssh-forward-fields.tsx +++ b/apps/server/web/src/components/connect/ssh-forward-fields.tsx @@ -103,6 +103,12 @@ export function SshForwardFields({ Each forward opens a listening port for as long as this connection lives, and the port closes when the subshell ends.

+ {!allowWideBind && ( +

+ Forwards on this machine listen on 127.0.0.1 only, so just programs running on it can reach the port. Its + owner can allow other bind addresses in the node's SSH settings. +

+ )} {!composing ? ( - diff --git a/apps/server/web/src/components/nodes/node-ssh-card.tsx b/apps/server/web/src/components/nodes/node-ssh-card.tsx index 5082d037f..3afcba648 100644 --- a/apps/server/web/src/components/nodes/node-ssh-card.tsx +++ b/apps/server/web/src/components/nodes/node-ssh-card.tsx @@ -62,7 +62,7 @@ export function NodeSshCard({ node, children }: { node: NodeDetail; children?: R

{node.sshWideForwards ? "Forwards here are no longer confined to 127.0.0.1. One can bind a LAN address or 0.0.0.0, and any machine that reaches it gets through to the target via the SSH connection. Turning this off affects new subshells only." - : "Port forwards here listen on 127.0.0.1 only. Turn this on to let a forward bind another address, such as 0.0.0.0 for every interface."} + : "Port forwards on this machine listen on 127.0.0.1 only. Turn this on to let a forward bind another address, such as 0.0.0.0 for every interface. Remote forwards bind on the destination under its own policy either way."}

{!node.canManage && (

Only this node’s owner can change this setting.

diff --git a/apps/server/web/src/components/ssh-pane-labels.tsx b/apps/server/web/src/components/ssh-pane-labels.tsx index bbc7008b3..3b01072f9 100644 --- a/apps/server/web/src/components/ssh-pane-labels.tsx +++ b/apps/server/web/src/components/ssh-pane-labels.tsx @@ -24,7 +24,13 @@ export function SshPaneLabels({ nodeLabel, }: { subshell?: SubshellView; - /** The pane's machine as the page labels it; null means "a node you can't see", which the tooltip omits. */ + /** + * The pane's machine through the page's shared `nodeLabelFor` ladder: the + * display name once the nodes read answers, a short id fragment while it has + * never answered, and "node you can't see" for a node off the viewer's + * list. The tooltip shows whatever the ladder resolved, top line; null or + * empty (a caller with nothing to say) simply omits the line. + */ nodeLabel?: string | null; }) { if (subshell?.ssh !== true) return null; diff --git a/apps/server/web/src/lib/ssh-forwards.ts b/apps/server/web/src/lib/ssh-forwards.ts index ac2674b01..b6b89aacf 100644 --- a/apps/server/web/src/lib/ssh-forwards.ts +++ b/apps/server/web/src/lib/ssh-forwards.ts @@ -153,7 +153,10 @@ export function sshForwardRowsToWire(rows: readonly SshForwardDraftRow[]): SshFo export const FORWARD_EXAMPLE: Record = { local: { listen: "5432", host: "db.internal", port: "5432" }, remote: { listen: "9090", host: "app.local", port: "3000" }, - dynamic: { listen: "1080", host: "db.internal", port: "5432" }, + // No target fields are rendered for a SOCKS forward and its diagram names + // no target, so the host and port here are deliberately empty: nothing may + // start treating them as a real example. + dynamic: { listen: "1080", host: "", port: "" }, }; /** From c99109c1d7b4f48d471b2d7baea01b0e9a62c2b2 Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 21:19:38 -0700 Subject: [PATCH 29/35] fix(ssh): answer the second review round MINOR: a stored forward list that fails its grammar is now withheld as null, not folded into []. An empty list is the claim that the connection opened no ports, and the failed-read-vs-empty distinction the app holds everywhere else was being rendered as an affirmative absence beside running ports. The destination still shows; the header stays silent where the read failed. NITs: the view-schema comment names the real inference-depth site (the App-type-wide agent-or-ssh unions that flattened the input copy) rather than misdescribing SubshellSchema's bare consumers; the gold note says "the machine you connect from", the file's own vocabulary; and a half-typed JSDoc line stopped wrapping as "half -typed". --- apps/server/api/src/api/models.ts | 17 ++++++------- .../__tests__/ssh-pane-summary.test.ts | 5 ++-- .../src/services/subshell-manager.service.ts | 13 ++++++---- .../__tests__/ssh-pane-labels.test.tsx | 11 +++++++++ .../components/connect/ssh-forward-fields.tsx | 4 ++-- .../web/src/components/ssh-pane-labels.tsx | 24 ++++++++++++------- apps/server/web/src/lib/ssh-forwards.ts | 6 ++--- apps/server/web/src/types/subshell.ts | 8 +++++-- 8 files changed, 57 insertions(+), 31 deletions(-) diff --git a/apps/server/api/src/api/models.ts b/apps/server/api/src/api/models.ts index 74ad7e776..eee4fbd94 100644 --- a/apps/server/api/src/api/models.ts +++ b/apps/server/api/src/api/models.ts @@ -4,12 +4,12 @@ import { t } from "elysia"; * One declared port forward as the pane header shows it (spec 2026-10-15): * the row exactly as launch approved it, with the nulls the wire carries. * The renderer for words lives in the web app, not here. A separate - * declaration from `ssh-views.ts`'s input envelope on purpose, same reason - * that file documents for its own third copy: this one rides `SubshellSchema`, - * which answers from routes that also union agent bodies and responses, and - * the expressive input kind-census there has already pushed those unions - * around the inference-depth edge once (measured the day `forwards` arrived). - * The protocol parser owns the grammar for all of them. + * declaration from `ssh-views.ts`'s input envelope on purpose: every schema + * here joins one exported `App` type, and the expressive input kind-census + * has already pushed the agent-or-ssh unions in that type past TypeScript's + * inference-depth edge once (measured the day `forwards` arrived; it is why + * `SshForwardRowInputSchema` is flat). The view copy stays constraint-light + * for the same reason. The protocol parser owns the grammar for all of them. */ export const SshForwardViewSchema = t.Object({ kind: t.Union([t.Literal("local"), t.Literal("remote"), t.Literal("dynamic")], { @@ -101,8 +101,9 @@ export const SubshellSchema = t.Object({ t.Object( { destination: t.String({ description: "Canonical user@host:port the pane connected to" }), - forwards: t.Array(SshForwardViewSchema, { - description: "The port forwards declared at launch, in the order added; empty when none (spec 2026-10-15)", + forwards: t.Nullable(t.Array(SshForwardViewSchema, { description: "A declared forward row" }), { + description: + "The port forwards declared at launch, in the order added; empty when none; null when the pane's stored rows can't be read, which the header must not render as 'none' (spec 2026-10-15)", }), }, { diff --git a/apps/server/api/src/services/__tests__/ssh-pane-summary.test.ts b/apps/server/api/src/services/__tests__/ssh-pane-summary.test.ts index 58cc6e5f4..308912dc8 100644 --- a/apps/server/api/src/services/__tests__/ssh-pane-summary.test.ts +++ b/apps/server/api/src/services/__tests__/ssh-pane-summary.test.ts @@ -62,9 +62,10 @@ describe("sshPaneSummary", () => { it("keeps the destination when a stored row list fails its grammar", () => { // A header is not where a stored-row bug gets surfaced: the destination - // still earned its place, the broken list is withheld. + // still earned its place, and the broken list is WITHHELD as null - never + // folded into [], because [] is the claim that no ports were opened. const summary = sshPaneSummary(JSON.stringify({ ...column, declaredForwards: [{ kind: "sideways" }] })); expect(summary?.destination).toBe("theo@build.example:22"); - expect(summary?.forwards).toEqual([]); + expect(summary?.forwards).toBeNull(); }); }); diff --git a/apps/server/api/src/services/subshell-manager.service.ts b/apps/server/api/src/services/subshell-manager.service.ts index d6525c759..d6daff0d8 100644 --- a/apps/server/api/src/services/subshell-manager.service.ts +++ b/apps/server/api/src/services/subshell-manager.service.ts @@ -2329,7 +2329,7 @@ export function toSubshellView( * and a column that cannot be read yields nothing. A list must never throw * over one row's bytes. Exported for direct test (`ssh-pane-summary.test.ts`). */ -export function sshPaneSummary(raw: string | null): { destination: string; forwards: SshForwardWire[] } | null { +export function sshPaneSummary(raw: string | null): { destination: string; forwards: SshForwardWire[] | null } | null { if (raw === null) return null; let parsed: unknown; try { @@ -2344,8 +2344,11 @@ export function sshPaneSummary(raw: string | null): { destination: string; forwa // The same composer the audit row and the recent-destinations key use, so // the header's "canonical" is one spelling, not a second one that drifts. const destination = sshCanonicalDestination({ host: column.host, port: column.port, user: column.user }); - // A row list that fails its grammar shows no list: the destination still - // earned its place, and a header is not the place to surface a stored-row bug. - const forwards = parseSshForwardRows(column.declaredForwards ?? []); - return { destination, forwards: forwards ?? [] }; + // A row list that fails its grammar is WITHHELD (null), not folded into the + // empty list: an empty list is the claim "this pane opened no ports", and a + // stored row a tightened rule would now refuse may well have opened them. + // Failed reads stay distinct from empty ones; the destination still earned + // its place, and a header is not where a stored-row bug gets surfaced. + const forwards = column.declaredForwards === undefined ? [] : parseSshForwardRows(column.declaredForwards); + return { destination, forwards }; } diff --git a/apps/server/web/src/components/__tests__/ssh-pane-labels.test.tsx b/apps/server/web/src/components/__tests__/ssh-pane-labels.test.tsx index 60cda90cf..d1fb85ff3 100644 --- a/apps/server/web/src/components/__tests__/ssh-pane-labels.test.tsx +++ b/apps/server/web/src/components/__tests__/ssh-pane-labels.test.tsx @@ -93,6 +93,17 @@ it("names the machine even when the pane carries no summary", async () => { expect(screen.getByText("This subshell connects to another machine over SSH.")).toBeTruthy(); }); +it("says nothing about forwards when the stored rows could not be read", async () => { + // A null list is a failed read, not an empty one: the destination stands + // and no "No port forwards" claim is made beside it. + render( + , + ); + await act(async () => screen.getByRole("button", { name: "SSH connection" }).focus()); + await waitFor(() => expect(screen.getByText("deploy@host:2222")).toBeTruthy()); + expect(screen.queryByText("No port forwards") === null).toBe(true); +}); + it("says so when the connection carries no forwards", async () => { render( , diff --git a/apps/server/web/src/components/connect/ssh-forward-fields.tsx b/apps/server/web/src/components/connect/ssh-forward-fields.tsx index 1b5a67997..e20189aee 100644 --- a/apps/server/web/src/components/connect/ssh-forward-fields.tsx +++ b/apps/server/web/src/components/connect/ssh-forward-fields.tsx @@ -114,8 +114,8 @@ export function SshForwardFields({ {!allowWideBind && (

Until the owner turns on “Allow wide port forwards” in this node's SSH - settings, a forward can only bind 127.0.0.1 on this machine, so just local programs can reach the port. A - remote forward binds on the destination instead, under the destination's own policy. + settings, a forward can only bind 127.0.0.1 on the machine you connect from, so just programs there can reach + the port. A remote forward binds on the destination instead, under the destination's own policy.

)} {!composing ? ( diff --git a/apps/server/web/src/components/ssh-pane-labels.tsx b/apps/server/web/src/components/ssh-pane-labels.tsx index 3b01072f9..739e05836 100644 --- a/apps/server/web/src/components/ssh-pane-labels.tsx +++ b/apps/server/web/src/components/ssh-pane-labels.tsx @@ -58,15 +58,21 @@ export function SshPaneLabels({ ) : ( <>

{summary.destination}

- {summary.forwards.length === 0 ? ( -

No port forwards

- ) : ( - summary.forwards.map((row) => ( -

- {sshForwardWireLine(row)} -

- )) - )} + {/* An empty list is the claim "this connection opened no + ports" - made only when the pane can make it. A null + list (the stored rows can't be read) stays silent: the + destination line stands, and a failed read is never + rendered as absence. */} + {summary.forwards !== null && + (summary.forwards.length === 0 ? ( +

No port forwards

+ ) : ( + summary.forwards.map((row) => ( +

+ {sshForwardWireLine(row)} +

+ )) + ))} )} diff --git a/apps/server/web/src/lib/ssh-forwards.ts b/apps/server/web/src/lib/ssh-forwards.ts index b6b89aacf..d06398678 100644 --- a/apps/server/web/src/lib/ssh-forwards.ts +++ b/apps/server/web/src/lib/ssh-forwards.ts @@ -146,9 +146,9 @@ export function sshForwardRowsToWire(rows: readonly SshForwardDraftRow[]): SshFo /** * The example each kind uses for a still-empty slot. It is the single source - * of truth for BOTH the field placeholders and the route diagram, so a half - * -typed row never shows one example in its input and a different one in its - * diagram. `dynamic` has no target, so its host/port go unused. + * of truth for BOTH the field placeholders and the route diagram, so a + * half-typed row never shows one example in its input and a different one in + * its diagram. `dynamic` has no target, so its host/port are empty. */ export const FORWARD_EXAMPLE: Record = { local: { listen: "5432", host: "db.internal", port: "5432" }, diff --git a/apps/server/web/src/types/subshell.ts b/apps/server/web/src/types/subshell.ts index 6e56576d2..2f7fb874b 100644 --- a/apps/server/web/src/types/subshell.ts +++ b/apps/server/web/src/types/subshell.ts @@ -98,8 +98,12 @@ export interface SubshellView { sshSummary?: { /** Canonical `user@host:port` the pane connected to */ destination: string; - /** The declared forwards in the order added; empty when the launch carried none */ - forwards: SshForwardRow[]; + /** + * The declared forwards in the order added; empty when the launch carried + * none. Null means the stored rows could not be read: absence of data, + * not absence of forwards, and the header says nothing rather than "none". + */ + forwards: SshForwardRow[] | null; }; /** * How many sharing grants this subshell carries; 0 = private to its owner. From 0c86fc2afb8494d0852717fa603bb4f7b7ec39e5 Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 22:13:55 -0700 Subject: [PATCH 30/35] docs: showcase SSH forwarding and refresh sharing controls --- .changeset/sharing-permission-selectors.md | 5 + .changeset/ssh-forwarding-guide-screenshot.md | 7 ++ .changeset/website-ssh-sharing-screenshots.md | 5 + apps/docs/content/docs/guides/sharing.mdx | 8 +- apps/docs/content/docs/guides/ssh.mdx | 57 ++++++++-- apps/docs/content/docs/nodes/manage.mdx | 2 +- .../public/screenshots/session-sharing.png | Bin 88434 -> 76445 bytes .../public/screenshots/ssh-connection.png | Bin 0 -> 252680 bytes .../__tests__/sharing-dialog.test.tsx | 9 +- .../src/components/sharing-dialog-core.tsx | 105 ++++++++++++------ apps/website/AGENTS.md | 2 +- apps/website/components/feature-showcase.tsx | 20 +++- .../website/public/shots/session-sharing.webp | Bin 48728 -> 40648 bytes apps/website/public/shots/ssh-connection.webp | Bin 0 -> 121536 bytes e2e/AGENTS.md | 2 +- e2e/capture-docs.ts | 19 ++++ 16 files changed, 182 insertions(+), 59 deletions(-) create mode 100644 .changeset/sharing-permission-selectors.md create mode 100644 .changeset/ssh-forwarding-guide-screenshot.md create mode 100644 .changeset/website-ssh-sharing-screenshots.md create mode 100644 apps/docs/public/screenshots/ssh-connection.png create mode 100644 apps/website/public/shots/ssh-connection.webp diff --git a/.changeset/sharing-permission-selectors.md b/.changeset/sharing-permission-selectors.md new file mode 100644 index 000000000..bb43a0eb0 --- /dev/null +++ b/.changeset/sharing-permission-selectors.md @@ -0,0 +1,5 @@ +--- +"@internal/server": patch +--- + +Use aligned permission dropdowns and a styled, searchable recipient picker in subshell and node sharing dialogs. Clear added recipients from the picker and require a valid selection before adding a grant. diff --git a/.changeset/ssh-forwarding-guide-screenshot.md b/.changeset/ssh-forwarding-guide-screenshot.md new file mode 100644 index 000000000..ddb2e9c86 --- /dev/null +++ b/.changeset/ssh-forwarding-guide-screenshot.md @@ -0,0 +1,7 @@ +--- +"@internal/docs": patch +--- + +Expand the SSH port-forwarding guide with connection steps, local and remote examples, access rules, troubleshooting, and a screenshot of the SSH panel. + +Refresh the sharing screenshot and instructions for permission dropdowns and the searchable recipient picker. diff --git a/.changeset/website-ssh-sharing-screenshots.md b/.changeset/website-ssh-sharing-screenshots.md new file mode 100644 index 000000000..ef3545394 --- /dev/null +++ b/.changeset/website-ssh-sharing-screenshots.md @@ -0,0 +1,5 @@ +--- +"@internal/website": patch +--- + +Show SSH port forwarding on the homepage with a new connection-panel screenshot, and refresh the sharing screenshot with permission dropdowns and recipient search. diff --git a/apps/docs/content/docs/guides/sharing.mdx b/apps/docs/content/docs/guides/sharing.mdx index 2b12f3c28..72ab741a6 100644 --- a/apps/docs/content/docs/guides/sharing.mdx +++ b/apps/docs/content/docs/guides/sharing.mdx @@ -12,12 +12,12 @@ Only the owner can change sharing. The recipient needs an account on the same in ## Grant access 1. In the server dashboard's sidebar, choose **Subshells**. Open the **⋯** menu on your session's card or row and choose **Share…**. -2. Choose named users or **Everyone**. -3. Select view or edit access. -4. Save the sharing settings. +2. In **Search people…**, search by email or name and choose a recipient, or choose **Everyone**. +3. Select **View** or **View + edit** from the access dropdown, then select **Add**. Repeat for other recipients. +4. Review the listed recipients and their access levels, then select **Save**.
- +
Select an access level for each recipient, then save the grants. Select the image to enlarge it.
diff --git a/apps/docs/content/docs/guides/ssh.mdx b/apps/docs/content/docs/guides/ssh.mdx index c0ae50743..69654fe2b 100644 --- a/apps/docs/content/docs/guides/ssh.mdx +++ b/apps/docs/content/docs/guides/ssh.mdx @@ -93,7 +93,7 @@ For these connections, Subshell stores the public host key and destination in th Connections using the connecting node's own keys or a password use that node's OpenSSH trust records instead. They automatically remember a previously unknown host key and reject a changed one. To verify a destination before that first connection, have the node's owner establish its trusted host key on the node first. -Once connected, the terminal shows the destination's shell. Its top bar has an **SSH connection** icon with a tooltip identifying it as an SSH connection. If you selected password authentication, wait for its password prompt before typing. Only the owner signed in to the dashboard can type in an SSH subshell; MCP and other bearer credentials cannot send input. Session [sharing](/guides/sharing) lets other users view its output. +Once connected, the terminal shows the destination's shell. Its top bar has an **SSH connection** icon whose tooltip shows the connecting node, destination, and any port forwards declared for that subshell. If you selected password authentication, wait for its password prompt before typing. Only the owner signed in to the dashboard can type in an SSH subshell; MCP and other bearer credentials cannot send input. Session [sharing](/guides/sharing) lets other users view its output. ## SSH presets and recent destinations @@ -118,24 +118,57 @@ If you select **Save as preset** while launching and the connection fails, the s ## Port forwards -Some services, like a database or an admin API, live behind the destination and have no other route. A connection can open a port that reaches them: in the connection form, choose **Advanced SSH options**, then **Add port forward**. Every forward belongs to that connection, and the port closes when the subshell ends. +Forward ports through an SSH connection to reach a web app, database, or other TCP service. Each listening port lasts for the SSH connection and closes when the subshell ends. -| Kind | Where the port opens | What it does | +### Add a port forward + +1. In the dashboard, open **Subshells** → **New subshell** → **SSH terminal**. Choose **Connect from**, enter the **SSH destination**, and choose how to authenticate. +2. Under **Port forwards**, select **Add port forward**. +3. Choose a kind from the table below. Enter **Local port** or **Remote port** and, for local or remote forwarding, **Target host** and **Target port**. +4. Select **Add port forward** again to add the completed row to the connection. Repeat for more ports, up to 16 forwards. Use **Remove** to discard a row; to change one, remove it and add its replacement. +5. Review the listed forwards, then select **Start SSH subshell**. Follow the authentication and destination-verification steps above. + +| Kind | Where the port opens | Where the target is reached from | | --- | --- | --- | -| **Local** (`-L`) | The connecting node | Programs on that node reach a host the destination can see. | -| **Remote** (`-R`) | The destination | The destination reaches a host the connecting node can see. The destination's own SSH server configuration decides whether and how it binds the port. | -| **Dynamic SOCKS** (`-D`) | The connecting node | Tools configured to use the SOCKS proxy pick their own destinations at runtime and reach them through the connection. | +| **Local (-L)** | The connecting node | The SSH destination. Use it for a web app or database reachable from that destination. | +| **Remote (-R)** | The SSH destination | The connecting node. Use it to make a service reachable from the destination. Its SSH server decides whether and how the listening port binds. | +| **Dynamic SOCKS (-D)** | The connecting node | The SSH destination. Configure a tool to use this SOCKS proxy; the tool chooses the target for each request. | + +“Local” means the node selected under **Connect from**, which may be a different machine from your browser. Choosing a key from your laptop doesn't move the listening port to your laptop. + +
+ +
Review the listed ports beneath the authentication options before connecting. Select the image to enlarge it.
+
+ +### Reach a web app on the destination + +Suppose a web app listens on `127.0.0.1:3000` on the SSH destination. Add a **Local (-L)** forward with **Local port** `8080`, **Target host** `127.0.0.1`, and **Target port** `3000`. After connecting, open `http://127.0.0.1:8080` in a browser running on the connecting node. Here, the target's `127.0.0.1` means the SSH destination. + +For the reverse direction, choose **Remote (-R)** with **Remote port** `8080` and the same target values. Programs on the SSH destination can then use its port `8080` to reach port `3000` on the connecting node. The target's `127.0.0.1` now means the connecting node. + +For **Dynamic SOCKS (-D)**, enter only a **Local port**, such as `1080`, then configure a SOCKS-capable tool on the connecting node to use `127.0.0.1:1080`. + +### Bind addresses and access + +Local and dynamic forwards bind `127.0.0.1` on the connecting node by default. Programs on that node, including those run by other OS users, can reach the port. Opening the dashboard elsewhere doesn't make that port available there. + +> [!warning] Allowing access from other machines +> A non-loopback bind can let other machines route traffic through your SSH connection to services it can reach. Only enable it when you intend to give those machines that access. + +The node's owner can open **Nodes**, select the connecting node, and turn on **Allow wide port forwards** in its **SSH** card while **Allow SSH** is enabled. An administrator manages this for the built-in node on the server host. See [Manage a node](/nodes/manage#outgoing-ssh). + +With wide forwards allowed, the form shows **Bind address**. Leaving it blank keeps the default; a value such as `0.0.0.0` requests all IPv4 interfaces. A non-loopback address requires this permission even for a remote forward, and the destination's SSH server still decides how a remote port binds. Turning the setting off blocks future wide-bind launches; existing forwards keep running until their connection ends. -Fill in a **Local port** (or **Remote port** for a remote forward), and for local and remote kinds the **Target host** and **Target port** you want to reach. A local or dynamic forward can also name a **Bind address**; left blank, the port binds the node's loopback address, so only programs on that node use it. +### Reuse and troubleshoot forwards -A non-loopback bind lets other machines route through the port into whatever the connection reaches. That's a decision about the machine, not the connection, so it stays off until the node's owner turns on **Allow wide port forwards** on the node's SSH card. A launch that asks for a wide bind is refused until then; turning the setting off again refuses future wide binds while forwards that are already running keep going until their subshell closes. +SSH presets save the listed forwards with the other connection choices. You can add them in **Presets** → **New preset** → **New SSH preset**, or select **Save as preset** in the connection form. Applying a preset fills the rows for review before you connect. Editing the preset doesn't change an existing subshell. -If the port you asked for is already taken on the node, the destination's shell still opens, and the terminal prints SSH's warning that the forward didn't bind. +If a requested port is already taken, the destination's shell can still open while SSH prints a warning that the forward couldn't bind. Check the terminal and try the service through the listening port; a working shell alone doesn't prove the forward is available. Choose an unused port and start a new SSH subshell if needed. -A preset carries forwards beside its other connection choices, so connecting from the preset fills the same rows; you can still edit or remove them before you start. The forward rows themselves are not stored anywhere else, and every launch records them in the audit trail with the connection. +Forwards declared in the connecting node's SSH configuration through `LocalForward`, `RemoteForward`, or `DynamicForward` remain unsupported and cause connection resolution to be refused. Remove those settings from the configuration used by the connection and add the forwards explicitly in Subshell's form or preset instead. Agent forwarding, X11 forwarding, and tunnel devices remain unsupported. -> [!note] -> A forward extends the reach your SSH login already grants; it doesn't add a new credential or a new trust. Forwards end with the subshell: to reopen a port after a disconnect, start a new SSH subshell. +The **SSH connection** tooltip shows the declared forwards, not a live check of their availability. Users who can view the subshell can see this summary, and each launch records a forwarding summary in the audit trail. If a node is too old to support forwards, update the machine named in the error before trying again. ## Make the destination a node diff --git a/apps/docs/content/docs/nodes/manage.mdx b/apps/docs/content/docs/nodes/manage.mdx index a1ffcce16..7f62a83ab 100644 --- a/apps/docs/content/docs/nodes/manage.mdx +++ b/apps/docs/content/docs/nodes/manage.mdx @@ -27,7 +27,7 @@ Only the node’s owner can change this setting. An administrator manages it for You can also enable it when starting an SSH subshell: the connection form explains the change before you submit. See [Connect over SSH](/guides/ssh). -With SSH allowed, the card gains **Allow wide port forwards**. While it's off, port forwards opened from this node bind the node's own loopback address, so only programs on that node can use them. Turning it on lets a launch bind a port other machines can reach, which routes their traffic through that connection. It's off by default and sits behind the same owner-only gate as **Allow SSH**; turning it off affects new launches, while forwards already running keep going until their subshell closes. +With SSH allowed, the card gains **Allow wide port forwards**. While it's off, local and dynamic port forwards bind the node's own loopback address, so only programs on that node can use them. Remote forwards listen on the SSH destination under its SSH server's policy. See [Port forwards](/guides/ssh#port-forwards) for examples and bind-address rules. Turning it on lets a launch bind a port other machines can reach, which routes their traffic through that connection. It's off by default and sits behind the same owner-only gate as **Allow SSH**; turning it off affects new launches, while forwards already running keep going until their subshell closes. ## Service and logs diff --git a/apps/docs/public/screenshots/session-sharing.png b/apps/docs/public/screenshots/session-sharing.png index 219702a5f6e57b8a60156e4626cd96907ac1bcd9..6a0ce3907ab818d9fd9dd9850f2721be1fdaba17 100644 GIT binary patch delta 29212 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

TE<(( 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 BnwtOs literal 0 HcmV?d00001 diff --git a/apps/server/web/src/components/__tests__/sharing-dialog.test.tsx b/apps/server/web/src/components/__tests__/sharing-dialog.test.tsx index e7adc59b0..9977bf7cf 100644 --- a/apps/server/web/src/components/__tests__/sharing-dialog.test.tsx +++ b/apps/server/web/src/components/__tests__/sharing-dialog.test.tsx @@ -71,7 +71,14 @@ describe("SharingDialog", () => { renderDialog(); await screen.findByText("Everyone"); // Choose bob from the roster, then Add. - fireEvent.change(screen.getByLabelText("Share with"), { target: { value: "u2" } }); + const picker = screen.getByRole("combobox", { name: "Share with" }); + fireEvent.mouseDown(picker); + fireEvent.click(picker); + await screen.findByRole("option", { name: "bob@subshell.local" }); + fireEvent.change(picker, { target: { value: "nobody-matches" } }); + await screen.findByText("No matching people to add."); + fireEvent.change(picker, { target: { value: "bob" } }); + fireEvent.click(await screen.findByRole("option", { name: "bob@subshell.local" })); fireEvent.click(screen.getByRole("button", { name: "Add" })); expect(await screen.findByText("bob@subshell.local")).toBeDefined(); diff --git a/apps/server/web/src/components/sharing-dialog-core.tsx b/apps/server/web/src/components/sharing-dialog-core.tsx index 5975ba7a9..32c5f7196 100644 --- a/apps/server/web/src/components/sharing-dialog-core.tsx +++ b/apps/server/web/src/components/sharing-dialog-core.tsx @@ -1,5 +1,6 @@ import { Button, errMessage } from "@internal/node-admin"; -import { type ReactNode, useState } from "react"; +import { type ReactNode, useId, useState } from "react"; +import { SearchableSelect } from "@/components/ui/combobox"; import { Dialog, DialogContent, @@ -8,7 +9,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; -import { Segmented } from "@/components/ui/segmented"; +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import type { RosterUser, ShareDraft } from "@/hooks/use-subshell-shares"; import type { SubshellAccess } from "@/types/subshell"; @@ -20,7 +21,34 @@ const LEVEL_OPTIONS: { value: ShareLevel; label: string }[] = [ { value: "edit", label: "View + edit" }, ]; -/** The Everyone grant's sentinel in the native picker (resolved to null on add). */ +function ShareLevelSelect({ + label, + value, + onChange, +}: { + label: string; + value: ShareLevel; + onChange: (value: ShareLevel) => void; +}) { + return ( + + ); +} + +/** The Everyone grant's sentinel in the picker (resolved to null on add). */ const EVERYONE = "everyone"; /** @@ -86,9 +114,16 @@ export function SharingDialogCore({ const [pickLevel, setPickLevel] = useState("view"); const [error, setError] = useState(null); + const pickerId = useId(); const grants = draft ?? serverGrants; const alreadyShared = new Set(grants.map((g) => g.granteeUserId ?? EVERYONE)); + const recipientOptions = [ + { value: EVERYONE, label: "Everyone" }, + ...roster.map((user) => ({ value: user.id, label: user.email, searchText: user.name })), + ].filter((option) => !alreadyShared.has(option.value)); + const selectedRecipient = recipientOptions.some((option) => option.value === pickId) ? pickId : ""; + function handleClose(next: boolean) { if (!next) { setDraft(null); @@ -106,9 +141,11 @@ export function SharingDialogCore({ } function addGrant() { - const granteeUserId = pickId === EVERYONE ? null : pickId; + if (!selectedRecipient) return; + const granteeUserId = selectedRecipient === EVERYONE ? null : selectedRecipient; // Replace any existing grant to the same target rather than duplicating. setDraft([...grants.filter((g) => g.granteeUserId !== granteeUserId), { granteeUserId, permission: pickLevel }]); + setPickId(""); } async function save() { @@ -137,27 +174,29 @@ export function SharingDialogCore({

)} -
+
{isLoading &&

Loading…

} {!isLoading && grants.length === 0 && (

Not shared with anyone yet.

)} {grants.map((g, i) => ( -
- +
+ {g.granteeUserId === null ? "Everyone" : (roster.find((u) => u.id === g.granteeUserId)?.email ?? g.granteeUserId)} {canManage ? ( <> - changeLevel(i, level)} /> - @@ -170,29 +209,29 @@ export function SharingDialogCore({ ))} {canManage && ( -
- - -
diff --git a/apps/website/AGENTS.md b/apps/website/AGENTS.md index 13b5f4f6b..edc456820 100644 --- a/apps/website/AGENTS.md +++ b/apps/website/AGENTS.md @@ -84,7 +84,7 @@ and descriptive captions that do not require knowing abbreviations such as PWA. ## Feature screenshots -The five feature screenshots come from `e2e/capture-docs.ts`, shared with the +The six feature screenshots come from `e2e/capture-docs.ts`, shared with the documentation site. Capture at 2× with `e2e/screenshot.ts`, retain lossless WebP encoding, and use the product-colored screenshot frame tokens. The PNGs already include a consistent 12px margin; don't add another inset or a modal backdrop. diff --git a/apps/website/components/feature-showcase.tsx b/apps/website/components/feature-showcase.tsx index 237d8215b..3c244e989 100644 --- a/apps/website/components/feature-showcase.tsx +++ b/apps/website/components/feature-showcase.tsx @@ -27,10 +27,17 @@ const SHOTS = { sharing: { name: "session-sharing", width: 1072, - height: 768, - alt: "Subshell sharing dialog with separate View and View + edit permissions", + height: 760, + alt: "Subshell sharing dialog with permission dropdowns and a searchable recipient picker", caption: "Choose who can watch and who can type into the subshell.", }, + ssh: { + name: "ssh-connection", + width: 1200, + height: 1964, + alt: "SSH connection form with a connecting node, destination, authentication choices, and a local port forward", + caption: "Choose how to connect and forward ports through the same SSH connection.", + }, nodes: { name: "node-enrollment", width: 1072, @@ -188,17 +195,18 @@ export function FeatureShowcase() { > Open a remote shell from your dashboard. -
-
-

+

Connect over SSH and work in the terminal alongside your agents. Use keys from a connected node, choose a key from your own machine, or enter a password when connecting. Save frequently used connections as SSH - presets. + presets, including port forwards to reach a web app, database, or other service through the connection.

Explore SSH connections
+
+ +
diff --git a/apps/website/public/shots/session-sharing.webp b/apps/website/public/shots/session-sharing.webp index c115f0fc314c81316594cc99eaa3edf3d5552b9b..fa913fe71cb20506c5dee9f5bca7cdf01e4250d7 100644 GIT binary patch literal 40648 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
3OG*u+0zsi+LAFo4$AopO)fsYw4& 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 literal 48728 zcmZ6yV{qrs6D|Dt#&)tvHnwfswr$(qWH+|8vCWNbZftvFJNNg$_to?CtFD@=nXW$5 zQ!}T}s7OmlD0KsXrnrcbh7y;Y=zl&V<6sXk#pSRrm1v~?)$~HCw>V-4f%@?~X#6Pv zio#*LN>EBwo-OysA8#(0x|_>vVTc)LGw2neRU#M#nn%4%GH~4A(E}rke5R$MP$@rj zmKr3T|B(5z3jArcezPw45s^cN*b0k*<)IL(znWEKu%VIofW(d&<+(3Iaya;GU>=G?ni{g9TR|El(J_HU>Eg|K{p&^7(EJaUju3cdUKrT{AHJwY!=#TI-O1iD@KfB=Q#Q*p>fK}$<5vmXtjF7~ z8Jb6viP~rx4rP|3-N;j+0LW?I#d~ykE-Nv zZIxl&K-C1aAy22bfVbhuF5!{7>U~(!LLbV00st-j(UtKwXiMXYi+uHfZTmHghPjRV zl;i*`Ch!?SEXcaNU(oB9@VZ1|B2wT2Z|fB(JREk1@?)crdns_Lpe(>%jlhjEQk5-y zl!^r!1`dwJLxHN6S5cxlHC;{}(VsJespQy2SvU*;g;r#&7Sa42jyQC3p5`gBfuBG1 z7Tf`?wWvFo+S|4)VufZ{Q!!2ce+LzEj^Lx|;T=t;+ibty!Z)7o%govsV0uYEmZ{n$ zuXDY{?=RUav}am7EFfnxHSsZ7u+C*qDMtTLUYOA3yHL$>%7mPjv-!h-t~m!YjzbH zIynL!@b62p=-z5(IC|1|-;E6NS`+#h3b?B#EsN+gm=CoU8~Z*-A*3uZdzC*9|X zueU6=WN>mRmL6yOhM^K2>XZnkr`xNNttx0dq-XqJ!%3T*e2mPn9zmeB_@5!ql%FY- zvrtvm=(ticbXxF|9&V)Yh+2CTV^~m$Vl&7y&e{EDYMhsAW=%1ZMtc1Eh4&Hgm9a?e z;S!re^YBvB8Z)k8ZFsy!w_o%?(vq}^xXjD?#ds){h$o$>gRt|vn?R`F|0V^Ryd~kk zfREjxAMzPy_~BOBGc&59Pml#{-Y~|hS#5VWX`4DjHZfUcTRf-i{t>W-{|A1%z;WjP z3){Xv+C(CX+_t%r+p?tHlH>jOyF&YG$$<3J1=DnxVEI5cEN?ke>42K?8Oq`RO-;q2 zAP_%tT|;# z{F`Z7-0O4yCGJ=-_v?So43`=NrVkoXJi)#^4K)QhowUPFPl38PS^BM2NyKm}!TL@4 zPh!P~coh+AS$gnkh#vUA14U4Z;2h}Q;R0|37K4<*%!07gDnG z=1A)*+J%S0SEq;5Bxc|5@nWsx!Yg|aetHi~gb@ykEM{#CXGaJamLk~ zsB@z$Svbg>&%G{Ov{N|WoL4f_wK~q2N0QQUzTT=J8E3dciJmQfd1QI5-kkgSyAFmd zm=JG2%A-#dGCAI5HZ5fSSd#0u220J+emTln{RBN9{d|G|N8ZB50Kfdz3+m`r&Ne;GgUNfeprwN@nmuz()ZIyGX$mL z1ifYpL=gr()$#OGU_c%VUSh$&)Lm1$0++ceG+Wxxy;dc?;-mUuNWFFo$9C>VW=(e~ zFj-0n*_BQMdvQ;VKQh3rU`muU%Bx1OvQ|($UhHynmN`z?vUdEyuRXsp9?o3lB zyQw#~3X%Mba8vni%e(s!!~d$r@44eG27gU1mZMn8MzbIxiGIpd(Aei$_%fVJr|Erd zC&+Uy-g&Kt*OWfXYMww2-4i9L<9Tt7x4q-5x+`{AiPu7g)oUo)_;g=G4fk*1lS@`q zA|?$hVQp~tySu#4ZfX*DN<>D1)|VYNu;#kOq4wxpQAF9ra$xaP^+B&7@|@8Su39=e z%-OKUfGiHO5?l-b1LkRRmGl9$eu$IIee;a^eF}2kIDAvQWzBsw{4lhf;-9uwZKSsq z@<(s6vgIZki7R-XoDNaZYoVOIFr|f#9~q>y#@t(AOD9+~+`C{>Rbp}8Q&QAr(dzyq zY6Dqm-F^G%Pq3fE)xRH@#finqxp1q}_fi-*MZ+bUST!ig-piflat22td4uY1x1biR z8BIx5LL1_OOqYNk>J=vENC?s?SHPq$Z0p<)^j^xs_3OpF&zh4b7t7)#xGaU4on6b3 z^4jJ@SRz$*Zb0-roZPGkTo8bmQ4lPG*iK+HS6mXpX3(5@c_FD+qtVGwG(iCSdz12p zwN1U5dVjo;s`GE7Vd#(1GHA-( zpb6F}%I@_juVJ>K6{mxaXwV5y$5(oxSkTrt2H>TZzSiF$v`V%Un55HK7kq3`!#I`A z`mrrT2!J7!WwOVr3F%{(R`VcbL~u?RB7>(kQW5 zt4Zkuf|M3#G&u~%Zs%hK^?*vAXa6g3E`9*8tT~U%X$1rIX6$dvgJ&1~n)JtRMKHVP z(6|yDq)EpLlH~Zx`28c+?$K@U*B~UuMSL>d5ePd694e@Z+AW4~p)kxw!Odu(F#uF& za`6gLPNuYNg+1~9XL;>5QKc5zWL$vZrl@rbRByA$CYD`^hXISo#Qcl zulqDX{QY22*q=fJ-lr~Wpu9Sxx2OS{D7p~n4f||_dTwshetyv*Oq3z{nZNp0`+b=V zu`B?XjrBePNfk0se6Py_q>xmmf17>-R>%>7d>dlm*%u^E7iM~!a)05hp7NjTqu5Y>_MdiwA4^iuAiJWUl5w-Vyy zdNvTucuB%EgLyn!cK1u^4vJ$~=9Y)QJCvmT&ye29KrMnGj9`MsX;6AI3#H%5SeOl~ zix0{M&?^d%0aD5Uw#FjNlrJEXtrR%CGw7okMq#k|Y;{NJNruoK@yvUQ()b&V!Lfuo zc8gpw5*r6tN_c@!lHdtlQnWzmnV)JHIkJ4jShYPd53 z>m07!m~;w(DAU?jf~uSG?~qNLke|eR^`Jc3>(Kz)U;n^wlvk%_s^7lUe@kT$K?+mG z7l9M9z<8s~$S*&lZ&oa=o2 zBr}~DOn*S5e0MgMOMx`LDKXvvz>DN(`0%yhF3n$~6ukcOQ;jy!W|=Oi>*&cuxlrgc zsTgp|y2H`B^s>ICwusF724>naoyZ~MlHWEhJdw}A16cqLY&3tYo>F?JD)-8eq*64W zq9w9&l2-fEV(zY9o@h2FzfmL9i^DxjJtvcp%B_`Vf--G7#{%U&CbKEkNhWB3*M<*Q zSAZ?V#Xz3uo(BrE5pP0(5tiCtMm+?G|M2-lCgCiYtn)P$w`8^?P<`3Lp8cu9|Z_dUlfYynrLm z-+%|!D;I)}q~}x*^Kj!cJ1H3>+nnOKr>rfvYN9`FlhkgYP#Y=06OS<{WEtehgPu*> zkbh2g!{+y3FKUB;g95o=6hy-P`D6XQvRm&034`Y!VQ^oW#`ayy$F?;Xm-B|Nyl&hn zeHYnyL9>=d6()>JVc^d;;Y(2QcYjWO@>;O#HBWWL4Orv2BLAYJT&A_8hu%Ug}2(wH~i zXF#Ijyem(}m>vzpihIsu#hkL`?N^08VCkS(nyf}8+%zle@-roPlhPgcO=sJUarl`4~@ zcVxyDFw=dk{tbUb4K6;2iA~U(kWJkmzjIqUez^C(CCZphwu~r}7WWn;z0{0j);#{+ z)TQ8&5-gNLFR_@b452qX^qTaFcwTr}_h!{UWMVXFysFS!1>Y&n<#t45%A|zZS0V3* zqSL!**^_Y->s+vBsOgdYUO7KTGt#byH}LNYO43cyVnEQ|OUAPu3Poa>C-r9FTIS~@ zer0Op^wGAxvj!bM&^{^9u>;jhjqxXO!!DitK+#pxahP3=058OLu4LNs1aooGDx*&dyH{~1qUaozc;{E zCg{-FHSrDSMJxv%GGEy3l1s@V7B+XIzWqyASu4^>l|<%j)7zCtJWrvR&Ky?@m-9NV zd&MQcpdq*6$D+b{sS}Xburgu#rGq@LuB>2jR(=;3sgc&^Q?>=VrDZj6_fqY2X^1d@ zY&Pm5GOCCyAmC?ssMnv+yyC!S=e7A4cfi^(A^Z1ACuSr9v(Qf`qpV|~RGzgnxh7!A zuU#cqE+Bw2ud|e1#($9b zYFDl4OZWiolQu!nWMh97g_iA-p4fD|ZiDC43}fI|Y|bR>l(27&wBOoF2%Sq09`wCs{L z8A-Ls%flmG*EYiLJQZJ+w?mV(e$}^KW^sHiAUyG%;P8H*dnxHQm_eQl>*2~c^s>n6LLP6HSuC7ceS&mZ6*?EO>?8aW$-~>b%0Nj$c}ccP zmhkDWIGe7++$D8|Qsomi*6$hDygtKY7gh=d&Q&FNnAU4mqr3H#2ID&&QzdY`P4Yue zd7qW+XT$L@aRl}ald>Yv_ zEruTlRYa7+YQse_P|z0DMcLR`ZJJX((8~mBiRaWB5%(z1Wu`p(&Pit4VOc44{|$j7 zUHIXm5aa7A<1nK$F~y8a76{k9A0!L!1b^0?wh`pLcdiuKX1xViA)fn&2_@37Q5Qg7 z7t4B%na&rR$ven)f?u1?T02d}QwK{;tHuk8aQ)~wnqqZfsX8n3D^Z74&*`}0zJm$V z*Q^Rn+6fM@)>5XT^?;zLd6qGTf<#>;PctwMl2cwV0?XrX=$~L78W!LQ$(vWnK?ZOS zr7RLJs#x0szHLj$bjOX?{;%GF33+P7N>#_zv0u$K&~Y|SHQ+(kmDOr22+B7FDX-a) zG7$&Hx%G(tO4O$kM=-X3o4LFx;^@G;eF`SXogArup4|Vt+&z=D=~s4|60Y$N4n(S=nJRsN0G4p-mD%cK{_u(q_1yDhz%wo>#L!}1s+m$=a}t()C}TOJ~@Fvc*)2882Ra*2-gA zCuMg;p9e}Tf@1ZOr(Tv|m?;8aifN(!I zOiLU@NQ0p%U@KY8I3PBpC~LR&?S4v=0X8GaZERzChMks%<+Bxbg2u6@=kEf;*b|H9 zAv1auUy)(SYkx*s^1;2(85YCP8=`@>dIGzxNaL()+j5Dk#J3f)D0>4W-rXs^{k0;? zdI4wp&4vv(I!4>6(_&CT!L!vE`;sN*!xVIdYlrYsHUT#WHPr<~4j{MGe=IY{W)Ih! zNE4M6g5l-6A_co@m6QBec}ueAb89?u@`-YBC3So8G62b__+cEkU_Upls**3p^hbzU ziW@j^uEWiOj-}`pJM=0|?Q_`4Ys^O>Qzm1I(pw4wx^$FRtG{m|0c%B^P_z(Qi{`;f zA2UCeB4z}a7p?1LCeGaD4+k14$kT3a2kCd;i;|aw3ZEOciT&Nk-1cMc>8QubZK57v zM!L2dmVHw>6IzdI9%Y#PuBzq~+P))?kr`lu)S4>UVHmzrgDS764Jhz3!pTo|tSBUH{?E3sgGCDt$Q_>I5wsm2fL92Y-Aj_9eS zv$frlGgJP3Son6LWYaKYrjafIsbsv0UeS&$3gNAdnWf{c?+1a_iZ;QB&(hiPu*YSKcEyFdSA579vO~Z0Jd5cvtnV1UCW~{fM~y@ zqQmvHREzGL?5Cr?z_lC|$N%Y4rNC<_a;Z+IgK_*!Q5-aRZ`XxTI~pX)^50Ese7qRl zS;3IT-JiM3GZWr1K21GEs#4{tS2iq7tR8h#8Y795U~-A)La{tq=Lh&kefOaFSQ&3% zO@q_J%pt2xr8Yc+wK#@XjT<7FxqOCPJ`dYAwBN$<8U8o=xgfQT>KXkJzLL&3xyFX! z;{{p>8b(MBp6C_h=5e-Nz)%&)Vfj#>16SVE|@?om+{P&Wish> zn{3jW!dm~DfT6gp&>E4rNs454x(flcCh9^6Oogt-EAF#ScVhyE1~Co4v5MkqHt*+} z%GTwT*v^w`o3F@7I)bD%@`Wyu1=@U2)_P{`xtbYkA`O(%g<))4ul$Dj!_co%Xqbi5 zIQuR)+AAA8b4laKhajd}V5#qGvoXD@U z;u%-Ak4%`}u3&BhVQ8U|N4LgqA0OGi&&nQWA%b`~(+=|ts=6Cug*>wngs^BT!{Q{mrYfTP*FbA4;(lNX0%NmT zub(R-u55xlJ{X~JEOu*mZ#y(VIyrKOjfn1hVCmJ`B+pRo7f+jD?;WVX`|P&9?N{Xe z-D63XzwzToo;U#@a)Rrul_qfJ4Ek(ko$-)OI{;~!<4~xTZh%1~Xt!TzQUA92M$;Na zSkd_^jpU)`x!k}{gfqU{j)q+xWt;PNWDF5OXcJ&6TGLFBPJb33f3pIk)Heol6w*km z>P0PxPOgMxZp6Ymc=r|KjdRkTVaBv~;Kc3hVNHz`~Qd_wEbM2ZZA;7M_m zT42ES2ITb*%)>x3NYyT`ieDA@* z{Wfe?5l*Buvx#ZXMk-^4M*XK0MNH~9Ah$!dMy9Y(jA?NJ&jcqE3Lr$RW-HPj2Xjr9 z{~OE&_@rJYsLpX&!GD7Vx4ij(2TUqSA$0lk=0l&O=iMhE~OCjI=`LH)M=#j8jy1 zj}9T7x-NV0k#aDYg7wZqX(S-cx|`-6PPvwL#`%~t00SQVxTS_-YTp5^g+m$=^3Zna zQy9P*zk~f8tMrm*H_}#YY;bYEsxg{u#fi15)JyTzD(jGT|?!jB$<)pD`T5)v_;Z zZMRBauq>$r_0X)xHuRdb$p5H|uQV>2n8snHHxZ-8$71<-BT_y|HjggWw`EPD*)I`p zb8TKla^=#W5?-+v?Ym8W1q*fXpdRoNVUa6U3;xbF+~(gC5-U%CS<6*k76SvyGEt#} z*ZobZd5zQr0&L8sF~2)SohV7{Aen-sM}(qJfL`C=%`)SP!+dU9Sal(szkgYE@3tUs zBWJ_mgO|5is>tU=kWLseUr%TtiRzcbB zMCpc14!0CHKJ;%s@}V`i$kOlfal%h<6II!WM63{{UkN6w#JT0KR$@>4&M7A_i8B`y zS9z10s3MH2ij&AX&CA=;T_t6~DkQtHN}GrP#K!DE-Bu!Na5!3|L*8yLS-VpF*e|>I z*DA=6yXbI(>NXW{_;$lC(niMHE88BQXmRkxI|POp)M{C>wS!+i?|0Nn_udw(3}B8b zzo!#$L;_TT8>$*5OnwQJU30|+Q8=F{{YyzEu+j8pn%Chqe2{8o)z|s4@l;5*nmvZ8 zE*uX8o$^6uyMH{_w*PK z`Ekf)!hA4HHBeWk%dkv)S1hkIz}QvDJ!n*AMMD>$z)h3bSH+&V9wx9BKBaih6%>X; zW#C=Cumz{N$dgp@Oq_(s&8rDu+$?R7*ck|8e=sy4<;l129Y>L!q@h9>kO-r)pvS7; zT~U9z4F!C_>sFaFRGU;(kYfvlOy`Aq!?Y2z7zh6)q9kKc#-=|dVk1Q%C>J_c_tFKN zH|JScETQPMjM2clTVvhM3&1foGNpLPx`!@Fz0XB$rI`_l6M3Sol|*R#Qf@FUa^lp< zPea@oi$h}jt#C)kAPwj#qpI?#UIp3GG>azrNt0Y&^X@bCl!rv*3KUAf2)N&kRq9#m zw$QM?!|1axQvmUK+GVAXxx;^r)zc;rDx5TtgX0sTsHIZ~vi35FVwCUyF29)sDs>HS z@1ZC3QW4L~%RPgIW_(XOMa%Ru1HXha>}7EI_o(z0Q)nWKiq3MY*<~u!uQ8fC&S!Ls+R@$QJ(*7@tDq>8Bk@*;pOMq5CBZx^Fv!8c$SLyz-QWZu)2#?afw? z3_(VRB}v^uQL0#8^ioZB_oznt?+HXwqLYVfU_tBIFlfe7Gv#X1Cc%V(E(_2_2<>4o# zq+VoaOvK#1^jqbx;wy}N<@_-X99 z2Qn@cZn06JZv+=w1TXPN`#ekeN(Mzffig*5@DwKLBDTlvOa~8^vP@MIL3)`g<8P?= z9g!HnII*G0Vloc=O#+ldd%oAcR_fRmO_^)(0XX93!fnj0pdL%w8*hKuR9xDxUY5Vm za`vI+?)*gz;5G74+o@Z_@X+X#1m=6rHuMzrnBZ02WaTn&C2`X`AnDS$~cc zhEE{N6&+zaQ|v;YLJQ+)vX1wYw5(HONir)UKJUsMe;dLr8Sr-Pg`5%09cZ#iuP3V6 z-D;@CR??E(q6Z%6TLK*X~Te9~MjA*)$ z7&ULj@O73GVOY$nK9{OJ<^@dzB49f>N&=ND%;;=%(L;(QRr4uX9o;Pn9a=e zAVG1yMkvdf5{{F=w4e>wj=Ji;PIHQf6;>?j5x;KYgJ4OQmT#1ZFH*^ zN-`z3Tsx$k^2{V1VC^OEy|rDw2UIRk={U_{;y||ldJaL$^{Dr2@+wJFd#1Znnd}AM zi6>J@b@w*Moi6KB-jl7Xx)J7=zYEJNAxl4r>IL(gAQ*u;goMpVyIx2Lu_x&AFA9m! zK|x9W6Cjjkt&*g}s?J1fZ>`vaj!&K|i#aTQx{Spg3@}yMm8ov$$%ELDf;7zO$#<1ShA`0lu$)710Pu`;D=JYy;>v4W%gY>r>ffuwQ?=7y z5gy!<`TbRY`A33^;t9J@kfBz#^8^3fI`@OBG5MCdo}~q9(W1@w!nu90Ys8=GE(`%| zqWKK$JWroaAJ#e!&=%whC7OtgIIqo@O_juu@s^QMtTRLr#nSC(OcMb9_-$4gbki)1 z!gpWUb_m)^A}^OQoB(l`Ao z9HAypmbFtD`2xPLzwE3EdsL4~y7fvg7rAyv1wwrDG!cc8% zJX2=p=;?<_b|FisKhH**&ega?7Ly{mh6LZSf}Fq6^(e@i&DMs{FtK#iRK8>Wqt5R= z(uiQRMm4t4AFOA6ebj;%oN8Uq2mq|9ExupJ-2qRHck@B z1<})O99|Cr6?T2#gtn*t`XXW3XQEWOkpg^{+V_>N$V4|pY+rit8kb1Bg;}Gvl|1q& zDgW(Q+~%dq??iNQR?+U3*km)1rP7Z2X^HBXp4BB8+#j=*%}~YtR=-=fS8N~;&~+`u zj|hv4=xmR*IiW&U`~K3d7Z1f}8>kQv;!~23P4Da6L21yc87*q;fB)Xslm;hKZ3 zb8B-teiKu}AXg?w_lL|jgJI>Zo5zBEElVYK5tflOCn4Bt5R;#auX`aB> zGSPnYW>H&E8&yem+vL{a5HgW<22oQs!E<+_o`W=wD33CnB30lzOn92~EvGrB#(%nM zZ-l7;E>)~{+vfZFKb~>VJj1k1ddub20Q-;jqNv<66&OA4mPxmfN#aD*8lTWxrHtl| z^=y5E7M~n-eHS3;>r!)N0g4o_djwiqpNCMp#Hb}R!q0*Rk!RUNh{;SVg1@Cg)xM%8 zIl4@MoV0+)N+QOA={rT=iO=iUx%)^Qk~;_Ov80YUS6W^=C=EmHBlpFWKDQmG-k81EBlvYfF2IcsZ}#bGevM2m_7j zR^it$B+#4YPpDJ-t!ODCXIg)TTOvKlX#E{RvtQ*D-WN5s#wL}Ih(!kf%u>AYS2YZu zf2DH)Fx(XUdPn~h8ChKy^T+15Z*IR#Qq(QIxNnJ3Rgs^s7#jBn0N+(*=-%F~=0kN* zrRvsog?&8^$sT#5moKp`cxF7eZ*rlb*1cUc{Ov!KSr=+2K|w{g*2 zukAE^?PQ64r@A8=BeHD0>_^K}G>X2LM}$8Dq1r5q`CW%iD{IsmFy->RmtQcniSSrN z`@cQ1l-2pQDwsVrzCSrCG``Cn&5xhEADiza2v$5ykj5t6gLk7jl05Q2XAfqKoFpQh zTSDVoTI-f2`Qbvf;Q6NIxge{U@{=H(!>< z^h3ktf-f=&IaRwNBG4O8T;E9u*x4+M9n(rR5ZRpBJt)kJJ0k$Z*?%$-41w*tm=kM? zp5zn-j=Cr1`IdQROi-XYr+7hrF8Kqu&GtTIylnz&T4QXD!x}Kre=ac~Z&$`o+%svE7W4X6&H7luD}H>}96Nd4rCAWT zoo=Bop=%aH!qUX$*+Ts{e)X8=%xCxYL$Q!=qvewC4&d`S-bH=zkjK(__mc0c)sV?n zgy%#lB=xk#*SK{aLX6YHOL(b~k%m~F-D8gO(Bk! zOg`sNiI77)cK4DE3*UPs`W_y);Doj6J1BKW|F;j1aZp%0Z=LR0WK+~3VI<+ooi_Pv z?+}&T-$nj$SNA4zINMggM~Y#f5@XwtAkL2{PVZ2qmUi7>My=_iu-{G$SBt0EH*%#| zyVJ|@7buzy=cqqR$b%mGE7D~A=<3@F^SehCY!TB^&DSA&A7=7kQA`(1F{)f&Rdz{p zyh7^BPD+Ocx>WqUsypzZ1q1Oe-3+F zRuS)!()6c6w!O#v2k%KEuk5ezV5M!Yc+bwA|Gyac|EYo((be)GH2_j68Ef*CB9uML zk#P7B?&w;_Z&@Ld>gr?A?-bl!67eon#6bga-vbs=Xguu6n&tL*(UcYcRFsvwfCdDc zmwOLNB7(J}35z|AZ4^MJwZXM<4fp+Q!%{jTl@yRsQurbR=_46qh`y*xt?(G#nEGr9 z5a2lU@+30BB!2K@ZJ$92(Iq3z5H=J2VrI^G7HEbhzRq&NYu1Bi2^prPn%p|;wN#agxyeUS`4Al2|7vF^nXCC2 z-8Lc5y5T#kn=@Np`8QcYBAQakW~vd_abS|GjC=J{h713Wus)}Glawa(47{%)R)fz# z4@b8K;#BOPEu1v#7~HViO-Sd9=S)xGKVW^Y)k8o{_Yw!G0I z-3(VjDtX9M6>A3}vY2}2mSVVwN+rJ=MZT(R`teGOY; zsY-K72c?7#-RKVC5(1p6JG~()`F9m&GF9{2x)P*OJ@erbT^vV#<;j+sSug+z*&BKL;;FaEmIEBT$)9V%Uo>K?<`uTb4yD-@{7!B9Bd~(hnX32DPvke zESxzjLiKnd>GO5LuW~EtPTvBk&2fg+?bf0m49ljD+1n{w(%Js{bd{VPRGb!8rd)Ie zm=m3TW4d;KyQRGXH@xC}|Sy%rqF^Q1Qvb+$Ql!Gf;$SJc@One&zEy z%Hz?ZX{Mr4PF2Z?4w5S>%YydAyVJg?)rglmY|b0j1j<+RB?DK(YTu>A4SFklSX-+;VZ$l#pdomy1D&9nrNzGA__yZJNY7_c zouo}um5|?MN44Rx=u%m(`Vc7>bI4Joll0JbGy4_(Db7V6cR{%cOt^*ga{)mk8p;)e zqA(XPXCVG!`MyIviD03eEC-O5?u4s?dwp|9G&WSHtHXnJ;@0a`qm?Vml23YH@jD<_ zs+Q(C&ro2SuJ4A3)Z3{E7b~~rD99p981g{)4P7JAcF<*9Gp+%?Hy8(tsG(agHNwlv z@R*r6KH9h?O8F{&`9ZTrLW(APkoBO7>9@p-zic+q4@oMWR zm{`abUQ^M-X|FQ#+i(2L+tkaFZv@}5=2NtLdeCSm+v6Q5V}L$dj=Jytb6{hk3_1mq zg_Wfu299*(xr^aJe z&C#yQB<6R4(cxEfb$bGa zF89(@XLH9IpneOaHxF5b&&&AU`O~a(wBJT-%Kpx^`g-wSG|WAB1ik8 zJA-z1kB4Z_kcmW-^D=Uk`@_L!vf#0G;e3Ehq5Lm>Plno;pPe?cjJSps2i70W4PBp< zpC|AMYF_(AlG~5Sov@wCs24g%xutY8x*pZgmf(7(dDXNyJ3ZoHEA?AC##woPmvx7^ zO2+XLE}V$M|C+1*F6wGo5}-#?VqrQH*A523R7s=N<)DdRJ3Q1$YmcBtRPxeu=VNKF zd1(+qB)@uis-@s7-|nhU_;i~*%t^z>h2)`~-8}vAmrrk&esY`jpVk=LL5a`JSRz{}#4M5n`2Z zcyMH|IqNyUo{%CREon&71ket5m#xspM-r7w%q!_c{FM*hdnf2R*3@C^CEk(yVv zO8KNFbOH&r>9Nc5f;8+qC;xmTU*JSn+`i64`p|@qd4FlKI4)6jz*YWWg&WO_`G-#3 zSMFYA)1!!+jdMdtt=N-|?fgMrwe+_tv#B?P%%w>%D#wj&<%+eki8}^7$h}%~ZLxb= z9(zO6-M|YHu=h|)tnOp)m8esjy=&u+3KT)o&j|Gwz*&gUbr@@3#38d0sjoLOY|R`Oxt#`@h`3@?KdLM_dVgXcNG<1yUH*>!jCLzSFs!~zF# zb6O4M8(F#j(1dV_3_c`mvdPI;>_!efn1%2_PJs*#;yLmG_1D6ezoe++!Pk)!F0={! zqB>i3NGggXO3>VKN}5Ef!6Ta`X3c@p7uuUXbe@8V1^Vv?WNq(s2oMuV1knWj4_t+&$L7r-vJp6!SuA zer)1Py@DCOd#CbgqEEti7#~}uGP!={cPtX_yK^RL*_4}t?#(|~4HHzTX9%;4OA9W= zLn=3skiCThgBIuz^?FC3VHt3IPN_(JXKN4>Gk&z&+%j5_f*iOO4K zw0IU4CE-i;b!-%f&ejvev@+Y|J1oglHy`Izo$|r%Lrw$)v$I(zAwhauPF&=>B|Aa7 zTvJw4Jt|}Dy~0?eFp@PX!&cv@}NIR-p-EV;G4jLkn8DC+k%aQ zrzi}KR^g+meaRVw2}dwanO%0ib*cDAH?@Ugx&$K>bMVL<(Y=ZfshXNWhb-ZmF$jF{ z1J$)LVQ~B6-W|@22Vn-V@YkQjUnfy^o||%`UBJ#aY|Pn~RGd3rqHj0Ei-kM5B(_d~ zk}t>^=b9nP4Gsk_E}fW|~SBJm5%&geR<_gz_>qNfEkOlO~-@mQ*f z0o-j1gM^w^s6T3uH%3BbPbj&5Qb)j_!Va?E3Yw!etPu905ehrTvakpkXnZm|^b`xt z1%Lo*0wMM-Wa`c4^{d(OV$sKQ7d9!UgvxF7ndW~iD*_@#SxjuAhY4Gmga?sTb#QY8 zJg1A2glC5{t(JNw2`n#T5X>p*%B#>LW;v4lQ&cCw{mVfNgABrO&{wn<4|td1kxGiM zY7rTW;@loC_!l!6(h*IogAY_Ui0qlee_JStdEsR6#wmNNvKwWj52wK15kqZxF&RdY#Ot%w4@V_kS~6iC zipFrz#V3Q@WiE?_kYR|ja`yHk6v4*f>AH$t-Ij)HWMH3cayVcWC*ui8Y3@Wu-}a*Z zshfo*%A#R+7bo`z=QTqu3(_%U&$tgrEHqW) zZC)6Ea!|XiE05mG`urr7tDch*!1Biz*rw=b_Z0MWW)7YY#xm zY|2@;w*=+&n}0AY4ntbpS>%<$(g4QpdSjT z?pr($@T%ujElyzSoWS_#Obq;VGY=`MrTi&FKc>0BS4=^2cUcpH=q$h2F$al<2-ecPnT9nqxxQUZDDaOBzxUlFFo~{xRH&7V#m+h2s!=X3s}Uhpc#( ztNuR#;Xoe017VtPJ#)2?v6QWiwYM9W!k?{()+M|t+8>%9rRSo*NDlF?;Cn_2y-Fsb z)DR)>C^53I1x0#nfx7L_SxcXg>IuM&q=@Jgq@*JdRv1Vqc}YtS!*UfM-sXCfPlYTm zm?8EcENOwt(^Idc82Lc1{y%EyQj$ww$z@i7nv&k8M%JxaQ6$)h0Li3J8F zT=ChKdIy59iOcaNil}ZkZioM3P`xR>!?PQfq?=n6X_vW4?ow!4v^o^P&^DdCC6r(E zD3s_p+?%88W=ce*BV?Ye8{M{u0_X#umJifFLrp>A_lL^#xpTQ>rM-m5 zwsT5Es$MstzTLPR?#LuM7$T!GHV%A_BBA+_rj;8WyB2`PAulTl&{btw5Lp*S5zo_& z9JLV*s*%Nkq0(Q`NWZ-QtuNA4s-k@X(DwTMhg)G)zz@qpr+5H7gjLGxT9~%EWHx9Y zA@z%aLIX~-9tbqg0iPLU3mu!_Xuxj@MQI!WK>Mvy*|M?fRCvHn_x4S6+%G$_0#KlP zR&Cm|*JoDQY3f{k8erDt*altWOg?Xy4CDXSE=O(z>nab@f|*d)y`sW_TCh|Wy72?x z@A-$3CB+NgO~8ZzG{|mB%r2aGy#~z4U0+R%QmMd&G_Hno6KF9v{+*GV?DmeRzw0GD z=vC#lOcl59{gowKaqQ)dXpAU|)nh{|g%Qi7VglaY2yKGZ@&FzM6?Mf(gY)aelB<0a z9iI_%tJn1DnDEl2YJK=Jn2?q(msl79wX9e=gdDkTL2L64T%|LgSm@i7F2S%3MR zh*Z6r2mS5Fo$$F%f+e+m(oyBeDg%W^;ZKtm9WDeeO+Vt-;E2zvtHx)Cf0hFpl)^*v z=hXH}#A+P*l~;wDLD{33M`dml17*V(9yKDu5m$$a_0hin?z7!>@YiiS#(1ZG02a7H zksR@<+S>QC!~b0-x|g&TSA@*7d-X(LGyZSnjI8AW2;riu+@#1sU%cuUIBW2+ElcF% zkksYst<1@(Xm3mqWhNNnTn+kw3aYNcGwPfTPYu`O+XC+2+xi$EIOJ znV{^K;$$s55&>ZhuAWE!?495YUCt^YdPHm)ovi~|5T_7qSalGQZusn4PHwStOG znu2TV*V46wy4oN2vC_(@FK&aN>Yed5Iq&kak96Kc+g@2mX|N1DaYC|Hd8F0P29vuS zst`=Pls}mL?HA@}0wz`$Ym_;X8~JBt-Fdj6XmSB9l9E-GS`RxHxYvRhrCNbU@{CTd z2F9rQc@-so@@zTf%MdK3~0{LQaG7&kE~DZvj1YdTv)3>#^4>q_2Ef5jB7w zM_iq=C=s6;*F=u1{{Z0RoX7Q=ws1)3-Mr|!s7{4M?OM$h-gjX^{4_&4Y9a-@(Tz63tWj$mEg67#qDbTHrUS$IRYMA;t_A8liKHO&BNM9tB8qMv-rLa$v zxx-WS_kbQ%&k%Rjf{FAo9`k}AK@KSJk3yzk<| zaEDzPDCoQr1u`Npp6MuY3?3Ysnz{MV+Vgi?{!;rkUvz`1lfUkhBRH&f`t3&%wcBsc zNp30)$X;!iUu@AS_sa`5I8q{E~<;TR6`@>CAl*OEcL;)UJ?Ekk6|I1ynTZvPBLcBn7k=_C*;&Ckrv^7 zy(V9OQ5*r+ZokcPD&q-@Z~07AWMXY=rsG&F`6Iy7Xq64$<^CT?_n>soH;vd?5e)>8 z8yKLZz#mF!a!5eZ(WpQ?OKwwj+uuOk-+HpajHnutC>Gwx%Tn>yxR7bz#t~C3yv=9A zqBK^5^8ag{BXjevzs(2quJD|a>i^u$hf@8QZT~|rL>Gl1Mc&|ZUoTy1e(<01br?|MQ@W_6Ub z45h)uDVp1jn+pdy6dg)(ZFc+ZBjS54Rc+%_$w2_}jCjWj05{$~IIm5YybCfe1Fp}N zjEgsAZjiZL#VZ;3l^y=tFf41mwf@ z+W?75({MMrdZ~W9adoT+BbXcGPpF*%T84icXv-y8Zz50Ex1g9n6Cq*c2Q(gsEiPX48Vp%ha+5zn47kd{j5YD9dCbPmJsSTIX;48(lk( z9Oc}Z1Yptk!^Q#O6>!~t`xIXXjODWS@g8 zR!OM49k}DYnO{j<3>L+VNg9`rXW17CToW zsY}n{RK?CNo=P5(6B}Ta?)I{~zvQ%K+HxnUP&B^b$*WDbNIvQVvoL(;Gv*nS#&rb%fg zJ-9Kpe`F-29s;KL9y&B9H&uK0mv4^+em7Stpx|NgXwD&s9g@YA7`_48qR6C)4?4rI z?1A89XjhU-B4a7oVYFzizygHGvUjQH+4K6Jrc(lxS_UB_WzJ7EZP*nF0Jdd1;}U{Q zhACu%&>bJ$bG?#jMCg?R-t}8a7Gr(q8*icm045<6BusNsAf7};+Wb0X!|Oi;Za%fS z=Ox1^pwkfm@b}kW_NzFlodbz3z41ywkS(*o+o#;DrmBff6`N%SM5SIpRsE2DXP0d^ z-r^S^)HXhP(jR-;;X&q*v?BZKwZAGO0B3Erj{b7T zxtZoX{Ev5sIoTkxcd6jnb8RgsxX4(S|9)$Vv+G*0nZ*CL@Pgw%GZO%O`s4HkUqIKK z6spUCYQOT#s#Rhhfo;{DM(Q}=+Lir zpvI?L3NAU}cMrdnG1}l?t!UUpvay=%3Ie;%?E+f}IlA1Fy0BmYoNcWyUu2!gAkuAFods*;w($C|oV6T0~nu>Vr^>qkW*(wAGUBfsvt7Y_eyhe=UFFD(!q zs5-Z%-SfFDWE}yJEWUZK=PU!h^+}%tfC*W;yf~l-jHOPEK7eGIh2B2Z-Gf3l7A!!j zcU7b8cT;9S6qy%L1wW*Obs?RMA3(a+;VEZb5bGlG398^(R4T8Khb!vHudds!_RvwE z?fY3tfn*;lA{+x5<~t?ooKt(a4|ZWE9q`rBUu~-yVn5#ZAD0BtJV7(N`0NO}Sge!< z<2Du5rx2u1?XRCtKfTSXjP>->iJi!SpFf{oPZ+wV5B~f~3hm4xUq4_&UXyno)ZBgw zH^3@QyYF*8PSV}UJ@mPs-*F!H(v`HGK#^(F&MQTYGuk=rMDj87GP=jJ%z<#<|8F%M z37y)#mhUEJbcGZt@&i*z#gatZDt*yV>(W?$Tdd|Tp}k9qXHS7^PBY-Wc8|EqsF0ez z9{_1dNPxlUiU9Wlu*_O24B&P_CT)aaBJ|tv*i6crTR)8g09k809Qb%#MV5(`AiMI^ z_LB^cawWP9SvNT)W$DT13MJi7sNFph})a)#QcA!RAGmFh*BEMb`&2 zK*c_ki~+IEw_TYSe(P3Y=tO#Uq|5Nu6EKk3#j9krvb$KKlm+9~xxy0YnBa!%#TUn) ze)8Lq?}*mdW}$t6IeFK1EyA{L(s|2t}Jb2)F-I6rTYAmjtKTy*RPG2LR=9;IppR;0lii z7}dt39~X|ubXZUMDXry3sPwqq?efp<{@Xt7xgnzBR9G&x{cqKO*(R z?Kdx=K({v8DM0HB$gRQ^$$4oJ$UYl%sce;M*rxc|i)bV)~H+@ZV5}~ateRbRdF0h$x^ug+qDYMwyr_8LTj)_jySY$vHBwj!j{E%LD z7S4<HXqPz%${nysNJh9`iZTrvNdeMc7eJC9RdvkgT>nzA{ zhhHAw^VhcgZNEALso9ZE^GAa=XBq&oQ;bLP;beBP)Get6(iK3FHEg3>QGfHhgXW>rb^`zX;R~bmY_%&L=Mm+T0jtE&kNkytbu%z7N44GN^8aoY z02A}$z<5W&&1V+h={Q4)M3OAUfZ}eglBLrTFTB1jHW50u0pNN5qbyEHlv&B#KCN*N z3W}Udw^|v@c<8~Rrpvv6%KVTnP$w^*%SR6|EDE}ae1a-^7FCiLkP7xmWczTE5cM%2 zongC6K@AnUSf-RU;}!tgxX3alQv=XZR_N#e2&yCQ=EI~%+Fk#2^KjwPbNeZAwmMWS zUc%;BGhQ8wVdV+{E*n-Q11O46M#`X zk*u;605rrqs?6r|Z+Cv~M@OueERm#3!ENuz(&&hU-hrBlP>;g8#AQ}8w@;aSP-{5^ z02+l=O_zHCmH8n(y{PZr^Aw<>OO{Vi4bP%V@`5l}06?}6KOPZ^&PpXRWU+!XcNBNA z%q^+MV;8mWI z(stvcJ=SSeEeUeZo)Xub^F}}c06QfP9Z)(KBLpm@4+Aa$cr3EC`ICUp20+|Im` zfssk`83e}Wo%nmeSzLcjdVwG`t3pb`jgg2pHwINP>=Ei)3TN|~Ez;7JEs|U*_>(M+ zju^>zq$WbOFvGMnWl?g77nNDj+&*RQL9ONWe6lh)@H4Th=~6GCQa_}N4C`$zprnhy zC#b4tQ8jr%7;JHD0Z^q6)yIG+Lw@+j^qrpMDeGdXTT*MrEdbEgkpZIumJ0)peRkAgIKl1)(tDpoqeJe7-ec3i?qvElZmYUjqQV!&AapM!%_SHlG5K zF3+EPQppfWt`vMGq>hgGm8?~Zijli+mMOEMxqZsqgIdc5^@#ugnoU>m0;=kVv<5~1 zDCr{h2`ct1swFQ>4%Q8QN~y+I`+T*6bcHH?s5b`S?mHuyJ@c@yHeQY>_A5YDDfMxy zM8EReX)7;a=x(6X4gud18ai-{)a~c+OMwxELMUkLkf=5x&X#T0U%ry{VEMZCb}J1* z*9T^tSf_+yOy)btHFPKxdO%1UsYv5kC_5cbg)~YcOwE8!@wfJf-7j4b5Q!F1iYfr& zl+ET7$ofk}HM@Su#)_n%6w+pDbwmmYDQc@1{dlQodEvu5&Xg>1nHA0LQ|2DjrqFD< zWnMr5KuH&gPf)REQ5|^!_ixv zA&-%I%<7pAsF;i&U>OmM;IXMEeUlP-7_WjrSW+)aKlGVNA{$?#P&S)Sxky`NilkyG zzN*&|5;m27ep)90*++%BecJ6FRI%w6c>z`NL;5RzEb|_!>JwD#SyW%%n~yxzeyLv* zxqOaxDH>K*i~)dOUue7h?$`{1r=L`z?uttN6HB8_z2lH4SPr`FA|i7ox6CEjxOCip zTDu9kgIJyEuPat2yCG7~o^tpV>GV`Cc+5X48dssrPE|rF6^~n~$J;{56pv?MILx+~ zUn?@INO~<<2tZ1BPvLAn7_9=pYbwm`(`NUe zs!g}Z3#f`8(seJEyqkHT(0iz|Pf(#}Q48dShZ?pMA2I)JC4Fek7!YN6M1~iS$jd3W zqyeX&Blj{V1$4iegV%$i%|%h(l;|$iT0(@`n*6k_jyV z1URGPlgzfr?Wckw+8U1m<~S=Qlt=(oIUlT}_Dp-13Z6aXC$xhV$CL@`wW*xXCG1+Z zRMu(S0!3HO;-aW)0#zI9sLKn;JNY-7=L+EBtfPEYWu1Nt0kkXSN zBtN%LU9*~!z59>-zplntrKE%<21J#-fGYSQWi?yB7UAMG_TZ>3SFBaH`jY4(@d+yS zEUGK-&r{U(h$a`Rqz`q*fJo=1Mfet~%#Kv6CoRFVNTMW8{D-|{pIk!tHv`60LC=Ao zOR_+CMXYlBDN_W{)_CU~IlcT!$4M!uJ$3kJ+s>uudb-zh%aYKGn$X^*%(JJQE(s-R z(Jc|BvK#ADCOJ6#GatfYGx}jbbqwm!yb4qElRy5K)fgA!Xnp`%r?NiHmP5YsB_P_B zSUMS8a#u0W&Mz;#71;TjaaqiVVVs?)s4$yPiAeWB z)}xIK^@z~uLWc+rYvzBu4o)d8YiE)c6 zv-yDkW8*EI*v0|o9YygbPei;S^Ny5Xgox<~va!07d=_xmPD|f-|N1uMF zK8Z>?B8j(8U9+0fy*r{{bfKhKV?b2J3#fu0Ql{SFmGf(5YFVl{^3yeXljtJx2`cd{ zswJ-|*ySguta#*?g){q#p)YzGlX*>BFuh?b!!o)-X?1)0kb6#n_G*;fBr^Pz~L=W_koe zAC%s)(<+;M$yfqP9EB!MWt;%Upg8!RrLHRZnD!g!{`=?9dTSul@1{a)5@7+}cWy}L zU=yp&UqaiRi2RTO%$~peS#y4_q)U{g@KrsVD62u`CC)z08SbQ>MZdbxsjZ_w*U9B= zU&=&{g`VZl%vg<_?ON1Do5$bg>yNPiY}$_-`8B(VNspm47^47`W{4?{hZw7ZUQb#R zquBK~XDx|lXaI=3kld?hK;sxDnJV8G{)C^Fy8W#6@5S$TVm?^aZ<)fSWXZ(KAD>5x zYn*82_HTyXw;TG=Nw3-@K>Sj&NjXuyq1&87yaQ`iXzAeekBdDsO`Qd3`I_|QwCb9B zN0rEZqI!{LOINMF)(V>+WA04x%_k4q1XZt1f@TrI?YR9o^;8q9w^gAPX>)eWn{>x_ zAzx3lMRd_S-ji?>26(&lL#s@#i5rIBr- z>(m5XUq@J^jt6|8p2imntVmy1GbBwxV0U`@G`^1TxVD52*?^r3u1H^3bK$_~%cndu z4YB8s3?pl5-> zbB%qLDaJ90-F&n5$&I#u1KkZYEl1k^jdRETJxAL9jU>_pFiKODs9_~fV%ko{)ttZy zpJmi(LAp1s6l%8bPA$7KwivVXvNIpZ6Tm1$ zW*)Jg$a{`%#ufpPjJ^4zErxwEEW>)R$jAp3AzY5e2zn4N~D;a9O?2HGp1TZQj z7E#qop2W1B%+;L4eO*RL7yus_YO<_AhS3BdEsP*Ez_#bng}Slp7^#bB)(a!IeYbSi zTwj{fs0{<_U&8D2>#MpqIHzozT20p>Qt-|VWANB ze(m@Ov+F3!*rHOBu}7!vRgzqz#ZrJLcyBlW6>BKRR7g~P*_n<>k^n}9#3CyCz>}D^ zle(Idx~~%pVPuhJ1I)Ul6|Bx7a{(5z4Sg>FK2U-LFG_9Ssl?lJb*-4zowKs*Xm!RG zfi@Od#$>8q086q$M*$F2M_h{MWoJ4TktKjpo>)W`A9zZp?If<|B<|~wnyuVmNL4`A z4HpI&83#yT1o1;vUf{)}nXi=i0QIWLX}8y1S=n{8I%A8#5BeoDc>(yqFwts{Ko^wH z%g%HlO8}!nViA>m;K@zf$z9E;5~W zG%am-Hucc^io_*k9Y_rW5E=n!+rC4RUB~sJm|aJ;=oh~+&kvggL*1o8rwst!RApXv zX5+Dx62M4{1wQb^rtPGz=EUynnxsvnHcEy->LFw4WNf^lPsjvzGK0R6%2C_)oiMwO z$1iKL>!?&*+x#u}K#ogdUUoWns?5vIOrVwkMoWF*2~68@PPH<&T7T|eM>YV77oz#X zQZxmXfg-8MKD-9c@u|Mj7WYfPM)-=h@6@vEc#S#51S-TT@Gh*+*dhRL&5?`MFV^Lh z%F3M|)NUHS?92p;31GC|2i_zmP1?%gyhnw#@Ms;=nV z{}N}{@mp+MYs8|AEduhkzcUxqQiyWrH}%*Dy=-{Yec7$Nm`lrmsX&|nMhku5iA~$h z2GG@{S8$3I&4e#BoBty7A}=b-F_x#F@ElpI1fRQ7aJNhBsZO3qNycrB zPlYMH*v zQ4hs^?wtGCZ#zp*4qQcT-y7-wH5J@hsLL*Tg0>UuH590U6U)0(`d^ z0gU^aK(`o6^@ZG9slGBQqh&_9wxdW#!5w?I)3E9)tOH_JyvMhyn2L7)i&&Xznot&F zYBEe%+~sUS)xnJ!j4muXDA7mURl7L4T?~Vdq4P0RK92ao4fefa3*KB8arF;>bx>TPlK zR26+31zaB5vGzt^8^aL&s&R_;1RdwFKLf~=4mQZl0sCPu<_sk<$NeHVPROniv(y*j z&}}r2d?XC|=r(}3AB{_F>&58A{b-6CU{i0q-Zjd8H(egtrQRKDF|`PaqCNf_--PG; zfpNONsjYgyAog8pBJO0O4&8o{_azBo5OXye5o4tzgg&}H`r#qQ>Ch6qtbgq09}k^6dkq`T;Pei1vl{Vy<3GwG!WBFTz^iLq{yQ)ev1@jcsnY zV4uCL@l0ObDtr%r6Y6{PoudCtI^VI%UJeHZE#3D}5W99?k_hW1`yMh8eY?QVwGDe& zX9}#!_h`%d9$}rT75E;?{W;${i&+PSR^xl@hkcvy=DvrJS4Z@V{pioNvYEs2;(H+P zpVb_e>U(I3eK{fq7nbjP6giIcgWVif>3g*1zK2Atm+pHg`#9e+n^`OIJqn@#B;Ui7 z2r;aR7%b;-rf|oL?*Ty3$4$dkgz66f0Qjiv)7MvTR^@vXdAA_$827}1d%&nqHSaxF znPFODJ&H#;tMEO<+-;2OjxtvU>&}G=OYC;qKdKlDu>T)GH(OO>=(bFPp*UG8ad*^{ z68I+eZcOYWJ(#?Q<-zBgeYo{`_T~2{4;;MXs{h|{^-O$^b*jQje2;!eY=ENffqPm7 z_~z^p$(B5UfoY-5uJVD?rq0A}t@M4&RSdBFI%MEwI{43W0HJ6Q%P2-=HSO;%I7K52 z0H%Lk^$T;ySLK;-P&E~C1gO08;Hb!H*b1bV7$XTt!jt~@@BhBGJtjON;Ddi1@w4@c zb9-fLIH0;Ow3`_HnJ<0E+X#FyKE4agMl&yXB=X@5Kn>0AR~_oiQ% zhsc!so^J`2Ir##$2D5?7$`z1f4q)B@a4M(Jg_yq~rO6sc96?{sRfn0HFw{En_1G`Y zonMh014#G^AVnfHBmzNY2npln#K$2dBEuf72H*;tY51V3=-Mwu#C&@;(M#Pt<H)mO zU8@}83s*e}7$mD8L@kPT1Rdk$r#5rh(8I>cA=8^nV2dkfDErKZSd>r3?EPk^r=Bp? z)GRD%O&>YIk$Lg2Q#T_GFyB%}*HV_AjhYW-=v6wR&=5C9JdF#b3w~ z3&?w|d)|Sl44$u~^#X9_l#hZ=$v?BOZLiyOjm9&f%G{P#{5Cd1E7z`vL)XOsPp)+w zY6<+kt^^UXMyg)DZJTGMPZ>*e#lz?v|I!fv$sF9_g`5V$S0PSB8zDmg0y!e|!$|z= zLn{xxd!WQlHGD<1Z}tnk09O^de4xf~6DE#`$d5WxBfa0o*iiZ#Virrat=-9y)7RV5 z7XMjX69h&G}LdfG@%p}iV81Tlb$ZYe28$kX5t$`#r zN5K_iBb4(vkMX%;P>mnqJrzc1Tm!N+V}Tnk1*BBMK)>eaq&FwlIMf;?7w}EavbzPX zq3zqD5=%k)W5ED*fyfbk986!}lK}v$pCynzA%e~pUNNrCI?9+|INKa;ut~BE2LL_t zC2?tGCjFMvz!{SsJ0DXtptpaZ)<;Z#+# z+y(4*0joU=fLPFiTLXHmoCi|TSO9CEa>oK7G8c2w3XU+#AWA=5=c;&%2Zq(}eA(ez zDFE#`V9gX*?N;yIVef5yt$Vi3DIe8*msk**o&Rw8<+a}WKw$evmtTIl(0?(p0 zRf24nW%(db*chA<1#`E&=%&3V2ojwG-Z`>KHu}DC4S-#*cP~{t>ocKcT7T(dnQO&D z;ML#I{#6*TVapeq=B!tvAw@RIZ&yg=90LGWRq^^{=ckWh&2xnoo1IO*1DPC-aEAzA zg?N$3K|#=ngm1QQL!&xnt-uTO*fk=Ga`Si9h-apBq*)@Q2kUjU7y#(xPI-0wHkjsW zsB29z6ry%bA9=htPf!)tzv*DgzsegX*n_HAWKOsaN=*z3f<5V6{e)^YK;8zB=Q1wn z@=AKkqQZ5hFw5-io z?eYVdb3;R?{WwmBsVfaFv= z<04qdmn!X$1+dSXZR!qyS>HPT-2QUgBKcI3)mh*|k~k5GGhL4#Mv zf6$?5&KZETalB|Tz}CT~c6u&4Fn;#^LRE zy!6O%GBOb2zKy*D^XUw7@ps1uPjJe(F%bbIh~;S0fq8m7!@1Ve4OGVV)#u(q`9 z?lNB$o(T8`4sDy3XB|gnMy1y0$eI!;i7OB>A?%;D2jc(@h(mWx`f2vkwK zI+Yp(oyM<*BqJp+rfqpu`Cdr+Th`!sC9J=Y?FMtF86-=aL7t-xbUmnO)}9YeKp&cE zc1d%($aPk!#z4_bUM9#jj%FQXjMacjzTE_a^A1ERcKE+67vNIBX4h}QZ7wM$ZQx^g-B(UW6DU@bZ)ATcZ~?vm9KcBRoSawA#}IIv6AJ*E<0td{&t451;E4f|Bw~zB+#H1&f%3y> z$J91BcS@zyWnZmc%(9-hDO8-qL2s}DXH-8CZcZCJ8d`_dzx6eseZ`EMjctgW! zItOR6S0{R`N(XYL0c7Z)X-X8CF35HPotIdqD$sy5fdW_601euxCGxGbK!3J=0t-9s zJ=3CiwtNyM(lYzLMCRsd=O;3b8|u7qx`W-G#g2EgZTVw;lCyl>41Ku|UM*>`tm;o6 zqsig#GKC^Xn?(T7M71W)nM2>Z95{nh7THg&x8l*lF$3Y%jSg_^D)VjQZ)<+P0R>i7 z=As9#0~s-IK%NJPPDA9&Uw|1k!I#j-K+DVr0LBYPCV!xU z4vG_6bSZ0`JjbM#fkUOaXxe}m8I7BSp7^;#G8O_ogMfR$8su|uf6VM(YTrXmWSEBV z0X!nw(%OJ0^!$V!>z1#p37&A<-z08-i2!_^Xa4}>(a)r|?skEu<>9}r8K`BnHR%i@ z)?|m?E3x0W>U9;{Q|?K@=4b%Zya!2br7=M7(Z$OPX?ri{N_O~=znHU>lD;Rmo z_a?J<#lb#XJq@<1xb9U)oM#ga4oPYvLk%^(b{z6#asw9+-3%Ms{-%H_5JD0^N2{t5~5)F(FgfTx21 zym8$eFJgDAKgU>j0X8=v^TTfdeSkC#fl;q~3m8AZfC6xP1sDht0t^^{LHGkRrqM`C zA>RcnYZhGU6qLWf$H!fGx4;9iLhw|3022cw=q$M}$KP!|mA$PPVENE7&m2u{O{>Z|kQZN3JIh-v(5xe*ggL z@Ys5QfcBb1RJT4kTDKX8qQ@NP;IDTsv%H5u1Zn0y(u4wbcxj;Be1-+t(t{iaeAF39 z<^icIY0dJ2=T4LC80xAe*y|uk#Mg14=^~=7h6{{#6j2< zmIVX=fL{^I^(MRk%okvX#C6yx6nFr_0RjRLegNPD5PjVCzd#fFhC4k3>C3#4_rOG& zqM-bRKEB-REn9M>GxPsR^8f@#gFs3>Qz61eR_*%eqo*?}anUpY6ADv_U4!?L4^uXW zv;4zRYvcZy+rJEbk1>ukG^q$BWSQaLE4+8;?iSGfCL`-MvQmnNuSng^l zN1%g6M^cmkKaG#bs2AuOAjK`YU|?_oQaYy_M5GfE#!ou{H(=B@L;e7X%L;ptxHP{a z1Iz*-Jh4~3b(A?(?RN%Yo3qigmx0I&xXtep`HRqmfRaaXL?1x2Kvb3Rf=dl_t+2Ds z70`j;$d|w1(`Na|GWpLTtH4dKG`M5{d>tlbVZNnY3%zVr#coHe0*^7Tr2zcGd@*Yn zzvL;&!3AC|`yK`aYTrY6k%tHVo($fcsdnZMIO^Jk*D|rvzEEyBm4IBEaofO)GTxi6Fvdn`&QvR!uo~N}PCOJ}YUvEJjX^%9FAkzgnxoAhMo@v) zZZ9sX=%7kXBsXBuFH;0g=f z6`6S3&H?CQ>iF7{!E%v2UJIn`CZDKeT8WSqc?akYv8A24jVNcxo1$qV=natd5o$58 z0l5pH;Po1~2AJLkNUe+oZt8+$`JEyf88*qN9MEC`(28e2DEDB09#tOUg<9lD5q=}s zO%9Di{MvS?1lGN@nBFY1By*U0H0yX7`yLbKsPsf8R`{Xc$Tliiq?L&KXZL?cCYuol zD_n?|X*J?~whMQrN?_{>uB{c(<%+|R0~qA(>ZdXe530}8V^ejOX;VN)ZRiYn4->-1 zO)KwF&EF?ujK5X9I?avX0~ z1)Ze2CR!9v$z(s>7m%>>w4hung4@7K3%a&tYR;nByBx#FS_Z6eoju&4U9sZ4(&!w# zj(lG{TRd$1XYSB9W8w&DsBoRR0k>su4mt~!beBiKF8jP$Ox|m2fJ*^rB(O~K1D7@> zlr)WBL;@sWN7oyHON(2-p8+6iw?t`>S7p3i+vXup={PWM^vTKf@$ zGb93oxOgjhJtOGBEo+5PZ{#mHQu!P%SZ9wLykO#cDEgRirg;y1h+<4BPkQ?vKrZp2 z+b$g$MZOkF$?*0%?tQ;UkxqZ><0WcHjZ;bUftE3Q&70$@aqrc(me|h7T!@P<~AsLe_~W80z&^Cg{~cZ@n{Sv+a__Ce+!03uddH~A#3~qmCH>A zT)zEN4~PwIfHj!a4zKO{*f!t6$+Op6P^r1p;214u9FG>=q++CJZ$N?TEAP&Ev+onNbRjRT4s-O-aBH z@A;4dS_l9*w)TB1TOJLL*Jzn_XBZ7yrVjGy$>(sBvsm2ViJ|Wy_M!GYz*7kgIGd?a z0aMiDwl_^Vc8&r7(AGqqe{N6r>*Qet0DufFJ=j~8VC}33n@4cq6Tl${G9}@rmG=+{ zMn=Uzd#g}@oBBr1*woTPM(}=NB;pu|&sZK<+S`D^2GA|2p&dK>zn8NyD@!CV6v1>% zKA_963bM>4dy|NW4qz8zO{>jmA8aAB%GFn)^*VhjoOwJ|9CZX*e>EOJ8@mHsRqF)+ zS`0uR+>t52Q$!O@*Mp`b4&v;o`BL!@PQ+ggL8s5^uv4NnUix5qBZ-7+r>Ct_G-{uGP|tQgr9t@k>Et~G!r}bc+7#8y0kk#waABUbNYX?HV1oKt#~l#2!?!&iatG*D z`~0=Ecl*AP(-0pf1`imH4YQ9_Lq5ullrl)v!OB89)nTUPX&Ok5Se-DfD#!<^x91wnS6WfCLk zFf1&6;h|w!1On$tBE&(wE1$#d{^_%EgI62+9%Mj{5jDPtKqwp+#;eC|J#YK&AH8xp zgno{!>&T?f#vh#{T^sq6dn%ownoUU`mVTu5e$3XhKtQm=Q}5?`|L5;6x|TBUf$Le| zseKO>!APTch0)u)3h&R{f4~dYaVTTLYDmw|A+YaA4oNU4)d-+*Ci4DvzvW0B>mA{1KX3ttdAwKXpcC8ZfobCX1$c?-2C>Sgi4sD z{Eh68B@uID*8Q>bQ}w4Eew6%f?Qh4=BJl}TD}5{c?cH}9qNMyz6y2rDIl3Qc&{UFP z_*OgAn1c&s*70THdq{*QnB_f;1WGDJT*!ovnDdhND3t8GIHgkf9;Sp& z4z5Ee4rV<x&zVzQzb*2CrX$=osX?duN}C zWy$}kO!yUvq(3Ro#lTmiImn&}L-ByM;@_v`P2D54)+qqvd0Ft&Jz$oL1X<`<~`oGuoa7|rsO?=PNzi6m{_5q`V}zRIBWI+}VRo1L)e39zPYf2>V_%J>lq)<7&Y zXdnmaF^T-q1TL~3A;Yn*2-hIOZGAWG@k^)0o`_8C!8mvtflxG>^8}PX3h+eMc(QX- zemjU|QNYCTce1y_?kb(vv@y^OI@)9a>a0`|EgV_%PNBXJDylR%8v6Hb7p2=D86WsX zhBNQ`Q6g{{^1@5;5BhibpsIxBfw!-+(c8#r#R@039X8@&im(ku1)*1*2SQVt!dH#! zilB*+z4R-KOs_i1afb z>zlLjK@FRS?y2;fL^>Y=m(O@Ju7*6wvv~|JI$s0qi+Z?3KM}6$NxD`PIHNa*3*s}@ zixYJw5dBH-p-zCx*EK2hfr55Mc2mcVx6MZx3@v*f_th=&U%DsLv?p$JMx5@t$;Q2` zjxCq1Uau|~wK~|RTVF(fE;H7{F`d8p<2-XDMEmm<`gVTrOn&qC^fGTL>K#nYGr~TB+f<3U#wqK*@@c4GtE=ws{1rBN zWK2a5Ljj_MfPZh>d1Xs0OQw=@2d%Y-FGwMnx|buC8p@4q5;I+i_U>OC3nymo34EKa z<0J?M$~-N@Y$ebH3O0*VT`A3BUYZnr$r+E0YxNP9FZNb)Wwo^90K74W2WsySX)@08 zQMe)eC;eZWGMqN@i1|HQ*n#p|i2^CHzrb!5()c}|N>WPNt$Cz!y!K`&;gfS1p{x-v zE2&n0g!RBc4WdMrkzXHvGZzaDY+2)s5^W{ThD5qrd3x5b?TqMGR1| zf&{_|2W42U1>XEu4_zW4hPU`hscCwmLCKjV9cIZitf7|BTSq}*K-A~>NuR+LtX|sj z6Z}b@P`f_P284q14vkAGOP)EtX!jE@!YL?Iv-6V3p!zilI-7@h)k%i{l*mavf7&yt zoG(j(v34<~7X^ZFe=8g#7|-T4HmpEroh*N=O&;B3?%l&BOeWE-TL!*~c+lnKdVv}iq{!{qnHkn?K)BqjhhMxPmG52}t1<2TO!={%6@6-0}b4zWE*-%D-tlj&|;H95rDdX9;~0EvfZov%i|P2s@E< z?G?U3>9bAIA5R)$Wo~*8A2X|Z8>N+xj(rM?uVQinX6NrF3x#`v5lC%SzCkONMOKZz zL1jiY0Y<|4tKH0fL|0Og?!Cn(n3I_2Y`^3k1`_L$3^uDk5u?bR9fV@X2@HVSGBM#4Wmi7_a!3 z@YPW-@Pj?4ZHq(~R~YJ3^X?ue(=(6$@T5`3MzZ3oQAG+x<8t4!l#Z{7UE9bz{A-fS zYfK;Lc?-Umw8&oE{z2$d{(*|He2YZ&yKmE@+q=g_GcUp6f`JnYr;9N;HXZ(u%F#d% z30WJP(i_P$w>c!~{zei)H$Nk>w8PYY@c~ z-L=l#GO3}2`kWcwOWyZZTd>+%!EzRgImPuhs^ltS4O97NvD~2YC}BOm!#`~UmFLP3 z0J~FWqjyuAmxyQYWd1M}%gcu^nU@>_35+?ZBc@0^=rI05kUK%~-UuX8r8GDJBk`bw zAkWUTVLu*+ZXxlXQybggjrOz@<&=xU$K!BdXa0U3X}qvcao3O<>zyO?if#|ufDd4B z&4y94B5Zamtdi~q*Xw-YFW|p1qFXAYmzmFv_QkvXlctA{onC39b|H`1{ie6tS3#Md zq#dApAq{lqS)Oj-Zp250O}7v<4T<8*Y2nJ(>YPKvSq#B5gg=Z$2-l$LpKvck{ zI}{s4&k}k-H>1SF=_N3|Fq=dQ^S_mD-(f%LVP5=UzlqaH?>$xitwHJKS14g_gHWoM z{!U3bd$|{4H@Bqi7fR%2ArttJMar1Ww zi<9g^moBOL>MfF6!C>Yq1#;r7p%31IM6kW;TR}=KH~=NU=3rCGs`4vr6}Vu z>syjqjvIEoS(r<5<#&IZFv+gqVc&u#pvu&#Z&2G%GoH%M*m~06CUzOLkL8gx$xOt~ zzi@nGL`jA03*hwdY0Qog2$p9eUU7b@R;zxrIIv5q25je^*n$UA?Ly`<<>8AG_{M3- zlMW=G5!`KeQ)arIz*6M?47{Z~n2k)S^P-cmo9Hh?LjA1Fa_%$6lyEg6`bP!~yS|Uw zVye+v?8u|Z!b2Di;JEYRk~%1!_*N*Ql*je+FIex`7J)yCS+D zK1i8B5l)m&mGcashzUCXkzcMsPen`L1QCom=Fvv0;If{7(mtJtU+noDz;_6IyCtMs%7Wt)dGR5wO{GyZuH zC=zT)g+GZ3()u#mF*F^^pCPw#wu==SVdNxeHTytp{IGGm>}rp&3p>>e_^e6o`QhZ( zaov-s3YM5DI>YnIhKJl@2m&gA5LX4W1quIo-e zdtDT|Vt2iD{yi+Nlnwm3aB>Rb(>hA{wHfy7nHPYRB)J?D&pn4RepPSsSVSHqd!G)B zA@4dsZ0%B~OXrFjf5d6w&+Oa-WXqI)lh(50h-b+XKpz1#K2J9lRDIp4JVw-(MMJmv zrQ%@>z)8=P_v_Y=-*$MMGZBUL|1Nv=2kfsWd|F+6NIISgVoEA~Qu(sQ72zh?+ z{RjPztKi59p_B}OU$@3d_Go)8R{-ruB@557{JBIs-7Fy!W4;$k5Z*Tv@#C&+WxJfk zy6-zBcdd7ncCl(#E08Ac)$>Cz-~QQczWp?BdIQ+sJBW|sAZ6EhIKfz;kurppH+n2P zRnUHB3_E_E@xA0s3&6yR8nAza2T=ykzFFA{iDLe%An6ukol$Hr+ADgqK7}xpK3Lj_ z)~TnLq(``$B6?OZuw$H3@Emk7ha9lA{+fNtj@8o=Si6PJW)z0qF}OMO;4DF0voeU& zo^}9V@jOj9B*KfTMNv-htt?le^~oLJAG5Ix#SaZsq{SM47xBjyjAWjMBWSmUXNrp3 z$wl0HMoD0Z3f{RlPYO@Lz%?l3PGJJUx%3OrroKW=+ajAR{tLdC#FKX2gpY6{eAOB4 z7r%;nAzQm)taD*imuT|3v?qJ<7*s`UeDOGPH4o==#KdM*Q{gCoW+Hpq*P-^=u4>`) zIBH5vrQ+Pe!^Cby=pke`D_pXCd_zEK`MuRxVX!7SjRQfDM@d5mw{uJ?H}2!_mJUI` zY{`lWps8v6!(`Q?2hoiP^|TWVN(xhY?poPoVxCeVX?bG;oJ|SnG?EbnadQb@`A@8O zL)!;p|J;d@S>Va0vS@PvIhHmkc=2HsK>laqIrcVm zS2?2VIXE&VX;#~?cLaBz9O?&3eJyYHAZxFTj(^T3$%4-ooZ$#Uv@zFbhRdz}UaB)r zr54OKcwPkW)Wg7bn2#@n+o=)p9Xz05y=e9`#qbQR%RQ1`I*1xOlh zH}ypHx~9di3hCt>hku5eIdIIVcgi7NpDBr=!1IHh0xHfE(ms6#mK7vs3!Z_g8oGW= ztPo<_E+no012uTQS!bLm*oCl}Pi(^wxQ93Ta~mQn=`K_`#akEJ!s!svwyP;p_8}E} zBTThj==#!Vr*tuorAD?|8?Fj_C-;tTb!yr3=nGVbl{b{#^`}PnKe{@K_uj1m}UCJe>Xzz*=7!t571Dt3aAH-2IF1lofT`K>Qzj_eBV>N`Ty|`4}m%>Od?ydBu zmpyh035B8lThe&8mFO-qt@}YB);(ai-b^tpC zj|O7gA|7tPd9Zp<$;C9KN9BCdb@^Jiq{_JJ;W{K9C5s9BCu}2ID|wR+huW^wsoQ<0 zjG2&Ty}+`?MlRXH z7%nT&?~7H{SS_Daj{W3J^W>Nyl#L=*I6Hn@+JONJSSx#*?q4OV;mc$%trJWy7(X7K z_ji0yJ>ibbn5Tm*6*nE_YdcQ&h!QLCQCuDiazSVgQ0L*dHUG-$ z2{zrqjW#vdYFP7#04J1!qpW+K(k;vJJCT`k-dTu~5i(`~^YYqaKpLqHG~n&y9)eHa zfNSKUZta=Ii&P!1ihX;9G7JL1=uXel*Ye?Vv0lt$XL$b3peUN8*Y14t25~ZgV7I>S zjm@9r=cz%f?K+#{379=C7L%~IKWZHuK0{XwqWoy_-sQl^9n`5};=OSR#rhl0J8>k$ zF0(TBP!*Ew-6BV)Nv()wkTg9!f?aLZkl|}LEyq+p64*mh>)S~D3g3OQzf(YOn-a!>cnQ9XHxShbG=kXA4LP8vGfkopY| z_^Z5jiA35zfrTApra!j@9VuV9R#u#9umMdYf7zOpXP|p%l~O}VBmSyx9-KFc$`=-_ z!8aJQMa}$1LWX>e5`{$T&FZWR9sEN0;3)IK0nI0`f2#Z0a{Ai<8(FGrM1P&Af6~Jc zYun8R39uQ$l=U6p2Dj$FPNAv<2-kYI532eNzQ=d8ku@QVM**DCMR2upMMLRevaHR= z;S9+iTM{6%KTc+Ho)zjQ(qY3HIX_0X5dOkzNFD~SzFAQtFkY;CqhAN*B`5ZB^fUO* z7GcounmK3^IeXQRoiw3`l_ZaerJshR8zoPGUCO=XA2`~nXca!Am>3>jIl9`4DR z_wdX8Wrlts?e{hBE2%?a7E~WV5TZc17s_DV8i^KK5~vHSpeu;>Qk82#pxg;1SBgX(8N{=lykHTo)uL<&7r;O9T9t?Rv68oF7m;6T|Mg z+wnZ+iN5a{^>SR$+Z@u#B`OPuR8LSTmW-6QJsM%H_ zn)mO&dU$;RwVTeTVI`T&wZSf zy(D+4$}7C|HDnY`8+mVB>sN@%ZY0U!DbuiYy7px~bXjJCDLOO_Q;UvyFYaj?GV9S#sLFZ+t^HF6eTsm4vx$6AEb$ zW~LVaP$MlkX9I|7&Sv`FZp3LlN}9h}{YCU6C7P3mk)A;WxH~~<` z=Az@1PWBfITh@kn0&u6&Q}$*k`qAes*CSHeG64G6{)v>5wB9LP15B&F@`Nq`2n!oc zsc4s?PZ)Jp>@S|lRD6f3RgHzBjAuL^N58dSEy22qY1rhsqlhq?fm)xmk#6_R9h|HTba1lW)H|$ zWc_XzlQSUU6_VO}ffo1x0D#0YxRV@}@wyIi6@0d8@4xK*3!A?c<_>1CA_UL%a(Rc$ z&^3KW?;!av;Y|tk8v&QLtq10_;l`X#1cepA83k3tNi*Kh0M>chYrwq>;4joQkD5Y^ z-KW=7c!lHZ86u?hvxshj$f8w4JkQz#8_@pltgW9U;8=-4hz8t-h`c=e$P5GgW6@EmAF zCsB=N5l*^qkbbjMx_5&FFhJhk;G|@}CsUASBCU-$ntTPzpz^<-Fv6b}6u}8wHn9c| z&<^Yho_kO4D*aMM2ldo0PPn_i@LTm+K8(T|Nm$hU^D~(G+X!vmE05*Ucbp5~%WJ~m zW#g}5;n6tcdV0B<*FJazoWPp0Fd1b%;xi@h{@w!ia|H9EB|a~XTuK?g%j~gj|1WKO zeqGvM0P>+@e^HHtLC9gRNqb)}ND@k2`;G16h$%EqQ`+wBavEPT>lwXLU>H{Cfe_#C3$EGxlQUlrXq`0YW9PmImPe7Ck*{ zZj)1x1(T)VU~Im@y@&vHv4H9_FqrMS&8A-vugLPo-c2L~kEm@Mu>EsS|25qKJYFEx z@4{(BAF_2KR_9cpgLU%CX-7h{q`?e7F!VkNAmt9=%7l#!72~(Jwvb zWh+Exe*0EVI84Ja|41J^6zqKCxv17q+~oE}twkO^g)CsVFXrK8rU<35d2|e#;cO+0 zZC89U)>t=K41#2XVPXPUQj0QTkLox`8MlAYR~RFuH$W*;3c`wFYLa*92f#Z8+7)Ud zYH+G)$TlrYx}=h)D0YJ>9DV2hRMsfrj0J@I(HDTX*Jl*hbEhUjLQ**m*^fY)jbK_Y z^R6u)M15YRKl8paQQPlx%-e)~P4bn^{#>$)c|Rd`7gZ$G_#+s2#-fX~({KzpS8DuQ z)`BWj@osn&j24@D0nGM+3~Ia)6gGD}=qXS}q3ks|rh59%wR;{2_-JrP=w;WmhK$h? zbwWnJ_qZWXD8C_Vxi^*hI^1z+jZGZV_wc#}Bf!~N{*%Fx7x^{FEsO^4sqeNDkvzPT z%$)aL`ip+GO%WN=!235HN|% zgTNtzuqpoUQB_iR=N=>j_FR(X94*AVWqjL1VTaz))$9)f{C?Jn0jN>w`HqmT-FeVn zBg861O-3Wu896BGf}QWHZLmesAx%VSp;M5rPyH+C4A^@dQZc{^SG;e4Px?FLG6%*E|uYQy;VI37yC z4I_{1_h%=ax>t?ob@H|*m8ymVUTd=cZt|;SU)7&?xXP#6mxNDSg#8G=7*C~yws zqV_^h5wdHyMynLa#vd#qwCeqi89z*Iih9CN5k2fre6!4mAumYmn? zzS}!TdNA6z1TCERdb|T$1bRigB_(Mve<4ZU*0h_p#!tP^^-|&*mfn9e=h^%t|SA7(Lvy1 z7(NP!o2ohSwf6|Wh=$LCqtIBsER-VQr_*_(p=XzB)AGs--pnv0OSN~U=?)AM&I-&L zLkcqpPN!esB;|E59rD)bxYxlPr@HjE$by+(KZ6mFabrc%PR*}wL4UsM$S2^d>le29 zE??MQ-B;O%qycPJ`+TZor#y@q4k}DhmSlM-5_B~4TH-0~Wqg|QWI#pU6Hb+O%gL;K zA&Jpi9W3n8f^@4G6{C||f{!gF3U)Fu+RtInI+8qT$t<&O30QeoGApzX>g#Fz{LGn8 zX(W1`Qra)HlS$_~t!=5hB;mko%mMU$)GEHL$1FPrt?cP-km}Zo(x^@O?xT;_^R6LP z1U3$-gu#WWci?v)`|J_Ugr=5%pW7uI-g3G%XkGN@PE3rV8^#?(7H$Ytf;s#$wolb| zJxDRMUVj#2-(OeDSaWK|!Mc$RIi!?>48JJRI8;C3LL#}4)aP)VXS&&%Lvra3#W@Z* zrZP=r&GowCk#NM~_RrC{8i!_q=}jjkG=c-fEm-gnMC1!5%E=)BqGB>Q%~N-S|ICE= z76J@kILx-8{KxaJ1;j;t<`S-VU2z#f5hE9`s^K}S! zojb$~;8@8uF1G}+zXny8qNZHRJW-g&2grAF*n^^f?Th{3`@PL6P&T-8<0#W(1R4DO zYwuPO&^=imTIoCY!~ItntN1*wc2|yJIVeb8w=-MJ1IV)h&!Cp)QH%@J@I_@iW8C`j zLH<@kWWc30n}kvZ`|~?$>;4wiViZXZs}%(s{Wj+arQ#-`klx3J<~Qfl44*ABzQR^Z z6!2tM5%0^=Q$P^!i$&Ub4)XN3UxVEW2D|5BUXjvbapb?1q0BUb)*f`p;)1iLj=K^& z>(yUv29qnson#rWRgYsHgLh%Y;C3c+@*aK6G={nwX^YIizX2u6BeUhUY>FBCHVe{58@iiP;2K5Lim0Pm7thEw4W~} zP?wAd!LoS{WbgSMhmC?895Nyqvq<<(2TE9RyATi)W%cT80Fq%~Mg|m@fjFnYrH`26 zx%lW58X1(}_@mg>tSUIbCK9g7xICh7D1K(!4)OiiJ6YT1(+0H`mt$yiW5vaxJlx4T z%unAQs1CZXcPt+l!GT20PJ*b(v)#v2r&eEuHn{;jSIyCz9UzE}Cg6GdR7QL+tUPA` zK_;BSn2$VW#uYX!O|tScO;m78fGn{eY1xAn3ba+V6NU*f01$2)VWroar!Gi=r{c)G zZ)Yl}8l||$GscpVM+s_MAUrQZ54RA8fl&8@1Q^@wIsuf3gAJ*OvaB(HP+r~ib<2_M z#|HI-=h^jlaqmo|Sa-ne? zB&O`>Z3YI9)9Wa|b_I6;>DA1`u=!xKfB@6$q(bC5%(fV1pYv9GBjj$SHt!YBy?6Ps z-1Y;7mS)mSAWy#U)V1Pvx{yx;XrZpHM(L>3DyK0TkMG_5xu$VUN}R5&Vn&^7WAm zzk*$o#}mikuX`38?ZYy6Nr5hDKmkv_Nf}lZx8GaxcdBXEZ%6>|@`pC2(}Y}y2)f;$ ze}=3s7j#)t@np@?y3#w2IK;^_xv{@awDLiaddUu8^+6x1OCE|#1h00v(Hl_~Xn!kL z4w0tx*Zvb@j*EMg^E$q5Xfyr2wH`=jc@5^_f;-Pf502kH`;I<2x%8b;my%z_%wK*I z#u~q^R(7f4AuQ#a*tQ3>y#BVZ7-B1-=^vo!J?TkDw0j6Y>I&_glr-MqQCq(*G7@q zYc$?!pA3i(`SYzI2{%G~o|CJQzlRC_n;92y;}lJoG&zL!v&${(mDrnRBdpv%e(Pa} zYuP;_2$>dmX}3vWTTAL0*SrP-prRXiW0y(Sj7aOnk+mM4=l@3eCRsnj4bzY zuWQQ0*lzE4+^_t!Dki<4{DTy(=+`ZT8-?Z%wJjzC&YCF;WM?gVR7?4Im!gHdC@K&t zj`&oRPAC&O(GK;1sG77dw7Y)7fY31z8vM3zmd?7T9xL64I`cx0l)NL{R2nl7eboki zP8bbRWoZKx3@@(UBPCMAx9I8STo_GS9uP0vLGo-r;TIXeQ;^gn)khx?Uw;x>y-6sE z1)bv9E#!T}n#^%ad=y?hKjN2;nM?8vW-8yG*nz3i*okM8oZ)1g(1$IvD2Zis+(n2q z7zG9uVE0b0n(`nZIxZswM7I`dD6G;N1HpUa&w`oM>rBghHJ^F5lMy-AGTEug6p`za zuX{2Y7JQO0DnAfYz;0h`w=7ivB*R?K)OX|8l5IK`+`Kq$ zq9o;TB)E(Nse53BpMMRfEA&Tdike~Q2a#%QQD7%NT*R~k$pQk@(PHJB*4NxlfZjFX zvnj7lu1)h85z-#*c3X3(CM6Xc@iF){+^$Q{#CGx5ykIEWm|Q~U^@#C>AAESBr~>@Q zu>GI?_B}~GyQ^NBtG>JvKZA#VOV-B~t4zYKRH=MIX6amoLidYn9WWY&Ksh_1vW3{5 z%|OAF^LLR<_Rrr6M1+M~Bp+*Oo;^&hH$eaIKQDgl-(H@v$mV&?hI5R}LU*7&MFL}G zEk>uiHn!<%3-w;#{Zn;-sHP-wDnQijt)zu+k3$Zs9uo8^f<^J9qK})@XglgUg1K4# zPFt3*F<5{QRQGs+?X2e}^(&1Ef`D}a7n_AwF(jH@cUGJQ-ynEVRo`{>+Udy=3*Toh zMKJKRa;{AaDQ&{IqJxNfNh=MunMoO07Pb%c_p_vr0O;;{XxA;YK%nX|Q3Hwqs?Bbc z@U>2FmQOB=DPP7-TrJWd!Es+`xb)v6oeq$*27yrV#j(fI7YPAzU*4}Bb~Zu7OuOrr z%2ygXrV(Q(kFU=hMnF^=QIg>curPcWa|M+zh+ZJ61{QqpQwq`%f&PRZiw*Q0c$H>8+mlT<7+Rcm0S< zmiiO$-pjvxK8>R|M&&L|yz5JRJgJe_SvXL&E-YR7Ubk)s^rDvFhxi6rFL`Y&1~O5g z&7X6shVXIZyQl_zmI!>d7k?i9FJ|fguuXa7XqZ_tRW_#c2OmzEqU{iri|y#PH7yKt zs~Ly^A{lNp92fNLgr|q-z1Dv8Y6@x!%bp2~l#A8WqaRMJTeg`OnxCEtrkM?43-6X# zQ_<@DwPuT)=o-1l^`kze1bOYJ+8NocHZ`0^D;qhB3iCzO2o}`XOzm~JE;gia!hRAF zoeJHH`c@{Jcm&KZb9o*edpu<){(5i9xhbcQFPFufto1F6<}H$v)>(v{SZ2neaHQFq zJ>->BPRA!cigdKRsnG!|@o1F_k!6!5l zXy~{R?svu>Rn7kpB;*hU8Yg^x?Pp@w&{-WT(svS-5}W4|W5?n~dkV_8`6lyJL(*lU z=?@2A5qXkwoLzGQsb9jJwz`tdjLk}>Pc99ej=m`=)}!A~FR|TNbnecU}VAfc$sZ8JZRy;r+um2nwa`(4<6@?sXkCJ1ORheOrohgB>+{- zR3Mc)ys+qBx($;@W3|W1>k|GGF*F>4=CL2N+P0V&N0Dcu)Y=!tmIsRC8ulB3pXj>q z7Vu;sFalGqD(nyN5C7Px_tvFB$4p5>l@N-7)t+Ay07(I1yU?H=2q6j-+L~d>b^2jZ z-F`Ru7X%<}%V`JBme>}Nynw?&WPjw$eLwl&51f`}lpH7O9=P%m-aYCu5DRIz;U5zx zBRI_MLTt8VN*pu1Z8OFLjPQjBs1Mk4-5@Y?p|3BNCG}olEl{H;q5z~YJd$f{V0ZOa z(2ohohXqWdEUoaNF{h@SUQi+MjYT&W7Ke=>GyXs+sCs1Y*-_(D{iT%#;}|YJpp|=o zF^7cDb7~toq@WlU?oi!+KCbfn4gj|$650{PB07}J;^Ur~^%ALX(Zyyoun=v7hc0VQ zi(aEc>1S<&Hw1ASoi~f9<*MCfn3yYEQCA~&_#Bj9UM5wBs6Jx+MfXe?_nnrYcuptG zvzgvj>ZH-6Ys~3IQaBC0;df2*1%ijJ-pZdNEN28GhzfatMUBES`{)i-Sc||-En%|w z#$~3zA!Nq}d^Ia2&6=K2BuYbwi(V)%eOI7?=^&G$&l)Gk0>la|A@`6$jC&sc(ALDDIl!_PQ>#{@{aT><1P<@+O!A@W;u@_QkZO%?}B&jnIO34THp%;`&2 z?ND#vud%JlO#`kiJFR-5=Y6Y;vtV~}`D*de8+)s61vtbp30Q%%vqaAbvi9RfV-pO( zSqG0oYRF)OjwnCs9xW+2)VggsGe()FK@sA*&%$s+rf$YfZ@7y+11QuBYOy0RB0+A->pN$Ug3O!DB~Y%9-EYQfMuG9CRUd zhF;~}{zndD9GxX*h}?OpzXci)ue%#da>Yn;8Mth5;5RbWS?XO+DYgcnc(BS)p0fAr z#xB^sQO_?{KC>gnu~%&d0LF+1&ZjNSe1el`MjaL)TRN$k`(6Ajp08tItFhn+mJckH z+qL(`!0$QM^fpPyeB>?K3rF(G2ErW&)zm;JEj4o>4|Pf9Tuq>2Ke+YsPM#HBRv-zs zyFCVGx8xS&hLU4Mnxc4Xsvehf2d{GBetD4C__IiFuvTf%%zi6iZEOt-CQ2CAL0m)Q zRWFO2s}4Y_m&wA-2}ncVT@vMm=DWA_7#r#?VfCHE~6ZfoWBz zfx^NA07{INc-pqH5;nb0XL8-KW8W2xopKnc#o9#tf?Fx`g_zd^V!Y6ZC5}7f%Fej> zNAlkjlG#^cRYzO_;r3|8CCO!+Fw(11f_l=D0IsetTSguL##K4f3fMpF{jysgNf{Qr zJ1H6Tc!kB-P#e7L3YM}boW0P@L9=R(lYJnxC_)js+mZ%~;%4Dd1k|CFwQLJNjx_LV zHXBD!fS4^wtl-x##`vU%FiqAjd$dk4{wYEL6J$rXqnmVgx2_m})Kg!Stov1KpyvuF z4#3P^RKZkqYu_kKbI=aCBYMiHIaq%h`A}`c5G6W$VnqU)--fNK{|r;Po<0=iKI;=2 z+%Ww`Nb^eU8=A_u5H%a8E*$*TT!~PxXHKd=bG#Fft_%p4fanQJhY|j{;7t=p?txDa zyPf_-ta3J3H7zM|zZS5g%L$1q84#!>GFv4>RJs8jufCwEE zN7wHQpu94_qIjNWl};w5-aZLHl^`j)Xmq*Om2?(jQc(N;EU;6?6}dOpFX6=8C!*(D zMj$yU@rSXP3jXJcIF9ZM2#`CRH5U>%9<+7a`x&^5|Fb7TS~)`ylF67M1hA_7BO;g{#>jU^XW1jv5TKJa|B$RL-@|$%aPTaz3aD~y-k48E^aqc~ ze#J&)vP3nNc=?)tJic#NxyyHX2?Msg+{GnmJAk)t9JXr1Pu(wlXC1=UHdlTLfbFF0 zh-^zKx+`^nfG7VZr#XQty4IYG=UfHJD~b?wQiuE*CSp#s6m~T%OUa{@mHlT*a?JDA z--`QKE_Oae7KeDZYb3~#!06=>-hjw{FLp9TvPA@O)4NS(Xs1()7@<{PjT&#Lmks3c zAfng6zQYU5Uo#t?jnvSQQUij;s_Iw{0Jn!K^SNOP!A@7!I{f8djvf|>n>n>>YL2Uw zM&lD8>mU5eB6ZZg%q?V4Ur51ZLNj|PjDtwj?FQW6qS%+GC4|TmjfXHSUBO|7C_e15 z2ZW$0xm>dP3>+(^m>yPge=2{^#Faf;%!~|w>|rsDMNTK?;8V@B+Yugv6uj^3zYS*_ z#63_AMG-_3GKMcK=rG)CVK`){uCSRO?>m8lb6u13;6VeteyVh?f0OJNbJr*^C5IP( zF+ezmUEw!Wzr)vP;oDtSFX^R9u|)@73{P3$)+y?mb^*9ojKKV<9K~QH(lNv^nb^N3 zy^~%#n|=>0fb;>!gU5RaEyMt>Ty+vi@0lds_1Sj6$2-2S>a$*Mq|TBLTuN+r0%(@w zJCg8}p^V>1J{m67;A-Y!`;?&O+eEZI1J|x~Dfs#}etAF+VH<*R%PFqq$U$p-66E|r z7vxaZ-7UV3zs-A_0PuVPn%_My= zFW3yHs3pDc@5$Oz$a_NzCkpo|=fBFi|07G$tz_K8SMS}n=`PddWgXt3a1X-pp}lx^ zYqj0h;l^elG~9=P-1CtQx;#5+%q;Pma-7|MjqBu;xsFcIcW;mP+)Zi3U-;poF8BTTWelA7hzyL}Vm#c~tt3!S~n0@h|hOrGpV% zG4K!NO33rG0#f)&2!GQ=GsXNu3l~U0mzCQk@qD&p3HgzkeVam6j4d4?Osbqm6|Umf z1Fq#9rAJ>}p7&jT4I0?GT>UO>0BJl;jGCiWRh1KXE$2;41si}Q2s`JCRkAPQ0vj}o zX<8wApL4x|cwx60IC>1vYamAVlPf{`nkGRyQ=?z~$R`;$7jwB}UI0kp$n&xIQXz{4K$R*T5mxx|te7xxoHQ zi{_VhJw>xXZ)X*CeD^K)U_U7`7pwXr>)D0zW}hNzk9H`MHb^j!O@vND`Yu(?-O=bA zMRrTdQ`a=!%P;Bnxw7V`HTLQgl89fKs)Gp})OOg-Fs8zIg+FRW+1L{?IdOKpr{~No zaWru14EvSXK}VF&37P);Mqqzu2oQp!&oQGX;&g&4g;I3Z=X|vJKNVfQ{!QKjIs1_{ zs%{r@f&@6G)0#`Dmsghge!F1mfzK$V=aa?@v4+le;Rso@lxW1qg`I-g73uChxDNYW z^T3ZGzHDyq$J#W`%JNt^`)TQ&fciESsPaw#x`5<`E&MDagHUoZ&BUlRES);FsV8D^ zmMTs2((it=1dGgL%@A+d)TGsB<1`?i$PaSTHXz;5m@MZ(p>R{S~l zLhO$91YYdnNCW`m2AEJ98iWQ(4kYon!-S=g_ie!eY?e6${zi`(>B)uRGWGm+1{|)1 zdDwD|z9p&!*gy`vM6VyJXm4CP|0xuv+b$%~W?gEsdu?;ObuNA~IQH%MI)01S$A{C4xcAe=DV&3| zDQblqrGpWk53=N!TKkkr{6u*VG7D)fG>-T++3iSWe}}g>G^uajdf&_cdGmiXwg35% z7vv*3N1Nq8Mp>VWb)W9$2R2z6m1FiFPnd%E>G>B#7JGA?_#+4U(hw!)mh zuPS*@+!gDS5&0=EA1e;`0!@B@WNJIjSs5Iy&gKg2UUhASynxCBXQj(&3qx!+7pZ-} zgem)7cul`oIlklly0Be`C9M4%C8iR~BGDkpS1*eTz<`*NQ6rugxkkY72!{rq--&$| zw3+oYxlpG4i=*65!eA^Eu1}^cVF~GXquu*xXk1WeZPs|S(iBe|Ed?RK^6WJ>=TMYXq0k(4}*>jRCckj2x9TwM!htC-uc2Z}bFhgyp- zJ&`o0om37(PdCy4X-hCs7Ul#?h#haQGkwc01;(jD+g0j4ic8)zc_gzjwVr-0{P(J$ znka`JsC$IoI*)V3N>=(f>5_gf`YEPeu=IOcwSan+hHsNpmQ4@xSboqJ-`atd{EY!Y zAE{1YB^9PS{>di{f9iyV97&r(V6s$cRL{>qdmbYQyD8kgh&w>Cbh|5a{-Sb_67&6W zLylGcqWm^2m3=zBW;W|EY8p~t4)yXtI7vj!gU3c_mTYj2+rKjAHD`7@Jsn8E{{t&% B{|f*B diff --git a/apps/website/public/shots/ssh-connection.webp b/apps/website/public/shots/ssh-connection.webp new file mode 100644 index 0000000000000000000000000000000000000000..74097ac85d1208ac6defa25c7caebf4c74a401ae GIT binary patch literal 121536 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�l-ypo3!6 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 LbXN&5SsVB9Fb$u z_rj9zl1Pqfp3-cR2m{RfUdm6+LElT%1X{@HFHgN+h{s{kg|hMP5;sfYzso>$ z4r8^a^S6A20cQ#333c9N-uLna%53=eDE!b8QL^NFK@5Be^F*A<=Xtl{dxemM7JaYK zlJ7+dA9^t7>2II-oBMDn{;1|3eDuEmDplLFAc`pIJnJ8-Y7&c+p3Yg3^j-j28&LFVmc9ZZD^9sV(4rw z5aLtCBxajBHK)HnVfCSFSAtpJOUbo%E527ad;UEN&HG+T{-Fim%K>7P=Za8L6GUXi z_fiNi1PerH$@ju5={=TCfA{L29(eu}m*de~HDQJ10`bIKT+Nn&r+MX*4$5&v4`KgxCaKANOGX)_fx&i#V>GpGLJ?nKWMhjg5xRm6y#Vga42KIbB46Z2bOMWQvEofKa_>bd1j{i9RO5SCXvvXh5gGVPS}>{T7WJ97w)9iHiU`o#Z8;Rm(zkBT8B^vfG(gV9j~@v&%uv7l=0V;;`e6YZZJ>nVxqB0PF#; z^S5I?xPfc57P^p9YjT~P05bqU_LSSQ7U4(Q14tF%GSSwgjgX~(TYx+;LwJj)@}Dh= zT0aRbO~7b0VLSjdK|gtSaZ5js7652LK25I^_u)o20T@rchu(3US^YQ218C_=^L<3q z37P;H-vPjQ=p*lyeUC3&&;{{n3(|I#@q(K~;RhWLJtR!LSHX>EUR9I%CjiEqx6xY2 z-8udGCb$&bQ#CiBj12%VT5y>ja!~~U5PK$kW;YX`Io8vtkksWEoWLl>;TgBmKO1*v7Bjva94O)UTzbg(O1ric2zfv?T~#V&;jTs zZ_45-D1TlZP4pRE Date: Thu, 15 Oct 2026 22:53:51 -0700 Subject: [PATCH 31/35] test(e2e): port forwards get a real second machine Move spec 26 onto the Testcontainers sshd so B and D are finally apart. That let each leg prove what one-host fixtures could only gesture at: a -L/-D row's far end opens at the DESTINATION (darkhttpd in the container serves the marker), a -R row's at the client, a blank -R bind answers a loopback dial and refuses the container's interface address under GatewayPorts clientspecified, and a wide -L bind is reached BY the destination container dialing this machine's interface address. Fixture gains GatewayPorts clientspecified, exec passthrough, and curl/iproute2/darkhttpd in the image; the spec self-checks the marker server before any leg depends on it. --- e2e/AGENTS.md | 19 +- e2e/fixtures/ssh-container/Dockerfile | 2 +- e2e/fixtures/sshd-container.ts | 7 + e2e/tests/26-ssh-forwarding.spec.ts | 280 +++++++++++++++++++++----- 4 files changed, 248 insertions(+), 60 deletions(-) diff --git a/e2e/AGENTS.md b/e2e/AGENTS.md index 3226aa57d..ac2c3e84c 100644 --- a/e2e/AGENTS.md +++ b/e2e/AGENTS.md @@ -133,13 +133,18 @@ asserts recipient-filtered reads. ## SSH container coverage -`23-connect-flow.spec.ts` requires a running Docker-compatible daemon. -Testcontainers builds `fixtures/ssh-container/Dockerfile`, publishes sshd on a -random port, and owns container cleanup. Missing Docker fails setup rather -than silently skipping coverage. The container creates disposable developer -and operator accounts for real password login; no host accounts or SSH files -are changed. Host keys are generated under the test's temporary directory and -trusted explicitly by the connecting process. +`23-connect-flow.spec.ts` and `26-ssh-forwarding.spec.ts` require a running +Docker-compatible daemon. Testcontainers builds +`fixtures/ssh-container/Dockerfile`, publishes sshd on a random port, and owns +container cleanup. Missing Docker fails setup rather than silently skipping +coverage. The container creates disposable developer and operator accounts for +real password login; no host accounts or SSH files are changed. Host keys are +generated under the test's temporary directory and trusted explicitly by the +connecting process. The image also carries `curl`, `iproute2` and `darkhttpd`, +and its sshd runs `GatewayPorts clientspecified`: spec 26 reads each port +forward from the side that owns it, dials this machine's interface address +from inside the destination to prove a wide `-L` bind escaped loopback, and +shows a blank `-R` bind refusing the same dial that the wide one answers. The browser exercises key login, password prompts and retry, terminal I/O, view-only sharing, and saving named SSH presets for distinct usernames on one host. Password checks diff --git a/e2e/fixtures/ssh-container/Dockerfile b/e2e/fixtures/ssh-container/Dockerfile index 45085d1d0..7e1957da2 100644 --- a/e2e/fixtures/ssh-container/Dockerfile +++ b/e2e/fixtures/ssh-container/Dockerfile @@ -1,4 +1,4 @@ FROM alpine:3.22.2 -RUN apk add --no-cache openssh-server bash && mkdir -p /run/sshd && adduser -D -s /bin/bash developer && adduser -D -s /bin/bash operator +RUN apk add --no-cache openssh-server bash curl iproute2 darkhttpd && mkdir -p /run/sshd && adduser -D -s /bin/bash developer && adduser -D -s /bin/bash operator EXPOSE 22 CMD ["/usr/sbin/sshd", "-D", "-e", "-f", "/etc/ssh/sshd_config"] diff --git a/e2e/fixtures/sshd-container.ts b/e2e/fixtures/sshd-container.ts index 1eccccde6..ce390591b 100644 --- a/e2e/fixtures/sshd-container.ts +++ b/e2e/fixtures/sshd-container.ts @@ -32,6 +32,11 @@ export async function startContainerSshFixture(root: string) { "PasswordAuthentication yes", "KbdInteractiveAuthentication no", "PubkeyAuthentication yes", + // The destination's own bind policy (spec 2026-10-15 §6): a remote + // forward that names an address may bind it; a blank bind still + // lands on loopback. `no` would flatten both to loopback and hide + // the distinction spec 26 exists to prove. + "GatewayPorts clientspecified", "UsePAM no", "StrictModes yes", "LogLevel VERBOSE", @@ -82,6 +87,8 @@ export async function startContainerSshFixture(root: string) { user, host, port, + /** Run a command INSIDE the destination (spec 26 reads its own forwarded ports from here). */ + exec: (cmd: string[]) => container.exec(cmd), password, hostKeyLine, trustedKeyPath, diff --git a/e2e/tests/26-ssh-forwarding.spec.ts b/e2e/tests/26-ssh-forwarding.spec.ts index a354b9e71..99b2b1e93 100644 --- a/e2e/tests/26-ssh-forwarding.spec.ts +++ b/e2e/tests/26-ssh-forwarding.spec.ts @@ -1,10 +1,11 @@ import { spawn, spawnSync } from "node:child_process"; import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs"; import { createServer } from "node:http"; -import { tmpdir } from "node:os"; +import { networkInterfaces, tmpdir } from "node:os"; import path from "node:path"; import { type APIRequestContext, expect, type PlaywrightWorkerArgs, test } from "@playwright/test"; -import { missingSshBins, SSH_FIXTURE_ALIAS, type SshFixture, startSshFixture } from "../fixtures/sshd"; +import { missingSshBins, SSH_FIXTURE_ALIAS } from "../fixtures/sshd"; +import { startContainerSshFixture } from "../fixtures/sshd-container"; import { PORTS } from "../ports"; import { shortTmuxBase } from "../stack"; @@ -17,28 +18,40 @@ import { shortTmuxBase } from "../stack"; * the policy block, ssh really binds the approved listens and traffic really * flows, and that a row nobody approved binds nothing. * - * The three kinds each get a leg on ONE launch: + * The destination is the TESTCONTAINERS sshd: B is this worker's own machine + * (or job container), D is a sibling container. Where a row's TARGET lives + * follows ssh's own semantics, and with two machines those ends finally + * differ: a `-L`/`-D` connection's far end opens at the DESTINATION, so the + * marker those two read comes from a tiny `darkhttpd` the spec starts inside + * the container; a `-R` connection's far end opens at the CLIENT, so that + * row's target is this spec's own in-process marker server. Each listener is + * read from the side that owns it: * - `-L`: curl the loopback port on the connecting side and read the marker - * the target server answers with (if the clear survived, or the block - * never matched, the port stays unbound and curl fails), + * the destination's httpd answers with (if the clear survived, or the + * block never matched, the port stays unbound and curl fails), * - `-D`: `curl --socks5-hostname` THROUGH the SOCKS listener fetches the - * same marker (a proxy that never bound refuses the connect), - * - `-R`: curl the listen port sshd opened on the destination side; the - * bytes come back through the tunnel to the target server, proving the - * remote forward was accepted and bound (the fixture sshd keeps the - * OpenSSH default `GatewayPorts no`, so its bind is loopback, which is - * exactly what a null `bindHost` means to ride - spec §6). + * same marker from the same httpd (a proxy that never bound refuses the + * connect), + * - `-R`: sshd opened this listener ON THE CONTAINER, so the read runs + * inside it (a `container.exec` curl) and the bytes come back through the + * tunnel from this spec's marker server. The fixture sshd runs + * `GatewayPorts clientspecified`, and a row that names no bind address + * still lands on loopback (spec §6) - a dial to the container's own + * interface address must draw a refusal on that port. * - * Then the wide-bind gate as a door, not a copy: the same kind of row naming - * `0.0.0.0` is refused (403 `SSH_FORWARD_BIND_DENIED`) before the node's - * setting exists, and after the owner flips it the launch is accepted, the - * rendered file spells the wide bind, and the port answers. The audit row's - * bounded forwards summary is checked beside the bytes it describes. + * Then the wide-bind gate as a door, not a copy: wide rows are refused + * (403 `SSH_FORWARD_BIND_DENIED`) before the node's setting exists, and after + * the owner flips it the launch is accepted, the rendered file spells both + * wide binds, and each escape is read from the far side. The wide `-L` is + * dialed FROM THE CONTAINER at this machine's interface address - another + * machine reaching a port that loopback rules would have kept from it - and + * the wide `-R` is dialed inside the container at ITS interface address, + * which a loopback bind cannot answer. The audit row's bounded forwards + * summary is checked beside the bytes it describes. * - * B/D are the same host here (the fixture sshd runs unprivileged beside the - * backend), which the legs tolerate: they assert a LISTEN exists that only - * the approved forward could have created, and that fact does not depend on - * two machines. The binary gate is spec 22's: no ssh binaries, skip LOUDLY. + * Like spec 23, the container legs need a Docker daemon: with none, beforeAll + * fails rather than quietly skipping. The binary gate is spec 22's: no ssh + * binaries, skip LOUDLY. */ const ROOT = path.join(import.meta.dirname, "..", ".."); @@ -51,7 +64,25 @@ const SPAWN_TIMEOUT = 60_000; /** The marker the target server answers with; every leg expects exactly this. */ const MARKER = "FORWARD-MARKER-OK\n"; -const missing = missingSshBins(); +/** + * Ports sshd binds INSIDE the destination container for the `-R` legs. They + * are not probed for freedom like the B-side ports are: the container's + * network namespace is freshly started for this spec and holds nothing but + * sshd and these forwards, so a clash would mean the fixture itself broke. + */ +const DEST_BLANK_R_PORT = 18801; +const DEST_WIDE_R_PORT = 18802; + +/** + * The container's own marker server, started below with `darkhttpd`. `-L` + * and `-D` rows open their far end ON THE DESTINATION, so this is the target + * they point at (loopback inside the container). + */ +const DEST_MARKER_PORT = 8080; + +// The client-side binaries only: the destination now runs in the container, +// so this host's own sshd is no longer something this spec needs to find. +const missing = missingSshBins().filter((bin) => bin !== "sshd"); test.skip( missing.length > 0 || spawnSync("curl", ["--version"], { encoding: "utf8" }).status !== 0, `spec 26 needs real ssh binaries${missing.length > 0 ? ` (missing: ${missing.join(", ")})` : ""} and curl for the SOCKS leg`, @@ -110,6 +141,43 @@ async function socksFetch(proxyPort: number, url: string): Promise l.includes(`:${String(port)}`)) + .join(" | ") || "none" + }`, + ); + return parts.join("\n"); +} + interface ForwardInstance { child: ReturnType; dir: string; @@ -216,16 +284,26 @@ test.describe("ssh port forwards (spec 2026-10-15)", () => { const leaks: string[] = []; const specTeardown: (() => Promise | void)[] = []; - let fixture: SshFixture | undefined; + let fixture: Awaited> | undefined; let fixtureRoot: string | undefined; let agentPid: number | undefined; let adminRef: APIRequestContext | undefined; let targetPort = 0; + /** The container's own non-loopback address, read from inside it once at boot. */ + let destAddress = ""; const livePaneIds: string[] = []; + /** Curl run INSIDE the destination container: the reader for every port sshd bound there. */ + async function destFetch(url: string): Promise { + const fx = fixture; + if (!fx) throw new Error("beforeAll did not run"); + const res = await fx.exec(["sh", "-c", `curl -s --max-time 10 "${url}"`]); + return res.exitCode === 0 ? res.output : null; + } + test.beforeAll(async ({ playwright }) => { fixtureRoot = mkdtempSync(path.join(tmpdir(), "subshell-e2e-fwdtier-")); - fixture = await startSshFixture(fixtureRoot); + fixture = await startContainerSshFixture(fixtureRoot); const agentOut = spawnSync("ssh-agent", ["-s"], { encoding: "utf8" }); if (agentOut.status !== 0) throw new Error(`ssh-agent failed: ${agentOut.stderr}`); @@ -242,15 +320,41 @@ test.describe("ssh port forwards (spec 2026-10-15)", () => { }); if (add.status !== 0) throw new Error(`ssh-add failed: ${add.stderr}`); + // The address the container itself answers on: a dial there cannot reach a + // loopback bind, which is what makes it the wide-bind proof's client end. + const addrProbe = await fixture.exec(["ip", "-4", "-o", "addr", "show", "dev", "eth0"]); + destAddress = addrProbe.output.match(/inet (\d+\.\d+\.\d+\.\d+)\//)?.[1] ?? ""; + if (destAddress === "") throw new Error(`container eth0 address unparseable: ${addrProbe.output}`); + + // The far end `-L`/`-D` open at: darkhttpd inside the container serving + // the same marker this spec's own server answers with. It runs because + // the forwarded connection's target host is resolved THERE, not on the + // connecting side - with B and D finally apart, that distinction is part + // of what the spec proves. + const httpd = await fixture.exec([ + "sh", + "-c", + `mkdir -p /var/www && printf '${MARKER.trim()}\\n' > /var/www/index.html && nohup darkhttpd /var/www --port ${String(DEST_MARKER_PORT)} >/dev/null 2>&1 &`, + ]); + if (httpd.exitCode !== 0) throw new Error(`container marker httpd failed to start: ${httpd.output}`); + // Prove it answers BEFORE any leg depends on it: a silent background + // daemon failure would otherwise surface as a forward that "never + // answered" and send the next reader hunting for a bug in ssh. + await pollUntil( + "the container's own marker httpd never answered", + async () => (await destFetch(`http://127.0.0.1:${String(DEST_MARKER_PORT)}/`)) === MARKER, + 15_000, + ); + await startForwardBackend(fixture.sshConfigHome, agentEnv.SSH_AUTH_SOCK); adminRef = await freshCtx(playwright); const signUp = await adminRef.post("/api/auth/sign-up/email", { data: ADMIN }); expect(signUp.ok(), await signUp.text()).toBe(true); const enable = await adminRef.put("/api/nodes/local/ssh-enabled", { data: { on: true } }); expect(enable.ok(), await enable.text()).toBe(true); - // The marker server every leg reads its bytes back from. It listens on - // its own port; a leg's listen port proves the FORWARD bound a listener, - // because only the tunnel connects that listener to this server. + // The marker server the `-R` rows point at: a `-R` connection's far end + // opens on the CLIENT side, so sshd's forwarded connection lands here. + // (The `-L`/`-D` rows point at the container's own httpd instead.) targetPort = await freePort(); const target = createServer((_req, res) => { res.setHeader("content-type", "text/plain"); @@ -314,16 +418,27 @@ test.describe("ssh port forwards (spec 2026-10-15)", () => { const localListen = await freePort(); const socksListen = await freePort(); - const remoteListen = await freePort(); const launch = await admin.post("/api/ssh/launch", { data: { node: "local", destination: SSH_FIXTURE_ALIAS, name: "e2e-fwd-three-kinds", forwards: [ - { kind: "local", listenPort: localListen, bindHost: null, targetHost: "127.0.0.1", targetPort }, + { + kind: "local", + listenPort: localListen, + bindHost: null, + targetHost: "127.0.0.1", + targetPort: DEST_MARKER_PORT, + }, { kind: "dynamic", listenPort: socksListen, bindHost: null, targetHost: null, targetPort: null }, - { kind: "remote", listenPort: remoteListen, bindHost: null, targetHost: "127.0.0.1", targetPort }, + { + kind: "remote", + listenPort: DEST_BLANK_R_PORT, + bindHost: null, + targetHost: "127.0.0.1", + targetPort, + }, ], }, }); @@ -350,9 +465,9 @@ test.describe("ssh port forwards (spec 2026-10-15)", () => { const config = readFileSync(configPath, "utf8"); expect(config).toContain("Tunnel no"); expect(config).not.toContain("ClearAllForwardings"); - expect(config).toContain(`LocalForward 127.0.0.1:${String(localListen)} 127.0.0.1:${String(targetPort)}`); + expect(config).toContain(`LocalForward 127.0.0.1:${String(localListen)} 127.0.0.1:${String(DEST_MARKER_PORT)}`); expect(config).toContain(`DynamicForward 127.0.0.1:${String(socksListen)}`); - expect(config).toContain(`RemoteForward ${String(remoteListen)} 127.0.0.1:${String(targetPort)}`); + expect(config).toContain(`RemoteForward ${String(DEST_BLANK_R_PORT)} 127.0.0.1:${String(targetPort)}`); expect(config.indexOf("Host *")).toBeLessThan(config.indexOf("# User-declared port forwards")); expect(config).toContain("# User-declared port forwards, approved at launch (spec 2026-10-15)."); @@ -366,21 +481,33 @@ test.describe("ssh port forwards (spec 2026-10-15)", () => { ); // -L: the connecting side's listener answers with the target's bytes. - await pollUntil( - () => `-L forward on 127.0.0.1:${String(localListen)} never answered`, - async () => (await fetchText(`http://127.0.0.1:${String(localListen)}/`)) === MARKER, - ); - // -D: through the SOCKS listener, remote-resolved like any SOCKS client. + try { + await pollUntil( + () => `-L forward on 127.0.0.1:${String(localListen)} never answered`, + async () => (await fetchText(`http://127.0.0.1:${String(localListen)}/`)) === MARKER, + ); + } catch (err) { + throw new Error(`${String(err)}\n${forwardDiagnostics(inst, subshellId, localListen)}`); + } + // -D: through the SOCKS listener; the chosen target resolves where a + // SOCKS-over-ssh proxy resolves it - on the destination - so it names + // the container's httpd. await pollUntil( () => `-D SOCKS listener on 127.0.0.1:${String(socksListen)} never proxied`, - async () => (await socksFetch(socksListen, `http://127.0.0.1:${String(targetPort)}/`)) === MARKER, + async () => (await socksFetch(socksListen, `http://127.0.0.1:${String(DEST_MARKER_PORT)}/`)) === MARKER, ); - // -R: sshd's own listener on the destination side; the bytes come back - // through the tunnel to the target server. + // -R: sshd's listener ON THE CONTAINER; the bytes come back through the + // tunnel to this spec's marker server. Read from inside D, because D owns + // the port and this spec is no longer on D's loopback. await pollUntil( - () => `-R forward on the destination never answered on ${String(remoteListen)}`, - async () => (await fetchText(`http://127.0.0.1:${String(remoteListen)}/`)) === MARKER, + () => `-R forward on the destination never answered on ${String(DEST_BLANK_R_PORT)}`, + async () => (await destFetch(`http://127.0.0.1:${String(DEST_BLANK_R_PORT)}/`)) === MARKER, ); + // The blank bind rides the destination's default, and under + // `GatewayPorts clientspecified` that default is loopback: the SAME port + // refuses a dial to the container's interface address. One machine + // answers, the other address does not - the bind really is narrow. + expect(await destFetch(`http://${destAddress}:${String(DEST_BLANK_R_PORT)}/`)).toBeNull(); // The audit row names what the pane opened, bounded and secret-free: // kind + canonical listen per row, target for the two kinds that have one. @@ -392,9 +519,9 @@ test.describe("ssh port forwards (spec 2026-10-15)", () => { expect(launchRow?.metadata).toMatchObject({ nodeId: "local", forwards: [ - { kind: "local", listen: `127.0.0.1:${String(localListen)}`, target: `127.0.0.1:${String(targetPort)}` }, + { kind: "local", listen: `127.0.0.1:${String(localListen)}`, target: `127.0.0.1:${String(DEST_MARKER_PORT)}` }, { kind: "dynamic", listen: `127.0.0.1:${String(socksListen)}` }, - { kind: "remote", listen: String(remoteListen), target: `127.0.0.1:${String(targetPort)}` }, + { kind: "remote", listen: String(DEST_BLANK_R_PORT), target: `127.0.0.1:${String(targetPort)}` }, ], }); }); @@ -402,16 +529,25 @@ test.describe("ssh port forwards (spec 2026-10-15)", () => { test("a wide bind is refused before the switch and bound after it", async () => { test.setTimeout(240_000); const admin = adminRef; - if (!admin) throw new Error("beforeAll did not run"); + if (!admin || !fixture) throw new Error("beforeAll did not run"); + const fx = fixture; const inst = instance; if (!inst) throw new Error("no backend instance"); const wideListen = await freePort(); - const rows = [{ kind: "local", listenPort: wideListen, bindHost: "0.0.0.0", targetHost: "127.0.0.1", targetPort }]; + const deniedRows = [ + { + kind: "local", + listenPort: wideListen, + bindHost: "0.0.0.0", + targetHost: "127.0.0.1", + targetPort: DEST_MARKER_PORT, + }, + ]; // Before: the refusal is pre-launch and names itself; nothing was dialed. const denied = await admin.post("/api/ssh/launch", { - data: { node: "local", destination: SSH_FIXTURE_ALIAS, name: "e2e-fwd-wide-denied", forwards: rows }, + data: { node: "local", destination: SSH_FIXTURE_ALIAS, name: "e2e-fwd-wide-denied", forwards: deniedRows }, }); expect(denied.status()).toBe(403); expect(((await denied.json()) as { code: string }).code).toBe("SSH_FORWARD_BIND_DENIED"); @@ -421,10 +557,24 @@ test.describe("ssh port forwards (spec 2026-10-15)", () => { expect(flipped.ok(), await flipped.text()).toBe(true); expect(((await flipped.json()) as { sshWideForwards: boolean }).sshWideForwards).toBe(true); - // After: the same row launches, the file spells the wide bind, and the - // listener answers. + // After: the same row launches, plus the remote twin, so the wide escape + // gets proved on BOTH machines the feature touches. const launch = await admin.post("/api/ssh/launch", { - data: { node: "local", destination: SSH_FIXTURE_ALIAS, name: "e2e-fwd-wide-accepted", forwards: rows }, + data: { + node: "local", + destination: SSH_FIXTURE_ALIAS, + name: "e2e-fwd-wide-accepted", + forwards: [ + ...deniedRows, + { + kind: "remote", + listenPort: DEST_WIDE_R_PORT, + bindHost: "0.0.0.0", + targetHost: "127.0.0.1", + targetPort, + }, + ], + }, }); expect(launch.status(), await launch.text()).toBe(201); const subshellId = ((await launch.json()) as { subshell: { id: string } }).subshell.id; @@ -432,12 +582,38 @@ test.describe("ssh port forwards (spec 2026-10-15)", () => { const configPath = path.join(inst.dataDir, "ssh", subshellId, "config"); await pollUntil(`the wide-bind config never appeared at ${configPath}`, () => existsSync(configPath)); - expect(readFileSync(configPath, "utf8")).toContain( - `LocalForward 0.0.0.0:${String(wideListen)} 127.0.0.1:${String(targetPort)}`, + const config = readFileSync(configPath, "utf8"); + expect(config).toContain(`LocalForward 0.0.0.0:${String(wideListen)} 127.0.0.1:${String(DEST_MARKER_PORT)}`); + expect(config).toContain(`RemoteForward 0.0.0.0:${String(DEST_WIDE_R_PORT)} 127.0.0.1:${String(targetPort)}`); + + // This is the second ssh session against the container; the forwards bind + // only once its shell is up. + await pollUntil( + () => `the second sshd session never reached its shell for ${subshellId}`, + () => (readFileSync(fx.logPath, "utf8").match(/Starting session: shell/g) ?? []).length >= 2, + READY_TIMEOUT, ); + + // The B side still answers its own loopback curl (unchanged from before + // the switch - the wide bind is an ADDITION, not a replacement). await pollUntil( - () => `wide forward on ${String(wideListen)} never answered`, + () => `wide forward on ${String(wideListen)} never answered locally`, async () => (await fetchText(`http://127.0.0.1:${String(wideListen)}/`)) === MARKER, ); + // THE proof: the destination container - another machine - dials this + // machine's interface address and the wide-bound listener answers with + // the marker. Pre-switch, this port bound 127.0.0.1 only and the same + // dial could only ever be refused. + await pollUntil( + () => `the destination never reached the wide -L port on ${connectingMachineAddress()}:${String(wideListen)}`, + async () => (await destFetch(`http://${connectingMachineAddress()}:${String(wideListen)}/`)) === MARKER, + ); + // And the wide -R escape on the destination's own side: the same kind of + // dial that refused the blank bind now answers, at the container's + // interface address. + await pollUntil( + () => `the wide -R port never answered at the container's interface address`, + async () => (await destFetch(`http://${destAddress}:${String(DEST_WIDE_R_PORT)}/`)) === MARKER, + ); }); }); From 4eb9e1280454885dc492cf1168c4026281e43e40 Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 23:22:35 -0700 Subject: [PATCH 32/35] fix(web): pickers explain short, inside the field's own width Two ends of the same unreadability, from the SSH launch report: - The combobox popup had a min-w anchor rule and no max, so one long muted reason stretched the dropdown past the field and the dialog. Both bounds now sit on the anchor width; rows truncate inside it. - A machine's row says "SSH not enabled" in gold when its own SSH switch is the sole refusal, instead of a server paragraph cut to "An adm...". Multi-cause refusals keep the full sentences (naming only the switch when something else also blocks would mislead), and the whole story still reads under the field once a row is chosen. --- .../connect/__tests__/connect-panel.test.tsx | 18 ++++--- .../src/components/connect/connect-panel.tsx | 51 +++++++++++-------- .../components/connect/ssh-machine-step.tsx | 21 ++++---- .../server/web/src/components/ui/combobox.tsx | 22 +++++++- apps/server/web/src/lib/__tests__/ssh.test.ts | 45 +++++++++++++++- apps/server/web/src/lib/ssh.ts | 18 +++++++ 6 files changed, 134 insertions(+), 41 deletions(-) diff --git a/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx b/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx index 017341b70..84c47629d 100644 --- a/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx +++ b/apps/server/web/src/components/connect/__tests__/connect-panel.test.tsx @@ -243,18 +243,20 @@ describe("machine disclosure (contract 1)", () => { await settle(); const studio = await screen.findByRole("option", { name: /studio/ }); expect(studio.getAttribute("data-disabled")).toBe(""); - // The row carries the remedy, not just the cause (the changeset's - // promise; the server's gateCause sentences are the pattern). - expect(studio.textContent).toContain( - "SSH is off on this machine. Its owner can switch it on from this machine's settings.", - ); + // The row is short and gold, not a paragraph: "SSH not enabled" reads in + // one line of a capped popup (ruling 2026-10-16). The full remedy + // sentence arrives beside the field and in the launch refusal. + expect(studio.textContent).toContain("SSH not enabled"); + expect(studio.textContent).not.toContain("Its owner can switch it on"); + // Gold: the switch-not-on tone, not a mistake in what the reader typed. + expect(studio.querySelector('[class*="text-warning"]')).not.toBeNull(); expect(pathname()).toBe("/"); } finally { restore(); } }); - it("names the admin remedy on a gated-off local row, kind-aware like the server's gate", async () => { + it("shortens a gated-off local row's reason the same way, one label for either switch", async () => { const localOff = node({ id: "loc", name: "this host", kind: "local", sshEnabled: false }); const { restore } = await renderPanel({ nodes: [HOST_A, localOff] }); try { @@ -262,7 +264,9 @@ describe("machine disclosure (contract 1)", () => { await settle(); const row = await screen.findByRole("option", { name: /this host/ }); expect(row.getAttribute("data-disabled")).toBe(""); - expect(row.textContent).toContain("SSH is off here. An admin can switch it on from this machine's settings."); + // Short in the row regardless of whose switch it is: the admin-vs-owner + // pair lives in the field guidance and the refusal, not the one-liner. + expect(row.textContent).toContain("SSH not enabled"); } finally { restore(); } diff --git a/apps/server/web/src/components/connect/connect-panel.tsx b/apps/server/web/src/components/connect/connect-panel.tsx index dfe4f8644..5df4187d0 100644 --- a/apps/server/web/src/components/connect/connect-panel.tsx +++ b/apps/server/web/src/components/connect/connect-panel.tsx @@ -41,7 +41,7 @@ import { isDesktop } from "@/lib/desktop"; import { fieldErrorToned, makeForm, useSubmitDisabled } from "@/lib/form"; import { nodeOptionLabel } from "@/lib/node-label"; import { REQUIREMENT_CAPTION_CLASS } from "@/lib/requirement-tone"; -import { sshCanEnableOnStart } from "@/lib/ssh"; +import { sshCanEnableOnStart, sshMachineRowReason } from "@/lib/ssh"; import { sshForwardRowsToWire } from "@/lib/ssh-forwards"; import { sshDestinationProblem } from "@/lib/ssh-input-validation"; import { launchWithLocalKey } from "@/lib/ssh-local-key"; @@ -174,17 +174,24 @@ export function ConnectPanel({ // several doors with no preference, or no door at all (spec §7, decision 2). const machineRequired = nodes !== null && !ledgerPending && nodeId === ""; - // The machine disclosure keeps gated-off rows visible (decision 3): grey, - // with the reason carrying the remedy, not just the cause - the sentence - // pair mirrors the server's own gate (ssh-launch.service.ts's `gateCause`): - // the control-plane host's switch is an admin's, every other machine's is - // its owner's. - const machineOptions: ComboboxOption[] = targets.map((n) => ({ - value: n.id, - label: nodeOptionLabel(n), - disabled: !machineCanConnect(n.id), - reason: machineBlocker(n.id) ?? undefined, - })); + // The machine disclosure keeps gated-off rows visible (decision 3), grey + // with their reason: the row stays short (`sshMachineRowReason` - a full + // remedy sentence is unreadable in a picker line), and the sentence pair + // that names WHO can fix it - the server's own gate words, + // ssh-launch.service.ts's `gateCause` - reads beside the field once the row + // is chosen or the launch refuses. + const machineRow = (id: string) => machines?.find((machine) => machine.node.id === id); + const machineOptions: ComboboxOption[] = targets.map((n) => { + const machine = machineRow(n.id); + const row = machine === undefined ? undefined : sshMachineRowReason(machine); + return { + value: n.id, + label: nodeOptionLabel(n), + disabled: !machineCanConnect(n.id), + reason: row?.reason, + reasonWarning: row?.warning, + }; + }); // Aliases are the MACHINE's config, asked only while a gate-ON machine is // picked (spec §7: discovery runs where it can answer). @@ -215,15 +222,17 @@ export function ConnectPanel({ { value: "connecting", label: "Keys on the connecting machine" }, ...(nodes ?? []) .filter((n) => n.id !== nodeId || n.id === keyHome) - .map((n) => ({ - value: n.id, - label: n.name, - disabled: n.id === nodeId || !machineCanConnect(n.id), - reason: - n.id === nodeId - ? "Use keys on this machine, or choose a different machine." - : (machineBlocker(n.id) ?? undefined), - })), + .map((n) => { + const machine = machineRow(n.id); + const row = machine === undefined ? undefined : sshMachineRowReason(machine); + return { + value: n.id, + label: n.name, + disabled: n.id === nodeId || !machineCanConnect(n.id), + reason: n.id === nodeId ? "Use keys on this machine, or choose a different machine." : row?.reason, + reasonWarning: n.id !== nodeId && row?.warning === true, + }; + }), ]; const keySourceProblem = sshKeySourceProblem(draft); const enableKeysOnStart = diff --git a/apps/server/web/src/components/connect/ssh-machine-step.tsx b/apps/server/web/src/components/connect/ssh-machine-step.tsx index ee3489259..8f2e6fc1a 100644 --- a/apps/server/web/src/components/connect/ssh-machine-step.tsx +++ b/apps/server/web/src/components/connect/ssh-machine-step.tsx @@ -4,7 +4,7 @@ import { LoaderCircle } from "lucide-react"; import type { ReactNode } from "react"; import { SearchableSelect } from "@/components/ui/combobox"; import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"; -import type { SshMachineReadiness } from "@/lib/ssh"; +import { type SshMachineReadiness, sshMachineRowReason } from "@/lib/ssh"; /** Lists visible machines even when they need preparation; launch and configuration are distinct rights. */ export function SshMachineStep({ @@ -42,15 +42,16 @@ export function SshMachineStep({ placeholder="Choose a node" options={machines .filter((m) => m.node.id !== exclude || m.node.id === value) - .map((m) => ({ - value: m.node.id, - label: m.node.name, - disabled: m.node.id === exclude, - reason: - m.node.id === exclude - ? "Choose a different machine to supply your keys." - : m.blockers.map((b) => b.message).join(" "), - }))} + .map((m) => { + const row = sshMachineRowReason(m); + return { + value: m.node.id, + label: m.node.name, + disabled: m.node.id === exclude, + reason: m.node.id === exclude ? "Choose a different machine to supply your keys." : row?.reason, + reasonWarning: m.node.id !== exclude && row?.warning === true, + }; + })} onValueChange={(id) => id && onChange(id)} /> {description &&

    {description}

    } diff --git a/apps/server/web/src/components/ui/combobox.tsx b/apps/server/web/src/components/ui/combobox.tsx index 25eeba458..3ae3957c7 100644 --- a/apps/server/web/src/components/ui/combobox.tsx +++ b/apps/server/web/src/components/ui/combobox.tsx @@ -11,6 +11,12 @@ export interface ComboboxOption { disabled?: boolean; /** Muted trailing copy on the row (a disabled row's refusal, or a detail line) */ reason?: string; + /** + * Render `reason` in gold instead of muted: a switch-not-on restriction at + * this decision point (an SSH-gated machine's row says "SSH not enabled", + * short and gold) rather than something wrong the reader did. + */ + reasonWarning?: boolean; /** Node shown before the label (aria-hidden — the accessible name stays the label) */ icon?: ReactNode; /** @@ -192,7 +198,13 @@ export function SearchableSelect({ {(option: ComboboxOption, index: number) => { @@ -225,7 +237,13 @@ export function SearchableSelect({ )} {option.label} {option.reason ? ( - + {option.reason} ) : null} diff --git a/apps/server/web/src/lib/__tests__/ssh.test.ts b/apps/server/web/src/lib/__tests__/ssh.test.ts index a00d31f16..02676a075 100644 --- a/apps/server/web/src/lib/__tests__/ssh.test.ts +++ b/apps/server/web/src/lib/__tests__/ssh.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "bun:test"; import { SSH_MAX_SELECTED_FINGERPRINTS } from "@internal/subshell-protocol"; -import { sshSelectionError } from "@/lib/ssh"; +import { type SshMachineReadiness, sshMachineRowReason, sshSelectionError } from "@/lib/ssh"; /** * The client-side half of the grant selection rule (spec 2026-10-08 §5.4, @@ -26,3 +26,46 @@ describe("sshSelectionError (the over-cap hard error)", () => { expect(error).toContain(String(SSH_MAX_SELECTED_FINGERPRINTS)); }); }); + +/** + * The one-line explanation an unlaunchable machine wears in the launch + * pickers (ruling 2026-10-16): gold and short when the machine's own SSH + * switch is the sole refusal, the server's full remedy sentences otherwise + * (naming only the switch when something ELSE also blocks would mislead). + */ +describe("sshMachineRowReason", () => { + const machine = (overrides: { + sshEnabled: boolean; + blockers: { code: string; message: string }[]; + }): SshMachineReadiness => + ({ + node: { id: "n1", name: "box", sshEnabled: overrides.sshEnabled }, + canConnect: overrides.blockers.length === 0, + canConfigure: true, + blockers: overrides.blockers, + }) as unknown as SshMachineReadiness; + + const gateOff = { code: "SSH_GATE_OFF", message: "Outgoing SSH is disabled on this node. Ask its owner." }; + + it("says nothing for a machine with no blockers", () => { + expect(sshMachineRowReason(machine({ sshEnabled: true, blockers: [] }))).toBeUndefined(); + }); + + it("shortens to gold when the switch is the sole refusal", () => { + expect(sshMachineRowReason(machine({ sshEnabled: false, blockers: [gateOff] }))).toEqual({ + reason: "SSH not enabled", + warning: true, + }); + }); + + it("keeps the server's sentences when something else blocks too", () => { + const also = { code: "NODE_OFFLINE", message: "Start Subshell on this machine to bring it online." }; + const row = sshMachineRowReason(machine({ sshEnabled: false, blockers: [gateOff, also] })); + expect(row).toEqual({ reason: `${gateOff.message} ${also.message}`, warning: false }); + }); + + it("keeps the sentence when SSH_GATE_OFF is not the machine's switch (the server gate, lockdown)", () => { + const row = sshMachineRowReason(machine({ sshEnabled: true, blockers: [gateOff] })); + expect(row).toEqual({ reason: gateOff.message, warning: false }); + }); +}); diff --git a/apps/server/web/src/lib/ssh.ts b/apps/server/web/src/lib/ssh.ts index 18a835af0..42339488a 100644 --- a/apps/server/web/src/lib/ssh.ts +++ b/apps/server/web/src/lib/ssh.ts @@ -217,6 +217,24 @@ export interface SshMachineReadiness { blockers: { code: string; message: string }[]; } +/** + * The explanation a machine row carries in the launch pickers. When the + * machine's OWN SSH switch is the SOLE refusal, the row says it in three gold + * words: the server's full sentence is a paragraph in a one-line row and + * truncates mid-word (report 2026-10-16). The sole-cause discipline mirrors + * `sshCanEnableOnStart`: with anything else also blocking, naming only the + * switch would mislead, so the row keeps the server's joined sentences, which + * name the remedies. The whole story reads below the field once a row is + * chosen. + */ +export function sshMachineRowReason(machine: SshMachineReadiness): { reason: string; warning: boolean } | undefined { + const only = machine.blockers.length === 1 ? machine.blockers[0] : undefined; + if (only !== undefined && only.code === "SSH_GATE_OFF" && !machine.node.sshEnabled) + return { reason: "SSH not enabled", warning: true }; + if (machine.blockers.length === 0) return undefined; + return { reason: machine.blockers.map((b) => b.message).join(" "), warning: false }; +} + /** Only a disabled SSH flag may be changed as part of starting a connection. */ export function sshCanEnableOnStart(machine: SshMachineReadiness | undefined): boolean { return ( From 9710be2d5b45a5c9ecd308fd1c8c96af89d5266b Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Thu, 15 Oct 2026 23:22:35 -0700 Subject: [PATCH 33/35] fix(web): the split button drops its border like its header neighbours The only outline-variant icon in a row of ghost ones (workspace link, actions menu), which made it read as a different kind of control than it is. --- apps/server/web/src/components/split-subshell-button.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/server/web/src/components/split-subshell-button.tsx b/apps/server/web/src/components/split-subshell-button.tsx index 86f98e585..d9afb7c36 100644 --- a/apps/server/web/src/components/split-subshell-button.tsx +++ b/apps/server/web/src/components/split-subshell-button.tsx @@ -77,10 +77,13 @@ export function SplitSubshellButton({ subshell }: { subshell: SubshellView }): J cannot afford the word). The tooltip carries it, and the dialog it opens is titled "Split" so the word survives where the choice is actually made. */} + {/* `ghost` like its header neighbours (operator ruling 2026-10-16): the + outline border made this one icon-only button read as a different + kind of control than the workspace link and menu beside it. */} setOpen(true)} > From 1e22166cac49c7edd8dcf878bbc956a366a08984 Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Fri, 16 Oct 2026 00:01:04 -0700 Subject: [PATCH 34/35] fix(e2e): answer the CI run and the review round CI's e2e caught what dev could not: the job is itself a container, and a sibling testcontainer cannot route to it across user-defined docker networks. The wide -L escape now reads as an address pair on the connecting side too - the loopback-bound -L port refuses a dial to this machine's interface address (test 1), the wide one answers the same dial (test 2) - which proves the same bind-scope fact in both topologies. Spec 23's row expectation follows the new picker copy: "SSH not enabled" in gold. From the review round: the marker httpd's daemonizing & moved inside a brace group so the exit-code guard sees a failed setup (a trailing & backgrounds the whole chain and exits 0 no matter what), the ss diagnostic no longer throws over its own message on a host without iproute2, and the picker/split-button work gets its missing @internal/server changeset. e2e's AGENTS picks up the port override the forwards slot earned and the honest account of why no dial crosses the container boundary. --- .changeset/picker-rows-fit-their-field.md | 5 ++ .../server/web/src/components/ui/combobox.tsx | 7 ++- e2e/AGENTS.md | 11 ++-- e2e/tests/23-connect-flow.spec.ts | 10 ++- e2e/tests/26-ssh-forwarding.spec.ts | 61 ++++++++++++------- 5 files changed, 63 insertions(+), 31 deletions(-) create mode 100644 .changeset/picker-rows-fit-their-field.md diff --git a/.changeset/picker-rows-fit-their-field.md b/.changeset/picker-rows-fit-their-field.md new file mode 100644 index 000000000..dd96411ae --- /dev/null +++ b/.changeset/picker-rows-fit-their-field.md @@ -0,0 +1,5 @@ +--- +"@internal/server": patch +--- + +Launch pickers hold their field's width now: a long reason could stretch a dropdown past the dialog around it. A machine whose SSH is off reads "SSH not enabled" in gold on its row instead of a full sentence cut short, the split button drops the border that made it look different from its header neighbours, and unlaunchable machines keep their greyed rows with the gold or muted reason beside them. diff --git a/apps/server/web/src/components/ui/combobox.tsx b/apps/server/web/src/components/ui/combobox.tsx index 3ae3957c7..c3537c9b6 100644 --- a/apps/server/web/src/components/ui/combobox.tsx +++ b/apps/server/web/src/components/ui/combobox.tsx @@ -200,9 +200,10 @@ export function SearchableSelect({ data-slot="combobox-content" // The width is the ANCHOR's, both ways: min-w keeps a short list // from shrinking under the field, and max-w keeps a long muted - // reason (an unlaunchable node's blocker sentence, a destination's - // detail) from stretching the popup past the field and the dialog - // around it (report 2026-10-16). Rows truncate inside that width; + // reason (an unlaunchable node's blocker sentence, the key + // picker's "wait for the current connection attempt") from + // stretching the popup past the field and the dialog around it + // (report 2026-10-16). Rows truncate inside that width; // the full sentence lives below the picker once a row is chosen. className="max-h-96 min-w-[var(--anchor-width)] max-w-[var(--anchor-width)] overflow-hidden rounded-md border bg-popover text-popover-foreground opacity-100 shadow-md transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0" > diff --git a/e2e/AGENTS.md b/e2e/AGENTS.md index ac2c3e84c..1e80835cd 100644 --- a/e2e/AGENTS.md +++ b/e2e/AGENTS.md @@ -54,7 +54,7 @@ runs discovery against this fake), and no spec ever dials a real IdP. Every fixed port in `ports.ts` takes an env override (`E2E_PORT_BACKEND`, `E2E_PORT_FAKE_REGISTRY`, `E2E_PORT_FAKE_IDP`, `E2E_PORT_ONBOARDING`, -`E2E_PORT_SSH`, `E2E_PORT_CONNECT`, `E2E_PORT_RELAY`) with the committed +`E2E_PORT_SSH`, `E2E_PORT_CONNECT`, `E2E_PORT_RELAY`, `E2E_PORT_FORWARDS`) with the committed defaults unchanged, the escape hatch for two suites running side by side on one machine, which the defaults collide on. @@ -142,9 +142,12 @@ real password login; no host accounts or SSH files are changed. Host keys are generated under the test's temporary directory and trusted explicitly by the connecting process. The image also carries `curl`, `iproute2` and `darkhttpd`, and its sshd runs `GatewayPorts clientspecified`: spec 26 reads each port -forward from the side that owns it, dials this machine's interface address -from inside the destination to prove a wide `-L` bind escaped loopback, and -shows a blank `-R` bind refusing the same dial that the wide one answers. +forward from the side that owns it, and proves each wide bind as an address +pair - a loopback-bound port does not answer a dial to its machine's +interface address, the wide one does, destination-side `-R` read from inside +the container. The dial never crosses from the container to the job's own +network: on CI the job is itself a container, and docker does not route +sibling containers across user-defined networks (measured 2026-10-16). The browser exercises key login, password prompts and retry, terminal I/O, view-only sharing, and saving named SSH presets for distinct usernames on one host. Password checks diff --git a/e2e/tests/23-connect-flow.spec.ts b/e2e/tests/23-connect-flow.spec.ts index 9b17ef662..b14c40297 100644 --- a/e2e/tests/23-connect-flow.spec.ts +++ b/e2e/tests/23-connect-flow.spec.ts @@ -31,7 +31,13 @@ test.skip( /** The exact strings the page ships (components/connect/* + components/ssh-pane-labels.tsx). */ const SSH_VIEW_ONLY_COPY = "Only the owner can type in this SSH subshell."; -const MACHINE_OFF_REASON = "Outgoing SSH is disabled on this node."; +/** + * A gated-off machine's dropdown row (ruling 2026-10-16): short and gold. + * The server's full sentence ("Outgoing SSH is disabled on this node.") no + * longer fits a one-line row; the decision-point explanation is the notice + * this test asserts beside the field below. + */ +const MACHINE_OFF_ROW = "SSH not enabled"; /** The seeded control-plane host's display name (seed-local.ts): the picker reads names, not ids. */ const LOCAL_NODE_NAME = "Server"; /** `configGroupLabel(selectedNode.name)` for that row. */ @@ -301,7 +307,7 @@ test("gate off: the form explains enablement on Start", async ({ page }) => { await page.getByLabel("SSH destination", { exact: true }).fill(SSH_FIXTURE_ALIAS); await page.getByRole("combobox", { name: "Connect from", exact: true }).click(); const serverRow = page.getByRole("option", { name: /Server/ }); - await expect(serverRow).toContainText(MACHINE_OFF_REASON); + await expect(serverRow).toContainText(MACHINE_OFF_ROW); await serverRow.click(); await expect(page.getByRole("button", { name: "Enable SSH", exact: true })).toHaveCount(0); await expect(page.getByText("Starting this subshell will enable outgoing SSH on this node.")).toBeVisible(); diff --git a/e2e/tests/26-ssh-forwarding.spec.ts b/e2e/tests/26-ssh-forwarding.spec.ts index 99b2b1e93..cfe1c3248 100644 --- a/e2e/tests/26-ssh-forwarding.spec.ts +++ b/e2e/tests/26-ssh-forwarding.spec.ts @@ -42,12 +42,15 @@ import { shortTmuxBase } from "../stack"; * Then the wide-bind gate as a door, not a copy: wide rows are refused * (403 `SSH_FORWARD_BIND_DENIED`) before the node's setting exists, and after * the owner flips it the launch is accepted, the rendered file spells both - * wide binds, and each escape is read from the far side. The wide `-L` is - * dialed FROM THE CONTAINER at this machine's interface address - another - * machine reaching a port that loopback rules would have kept from it - and - * the wide `-R` is dialed inside the container at ITS interface address, - * which a loopback bind cannot answer. The audit row's bounded forwards - * summary is checked beside the bytes it describes. + * wide binds, and each escape is proved where a loopback bind cannot follow: + * the wide `-L` answers a dial to this machine's INTERFACE address (a socket + * bound to 127.0.0.1 does not exist at that address, whoever dials it), and + * the wide `-R` answers the same kind of dial INSIDE the destination container + * at the container's interface address. That dial from the worker, not from + * the container, is measured CI doctrine: the e2e job itself runs in a + * container, and sibling testcontainers do not route to the job's network. + * The audit row's bounded forwards summary is checked beside the bytes it + * describes. * * Like spec 23, the container legs need a Docker daemon: with none, beforeAll * fails rather than quietly skipping. The binary gate is spec 22's: no ssh @@ -142,12 +145,15 @@ async function socksFetch(proxyPort: number, url: string): Promise l.includes(`:${String(port)}`)) - .join(" | ") || "none" + .join(" | ") || "none (or ss unavailable)" }`, ); return parts.join("\n"); @@ -330,11 +338,15 @@ test.describe("ssh port forwards (spec 2026-10-15)", () => { // the same marker this spec's own server answers with. It runs because // the forwarded connection's target host is resolved THERE, not on the // connecting side - with B and D finally apart, that distinction is part - // of what the spec proves. + // of what the spec proves. The daemonizing `&` sits INSIDE a brace group: + // a trailing `&` would background the whole `&&` chain and make sh exit 0 + // whatever mkdir or printf does, so the exit-code guard below would + // check nothing. With the group, only the server is backgrounded and the + // guard sees a failed setup; the poll behind it still proves it answers. const httpd = await fixture.exec([ "sh", "-c", - `mkdir -p /var/www && printf '${MARKER.trim()}\\n' > /var/www/index.html && nohup darkhttpd /var/www --port ${String(DEST_MARKER_PORT)} >/dev/null 2>&1 &`, + `mkdir -p /var/www && printf '${MARKER.trim()}\\n' > /var/www/index.html && { nohup darkhttpd /var/www --port ${String(DEST_MARKER_PORT)} >/dev/null 2>&1 & }`, ]); if (httpd.exitCode !== 0) throw new Error(`container marker httpd failed to start: ${httpd.output}`); // Prove it answers BEFORE any leg depends on it: a silent background @@ -489,6 +501,10 @@ test.describe("ssh port forwards (spec 2026-10-15)", () => { } catch (err) { throw new Error(`${String(err)}\n${forwardDiagnostics(inst, subshellId, localListen)}`); } + // ...and the loopback-bound port does NOT exist at the machine's interface + // address: the same dial the wide bind answers in test 2 is refused here. + // Bind scope, shown as a pair on one machine, no routing assumptions. + expect(await fetchText(`http://${connectingMachineAddress()}:${String(localListen)}/`)).toBeNull(); // -D: through the SOCKS listener; the chosen target resolves where a // SOCKS-over-ssh proxy resolves it - on the destination - so it names // the container's httpd. @@ -600,13 +616,14 @@ test.describe("ssh port forwards (spec 2026-10-15)", () => { () => `wide forward on ${String(wideListen)} never answered locally`, async () => (await fetchText(`http://127.0.0.1:${String(wideListen)}/`)) === MARKER, ); - // THE proof: the destination container - another machine - dials this - // machine's interface address and the wide-bound listener answers with - // the marker. Pre-switch, this port bound 127.0.0.1 only and the same - // dial could only ever be refused. + // THE connecting-side escape: the wide-bound listener answers a dial to + // this machine's interface address, where a 127.0.0.1 bind does not exist + // at all. Pre-switch this port bound loopback only, and test 1 dials the + // loopback-bound -L port at this same address to show it refused. await pollUntil( - () => `the destination never reached the wide -L port on ${connectingMachineAddress()}:${String(wideListen)}`, - async () => (await destFetch(`http://${connectingMachineAddress()}:${String(wideListen)}/`)) === MARKER, + () => + `the wide -L port never answered at the interface address ${connectingMachineAddress()}:${String(wideListen)}`, + async () => (await fetchText(`http://${connectingMachineAddress()}:${String(wideListen)}/`)) === MARKER, ); // And the wide -R escape on the destination's own side: the same kind of // dial that refused the blank bind now answers, at the container's From 6b5a4397977b80c57aae4a5483583596894fad1a Mon Sep 17 00:00:00 2001 From: Theo Gravity Date: Fri, 16 Oct 2026 00:07:11 -0700 Subject: [PATCH 35/35] docs(e2e): the container's address is a dial target, not a client end Leftover phrasing from the superseded design where the destination container dialed the connecting machine; the address it holds is what the destination-side dials are aimed at. --- e2e/tests/26-ssh-forwarding.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/tests/26-ssh-forwarding.spec.ts b/e2e/tests/26-ssh-forwarding.spec.ts index cfe1c3248..f6bc7e801 100644 --- a/e2e/tests/26-ssh-forwarding.spec.ts +++ b/e2e/tests/26-ssh-forwarding.spec.ts @@ -328,8 +328,8 @@ test.describe("ssh port forwards (spec 2026-10-15)", () => { }); if (add.status !== 0) throw new Error(`ssh-add failed: ${add.stderr}`); - // The address the container itself answers on: a dial there cannot reach a - // loopback bind, which is what makes it the wide-bind proof's client end. + // The address the container itself answers on: destination-side dials + // aim there, and a loopback bind cannot be reached at it. const addrProbe = await fixture.exec(["ip", "-4", "-o", "addr", "show", "dev", "eth0"]); destAddress = addrProbe.output.match(/inet (\d+\.\d+\.\d+\.\d+)\//)?.[1] ?? ""; if (destAddress === "") throw new Error(`container eth0 address unparseable: ${addrProbe.output}`);

X9EL&PzPn9Zo#{KMVarjaiXb?dvOk)yOV4|wJKa-7b zTnza0z#p9~&BGgB9NW!B*o`ebxBB39U_vaPRTOQkd=9ZgX9{4)VLBME4E;WB? zLpvF$9>9bEh%Yf8VdM4q<2z+`i{AAHXvgM8mqwIc8iAzA6r?v z``1ege;p~=|IIyJ1iyCB`F8=xfxqYSfi<|TKXUJGvw=*j+KgI)YO%pSnde($cP$g( zha*#w5%FB_jWkC`V1-c{#7KteDWT4>ID(A0-S&vF(b^0GT{5V}lq|zUO$cI*y1uVl zeX;U<+DS{7&W*4~8#;y&L}DPG(P zajk&l8;`4A`D|3zt?g8gAfUqw+aH6*5rtrS?%2L&D%w7`$l&H2Ael2?-VT%+-GbcC2iP8hN_Bb6$f`AGKT@f~aY52WjB(D-7yzA_vD8@~QV# zGwAl9?2{Yo0C&5|txqA;2@TXx45kEQxyl|Um3<0w(Muo}_jlZcv)JrU%kE^3s^tyA z#`^l*4jfsCPYbKYc|s#|p=1J!zSix?SJBxqx=RvD+kL4W)y`I|o-njx2X0Ic=+@cM z{ZTQu{sS3J)=wVngdz zpFe<(j7>OM?1jx@z27?{5s`n4-p10j{9xDK)CFf(2?SNQSj(&CCPJjZURk%Nvj}Bc zc}errF4I&gsPOIW1sGOQ$HOb9yWG29_6;U~g-uBO?%M=@tPyvqrY9{u8$aXx;-XyW zEbxHv>|ur3=+td3gXu`}4^AL&dxC%ESl1wCVbEV5G^GYKtcNr_mQL+dna^LU9p}XgtWx zNrHHWn3Pr1`cAxumsXy_o-aQ*nV+0ygE-J}!NiIUr7zpx^!$PPQvmvH_O_ne=ebZ& z&ZVRWA+5j;^Cbo0k4XZmo6O+cgF$=mc0|B|W_|!>U7k7sfL|)eIMqPjLZ+a)8VnNs z^A2FnNFR(>-9%^6T{yn`CCg1$)}?O)kuk_9``;Jw81Gi`E)R7L0__DTy?fQ> zPJ>yUTI<<&{ER(Ai*ozzrWbRZ&CSh(Nvi4%y)jXM=oo=QIG(GX+R5)&20hLVi%ZT~ zYt;i;AQrNBS9eEcm3&oJM@L6bvjM3aTbq(nQov4feRng<&_KoKa&O&SqGrn+hky}& zQ#NPFW2h+oy_ZN( z8;@HD9n^w7em>2+lhja@?PDybRT#d1rKwb_dKO>I!P$o6e!bJP7t5*qe?7p@@Qmxz zH!W0m_CRpr{MMD<^%EKhHzhn&8m(2)2SkueUjKjT|7zvx?zB=ppi$L(`6KBk2t(_o z<+J-=Byq{SC@NmoIXrgvWYN053^-bZpl*ll%YCqYD?~tn8yyV1JM)#EEonZ$J#iCK z+Vb(EjQG6|9sDx9;XH^5{gn-@o#zt<_~Q*ZooK$%D8q;}5a$Slpw|s;>$^)EUtvyG z6nq7h;|{fUK2OfQN^U)OlA98M-fhlnr|o3XuM4I76UVJ5W)Lwc1|>)hB}eCtJW){K zvp)~)KLxo-eYXdklRfgO_xN6BZ|Qk&K$|e*YNHA&toRq#NTI4^SnjYG~Ns?*CM^R7dE~;>Og3 z&t6+!&wloHld;k|CgYPZ&=j(bi-|nmg4WuPPJ1tX5ho4&3{i2>Ww)mTT73`yqKadY znYp>9+o>l|F8yL?eYm|H5QwTz!6HQ3urhSDtVmXZK-DM9ZX0OD~20}zt{bRy(T$4+&AlJAgmV`-$^fg z@zeYcjil2EhHWrH$ur2>E9`PVleMFa%cMoOpfF~VYd3LW_BR2KoD!9~y|@*^hnsS! zdW%a&<|)eNzeKXS8dd5Em1gsBbUdpswFKfLRc--FPRP(iL)CV~YP0f(sAO^S=cyOc&| zXQfX{&bC#?&92NWq1>gBhvSusS&9W5w)oiteB2;->8tX zjGN)-)6LgEU$@R9AQ-Lc-JJ%S3knL5>>reif%Yc@>#aJHThB3Hu<>h(!pnGH<|Iz3 zmlfuFIk8_~cOs$WZ*Hpcsknds{5qq}n;}9!pqG}84ql&3{~m}_uS$zEgF${h4TSsI z%Go+3!?0tW$I@CwRA4%d_CnbiC;triv^GdoZb^0rPo+?IQR9{B$V6Ved7~sGHp9ru z>ezFbylEvUKAi`#{r0U}8oE1}e~R0IXk2{~fS#3aPvG#hV!OMTsV*w|2s}E44;sKT zQDCU^3#ZBa%|Z}XC`1I7B+EWNGj{JoS)%%-DAr@t)nsFD#4D0P3>lu=YVq_?>If;~ zpHAKmQlhzXUDubiyQQB^Ovwc7kEew_SxZ?7Zdoy3w9mk_#ZsNp+-E=lCu;SruUF&< zx{{pj&2C(TaMH!*bftEU+3MEX+R92Ee$Y}c9V6oz&>wymgLCN9{*U5M2qG7D8=KpG z4eu%b4J;kZ3kyR+LOMG=^Ce8ci7gx>c;BVR-ap*m`ozS{G!z@=^*53p7+3`=5S1!9aFrGB zaH0&ttSvQsUc8645NvL31{yYng%wA!H4y!0x%*1-sjRvjD^?*DV4LByM^f0tuE{>> z#Lvjd%D%6e?(CfCYa45CFUZRJUzjag^oIfH0Y5duITvsBH-Y>wgyz4dNv2FNrMq#Sw2_-N2K*EUz{*7f~$OdKJLw zCh+SzT;x+@}$W| zOEB5~Wb!E|=sWo%Tggr)&P1yh`kl%Y9X3CDiverALGI^IIolgsGqW>EN!mZ?{*!@< zdq*auCj5-k)6g(bnIZ}5|LwzBabPw?R<1kCR|U(;;-Fz-VEQ00`##d_vqecDe3)El z=&aj+k_+@WZ1A?FOGMqm+X{*5STq~`@YWY@$l|kzlTCCP6HRA{>ag;@Dph(}URqk7 z2ycL#&V?*Food~barkWc8((gO|uORt%m@}#HrdOd$1)sOnw z?cWc5>P2RLu3~8WEFJO1^25K8w@6!T^!O#>8GIb=Chkcw=62 zTvly0Tcg=QQ+xD=Nv}@a>BIg3afRJGB%}~uDX+pxS);E&sd5#T?YG$~1Q-ZnB>Vg0 z@7`sU=+DEfl$B&EM6RTIxJZa5ZSe8PYV9ZAtv(_hBN0zD2-7s*SuOe0l?k8BtXr2H zN=1;(I&OH0L=?ce5o1o`1Bm_9zgh|AqshjFMnDEf;0*cCesOGO3;o*N|0D4SJeo8_ zWVGr;d7PbH=;pQ(cz0IQcPDDrTbEu?j{N%7a;@+^@bCo|R?K#;(B9z|h>plcW;S)4 zPCmigFT;3vU){cn#l}NI^9_qrofRC`KqnO!0SS!t^-Xuq-QeT58k%c&uX*@*X6I+| zus3o|vX-Sk!Z7TV(_95t){hkdbX|qDhy_(k9giE%^)57Y5CFlN;I4qrAfFXD3w5~# z+7~AmJ0=&~+utz@atf_p0)P;iOH>dkz7IUSnD_g7hd)|Ng<#fD6#*U$c0(_MnxTT+ ztm;lHK+SP%^F^mfWYY#^pB&hHOr=c02*5l-tsmU;{GhDZP7s)myQ%iJSY3|vGWh=J zV~fqp&#OOH2f4N_MSEQ+dp8{TS~2#<^q{3jo|KV}2^pz4CLxs;{UP@Ueo~dqVu{J` zcmN;SEzNNAT$>0Ao3AaKkY@nYeUu&U&dFH`6A!@XoVM1hwR|GI85};~yZAFPG(Ik4 zvMO)I2e9^Sae0N$3I2Iu@h&kYsq*O9vKxiHv~ zf-4D&F_m9k`;iiL(wZTvjyG+Nm9tjSV0HB^F5!}F$;Q~qeRsheT*~Y7Lw)|N%O@c> zv7Sw`!;3ZpznA_i`Wvfg#Da9gyq-JWBIl`VYx{gAgW}xk+#fR`9yfEFzz17(&H-v7 zDzfQ0^gQG+%Yc%wU2Y&iHas8Ndma^CTzqg%+nw|?QF8e&>Ca6|OY56;cxi%T2o3Wu z;^fztq)vD8?V#e-59cc)cMYAWF9PbZT@^*#?SO zyxN(#9BCUSjdLCjANID6{DX#6yuQDYW%3hDH6!wMXoj(R6H4h&Rj~qO(%SlF*fkvnBdWlu%gbRWo68T5WD^ z-Oryn>T&?~hQ)xy53i5a@K0(ch#uyGGk23f<43t6-#Y|P_@v&UuE`H?F&~j^U(=&9 z)6$ZtYuvGol4mr>WoH9fM=4s|%uYcnIsU2_@-dRgqOBj&I;<-iFiqoe=bBObQE>D) z3N|-zR!di8v>E9-e=k;!_U{lLrIjKgeVyoR& z9>yAOEZvjr=k6QxoRMBdjZ=>lnk9JA>9kONMLNfQG))HPmm!eImQRxv7uwu_(J#zv z|LG=G?V%@K505zgHA#SX1&5ykWXX0wu!;dH-Se&WB=e*Z*X9#yzYB{4PkT9)Epo4^ z^>U?H(|099LqHFfdb_%Pz?k|3BrBlEjEzTz=dhlb=7is)4%j@T&E71Vj2K+xhY$yg z$(o7n6j{|aJ;|f*6E|QbNT!6}+x4}l=(+&6!$SE!GMFQqudv$-7v%4}iv-T1w51nX zTvqhBZKnt}82A?Ks>&5Iei`jq>WGX&fA!{QE^?+6Y#iOFU~@ zfWSOGJL$OJW8-dqGz`CW$q;`B8!^zad?_J&ay(1sK|d@NHxhB?8I8-xpmrZLN0IJc ze}!;-foHn4y=}8pXL5H)vdgWaO*AjG=mJFKI*q;*kswOe>+6oz` z75M+7tk7YwAkVxWNsKdPO;Og=-Y4`;zMvumOkZig&|dcIaK?9)GDIp>)=Pv$ZF-so zf2K28Jvwgy)UWk~>R}PgH|~yDkDJ4b^q3PNPHKhd79gr;`4 zg*`EpOL?gOlFP#3kI!n$S(rUP{U;Rx7jk;F>w(O}%ZIYVpQi_-9PUw%3}^c#SK9nk ze~UNvaGSO+&x(eq@t+|@hkznB{?b`t}ND06Vs2_UU(4InQSjBN5HF_>8}c#imnq>qGd@ zw_mlSC^rc8syZ>_j9}}$X}R_Iws636<2qks#ty(`AP>BJf4_Ktm$-KSAMM^-Q#}gDIow??h>Ft+;)<-zKg)8Lz{0jAtF>Pe-tacD!gpzRuX49Y z0s_s;*@z~sRyI@!KN`7qF9qMYZYnu<>IL070Wge<{Vs*zQ9^9={ZYN=hz?JPx!W?! z0iX1PF&MtrIUiZ36Ydq>%~xE9RvDWJJzdJig#^{da&&1L6lv3X25)5sCx-W{a~Nl$ z?DCh1YPc^nckx~$YJaog{d%9*AH5c?S8u_K`cJG9kn6C-XMQ_e1i}@I?}x&#eF;|< zT)(VD)(p+B#M|8sAFQomV#Ao3nQpK4LE(hHhQBKt-H#zQx2qQ=q21eiJhvW$Plk*W zcC><8JdY4ei*FYbjYUH83!TZqVjklt`D*Wqfr3D0Rf!P;D*rWjs}13~+Y1bn)#2~d zm39?ahi?MUfS5(Od8DszXD%JdI_#7cILgDvNf~ri2uIg$g-rI}{Q3@Krlz4?li%7} z!zIQSFxih?#(hv*#o~cYMYy}!KLp--R~#oNrdXJoF2g)BSG>|rT~s97sT1=DA zn$3fvRz4jC+I+uD{oyhJwKUt<+x%UxDZ5a6-2DTlp>0W|@+|Gp7T|`_Kgw7!h-0M? zN8En?k{UV~3OqAl3%c-=aMIGNVh(RPP79~j!N??p2|&Ghc3$m5+dw8DE_j027^cbxSTGB{GIsz8&)pW={`m}cH!T&A8}*_U`o^r zbyi0!2ByXVEqleLr2jbljlVlP*J-E{O9V*=8I}2BscGd{CkN672!3Y`?EhM%Bx?9f zDeZ&ou#*L$(LWb_4y`An=?-_G|0rvRiHTKJK!H z@keFZ65B7E__NF)qX%nL({D4L8OeX~jh&eAzIXegw@?17l7|K=nOUZ)R7Cf4^@!on zI$o^<4pjnY5HNoxS_URgl*`=9sBOBJI_o0##4=q_*tQe=T6CvZ>o}XNy?5)YjzWoW zhoN2NQ9ry?FRoCWK98S21aL$=j8=)p^aKcmzkNOJ%O8ZGh8rIqF0Wy@5Y+s5LUiF0 zAIOIRzkS>U;pnO9BKSVpL56~ca<`RWV`sdgib7)>$NP02*lgW%_Y3 zdES9=v~t-`b~_uY8Tk*f;1_HzDj|r6(jSL542w}{AW1+b>UP>`gK2gSO{TAp!}0NP z^zt)CD4ET2{UV?_LVW@oNFpq$3UH3kfTjefTI#g}SYUGNy;8ka#>PH!VMKqAj>0Jq zVkomw|5pnLa#L>LV@!G1RSu}GYK9_oHT=H;-D5B_N99`;=_hw6(S@x;&tq4>r_ZLm zygaLn0&Jf?J(Rh4vgqb1r6Y%5UZU)Ac8;;Ax>D0DV*L4TAd>ftMetgb(5ho9Y>ill zCk6kMXV<`Xalz4+OkQGQkfOs!d9N66x%0WKI*dD9r zW2a03Yw2CLXfpBRV!0!2^E;N#wU0WTir#pIoAwKYiRulSve=`ZcYBWX_pK#lPn|qP zH)%pIOowhhHXlzV&j-r}gBJnqra(EACM+dzo}&FN+nygW>!bTw7q_jPlte=JX*Qpm z$pRIQr_mJ`JwytH()v5yyKpkz!+|5yWoeZrCXjsf+_}H+Lh3xkf}V8;kVjzr!3$Ix zD^~_ABRjYVlHJK{+C!znsZzB%3@*zeGv^TgDi!=L64Wk|{d}P40h235JXlv(C+vxBeyb0554fVu%_0`^ z(2&7eZB63yU{Gtei4gK+e$v(g4GVD}IzsEYW`gOL&>X+ISMv2|CTP_e?^pZMgAEY6 zs)0g=L_!HFB+ori@hK5fOj~Mn$88m4L>Y|aS$ut`WgqJ#V#L3)Av0P@=T(&a19tZ( z49)gtuG}Wqo*C0{D{nRPSU1w-_Aw$gtC;{8mi!sh>_eQ9jO@&|6~#WznHVE6NN=8mn_$OCwC zoD5d^N6SGTYyFuCq$W4H62b33sTjQcCkNPJ=#l9 zkEfB~BpbIBttij27eO&<7b9Y0| z5LKxjd@v#PwuRCZ2&cGxd1KuJ|A<$tQFMx{l4*Z1gTwX&N=K}E&3SsR@2?`B35xys z&xy<%wjfCN3HfT}YeW=l|Dix{bEJ_VV_a>;uV zQ^lo`|0P7h`WhMu&~%v|}biB*A_S9ULW+r`okMy>s=5u% z#VQNWur<0`iOi)hsOlJ4^i<#6f)lKGk=Jsy!kF?maZTB`ih-vTg3d#X3{+7!v8kdk zLz}sBiF-ZBa#XK}{=-=JriTsL%Rh53o=E?-Bt3&e zyR1Hswb|QZR%GfZKRNT(2QO$yiuLKY^-0&$U}=^8w!*RLtX+gJaBk$y$&lhmH!+UD z!CV1CU+Tj6AllPN53vcdPwzQk4!~jebnm`;>W8=|WZx3MrpksYo=Dls3i;I<6@hg# zLXDLDEB(WA2-@Y2O~_*;D)PtsN_psJ&onC?)9uYjYPJ$n$#_x?qqoAuf~DJ%`B^wK zScOvU1!2LHl3iFzdfsY(SK;zfCTleF48)Tuk#(q3QUsfV=C=xYC>uMwSoKPDoNTt0 z->*JlOLZekrmFB6wL~QBlnfeCMrLm<#L!;^yy%)4&c%LciIxyF0Hpg0w`F_1`bi~2Mwe` z=^+Z|d&#UDcLfEocohQU=c~%&O|uk>tUMWsP~r@u45+(=D0K7IbJMEp_qbAa4?2Up zbWa@aH15uL05j8PcMKH0E=Qdft;@JlVzrtD46HazrnA0Z3ZSzs7Dnqw98L{(Mz2)H z5pW2&Z0VY@cZW+VAz7=EA;00XO%3xYnjN zo|{W?DtvVYLME)NbU<6`#A~6_Lh+8b-F+%gPwYPA-v1V6)o_`B+rB$dka?_tqcJ?cI zySiw8*KQ_m8tTknMJ{|@F7-lfA>L@p{P-K^^#X^yX*?H^r0VoT+}3GkUG8h=($J$7 z?RCQJF9$kW0hIofJPaE|qHgz`wJX*lfrWfurN)&&Ub;F@u<}F`+7cu7|Iva><&s4(ajUEC$ljL66r|D%>bOIg&sg;ceoeR}h=%$1 z^Jbv-;9$=E&}ep%@Uuz>C#oPs>e22XI{2sfy2Bzf=2n_Dt zJIzpAY6%HNaLh-qhc&>gmg;b(6D){*xxs_ufvv2lV)r9BkY0#0AI747>*byY6gB`Z zy=Ab2(saj_YM(&VwAK;x%Fj{&2GwD2baHS4@Izmi;N8du^%0V?4GpVdQ-P7Rm&L%n z1PF`PK=^sX&b)?mKRzj|Btccf``CoxDrP()l2D>685evW>QOCnKyHl#29gb{J1FDA zt_T^CPSi8Ymyv818pHcf{T+ycAIJhXfpLK&EdR~K;ryyFk@kP0s}~wg18p+IFI3fc zl1nfUqb~E#)JbE)QRPMZ?PJRO7LCUTI+XA2HXYRIzB;Pz%Q%PVI81t+W{elO7k-P6 zR~wP{cwN_NoGag37*iMpOz4<=ectlH{d;d-W1QGb9n`# zhoSA~gVH19^{v#{1V2uNbgn`af^#BC5e$DF)gI-Mj`5nP|6^GlmH(T4d~zWC`jzuzA@>+5z7VrlUh|W3`cb#UmCia=Nhk#*K}%IcRP7^M8ye zP7Fqu{BraXdVrt}$epNo%oiIk`W+a%e)ehv%Q=8Y0Q2oUKTn#s$m2v|K+;;N_y8## zPAmbJC|mI9qHg9_{6DV5){E8E)*3t&RwEyOg%7Y&et*^9L&i~X>`j&btS223AJyQ% zFE2Z)x+7cfqK=`IYP*}RQgiBTbI>Ga>5C!+olu6gf0=J;YQN0ju?{#V31w|Cn9Y#6 z!dNNz$g49Mt1CYB7Z!|e5-`rdny#W8)`S_UhfGJ#L`SExLnR&pgk9+tokR#FbIRTF zEH(s;jFXjo%*6$s>IW|zcD)9=wJ?Z*HVRm|@bc1JT}*HsT>Q6M;!37MMN7jfL9zF~ z{XSk!>-DRbas>PcH^dW6hcgXg1Do281h>ewge!Z?nX(bbqi=OxdZv;aPCZhUhD#LW znVYoJcnZH#Nh_4z=PCC-57Hf|G8@`n`HaDJWi|p%hSqc{|s!kp7sfuM;>662}Gj=>Gn6q~wkEHqMKKgN&5*Ltq*>xc$28k+|s?D01 zk=pI~keq!hSo^DxuA~I$xaX2WcU_$Y?Dx^1w}WczL&Kqdc~II97bT%e-8tsq%qB|t z0j7EPnZ@pYKBhR^Ug_ESfW`m@I5;?!1L<;c>cbtRgvX11cGh*CPL6Qtumd!Q#+45i ztx!5TBEXr^#blh|tsSN(pi z6_|uyIK)Gfk6I3@G@pOK6DJh2BWjfUE>ZP|TWKT@A{pwT&!+J1H^!30)JVnL0lc;=KMDKqWqWS~Y!IeKDZM zidT>WTXYKjzghrHBO<&D?t9c-Jm8C!8Jd1?mYLXYONzo(7vzp4%ge~WhrZPBS)L|y zRU37Uv)9`9_|*u!MuUlGnv2Kvz`%-%gd$b|x2=_1lkD+yhjy{$7Dt0JWTZ0Oo3~aO zE8$l#-v|@W?aXMG|lnt)F7}C(?77e4C z#Vs(okdu=cU~fR%A4;gsj%^^w)M_&NTu_ItqEi36+FjJ0H3CRsQ2ucKGhGFy<2wh6 zX6V+BPa2qCoW*t=d|2OHX~@jM;dIQ)jilu8Z8pjjH6I9Ee3i%rm;9CV?J}1iUWtF^Sc!sBK zy!o2m2H)c3d>rDRZ#H95?}?AT#A?*xj)kQhH%3A#n&&^#H`GP_Ph2eMsbHNa6nw!o zQkFLMtoM_BFvao%A{s5r5kB$`pdK-?7MbGSNDI?s1yd{o0S8QZwd|KMDrP?WAR z@-whg2pOn1Wb9#qR4VM8lRH7MvxE^e%-7`lzI^+VCI=E--->~2C0aQ~@jRbIho6la zfe6JB`zA1|0}e%s10M_TZjtqDgNi`N!e?G>KtA>DS7JodzpbUluR(6jeszA`!rNKZ ztK84`ftV67R4qm;W*+#RM^c1C$o3aMxmtXm?XlU=ziHNUV|YW8I59iNKpFq0_+d=_ zgochYsOM2h0eyD9Foj=winN&YmhFkEU z2;-L)EPgV08PxF1(4w9)bB)23g=1GzW&Jjczsj1`N>U{_sG3NRRBw&yHczmv1>|O< z9wVE3C<+FqELB{R#M)M$3F8v{@OmPD z@CD6Mq^-o%jQLQeWMe+8t_~qCx@homeZXShTlE1`b0^G)fA1%fsps{B)XDc?m#Y?m zH<LRVdau!Yc74bAvt3$VIkfUm*XJllDS9D#>ymsobYgVMKQycI?=7?#EMS(erZJ`B zA{|*Aw;Cz$3&|9Pb#A_V+%MKvVx$)xTjV8}w{*<_7wwcGe&)PBo)AGY@E^tJ4YoJ!)53#ohDUn`^QAq4?#Bz$Aqsxzq4`H< zMK!4vveE|%cPb||Kcb^4{gnvOw93u1b-uV2&G?S%rV{;3&1q(XQejC6D|{TsMF+1O z-=;epUFzw4RU%atxwr?nzeda@9(HbSygS1087l5I)&t#*jg8hM^+LiIbv?Hz9^d-e zixX?-s@##5IE_Y+6+2Sg+BZ7>?EBf46pwn7XFI- zt*XjP5AT=76?%kN`o5-+Cz(ETvb=E79Sj#%95S|&U6|1Fv@UhPe5@!)A?ahuU8=23 zVvc-L=Vc&A%lfs(XnTBagw>@LA6-q_+SD;CvJ5ylOXI~A+ zfh+^5d~XA|rWP3{bJybXERE6|C1SQ1ST!}Z^7SzaU{J__1Ltlcx#bHQmAxs5X{p;w z*@vHSo%P@Q;l=&%@ih0Qo<^(praOz%$G3Aoxb<$tqGrBU^_~gRaXKjT2RIIYjT5?% zFFMAeW8KnY7j@a$aXiF+zcQP|^4@91+%Dqw*9(frOS^f9l$d9Bdk=RiZizkj=~~&? z*lEyzs;babG5cVa){@akZNthyck88ytyy8GqAkW5I68%r9DXX(q03H^x<3BZJ?~b& zwvw^fS=Cb}R}W@UU@BNBT&u@QIQzw+t zHyebU);i0`R2=qEhk={t@?flIx(7{_9Vz}1aGtKFPbD<^!maqpgBX}IkGgW_I15T$ zwzzB-K8B*mr}AfRhN%juMA9@B?eEv-=cK1klz#XC4QYDjVEwyqLS=eNfsUnc+W4DA zCjJIGW9fKY2qJjdyf>eIH}?7S=hf;Cs&;GzDKepS!fpMq=3vH;^SH%b`k)8c^0;bA z9RJwtaiCRg2erQeQqHh>ZWf(Kyn?1gt}o+7hQ9^jF5RB)B2Ll9_INr^{E^>;H~Gop zQBvx{zF>q)6aNJ6_W1a*J#RZoAF2a~{5&5kgd)5!IUIKTYP2y^RvKLHIVe9XFztVbl_Mn%kl zy($oxjo|;KQPM=<%I%WhU7RjYoQ>!(e!BZfXRAA$*w{Ru;-#F@CDBxwo*0QXgS9f( zhzQSO{=wo{ykZUkyBmjvB$Qsi<)WEGK=Ov3jkoXjqHyU0P9$W4`NBl$c~Mdxv1dxyYFKsC!o|w*hf5@U7AbXAkg2wl0$R z{f#skn8+w+i}mvNqcW)Wn}Q}KX24_tWw|>Nsv`8b9MQZ*NClePLmex0KAvXnK07bx zkLt$Ms?I6jT6{GlEwPG|o7*2p`-V^?`oXp>DPwVd+^?jg2H`_rUMHI(qiSqXUKUo&i^bqi2Lq{h)vI zi{uFPCx||Yf^+%HuF62yDq7X^CV^m72Ds|o8LhEBoXb1&S@!u1cPZ=(`#`<@0i(CR zQTk8UL-VQ4cU_M9O=ml(Bo2XQ8C>$2RtP0;(zhq)e zxboY3(DBwW1b>I4nf{ghqAilM`E7_IcgBPJea#X5!6aV7MP!;*7!iksN~z()u?uKr z|NKcnx9Q~UM5$Eoc5r$r$nVR*L+Fg`NvLl3>xOpZ*ZZ%?>h(t+$+UmauA==09uwbv zrev{##iS#mb8dpLd2qmVoDY)wU%#TTA&|kN>~V=p@AVlftepA=!f!ITy1K&g2LO9x z5~oE@MPoQoPy51sxrfh*&qYvJi{D;IKm%=X87zF=x>otyNdZ`2hZA+mf|#x=^MZng z!5N;%m|e&DZQt8^>+dVAH^CjD8ZYI}eAvdbp_hDvxZAg&vjDPyTGfWtki)6@u;8jd zzSS9fLB1;>a|50;M|*oreu0@PXS$~1A~`C?hpK=SD#pplHpcU| zsy;-WW%U{ZPsshS=(aEqueP>wWM~LO_HFckK1S^7BZY`m7phBtpJJcV2v$^vSO-WE z^2EdsDaUeX`E-dC#kyeLNE%=DZk9@j8(IqT+hn zp$~THB+Vr6#j%5c6Ux@PQ!9NRC={UYXLkxAi?lYyk)!D^0zgy*3y;W-5jWR1&C^Rm zM8CzsN2UBgbxP__woF(w6X1FJjdbF+_D_f8B_t+B>qj1~?K6B^^IScZxcL0@cKk#u zxbXKMj7(e1)vTD>bL~oJlyE&A3rRGxi}m}|mk+E$lSmSV>Za5A>p7o3n}GH-9xfhG zy{@4T^$qEQIcPtd(lt4BHZ=KoP_zO;+-4BO1!XrLGsTWT#_@FH0BtT+xvoyy&UGHL z1ygd=72xUgW>?|uHwkJeT;q`{%JaaG6u`y6J?Ta&U55a%c?YKqGNNZ+bO(fqtqt>M`@Ym@c&XYzWxhAf>m77Rspn>eNgJzRR5h8?Yzk-rs8_Omj z7*P#$kjbxHMaMo&_V)JtMzVSOG@QF8=8v`~#Pa(7gUtGC+HXt~_0E>%QSwb?_4@AD ze%+1~+o4*oKc4R|EPmwWqr7DD;9zMqK9c{X>HQ8B4-Z$Wa^ktOw*(`u0x{<^+I6Cl zCp5>H|0Jqx`5`DUk3Grnq}2N=IR%hLkwxZ`q&zR|9xj~uxS+p^#%aQZTELD2Y=q8l z@Oi~;`LHVkRaY_5l#PI8&qM)SbvNyDNoChN+=C1o@sY$kiI&4As$KDw0@{nO{w?0F zVhab?!D=*nl{Ugn7swd@R}0vi&LorBt&#E0=vj?Ddb)n5iFx4#8MRvfWNMnZA2?qe zb1L=gXF`JGpN&-zR__Z~C|)W~r))`E=@IrMo%4LVJKi}Z?tS|6yKn-_VE-`jglk&= zDP0efcC{Pmh1rH#&%YnryBeB7{DF-X)~G~Zy51v=#dHz zbTh-XGy5}{Fh6YzrQUT#KKg9BszD^B&Jvg)o^)<)<9;*mn(8mbDYS`A;3cqPnWwBZ zms?E9NM5{;{SMh-6#8uYu{bnb%h`NXd$QE-`k%w*MZRxRf+ZSpA(+B4$RH;s5uG0V zjN-ojxHgyeM;3<7hdcO+WW>8b(Nov*7JS8 z*Hca??9RRlr<`eCh3gVdSOo^YmnkEctfwaGf=)jQavK@*)gEZjR-tCRV|y#M;(pjJ z_z6Z43=DYM3!~z}70#bf(TGp&2iA7i=&Cl&DS_ zz&1~b=4Hc0uCS;4D2I1yJ;Tz=r)yUsOnx4aNXJ6cB7Xl=q94Z4TW{zv36~}|si1RT zTtZUs`+B+^w>yG|%+mGE>5toOW7$K&s-vJDNy-2!l%W8m>m$#nbRmI!KCtKXo~a(p zUte=yEt~6I&uEE%xY!*`fWC5F_j_6;RMJ~E=XqmWmM_4{JhsWx$>vXmB1J+u((ZaU zty_MXIvS}pL&Kv6UBm)!Ta{9{Z^U{L^j2)){q#5U7*3u7cQGl+uHwE@X)H>}gwVy- z^k`;Fu{+4&x`wJw|FLcr8ih7&+6AIbVF$7|y$)H7xr|<@0 z+dKC#3o=Q1q&UuFsubjZ+|hP(!?t=I9gR{oK#t~!Rvu8lF@YVHK#T1tu&@A00RDAb zDjLako|-u0f5n}q78b$YJK#Kmh*X#^B?Jd2rEY4%T>^3AW<@UTZiBYJ!{j^AiCvurloK;k)rB~->O&jIAs?QfUj2=J zb^1-l-MdfqMSQ>;iS@PB#W`Rg)G{NoGlPs3mTq$ygbwBUr1py97&r{SekFQHRmzB* ziB+^lX1zD@`(p3jU{qV;^?UrMoEh@ZpHC0~{<|QQ&Rv)AnRpR1v!>qYh{m;Ur|&GBW2;`M1-0N*UY>8*W_axhWj|Hf?D5q9bY@p{_ZmqBc8H{;?_F4)*w zxEvgv97G-mvZ=U`L!dM)>d!V+Zj>U`aU#MM5h_{>?VBvGo=eDE4t#46iPEEat>N#5 zGtVp_z{JT^xPfp$=(JnpCF&5QN)@L4-fe2Nl`1G%l^s4yz4Hf5CSt2pP83yq2+X*7OYD_U` z=&-FG?t5lhUzKsvJ?5*%tbllKYai1W1^EctKvdjS$9)Qntmw>@w#*gZ;~yYm(VooI z2o2CIv=(p2xsUw|OA>{?jpO(v9l6Xyjp*Gje{ED_Lkfx_2nv4Go*n6k5%%}YveR&% z4-S10ar`4+Urc~1g;$(q=L&Vt546>?P1|}T0sy&RF$}ms&&Ftw{Hgu;I7phHXz4KZ zdKHW6cSpB1D1lE#f9M-ZBwyF>0^=M&6~b%k#e8W{MC!}>q6_)qq+8!}lhqB}I`#in z87}a5`9R$rdA_J6`10%ivZRsFZ=v=5g1^IWFpJ0Q$Zn86L;)Mwzn>`-hQ%kvIv1BE z%Fji_PhP-Lm4lwy-b_$HwGXiby5l*m%`FTP!1=Q9w;7c{(01#`1Q(`FNmW@@MF}ex zSASO*0GU}0t;Ff(z5zUjn@VbjpUg)#kPL(+4Xb@l$anVELejfJ8u*aYCDcB)?2@nF zWge+w4#7fv>pH)Z1vIjP(VE;Wc6!G+t+=g19e*-PgExzyV~{#)V+aw5_y9!M3GrAdL^ahk**89y}h8{)W3e*4NTKU7~;pd094;dL5fk}5nc=(>mdVuIj%G%o2+R{p5QUvtWnx9M#$@80fG`{m^tol0- z-N~tm3)kzl)%AJ#>Ou_}rToKA3Z*PH8i;YA*X)OM z-j9*bx913O>^~Bk4N2YSOm0CV5foq5iXXJk zwe9UIMk50wVqo|8LM^T&tmF%$76gKZ*`=ZoQcR#@(>@jDhPj7FMlXZbqhZn*BzHJl zddoW1x73P4WGSvojqN)1Km# za^l1G!grCCCs7ZXn3QK`XOo0yUOnrwc3*a(A&iw(Z?P`@_Ve~`&hygPc)z#5w}04e zZKg&;L!-(mSR2w+LuT4=mGePLVqaowF;mW z!J7iT-kgkYuv^v-;t+_IRHNhKmiKSJ-v!*o|K58}e+Ivmz|`@1EZ})qb^FAd{X}0dQn_E}{rhI!x;x|huSo^<(yxjTnV$YJK_Qkq zxut~OU%krGM1+J|5N#@|oToqhRr8oU%C20mD#t2S+32X^!o0G)v(molS-M*q8=GAV zpYF8aJ9ki_TLb_h7GPyqkhZ z@JV=hH27^umMklqp08YnbbqdoUc(m`?+{57-f~`9){;=r&`{kPr$F09C;cK#1f>^= z?_>E3vvJ{ZPHuDsrVBfXJK)F!3~p4vn)L93FQ1T{h6&?HUpLGa*66RS!gA z6@#)&+%&~l)jxiBB;EFBFWX^)z1NL$_gDWj0u}j39}%aOYH=EH3$GIWN%_uct!);? zxrWN+()*jDTon;?$~kXsqnEk2zhBUC@dV7DQEfNJ$HxPVc#;S8O_y-$O8D_(Bu0L| z(OF_kFg7mUV=SZCf>VQ(l+;P;v*zCt_D-tnbPg`I&EX;yC^-xaDq8D(e|5O$#YfYh zv=o#1r&Ok{ALE8tF}4|(%FxuJb09gDiG4q#BUjDd&U!c%q%Fj|#C31Ex%X#Q%W{|# zU2HTbz$k?=QlAs~jQ#DI<3K!F%K4s+rJ-yO%g3582W@F=JIVa>VTr_s=T-xY9?s-* zyWwdMRs=&Zw1b! zjAW&!DOIzd?56NLAMIth@zm64$18h&wAw6flJle^GYZQ>&gg#?$%F^!J zz_BRHYh~6M<>jT4&qp2x z%=1?48X6cC79lE!F8b=Ko|6fQk~hy|OnF+J#{(K7JFrc7{wp*#KEC#R1L3Pc*6ngO zF_7giH|>du?y6*WeS~mdx#POu`@$4N)IRD#Y%hru26e+|z49V`+y(v2Cvb}t1t|q7 z1R3}OMc5t*JjzE=AqqfM?C59;(G?eeq88S)!}m2m!M~+#w4CFShev+pm_tEcS{@cI zcH2C)(Is`z)l`PKl^jzakl=p&{<}}DPh6fEX(;dW%CpcEp{@azFuJ4sRUW5_mwd#0 zsE@I+=?KF!3kyF;{e6Nsr8`;b{83w5J0B9S*wa9b{6w|Hlk_@3Nn>PeI6EngviG(V zvpW2&@@zgN`Og`t2w!4&yhsAT&8ypZQ1+VN8@E0p<= z?QKHEUrXwXeD0c*uKc~z ze6H|yw3S88csDrnWc+e$e)Kc#nn34kv6QfIKdDHeuJc>CtgWmJZ)Wj53{wh=49OtK zGTi5{R_R_G3HCZ{?NF4Jw1v(jTmTLU#~`A%5rNyTgph_nuIHAoZLsGe7kh|C?+WmP z6Shsa2NCrl!60uR8e%rgwfVBw?NW=a&214y(fyY0YN)+FBrTJp%j6>F!{&qI zV>DA=IypH#3J*HZxkf;i*xD?u@VM=)4?k|V+j(X}cXAwdH~kpwj@+Z%j(*&^?DZ+B zJXTa?6$TwKDz+bT#2jY@%M$R;h;@0TqEM)0g_SM~c}c7<_PbeLhb*?vfy6I$ZG#6r zg1TQuPg3bG;KUl2yCXjCtP>r>@yg7^hzlyEgG#Rb{pCBgc_h_{z4@~FCO2n0@4PLc z-*>`L(xkzILES1uf5qT+{kjPf#>0&ddGZ5du1KduS`aNX@9(g)y{8zM2J8{4Q8_PI z1xw!jNo5Itjgo)vE8%T3e~ZbtLE)Rg-Wv1`Zp+M3co%J9%}SQ=tsJ3$@Ax;F0ZGg z?Ad!wdaC}13n=4>Fd2c=VIpz{KR>^9^AOK*nC@?)TQLrpDt59QDS-x7GRrfcx^F4F zMlf8iOhyEbGeeu~-!Kb?Kt+&V-jZw_?SUyAaCfls z(QU)=0W*Qwi+qgvS{USL@efKU`RAcmD!6RB0(M{N^21biBOp5S#f=$Bk5ndJ?7?nw zHxU`vodftqbAOt?c|mP2$&zYSI3(k6JUJwtNN+-|B*t?fcAxbF@~0>k(?0US zdwa6mc;jej_m^G$)Or>E%{o`^b4PlJlHCs^GBrH^z+L&b1CYfLQU2WKsp z&3ucvm#h-nA|SKM>Q+AO|6!3LcU_ox&rC##Tbj(TdjW(A<5W0$BBRoU$mNke67Cm~ zzF-UGM}@_xF?n0B)zi|H!{qXiiuKJy91I^xe_GjF-xV>Aq!G(#h z?sv}5T0zGeZ}9A$oR2j?!W*1F);opK$;uOr+7-7m>k0K2huA1xE60JU{EhvH^-wzV zRTc#q1rXs})PnP`bgYnD~& z?jLRmr(+-=Xi0J*#HO5NKJWK(ljU(r-i_<%=+6EI^T>WUQv-|fP>=pl_2_j#GCv15 zJ8XaJaG$7oN^|gDpFz#Q)hX z?9qqp+{;}4ZR)4k$Wq^rc9Ik4l?X$)fk{1|1ph+?*f=yNt?@mHiujtbx5vQ`$!(%_ zwZzDbpRje^$4y@%ett4CyEK|d;>;hfw%e9gbtpIV6*r)MutRQ(*ZNoLS z#|sf3KfHSABt&P@9~mVA?sxnD?S89=CnoTRN2ts}e1@?sQxaP!Hxuxv(E!PhL3-$1*xJX0@ofP`_bJ05!;-foMAJjgWI7^orKGTY& zjQmJ@*H3_pdsFF%<8e*KDL^z@sKCNkt;=;5!3h=l8d%{S6Ll)!h}2zRXJ+F_Ro=B6Y*^*#!A9$DIab5n|*d!ci(TXtFU zkdv-cK2QEiMCN2RbD>|QBWmyIy9r=CwYI)%Vftw>U#kc7kL>W(-gP7;$X>2=Im>T+ zpb3iqWMGy=(OqqOExwabEt>X9&wO|GZJ#^2u4ZH-{WkBkKyAM%Ly!1I5+} zYgVSGX(^aG^~QwoC^|LT?%vT6c(7ljCZJ9v_t^faoyNtOk{lI*(c7vX~3Nl zaB0jqA}_`sy3Cn*XDYHZ?OUPK-iVJEOuL{n3oWe&82ijj!08jWR3r>1pv%li;1+@5!Zq$jCK zJ8h;Xd@CMw^!o$aD9Ez}E;{odohf$jBSmY^F9ho{Q&V5jo>0kg{F+v@Q$adKb(W>Y z{VgESeZXn&;3e z1-6a%_VOl^mgZ1d&fq|fC2g8^vbD|q1*nR`F`wocyPWGSzI|}&v0Y&{7k?KGP!$Rv zO15m#d<~r}wsMDY?}C!0Z^SR6BF;`yGCpWUk&yQwSy z06A=TcBJ;g^0P6BREmOhtJDofT^)ABc{rvd4IL=!4mxHb{vt^5x(6GC1|bPIqPu#?IMI6yl654O{LP=c^_CP+o5uj;}@C zI0rlX{#*&9E_xRm`B_a(&6qmZm8+{8=Dxx75gk1Z&Gmc(pTu(#`NeLaxev;S@(K#< zoUH7O(sj?mA9sGqm}VEVS_w(wk>^|3UtZtcOp2uJ?eD65TQ$l=6!QA*Mtc&9b9Kv} zH7A&4h)GDmx#gw4QVVKKZVa^Nv>$0E@2)`hsD?ViTie9T+d7#MAHIJ%I+Oc5A`kCjx}e=bx{5(()XAmc{+SzxS- z>g}YEyq}xc3E{o+8}xh2djp~#+^#q(F@9<5qsQz%@r76aG2Zhb&ppvlc>#do2zp~# zUQm}(C6Ok1wTjSU(2jjpMI=>mRBcQ*D0q5$TDtEPOla`%)X1Mb4-qS*O=#)+ExzeyHF%h@0Ys%+R_Mtx!)8-#ajfi3xGc^i7vG7EDbH z+*UjArU(gg%w>7{|J|MvimJSlXS?eeCfz&~_jgAvT>9}3K(Wb;-HtXI5tFm%f;M_5 z(RbOjl5TjAGX&4~*6$jKQZP{O89A0ZB}DLm|I5*5Fb}J*pc7^vGtk-OzYnj7@s8se z_oWeYrWr^9fdjqCP*oP!$e4q(f|jmr&HE{`uc}0o$KCTL!!L#xr5Fr#75h6{JtH6m z)!8`$Dik!|6m&b=*-Djum$+Q7fi;}cp02bQil#H4a=kmeO}chc3eUemN&{FMpqS)k zkK(@RI}&d9UjT55y_Z#bDxsuBx5}bZM}kEvM)Om>q_Wf)wXV50hsAVYQ6Lf}+C5yc zKPfag)W9vZq^e@w=TX|v&5opd__&TKDd~(N8!SgWTDbqoM1f@uIwF@4@JZe%fJ~aLE%8`?WrH>65@*2{@-3Vm#=Iu95+0D zsGDC^@a`S1)B7;m+#S+KTgn2{9Xb%mHhg*Z4NXM}3nR-_zbVE-%n6dr zgW^HVkdu?kugl4#q@kkeA$s!FDauaJ+}s@ZEhQy2Q2L8%B&Md$j$T{Sj@S18-o_ok zQI#PYkmXIat9+Bdt?V1CfqLnDj|O@>A*feQiAnjJoA}vQbPp9GY!>+B5<+II@M;WRPB0#5(R$E0w12Z(> zZ*MPS!)L<&WPo;f%q+~5*3@GYaS1}3w*XC$nqTskrI)F_O|%j6N`vOPJ5{orO2yX~ z_p9u+!x{X3T)JW!^1b)P#vc%Z2C^yxjv075A4Ly>>~Jizn*zuqM*_J3Y> zza;->LH=!w|Ce`2B=sY5$f-HpSnm2;!tL>X;kflj6G2|q_o#f=1 zUvYVK$NmL-OGhK=Swuuc9J8fbf#^ydz#lNK+c&+L4u_^}1bAoeAGe!4O#Auo5l|!q z(;Cmo`N3RLa1$j1`*Bs@oq@2FIXt&Z+>3EwiY$u#)vrYzuY|FovAo4K&*Ofp|9**) zn#}Bw)&AEwwW6?4869oq^ zM^a48`N83vHG>TxN;)2rWVW;f?}6RbK}-L%c80n+Z8yu)PN6jr=cjVdzBKt(7N>e3DJjki(LZlSdNY&T`af;MyIHI0pP zSp^zG>h?1%SDtG-JF+;MS!{me8ylLF41;nJF+xo>UpBn_ z_gkYfwUP9oLPDUR=yavFvki4S|BR$pfX}31qsib{h~F1)Kcb`*riX=f<80=2BQdl$ z*A$bS+%I2{mlsUR)BWeq*ChDJ__7T>2L}fL5i6ZQ0e3_fxPR{@)s5yW$>{0|DZP67 z3~jb?Ic3V64m$43V`-S+2_yyG4-_3z(GfJ5!9O#2%W9k0mCBmYX1LqItizccyOYm0tU%&bA1|}qAeHoY#s@9tPa6AO%tOh!uh zU5_qKYS;k)3HNRgXz1WQ&=Xd?{Z?WAi z@6Wwk2oT9!npPtrK`mqT|IRH-#pN*^sWa3KCuTxBg; zq`r}t-Bq+vs!AJu9GM>n^#yfZXyzl?R$4QdKibe#U0;*6B3SDe{8ZCfXNQM zaY=yPCP5Ipk^W>A0utzC`GtjjEacckys%9XE>E20-Aw_LE7`6enx38rL@Nrn^_3bW z_F)Y9G%R+vC-Pwe>wM7uc-{WQ;u{Qfj7y_%m2e>AZhtkSWi1sKYC3d8C80n|enI~)!sFU;b1+Z6Pg_~&#O2dMshUs&`tzmeK@EQMji>hZ?BY-M z_|i#Va+{-9)kEfW{J)EON}*~s-L2K_L2IR~MPI!rnylVK-@o~-&Zsl?OG+g^b)rL=XP)JL-qImO>ct}SM(nSOHR-7%I+ zuq`_$$IUjio~>(TGMmq4%HU_uk!7NCq8H0KOX>(FVZz72=GGW*w%O1S-s>b9QzPB)q0?Vb737*t2Cv+{SCrvXb{^>gw(ekfd*Ijq)|@ zL91`%D2B4((dxSHTQ)^W_RVSkpi&P$Ymwx|#e#@J(5WnTpeBbqWqfWCpr=b_cU(|LV2YyzQ8U%h5@+m6Vih z&R3Ch%V6zf_uYXJ2xU9F(z=8M6RS}9T|P1a+sGzqP0_ZdyWP=oxRAriXT`tM_+_}y z?&@71#gzP7-tCj8M8zhqVs`&{&>M#H@yXFb*oPkV!cVwZgKxjOHAc2|bTI3dZA~7V z;Nt_c!75U&FSK=uugwGEd_#zsE%=a;k?*FD*4r5faImo-5pj_SnKO=yYUwHrz~QHh z8NTTEcm0nfvTJNKY7k9?=5w0E_;WV@LVW`y3&ti`(Rot-xG zm-|3&sHTcGd#QF&2{qvS!eH#K#Ia|d58)pWlJ3=Tz98tqD;BFid-hWoHeYUjO9aT< zmdusq9Qn^>fO&L13f`i~u}D~~jrgMds!EtZNWv`H7yx49834^H1 z_4lq<+D?|DCJ9n@PR^lQCI>7A(-4*K$hN$)a?M!xiN7-~5jIhaTtdBDw|tNyZN2*` zDy*v~^%de*QXy#uzfOffwVAXTq6)^6@ea)%Eua})M(|v6QdQg~@ymdrawa_&NBi6d zl#4Qqt+9xTB%BjqMS2|*dt9}tBr3rmS;v)fzZg+RTKCzU! zg@v=}v?r`<3xU!xa8&a6Arg0Ck7}uKQye~eY5r3#PlS)-AMFMos@FvMJKBfp@*o>nz3zr!8LBhYXZu-^C(1sw;1lhRkE=J&*T^c$IW(A{{V`UzM%KaEYeUQ4GQy zH~^trbs(eDmyr#MB6-ohFQ@OXG}@GKbX!>G9NWz#D40y#)7-lwkb&$vdbA+nbxt;u zA^;0{yJ;m|W|6~;hx=k!{s{6TJ}y20PZ5aoa2@|3yHakqQ<`c3 z8@aRF;gk0gJm+b8X*0iy+fGN)&FndKJh|9-{V{B&muSnYp{JoXxFjYkG^YO>2w4WF;yblC={nXnh&wc)jGPArP#NX(5^BQ#>pW)8ht2^J*>!tv*7RS1fwZ5ycq^yq{DNVPHtNT`=DT2Wu z_+;cR>SQQ+`GDxz*V9AF<%FAozRI7DVZIjcW@y%iON_tZ)pL2_tggMQU}CnAoV^kE?Q_aQ%seVO>cuB1uL`xx7aq0%pSGh%LIfr;`;7>n zuz@7Eb64m6_P(p@%b~y|LH8S473;_e%b8qOf^J^kcl~j!GNqaefx_Za-7ca-Lqjm@ zX*Ru`FV<4RUOp$`hg!~al$4*nPA{*oMgrYpmKGCdNIacW-7(1o9GvY9a_G4BM$_Ij zOiccX*%u<#TU> zphi)d!iQ%`OJrKKR6s<#U7lRX@w=URsrtt{+uO4B0iy=D%dOR)gNoU&3pMMux+@BL zeU%Y!u1v;5pl)|PeSID_Qdi1|iL0Dn&KgA!dqfMe);@4d781z#NSbRsR~dz!jO)}* ze*h|~`#B19xqp>-?|;SvdR08krw~pCYZhtF>oS6YDCG;H{SwyU5-5tb>lP;ay{lX_ z6g?C@h1F!NNZ=0ILaNg*KMvGt1(Xp3Q>VMe$E zh>&jT1k8Pc!f99i#t`+MZ5OOndox(nQ7;4D^6_0ybm8=`rM5Xs`F= zw3thLrCbxM(+=j#kXo!c&LaeP#nEM?S!cl9$@` zxKc<$;Uzg1Ve`K$axWcCv<&34V^k?x-lc#3ykBX%!gjVR8&SNZlKRhJj{4AEX@ew{4;=L!F5? z>$Orxgvo&DaL38W;NWK`2f)gMI=r-u3^=TjGM)nTPfAKs#>P%A;PE<~)%erq=;&x- zQleF1A+nSRZ&p?&#=mEqKGM4e?>5+fv36RqdS2SID&&8tB|*Y&jHd;N-#Gs+<-sew z|35QL3|MFX+ehI4!LLP9XB>@=a&mBv!r>gFqj1y8S4{^nAQyRgdHD~uJP?Lw)gq;V zN?k~0QAkKgc6RjWL!c|GH94uJmHz$v!+#hUl*p8ZzA7px=;~^ud~aznlM`^ZcUFPo zutV+R@r+T!*&@7<-@oSNi`Tw#eL|RCE*s{jWboh0=VM2qJ*DelXX8-UU~&Ia8sXOI z^E{ZpkUulp9s6!Yy?0lIA_x5hfQa=cCV$A@d5obT9}eL9>|TN;_)aV_08A61K&myzk~?l=7Js|B&q>=~|z zad0lJzqQB_!bbx%hTgK4>qjH!zJ|Wn9r%jK&ZcgHC>DKHRErNXw=mn@Cf+dL=r`k; z?wbi#QVcZj+eP0c(E0dhgZz$n=?V%$MmOKYm1d~ z#J<^^aykqQ^znb&`aueRlqs9JrD1+ikzkv%Bu-9;G8$!On^2&O`|9dy4y%IbK{PW!n2A>(O=?w`#tj96DV7+uhb!Jo4abcK3>&;4a0{%2x~q8C+gO8886|&K9We0w& z@wD1Lv$|h0_wV^%geAKandGcgpep3ky!tlC4W1<@gExupaZn%S)OR)m=qC2-L{1f# zJ-53zeGYQo$WrF!Tgb?lK}U}{&`n2Ek+`qdD+}|?l)uBT9Je{GW_~09?q*)}{p(|Y zpr?Lv#7MR&s)D0npl&p;5IfN*2Z?`bI+$COa(7^x@$#n^z^#F0Xnahv*lerpo+p~1 z?a=9}ZBqq^;C+frQxA?e&d%O(Fcp1yk0(>I>2*lQhsM&HkV1mnqD|I~2d3{n&<(u` zMC}!Ndp)3ZXKrDB95Y^40HfXss*J9*^QAgDJi1EPg;_hTC(kNB5&@LojXt1pE`K}} zQKTzkhSzMybDFL3xGshP$i9Rg8FaP|;tBWOY14hFdJ<8Q`D{7Dg!7$352P zpsuW`e%~L8Yt7`>*^S)uv*2?cNarshrTK{K`6z0r@V`P=P)tSp5mWL_Uk1G#qdHha zK55E1cYE8$*x1+UI{rLlI z;n?l$ogKHw^3b>;4X&qvFDx4YfsosJKD<`lRh;*>5L&>s`xgH3_~-}+*&m$3ye?}F zbSXyG#$$yt-1Ig^W?|9MWLS8FMEFj7EtkL8POr_4%qFu`2sC20qVe%JH@zWf&oDeL zE*7^ZG`m{hi3Oaw8uP%uXQ=tTgv7d>h=}6n!6BT^th1=Cjg7woqRzK#r-_Ll;}4Dx zE#T?i1%V761pfZd&dCM%-FN&}YUh%5fE*Ra`#Ohngh2L&tHtCm7hx}P`H+xJLc)jN z7AEo{0I>S>k}8GPMcyw_m)jYa^35ABa>D4l2c#q<5H2fmH<6m|+>P9Y!ND>x-{mb; zK2vx#JwirAb~aZe7mOZ!b9*zf`6q@2_@2e3?-!}mopH`u5D~pZ{qg;`-@z^)g6X!t z!fdj;58gM|KQ}hT%gMCs_;Chqpz>r zGauJHz|qeK2G(|0jg0iNva|8Y2;U1$gUlHSq8|p@h63A*j^rkXu)F9`=cF^MDJ@N4 zNEb_;lMNWr$G$!v0r+pk$_69)a)8#X)(WFtK7$@!KLnIMxAn_U76t~XHZMqVdm@}tNv{cZ zc^So8x2n=<(y@{UFQ}1hjQSaS!2kB3s;@~!^$!!Mhu_++S>}7oMCYK+9C0!j=+CL7 zjE%raIV}z6bsF+~x|vA$KfU6XQ&I^XaFr>Ngj+zNvzrmwrKC!BwGKO@*=pPEJuy=1 zh}(vc!`t)WZh|gLnSi!GFt}dZr?#GHyu2-^yE%Gv z1rxN}c-Z>WVIp2YK-0Uf4CO|miaNzRC>>vg%Nx7giI=_@=G zs2;O3NqLbv^3lUKL$6YF@75wxkLY%0g3xVY&q8@AsF5i1lqZxZ8@ z)PKIzzhbDt_m=G;U^*;RA>pjBg1c^Lctsa##T~54$#FZ2R~9AJ%D&|5AI-c-sQ?_^ z$b88gahthHBM^3XL=4^-m~>WQtc51?cg$k4v2(^BuxHYiUaF?WIvjA$#Y( zFw>4EtECjwU=5Gg-m@_n0umtP1byoS2WKbA>>k-=W$aZyg1e57kI%^kC55QMB-?PE z>Ss(Z7ScY(rNFLNQ=oipf)-on5p<c(@~nF+CMDLy1OXgm3+pMb~AwHuA?Xk*Z8I;9q6XWNcXW zH)3B}`+FtbrZn9sm~%)HjTTf{jga4%{LD;W2!2iF`Q=p(7cf7nl9|%!znK(YaRDWt=s8KOn<^NmHw#ud#w)olk(a1D#%3pBg@DtN$(c0JUQ8h zOIW&0%10*BWcKP-F&e%5Q|sxutx>c#eyZiLYV)zIxu-RPu7DZ|NoJj83*L~U$szeM z5sE(mNu{wx;;@=A+}=;1%6N}|>+T7;lL2X@eh*y{!tr(hv`iU&GJR)qg?V*#6=eCw z_j!VV2elB<%1a_5&+}7md*fbxlkM@s9?t}aW261%+Z_?!#e(P}+`uQ7eBwek7u+_n zrEG8CYF>}fGZSULv;l5;kuhtigM^SKa}4k~gJ!_b@_bDfX2SBj0p;w_pb~gJ`$e&k zboiV*&ewYcHUy3Wa^*@+N{526Rz4^Q9Sh^(g9pFCA*YF3CPpdY$5d>l5g3+WxOkA+ zf`N~#T6@(t_|5CmG4-UpoS^wVDEC+zh}j+W($)I+lN*WbWoAO+`X0E#)WkSlt7L+`aB z(`AmB%8iE;Q1bKFLHOu#v5fCPAD|d?s@)F{O5TKph0Ta`mvCFF zeyLH8F1FoXJ28isBq_$WHBN&pIdrO>yM|UB{rNNh5dXES1edFw$q3L=#Fq9Yd@tyW zE3UIuEn+<7GB}-%hsU6sgTA9*S=DKMZuZs}lYhxkM8*X0_$?F+$RZR?i?vlbF%zM= zD!%eJ*90>k!k5KE)>srIWm93#Hm`CvBB6E*2Nd^kBmk%aWw^NDPM8V>xXq+%`$Kwm z8~E-#RtLtVis$N(`BdEK87mL;Za!QkBpf5w-38x<#FrZOj)G7&T=v2`9j!`hK zZlB+QTM?G%k0XFA-F$XbL_5mvAyRNgg60snje+{)aMA}BhktdRwJ+VzWC{EBZf03>ED! z3;zxe?r#>}GI1u-EoCFOuIBZXyQ#mmK)0~q73z` zt@y3@z^mfSmK$$Xdyj+K-x**^%Q8EBQNz2huMax!Soof`cg_oidLXd__Uboun``Fe z_>V9GjF7sM^PKp_>ith&fM-yG)pS0F3Tti)x5*;Ev_xi3&Mcs4hmP8BoJn^`zFZGR z%b+Szic=7dFKx?-w2(upK2t%Y${8X?D;@ov^4ZsJ6k~;PP<1;y=Bgc+W9DJRm!~iH zX2+NyN^B7cw=N?rD4`_j;U_h?)=C^L4Ptmpp!D?jN>RGLSL^LQV1_``FD}nbwW;Ux z{p9b4UQ&XnFLTLAi14GsDsjgk;^0 zcAi0dPnZ-E1lTD#BHgOg0@XjhlAypLz-1M4sjmY^XuIOFMV0)A0 zqCUXeA0(dNLl=FjOAXT7H?1&g`};ywYxI4gDgZHA1Ijo_nnkiQI*%nMMT)1|o0(>< z^vJ_G=;MO20)6+we~&o$Y|^~7m?OClMss;fNEAj-^SBbjB5Vbe>sw`QI(b_m!Au^f z&q%3Vct)`xUzoPyX*D7>a)*tcW_%eyQT}>(P7Wae>Gbr7jJY1BPFc~y?-wP`m$RQ@ ziF!D5KM^L@IfTt0ZZ@D`(wZ(BGkvil+WtZS!x}11&+G4-?^n@5nK-i z2K@p||AtDFvd?B|+j4{_M?0-dC?V>AFr+;0(`qg9ecsYa`@?Q3gr&RtO+woeO$DR0 z)h2y3%JEu_0-u|R@rGBPWH|=pDyH(6%ID5|bE|B|zZM3U zbbWbPerEjbo|=kJ5OCR|Y!cvtPTc5`G4}RZjD1OfI{aoC+OysN)^MZ6dwC-nUR;!t zqA7)76yvi!Y!_&bPl%J5eJa*J&-7UU(mP7(8`vL={Wb@@utgzAfvW=`k4^T4~+CQHJc9(Qma@@)HEZvu!EzH8?D7Dx=w}`Oc$A|J=&U7mfc3_sK z(tpdp0*k1<5oU>h$9Boc6dClSODGh9WlRlBccZQT)&)2%kM;4Ot!J4S`Y|~>+b5je zt{9;a*n99yO!NH}@~f|m##dpj#%g_s%vOsit1Qu5$!4na#a`N3=`HTGPqO;Ew4nGg z5RX!dE|HdT?^pYJBp38G>$0k;mbH=%rU$jaiM{&D`-bWa^BUf^vu2fe) zm6jFJZ#f^yKA6@kr4=RNSSTpGyY`Z4?ka}lFyDA;(ZyF(MB9;0_3UX#VV^T(_pXD3 zu6mUz9*>LH@psvJRkDC%QQP0FCY#B6kM$;fn&`9Np0ln1n3M82yt^l~_Em4cf-0aN zi}zkD`7>VEb2u~)nK9_sR^5{m8;-9!9C~J!OcfL01Pet;NtMgWiZ1A&laaUf*OXL` zZp}1&)L&nZt+sAY(2q&VzO-{`AL)n%DKRR;XPCzbRty=_>!u`p=Ki3Dhv!D?^gJn$ zUk67NYSzm1S&vHv2;2YKjlBHCPXCUyaO0U0ulIAsNGkdUVfo7`yB;A%H>a-3ysP@2 zuSTi5>E}a1&V9~e_4C=IKJhzWc)~{`PvC$aKqup|1mG&1^pz4vwI`g2Sj?J1l8uc+ zo5Q(r<$}s+?$%4;U@Spl3Z{9>Wa*qvI2#mvfE6|Cb;Xf{JXpE-5r?TmdC9iU0 zX+Z@)X?A3NfHF`ELco2R30Wwn+B=bukQmT;JO1ZSDxg!mnusi|)IK&Ar3iU@1B?~m z@1N#aLj0LdrPcySB6WKqOv!!#0<-=3UPK$QbXo0+Af+1FmB$ zO3O(OCf6-sIs>%d5dtnm7roQVyEr|d=$ri3iQnUKQ*T1J)aZV;J8XN8RQx%>fT5mv zt&a7h9I0kruI&PSIt@;8J6c*}pw(hStl6r#XcomCfeg#Vj-RbrZ!+|^d<)1(RAqm3 z3YF0}rmo_yPM&9&CMM7T#p>2WnMDGVl!OljCWe{os+#NR&COJ7EdT3kPY5AA8F^8hLNgp!^vDEZSwnpfAcidArH$3QmLwmMX;pM78I=B8rpj-&Sv z3|`LOz8Yupu(&}uCX$e}htou&+d3BZ_jMdxonF!cP6L;hf}G%PQ_;kPrs$DWh<6Ro z@q2!ktH}9p!NpY%$XAx*m6<{TxPE;^-nRTJAF%-Me3V=@YCV=~PwA$9@ss+Z$A#UiY_Y z24S>ZsEa*}v#PZ%C;ku*vD$CLkrbT_%FLhWWxp7{YpT%(?J8~#yZtPRW#WBCXfa0; z)s4<~f=yoyxm_cbW8ZG_AymB0&25@a_?!dw`?#N(?ySFQh;WrA_=`iPnA@9D@m9kw zdh@DpsbmOH3r}F2lIE-e0s{7v2GzC~oOYl?Y~b3HT0Iq-PzmL0AcY+r!I$`+f@+gj z<3+fx-9n7+5j!T$K(eu5erXb|xBOF;LtSUOy%`4U3ptZUnNH>#XHdkXpHwdS4!my# zUDRg=Ot#evL3c-umW9RkSz#bbR<*}Db4+Nh+rG86wJjyj!7+Bsi`$o^hWr-Jt5ckw zPkGHJp60c0pg^QU zhM~K=LAtvcLb^e^23W`FbwBG~_j=a)y2PdK*pVTm@ z$7vresHrhoG-GIpC{|$wMBh{_C&gD;czgSf_$ySLoE>ZrRVFqSX~omx>#n|GE2*#B zmstEg>PaE;4F?`>-U{qGxA1McRntVA1Tj zO~_h$DwvRKVtRP9d0F`K($V&M%Zj+n?G-?cgUz}@7DeTBwej3dt8c27AZP?}vN19~ z3peXZOiCIgBnqR=0^U=r)(NMv!UoszxKV?_9}I$f=6elJ$B+|C*$9){kBedbhmur`-PL^b&)1p>+I0{N>PnRDz-wR>?|-{=fXfMk_V zSSBXaTw%^<&2vXD>9`#(KTyB*ps2mqLg)~Ax)Lk!vJ9cQ1Q1p(OvekJk zF2@&|%xTV+CS<_cSl467Q@OT&0-p$!kbS(lxwyEw_{QW)VX)5bXg4$vSUljl@hK^b z=dwB*?GVvL@_9bGny?8&6Hsj)Xt;vi_Hd|wP$XZcQ#o(aavdF~*s1j_<>jii&3Y+M z3XT>=leK0?neB5EXIt|Tnx#E-8YRxxJH%tZUn7 zkKDsfY3M7aUsQHcjgUk2N2}$xL+%)6GnWl`ccGp-{3>X^RRl_5WYZ;u4XKAoF82MU~{s0%9>vCeIZr-aI|1`X=gOr>%E z0O+eBKh$VBTC}qRY)yEq#wqqzxZ%-u-ZlSKoSU{@|G{-aM!`j6Hhh@z0E0@&D2;ldz8&qn zhC8>`Z2r_h6KZwYHGQx^oB0;m*N6I3=?7*>Hv9+Ex9?f!dtFPYqu&j?=|78V2;>DDm^CkC}b4!I_rVxpShQ~poqQn*n z8w}r)FsqPDNJxpM-bE&)#9R?*^2Y&2*>SuHxiPW}a|MO5;JG@VU_4s|RWmD4U)dON zlnVY9HCICl&yU?{rjFZcm`T)XCS-%fybC7QBLW%^Yh`X!;1UjnD3;ZY-Xn(H43-b zus9j=+0(&=+a1c`*U=v9biNpw-#oQOGa|5W*U(A0P=a1jo)V3AD*C+0*m!h-1WMiz zSW3Vtu-C$Ua$3^gq0WFhTL2Hy{DaWY7~XZW%LGCo&~!IdeOwP6Sm`4)0P3>BmJD{e zGw}1Iemi*VLWhYe6~kmX3FVS=yU8#9{Tn>t!(piRKX%)1_NE#h0kmeRQkEOL`9cE~ zn<|C;aq=>RFms=+ID~>wjA&4+Hk1Qzr`2oLGt-78b0Dt7_*ymRiM@-x3p(+)AcJl&uyOq%{)oTQVcg1A@R2#8nf^fXri|Z26Vk zv;{)`t@V4jp`jrVFx4}K4zfk%NJa{cp-zq>!xqD7HE1%jklgNsZ`Jtt?)7paXNKL;%N z%jJMp6m)zpxS29LCZqjrzC8sG@4b7T>`>4%o83l#!Z9gLap zTe6@Hlxx+UUoTqGWoUCu)|X_-OUuw0^&cx>MUY7?rGn@@T*JwZ5!^5tbIQqHawO+1 z*M)O6ZS5t{?|qNQnMgwHIq4($@CcK!?CB=^h06LAWT`}sf_#jKUpg20(pp!k&$K{p z^@2Dpq3PSA-q)vyo4@JpE>W`CG=xt7&MIDb|TPAz~%{ftK`?fruFMZ+3J3QnQCR>;+Fnr z!!9i-GzuI4>ar~-;tFGrPVGUBOe`?FXjI!+g3YdgpfJMOlKgH%4-<)7(XO(Rc3d6s z3rZOnJwJMYn*Tk;0MKG0%lE;xJ?WmLvx36Ajwkn#kUm4^ddV5QkbdVl;^T?aq2v|{ z@GY(8DI+Qc+3#Q3fo(H*@e2Mwl{$=jtiWJCu93%~SXEih!pMlFr=OXT@hHMOWibU% zSN__Oz8?}1%a3jTc{c~}ZrGsd z#W5=n&us^wo9stDN8j4on3 zq~Z=$icE4w&g%N^)pfO>ex`o-s-&a@;9!hb@V{iieFs=lbRRWG1m;pRbG6A2He}0K zBmtPkS8b}!#KQj=!IjV#YVyyl&qzu6l9x%-iGiP{ggJBwtU@5-;>Kuo7Z3fp`}?rJ zA4cpRPD{^Q&bJ9xmb6mij`q7MGNEp}6DM-0@$qqP^L0&`nH@oOtY`Rap%D?1XuGC6 zLXTgYf6%FcBS^=mPoC260oMuXO zTH+NqlmQBm0BERa!2uyUh^q%CN2lAFyS>zY}mfBt$_o zGczZO&F_^Qn$G+_I3iP-R@n(R->n;35TB&89R9qB4dt%2D|>HeHj$k(g-z}dqa4~~ zH+o6MTJ#ndOekR5@Is7??i`9dHx=vco85Xir<#dc&*t?9J*l(cIq=M0w3Az|E9U*K zX$DV}zuDq+fWfrjK?;C2lvIVwg@-`7Nh?EBhaLcIg@6X3+l>_f*KQ5aGFfiVo7p|e zpaoBXkw+>=Nf4*=>5m&+JRFXx>3{~?%NC#HL(DNkPR zmX%ecO>R$3?zA8XYi1%P>Ys_nNfjE`9vsd_tl;xyqj8(6!KVqwdtqnI8e*HS&-)XsQJlWU>W2su($H&JzetZ~*QJ0C>6WZoWN=EkA z(I}(xhA4lGfXnH4XSK}eMW(8Xs-5wuJNiRr=6P3>=t6)9>JG2f2JD`xaJYxFN|M!j zvj*zJ?aTABJAg`<7MY<4fQ^xAlhq7yao&V+QBb?(JxLXq!^!2JtqpMRVSvd*!`ytY z0ygPrx3~Cty^B^<(kXts?rcL-K;gc;dDY(B-b`a&Ntop>ZUx1fsY1~kn#br*R@cu) zMn+;(GTKFV-q7wbW98k zTeF4o4XJPL0Knl-8Xku|1u65w#Dcjt`UTbKog?7uZZudY2=st*xd|OW{Ypf)_00@V zyUWOG_MeB>Kj9K}ySK%Pd)WT@2FVY13wLjP4#s5Bj~Eq5Kgo~>!5q&#XMDOjQwOLm zvg9J-;(B9aeG(EuL4o6AV;ghv_7+?&2Z2CBE;ae#P8z-%n7-M`wN!@5Q=aq#n8hJL(CdD$>!a zFdNSF$*aquZx1HjIXq`Q&T83-Pjbp>2k zz~WJ_v5frZM)}7@6p*p|=;0KpT2CG&Dk8H8ow0RgJ}r#eB%q zjupwhMciX!jTS|Yi?p?}GP3BgLgYMHl$3;ggxG4>!iI<0{^b`|;6yuWDJ0R+e!D;; zql4MT@=^B<|EC(6N*V@su@pt}K(~k(Ub?k=K+pGcxc2|&I}Yp6ltZ>&lbLD2d{m;cIAc-E-mBz;75Uht$`r{~d(M zxiSUcWrT$%hkpJbWdM-|h7mMWF(h-jV4Q9&i<%BHj=LSUC@cEf zrlrQ4(z84Bo>w7B?9B=c=TV41227WT`OHR&Byyt zFK=!{=3g6(*Ulc|B#gvq01uqZ(*=hzGup;OPj*%*1>s_x z1_xtcII`D@QiuXG@@)Ij83AV~^$xH$leiTw)Ju)K{F}3R*GyZx1wfde5OT=%kb!*< ztHsP%MqB`xM`QhrQhA?VQ?3CK`B~VraBuKnf__kXpFjT`*xKB#Fq}wmVr622Ohl?v zCBd1zi#ziPSpbkD$qQp~#qdIKd2LS0?MmHqWjsRxbD&j<+zF%##dc4sDSii`|D zlRrgmdD~L8f6gjyw_lB#C>mT*VH=NK0H&1JJ3HDs%YZKFb~ziHZ(0*4A0HoUyFJoh zP@5N-A*ia#o|rULTnt3gBm0rQzOm*3Xkw$yYB(^)EqJ6x*M7`pQ_fr4>Fe+4Xm5Dz z*iDyQP%u}+Pi8?!M}7(P6r9}mU)E+mEap|xM@F9Bc!NcO zD#S_b+>E5s1)BYWhXr13UhD4eO=dTVTeGttbIoo)Sla=+dHAZuLPMQ%e@{$|p%4aU zXy9x1dWTVc1-D4&DvKg+1G)sQ5`9H05FBPhe!eUe^d$>4JHmd3#cZ<01V61T4YCMQd$z96;@e8P+R zKv_f}BEplxp&&Oi>m>-Y`s+chYY!{*y_qg7k(EHhdsP;HDsU68J1>Z z&muZtG@02yD~Q&fiW~LOAY6X5p#zW+z_xCF;0oJSi@)*p2b1Oy8zLZ%1U^0{j{C4! zH$UfOY=m`7)#kI-ih`k8V}1exzEj@t?Zu{hSJg&>bhd)y`Lfp6J>#=)9^P*OzUU~e zWDxLu(GkqZOtBYV{^k1e*na27aD0{7bd3RPU8vN~?`HlQ2AEz9TGD20wk1|R^DvF? z!q#}fXtrw@Sa?_0wraoaeMFsai(&<4-|Jjv@I(HW(hJZA*4xXvm5EQ7?lR=1j+@fz zOd@jf@;Eijj~;$vcatsJ<02_?2Eo+!BW#hq;#7lh#XS82t%-cPV3Y-fow@O=k+CW+ z@jWx^Ie+HstK(H+Px$y`f%<5f+7r8pqF_1>zSwc!5Q5TCf6XobRLI=SDpif)duAHj zk-i+Tc~o}~55>gsk={9doF>TGl||Yh}g(Fp1<1`d3(w3)Z#l&wqWH zrS=YZNvj3?Vj{Y_@2WxzacTuWz5uwSh07V>aNA+uA4_@BbbaABN*0@X+6{b|i`XNI znAem(u1d2qOaN+k^#uPjVj1hn@@K{A?@gOYsRVT!yGjOO=x1w=n*Cg8q_1%Q%q){P zp-v;tpwuXa{r+r*C~pMlm1m8t%52eH1ff$YNpMmaoa~Gax7{HR{x1*jpcBva&~U4Q zEJnyfnEmhXzcgNS&QuuR)|z`34!}0L20N>cRd@c=M|(AKusDBsc%1qTG-N?D(S0o$f9P zPhON0y4_nEn%#?lmtUSO?wc`C`g!=CG?{-pVCb6Y$dn$`RBr)?Y#Sx9Kz*bWL@i4;3%6>NG{2gwi1Q-Fx8N-j66scl+y^Vgy;~;VBgrtSJOh zI3`;zd07|?Mne^qHsqpTz=~&^n=MZcvDDd_SXu3F!oAh?!%t2&EKTbypJTp;_pkVw zI_Zmj8;EDFlI&I5QIjW?!u{a@D-HrRGCLS+KNz>nguD`BO zEBo-oLGli8(blYmK`5u4e*avF9X)??ztUvRC*GCdRo8yCcW#q#FyXR3tkSr(%YExu z!R1zG8~YBOW6JmS(?pJ0>Pd}s1?eQN__ELQz|}J{Ito_4tL4-Sw)*V&B+Jy-QeXu( zk4lBu;;{k)QhIvkY`yhWUCzQ0-lnZ73`#-R(;*Q9-vvv6;T41;j@uyWeNMH>3#Yp0 z=z3}gGxtY~R(Oi5bk1n^-1*JS3hYXysWob;CJVQQ(t4}pNeicom4=1e4Skyu5AhTE zXBMNI`2e)#7mwunXAsbczL2nbhOrIEO6=cOLS6D9rnE5Nu^x2Y*qt%|EN+%?;U2QV}Mxju!Oyh`l9{H?o!n?4WmqH!Cy&wdClvQE}V(WWSF zPr`wV32`jvtx-Jq9?IaQFsY!dJSnFsA+ktf_P$6aqxY%M0YO~q@6COlN?O1mQY6X1 zKK=dsS0>$S1tpV=Hkf!NHPv5;8lLvY+ z=#SH@hLORwBahGJDX%4$*9C}*WQ!OG^=F8CL8za=ro`&e{vQ4b{JO@s*)Y{?o8=f4 zuBn(X>9k99+y_jBfXMcFo!1E4m0!StGun_R zHY%>C9*uKsJYc(@c3}Lxoq)#yIG8>Oe28vgU}Q|Qkfi=C^Fy~P;V$37YTE)m+12w9 zur%J>Yj}mDD>#Gmb{MQzu~rXJF`lc1hs(_hXUh$w(-b|3%ib`Q)hBf;5a836vnmCy zDdOh1m&>y1HrLKZ_xjaTRApymtZLxVwQ%wmQYpBsb-;`0bh;l&O*(Nh5Y~|45_M7c z;Mo~I=X=ASM1{UF!MihNmq$_r-&Q9M7-4l8fx;m}bqBHxdQvS~#BCozC z7qs#{(lagYGZ^uYF2*7ib!ek!Q@MO$J~4915Ggo85q>KdtrcQEcTsk7y)EYQIQ(!v zaJKqkD1$Eur2dMkGE|zvodBnWh0Eiyno+o|L0_!x)t*KP`iP~i>Eulj&kd$YDF6c? z$*?H$5b2^`AxWfcRY0Mx?tyd-|EdMB;RTOuIo4j5=P()FD~;3w=1o$&sCqjWXKkIj z)z!#{u>%?(cx?K9AmADLdj17dfNB^i?xvmP5Htq)e#qI2O}kXJCyx2BaIU)X>}O-5 zlU>u}M^V-s6cmT28L<)avkp0x!I0dhLUX?`tnl8m$c(Js_@_!eeqbK{(j$C>7-D@r zi{8N_7-~xIi>R$MxAc_ily)sTTxe?43-T8Z_%!oW0TRNb8qArK?l4^Z_Td*~=c}NB zXj3AC3UG#^*kzTp=3An}n7*D$XR`$GT zZ5uy1%BnpC95HQeZ9|PhhFY^Lt<}x6RNG!9`1?qYpIWZBk=v>|Ae+cMw}RUz;|k$l z&IGt10I9i8a6OOw73(~XoJ(TE~>9fW`xyqU{-+K7hujgEK<;J8L z0fLP35k4n)U(5VHy5~1fkb_=fzuXsfgt^G4bzFl|r3A=vB~}FQnirZ&u#M%*zTTI8 zU2VRM;<}$a5j7E5>c?d^RqV=Nau_;XVh9n$g4j9djD-UFj0YEv}P^V`hx zp9q&_SXb9>Th0=#@vMdh;Q0R14>@#93_AvaI*{K$Z0v6H^(zjUy%2@5&kAHj?_y(d zj09PKfuJ+`anRV<3cDS%tu4O`zp8g8D2oZk;z)Qu3VFOI+{(5678J(!n)x7A72jflBJouUk3)!ysq%{e1*XcBV6~)JrEM)>H@Hk|~nT3g( znlkspC=8ep0hKO?grq4yB(=}raL;qeBt!{^rMm5FFz~U3+iC+tVz(r4VxZp`hmL83 zH1pcX^AKupT3a@=xf|uLh7U;&l=9Uy>U}da1J(!D340?=CeP0}z-0Nx)}-8jMT&sa zDyB4lr`*@q*L@L2%dZ7U@x3R*`@#78kbRKhv zD|GZA+khVesXGbODuX6Q#>R5YK%z^caKxo#rpbUy+cmsYJa0VTv9^n&gaByaRsFuf zm&GrbDTXOVK!Wk%mKwC;Zdg6yqpYnGoNq7!X^--VrOT+ygIivgbgfA7D+~9lpcoW8_vH% zT3gc}C6i=x-q`?!E6nDBM7H?66Ut}1i@=%0QRxIz9Za*^1<^Wa-B(Q_%xWe3>!q?A z^=@F0a=TSqqCSX+tHi`xs5jiS#mb|gTuvxWKo&rv?}kv6Au00a=1OL(?8#C3c}_iljC)^XTgn)Pz(G8kZTS8#- zhGl!$w+Vn**6VGs(W~v@W!L#*G!F5F<&ua_M-ampU@^k{eY9nJyElIUZ?{f)?{_nt zy}UbDFIon|T!~FKmi;~za2m3Ar0Pw!4SKSN3s6?`&grA|wo~`L2k3ZAD;))DpiJg? zyh86ZEC&H&*riYckQEShXc=xwdrvk{1Ex?(&N4|p!$5WdoOfhohYP`Lq1-(Ab~(>f zzDnUM4-YtC_sMA&J2!CmY=Wrx<<(@ts^)BHUxlNs`H&esAQI6jt!8LsIb-l-5c?b| zPntL4E77vEJN}oWl8Fgm1|K)PYENWyJ3VquveZF3Mz{uN>%gLm_3TCAm7Yth<4Mi; zbPzjn`)-SYtfS3kFkfR|WPhePo8Od_bXym;ib2TJ1Bw6iVR7Tv2ZIjkZf*DHI;PIl zdK?j$mmvS{U+2chZo45ux8{P1iVDDHe|(EHTdhL;{At2fd)i#ijnN;Y9dYtM)%h>6 z)9T|5-5%9F*|QX_2iyLk-RJA}G%SGim=T?z#%*&f(i>jTyy^9t-AtSG^x2_gmVI1Iqyx0aXWJ5o~S9S<+2oxh03FY0{mEumFvi zL12@R6j#@ua=L7N;F%Mq*mLhs0a0{{Hrebwhy7~x-;=h}yjIrC+YS+i&CeN4P&ET@QRwq#j``(B4vZOBxdXRzeCJV03^0mT8bK ze_!MUQ#^$Fzq9)tI#vD>5ER^e2xZ_wjZt5Ok(h|5$e(|v)oN_uo zAO0D}zoG7@2MZ6604!iutJ#=cj@wA}06VL#{7v8y6eDMA(fp=cy;8iR$G_M%rosq9 zwp7geT~GZ8xE$)87Bn+Q$YHV05uxwbV3xPLFsi4R_7Y-mmcp@m-BioOwzBsUj-qKF z6EbcAdVrkoaeAm>so3xDyLsQnzK-II#0#Oa@@k&dF+D! zP#}Azj|?)*FQ)vSUw0;OI;ocS6>wBh96*Wa84MPMg}p{M!PtHCXAqhL94~ZMBGx16 z4}`uFDu!AAg;x~NhWh%mv5k$jO;dAnBFRMnsQ8(mz`lko;CS`6!ltIOiJHDs9^Z8* zV&UFvFyaNhbXhfF3c16L4;^z1g9W9PC#P%TDk8CvU4c(p!APGQ6S(uX#?>Dm1LA!n zxM@+=G@ucrDzgcL_m@8Fl0|GtI|%gSeRp@&rZ3X|*h+blrUiypB}=7LFb2d?@tA3( z6YQpz_b`75-~cdnPb?=jETd5Um;(fpxhL;Dp5uCi@hDTK2BPSLj?30Kme zL%?4Fr7aN=iO=ANNZge7y^xRyr8G6?y~SU$US9|+*kB^=+wT&D_bmGpPhaBl=3+DE zd%)>WOMNSjql=gve%<5eeTXDe@*LR@9Jc=`LX?EIe?>hch3DW?{(1fX4i_I6*ds4L zIW#7ttUM|subejR0dKhlcE7dtb%bvbycDCiP`p4B1ooL)u&&3&smh_PtgYz+=}^xP zOv;G60PX{&u;9Iq0GP+6!NpwBf?Wndkb`K*yLzd_9$}NBjWaQ!84U-B`3r2uzI~R~ z@#mM26cD6wxj+L$!}OFwzENN?wUc7eyQ>#DHar0uOEC_yj*SnCONw{*_Wqf-fB=a) zj{4Bn#s(;&hHn|E7>S7+l9S7`?o-I5D+}rSG(z1Ag9~%~R8&=smF!6WOPiIUO9MwM zQ>K`>xCAIv)sd--qH8d#1wxQ+Bt`@F+W-2h9=Z3uW>jY`FHwLvA==#n^4>yx0Kr!N zw!l2uf;V3K@8{-rp?vlfToM462fTI2IvcMlJq?#P%g1=o)#NMwpa1l`Ti);c(9q9M zl9DuB#F9$4^j4rpVM@?YX5izXf621ypmhycU%=F0Wo2Dj3K=4NZ040JHbZ3g=d28# zVTj^&W(a7tc&rQw8z2`I5~?jJ$teuhw6nv$hqVk2*v|Fa#iO7}VA;z)J#Kv9snv3! z|5;Y)o0jI87^LASBm_=L0=jJg7!J;b14vz2pfOZg=Jn71n~O6g@4}?U{|2zn8wV(q zxj8vTM#iB2vs` z{d1ij{eM+}#kdXvKyD{}3)w5F(4sSVUD(r^Oe#?qiTelc9&@$Pr9rVz{8c|k)jw|w z$N4BpLvtCQn6~o$eO7i!b`?1DJNOqpldv9W)6?(BqwQ@dY+xuQJ9zhjBekG4so%WWzRgTu)BlXD0 z$Pl?L0@5+)TsWLBTGrO))vH_t&_^I&hlDhuaPlVbC+OGl6iHNoPvN^v!+Ydh89_0E z|EU;1=o;)93FmP-xl}~q6~$~Q!NSEBqCS*xpc@ociyjiOBYU0 z2;TRMmRr8Kx%dmNXKDIg6RT21I|nFW!2AWg<-!7ja;xXF-bX6?mp&N^(4vqh%9=z^ zxgAagcH{wPFOJo$qdzk}o%@@@jO-^>$is&Ka;wT@G9b0!_M3>AW*h`p#xvOKRz@Ow zcLdPw&$zi96|L>`QO8_W%P2V+5}LwTXw+(IYNuh-Fjcjz%#4QMfI7$B9#FeY-{a+N zMKarM(H{XmB49xu_42L}oRmrgCIj?i^cLb406lYAy;R%-beZ=XLzONXJUrso0qKyH z^K(|1{ml{S8~3mffGMe|VUq-0hJ^;V5DG{20{*(wlkJJLQ35=?v)|pz@&?iY0&gpw zzeLRF(*RvGe{BVL7-=z4LlQV|HV%l*Jpn=t3+ij%($Wgf9IyN$0Km7oU#L?c`uXWA z9Ndemi;_`yI<2w_|`42gf2j*e~4snty*8`+W-JQ&z5pQmgc=1ld5ei3l_ zE#lqQm6JjE)h&UIBbkr5`><+%UZYW8PeOu;_(85i$ZPh57P4Gv@kUscauUFt6@4nB zWFv{CsG4SU-Iyt1WUVvVcT@k`j_Jbjg3Nic&Pkf4V5v7Az4tFt%a|qZxt_nibdqRY zKlW2n65HKT{vCFsS@O>pEhNCMa_>qP*-N43w)A%0wb2#s&5weymMI*FCrR%BOaS0q z)~dx|*Ou*muW`vk4S#OwfChgTUtcn5bA6fWR0Rl=8J{f{58F+z6d?#SxUp3xQXv`TISmW-uDnMu7pRJLLzUr^d*A zWV}R2+q4f~d#XSNP_4-Y7QH!JGMfE`II2+H6__3U39_W)w)WOK7Cr)I*X>lT9>6W` zr26n9c0(uk{?-R$8WD_Ijq)fu>6 zz~)bqNwGl@JotF>Gf!W+_Z>8*gXLxmwYwmB(@u+W6Dd@CvfFOU)$V3;*k|-iR<aDA_~~KgV)pYAyuMy# zEAg?zGywR;rUt+)2EiPx|06J~^s)~#A_^+B7%|pEpJ$dgaio9S1%Vxl%AXJa@*B+p z1YT%-hT&HrC}T4Cjc=+*Nl9%h6>fs?pgd-)OiQR!z)~t`4AS0H1$vqA)a?)55%K{H zGe1Gk{nh<#;bEe7Q@BFbFO7mkxnwoB1!^4h(hczovuip)itm}Jjs}U47maDV*26?u z`ayA2QPkI4M|Z6C;Y4iVcW3~Z%(RD!!9V$_9zgH}xF%@5MsEZ=;>K_D9-!$)p?a-4 zH8>xR?C1yurc?PQy1&a?H-;zWXiY|8qtkVLp`0Lsa zfFOO{T8@KhET+j;4znq2!;ZS}*k7nx=ppIVD*%$iD;zA6lLA0@3BqHGBSbHqRo8eq zsF9tO)k$8cwobdPl>Z#C!6b2!WqwoFIIj$Su_5yYqWNQV^roxJco3`oGpIMSx9)tA z?E}`c6#??PW)Bas=r^y~zrJtsm!9_dhlZ8+9znx$U9Q@U$eKQ+%miVm4`0uJfmB-c zAqoo0Xk!xfps>8TS*Kgw)Fwc%0TT-`S+E93^>WmGyDx(J+3^gd5L>W<)Lz3%OA^mmc0vpj{f`M<9A2BecOFuOeto6sh6SX9A98KxQwIyu2U{cpi! ztE8^2nVh5OBsYr6P<(1BVKsv-`-qK|=+fFo=sW;k0eh0TOY~hDR8HN3ZBRx4glm>z z8ox?5)89{p_<%4S7O42i<^yKOQ620VV^nl6yX)3{H$l|Aa}tU;+qDV`F6+evc(+@v zYzyJK{NvTjTHCF++jH!i^MG`>N=E@hmZo2+S|){z%p}Ow0nd}%!q!MhM1BSNGj^My z&q9_EczWH((s?sjZP1kD12Kjtgq(r#!2Q%n~ zwzIL&q7b@O^eNfbpg9ah`Ba1bzjSb|$A5HiBvw?rUDXOVjVeCnbu*~HoaU9@`gHmd zB^&_R)~m1(+=BcI>+^%}u{qs8?`T<;bxJ`$m1^T$eT$6DSoIk<$zxO0@xKf8d(oK4 zb=wKrVI*<7ZGkQTQ5M7HIC{R-pR0jM2flGRR>PRFvjR@x{=oww7*T2;3>NDFoxDdi z_&Ya>WoAC2~dF~QFYXzF`sn_kbb_NH(W=W{~tXgF% zI*FxRYTa!H3ap{BL!QD<+%B%Ge*gGU>v~g`IrwYlaoI*7jaI`__h=_r&Dubm+9GKa6So?Gcp47K@PI`9NlAw{tj@| z(#U)1`x@-kVmPgFZ{2^|6~MlZ4&J+W;UdE6eEhrXKxftzreyXCv})y-oj~BFB6Tv! zOYE+sET52rDu19ysWqxREh0Ndulvf)7r@YC)4DhYbJg+zb{69)OttC?eeGqy}w0z?Q3YH>v4# zX94f%Qqjidc-9+@9D8ZLbj>^M@ak$|*XhX)Kv^8IvqUje4U;5sXB3}ijpupr^KTqZ z)tjM_j8<2Hlg19{2(bwng9JneBt0zH*9uv3Hm?*sTFQg!4UwG(V$4y%r?_dO>ivD}}5b(_|!)&k#Ozsma%+JfU_o6BHV zb*7}-GArk^226s0vH;=TrDE&e1z0uNW1~pLZ6#}}8Q9DP)szP=m@&Ac5Dfu*LMhF_ zF;%rE;1NZ=0$tAvW(FZuH2M6IyF$d(RY(C?f27A{a=f{|X)vSsdjDwX-A`*#u+wRh zlS%$)y8-4hIOIocIRHutM}F(!l%Wh%d;ScI{=zp$!3~Q_rLzu$75|zt?k)LE9k5ia zW*S9Ly?F~#nnERas_Ogel+v`X12h{f3*HJ@0iN`69J)#S=_qK@b^C6UX8$vLl3>2%@}C(!QZ;HCn1%<-L8O?!*w;8D;tq? zjD~@RMfLQxTq;kuv6K{C_Qi)Ui%TJ?BqUg8uMiDy+deJLtt`ps7Sb|qlZ!hF`Fv`W z3Vo-myU)Ut*8$nN@MHL9({9BzRBk>T&JfEqTqtRo8P`~?LnbZ|bNGc zwX)7wdNAtOXzM&B_5GX;);n1k^~M0y${WQrT`i$npBOx7W<{rUnxhvzs#Zu2KqBA& z1}_|vx>6L_jxOBX9epr9HA6l`8HxYo1-KrRO}T_m8N4+XGcYkRH#Rq;%NEHC3bXR0 zzfORMJ&ymY5n#M0Mk+LT#1i2I z@HGSLDX9OS5lmHC5AH?3X%0M;IW?U)#>ub=%FZtS@epk6fZ`yVbl=iR1 zrSzj9p+t2no?(~39sFt|y1F7vwzS>o;IMC`_P7i91R!FN6DWZIroS5oA&1zT7aWrI z*6ihG>&qIx3z`b~cXAU1QUm4Y`@6%c7Jij#{AR0ZUogdS{T^t>S2N7nABx?-{BXv6< zkkoT>O&byOr(Kk-2gkivqLzAr>ZRWI^>*?4;6+oU1<6P(n>3R?Zn1N^d$xR!}?|E&h_jcB5-xqDXmgD!ZCvsJgluENt|I z($lo(pXQ|%m4zo~`y}$`Oi4NiNk*NYNEM6f6$L5H(UbgGxkf0*N353tdlZyY*|9_oCG-C!E zX`UNmd&`y6Rt@!oj<>#X%>H>z3N)HkmiL;{KAMP|WWGK;JS5k|zc`7x_6XG$vYjosyFBrm0~ARK9eB(3JV0oGv{)PL1>2+Q@Kc@ngxB z{hKUGwx4)rskLR|yQ=LjM2XyPQ2=^n*W5%o{ju7+oba0-_Ml0g#2vF;RXxHDL^YlA ziQ=P&qWbzmsAq6Ezab!=LLr}D0q;%f@a9(}sWoBB7@_0_O&a9DnUcYAr+A3s|j8$1202t{-* z8Tc1Jg}elj)GP=7KIRql4wu8g77dlC7bKlf&SmB=X5&4>!(s6im6hfTwclj?VfOo^ z;+(2V%wV$>YT{&MWTcYF>e`x;M#yA9m1FwSS;5B7@&f=cTj(pLvMQ_L!S_G+3wrlY*fyCl`KrbYyF5+uu)peDau?S?|<28B}GH zEk2tYr=;Zx=e^5%rzgr8nPmevC(F@fw(co+yMdbwha4G5K~DjXvda9j)t9yO?kY;v zKHRtI(qh|f8s59F<-+|FHX5+Y5ymo-p<{5*#ij6Zw9E|rY$DK}SXlnhx_v3bv+=bE zJz3pb!RTh(y&ja;oR~UZiNW7t4Z#ENudp?u;sL2tL4$0bi(G_9 zsBo2W)if=Y$BJqi%r#(+Sme>%*x7;i&Y*juIFF~8)d9MA!G+OyVbjeftn8({%CHfS zUFo-PBcwrhT{Rnm_ee^<6o9^_*R>~&Nx$cs=dOS^0u51B<$gv9u&}^}B_)S|_werH z=PH7vH8$qT>Py?{tU@Vz2#O-_wCis))I&y!DiObC|4k)h=o%=DA#aq`4N#(3!Vr*dl zAegHi6^GLJ3JP$#UV0H)M1ahOvWzrA2Fz`P*O$fYdLDgOl5aoe88JzKtdjhee5PUt zIAsVuA?$XIPk5447O7B)1|2b>QYyMMyB?h7^#*8Ga6(^=?(WSCU5|*=1LU3?Zdc5n z(y~9`9-y^-myR{od#HlRqWf>!dG5FoV1sSQm}X1U#U-n##>B_-BYTN@JeIxm^+SnG z-TJjtW8DV5j1XWR5bTX6_~0BWdtYyAjYjjQ+gj-J@c620l^b@1RfJ40a18hX4|c}@ z3)(hI_GXVAxJj+AC(z9&V53eu*~7Ecg8zfPw+za9ecOMRB3&ZgAgy#a0us_G4bt7+ zN+{jk-QA7S-JR0i-TT6|)^9)mXP((J`_-O3^W1a4Pz1ccb;Wre$MFe*kD=ZSWufEi zwLSoZ$sspVTa2Mrhcy=}$dDxV8l8i8Ibx{(A2DfTu((o6?n5Ig(d9ScAAzSB0sC2rRmzRg%=!l5Vw!7`G8*yZ@d?O9giIeb7 zEEK3%5Rnll*k zWbtw`rM^duif5Wmy}@r8ee36NtC>tG0h=ob&Rx+JPJY&%utO`z^SRw`kN@0?u*3(P zEy7P-O&<0DM7Bwi4w1xdJ9o~(w@~LENmxG451dOJXWg^lrg)&7?MF6SPJfe`s@399 z9f4;bPv>;I;lW||dePl4G!*l0bg{wKuy4RvVgmz%fY)pwFZt=<4NR6@_&YQ>I<01# zOWmv6=c)Wu-nQam-JlQczDc9=S9%$WY{qXy2(b*{Q?oEI0FAQ$rDz0+3rr~~skCJ=egD1yT0}@hb}{_M2>}6N@Ak%;#U`AR zAWXpVHDgY{!t0TUrzCjNr=hb9a$9S>K zBuwt&L)pff^mLR(w{$g*)G11|b7Y0*)S2pKq(iaVU=TaAOiKFPp^^DTJph0A+0WnK zt@gu85d2dL%S(Z;wJo=9KYhyAW{m?<`Eaj0_afF_{rqtSu+xKyth%|N4{(Q!^Ar5| zUwNz>P(JUb^AWx(9F|ysZT5-*oo0QI(Q)S9RQ-EQ#0Zd_`o~|NrT_!OZx0-q#Uix? z657)hI2v4b%=GkMVnHX)^2YQ%1fk#=`QYyD9h1=FlEco3znav^P0GXHysapN7sPqA zfJH`kdl9St2m}pa*uc8Ra@)HW^`M%a2Tku^=lMAU4X~=(QcgZNs#$@ijv_U>8H%2Wnp1i=4|Qe z5(eqgrJAh*bd1s8z|?7pPY8HT=`x^Hd9f3+g7ohXe6<}p@%F2q5&o0qF$MGRLGvh^i zN=gfGv?i!(2debF0tA`o2?jm;6?~+x2~L7<)wU3sP{#TH!0}>c9+R0)GX~E9C<8Hm zs}S2Y+S}gU#oopRzTWJd?DdU}e@)E3CXhayoS=Ur%p4vc9UmU9DI+r{?}bJ=_MU`E zP;2-DSdmV2P}9(`yyuF1R!)Em@J1moR+dM@ofStyQc_AhV zGozp+LdV4gd|;3um8D4a7L~2>>h~k)$I=tZY#}AzUhWSLf@Coo(2IHzCeyyytOlld zsStqdnRET3<gxx_*YU~msj<(8Wj>RIigy`WPgfXsx_hb5kNi8d z?26fEc-dewxNvy^&yL~zY$4A|+bF!(n|g+_AM&#E<1hTX^;4>O$laO3$#pbafo}xZ z*3)o+?0~gMHM3aJY5cg+IC6Y^yaNubC`E*YIk~thD??Lxo^>t-8P8&X|Fg$^A^o`j z$N0?E&1(#s<`p%6jLVA-xawEW36Lx-%vmwm+$K)rn+5Pu%zGgmUPvA9RZfzj-QnqFp?0H|OZ8Waq3=`@_@^+o@OcC|Ah4b32sW;ZB0 zR#$>laa-H7Nco62Z{A!I%+S@lR{Yxz9%UE>Tx#Z(jg{FJb1o&!=>dsR5U9V$X36dT zC^3ZrSV}k(EY~l9TIO$EKDLhtVxl(OS-#UUY?y)S5-CH-(5b{m~Fy=wr8#JD2T-v{53?&%6fR{eFs89 zd*#+)X99Q~A~Yu;twFb9=Y3`%4ILeyY?#yDP;sQ86jKFQH##1U^v?J9_q7FhNUnsV zoCns~JgBOxC+V#)K1prJ0(U70nVfwNt&bn=_4f;cL4s|5I{r3MB45a%WGYkI66p;T zFy0{{Ly0~3RmG*B&%h zKr!9;Ef3Gb;e)HhC|?{+A`7z7NKfx50}qKH(Q7;|`_o^U&_QN)9~T?!GU+<|#(#`)ZWhRld{#q^A8Tr_Iw}Z^cCdb(XyrLbU(p6TgXfVzNY1u`=Fq%UBEIU6l#tgb9OV2d zkQ2i&wc28E$JVf(jMv^R$CIz~LL*sl3?x%J9^8ZiMgQMutZmVl zES$?fXe==W6(~;*D*%_>KL< zx#?C8-_w`%-zxLEXQi!`pW#NM!gJ z+OerIPUmg*T4XfqhnwloCCYcK=9mNV-Gd+|%zl41dYSL>yP_$e5Oqsw)jQY&w(Ejg z$R!f!`(!~x-P_%6`}Sj2j=~c<_Le7R{G~hFCIogq`;9>F7tiWS&ZY4mi}`h-h>*~r z>M_Z|8{gUe2(72uol?(QCdTcZ-FFW&H`5dv;u?47Nxa7I%*FOI!KmNlbn_e=FDx)E zr9mEqZ)o!Nu^0r6nE%ba(&_KN^SYvJWYSF3%x=$_)cfrmbS0^M5_kiw(myF+?k zYkAZq*&0Bzl}dTLV)t|Rxan>d zRaLtVSN&P*BFK^T4gc`eC6#on0Vuj;o-TH<#p<=HyNK`rMpqnskY(9ox>SWt0`ZVx zIOuwJM1R#84k|vhH^%W5NMVoB$~9lgXOL4pPPIIr21iQ2pC8SKYRKJ|g4Mr5jIXoy z!@EB*iY&tX=c%d|4FSj!sag~4!RG7*ANQfoPNNszRC7~qTnx^XQh0L7GJ0hsTmhcB z%t4YGqix@WTn0Y_h>Y`mJmt$DB{G&NU8<4vl)03i+I?*CKtn{rW{*piL5F!0FXvxT z0S0Me;CK(fX+*+j_NnQm?CG92+*~)-0rsz`wFDM(s7?c&ei|C;oxie$y?q3dohvux z+I(8WBHk$sN8%t>;GSQehJ-jLE}2a>ZA|z_W(k7|a%m;4u<&<>-z)xTcMMB#m*k;72x*7y9HUOW5ua1o{RKxnVm4{|! zNFdaw)5c@}3g*@#6*>&WHxv}P8}PAm1QFrVkoa5_OiZ>bp=YF-{9_r$XH=*ZpJ)j= z>;T-I32?OpW`6^+v%yf1N)Q6vgOs>Pcgs!fvc9{!-Mfg0h->lBbXq3|b+8fONYA}- zt~+Dze-V?}?=9#r1}K6r8jn$aDX2p2%&^qn?_uDhQ6a~mF6X+L#DuuL*d?h+dPJ?Z z=uCKYzZHAf86~T-*ud%4Domo#yADeSRdjus?+`_MTeTh=CcT8b59@3tvmY>F!392k zp?LANjM89yM8>9H*})qUGJ1QIq`=wj<_5E3Df?Z2mL&zFKBN07GZ^qKVi;YakqDct z?=a>)f~ZVt*beS`D?T6n+rpP2LSxL%h*|@Q*|WxCTHMZ;>mZI5h?!Pnu{rdV?J+Th zQ9y9-VxvRk#bMkE$ z3`|I`paH7TI(Kh9UVb5u_?a}--5bT1)%NlmJ>S!TyaV`HIusVm?=AMW<+~F-<$q+T z=a70N*YP*sHgHLP&I^2{`iH$i6P9{X&y!i7ZL8A*F-6Kl zJap@OYvR}t4tM|Y=!dRj6tucR`DdiX67$n}dofQ;ZtPsy&tICOg)P+CNA7y5znZKy zETuwShDrtPK2TBujPW3^lEtQS(D`j|Yoml^1<~sw&>{{TLhSCY7fa=?m`V{DeuHYY z%tcC1V0MTt)zHS2`v81DpNe>U#b;HKnUe1?caf>#ot(^iDGtHh~U^)3NhbG6x& z7nMtc|2#FM(1M+nD9lyVoL_qTwQkYql*iLqXqlfCrlDbg@obHi#3C@F33(07DMTB81C>j1HJ^y_uQ1VsMYQwqCEW5hxYGLZ{@dd)UpX1N#R%> z4rlQ(P-MINO@UBr-^uXm`bf6~7R}tTmnua1WGwM$?5~ z_~j?Hj>{nsa{JIKS;PQG(vv+&vYS}%7#(ezHVG&nm+Vj{=cRiGjPIPAsy1sc0_OtQ zjqadHK3cqqWyL$4OcN9dJx(1%v)#O(1tlJ&(4gZYjc3$*i+uou4c+h?3NeI-sK?rk zVQ@ysNlC|8=shNs4^K0t9qsMiueZ$!W(Fqoet#{kLk~^nF4ya1ywpNyz+o%WR2$64 zkw1UE*#Wyel=76BL7~?x-M=kltU2dd;+C7Tn8hnZ#dm{T782{|53NqtbYFknJRBlT|7QCLmXw;7Y(c zFX*yn;VUp;fXf)s2Nkb>wMJnRl0Qhr^V(-co=_? zVpa7IJ*5NjiHY(ZZM?Li)mC%PF4jjl(E|o`>`IvhW%T)FpT)2zfFEY1RovRrPJ$V; zajH};6)_+$r=K=mVG__oXVdaN4kHUhzr{XaDVk;h=0;t>SZFjz5NZPO zFHhL)wd+biV+!)(=H{;3`Qpz&f6WsTcWRQP1GfFm2JNc88T_9Vwrp+e3ih^QqZO0( z0W?4t&2D2?(ZftITF66OLCth;ObNu)Wn&GutjAYT?wW<6qsuQmP$`B}Rj8h?xp~Y5 z0BfHF=aDR@Sl>gf`!tY^9goYqT^%lO<wm>s^}tF`CVFBclX_aKkJ)QF+k1ZvRkKrS_J0A z_ngdmZ$dT3>cuf{Vqf7SK@uz!L6&Oh0BOT;pd3^jpSZT0x|=nTp@qSasQlw{zNtOi zBWe&JEYLl4Z^#wnut~-)gzJ)VN^{y@G7HHp6afp3%6vLAwI?et@1vxSHJ#2qD%lRe zY~Rmz4~yCDis$oLG$2_*1GGaR{@l2sbiMIy$t*fLfC2D7LfDnjSU9U)+b6Mi(K%nw zj3y=ZU1(u|ZU58NUDmn%G zA06YQtcc(=v;%b!Ny)*viaPl)fOiyg=x$?RjJY`vnb6!>4y<@ptX6Y=c4F^f=U`=( zhJ*gJQqCCYiAq;P3et508H&t{cI2;fM;8W)1RIeEs!inW3@t4ws^6ou{3GN@Hc2p= zZ$NAYdGwd~oMlfk*5AVl+`;gG-Q{W$xLWn%f$ynl*)Xah%gkFj4HVRiL35>Y936rs)+b*e^{Q4F)DtlDR(I>kG32^+~`IxRPSI zBQNG=(3S)JL)?=!Y|t>mMWd1us4tCZ!H(3o;P!>~=NN!k2t6d*+r!q2;tCj_W|L8x z?iuVKl-m{bmwULMr#r%T@Cto5yBgTXHuJr?**hnvNy!hy{}k2g=%N#76nidv09|Z} zpP@1E`*p+|kCUMJ%3X#Yh<+EyAv7GaSk5UHKyG?QBo4JX^GPIYrjg8xU z*y%|m#rf6fc(<91Liczg)hz7R;@wppdlo01KA-~|kSy((s`r-7b_u(T$=L|Spwp?4 zN<@r(ld14}kq#a*xr~pgwUCT&;%XR&ycHI%?REN|xHfVV<03H26p)ZQsi^qnBJ{p} z)TEG(iD48(=|%0l@K9)oic%;ibFNUTqQ$n4m1@kh&i_Muh*Jh z+fDROG6W%m7y|?A83LMAwl z2A+S?4bnRL zxB2|jt@+tgT)=d(Eha5<9{>_nxxqf&BmtK|GzC$V+tlbpk&|h@asI-%$h{FgF@yG4 zP`-MPwz0N~*ay*!%uGYNIPqVGxef==o-A>Ec!c^Tl+^2dA@*xih%P|!dcHg7{&P#~ z7(yWdu(bF}wjw~hmlc0_Wn^Vn<2}v6Oe3xK60df4aWSYY7*snp$Bh{`xjf)JSzWuD z(uNmRQr~bZe^fxr^z-rgSo2;~hq!nn%h$IJ_O(8~tMjqEUR1f+Vm{9qLq}7<<}k40 zL~Ck36Uc0IawW4K9Il5~TmIn3d|nb?1sy*8xayX^zicj~bP?dCeJpLC_}G^@Z9uO7 zbcG0dDhLHmxr|BsN&6rv8IDOpa-d2d8H)_QSjR+1>TkYR%aG7}7$I-}w$&~ri&J|) zEsy&Y;34s#o>KD;u6{|=M><)csAdN1ndZl4xg;zGM6-y_?c4Y~)=A6WNYiWjJ0PP8 zq)yVi?dO3e&}raL=YyQRW zF{-_pe($Hh-fei8imQtgephx|O4IZn>pyGdrGOSGryvGF6Y3g)rR7+frJ{sFUTN1B zzCex1t)e0%-P7H9RM9g#+dH6eUz;XlY66y_r}EKX!}JUcve3yqframX&ZrQxh`B-0 zKUJj#5Du|or^R~PABh&vf|PFQ-JGb&HF$Bs=Ii+8s)%6p`6@HADHwTKkSTKtF$8MX z3(5`>qwNO!J}$jmQ1bHP%rT}97Ihq-BHVrCm>IRl9Cxrgm5^KyWFKWJXs@q#B7OT~ zk7JFD(6sVELZ0l=ZVM6;(n7mv6^0Ty{kHiQ#>IU=H)o@~%?PBJ&oi@2(BKcVD^O7V zzc9N>G6bs@-DMW2C_Ag#p<=OCD;z^L_3Kol?DsdUpTS*3JSK7HznHeS_3Y6L(a$iGCr?Uo&Yips5(TYiZNqv z{P$3cB6aLxe%V3)N7Ld$wd*`N4-b&(98eU2uGcnm(hiLnt^PQ;e$;UbOO5L>=X3b! z{|Ji!2C+(SIEzPD)4)xdnIR2I{sx_su~vtB+q28!vSXP7YzO{RRU-r_(8ia#B{(}B$>y82;x>M0^D?Ac=XBb2Zi z#)aR|zs(yMz|QXCw3%4{J6|g9pd`|y|( z47M`Caa!N518B32mQE@`^SS+n#bR@2DWC^**SaUxNW@cu$B6nJ=k83e?n2x*;48Ur za#?I`bma9sQKyv1@TA>!a%Hm|3p&J1`a8Tr!k5GTQsL;ZdB(UQYnoQ8-gP+z?2=tw zf8dl@*gxzg#3z6icQDNdLtaqS5gVwe1Y3IsM#h$>V}8(a0~1Z_-f!TITv&rVBkWAi z{Bd`&+kT1sQ5X^5!crG-h4Z)QG)G!z{yX(-SflADhWxe<{hQ((%waXF?Vgp1D(1el zF=U5_Qa&d1?;iNNBEg#t!PtB*MU|Q3-rb?w)3Vd07Pzx)iC`D;&JVOnC1lyKKJ~uj z(ST@n%U`{)HWdMXeQ@tS{4?5IC{1E7suY zwzb4l0@sNayxWDi?{IxRAKnM%2%s~2{EVnN; zPHr~#F^+A_iw&mkAS|l-`_1HelyFOE*|ZObyTPXW%3Bhn>}$53af5W!rfl20C+Ek< z>yV_t%->eWZ~_$WlwY-VvGPNbIf>uR_I3}>*Vcdak=&PA3QGOudU}ujq#OJK}QWCG@CBQ-lc~OS&IB!m1HhpQ7{}FPo(YFWygEg>H^X;<@-or+Y$8|6zks_N4wT zND4NG|FBa57Rm%ydEoEyY58=a;?sx zvDml6FP>*|JaiMrzH~T|@05~65)#9r{k?vRnX*~Z2gt9M$6}Lh84e_a zG@LFsg`FMa%B?5S^uVNzqovkbv&n!7=J-V)IVca{@w0>Q%YOxI3&3ii-nS$sckTld z89_DM??wue{v6h7&QmO(mQF%5+>gPDiHdv&L@D|vD~lX-5#+8#=n_Of zNn*gEf;MB`2Yy8aNEJJ8w||2(W;dV|3ueA8vi}zfR|;a^yH`Ja%;q1Jq(a8##^sJV zjJqa4x*;RkRZNAVE`Vm@BORZO%>wZ^y;{3n;*OCfNI^ixXtgPZpprM)X??q-?x zG2ME*vC-|~IlChs?h?$HK<73#h$9vmpkWEJH^JdHs4%XladBxO;Y*xAwe}M>T&^xR zaR7@$8j4_PER|R(5r~1p;x#~9bPnfS-2;)T;4W}itzfAJrL4(Y7SD%9jeTg*>4V~V zu@-+K|FOq&y4_zSKdx`gwdLMCjDMU)_q+y5iLb~X279L>6RG5n&m7R9f3&eGJktr> zyr^lYO(%?3z$fvsg7`sI?HZo$)Q_{Ly?D06ifc){w^ZF=c5f#M3?Qk>s4jN@H zCsKmGrgd3mZX*Z4n}|6R)vp4}@VWF#zw*P?5LQA#(t%%7?=E)=yNYSo9h~(1Qr$-Tb~X{ z69-i;X=9159#goT4=*n-4GayJob@-8|Kjt6fo2UvXLJ~}^n^=odHB_+R%5va<3$9M z!6Fq>q4;KJr!9&zSeJCw2@2b%gC-=2Ulb05|1}#m#O>wXetV4boa17vg2m=b{@1Vg ztr}Qij7{L4|7b_Bl%iR{o4{SJj~S4FqcRXr0)DF>lJHSQ(3@BMuRHAZZl;$vwzdd) ztoH$G*;h!&SIC#Fvy+%O{mfv9fM(9#U@Ju5*^d>V8NIlu?l z3;xmA*qYkv@BhhOQPIq8fu@EI9vtwf^)_oir4Wq2TGCj>7{m`G8*q?pSn(<<*MCmu zYJJqQ1L4xS%}ZK^Ij>quP#6Nfu<__qx{uCA`m zb2>D>KGg}zg^Go>Z*X&wlvJ0 zbp)scGqsiq!>5+mWvA%pvGvb8>^m=xPrv6Q6)(fmz{JGxwBvS|b~-_}QoM}&`Vbrk$AvXc9OB_j6MtOPozrW={kPxvgZU&2ZzAv=(@wC^K5DAAq ztl8I}MQocF)bfazS957eQxg#J>YbfIxDJ>)-qTWjZfXgxmK@Hbyu7us zv-9BR)nw;nA4uh*V`iV2>pLB8eG*_shW3W=&XI^Cbz>kR`Z5!izHcvSe^DMu!CuYI zRm}+F-t6vEn#$u?QdOjIZyee~5U$ba>{NGmcdIx(p=W5Q1u%=#DT^Q!C_w2DV-!TA zpwNkli4AM8cOxMYGMcR)qYxY(f(eDL>Gt%i2Lw=O&WK=RV_U|EGrbcL)n!=MIGs(NOgmj5SMn)z?)`>}{F;`_@sUw>` z%}PSeYu)n?d1~4K9j-%aA%kAcew>@)>rB3G2%>jW!$DLf53&c&|2RY1v z2@+Be8c~Ja(nBGX^5j>i;{sJv%hs>E43NlGdZH%HdwV_cNq(J{(Y>$g$C2xHWohie z(pl<)NT8(_v);&;rmvk`+P7$Nf|f0(oLn#A;Ned^do#F58tn~5zR2_3ER45JADA8R zKK#}Nd+C)P@zT}?Kp0p#2+AKnYB{65On7+Z%42z%^l6R!r@$F7vQ=6k&LmPdTI`{;UTUsj750z+=3Vb92xTq-(tQmAmIG**k9VL6c`kpG*h~>YICSFI(tP|HE{`Z+UdFoY z@!;v@{xTpSP$U=&mP`JyHjByp6ph1F23?aN<-O;v;wX+D(bC0Kp(VAd(~pAE_9}Qp z*oX6GWJuH3Q89fwp}9jz7$J7Zp|2A?fj9)U71AdAfXu~p*RJ2bO|X%&8Y+?ZU~D7P zkO%F^j2 zV9>PfMSB(%6q+WqB_zbu0QNK>N@8N5!&1~W)Nwx^uO-1vOt7|P)k!L9?aw#X7K9S0 zyQE%wIo~)lSeHXW`XVAC?)ThE;(`^>pJAqsWAutDlU@pAu|~#iePTy`-#0RITq97G zTzhI_!tHjW0t6lY)Y+MD93BtlBlAJTTy8O1iy5RxM=K45!4-c6cSlUcann~_I5{~9 z3}n#AgmV?{Y6^N2wf!wq?Lm=aE_Wb8=_6I|@HnBFS)PRVA zZ!aDNp^-te?hS81JqC%{GMmjc%t;QCmiyD-_d&_>=kb?~)uy!hn$gu1#La3WHHdRZr+=Dt1`A0{IuU$y^a+SM!uzB_ zWn#DPKXN}!FHD^O+VX%#r?0E~-ed1*4|)%67TN|P0gYSO^NNo2cMwjKqtEQ(Z zE@{5Yj)8opj;Sy1?hmoc?j%hYn37~PbnoYz>;tdgg189Z>$v03qNN$4CMLT~bUf2H zQ$)uoLbXFlT>VA--+;F_aQX5e+h%UPqO?`DQM-9{31M|MWo2c>?9=+!%}+IXRk{=* zWZ9&ZwLFPi$(P$pg4a{|^11BQL{jkLlR#wUHqzcMIC)cGf=jN|ADY5J;zNkdHczB7nyf;<#6afmlatb+Z>?JEM}**;35e)W@(XVrqJ5H+YnPJ zhvw%~wfTHPc8n(PD`|cLa44&32rWy?6?+}Ih=p93kd+c&jn_Op(aQot`WxW6i}J8r zpBerB9ZFCzUhUVwL=l*L0S9Jh7Vlk9NO&jiuonC9%kPUMdh$Fdd`TS za4Hif)FoY^2aoL`p2$Bt7B(8a*L@O=@R)RuxAOt0xoSd%O-#G~$JPD{{ z=R1?QQ0Zk~>2FP#yG6(iC6YrhiGNk-`#^mB&M$A^JDlaUA=V5~&h9U2qrpVN^q-GTaclJYvDQ)A z!b4NiTvA(G)}2#mD5&?9!Aj13mr+j?amBSd$`~MZUI*5_muC>PfoN;52DNkx4tY# zRLfZ~wc~yRxVw`enaep{zx&V1yUk~{!<2jUKF6k#>KWF`G7Ks z4TY17OF?TJSi~L!izln@=>+7NC90)?Uv1&^{dl~Z|35d2Pf40PjN%*I=?KS;?*4hn zsOVQS%JZ+FpkOS4d7>US2q4LQI$o`qhgzI+Ay(X{w#`Q|n~yDh)M}21OzhWetgG|! zVaE1hm@ZLd{ef4iUJo*D!ONMs6B)E*kO<8g`bj>93znT;DSEL`-etHJ_Dy>wC8@92 zhaR3{e{D+$EKb?qH+uy^=)Mcv^cty+9pM=HHw?GfN7sLA0sqdxJ_rKQc-#vWt$3?u zHuLC9;!h8cCZ93RMYx+w0pMJ7_0MCoL^4nIa)?mT<7K7g-HUj%&*DwvR_i zl0GManqD(KGxI+BWR%#s*nr}S&vv0?Da6A|pAQ=w+v3^!*=-zM#g375)U+6w_QzlO z)$iCncf5`{nMv&?{KDJRlsi!q*@nZ~`}+ERElC~z26CsR+us@|C?q7iBL8e_Bw@*- zWqU!M*BBF16L$hR>QD+pe^=KfsGL++SM$6j$(2f?PI5jzP6rtUHVKhraLnHt59?4_ zSE}nw{n$A;IXx~8*bb8XV3_QGH9@w~9+$RWn>ZQ8*7>jl$l;3McOjM|h!-AO4?kwO z$NeI6rKNEAzBOZ9#?(38WLSBHage{wAk!Y`HDHPePl|Z!ao5ovT6DjLtUfz5b?KX% zy7y+v*OU+>j_~QXk**xz(lt+!F;mm>4t37zca&)_zKybUINIF3?RVr$=5=&AdVGkb z)sm2iF$dlbZ3baI)#>SJ5{bTlM^8x@Y@c1HqfF$M>uVv~C6DY9vumn|;SdH{3ZJ-+VBeN%80-t!+0s zH5m|@V*+PZBd$d22!GVKlu;Fku|T^KNS_ORiAygNg{K$-bb?9+QdA%R9V!`_CnfR0 z3d8v+GIYXc=u12IRbQa7YH^G1{mrnJy2$BxfYy^{BNSA00qW}(*V7wA1#@a*$O+_m zZ2=-05{;&pWequ;B^ScSv$L~hKAj@(3JWIomyb^>hA$82yQ@P>TklHJ(qwi-ESsOC z(kqgM=3X(Z54T(={bvS6WrUgkGCnm3f7D<*ncBLwUS*~TeMUvW=Gc9x5qMZH-XtXr zUvo5fb0|XCp&pozeqbXDxUc)`{sp`5N*2@dQr)Zf5226dTWm#~g?z2>_+n#HF2@5> zk6hQ&*Q4}G+=*H!JaF4BKP~Tynw}l4&yELR&erLVyApa*8pi!vb-4*vnzo!Pv);of zaZhTrIxM+6;?Mm1} zm_z-4kKa(dhcvVJ)o`dR?w$Spv61F1DgBELb$~JK?9>+E7aTZQkNrhQ2M=Xwa%ie1 zFK-*nE(fr)`E5Uc{+#ZfUYx8>%LBRuS;?7WP8~hH^}U0G!$VNUmHYhJWpja}1Of&T z7I=PQc|?pWd5VjQz}lIGg{iQ(*ta(!H8t1nNbH|g@3v7Ao(B6YE3^O-5`>gshbv+j zzvw@g{gw52nwoc5&Y$Au|CaUs|5w`V!W#{L1^G$}n+uyRhUO{4lyK8v6L1*dVG+tJ z%c*E-BqgL~9+yMz+g?fyV=aJeAA;rzTief1cbFpEXsD>;KMpk=uv(bv zD#@rQDk({6{6x>nTEkpRP4#{SX3*N&CWS>mC$}*-+gAuMfYk-|8))|G)m|#nBpF4n zcz+kN-wRJkE(c0We=TbVyT125TYf{U!g*HlLrfNMn$&lX@F&(L2yYnZ>7DkMM2>aV zLI(YN{gXcTyjqO7J6T;W-upCy@@5niu9zuFNhv>0=1wY3HX`45+ar9&n0jn^RBr@r zWS7%#-5MI|vuZ%(;i}fif;(+}3w?;MXkw<ad0F96L3y&9As$zX& z%Xjzf4GpS?-=Rw)w+LOQ#>P(fQd`o?W#kt$7E|k<7=Wqna;kl>xgjT^QBh{D4VLOd z`m_->E`!tcx&Qc(24}0zg;>b8lSLR@ZAF=mh6Dl&)D93JRfK#e4jRWGY2OS~Hu6Lf zSbna$Q4$#ql#Lef(ooUtuzSlW1>tbMnIf7vu}_>%<@Ic+DJc;bohi=BLt&%FS_Jr1 zyaRmDe5tgp;6UV;;A*8WO)CrAqfWw023EY2w&Ctj1OdrCLKobkUu_v}t!zd9{(;}@ z4-PknP+YuYe_%f58s^v@kgyYBJ$c{1$2{+kYTl8 zI5+Au(pTJAK@~v@B@yUZJbrZpy~2lQ_RLZjDN%3iL;2Wk3t|V3M`+2ivsIP7hopv( z7ax9ZCaXPimHpVGW~^Ww(quQ(*SB98gOq9=&3J@(fH?N-UP@Nd)qew}Bfn=X-Z9wY z9vBN)19Vz9B-6YgnL$V2*qQkDt;jYK3(u`^iIoWgen*wRyr8#$mdl;K)KNWcUl+}H z0&E}j=x8+uiwz4YylX!ZgPJZJ4*sf0uM96skH?VlfIJIX_1y89nYz;>MZ9$92au;9 zmk_rqBy2z~vQ6m0`&ctov!m|X?oD(%hkkGEBPBbYX}J!U+ckm$x>G-K)?rfiGRziq zWaO};v8?3nV>QC7&CRGJ ziTU;6&!>W2v5VYaB0A>!$6u{=i=%T9UTpD=WI3+ zCBKc1j7)VS$?q0fO}b`gdImcOXJ*jKsTLS1UKol{;Ns4=wRvSk=&to|r$7iojStwSMVsQ#iWH|p$!xtW{0u&~hf?5s+2%E(+F za6mt+%gHG#=e3-y2{-XHodv=H<#D~k*OU3h$;FWonN_zY2M5EQ3%W0+=Ixpa3i+rk zs^{d)P76{}njsxG+!&h}DTf4P`q_}akQW#4Qjyk!0wV*nvmr@MWUbQ^n6e>Me7axV z6t!3egwG^@1KG>8De=*>DX>rYN*wMPF18V1JgKRu=+eN9HHm44#ph=&ou4WxYT|A! z`;ak!M$gC?u^8*C@4=svkUEtSEfx!#kb-QyzrOy8KQ=yDQC21Wv7*32CXj$82lZW6 zW;nZ?osV+Z%+4!jd@bLH4PUaWIj?0^vI8X8`T`BOT(q9qf*dJ{1$pmQ9EX<=}0X&F3#&n&>UImP*!YDVM8?bbq*|1nNGvpW2LunSx)_t}ZqJd+|TmHE|u|Ax5=FczmRcw^! z?bu{zXHOZ`Q9sa&Pg$tGufZ4?99VX}NCx8u8=nWRjmhIcJx}A+x|8nF>~tz-)|QX# z8q^i8nYnrJbXgzm$2HD9=1GF@$T*yOKV`u1DoWWzQ%cEaw?A?S;kvG;qxW$=!Z#3P zX5E)(cpg3KiUj>qvck*YW8p!!k4TEiaI*F*`=m>Lc`%E4>K9FD-*V7;kmq?~T$ww7 z$M~fuL2^$jm5;}8ci*qfiZ~ZiwZB2~Ni)PBBNf^A%fv{yAQZy)T1|Es)931%6I`@O zw7n#*RbpAfuRsbdXH^$i5dG=bZD>kzXV+KPd#$^brgAt}tLAp{Ntfbhl7XdmT?(hC z%h?dkDoSj1%xc6c8?PIx;BTK%_GNSu#JltB9aP*JbF^t#E<(B%t@3*lv!H9*Ar0=@ z)6(UxP@LsfN3EKnu*e&g0@u*`6r{{YfFOdt zz(>Qw@)7$2J$iD5cW=4XvS=fQ^=ju@cCAgY-IrwB9a|;7@$!JvQzbFKP3EnkL<+%d zHkX^jzNm%fQVB#O&|;A}=Hx8Kk8%}U`Gl{fU6s|!xYz8@IqdXhPp3FTvfb_D(&agt z+u3b^Rm4ydGpYwhQx_(m>m8~C9uLfeD0}e3>x5s&-`eqplbr^zrIVU`9j#>V(m` zUpv4~WVn-5ykZNwW^TggF`HN~j~vmcw5oydd%wVL6Z`C7glF5#}}m~C0^V7 zhC}_sx(15ID&kogrpg@$wwMAW-+03#6Xn&E;4yK8qeKp!VIKH*>`eUL#W@4pHG0TT zTf#&vXn}-brGIMy&x;Mw!}Su4h6+p+jkoIvZD}87d#6`*eMuabTV7eDgSuN(SyolY zY%in$ae*3XR2yiEeLU>j{#gb^sBDwk8p_A@(l=4Xd!!X}xhBu`qvPygObkXe+OHSO zm97eDT3^pE#;yF8?B|nZrd4UZx{Y<=o$+U@yF&{-LYJ<3S`#0mQ3K2B4z=9qX#MW=HQAiVz+&M@%k62{^S-FAXMDch^h@gFYm?)K zZ2cTHHPsdVv}$S%yU-cC7q!DGKdJR5gg-m5;=tk%6IhFWu#!}}EE7glHl3r^4)Y}9 z&X2*s2%Cd>8O3^6pUwq`84ol)W#0tZUn$f>7ev-A7bk3TZlV^ zI5u6{TLEorS$%!q#`hT&m}?ce3K|WRJwJS$ z>nA;>==gnT(cmEv79P&p-iC^XMi;|F)x`wA^l*K1qYSlbTe414;hMyek2FphrqQ~f zyrRZMZ>RmWU7l4Gc0pNVAeTA~Ft}D$Re3RqvZK{-F|+%bv|y&j#fAGGw?m2NMuK%0o71W%$bWnVRsit}Wl?c)CqzU)MxoIA zNl|hm7ha2-79nwnd%Zmy2BNnu>#N&A2CRm+c(}NY2d*{Zt^8DP6vL(RUQ6;b=&Y6s z`Fy|H1z>G6jjEG!JaM(7X{AY87x;p_S3GiwS0 z5_QT*6!@Xixyzhi*YR0z`n(?X(mMD?hWuqDf!kv2$n+6y2yelWsE8j6g6nesV^Ju+ zkMDS<>3a0k_{ONc_T5U7d1E3cddF;=O6{*u7MmWw_HLv?x$zZSeT!*FO^^PuHk+dr zcM++oiceO@Alesyzu-Y&dwhyE=xDm|phP2o6;9hoB-`fclOptG{9 zG_C}K4haF)h$9T%YY%1&d+qf(ggk2HmnR}W0qsXpix+KP?y_~Khwe`-%&h7SI5Zt6 zZC-*$8dYd-umiZfI97E&4Dq@gwOi009wT;lXi7>+qL6aI4Hy0LWGz9lzz+SglPrGs z@nR+G1sMVaG2H#0)_WgOr2WYsM;;^XgAYqwu^y*`x&Ie?@7SG5-*x?V$F`l0-Lc(C zI<}pTZKGo+osMnWwr$(C{nUA#_jNyGe}g^7-Y=6E8Aqy)`qx@>{pMWu?is>L3Q(44n89yfVZSW0~VchPUyCDWl+%? zMxb8dG;pemxC0d|71<9{s83zD7o)eA=~yxJKyDCntX(@~j?V20W5{V2j0yGM#QI|) z(_tOFYW_#+K#ZNi?D-T<_Cl9BRIt$HkdAFxgSx#E$4B1X4=p@I|l%dv32D8J8+|bx)|=xb*-L*t>UBt z-4_QY68qud(&AIwZj5zj8N@-zlC%#${DkT?-h(d8mxb^fF6cMv5}i4ST{q@0jHQu6A<<@_uW>e zx1DPhW7zmo$~(_9DDm<$+u@(rE@Pf5e#TkPGOQkOCEu1CM+1_Rn*!}f24HS=m3%$= z2t_f#uz*?Lyrl#0ubGG=3sn^fESCI6W!5z`fBbA1VHumM+G@AA4l(r4?%)v*c1D(S9U9)^fXaaX<7YR)Bk5t^UnmD%bh)^D^4WM<=qK8V+J{#~`(!0iuCc zF$fkCEDItwfCiu1k6zf+zXZ^JXpd6J%KK+>rE{uMrdVKxk-0b0p_eT4MjM|U{HN@|b zG~USM!N8}7Q$6zx;36&#+--{<$43~=^!^M~_fMm>lkd0rBubKlwW^dwBrCORINu(?*Qvh{MQh&X-+H-7H z%@)CS_McQl+CU&QFEo?8qDsHDEK*-73Mw2a>a>dc6yw|-H7@&QAPB%;cBZa-Q55HZ z1%BLojDkquw)AIwSA;F-ETp=X31-h5o&v{v$+3PbkYSRNMD#4_3KPQ^6ZiUfwik2v8?eOystIQarQrtC(>w5`3Yp#F z*x|}KErwN@^I7k(3KM4=XZ z`8kbDr)yA%`bW(PXhKAfB6f|UT+<11fc$|H&7dqQB<>KbT14lU2tcg} zAqSWqnvdn>rH1GAHP7T9&gMEyUt0nk^&Ltd2>H6iw}7J3UW{m8{m1GsF#o{l+qov( zChEVZc#S?gd_p(U@7A{`?3S)KN_^`>{5iIVrY^wG!tsM+=pcN6tZy5Up~b~ay#W`6 zbX6Uyy?tB+{29E)%|Iqf!Wz}!-Ms8=Zsd!u4_o){R&}pEp^>qPgcj5ya}8#OE0En5 z6MGY@punsUtMK6ChG!EeP?oIqkEI2vo5~(JnQp$`VDK zE`3e03Zco#3$&lbql0COPTe&c75?)T@emnFh!uEN!P`xmAwo z&v1Hu)MR zCN*0={Qa!kW+7q$@spCmSIbDzV1#Cb_^gbUmb1L7vHsWWet+o`*p`t#4I=M4?;e*M z5NI|7t0Nf#e}@QXxxuT#rW=?!?!!tyFfd{~b~U}NPlqhjFmD-ABSP{d&yCBi;(_q+ zu+c^`@^hIs#KLY74Uwrlr=UUf7{Zj(jZ+5&Bdv*RIKWB5JxpPd=mM?di+*5BQkf-L zhj~+-JoAF5IU$53iISo!&E7VW>0WIB6}<)}4UKZzj_wBDNN>M792q%%P&vFBhlk)# zfuU^FV1?Nk7VVKuL%BmLm)a$H?C33cQUT@zy+E zdelR!8B|R$69ho$NA#zl86k}zp-(r|L!aCSKBgV5*s#*jj$A_&i`S58}G>2 zFR19Qxj$zQ3-|B20u1_265E)k6b2*kX4y-k|76Qkv3Ytw2n~m;gom?Aj?sa$FOth= zs21^nGq+Je!9ZiuB)7gFGog3h5UtR$Hs}%k-v>HkNC@p%Ao}z z#bGU3j%_v1an+_}HeNRUBO$a<4!p$Yuw0{slO+@lQRsZ5yXRND2@8v>bk-x_i=af} zvM;@f)4-=4`I;Q^YVZNr$dhde*IbZ3;ywbJliOlzlvXwnn?77Rz4=X6%C4raI@({h z5akB}E8RJ7ovfMDFfp)XBG@_M&n^Y3eGN5JtIxGp$wYP4pUN&2ZNok)TJzS)9jaez zs!sOuc8ul%2C#=uoRSjJ%*Wj&ey6`L%7p7Kr2BE`PcN5wVWJAz3iH!>97e|aEZ>Uk z5D=nZjW-9buiJrT9_H1ztQjPugheI#Xc(g}0L$qa1sS@o4n;!gRTh{sCwr(G^)1EP zn}nR5!R2{`keFRBKnjofs}GPfDJDZ8s3!G(#-NWO>$_rFyHKtaCN;I=iMwI>dpcic zBw15nIKlPbV**^AQ~fyk`1{7o{aLHNrs7|d4{IT2<$xd9E4Mk zyDJpUvJuHkiQAN(R66`~i{Z%P%|w3dc6qsMz#fB^ZW-M5zxjFA?FI$8>)LFF=2F<6 zXOTQyA*&GJ7A>VjC)3}gXR3wnlYrYvYaEli;_GQi0NgLWLbf=`{^B{1Fpu2R zJXG=KkZfP#Rg)ZF3pw{dU^zs?bO;&{ffz?1cG~+n5L6IUar^Jh#(4uXsWRL)_?fiQ z(sG-~KtvDuEU!X5Yiul?QOv=83REG3GMQLYg`Zt~@7WdLkYL-0e@&jl2_GFPwXwKP?bC)_s%{(<{#l4}`kd_;$-=68l%7yD&7t zj@(_(QUIg3#3zmjux94v0Yn6ieWnF;AvpMMuKdtWwd=SEY&j82A-p>eA3~pG!J$el zLx*?HWFsv2?U1VNh4odx=XELr5mY{6&t%r8*A#x)bc=e2#45YMA7M{q<|-;9v-GP! z)ozEcQCps3t>V1RZ5T?iF;3*0I;5Sv>vsgBHqrgl5q+GEl+k+%0n+#15u0t+2M`U_ zskyKl8bQDXBfs1{djN!K8XA{MbrH3H)vZKfTI!J`A=!PPV7!F-UexOnZf#_eo9C~o zp(fD?(R`p+^KD_F=9yVYG$-n4qDAO*gB0HtgGeW<&g;2%(f+(pB@hv+BU^Y7VS93N zuv2%E&;JEJU@L{)Ya*;u#qO`~3!v(MWk|v&L{$mE7jq1s4O@FFz=nKyhzh%KQ0Au| zye~;Z*tv-xeR8OCIm4f~n46L}y>c1vyiK3nP|cTuy+1I$%pyNngDzJhQDZ^RH&+}H zZxf7!we?pH!K0W+OXG2_DUYPn0<98$dAtbrhZcw4!I?71n^GQK%$XJcfu-Bm`UFFy zRYqeW$2GHB;%@!Votw_hW3t-0*cth@5*$6s5awj(9M9dOklIz=(_qz1Z)j*#L+WJ7 znThA??K^fFn@&U3N$5{^0(*BWCPnzSC9jN>ub67_>iGy(E~Blzjj;>tbreqM8f{I$ zXL&@Jqiw%TP^LjUfVmJF{0)yTNmJs`j50nuEbkg|Xme2Y_=it}43lhf6Ed5K5|5hN z5>ui->laK+_=nutsFD)uzO|00YnlkDt5PReRWogix!;wN(BMls&O8R(W(Qt}z2vu5 zaFF&K=1Hl+?6mtfs23)Le8;6#h-zYvCfeCfHh-f!E2aLB7m!l9qpqM44Ht`lT58ok zPVvb&Jfc#Zj@F29`;L%uf}mMCN$Ys?6{3rX1WIiq+^E`nh0^z*s&V2&YJ609wW?m5 zxA^<#M%Sc8!qq)%c*_*OjA5itNw9t+lWq`D8o8plhG5+nEk)wo3emr&Owi54-Q+o8tGe92--Ej9GABVRZG}cSUrs|~Bp?@OGaC>n z4=L%0Pe~GB+@N+)b|s(dvrrwmVw8ic)>(V~gwbIc60v1ru?hGDW+K7Q5Jb<_*f{F7!zhnJ!C0!uZ}XC zi;TGByty&dTT(JcQs5N>SMz463sWZf42U^J6knSYkVeV(N|QI$R8C5dX z>J%b5+*BGaD{5mMlpZPXO(fa`WqJv`a+O2A4NeS^`#D2H-`+p?(tRZnZ2SFZw*O~# zJuufUR0ORJ=mjc;1Dp`0>JCw4w&2;hxx9B|kz=cRY3ql7Pq)CaG;)49!$pi^GfPVr zlNU>V8@E`9UY=MjFZVkFR>!=j+^pXMRA%3udM4Uizo)ctaBp(jAg5YQ3dFeI|8W{E z|2&^E6@tQ3Rp(r;f%kjD2cKxvi5zDs>}WS6B+DlcW&T=~?sH~!?)4*eU9elDZ421W zGCmi+vVw!Li!*2Q$CaJ`*RNRagLQg&j6=-5>Tz3R>11q_rJ9(aT>st~k;g5FZEWS1 zm2!*3kZO_vD4TJqaqG-lqz`H}^&PA4QR#d!x@upHuB%@OPN`S4gLkdA^g)BoQ#7=* zB!6N?Y?7BN_WW=PHaQ&tn?M*RbqtPN}aTEmlpd( zPPMR$Qv_$mV&fr^W^BL-a`^=PEnfwuf)sgVdP$=V(D6>IcW~mXGa;PrC$9SP#D1%f zn{EuFAA?=#GU?v~cOSXUWQhX%DS@Hx6zVpu4RuxW^V_k5`ct(uSGoPo6YmspuR4a& zI#bhS*O$&O)i+@)No?LQlIDN^7SMN-uWo~C|C(;&?$hq=Uk-FTZIUs|$BAKBRo6Ta zYx*@Ibe+_5ElZf<5@$LJNyR>7wv=mA2O|$gV6x-^gl~}q4>=Qb0PHKH*zda-A%oFD zv&{B)=#RO}zd7=SvvKcY2wgAnkqqG%)ksu7T^Iac5&ya(vqj_If0w!;_br(!0@^2I z!hhN)4dJ7`EhywH*ERsUy+`~Qysn*})ZtiUW->o{qROz&$luS8LWug>@c}p;JIkMc zb&ox3Sul(Nrs5W`U@i`hoj1nbL4l_p0edIA+PW&WRW5CVLP^9ryf_K6E6S;1BCk-^S#ZFdl+NUclwN6D;QG7_ zJc8Z58OZkFcYS;xoJ{-8kAMk%hZwMGyq@4=BAqd8*7HH~>fw0qTe{BYLhOyKji1xK zu&^*OfZ#GmAu;pd0t5sCuz8<@&(Q_sm2G$!EC6mXJ_y_j7cW?9lpe9h1vcN*Fdh0M<@Psq^Is@^ncul5bMGFrbsOkO^2Hlt-4*akL31*>R#wF6WED1ZJ{DPiY9 z9mv<=WknNK78XMwTf@)@fTW*Y-Q8}(emRcCTa?VwTde%97j=gQ;;Eo4u%ox02Q&QH zyZeX14{Omtf@KMSUP;spne>=oqr9rAsH_YPG%q-noAz@lV2Ky<(S8xJugK+mlhSWQ8{U&`K)Z$~C`eaSAI5X)p|XT80u(H z>zX$%2i-N41Zn=Nn$@H%mZDMlY{i+$^SShDwHo>#u4 zf3-ckl8@2$=z2odDlt6*hRa$eU(G^})*^K2Y}{Yk&^>N1w(~?PA3vhrf?~GB1d87(tK#-vq%Pn79T&- zBur*9Ho8P_oUw6wqvmZczS$j*kTu5wGS|lE%`Vk13ef%DxV9A9Yca;tOLtgvcr^m*$< z%-1b!DR{ZP?ELMqy0|K~6aw9Q4v6`Cl$L%as~2S*AFM**8+piBZ9XsOg}~gWwY5bU zfBVTy#8#tH=MnQqSlSDCAYzPXk$biv@zbv1@Es1lcJte2H?L)@m9#JB*{%&=I=dGF z2$ftvm=#>hlkVpSy!>Q6BG0MY>;c>n_m`HZ-y83C7DbGvKhfj&72tYgO7Vs&F1^*g zHw~Ve%L0>$(xL=b!y}Fm$OvRG^o!XddO^hy(s))IZXSWpO(ZqfXrk*w$lrJ%GL40{ z^FuS)3HTbL6&+-Hzb-c+{~~D>$r!y zT4EA_aei7p>s-`8YaKE)FsiEL$`pbFAd8fYWdD6TZ=04IE_%L*m|CqFyq__OD$2** z>LlXE`7VsqoIYttLexTO- zWZr~>F9Qxb5JFB(3vN?6H6B<1dwF(bYGC;*yFjkvI1sjiGwTAf3Rp#*!8GWrf1V%TQ16 z3OIQNs8+|d^d91pr*OCW-fw~{Ig%e2A?1;ug2JZ?<}QkhRxj3!-z@^-Hs%Pvr*U(0 z?ru2PI|Pz)kTReDR|^1>h$#+|nYh1)@6YeItzMiz7T5uh64#6a{Dj@4C&l>PVc%Rq#?v(rgQWb1TAPCz&H-r##e=h+X~14~yb`vL)_%RuNz zh?VK~{2b=-RtF6;Fv@=VcSgbVTw?P0NTmea^@0+`y0fkA8y&NBq?+|DME%h;yI!EF zZ(NL!*C;zXv(MpvBht;wR(N!cJt?PEz6;lR7t-$OKIAzgH@jH4G!&!S)60{3(kO_9 zZDpMJs;e9?(1mr;aW1Vtad3{n9p5?XceD@8cJ(N6Z#Nuc^g^UYr z>T|t-5J>O{(j}~BjZu@s~xY<92cG zEZ!njBCs2no9n^jI0UQU@7{Y%7&XflMY5q46%)mHN6Yx^DSE$~FbGHML6c6cDu#~! zxjymA@!~Uo4eQ+eHSUgZtAy$VMt){@>;q$jUYsAd38wJ&DUSbKky(Os8X3)Fad*xVX4! z>1pcr(`#*Q(%h^Q#^Og502!DY6xFAztxebIS2{P-)6>1-Yt`ZT@`l|XpbW%ofMKg+ zV`~(6kc4RB{U_KWuF7t;AY(ljSlM9d8n6pw#|5yu3LAA0TN`HiUu=Bp0^?G^#q;3& z@bqgCz!=#t*Mw>p0XJvJDS{C;85FjVJ+(%RR0tyzOHFNkZGAQ4cNSpT4HYfDLZ^NT z7N!a2(iil2^cmG9)W+`s8hu5ZMK`~&kPsJ#mKdkCsl5gKDMt`^=SA>!V{9`2KmQs< ziJ*201REi;iT{i90~UX>|CeQ7h&nIOr_oo{*Y|#ojKEwRkWvy|O+1G~+#UP8-Py@> z0#S%wj>p0P{xCT{y0WXzy1J(4-(L%fAt2~<84sWcXkcl8H%8R& z*siIm&Be@ER#wRiz{23*#9&n|1)f3+ICwa<$LjBmNC+EGUL(Ehy_16s z$^YXgzQamkl30u5SmB0%1atV;Hv}eF2g(E$n)=rawQ$4h6(#C_?n;QWfTCz)T;hH>mwa69e7Q_gu&#c z*%%m{>~E`UYp171A8M9<7&0I-%!vbwmo9K@aFsKQ<)#0PU|DNBiV@_<``r3ye|7V4 zhZW1R1Jd2z@0w>%I*;@&zq_oQ33Lhobh|U@zr-oP+uNJ0tSm|6FlVb8$^x5Fw)Ft; z^j~C;iVv0g3?SD4u5fuZWhLb(i3Yd9nv$Ao;ldB3J=cAc(S-PJQFX&hdcxK9_L<$% z(h?wTb$mX3c6PtBZFnC`C$aFCy)gp(^|q_FHt&VkzkD5FJgfE}lp)-#vdYa7kEo0$ z;4**O(yvNhjyY>f^AASE<8xE;)cBV!o&10Bm>%375Aw=R_kg|x#C5M0X~>JY0V41; zT^laNsII}s@@i()F0gcJvhz9TX)KDeO(Bmb7wGM!aF~%rCqDVyC9h#FJ8lpzjP5&q z@cF%jE=aHf09yb63!vB30xgowTE{g&o7$VaxV=UZvdWmsXH{1-lF&FTc4!|m=ptn< zPfh7rS~5Ak#Q{_6Etlo)fV_bD)6bylLN%WM+&3!#1MVL0k8G!-q2YG=xC3bUz5%SW znMWP3!lt~ZNW{$mUA-RDv`cf-C6;@9^pHbMb!93SBM$I`$uwMKobDD|H<4!m%5S*b z$ey!K)QSD~0cia4Vt_=S(Meh2fu=+B_m6Ipjk!6DMfdauvM?8w5DY@M&0d@Xp%r9- zNM}i7^~Loi&o~c;5a70YzX|DAkoAQz?C)>eJ)TPJHHpFq&+>zj-C;@5GWd%s((Y+>8Nw&=xpI=QFt5gEIoq z)ssLV4wie}&NkZffpBZK`g%c(r7~hR80g!@TzPI^+c`MJdvrKEKOk;65uQn@^#H3j zl$5e$1|qR=aj^Wt;%S0JS;hb3!?J_T!i3n~>HyV;7Xmixm>f=cwJr&Q!Mi&~wta&M zq4Jwr#=pJzo$dED+}KHvNuD>J$>$=l`Bia1nx2vtNKeD#hWZ5>B&e_7{T~`g!jPI4 z1V_I+oAa5@7gH_!)?~szDAIJkW&BhW|(K% z+C2e`^ukjKep?VLV7-T=4?^EIi&mQgB*UJW+C(bF_xJY5d})bA3Fk5?7fIjU037c5 z!~?4|SiR?#EUp(({-XA&J^^x(+~VBcPRqi>q_`fC=&^r(sM9Z+p6YHgp>#1sCp<;k9`*Ju6 zY#u`9N_d4S6Pz1^fu3|32q>GrSjdyve$QT=$-Ks}P&0nNn#J~bcz^nrY*B`UCU<}3C>F)Yw+clhw zk4g~AR7qJiz0;GJnMU#HBCi@%T1x7#V(jP3qmBN6hOzTO%lM}~6EEvn|FTFfEHw%$ z1MD5~0SO-#GI=UUBM_|_g$NzS4+8^b$WQ8mzxD(Gx&|=pgR(tqEe@LA*_K9$i6)Xy zu*8Z>BCgdho1Mvm{nxc;Y?o_5a`;uyO$;F?{SRHFGQp@!!Y1ci_X;cIX(z_p za^DpmsmUIePNe#)(t5L2__`)#f%HFqyuR^?avP*?Xup%NC~5-{OkdV1b>evdc+;RJ z>|N}^!BZ%%YU{f*7)Hn$&AJCsK+cIDP}*p_^`0FS@7fa*Zz=X#q-pnl%hQ#k8mseH z50pda2C0P<{9AKlf3yZ1fI1l)Aw$*CmR4tJ`03X>5@nuMcX!XT_7KdL1WAyt+ecfJ zcxUKTso}8NcoGe3==8en5ue1q5^Qih?g||cH?TKLbljYrcIO4cunbxE%zuXfi3v?r ziGZK3w538{TQf}l8_mE_Hpy7(B3Q}S>@C`u`d&SGxd5<05}f+^hpo%qLE~QpT;6II zxwq3btR4+)4LSrje159Vd$KCJIf|`<(r)5QZRgPj?aGBnef0LJ*z?<$1 zBix&scYVCPyhK%kM0M)`fJ8F1qJNOuI}D1r0y=+rcHMs&1$YY+a_NOMH}~&8^0pjq zZOmd$^z_8MCX|NLq~;qN8isrcUn>NRSOw$-08o5Z9tTTJMog7mP%tnzS0`zj=pt(MnbjhNr*asBeAwGNM5XcAKlK5sUq*T) zod=`K3Z8POg|$BYyPt&M8VNlW-!~Q>Qlcd60Lu1X{hxnmL7eGi zLZ>xXxH?0qEuY5H0A~J?p+k0_bLwp0vZUu*WM+uye=iFaK-}<9=GoiX+Zsb`4>K2z zNsR$!mBX#ACcOtMyiBOat!aPE90uxEV@`^%kt^mNdfnzqd$rmB)dC!tnVq5qwu2?| z_BnuXmK|Ao`4_4Z)d`}xBJzJy1^tMuzncDXkB4eLgI6kJHe^OQJ=HKy?y@T zPEp85{3WtHSOZ#S@^LIi*FFZHj}eSKY+P35C*Ry)bMw-89X3`v=@)S2ub_zJx>=pH z4h8k`PQP^pgoQyRQ+&2>`k`|NAR!@$`;=5w5qIUBz28{-!&!c=T1Q!({7Fcz;5b#t zY7zB*R}Q`~8g#PV?l*mp0F#P{RPrJ%lS3z|6BF)DkVVAj_dL4`{p$6Ccs9E^yUxr( z1mz=MEIz9GM%f52tnE)KsXW_I!U_b95mM7Ia67*=l{hdl?e{L?)Mtug4e%wrJHrh!o6z{*Y5&j}Yk z4?=27Gl#Cju|Q_a_E@wzExCEsiawp>ZfW7Ak+m_N61NV@)VGA>bagqU-=%>NK?mpu zs~W32T#3JOIIVoCC$GCPSXV{Xs*XyofMO-6K0Y2;4_G3r?x~4DTok|9at<{11YFKg zK7g6=-TeW3&L*of`m^oWLFKJJY!>f)r39lYk{*cV;LdqR!@zuT_+T4fl^n^gbLZ4v z{GW3&a;!X4iJX@?eHT>FGMDohm`^aJ%@u0YwLqomi4N%mPMX$iiy z=$&C^T0wpJx!wDNmU&z{*c!I-1bhC9QB+ZpxxW&qAB&r_q8p=YDu*r}I)Kf7I4thwH@q3KPUtOQ6BkO^Uk9JmZ(vbfxibxzz zaRUHNQ3{`Ms<2a37ic*mr2k0goh_DT?i5;90s%ZA_ua7;(AD?%HCQoU`M=f!#${<~ zJWf-vRH{NMdrT!C@(;*oNQmcAsi<2_1}H-9y;$}ju)wF+5w!#qO0HiZ8a;(GefQ;=IVn8zKt_ z{t{D>Q^o&$T`l?X&%9UCXnF`5V0c=Bo&}MDB9FdhBC&pBS?lZjF(MMTK)MnEZ1?(C zPq2&q=U;=$OXFi$&2N#7-T*`sG#@lHq@(@q{DW5R#|uB>%bhdN!!{9vbgaeWdEtUM zn|GVws>W`hNJI`mOQnQJ>#n<2MEcn$qA;Vd)D6rZ7Z&E-nml;yK7Un|J5U3@Q<+Q- zX;+$Szmg_*>4I?=`0(FsX>ln^3JZT#L8)EU-I8Pde`RIz4_*!r)PwdF1D=F%OqVfr z4(C3sYq1*W()HY&77=@{!1HWKC(oo6X^{M>i5^K$Q6Y(W@Y;h~X*sN2u}DSp8|bc} zw!??kK+gQ8C*YV|w)WL@wnEnx8}gk~a&@@1dpplF$#Yow)?VHai~pAJ+jg0@<6P$2 z8_~5dWOCF=GxDc|xWrGHzHne*5dK(UTj!F-Xcn~djADWE_;C}<&~Y<13i!@XqZt_x0+>(QY94y`~jKJn2@BF7t|wp?$A-#o#_o~O&v zl$Y0+wu+3+t>x?_?w(&tQasdPV~0h6NoMu};yJ7|jNLUvTk-?*J1YrSm*6q=iAjJ6 z#=x7gG#31ZBiLwORV|I4OQXf36IvR?yG!_0HgiWo<-R^`|LTzjgib6k_3K$+n6bRF zq@t`mKtX1E{%d@|>7s&w!1@9($l(Dapt7=)6KWtMo*h$FSXfwRy|e2WfrrWT+1LB) z)N!w0uUxQi$JfyWpW=0dujf>4bHN#@4hKEzKOTfYgg0D2SiMuKef`thlatkrofE2K zVrK9+EG#UdGdURn4Zua$4alD<0vKtPTl%5yT|KRr1H@Q>Hjn} z)5AxBf>6>g>qdl8_^74H@!D07OlWMIv7jlYGS(BoXbDLcwa%IHh5O-R zt=^{bX>9Ip4#3!^bl<(+t_Kl%WH@a1x`r204G6iMDVb@0-BrmG`*u%Jou^_lZ3oL_ zaF_Rk$Ag?w0_DSbt;aI@{Sb_^f(=f#IpG#&kxHq>pIn#|Ir5om0=z^%Z$jYfCLt zCXa*pZAH1_psa#95_ut6ul`qD%eA5E2^r~9JpKHv^Xl~3u8@H^OPb0-eQ>D% zoeu(R7@sb-h(d;1+<%r-RwqX~RlBcFH(N6+8!KybQCI0fw%-9SFMviloD&gqZuc_q zrSV!ho9aQe2_d{S+!rOFm#RJ-VNjVMNtB6WeYzS_>X--R{iyj@DqyGmCul^{0tdHV z&3f(*oq<7Dra^@|v9+3!o&C}AmW7pdcxVi5GDj);s|gYslBuPombRjzf`XQkRvEPi zX~pH~{x2X;4H5-p1O#ViQ%UjSVP(&SZ&Zt7q$L#<1Q>j6 zxFs^N%kuvnOn?VIJ2|ziEeT6#&O=vKtZQjmmRXsfnk&xCm|IwItbaB6Qy&jFB#YPB z9_{PYV++X1eLs9d42=&B|3@TD50A~IP56^RUq&>`Q7&4FF8gBqf76Kpb!>sC?LKzo zzdoFI@x1gOG`y@s5V7wWfjv0S^1erL@rYaDy3Io#*l3*#yKy z($`j%Pmi(d$@d97!6TYVhM%Vgs@*#L_pv_a6;vMYhqcuMqLHrh3i^(dn1X9`kcLBb zAAxq*2Q-f@dl-#$GrCE!(==hT2Ca>$y7nILIa~ zA30lfKOMI3QjgzZZ3tsVbv`>Lje6}MD-^#oo7L5pO;TO(h~(>Za1TjRdT;<3U?X8m-o^m>~gPvfO*nB)o z_&5cZVbLk7R>#gTN09~UMo3YK*-6WW46FRteR%luli}@+cLXM;^`Ez2`>_&NFJo{lHMtWfH8Y>kv(F$a((HJ; z&He^#L;XG7$owob6FfX_W4d!woMrYM=6U(aB|ZbL|CXlgDEq!OL>zSjST?@9K2p@B zG7DSDQJ0Y>E5vqyj!5pe@WxC#(?Y?e`NkUgf`$>vw4yG1(|jEnR=$b(YV3ow1Gp1~ zl)o0vwL`_4=(yo8MO|UF&?-jpEbAH`2k^sy$;N$=z|KGn17Xrg%zI^@+@(N`|MWt& z+A>y5KN(rnXlO#gI$Y_~*xwPbl-H7)qYqI8k@)aiypGEU0 zU%rpU+t>3JyH4a4-=8<7x3dRrgawnod|sP#-CnSzEZ*3^Z&~9mpfPgLrIZ_5uW+=8 z(bRV>6(MW9j_|$^R3mdZ&qFZIe`zd9;p)8~#N+fkj0`OP)95<=SUi+tFMi=)%^M!D z7$V}?xK2kmIkwu|;(rivgb1Zoiy0XS(fWL3kfch+?I#K=R$juOBVD`XKJxWzDZa@$ zr}phz+Fkc{J}+VP-ilZ-#tnH+p3uVRjighSLPsYD;NA0JSi$cw0HyFN)hpa{re794!?yPT2g{xcWR!*fXKI-i>$ShnAh|;>>#@3#^f@?%b(sNs0wGx}+bH zFhn-JAZ71zL&9#{Q>MbL6s8x>nAs?>I2F$h^m~m5v{+sAM)$t&h4R@w=LS*V)upv= zZ`60x7{+QB;?(ouYejMW2U|~FB7Zre!X?)=i)|dQ{OXw2`s&7#;F~@(w!h<~|J4GZ z?tX6Ht7@)&a`qW4vf*{F+NU)TIrr({MKlc4#1!Lcd!KP81O$)`%*afyVQx?Zz2e^@)rza3@sqofU(#n0l5c2s@=84$W71NkDaBhow8cJa3 zbP=g~f&r%k1p7Rk1S?lY%E5|E|6iiMKY~SHj}m}02Ob`07FG&2&aKB7j&hIdi(>3R zwY&nn5NfU{T1^b|&q;PG2jlcq;qpocX_ahojgDex^BjLCuPQP0JQVsKC!P*^R$Gjm zx@8ga6Ie^D=`Z|_X8H043CVGszo0~&9!tM}H|4>#$v^Q_ZEf++HqsjWLdVuqn3Ff7 zHXncy5m7OGU5xEA~tsHD~nUn91n9qXGR*m3$M^ScCqC^|qq^1T|i@>Z}G{b&&~SA~|21tO9!!$s{o@J#w-1D4)5GTBjYMIlc8E83R>^f zpVl->JI3XjIt7J4<*B;)pq*dDn~WTnh>^)u4&Vz7%IQhf=S!{SFA(Z2IUSwS1q~87 zLBu3v3Gn32huEwA6H}#FSyW9W6Atc6A6oWqBM+lunrS1EjtEsWg3nCpsNIPm$(Z4iRp?15!tH`(grw?mPl|lNN z#xTtkgEK;4b2zmns@oFi2^o=6B;=E)MZ9y!GcNyJ%@BOuXLW+uX#xxQ(#96^fvzq> z{>reNqcZz|$6_bmzT__DbN zC1}T9U<%**YGLebSTk^fWKtUO{`}O>h&L{XG;=F(BQwl!WK-Hb0X73F!)fp8M*l{B z>I^iqY+-vna%{V&DfQf8N`=sd3$7fKg*_Z>*4LIa=;R-bB5W~e-E&yJo4#%j>uAwU zcqQKZ$Vek!W2617rb?!FX1crwN&z(xmaAn5*=s|uqlNEg8R_wS7J6R|A-P?tCtmiI zJD^8upmKO*WhUXw5$4e1QY<+xuBJOPP|hKTx)TrcBTv=O3}^RLWG5J3jZ<4);}0g- zEA{ZSuQN|qeLO-tTU{M5Xl`e&b|U$`)P#_I8!vvgTbXV=la_K`4l}j2cMzRNAvb@% z&YJ8(2eV`3MIPx`&ZdEa=I*AJdOc{#F|AXs6{6G!6n1T>%wt`&8a_^J>e!Rhcr@ke z9s{qsm#sd_C!FQp_RG_(c9_IKl-rrLyJr8}Wljs75M;iTPyM50*HRt9eIH&5$!WHO zc_^j8wM8FyT7vy$+qLWD%=aZ^xC#?~{!Dc}uCx9Mxx`KNxSj|?*PT|rHPBu@wJYp}`RdvS3yfjdCn}s-*WXghFIHt6dDD+8rkh=& zM(&+Ue3c^_MfL}#w6e)n8fA0~M7sBojD2-RtGbd+C51^OQljfjs{vX$Wj$vq!X>TR z1cEqeq4H|giYtjba`0vDZp>rgpi0wN&-L{*>vr(dy#{yi>baNX+A?ya-Z6aG#u<6S z(@f`O=tBAtq%`*sHKr$$fgs-DGoV0O0nr<`$Q0@1q(*Mq+nY8Df7xl`BijbFud~Kc zYr>(HDWG+ffmINeR^gOF=<$aO(hDm^xGz=~nP<&L?RXyeqa~q(%kir2H6cgwGUg6Q~zJOkpk?`dl`Eh z33A6N@H5_~br1Ig zeuv#9HiJ$HUweYDTRdcW&Sl%i1QG^kL|EBF-XYSV60iiE?4u>ii6n~2yX`9KjIMeQ ze4KZ-Wq(481r+>MHsrc{uJCV(>uBWYpOs&5n|1*AM;h$(CXydwQ&Kc}>L-6Z`?mjq zsYYb80c3yMWQWFhevQ(q7o1;KU#Pu?Lh(4{Tt@34_&^`S3YsaVPKN{RJZ(*{;d0`vph;NWz4mf>17=U2rEH4Sh9DpEmN<27>^}7iQ`I`C=t#jv+js3T zBFFYk;8?%@f?i-1Z8w;ANUBe$K*?t(nQDJo=T>xoO00$S{dyWcPJkQDy`>I|6NN=3 zOwHwYMj%qbay~$^5MlVZlwif+8D-#*9(C7hp|}WCLEBRIQgJ*)k9Qlj}FBHdZE+rmxq;+9&~=qj5#3GL}b}Ef-%$;-!Uw!nw(|N zf{LL#qtcamNu4x0lK-gaOtdkwTCZK*0Hu;Shbi%q5BBs>mGOfQVJ0uyBV>PteI-yZ?;%>IZ8?pzBm%_=@s9Bxw429#edk+?8^ z!TVQL3jLIpTgRGnP8l zh02Ftfrbd}vt%9;L=0YmZil&UJMcy-!BX+#b6hk{HbsphrLN7S8{cv(4;lY%^5Id$ zA>&+eU709gMBfy-;qaC4VL*~|%AGV?Wsr{W6ksmYDXk$tbX%9&EQ9&Ff)ESOm zaDy@V#!-7S%pu8~yv9Jw9AvRMS`B#A5M*HHe!mz9l)mnK4KZ zz#*c#qpyF~qj-qAccWQ1Bgi+AZW!~S(cva!^+DOu9gXxPE56p7CWrL)nIvzs95^H| zi2<_>p&B0^RNNi)VX8TZ6tEo)xk2P#!8K7>_$??Dqm$}^SO~Cfz5F8xOmesFAfthf z=b$YtUIB&0j`2#6+A45$e}c)4LZbz^x?z;#W3V>V)qw4RL)<0$^*KTVKhR~pO2hlK zBtUy5_%%~ls$EL4QHxIM8T!Ut3&Yj$h*1K+EmM+mi!8c&37&M(=Og-HX5CRB(Q9(i z;~I!@_ zYl97e6dp=;+m{$`bXyb0$5K7w3!iAtnkle6_W#4&J9TFkZC$vPs@S$|+fK!{ZQD*N zPAaT872CFL+p5@3&f5EH`?T{D&gFZN){?CG&NW8w{TYL2E<0iy8xGotQIz->9S*T> zIg_96Pb)2Gy+!Wa5Rc^_DD{SLk%u@|II4k&k=QRo&lZx*&eyvXI|p^O7J^y%H?ZeQ zYKog~2y;K%tf)H;5B*$oCU#O~XGUrSVeTC&J`OGmy6SNA!itt|`@08-*rMF|l2|1m z8}Z{1U5}p|_wK@vXkIl3c8bfb;U;CU6%;Sz;R}%FuH+)C3dM&LS9Fs9DiV0wSS{th zTgg1*?GQ^uh2B0+217Aa-ny!L=v~-sMe;Jyp&%rvl4J9p%M9Qv{_*EW;rV>Hp8De} z?qGr_wcDzBdJQ@H1*11aC_LDLjFqMfqzyhd_9Zr zA)mIW;c3&tzWCoFM5YQ^a^O@#nl1lsj00P!Ne==j6d2v^2b*4Bd~~8f8%?Xe#pw@} zy7dXb+XbrAGau@-4q`!vBujs;nQ%3Fe7Mo=5pOB2j4a)5qA`d36U1R*Pn9s1$S7|qmp-czVH2C?Uas0196mOGoo#&42055W;YWBN-;&ZE;5BstD` zvOP+P(WR2>{Qk(w-mE^T=0i_1|PimdZu*4@A{$Lg<*os*c{ty0|^Cbf(`k_HTdTHU3Rbb5Tt#YP1UJHeX)#U zKNoNyN%5sRbS8f?q~T&-9I!tNTPEARgrX1X87!FRFW+F|m?7>7WNtuRtP5U+a-9aT zK_1(s%XBhAI#rv|KN`f9#ifwelDa}LtTU6q5eR*4joA00pX&Ec{D_{MD1~~+(zLUI zr*6N!v1TuG`}Hq(oS89_gqO0T@-SHdAso8|qed^u4{WT;0Iulh&qwVx7J}nzfN+uZ zUkD{^Rs=)P`DT|3#eYQ& znWoo|+9Q_XDNgxf*b-!}n3Rh|8G#eQ-trSM(p26ERp&6Ru#aSx(@4B?7 z*+3oMYPESfRcV?9QZ@8!2_4ejMlU;u9Yj4A+cMpCc~dIl!cbeei4$hiY6N@ult9qj zd3j8RQL*s&KEL#wvk|b>4d#9$is%aF>|Z01gQOy~sOfIx9iFtabKg?~YH{4~zqZ#L z_CG&)fa4=rvSx$0d2kA3eMdOt)Gm@0QfK$|v`)i0>7vRFRDuRg&E_ag;bX<;R7p-_ zQr>rUM{B^9ohHq?F~|?JyHo=5 z+a4+1*2Q9m9zqkHYzukwdXILlA1gj6?XQR z0fBsde@MUnc~m(<@TNO=JF$fCih7hK+*IK~3@?@(DwA=nii8IQo_`A#BU|UJ%dgwV zIHj#s6Hc`MZa4HeS^q{AI>0%6lNdVyua&vMQuBVv#N{46B6cn|NqhI_2MqCZg`+zI z4El7;0Or+&PH@yTro#1|-7-xndU(F=j$n}0cQBXXKSh$L@o5U2a>JTjYYRdr#vv#} zj45b{UMqh6Y9Ztr_I0-d-M)3VGjz#0P*E9+Q>F3$aEu>BSuWB;cG zpuup_TYw|+*A;hMmgIZn+?)$13ao1t{uuQ>ru+kQm+z|ASHEMTRrfN-@MF0XH5mD? z>U+buYI~4nc)4y?E*wF(*(54b193>%O7Cj$?fU(EIY_0i+nv(TZ|HsRZfIT<25Z+D zS3ghQ*vT!SU{M`NTh}WwZNsL2x%7n-PAC&)Y!aCob1FrX!Ffnz!q_fsy^-*3v_ICu za&Jg-JD=|in%=>uUl(62Bai#nP}!qss5!D9>=Nr*4wL%JiuYc_$E4V-4SLV~pMUCT zLMJ-inD^3hJFd_8+^f0a#`;^do$omq=egGIGo7MKr+~3sJr8V&!Zl$G5}e8}GG z%DrGr4VfpvnSr0?9Ai)p|litmU z2ljggx5-$Mz1&{RdFB44=2%zi3DZKpt+)PNs1vqtd3|7}Px-g_|vM)?Z&&(jl8aiaw8;8`0OBd7=W zV)T_J=)Sp1!9?-no1gh08II9TJn~~tQvZU66+T#m`?8#F`Nz+5k~rD%AZ6zA5!A9@ zHB#urmsO^{;ruP6%Qvob9fvLHX7qYu)1!~YOrebk7zObGktd)Q42?~_8uz?M$2~_H>-bpr|mbG$omyXJ{10=Lhv>B50R?m&US|csC*+7P37hN9w_{>p--QAf{&XAWs9o3CWvREOqw#NjGUbbHdHgW_s? zJ}e8pB<{wUE7OJ)YJLawq2*cZgXcnis|um(=Dtqjo^k4=Q-6MXy1SXQNQbKy8yvW1 z?3^|Alu;Bzh-dOce&OptzFw%#$Oif}HyMJNx-+CYYY;gT z+UZvKH`+MXkGFVJ{2+VbUt74*c4YJh^`ny^+s=7nVE68-t0;~nkg_|wmz@UV$eI!6 z>qY^HEOj<(B=9FEy2JBGe*hH^27jXgCX&@LYpLxK{FS3tKJUw%gw~&S5!1Tcc`3 z@L%Y6>fy3YRmJ7rRVMJh+HX(!Msggp9IA#z_WES8+EX$1<1<;sp3qVXudtWCNa4!K^v(&dxFSZDcW|19nrI%4mB;L? zz_{46m91*BOXVy{h%&q?MSj&P*( zID*6rQX!umU6#YM6b?=AN&y>JZgo4)$R2oKf&ctc+ompi}d(dH-MIM|@uq?w` z9A-D`Ge>aq@cOlXxN-wN=(&HI0iqb5JG;)!>R($ zNgXhl|FzBQM=KcnF>)m;8q&>Ns?ctszkt82hqc+<-ay#5)-+nnx(1!D`FY2s&5S$q zHrD9|R5vdR8`k)duRdVLu31`4j~^M+WbdX zCiBdV4-RLc8{qNd7*L8rL(} zG_~5aw3*k0krO(;a-z0Pb}AkO+XtxXT~Jn%dto)SG^n`K!hPIgHngg-ZZ#(z&mJWA z-wG*e_NOML9d{&rY^=POCG(n#? z^aQDb;4!FG%8pAsou;E26c}*yeeH`TI&=$H{KbxwOjx zh05DKbeibzLwyT?>^jqa6VCq&@l2j)(~0y98M&}rd=96dLmCkXU*1yCM4zs~mBLmI zlQ2Ote_hF6aj1L8ooU`bFVM2qs~;B&{iL2M6{3YPBl<+d8HkI$ROGGn7jml|4Yxi_ zMH?_rFHg6>qTs5bJ-l2iwQeMc@D+tbE24_>p=756ZZNJHT7vYXP-!SC@rbh^VCkz& z0-FjCJClHDR^26)sdeL-Tvmf{o#t(*>5vr1iy>u};t(UNjY)=esnY}pYZEoBj>L?8 zt{faJ&8E*Bh!Gyd*S;)2FX{lo?t4EF+NTZI6HaN}6Qq1uN*5g+>!dGpa)6R-JAmGp zExB>3eLu2_-+EWOa2+xZP4wG}_u-U6Qs~=m1uZYs2P+)~wY4azf?hdNp)?5==HjW+ zhh#?{7Em+n=I0`RqQg+UyKmH~f3Zg6`4MK;Iu>W9)P|X`(WHtfGY1FN z*5;D7#KLEo4nrs?xd)kp+T@!LE*C);u+O^z>4{ zb|)XP%DCk0S*VromX2-uKAR!PAaf(4M)gW~VPezR$&MiVX-R)8a-L!R?hISfUNml% z*rae-oy{3$%4DnMNQKgA&re&0#59 zoJ}s9wKqvG^UA1fw{&@2D|>=PHC7M8f79^O<{73@dIW@~s-+JSJX=YhlS0~po`+s9 z$ANapOt`u)Tj&t@p~&9K;&iib#F&u54hkNpfY6u*)4_+r2}bZ(gi7q1_?n(UtjlFthQ0|441_9zIkiGIsbb51O>-Q3b z5pGbT(g(DR>aQPaEfP0o68P|RLBbZJHr%Z1cmvywQrA)Sx*)l~SRU}Kd^ObOLFqyy z4vsddfZL(6l+8eU$HC_WApzuO-&qM=0eM(tCOE~`>3A(XqXJuduQ{9eX1qcQ66J`l z&zfhPnAs|&W`Kw`h41NBJ$d?%5t7i1==Vs;ZwGH6VG}eP*(LeaR1?fEgY{ovq7Jc z_v-V8D|T{Tt_l==i(l_!FC+Ff!DaN5{E(ugCKjB{dZiJfI zwYgf68^3jJ8M|SMdF_)(HR|Rk+KG!lzJs5koEMaKP#JG7PPLZb^QLsUwv3pNT49 zzImTw&n~0j>1CQusd6jnKN1h9WNv1!c6Xkc6E#%c8q_X?5>yRRPqVf~tcy7H4I0Z^ z0E|SVXfCv6(p>k&z z`W@kQaofjljch#bub+97^!x7`$}cArQ?8}g+i5Z1E%wdt-TBv`hK01-yW~$4dLm>t zvUyZS!%aH$h>mU8H!(1yyol3=G?u$;0>kblx6~p{CR6>j;wu5qCKawRA*?5OE}FeP zA~FUhXW)pZ#iZ^*@%tf(ww}d4=3FtDTVDA=hJV?9T28`bGZZ)QhFX4T7gA!b*B?^V zRE`#qxVfI0KuJL)JKNt?C_WV^40!)^Lpkhu_6ZJ0RG#C0 za!nbbVyx3HAI3masQPTbR(|gocU_=C@)pyM2zyfq6V%+Y36ZsXqeNvu%*WXj+4p5$ z%q_h)yNgMlYQ0uchNBLR;ffnvoODde=ISgFW5#?x&m!;ZCVojPvjA?za45`ta`~C1HL$r@L&} zL9ab7_=PT88{Q|-$Wp<UzAgyKd3M(~g4k&Dz;M&kcz0gv;x2Mr5E zETVT}?wO6@-l~^Z{^ir22yejen!6}_?*FLnvRFaj>&Z9~%x^TejmAurHfPMG7&>Y4@Qjb_ z&9)lZW9VSkV$rENnh8OWW7{Gec91qR+A{v5g`;d<;mnr*QRVl$xX6OKa}~e4zb%FG zG1QUoP}MUFp;T#zgssMvY!v^%OvKre~T5FCd7JobLi^9rp(?*V#T%A22+R8aP87`+NHTZlRWm#)fdCYIpa+ z;ojbc$YPx6N4vi>cHGl!QNYg50k)N%#c`20x=8|3A`T|&)IlQZc;WbVzl8US#7xLq zYwdYz#wHUu5MikDBqA?y)nm{oV3?FWUXxI4MdF3=V6y+&wGj^v!IfcfcU?g1ICYkg zK&CL<8hboTr77&B4(wOz@34aWArd2v_PM`#Y>IH$_+l9>ItqK^;O{|-j{sl!dex83 z06UW|>8mB4`<29H!3-l~G?(G>UAo@u$YLgGL1*4SgXJ`zNq~)H|9Kb#!QbPwaa}1i znT6)DC9?Q;wtRyLy;a(i&EK8Y0d!jth3(KoJ>*VlJeO7dPiM5H_z+K3{LkyXFmGe0e`rQ73 z*fr}(KA5@vtK%wL@BM$DPxRFu%yJ!|DnRPuG1=3yeJhgx?Imvk~RIaHq zvz}4M9D&-yFSfz}lAFzCLJA_ny*S}NuF2WV(HNV$CGgT~tErK}rVn;UFQn#fDdf14 z*3XLj_&h&;A}@V8wZ@YDE^$5^p!L}uTAJ`_J0V5r65C~hrCU2}YGUHp9bfn72XxUR zUC3p&GOf1pzLmappM?ER#fOQncd?hqppStOJ8PIgc5Bl|G@o!Nd&)zze?@TU0}#6G zIR%?E0Mt4}lVIgXtMtM4FAqz{Qh`UFCj`3SHNHIc-86i$KcPS{W2huVp_;m+Z`jDj zZ)Laa=uE*YTYncKxg^T{(Em!*93vey3Co*vp{~W>tE<$5M|TKFAR5F``!7>#C_6Fr z&=uqAr6o??I^tPMc*Hx>ZN3LLW&@91VzsH$k*s(H(|wq2jC_ApYst`HzG`UTy=(iW zH$rpY3*FY^@ZYM(+2!ZqxO)o|#(AAAzWrwQ-mV6)ZJ-=br(Vd&JuVD%!$F=E8Y<38)_TsN-zsXTRY|s<`zZ$Ur7*7%Ls71JqEcR7 z#9zEb&d}Nwrw1A*wo%BRnSMJ>%#$7KpZ*%}GkqJkTlUUqbauIh^!vnrCEU}!3#W#9 zKJT=6TLqsUE;g;ByCU|XA#Px!Gl1@|;1{ z9Sw6FPvWvOa1zs}9%oxZrq>v-7h1oSO@b_rI}n7U#$4kA29^uK{m5KEK&6>v)E6;@ zM;K{*x5;?2>|F{H?uWkB+T;&MInM*OINryJkybWV`yoCE+Aj;C>Ue3Aiih~J2M^Hd zc_5-V<2QF}nhu3qogK?Y-{flJyAQY_-t?@ltVt;^xp`BChkw||cu`&P;bk)@2TZkm zI=b@FuK-^n#)XcJTQ8hlJvLQ1ylMp=ilNt+h(4ULu2@<2D%VMQe<)@c?~r^{+GdMV zKzrART5S_sPGw|C8NTGNxw&c2Re;3lzRo;N32fZ$s{Z?SJIiXWF4K2mmU~lUuNGay zG~bpf7(Vpd@A!q@H``wp4+&A|V zPB9)Bya&)4EbTn9z$l>&9jL;N6tbu#ich)3JjbOkL zq}N36sN65ssXGY4Ox&U90-CCl5hI#AaEo|%KXf1FrWn-+%%kFt)lgf|Xo(1j$l=Iv z7)OEqEc$S6r*V6(fww_p4W+}MTdq}w{Nb*2OdN|vsj4bCQ=S5_m493QirI=a3qRjP z5PZ*m?(HrU?}{3|fbtV<=qAv4*ppgj=REjm)RS@^c6^W3 zbN501?pjdspaXSJ-s1PgyE7sABj$LN;N!3`Rpm4si5w;JTy2u%aU5 z)t_Fu1mg8s!Z$Pk&Gznb2x^xHNQ~533@CR=romII+HWB{9JHGd4i|B*H|Vif6oJ>M z#KC+U5Ai2|l$N#^$pL&vv2ESv;mg1W(WF7g_XC9EzWoRvjS3;}mw$Y}5<3>@CP zuf73OS=NA(1`0(43WNrC?S5>OF#@xR-k(ZH+9nE7$xvt;ag+d0eTsa9*)ngFc@k6r z0)uF)4+FgL@KBY`Q-ZMM&q!cC ze^O93{{|6e$3oeM9TLLEWM^ZMlnx?HEZ5|U#Lt{c=c3s<6X+(GI28ZQ95#A4ikiJy zRis6ptP94Nr+J06vqwcSuq$>)=&enqej^MM+qGTM>6ba$`V1Axwu)UM6Du{O%ZRmf zJ-Ef4Fm&}4$QF;l^1oLv`t}i4wr!R9rf{SFhbT8~1*1$|8zqG@3>L^4!&NV@7N^`N zcQgJ)xo_^r$67zKpk6m3te22YdDEG+I9V})-Z#x*wiI1yZ4R)xz(d$VBMkV3Tp=Gz zZXGJA;#-)`R~9oEBY9o;|toNMvNO z9UY@wHOqQb<~u?|Bt^XgzW5}s{0Qu@xHi#g`R|UjE=MatJqk% zYe|*@k8Lxz{qNfK&f(O!n8XS6e@9;ZXQYzwRm*Ryn@#p7st;&D?%%wyz8;QMSHbap zuRbp8Ob>;K$iR@I76IU;lK?xvWcVf7H&N+Px&Mp{*&x)=W^Gwpx0<{m(*j4kFX!FwFgyEp#1>6VB0*@*EAO?j??g>YW>a_u7OJR+p}q#g!9+)oo2l)sV-&hVpTUU&pl0VUZV}-wN+5bVu3x_1>u=O6-mBmY z+5wJ>yU|r&xg~n_$7TO12Z8YSmj9wZ@}tN-!-eE*%Sj}Vx-50xqxs4#zPeV?usHk7 z6SK6g%#5c{c-(2o1C`-jF3AejW&YXSW|nHxTYbdT)$9u*)`%gV>W|k9-SIyk1CSNC z0KjJ0xPnX9z+~V7mUb9Md)$laSX-Ob#``0(Q*{$BC+sIHAzR-}ZbI_#R-0Tt!xj_q zbbsy9h-yavEW4C^Kq*(71XBl{LXqx;^__I|{GF`&rRyj)D^iZz)idr!*6Vg#yh-bR zG~Y71-D4w6_;K&Jv9Y56Ui!o5c+)HudM3HcLuDo#<@S0hxYN!xn)Z8%TUq~1RpF05 zDP`wLH3FACJ2{_kXsZ7I(*ktMH2Vo>K4zokhoq9fZ3|>FQrBU2iQQHswV7VVzq?}; zLfYqlAD16IH8Yrl;Pzr9`=yQG%LJ*PnE`LjSl@HMxa|uswyveU5bmMk2ul8(A-C+4TWj3a$@nQ(CW`ZgRL)>+%_Bz6mPt-XFTr#jwaT zo0S`1^>y4AN@ar`mU@DkF$w{m%F`uGG>Mmu#Q0eAjl%D~x{_sw^!n(c6ejEt%HD9Y z%T4k&`i9ynfMUjzLSAve@M#>Tp!Tr0|}G4TdK@LmRHbm*i(QE07kl5 z1#DAO*X-E@scIG$xN-xr`RR#?o~Zq0o2`#wm@RtFZdlS$c42V}gd$R{2<>mcU;74{ zy!!=^6i@nfgHd}M;5)i&qx}R3 zenmrY$H*0rdJW_B%v7a}&`+hAER+O7gQ-VF!{&boIyOwiKWn!&RYPV3*&UgNS4x=Z zs$-bgCd#3YaL0mk-fXL3h$DmhqyZ0st;&3MjAH-tmA9p>39i!;5cAF8K5OP}*Qwsl ziRXcQL3Vof&HK5FL`ECJvX>LGgLBEpzb?_13q`Il@r@XSpk|uv=(lJ6>8s{;$B@`( z%hWS~&+$Lcj{sIxnZeU~xcaSSKJ}AgQ$Q;7+}?5O>Fj2VMGXMS&DeMubNK)MJAMFS zweI=930a)$J*hIY#4*UfET#54_+Py_?MxmPN{>K069bF$wQ{w0rZpK95_yn}>EAW` zgNXS?qvb4f^A^u;eXAwNW5;*D9xV7e9FKb69>p7@7rUa0?cG)%Cn1HR$UOZI>p+1v z@4N1|CDEf?zW`|8A2{!$3vKQ7Xudlm+dV{yhwKG_e`?qkEl-Nnl z#$~el9#LN`MAVP#Dic6L1O?Amrp1Vx+O_i_f1uif)V-Tj<9K%j%}W7vC|2(6VNLW+ zz$BKM%+F0^cR4bU`kUOsHWNjgKh@NDE+$#XE#H+uv`muwy%$ayr8Ei#z``J0sVU^6oGe2XkaeD%49AYI)Y^kd%IJUumA9VPd**?We2f zdBwlCuq3cH^$bkuS2$5)2U(A06|tN(&}}i)@gdP%YO1+}g~dLGn^N}&1Dsy@E6E_XmgAsJ-SMFD@^u<9o;<94vgnvOL2D?7 zL^LSH8Fus1Jf?k_pO}^2Ov6#|_b}2dWRua7!U)4n(!{!-KT$1kulNP|MEdfNoz{ZD zgQu&^T( zN^hJ@m5kR1_gA z9q0Duk~5`IH!;bIl@nWNVLEJ*a(Mhg;)PVnnp-MY)D?mi?y@9pTYTrvrt*?%aW9;E z%>T)QgOko-*W|B(#hsOODcLYehfgGW)?ushSyLrD)w*dfR*#V(&&~Ui-|c{n{RHSo z{TbK298;s2Z;@9zG974_?#TbXWMPyJ0A^@Zm86Jg4@S6Br^ zBtn(}5}@TRGwWGfUm%{ds#r!Z+Lw)(s(7&?&emQB?y=Slm`23R)D6vzJZ+{tGxP*~ZtKWt(J+49SV;WUD zMNP)}s@?WnC66n^hPzj1C{8ywGmAb^GuoupxB3SwKd4Ff!ThUqzlKF|Z9VJ|4wCN9 z!G@XF>I@E%@YO4-UBlGeRD6*CCjZM=nHi9V43yu6JR4^@v^V@W4%Y}PmGf1A#Qs>+ z?;@qFUyYwOoyB{sJF?Y!7kj!~pAV2BHGuF&M_segu-e3hXa6xqozo)YtlItE()FNU zn)55GyK@j`7nHx70}76iYuIL@94S)^_`MX?j*koSt5%TSFM<30QMUmtX9ycpZ{tI( z22pjsYAYAL+uK|U;mc{MKyieD-P?xuGGN%;YQv2#N4iGz+l5TW!>cj8hjm4ARnDk+ zQo^k+Yy&0rt73+-9e|4Vn)U1AcG%E7&2~wVN=b z4^A<(xUCgF>tru8t*EVG^G}&mO{9koJlLNOD}?8lKd)E4d)0306j&XpgxZ8_c91&mnp1ViiNrK`rm5Jm z-gTM*TMqwECK;`u>^O%vU`TyUjfULZDCBD4MG|}JsGNZkR%#M@OVEwu>{vrk_H9l>Qd)(Yl zf1bRZ6DBqNA5=6U1Fu%QZR4ySUH)||`W}Cc+*NtgNL!u+ONyOpw}p(x5-6*Z!He> z3(=B9+s(vzW+J$>J(yD$?k%fT@i^l#rP$K8-ng%a5Gy8k2mZ5~iaE?X0a!kiYLm^e z1Aei22vk^DnnCL%mdCgHbI!KioVTn+N+Ml?OGWMN1r=5$OP4v6!r-C1l?53gyP7_I zS&lEW^0&wN%6r^XWF@4+;8Jv1FRAT52nw@suf~=2c+3{}`#m7Qi7Zx*D`O$!$I?+V z@%#W*>fl&6975mySh8=9!+X%i%j17fqpR#BtC%KHWKw(Nvb`)6WU^FJWnh};If1Fr zb1%ah2Siu_g(V8(tJC+zDe^Gdy|3VlTScK^1y}?LP@WFN_4c*`#$Q7@{6O-^NnVs9 zW^%F%{V#Id&_qGuHV`eP^mGKW;Pl?j0IgH$yk>GwxQDRF@4|~Z(T`c8d^_7F65MpWE#y;JbOd*8RT|73rp}^Ib_zhG|*#4sy{U92lj% zmUYd=U3^8$5?h*juM<7_>5+J)GQ|WkO0+PP5e431pqj^AFa&@9lsN~0f@D8am$*Hk zJP$hoxvv?2*B!Z}FF6N@8LsuR=-M5Vlub}4!*dwtU-S33qvjOg!_qAk|A+l)XaOcg$kUWr;n zI9y&ZP7q75rmBZ52cR@i!MFQ$B@@x#l_~E5a;3_fX-L{IJ4i|A!s-e)`3)f5I|OOO zdO3mZ@Sgxx5d`jF-qB78)*T!ETbGoE0=~+FSdQMIAmKfMcm}g*C0*KxC06%0EI^r2(=o#nTKy?J6m-qvG+Or7i8$E0IFCfJ(@Bjq*&_# zz0I3uVKx^FUWv_nsGsvIxVm0($$7G>P&=3UfEZH?mA0?>dmKIq8)@E*&g^5C`jQS8 zoET-nK#_XY6hV4860T|NcteSOJX=5xK^#sVc)RZ#dnsFGDo{ujLb9sZS@)t5guhz>PRI=;O!L}&(6iU_B0bp*y^GPA|m{|2rCrJa@Rb&1-6^flfMp(`JF}+_*Fd$_dpea|Q#wQX zTQGz!Dp-%k^9~0{P!W8fo#~cSXb18>AT{>DfIV@QLtth~4<89S2&R4B=0xd)_M1lt zJb&K>Ha9w_v0v)Ru#iiv&;viFmB7({Dr8G+m=H#C`Z_rYOT zhTu5e{e`yL8lI=Kb*=q(jtkceI~)N=&T1k=Cp{z~itWRg$5qvt+o0dj6YFZsB( z=xFzalhM)e>i#^A^dR9uLBd=qbg<_GsHLJ72e8vyiNAuzw0Nm-omPD~yXk6;X#nCY zRPAr$`R%Sx5W(BW>Ii7Skaif_vP%A^1%z_Zg`9K#885w_N@!I$+1B4DGocw|@N-dT z7G6S-8Bs&C{W-}NdJygvS(3Ujff~!PcH@6$3DLWu>!y!2?kOa(E z1_-5_eHUmgFmQ%ncr9N@B_^sMyNnIi5gM14ii{zfGde^n?%B5at?a3yKagHe=S(nl zfmS*Dbv4J9*>d_=J8bdkb1$j3It=CxW?`GqhvSYP5>2=fK#tbUl>SI`BkY*aF+${v z2$eE*Re8ixlZDJn74EvsqL2mE8E)|U3_eyaZrw}{IylFBvMv$WLV;OR5V=k%&zrKn z%+qsN*m@&?3dLm7oWrx2^a!o#otMlc15tWD6&@*_B9pN}#!N@GX9to#^&FWaoD>z}nx z3>>C+2EDC2K&=tayG`0{5+Nwyej!I!09wtH9~Ncox{g#+{CW|>*chN#3-bxSJlnNv zJBBh1ja+_8EyV#zHU`*H-0C@@;scYo`~)%kg>Qjiy%1Mj8rL8t7U6$Q9}qP2T@(@T z!EY!b&%Dm4{``Vfev1N<#yiqS9$2JLNJ(h1Pv3vm;^v-gys5@37;t9fybU4*@@(AB zvycsnag9E>0lb5{ANV+*J}6p*S&?t+YL@Ki3GO9c4%{|=N4Blq!8czFd?Qh@Sjm;o z1UfWIT4lZC^Oo02_{kSZ^1c&#^6FJWmzv8JvZAbw=)$`jZuweWkV12=>OIi5My=yJ z(wyaEpWlm7G`?VDgyfr}owDb+80_~& zgMe|n*9vg6p%8@@;5u_+1Ev4B5E2>6;3cqNUQ}S2psO=lJf(2nAh@>*@k}5yi*nK7kAw`B3<~8=Ffkdtf)T~zFtfYL>ck7q4Sni=HZgsc3QJ_+$5sT7nNQvhL z-N(R@@>@Bj#%Ca8`s6Cezzf6I#V4$SNHbse{5riLxy?j1BksX}v&-PYM?vMPl(pYu+&^GBmlfJ#Hyt+moQ=U2zt4*|dWIYyO z9aY%JH<0>J+h6anH?C+w)TX=m-IjHiBX=sy8ixcX%0AyXT8hMFj>u=}dJiblK4S`8 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