-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix(security): send sensitive config over a bootstrap fd #1398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
20c0564
b35fe86
5d54afe
c3fe34b
e7460fb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,10 +11,13 @@ const UPDATE_STATE_CHANNEL = "desktop:update-state"; | |
| const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state"; | ||
| const UPDATE_DOWNLOAD_CHANNEL = "desktop:update-download"; | ||
| const UPDATE_INSTALL_CHANNEL = "desktop:update-install"; | ||
| const wsUrl = process.env.T3CODE_DESKTOP_WS_URL ?? null; | ||
| const GET_WS_URL_CHANNEL = "desktop:get-ws-url"; | ||
|
|
||
| contextBridge.exposeInMainWorld("desktopBridge", { | ||
| getWsUrl: () => wsUrl, | ||
| getWsUrl: () => { | ||
| const result = ipcRenderer.sendSync(GET_WS_URL_CHANNEL); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a specific reason we're using ipcRenderer.sendSync to get the WebSocket URL here? Since it blocks the renderer's main thread until the main process responds, it could cause some visible stutter or 'jank' during the app's startup sequence. It might be smoother for the user if we switch this to an async ipcRenderer.invoke call or handle it during the load process unless you have a better solution |
||
| return typeof result === "string" ? result : null; | ||
| }, | ||
| pickFolder: () => ipcRenderer.invoke(PICK_FOLDER_CHANNEL), | ||
| confirm: (message) => ipcRenderer.invoke(CONFIRM_CHANNEL, message), | ||
| setTheme: (theme) => ipcRenderer.invoke(SET_THEME_CHANNEL, theme), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| import * as NFS from "node:fs"; | ||
| import * as path from "node:path"; | ||
| import { execFileSync, spawn } from "node:child_process"; | ||
| import * as NodeServices from "@effect/platform-node/NodeServices"; | ||
| import { assert, it } from "@effect/vitest"; | ||
| import { FileSystem, Schema } from "effect"; | ||
| import * as Duration from "effect/Duration"; | ||
| import * as Effect from "effect/Effect"; | ||
| import * as Fiber from "effect/Fiber"; | ||
| import { TestClock } from "effect/testing"; | ||
|
|
||
| import { readBootstrapEnvelope, resolveFdPath } from "./bootstrap"; | ||
| import { assertNone, assertSome } from "@effect/vitest/utils"; | ||
|
|
||
| const TestEnvelopeSchema = Schema.Struct({ mode: Schema.String }); | ||
|
|
||
| it.layer(NodeServices.layer)("readBootstrapEnvelope", (it) => { | ||
| it.effect("uses platform-specific fd paths", () => | ||
| Effect.sync(() => { | ||
| assert.equal(resolveFdPath(3, "linux"), "/proc/self/fd/3"); | ||
| assert.equal(resolveFdPath(3, "darwin"), "/dev/fd/3"); | ||
| assert.equal(resolveFdPath(3, "win32"), undefined); | ||
| }), | ||
| ); | ||
|
|
||
| it.effect("reads a bootstrap envelope from a provided fd", () => | ||
| Effect.gen(function* () { | ||
| const fs = yield* FileSystem.FileSystem; | ||
| const filePath = yield* fs.makeTempFileScoped({ prefix: "t3-bootstrap-", suffix: ".ndjson" }); | ||
|
|
||
| yield* fs.writeFileString( | ||
| filePath, | ||
| `${yield* Schema.encodeEffect(Schema.fromJsonString(TestEnvelopeSchema))({ | ||
| mode: "desktop", | ||
| })}\n`, | ||
| ); | ||
|
|
||
| const fd = yield* Effect.acquireRelease( | ||
| Effect.sync(() => NFS.openSync(filePath, "r")), | ||
| (fd) => Effect.sync(() => NFS.closeSync(fd)), | ||
| ); | ||
|
|
||
| const payload = yield* readBootstrapEnvelope(TestEnvelopeSchema, fd, { timeoutMs: 100 }); | ||
| assertSome(payload, { | ||
| mode: "desktop", | ||
| }); | ||
| }), | ||
| ); | ||
|
|
||
| it.effect("returns none when the fd is unavailable", () => | ||
| Effect.gen(function* () { | ||
| const fd = NFS.openSync("/dev/null", "r"); | ||
| NFS.closeSync(fd); | ||
|
|
||
| const payload = yield* readBootstrapEnvelope(TestEnvelopeSchema, fd, { timeoutMs: 100 }); | ||
| assertNone(payload); | ||
| }), | ||
| ); | ||
|
|
||
| it.effect("returns none when the bootstrap read times out before any value arrives", () => | ||
| Effect.gen(function* () { | ||
| const fs = yield* FileSystem.FileSystem; | ||
| const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-bootstrap-" }); | ||
| const fifoPath = path.join(tempDir, "bootstrap.pipe"); | ||
|
|
||
| yield* Effect.sync(() => execFileSync("mkfifo", [fifoPath])); | ||
|
|
||
| const _writer = yield* Effect.acquireRelease( | ||
| Effect.sync(() => | ||
| spawn("sh", ["-c", 'exec 3>"$1"; sleep 60', "sh", fifoPath], { | ||
| stdio: ["ignore", "ignore", "ignore"], | ||
| }), | ||
| ), | ||
| (writer) => | ||
| Effect.sync(() => { | ||
| writer.kill("SIGKILL"); | ||
| }), | ||
| ); | ||
|
|
||
| const fd = yield* Effect.acquireRelease( | ||
| Effect.sync(() => NFS.openSync(fifoPath, "r")), | ||
| (fd) => Effect.sync(() => NFS.closeSync(fd)), | ||
| ); | ||
|
|
||
| const fiber = yield* readBootstrapEnvelope(TestEnvelopeSchema, fd, { | ||
| timeoutMs: 100, | ||
| }).pipe(Effect.forkScoped); | ||
|
|
||
| yield* Effect.yieldNow; | ||
| yield* TestClock.adjust(Duration.millis(100)); | ||
|
|
||
| const payload = yield* Fiber.join(fiber); | ||
| assertNone(payload); | ||
| }).pipe(Effect.provide(TestClock.layer())), | ||
| ); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.