- Secrets are parsed only in a
server-onlyenvironment module..env.localanddata/are ignored by Git; no key usesNEXT_PUBLIC_*. - Provider selection is a server environment decision. Missing or unknown configuration fails clearly; there is no silent fallback.
- Resume and JD are untrusted delimited data. System instructions prohibit document instruction-following, secret/prompt disclosure, and role changes.
- Provider requests have AbortController cancellation, bounded retries/backoff, sanitized error mapping, and no request-body logging.
- Route errors return stable codes/messages without stack, SQL, paths, provider payloads, or user text.
reasoning_contentis ignored and never persisted, logged, exported, or shown.- SQLite enables foreign keys, WAL, and busy timeout. It is a local application boundary, not encrypted storage; protect the host account and filesystem and do not expose the development server to untrusted networks.
- Database backup uses SQLite's online backup API rather than copying live database/WAL bytes. The strict local manifest binds a basename-only database file to its byte size, SHA-256, user version, and Drizzle migration metadata. Validation is read-only, size-bounded, rejects symlinks and traversal, and requires SQLite quick-check, foreign-key, and application-migration checks.
- Database backups are sensitive and unencrypted. They stay local unless the operator moves them, contain the same sensitive database content as the source, and exclude recording and Uploaded Audio bytes. Protect and delete complete artifact pairs deliberately.
- Restore is an offline CLI-only operation. Write restore requires
--confirm-offline, which is an operator assertion rather than process detection or locking. Existing destinations additionally require--replace; replacement creates a validated pre-restore backup, validates a same-directory candidate, moves SQLite sidecars out of service, and rolls back exact original bytes on handled replacement or post-install validation failure. Restore never runs migrations. The database/WAL/SHM sequence is not crash-atomic across sudden process termination or power loss; persistent lock/recovery files make retry fail closed and require operator inspection. - Backup-pair retention is local CLI-only, defaults to dry-run, and requires both
--applyand--confirm-deleteto mutate. It scans direct children only, rejects a symlink/non-directory root, never follows artifact symlinks, uses the strict manifest parser and full backup validator, selects age only from manifestcreatedAt, and protects at leastkeepLatest >= 1valid pairs. Invalid, incomplete, unknown, temporary, lock, WAL/SHM, and recovery artifacts are never deleted. - Retention apply uses an exclusively created restrictive operation lock,
O_NOFOLLOWownership handles where supported, discovered/opened/path identity checks, and same-directory exclusive recovery links. A pathname or symlink swap fails closed; a replacement at an original path is preserved. Handled mid-pair failures restore both normal names when ownership permits. Failed rollback or cleanup leaves non-normal residue that blocks later runs. The pair protocol bounds handled failures but does not claim crash atomicity across process death, host failure, or power loss. - Operations status opens no application database connection directly. It pins and reads a bounded database byte snapshot, rejects symlinks and live WAL/SHM sidecars, inspects an in-memory copy with fixed read-only queries, and degrades on missing, corrupt, inconsistent, or unavailable resources. It never migrates, creates a database, changes journal mode, enqueues work, recovers a lease, retries deletion, or makes a network request.
- New maintenance events use a closed typed allowlist and newline-delimited JSON on stderr. Only fixed event/operation/outcome values, safe bounded error codes, durations, counts, and byte totals are accepted. Paths, names, hashes, identifiers, SQL, credentials, application content, provider payloads, raw errors, causes, and stacks are excluded. No telemetry, analytics, metrics export, persistent log, log shipping, or external request is implemented.
- Business state changes go through the domain controller. Multi-row actions are transactional and idempotent.
- The permanent
OPENAI_API_KEYremains in the server-only environment parser and token client. Browser code receives only a short-livedek_secret from a same-origin, strict-schema, rate-limited endpoint withno-storeandno-cache. - Realtime model, voice, transcription model, safety instructions, VAD, and duration are server controlled. Browser requests cannot supply model, voice, base URL, key, or arbitrary instructions.
useInsecureApiKeyis never enabled. - Agents SDK tracing, tools, handoffs, MCP, and history audio storage are disabled. Candidate speech is untrusted answer data and cannot alter the application state machine or canonical question sequence.
- Recording upload validates ownership, role, MIME, byte limit, and relative paths; it uses random server filenames, atomic writes, restrictive permissions, and a storage-root containment check. Downloads select assets by database ID and use
nosniff; playback supports validated byte ranges. - Logs must not contain ephemeral tokens, raw audio, transcripts, SDP, ICE credentials, provider raw events, or absolute recording paths. Authentication and quota failures are not automatically retried.
- Transcript Lab mutation routes require same-origin requests, strict schemas, and
no-storeresponses. Unknown chunk fields are rejected. Only final chunks reach the transactional SQLite adapter; provider IDs are idempotent and sequence reuse by a different provider ID is a stable conflict. TRANSCRIPT_LAB_FAKE_ENABLEDdefaults off and is honored only outside production. URL parameters cannot enable it. The Fake adapter has no provider key, microphone, media, or external-network capability, and every timer/subscription is disposable.- Transcript text is untrusted data, never code or instructions. Only server-built candidates can be evaluated; clients cannot submit
shouldFinalize, confidence, deterministic signals, semantic output, or provider selection. - Question-boundary mutation bodies are strict and same-origin. Required action IDs have a session-scoped unique receipt, and the repository returns the original result for a same-action retry without duplicating questions or mappings.
- Semantic work is bound to candidate revision. New final segments increment revision; AbortController cancellation and a transactional current-revision check prevent stale results from overwriting current state.
QUESTION_BOUNDARY_FAKE_SEMANTIC_ENABLEDdefaults off, is ignored in production, has no URL backdoor, requires no API key, and performs no network access. Raw semantic payloads, hidden prompts, reasoning, SQL, stack traces, and transcript content are excluded from logs/errors.- Question Understanding POST accepts only strict
finalizedQuestionIdandactionIdfields under same-origin and no-store controls. The server derives immutable source text, revision, boundary-decision provenance, classifications, confidence, and provider use. Unknown or forged classification/provider fields are rejected. - Understanding commit transactionally rechecks session ownership, active/not-undone state, finalized revision, and source boundary decision. Unique question/revision rows, ordered child uniqueness, confidence checks, cascading foreign keys, and session-scoped action receipts protect persistence and retries.
QUESTION_UNDERSTANDING_FAKE_SEMANTIC_ENABLEDdefaults off and is honored only outside production. It has no key or network capability. Source text, constraint/focus source substrings, semantic payloads, reasoning, database details, and stack traces are excluded from logs and safe errors.- Uploaded Audio mutation routes require same-origin requests and no-store responses. Multipart upload permits exactly
actionId,speakerRole, and one file; transcription/deletion accept only strict{actionId}JSON. Before multipart parsing, upload requires a positive decimalContent-Lengthbounded by the file limit plus 64 KiB. Unknown-length/chunked uploads are intentionally rejected to prevent unbounded buffering. Clients cannot submit transcript text, provider output, paths, status, labels, or completion decisions. - Uploaded files require an allowed MIME/extension pair and matching container signature, nonzero bounded size, and one
interviewer/candidaterole. The conservativeUPLOADED_AUDIO_MAX_BYTESdefault is 25 MiB. Display names are normalized and bounded; storage paths use server UUIDs and persist only relative paths. - Uploaded bytes stay outside SQLite beneath
UPLOADED_AUDIO_PATH. Atomic temporary writes use restrictive permissions. Lexical containment pluslstat/realpathchecks reject traversal, absolute paths, unsafe IDs, root/session symlinks, target symlinks, and filesystem escape. File/database compensation covers rejected metadata. Durable deletion batches preserve server-derived tombstone paths across staging, authoritative metadata deletion, final unlink, rollback, and retry; they intentionally outlive deleted session/asset rows when cleanup is incomplete. uploaded_audio_actionsis unique by analysis session/action ID, and transcription jobs bind session/action/asset with a composite foreign key. Guarded conditional claims, unique active-job/lease indexes, bounded leases, and affected-row checks prevent duplicate ownership. Completion rechecks the current unexpired lease and atomically inserts finalized source-linked segments, activates drafts, and completes job/asset. Cancellation and deletion invalidate queued/running work before metadata deletion, so stale output cannot commit.UPLOADED_AUDIO_TRANSCRIPTION_WORKER_ENABLEDandUPLOADED_AUDIO_FAKE_TRANSCRIPTION_ENABLEDboth default off. The worker starts only in a non-production Node runtime when Uploaded Audio and both explicit flags are enabled. It is a singleton per process, uses no network, and has no production fallback. No real uploaded-audio provider is configured in v0.5. Audio bytes, transcript text, hashes, display filenames, provider payloads, safe storage paths, lease tokens, and absolute paths are excluded from logs and errors.
Report vulnerabilities privately to the repository owner. Do not include real credentials or sensitive candidate documents in a report.