Skip to content

fix: harden transport, JSON fidelity, and profile recovery#80

Merged
francoischalifour merged 19 commits into
mainfrom
fc/harden-boundaries
Jul 21, 2026
Merged

fix: harden transport, JSON fidelity, and profile recovery#80
francoischalifour merged 19 commits into
mainfrom
fc/harden-boundaries

Conversation

@francoischalifour

Copy link
Copy Markdown
Member

The CLI had several correctness and reliability gaps at system boundaries:

  • Streamed requests could hang while reading non-2xx response bodies.
  • Peer-initiated stream aborts could be reported misleadingly as CLI timeouts.
  • Typed API integers could lose precision or produce invalid JSON when they exceeded JavaScript’s safe range or contained leading zeroes.
  • Human-readable query output could leak sensitive values from JSON string cells, while redaction could round unrelated large numeric identifiers.
  • Configuration file I/O failures were sometimes treated as missing files.
  • Profile rename/delete operations could leave credentials, directories, or active-profile state inconsistent after partial failures.
  • Rollback failures were suppressed, making incomplete recovery difficult to diagnose.

Proposed solution

This branch hardens those boundaries by:

  • Applying configured read deadlines while consuming both successful and error response streams.
  • Distinguishing failures caused by the CLI’s timeout signal from peer/network aborts.
  • Canonicalizing typed integer tokens before serializing them, while preserving their exact magnitude and digits in request bodies and query parameters.
  • Preserving append JSON payloads after validation instead of parsing and reserializing them.
  • Surfacing configuration filesystem failures as ConfigurationError while continuing to ignore only genuinely missing files.
  • Replacing parse-and-reserialize query redaction with a source-preserving JSON walker that retains non-sensitive numeric lexemes, formatting, escaped keys, arrays, and nested values.
  • Making profile secret moves transactional across source deletion failures.
  • Restoring profile directories and configuration bytes after partial recursive deletion, including valid profiles with no config file.
  • Reporting incomplete rollback with a ConfigurationError that retains the original failure as its cause and lists each failed recovery step.

Testing

Added regression coverage for:

  • Large, negative, zero-padded, and query-string integer values.
  • Stalled streamed error responses.
  • Peer-aborted streams.
  • Nested arrays and structured sensitive JSON values.
  • Escaped sensitive keys and whitespace preservation.
  • Malformed JSON redaction input.
  • Configless profile restoration.
  • Partial deletion and incomplete rollback diagnostics.

@albert20260301 albert20260301 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the transport timeout boundaries, source-preserving JSON handling, and profile rollback paths. The implementation and regression coverage look sound; focused tests and type-aware lint pass locally.

Comment thread cli/src/lib/profile/model.ts Outdated
} catch (error) {
if (profileExists(target) && !profileExists(source)) {
renameSync(profileDir(target), profileDir(source));
secrets.moveProfileSecrets(source, target, PROFILE_SECRET_ACCOUNTS);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a clearer way would be to use a random id for config and secret prefixes, and just point to the id in the profile config.

Or just have profile ids be these random ids, and have a profile display-label (more or less the same)

Map connect and body-read aborts to the CLI TimeoutError contract so streaming and buffered requests consistently exit with network status 9.
Ignore only genuinely missing files and wrap other read, write, update, and clear failures as ConfigurationError. Avoid reporting a successful clear when filesystem removal fails.
Keep append payload text unchanged after validation and serialize typed API integers beyond JavaScript's safe range as exact JSON numeric tokens. Preserve the same digits in query parameters.
Parse JSON-shaped string cells before human rendering and recursively mask password, token, secret, and API key fields while preserving unchanged JSON text.
Restore profile directories, credentials, and active selection when rename or delete steps fail. Make secret moves restore both source and target state after partial deletion failures.
Normalize signs and leading zeroes before raw numeric serialization so every typed integer produces valid JSON while preserving exact magnitude in request bodies and query parameters.
Apply the configured stream read deadline while consuming non-2xx response bodies and always clear the active timer after body completion or failure.
Classify failures as timeouts only when the CLI's internal deadline signal fired. Report peer-originated AbortError failures through the network error path.
Redact sensitive value spans with a source-aware JSON walker instead of parse-and-reserialize so unrelated large numeric identifiers and formatting remain exact.
Snapshot profile config bytes and permissions before quarantine removal, then recreate them during rollback even when recursive deletion removed part or all of the quarantined directory.
Recreate the source profile directory unconditionally after failed deletion, then restore config bytes only when the original profile had a config file.
Collect failed secret, directory, configuration, and active-profile restoration steps. Return a ConfigurationError with the original cause and actionable rollback details when recovery is incomplete.
Exercise nested arrays, escaped sensitive keys, structured sensitive values, whitespace and large-number preservation, and malformed JSON directly against the source-aware redactor.
Compose rename and delete recovery from named rollback steps, snapshot profile state through semantic types, and keep incomplete-recovery diagnostics centralized.
Move source-preserving JSON redaction into a small parser object so cursor ownership, recursive traversal, and lexeme preservation are explicit.
Share key parsing and temporary-file replacement between set and unset operations, while retaining protected file modes and actionable I/O errors.
Use one transport-error classifier and one active timeout setter across buffered and streaming requests, preserving peer-abort diagnostics and read deadlines.
Name canonical integer JSON and field formatting by their wire purpose, and separate property collection from final request-body serialization.
@albert20260301

Copy link
Copy Markdown
Contributor

The stable-ID follow-up passes focused profile/secrets/configure tests and type-aware lint locally. GitHub currently reports this branch as conflicting with main; please resolve that conflict so the required CI suite can run again before merge.

@francoischalifour
francoischalifour enabled auto-merge (squash) July 21, 2026 16:47
@francoischalifour
francoischalifour merged commit facdf90 into main Jul 21, 2026
12 checks passed
@francoischalifour
francoischalifour deleted the fc/harden-boundaries branch July 21, 2026 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants