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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/control/commands/capture-session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { randomUUID } from "node:crypto";
import { chmod, mkdir } from "node:fs/promises";
import { withInterrupt } from "@genesiscz/utils/cli/interrupt";
import { SafeJSON } from "@genesiscz/utils/json";
import { logger, out } from "@genesiscz/utils/logger";
Expand Down Expand Up @@ -180,6 +181,9 @@ export function registerCaptureSessionCommands(capture: Command): void {
throw new Error("Nothing on screen to record.");
}

// The default output and every session folder live here, so the folder is owner only.
await mkdir(recordingsDir(), { recursive: true, mode: 0o700 });
await chmod(recordingsDir(), 0o700);
session = new RecordingSession({
target,
directory: recordingsDir(`session-${randomUUID()}`),
Expand Down
27 changes: 27 additions & 0 deletions src/control/lib/recording-session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,33 @@ describe("RecordingSession", () => {
expect(await recording.finished).toBe("discarded");
});

it("keeps its segment folder private, also when the folder already exists with wider permissions", async () => {
for (const existing of [false, true]) {
const directory = join(root, existing ? "existing" : "new");
if (existing) {
await mkdir(directory, { mode: 0o755 });
}

const { recorder } = fakeRecorder();
const recording = new RecordingSession({
target: finder,
directory,
output: join(root, "private.mp4"),
recorder,
join: async () => ({ durationMs: 0 }),
emit: () => {},
now: () => clock,
tickMs: 0,
slowStartMs: 0,
});
await recording.start();

expect((await fs.stat(directory)).mode & 0o777).toBe(0o700);
await recording.command({ cmd: "discard" });
expect(await recording.finished).toBe("discarded");
}
});

it("a failed join keeps a movie already at the output and leaves no temporary file", async () => {
const { recorder } = fakeRecorder();
await writeFile(join(root, "out.mp4"), "previous movie");
Expand Down
6 changes: 4 additions & 2 deletions src/control/lib/recording-session.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { randomUUID } from "node:crypto";
import { existsSync } from "node:fs";
import { copyFile, mkdir, rename, rm, unlink } from "node:fs/promises";
import { chmod, copyFile, mkdir, rename, rm, unlink } from "node:fs/promises";
import { dirname, join } from "node:path";
import { SafeJSON } from "@genesiscz/utils/json";
import { logger } from "@genesiscz/utils/logger";
Expand Down Expand Up @@ -198,7 +198,9 @@ export class RecordingSession {
start(): Promise<void> {
return this.enqueue(async () => {
try {
await mkdir(this.options.directory, { recursive: true });
// Screen recordings are private: owner only. `mode` covers a new folder, chmod an existing one.
await mkdir(this.options.directory, { recursive: true, mode: 0o700 });
await chmod(this.options.directory, 0o700);
} catch (error) {
log.warn({ error }, "could not create recording directory");
this.end("failed");
Expand Down
Loading