OpenProgram · Historical design archive · 2026-08-13

Disabled file-credential hardening design

This document retains the old proposal and implementation records for traceability only; it no longer defines current implementation requirements.

Current decision:Credentials continue using ordinary local-file I/O. Mandatory 0600/0700, owner/type/symlink/inode checks, permission auditing and repair, and revision conflicts are disabled. Only ordinary write locks needed to avoid lost concurrent updates remain. Do not reenable the old hardening proposal unless the user explicitly requests it separately.

Current implementation
Ordinary file I/O Retain only the data inventory, backup redaction and compatibility interfaces.
Document status
Rejected The following design is excluded from implementation plans; its body and evidence are archival only.
Code location
openprogram/auth/credentials/safety/legacy.py. Current production code neither imports nor uses it; keep it disabled unless separately and explicitly requested by the user.
Archive notice. The content beginning with Credential usage flow preserves the original proposal and integration records. Its 0600, 0700, ACL, symlink, owner, inode, doctor/repair and locking requirements are not current product contracts.

0Current implementation and boundaries

Current production uses ordinary local-file I/O. Sections 1–5 retain the old hardening proposal's boundaries, references and acceptance model; all are archived proposals, not current implementation status.

Current writing

openprogram/auth/credentials/io.py Retain basic cross-process write locks, temporary writes, flush/fsync and atomic replace to avoid partial writes and lost updates; this does not check permissions, owner, symlinks, inode or revision.

Current inventory

openprogram/auth/credentials/inventory.py Lists secret paths and fields for auditing, backups and compatibility; the inventory itself does not enable the old hardened writer.

Disabled code

openprogram/auth/credentials/safety/legacy.py and its disabled tests remain archived. Production does not import the safety package or provide the old hardening contract of doctor/repair .

Explicit exclusions

Mandatory 0600/0700, ACL/owner/symlink/inode validation, revision conflicts, the old restore journal and OS credential stores are outside the current product contract; redesign requires a separate explicit request.

Status boundary. Subsequent references to 0600, 0700, ACL, symlink, owner, inode, doctor/repair, revision conflicts and restore journals describe only a rejected historical proposal and are not current implementation evidence.

1Baseline when the design was approved: secret inventory and evidence boundaries

This section preserves the old hardening proposal's design-time baseline. Each inventory entry was checked against then-current writers rather than inferred from directory names. Only explicit fields in mixed files are secrets; temporary tokens are inventoried but have different backup policies from long-term credentials. Section 0 defines current implementation status.

File/fieldSecret typeCurrent write protectionConfirmed gap
auth/<provider>/<account>.json
profiles/<name>/auth/…
AuthStore pool: API keys, OAuth and existing credential_process authentication recordsMostly complete 0600-at-create, file fsync, replace,restrict_to_user, stable lock and external-change detectionFixed .tmp; no O_NOFOLLOW; no directory fsync; writers do not yet share one contract
config.json[api_keys]Provider API keys in mixed configurationFinal file opened as 0600; updates use an in-process lock and FileLock where availableDirectly O_TRUNC the final file; no temporary file, fsync, replace or permission validation; the atomic comment does not match crash semantics
profiles/<name>/.envAccount environment-variable credentialsOrdinary same-directory temporary file, then replaceTemporary file is not created as 0600; no fsync,restrict_to_user, symlink protection or cross-process lock
channels/<channel>/accounts/<id>/credentials.jsonChannel account credentialsCalls restrict_to_user after replace; in-process RLockCreation uses write_text before tightening permissions; fixed .tmp; no 0600-at-create, fsync,O_NOFOLLOW or cross-process lock
channels/<channel>/access.json pending pairing codeShort-lived authentication material; allowlist in the same file is metadatamkstemp Private creation, chmod, replace and FileLockNo file/directory fsync or final permission validation; temporary pairing codes must not enter backups
mcp_servers.json 's env, headers, bearer, OAuth client secretSecrets in mixed configuration; all env/header values are treated as secretsMostly complete 0600-at-create, O_NOFOLLOW, file fsync, replace,restrict_to_userFixed temporary name; no directory fsync or shared cross-process read-modify-write lock
mcp_tokens/<server>.jsonAccess/refresh tokens and client authentication data0600, O_NOFOLLOW, replace, restrict_to_userWrites are not flush+fsync'd; fixed temporary name; no directory fsync or cross-process lock
web/tokenWeb runtime bearer tokenMostly complete Random same-directory temporary name,O_EXCL/O_NOFOLLOW, 0600, file fsync, replace, permission restrictions, read-back validation and lifecycle lockNo directory fsync; classified as never_backup, with logical deletion only on shutdown

State confirmed not to be secret

channels/…/account.json is account metadata;owner.json is a stable principal ID; WeChat's cursor.json is a runtime progress cursor;web/access.json records bind/origin/fingerprint. They may retain private permissions but are excluded from raw-secret non-retrieval and credential-backup inventories. If field meanings change, update the inventory before writing.

Directory and cross-platform foundations

ensure_state_dir Already tightens the POSIX profile root to 0700. This existing defense does not replace per-file creation permissions and validation.restrict_to_user Currently uses chmod 0600 on POSIX and a fixed-argv call to icacls on Windows to remove inheritance and grant the current user and SYSTEM. It is best-effort and suppresses failures; the target implementation must validate results and return failures to callers.

2Current and target behavior at design approval

2.1 User input or import

Current behavior and risk

Setup, account management, Channel and MCP API/CLI parse inputs separately. Web config and Account interfaces already reject display masks as new keys; MCP merging handles structured masked responses, but extracted plain mask strings may still be written as new values. Import/restore and interactive input do not share a secret-field inventory.

Required improvements and resulting behavior

All entries use three-state updates: omitted fields preserve, explicit delete removes, and only newly supplied plaintext passing format validation replaces. Reject masks, presence markers and redacted sentinels. Classify and validate imports against the inventory before using the same writer.

2.2 OpenProgram saves

Current behavior and risk

AuthStore, MCP config and Web tokens already have most atomic-write protections;config.json may truncate on crash, Account/Channel temporary files may have broad permissions, and MCP tokens may replace data not yet durable. Fixed temporary names also increase concurrency and symlink risks.

Required improvements and resulting behavior

Every inventoried writer calls only the shared private atomic writer: create a unique private same-directory temporary file, fsync, atomically replace, persist directory entries and validate owner-only permissions. Read-modify-write runs under a stable lock; conflicts never silently overwrite.

2.3 Runtime resolution and use

Current behavior and risk

Runtime resolves provider, Account, MCP and Channel credentials from several files. Existing credential_process executes argv only, withshell=False, timeouts, parsing rules and an in-process cache. This is an explicit existing capability, not a general secret backend.

Required improvements and resulting behavior

Validate path type, owner and permissions before reading; reject symlinks below the state root, nonregular files and foreign owners. Read failures report only credential category, path and remediation, never values. Do not add external key-fetching commands or expand the credential_process contract.

2.4 User viewing, editing and deletion

Current behavior and risk

Most normal Web config, Auth CLI and MCP list responses return only has_value and masks, but account management still exposes a complete API key through the account-specific reveal endpoint and UI button;mcp edit also passes the raw mcp_servers.json to $EDITOR. Channel deletion uses rmtree(ignore_errors=True), potentially reporting failures as success.

Required improvements and resulting behavior

Remove account-key reveal entries from API, Web and CLI; legacy reveal routes return stable deprecation errors, never original values. MCP uses structured masked editing rather than opening raw secret files. Other API/Web/CLI entries also must not return raw secrets. Delete under lock, clear runtime caches, verify absence and report failures explicitly. Do not claim secure erasure on SSDs, log-structured files or backups.

2.5 Backup, restore and migration

Current messaging disagrees with actual scope. Default INCLUDED includes api_keys 's config.json with credentials.json 's channels/ and mcp_servers.json; CREDENTIAL_ENTRIES containing env/header/bearer/OAuth secrets, while exclusion covers only auth and mcp_tokens. Thus, without --include-credentials , an archive may still contain secrets despite printing credentials excluded. Profile AuthStore and .env are also missing from the complete inclusion inventory. Current backups cannot be described as already safe.

Current behavior and risk

backup create There is explicit --include-credentials and a warning, but classification depends on top-level names. Archives are chmod'd after creation, with no 0600-at-create, fsync or directory fsync. Restore validates paths and rejects symlinks, but after tar.extractall it neither reapplies inventory-based restrictions nor validates them, and lacks a multi-file crash transaction. The top-level migrate-to-system-keychain comment is obsolete.

Required improvements and resulting behavior

Retain explicit opt-in semantics rather than automatically including credentials by default. Default backup redacts fields from api_keys, MCP secrets and Channel credentials, and excludes profile .env/AuthStore and pending pairing codes. Restore preserves existing local secrets when fields are missing or redacted. Include inventoried long-term secrets only after explicit --include-credentials and a warning;web/token and pairing codes are never backed up.

Before restore, validate the manifest, member paths, file types, JSON structures and secret inventory in a same-filesystem staging directory under the state parent. Reject symlinks, hardlinks, unknown secret paths and escaping members. Publish each target with the shared writer and record a restore journal. Pre-publication failure leaves old state unchanged; mid-operation failure restores old files in reverse journal order. Report completion only after all permission repairs and final validation succeed. Default pre-restore backup does not implicitly gain credential authority; an explicitly authorized credential restore also authorizes its pre-restore snapshot to contain credentials, with the plaintext-backup warning shown again.

2.6 Diagnostics and recovery

Current behavior and risk

Writers separately apply chmod/ACL without inventory-driven diagnosis of legacy 0644 files, foreign owners, symlinks, leftover temporary files or incomplete restores. Windows ACL failures may currently be swallowed.

Required improvements and resulting behavior

openprogram doctor credentials outputs only category, path and status;--repair repairs only regular files/directories owned by the current user, never following symlinks or taking foreign ownership. Return nonzero when owner-only access cannot be verified. Remove leftover temporary files only after obtaining their lock and satisfying naming and age rules.

3How other projects are designed

Comparisons use only official documentation/source or the repository's reference corpus. External projects reflect documentation available on this document's date or fixed source snapshots, not necessarily latest versions. Documentation statements are not end-to-end runtime verification. Complete version boundaries appear in Feature-matrix methodology and evidence.

ProjectConfirmed designUse in this designEvidence scope
Claude CodeOfficial documentation states secure credential storage; gateway documentation offers TTL and explicit precedence for apiKeyHelper.Adopt explicit precedence, cache boundaries and non-echoing, but not Keychain merely because that project uses OS storage.Official getting started; Official gateway documentation; repository snapshot matrix
CodexThe fixed repository snapshot records optional keyring storage; capabilities depend on version and configuration.Retain only as a competitor fact; OpenProgram explicitly rejects that backend in favor of file contracts and auditing.Repository reference-corpus methodology table; Official configuration reference
OpenClawSecretRef supports env/file/exec with preflight/audit for activation and resolution; plaintext compatibility remains, and OAuth/runtime-generated material is not all represented by read-only references.Adapt inventory-driven credential-surface validation/auditing; reject adding env/file/exec backends.Official credential surface; Official secrets documentation
OpenCodeOfficial provider documentation explicitly specifies a local credential file ~/.local/share/opencode/auth.json.Shows local files are a valid independent product choice; do not infer atomic-write, permission-repair or backup maturity.Official provider documentation; fixed repository source snapshot
HermesSource references show advisory locks, version fields and lock timeouts for its auth path, plus backup/import capability.Adopt stable locks and version-conflict handling; validate backup safety against OpenProgram's own secret inventory.Official source repository; fixed repository source snapshot
CodeBuddyThe repository's npm snapshot records OS credential storage and multiple credential entries.Retain only as a competitor fact; Keychain support does not require OpenProgram to adopt it.The repository feature matrix records a codebuddy 2.109.3 package snapshot
pi-monoFixed snapshots provide partial evidence only, insufficient to establish complete credential persistence, permissions or backup contracts.Exclude from design evidence and make no maturity inference.Fixed repository source snapshot

4Planned approach

4.1 Adopt, adapt and reject

Adopt Basic file-writing contract

Owner-only creation, same-directory atomic replace, file fsync, directory fsync, stable locks, version conflicts, inventory-driven auditing and no secret echoing.

Adapt Inventory and backup design

Adapt SecretRef-style credential surfaces into OpenProgram's fixed file/field inventory, driving writers, backup, restore, doctor and tests together.

Reject New secret backend

Do not adopt Keychain, keyring or encrypted databases, or add general env/file/exec key resolution or arbitrary shell commands. Existing credential_process remains unchanged.

4.2 One secret inventory

Introduce a static testable registry. Each entry contains at least a path matcher, secret-field matcher, writer, lifecycle (persistent/ephemeral), backup policy (redact_default, include_on_opt_in, never_backup) and deletion action. Tests fail for any new unregistered persistent secret; backup must no longer consider top-level directories alone.

4.3 Mechanical contract for the private atomic writer

  1. Paths:Resolve and validate the state root first. An explicitly configured state root may alias an owner-only directory owned by the current user; reject any symlink below that root, including intermediate, final or temporary paths. Use lstat and accept only current-user-owned regular files/directories.
  2. Directories:POSIX secret directories must be 0700; Windows removes inheritance and grants only the current user and SYSTEM. Validate before and after creation; parent permissions do not substitute for file permissions.
  3. Temporary files:Create in the target directory with an unpredictable unique name and O_CREAT|O_EXCL|O_NOFOLLOW , with POSIX mode 0600; on Windows immediately apply and verify owner-only ACL after creation. Do not use fixed .tmp.
  4. Writing:Serialize completely, write every byte, flush and fsync the temporary file; revalidate ownership and permissions before publication.
  5. Publication:Publish only through same-directory os.replace . Temporary and target files therefore share a filesystem, preventing EXDEV; if a deviating implementation encounters a cross-filesystem error, fail rather than copying.
  6. Directory durability:On POSIX, fsync the parent directory to persist renamed directory entries. Python has no portable Windows directory fsync; acceptance requires file flush/fsync, atomic Replace/Move semantics and ACL read-back verification, explicitly documenting the platform difference.
  7. Final validation:After replace, restrict and validate the final file again. Permission-validation failure returns an error, never success; finally cleans temporary files through validated paths.
  8. Failure semantics:Pre-replace failure preserves the old file; failed replace also preserves it. If replace succeeds but directory fsync fails, the new file is already visible: return structured committed_not_durable without replacing it with old content again.

4.4 Concurrency and external editing

Each logical record uses an owner-only stable sibling lock, retaining both in-process and cross-process locks across the full read-modify-write. API/Web updates carry revision/ETag; compute fingerprints on read. Manual editors do not hold locks, so detected version changes before writing return conflict rather than overwrite. Remove every fixed temporary filename.

4.5 Permission auditing and repair

4.6 Errors, deletion and log boundaries

Distinguish permission, ownership, symlink, conflict, serialization, replace, fsync and committed_not_durable errors. Logs, exceptions, telemetry and doctor output contain only credential kind, relative path, operation and errno/ACL state, not raw values, command stdout or complete headers/env. Deletion means logical unlink/rmtree followed by verification, not secure media erasure. Old backup values may remain until user pruning or expiry.

4.7 Compatibility and implementation batches

BatchContentCompletion condition
Batch 0Establish the inventory and use a minimal private atomic writer for backup archives themselves. Then correct default exclusions/field redaction, obsolete Keychain comments and post-restore permission verification. Archives must be owner-only from first visibility, not deferred to batch 1.Default archive scans find no registered secrets; opt-in archives cover precisely permitted long-term secrets; archive creation permissions, fsync/replace, warnings and manifest agree.
Batch 1Implement the shared private atomic writer; migrate config, Account .env, Channel and MCP tokens, then move AuthStore, MCP config and Web tokens onto the same primitive.All writers pass identical POSIX/Windows contract tests without fixed tmp names or direct truncation of final secret files.
Batch 2Secret inventory, doctor/repair, masked three-state editing and deletion-result validation; remove raw MCP-file editing from the product.Negative tests cover legacy permissions, symlinks, mask write-back, raw-secret responses and deletion failures.
Batch 3Staged restore, journal/rollback, complete manifests and cross-platform backup/restore acceptance.Injected failures leave either complete old or complete new state, never silent partial success.

4.8 Test matrix

DimensionRequired casesMechanical assertion
Creation and permissionsumask 000/022; initial creation; overwrite legacy 0644; 0700 directories; Windows ACL inheritance enabled/disabledSecrets are owner-only from first visibility; final permission read-back passes
Crash durabilityDuring writes, before/after file fsync, before/after replace and directory-fsync failureComplete old/new values; no truncated JSON; directory-fsync failure returns committed_not_durable
ConcurrencyThreads, two processes, manual external edits and lock timeoutNo lost updates; explicit conflict or timeout
Path attacksTarget/intermediate symlinks, precreated fixed tmp, nonregular files and foreign ownersReject without modifying link targets; no fixed tmp implementation
FilesystemSame-directory replace; simulated EXDEV; POSIX directory fsync; Windows Replace/Move and ACL read-backNo copy fallback; platform differences satisfy the contract
Display and editingAPI/Web/CLI GET; unchanged mask submission; omit/replace/delete; structured MCP editingNo raw values in responses; masks unwritable; consistent three-state behavior
BackupDefault,--include-credentials, mixed JSON, profile, Channel, MCP, runtime tokenDefault contains zero raw secrets; opt-in only permitted entries; no runtime/pairing tokens; manifest agrees with messaging
Restore and deletionMalicious tar, broad permissions, symlinks, missing fields, mid-restore crash, deletion failure and old backupsNo publication on validation failure; preserve missing secrets; recoverable journal; honest deletion status; no secure-erasure claim

5Feature-matrix filled-circle gate

OS-keychain credential storage remains OpenProgram · and is marked evaluated and rejected/not planned. It is not a proxy for file-hardening acceptance. Do not currently add a file-credential persistence consistency row, avoiding counting design as implementation.

Only after all following conditions hold may an independent capability row be added and marked complete:
  1. Every OpenProgram-persisted secret file/field is inventoried, with no unregistered writer;
  2. All writers pass shared creation-permission, atomic-write, fsync/replace, ACL/owner, concurrency and conflict tests on POSIX and Windows;
  3. Doctor/repair identifies and repairs legacy permissions within boundaries and rejects symlinks and foreign owners;
  4. Default backups contain zero raw secrets; opt-in exactly matches inventory; restore validates and reapplies restrictions;
  5. API/Web/CLI never return raw secrets, and masks/sentinels cannot be written as new secrets;
  6. Automated tests cover crash, directory-fsync, conflict, deletion and restore-journal failure semantics.

When adding that row, recalculate total capabilities, OpenProgram score, candidate count, old/new differences and narrative together.

6Current source locations

Current credential-file implementation is in openprogram/auth/credentials/. Production does not import safety/legacy.py; actual writer behavior is defined by credentials/io.py and inventory by credentials/inventory.py . This document does not treat old proposal commits, test counts or reviews as current implementation evidence.

Current status retains only basic write protections such as ordinary locks, atomic replace and fsync. 0600/0700, owner/symlink/inode checks, permission repair, revision conflicts, restore journal and old doctor/repair flows remain disabled.

Related documents:Unified authentication storage · Unified credential validation · Feature matrix. This document is a historical archive of a rejected proposal.