From e39552ee68275b18bbdce30b2eb2137a772cc686 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Tue, 16 Dec 2025 19:36:37 +0100 Subject: [PATCH] chore: small code style improvements --- .dockerignore | 2 +- .gitignore | 1 + app/server/utils/crypto.ts | 22 +++++++++------------- app/server/utils/fs.ts | 8 ++++++++ docker-compose.yml | 8 ++++---- 5 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 app/server/utils/fs.ts diff --git a/.dockerignore b/.dockerignore index 9980d98..d2d3fe6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,4 @@ -* +** !turbo.json !bun.lock diff --git a/.gitignore b/.gitignore index 9f32df6..99880c8 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ CLAUDE.md mutagen.yml.lock notes.md +smb-password.txt diff --git a/app/server/utils/crypto.ts b/app/server/utils/crypto.ts index a9abe4d..34c3af9 100644 --- a/app/server/utils/crypto.ts +++ b/app/server/utils/crypto.ts @@ -2,6 +2,7 @@ import crypto from "node:crypto"; import fs from "node:fs/promises"; import path from "node:path"; import { RESTIC_PASS_FILE } from "../core/constants"; +import { isNodeJSErrnoException } from "./fs"; const algorithm = "aes-256-gcm" as const; const keyLength = 32; @@ -9,7 +10,6 @@ const encryptionPrefix = "encv1:"; const envSecretPrefix = "env://"; const fileSecretPrefix = "file://"; -const dockerSecretsBasePath = "/run/secrets"; /** * Checks if a given string is encrypted by looking for the encryption prefix. @@ -62,23 +62,19 @@ const resolveFileSecret = async (ref: string): Promise => { throw new Error("Invalid secret reference: secret name must be a single path segment"); } - // Docker secrets live on a Linux path. Use POSIX path semantics even when developing on Windows. - const resolvedPath = path.posix.resolve(dockerSecretsBasePath, normalizedName); - const allowedPrefix = dockerSecretsBasePath.endsWith("/") ? dockerSecretsBasePath : `${dockerSecretsBasePath}/`; - if (!resolvedPath.startsWith(allowedPrefix)) { - throw new Error("Invalid secret reference: path escapes secrets directory"); - } + const resolvedPath = path.join("/run/secrets", normalizedName); try { const content = await fs.readFile(resolvedPath, "utf-8"); - // Docker secrets commonly have a trailing newline. return content.trimEnd(); } catch (error) { - if ((error as NodeJS.ErrnoException).code === "ENOENT") { - throw new Error(`Secret file not found: ${resolvedPath}`); - } - if ((error as NodeJS.ErrnoException).code === "EACCES") { - throw new Error(`Permission denied reading secret file: ${resolvedPath}`); + if (isNodeJSErrnoException(error)) { + if (error.code === "ENOENT") { + throw new Error(`Secret file not found: ${resolvedPath}`); + } + if (error.code === "EACCES") { + throw new Error(`Permission denied reading secret file: ${resolvedPath}`); + } } throw new Error(`Failed to read secret file ${resolvedPath}: ${(error as Error).message}`); } diff --git a/app/server/utils/fs.ts b/app/server/utils/fs.ts new file mode 100644 index 0000000..82bad47 --- /dev/null +++ b/app/server/utils/fs.ts @@ -0,0 +1,8 @@ +export const isNodeJSErrnoException = (error: unknown): error is NodeJS.ErrnoException => { + return ( + typeof error === "object" && + error !== null && + "code" in error && + typeof (error as NodeJS.ErrnoException).code === "string" + ); +}; diff --git a/docker-compose.yml b/docker-compose.yml index 92fc271..5ac4eee 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,11 +12,11 @@ services: - SYS_ADMIN environment: - NODE_ENV=development - # - SMB_PASSWORD=${SMB_PASSWORD} + # - SMB_PASSWORD=secret ports: - "4096:4096" # secrets: - # - s3-secret-access-key + # - smb-password volumes: - /etc/localtime:/etc/localtime:ro - /var/lib/zerobyte:/var/lib/zerobyte @@ -47,5 +47,5 @@ services: - ~/.config/rclone:/root/.config/rclone # secrets: -# s3-secret-access-key: -# file: ./s3-secret-access-key.txt +# smb-password: +# file: ./smb-password.txt