Skip to content

feat: add CLI to export decrypted provider credentials (omniroute auth export) (#6683)#7724

Merged
diegosouzapw merged 2 commits into
release/v3.8.49from
feat/6683-cli-auth-export
Jul 19, 2026
Merged

feat: add CLI to export decrypted provider credentials (omniroute auth export) (#6683)#7724
diegosouzapw merged 2 commits into
release/v3.8.49from
feat/6683-cli-auth-export

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Summary

Adds a local-only omniroute auth export CLI command that dumps decrypted provider credentials (apiKey, accessToken, refreshToken, idToken) for one connection (--id <id>) or all connections, as json (default) or env (--format).

This is a pure CLI/direct-SQLite command — no src/app/api/ route is added, matching the reset-encrypted-columns / keys reveal precedent. It reuses the existing CLI-side crypto mirror (bin/cli/encryption.mjs::decryptCredential) and row-read helpers (bin/cli/provider-store.mjs::listProviderConnections/findProviderConnection) — no new crypto, no new DB module.

Security controls (the feature's entire purpose is to print plaintext secrets — these are the mitigations, not decoration)

  • (a) --force gate before any DB access — without --force, the command prints a confirmation message and returns 0 without opening the database or decrypting anything.
  • (b) stderr warning banner before any plaintext is emitted — mirrors keys reveal's revealWarning.
  • (c) file output written with mode 0600--out <file> uses writeFileSync(..., { mode: 0o600 }), plus a chmodSync pass if the file pre-existed with looser permissions.
  • (d) per-field decrypt failures surface as a boolean flag (<field>DecryptFailed: true), never the caught error text — one stale-key/corrupt row doesn't abort the whole export.
  • (e) no decrypted value is ever interpolated into an Error/console message — only connection ids and field names appear in error paths.
  • (f) no src/app/api/ route — CLI-only, direct SQLite read.

Requires STORAGE_ENCRYPTION_KEY to be set (checked explicitly, exits 1 with a clear error otherwise — no ciphertext/plaintext ever appears in that error).

Files changed

  • bin/cli/commands/auth-export.mjs — new command implementation
  • bin/cli/commands/registry.mjs — registers auth export
  • bin/cli/locales/en.json / bin/cli/locales/pt-BR.json — new authExport.* i18n namespace
  • docs/reference/CLI-TOOLS.md — new "Credential Export (⚠ handle with care)" doc section
  • tests/unit/cli-auth-export-command.test.ts — new test file (8 cases)
  • changelog.d/features/6683-cli-auth-export.md

How it was validated (TDD, Hard Rule #18)

Wrote tests/unit/cli-auth-export-command.test.ts first. Proved red→green by temporarily moving auth-export.mjs aside (not git stash — a plain file move) and re-running the suite: all 8 tests failed with ERR_MODULE_NOT_FOUND (red), then passed again after restoring the file (green).

node --import tsx/esm --test tests/unit/cli-auth-export-command.test.ts
# ℹ tests 8 / pass 8 / fail 0

Test coverage: force-gate dry-run (no DB touch, no secret in output), missing-STORAGE_ENCRYPTION_KEY failure path (no leak), --id filter + JSON decrypt round-trip, malformed-ciphertext tolerance (<field>DecryptFailed flag, sibling fields unaffected), --format env line shape, --out file mode 0600 (including a pre-existing-looser-permissions case), not-found --id (no secret in the error), and a dedicated security regression test capturing all stdout/stderr/error text across the dry-run, missing-key, and malformed-ciphertext paths and asserting no plaintext secret ever appears outside its own legitimate export payload.

Gates run (green)

  • node scripts/check/check-test-discovery.mjs
  • npm run typecheck:core — clean
  • npm run typecheck:noimplicit:core — pre-existing errors only, all in files this PR does not touch (open-sse/services/combo.ts, open-sse/utils/usageTracking.ts, src/shared/services/cliRuntime.ts — confirmed via git diff --stat origin/release/v3.8.49...HEAD touching none of them)
  • npx eslint --suppressions-location config/quality/eslint-suppressions.json <changed files> — 0 errors (bin/cli/** is outside ESLint's configured scope entirely, confirmed pre-existing)
  • npm run check:complexity-ratchets — 2059/890, unchanged from baseline
  • node scripts/check/check-file-size.mjs — no on touched files
  • npm run check:cycles — no new cycles
  • node scripts/check/check-cli-i18n.mjs — all t() keys resolve, pt-BR.json has all top-level sections
  • npm run check:docs-sync — pass (ran via the pre-commit hook)
  • node --import tsx/esm --test tests/unit/cli-auth-export-command.test.ts — 8/8 green

npm run test:coverage intentionally not run locally (shared box, heaviest gate — CI runs it authoritatively); this PR is additive-only (one new file + two-line registry hook + i18n namespace + doc + new isolated test file), so no existing coverage should regress.

Deferred / decisions

  • The plan's illustrative pseudocode referenced t("common.forceOpt") / t("common.notFound"), but those keys don't exist in common.*. Added dedicated authExport.forceOpt / authExport.notFound keys instead of touching the shared common namespace, to keep the change surface minimal.
  • Per the mandatory security instructions for this feature, skipped the plan's "list which connection(s) will be exported" detail in the pre---force message — the dry-run message is now fully generic and never touches the DB, which is a stricter interpretation of "no DB access before --force" than the plan's illustrative pseudocode.
  • No i18n translation added for locales other than en/pt-BR, matching the keys.* precedent and the CLI i18n gate's actual requirement (top-level section parity in pt-BR.json, not full-locale translation).

Closes #6683

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@diegosouzapw
diegosouzapw merged commit 7951b60 into release/v3.8.49 Jul 19, 2026
13 of 20 checks passed
@diegosouzapw
diegosouzapw deleted the feat/6683-cli-auth-export branch July 19, 2026 21:00
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.

feat(auth): CLI to export decrypted provider credentials (omniroute auth export)

1 participant