runtime/secure_file.ts

Hardened secret-file read — the TS twin of the Rust spine's fuz_sys::secure_file::load_secure_file.

The file this guards is typically the highest-value credential on the host (the bootstrap token mints the keeper account), so the read fails loud rather than degrading: a symlink, a group/other-accessible mode, or an oversized file is refused, never returned. The deploy recipe places the token at 0600, so the check ratifies the shape zap produces; a hand-placed 0644 file fails at boot instead of being read.

The Node implementation lives here (load_secure_file_node, wired as create_node_runtime().read_secure_file); the Deno runtime implements the same contract over Deno.open in runtime/deno.ts, and the mock honors the mode/size checks over its in-memory map. All three refuse through the shared assert_secure_mode / assert_secure_size / read_secure_bounded helpers so the checks — and their operator-facing messages — can't drift between runtimes.

view source

Declarations
#

5 declarations

assert_secure_mode
#

runtime/secure_file.ts view source

(path: string, mode: number): void import {assert_secure_mode} from '@fuzdev/fuz_app/runtime/secure_file.js';

Refuse any group/other-accessible mode (only 0600/0400 pass).

Callers own the platform gating: the real runtimes skip the check where modes aren't meaningful (Node on Windows, a null Deno mode); the mock checks its simulated modes unconditionally.

path

type string

mode

type number

returns

void

throws

  • Error - naming the path, the offending mode, and the `chmod` fix

assert_secure_size
#

runtime/secure_file.ts view source

(path: string, size: number): void import {assert_secure_size} from '@fuzdev/fuz_app/runtime/secure_file.js';

Refuse a byte count over MAX_SECURE_FILE_SIZE.

path

type string

size

type number

returns

void

throws

  • Error - naming the path, the size, and the cap

load_secure_file_node
#

runtime/secure_file.ts view source

(path: string): Promise<Uint8Array<ArrayBufferLike>> import {load_secure_file_node} from '@fuzdev/fuz_app/runtime/secure_file.js';

Read a secret file with fail-loud checks (Node implementation).

  • O_NOFOLLOW atomically rejects symlinks during open (no TOCTOU window)
  • the permission check runs on the open descriptor, not the path, so the file can't be swapped between check and read — any group/other access (not 0600/0400) is refused
  • a size cap bounds the read; the read itself is limited to cap + 1 so a file growing between stat and read is still caught

path

path to the secret file

type string

returns

Promise<Uint8Array<ArrayBufferLike>>

the file's bytes

throws

  • Error - on a missing file, symlink, permissive mode, oversized file, or I/O failure

MAX_SECURE_FILE_SIZE
#

runtime/secure_file.ts view source

4096 import {MAX_SECURE_FILE_SIZE} from '@fuzdev/fuz_app/runtime/secure_file.js';

Maximum size for secure files. Prevents DoS from unexpectedly large files; 4 KiB is generous for token/key material. Matches the Rust twin's MAX_SECURE_FILE_SIZE.

read_secure_bounded
#

runtime/secure_file.ts view source

(path: string, read_chunk: (target: Uint8Array<ArrayBufferLike>) => Promise<number | null>): Promise<Uint8Array<ArrayBufferLike>> import {read_secure_bounded} from '@fuzdev/fuz_app/runtime/secure_file.js';

Drain read_chunk into a cap-bounded buffer.

Reads up to cap + 1 bytes so a file growing between stat and read is still caught (the stat-time size check alone races). read_chunk fills the given target from the file's current position and returns the bytes read (null/0 = EOF) — the seam that lets the Node and Deno handle APIs share one loop.

path

type string

read_chunk

type (target: Uint8Array<ArrayBufferLike>) => Promise<number | null>

returns

Promise<Uint8Array<ArrayBufferLike>>

throws

  • Error - when more than `MAX_SECURE_FILE_SIZE` bytes arrive

Imported by
#