Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  secret-file.ts

  Sprache: JAVA
 

Spracherkennung für: .ts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import { randomBytes } from "node:crypto";
import fs from "node:fs";
import fsp from "node:fs/promises";
import path from "node:path";
import { resolveUserPath } from "../utils.js";
import { openVerifiedFileSync } from "./safe-open-sync.js";

export const DEFAULT_SECRET_FILE_MAX_BYTES = 16 * 1024;
export const PRIVATE_SECRET_DIR_MODE = 0o700;
export const PRIVATE_SECRET_FILE_MODE = 0o600;

export type SecretFileReadOptions = {
  maxBytes?: number;
  rejectSymlink?: boolean;
};

export type SecretFileReadResult =
  | {
      ok: true;
      secret: string;
      resolvedPath: string;
    }
  | {
      ok: false;
      message: string;
      resolvedPath?: string;
      error?: unknown;
    };

function normalizeSecretReadError(error: unknown): Error {
  return error instanceof Error ? error : new Error(String(error));
}

export function loadSecretFileSync(
  filePath: string,
  label: string,
  options: SecretFileReadOptions = {},
): SecretFileReadResult {
  const trimmedPath = filePath.trim();
  const resolvedPath = resolveUserPath(trimmedPath);
  if (!resolvedPath) {
    return { ok: false, message: `${label} file path is empty.` };
  }

  const maxBytes = options.maxBytes ?? DEFAULT_SECRET_FILE_MAX_BYTES;

  let previewStat: fs.Stats;
  try {
    previewStat = fs.lstatSync(resolvedPath);
  } catch (error) {
    const normalized = normalizeSecretReadError(error);
    return {
      ok: false,
      resolvedPath,
      error: normalized,
      message: `Failed to inspect ${label} file at ${resolvedPath}: ${String(normalized)}`,
    };
  }

  if (options.rejectSymlink && previewStat.isSymbolicLink()) {
    return {
      ok: false,
      resolvedPath,
      message: `${label} file at ${resolvedPath} must not be a symlink.`,
    };
  }
  if (!previewStat.isFile()) {
    return {
      ok: false,
      resolvedPath,
      message: `${label} file at ${resolvedPath} must be a regular file.`,
    };
  }
  if (previewStat.size > maxBytes) {
    return {
      ok: false,
      resolvedPath,
      message: `${label} file at ${resolvedPath} exceeds ${maxBytes} bytes.`,
    };
  }

  const opened = openVerifiedFileSync({
    filePath: resolvedPath,
    rejectPathSymlink: options.rejectSymlink,
    maxBytes,
  });
  if (!opened.ok) {
    const error = normalizeSecretReadError(
      opened.reason === "validation" ? new Error("security validation failed") : opened.error,
    );
    return {
      ok: false,
      resolvedPath,
      error,
      message: `Failed to read ${label} file at ${resolvedPath}: ${String(error)}`,
    };
  }

  try {
    const raw = fs.readFileSync(opened.fd, "utf8");
    const secret = raw.trim();
    if (!secret) {
      return {
        ok: false,
        resolvedPath,
        message: `${label} file at ${resolvedPath} is empty.`,
      };
    }
    return { ok: true, secret, resolvedPath };
  } catch (error) {
    const normalized = normalizeSecretReadError(error);
    return {
      ok: false,
      resolvedPath,
      error: normalized,
      message: `Failed to read ${label} file at ${resolvedPath}: ${String(normalized)}`,
    };
  } finally {
    fs.closeSync(opened.fd);
  }
}

export function readSecretFileSync(
  filePath: string,
  label: string,
  options: SecretFileReadOptions = {},
): string {
  const result = loadSecretFileSync(filePath, label, options);
  if (result.ok) {
    return result.secret;
  }
  throw new Error(result.message, result.error ? { cause: result.error } : undefined);
}

export function tryReadSecretFileSync(
  filePath: string | undefined,
  label: string,
  options: SecretFileReadOptions = {},
): string | undefined {
  if (!filePath?.trim()) {
    return undefined;
  }
  const result = loadSecretFileSync(filePath, label, options);
  return result.ok ? result.secret : undefined;
}

function assertPathWithinRoot(rootDir: string, targetPath: string): void {
  const relative = path.relative(rootDir, targetPath);
  if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
    throw new Error(`Private secret path must stay under ${rootDir}.`);
  }
}

function assertRealPathWithinRoot(rootDir: string, targetPath: string): void {
  const relative = path.relative(rootDir, targetPath);
  if (relative.startsWith("..") || path.isAbsolute(relative)) {
    throw new Error(`Private secret path must stay under ${rootDir}.`);
  }
}

async function enforcePrivatePathMode(
  resolvedPath: string,
  expectedMode: number,
  kind: "directory" | "file",
): Promise<void> {
  if (process.platform === "win32") {
    return;
  }
  await fsp.chmod(resolvedPath, expectedMode);
  const stat = await fsp.stat(resolvedPath);
  const actualMode = stat.mode & 0o777;
  if (actualMode !== expectedMode) {
    throw new Error(
      `Private secret ${kind} ${resolvedPath} has insecure permissions ${actualMode.toString(8)}.`,
    );
  }
}

async function ensurePrivateDirectory(rootDir: string, targetDir: string): Promise<void> {
  const resolvedRoot = path.resolve(rootDir);
  const resolvedTarget = path.resolve(targetDir);
  if (resolvedTarget === resolvedRoot) {
    await fsp.mkdir(resolvedRoot, { recursive: true, mode: PRIVATE_SECRET_DIR_MODE });
    const rootStat = await fsp.lstat(resolvedRoot);
    if (rootStat.isSymbolicLink()) {
      throw new Error(`Private secret root ${resolvedRoot} must not be a symlink.`);
    }
    if (!rootStat.isDirectory()) {
      throw new Error(`Private secret root ${resolvedRoot} must be a directory.`);
    }
    await enforcePrivatePathMode(resolvedRoot, PRIVATE_SECRET_DIR_MODE, "directory");
    return;
  }

  assertPathWithinRoot(resolvedRoot, resolvedTarget);
  await ensurePrivateDirectory(resolvedRoot, resolvedRoot);
  const resolvedRootReal = await fsp.realpath(resolvedRoot);

  let current = resolvedRoot;
  for (const segment of path
    .relative(resolvedRoot, resolvedTarget)
    .split(path.sep)
    .filter(Boolean)) {
    current = path.join(current, segment);
    try {
      const stat = await fsp.lstat(current);
      if (stat.isSymbolicLink()) {
        throw new Error(`Private secret directory component ${current} must not be a symlink.`);
      }
      if (!stat.isDirectory()) {
        throw new Error(`Private secret directory component ${current} must be a directory.`);
      }
    } catch (error) {
      if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
        throw error;
      }
      await fsp.mkdir(current, { mode: PRIVATE_SECRET_DIR_MODE });
    }
    const currentReal = await fsp.realpath(current);
    assertRealPathWithinRoot(resolvedRootReal, currentReal);
    await enforcePrivatePathMode(currentReal, PRIVATE_SECRET_DIR_MODE, "directory");
  }
}

export async function writePrivateSecretFileAtomic(params: {
  rootDir: string;
  filePath: string;
  content: string | Uint8Array;
}): Promise<void> {
  const resolvedRoot = path.resolve(params.rootDir);
  const resolvedFile = path.resolve(params.filePath);
  assertPathWithinRoot(resolvedRoot, resolvedFile);
  const intendedParentDir = path.dirname(resolvedFile);
  await ensurePrivateDirectory(resolvedRoot, intendedParentDir);
  const resolvedRootReal = await fsp.realpath(resolvedRoot);
  const parentDir = await fsp.realpath(intendedParentDir);
  assertRealPathWithinRoot(resolvedRootReal, parentDir);
  const fileName = path.basename(resolvedFile);
  const finalFilePath = path.join(parentDir, fileName);

  try {
    const stat = await fsp.lstat(finalFilePath);
    if (stat.isSymbolicLink()) {
      throw new Error(`Private secret file ${finalFilePath} must not be a symlink.`);
    }
    if (!stat.isFile()) {
      throw new Error(`Private secret file ${finalFilePath} must be a regular file.`);
    }
  } catch (error) {
    if (!error || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT") {
      throw error;
    }
  }

  const tempPath = path.join(
    parentDir,
    `.tmp-${process.pid}-${Date.now()}-${randomBytes(6).toString("hex")}`,
  );
  let createdTemp = false;
  try {
    const handle = await fsp.open(tempPath, "wx", PRIVATE_SECRET_FILE_MODE);
    createdTemp = true;
    try {
      await handle.writeFile(params.content);
    } finally {
      await handle.close();
    }
    await enforcePrivatePathMode(tempPath, PRIVATE_SECRET_FILE_MODE, "file");
    const refreshedParentReal = await fsp.realpath(intendedParentDir);
    if (refreshedParentReal !== parentDir) {
      throw new Error(`Private secret parent directory changed during write for ${finalFilePath}.`);
    }
    await fsp.rename(tempPath, finalFilePath);
    createdTemp = false;
    await enforcePrivatePathMode(finalFilePath, PRIVATE_SECRET_FILE_MODE, "file");
  } finally {
    if (createdTemp) {
      await fsp.unlink(tempPath).catch(() => undefined);
    }
  }
}

¤ Dauer der Verarbeitung: 0.18 Sekunden  (vorverarbeitet am  2026-04-27) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge