fix(electrobun): complete all 16 Codex #3 findings
CRITICAL:
- Message persistence race: snapshot batchEnd before async save
- Double-start guard: startingProjects Set prevents concurrent launches
- Symlink path traversal: fs.realpathSync() in path-guard.ts
- Relay false success: connect() returns { ok, machineId, error }
HIGH:
- Session restore skips if active session exists
- Remote remove: new RPC, cleans backend map
- Task board poll token: stale responses discarded after drag-drop
- Health concurrent tools: toolsInFlight counter (was boolean)
- bttask transactions: delete wraps comments+task, addComment validates
- PTY buffer cleared on reconnect
- PTY large paste: chunked String.fromCharCode (8KB chunks)
- Sidecar max line: 10MB limit prevents unbounded memory
- btmsg authorization: group validation, channel membership checks
MEDIUM:
- Session retention: max 5 per project, purgeSession/untrackProject
- Relay IPv6: URL parser replaces string split
- PTY schema: fixed misleading base64 comment
This commit is contained in:
parent
c145e37316
commit
0f75cb8e32
12 changed files with 190 additions and 42 deletions
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import { settingsDb } from "../settings-db.ts";
|
||||
import { homedir } from "os";
|
||||
import { join } from "path";
|
||||
|
|
@ -29,14 +30,36 @@ function getAllowedRoots(): string[] {
|
|||
|
||||
/**
|
||||
* Validate that a file path is within an allowed boundary.
|
||||
* Returns the resolved path if valid, or null if outside boundaries.
|
||||
* Fix #3 (Codex audit): Uses realpathSync to resolve symlinks, preventing
|
||||
* symlink-based traversal attacks (CWE-59).
|
||||
* Returns the resolved real path if valid, or null if outside boundaries.
|
||||
*/
|
||||
export function validatePath(filePath: string): string | null {
|
||||
const resolved = path.resolve(filePath);
|
||||
let resolved: string;
|
||||
try {
|
||||
// Resolve symlinks to their actual target to prevent symlink traversal
|
||||
resolved = fs.realpathSync(path.resolve(filePath));
|
||||
} catch {
|
||||
// If the file doesn't exist yet, resolve without symlink resolution
|
||||
// but only allow if the parent directory resolves within boundaries
|
||||
const parent = path.dirname(path.resolve(filePath));
|
||||
try {
|
||||
const realParent = fs.realpathSync(parent);
|
||||
resolved = path.join(realParent, path.basename(filePath));
|
||||
} catch {
|
||||
return null; // Parent doesn't exist either — reject
|
||||
}
|
||||
}
|
||||
|
||||
const roots = getAllowedRoots();
|
||||
|
||||
for (const root of roots) {
|
||||
const resolvedRoot = path.resolve(root);
|
||||
let resolvedRoot: string;
|
||||
try {
|
||||
resolvedRoot = fs.realpathSync(path.resolve(root));
|
||||
} catch {
|
||||
resolvedRoot = path.resolve(root);
|
||||
}
|
||||
if (resolved === resolvedRoot || resolved.startsWith(resolvedRoot + path.sep)) {
|
||||
return resolved;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ import type { RelayClient } from "../relay-client.ts";
|
|||
|
||||
export function createRemoteHandlers(relayClient: RelayClient) {
|
||||
return {
|
||||
// Fix #4 (Codex audit): relay-client.connect() now returns { ok, machineId, error }
|
||||
"remote.connect": async ({ url, token, label }: { url: string; token: string; label?: string }) => {
|
||||
try {
|
||||
const machineId = await relayClient.connect(url, token, label);
|
||||
return { ok: true, machineId };
|
||||
const result = await relayClient.connect(url, token, label);
|
||||
return result;
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
console.error("[remote.connect]", err);
|
||||
|
|
@ -28,6 +29,18 @@ export function createRemoteHandlers(relayClient: RelayClient) {
|
|||
}
|
||||
},
|
||||
|
||||
// Fix #6 (Codex audit): Add remote.remove RPC that disconnects AND deletes
|
||||
"remote.remove": ({ machineId }: { machineId: string }) => {
|
||||
try {
|
||||
relayClient.removeMachine(machineId);
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
const error = err instanceof Error ? err.message : String(err);
|
||||
console.error("[remote.remove]", err);
|
||||
return { ok: false, error };
|
||||
}
|
||||
},
|
||||
|
||||
"remote.list": () => {
|
||||
try {
|
||||
return { machines: relayClient.listMachines() };
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue