Files
OmniRoute/docs/security/PUBLIC_CREDS.md
Praveen K Palaniswamy 65e81158ab fix(ollama): route models by advertised capability (#11088)
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host.

Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean.

Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
2026-08-23 11:45:01 -03:00

7.4 KiB

title, version, lastUpdated
title version lastUpdated
Public Credentials Handling 3.8.50 2026-08-07

Public Credentials Handling

Source of truth: open-sse/utils/publicCreds.ts Tests: tests/unit/publicCreds.test.ts Last updated: 2026-08-07 — v3.8.50 Audience: Engineers integrating providers that ship public OAuth client_id / client_secret / Firebase Web API keys in their public CLIs. Status: MANDATORY for all new code that embeds upstream identifiers.

Why this exists

OmniRoute must embed these values so users who do not configure .env still get a working OAuth flow out of the box. Without an embedded fallback, the Gemini / Antigravity providers stop working for any user who follows the "just clone and run" path.

However, literal values like AIzaSy…, GOCSPX-…, …apps.googleusercontent.com are matched by GitHub Secret Scanning, Semgrep, and similar pattern scanners. Every release becomes a noisy stream of false positives, push protection blocks legitimate commits, and operators stop trusting the alert feed.

The open-sse/utils/publicCreds.ts helper solves both constraints at once:

  • Embeds the public identifier as a XOR-masked byte sequence (no scanner pattern in source).
  • Decodes at runtime via decodePublicCred / resolvePublicCred.
  • Detects raw values that already follow well-known prefixes (AIza, GOCSPX-, <digits>-<32hex>.apps.googleusercontent.com, Iv1.<hex>) and passes them through unchanged, so users with raw values in their existing .env keep working with zero migration.

This is obfuscation, not encryption. Anyone reading the source can recover the value — which is fine because the value is public by design. The only goal is to avoid scanner regex matches.

The mandatory pattern

1. Adding a new public credential

When you need to embed a new upstream-provided value that:

  • comes from a public CLI / desktop app / browser bundle, and
  • the upstream provider documents (or treats) it as a public client identifier, and
  • a pattern scanner would otherwise match it (AIza…, GOCSPX-…, <digits>-…apps.googleusercontent.com, etc.),

…follow this checklist:

  1. Generate the masked byte sequence:

    node --import tsx/esm -e \
      'import("./open-sse/utils/publicCreds.ts").then(m =>
         console.log(JSON.stringify(Array.from(
           Buffer.from(m.encodePublicCred("THE_PUBLIC_VALUE"), "base64")
         ))))'
    
  2. Add a new entry to EMBEDDED_DEFAULTS in open-sse/utils/publicCreds.ts with a neutral key name (<provider>_id, <provider>_alt, <provider>_fb, etc.). Do not use names like client_secret or api_key in the helper — those words trigger Semgrep generic-secret rules.

  3. Add a keyof typeof EMBEDDED_DEFAULTS to the public type union (it is inferred automatically).

  4. In the consumer code, replace the hardcoded literal with:

    // single env override
    clientSecret: resolvePublicCred("provider_alt", "PROVIDER_OAUTH_CLIENT_SECRET"),
    
    // multiple env aliases (first non-empty wins)
    clientId: resolvePublicCredMulti("provider_id", [
      "PROVIDER_CLI_OAUTH_CLIENT_ID",
      "PROVIDER_OAUTH_CLIENT_ID",
    ]),
    
    // no env override (always embedded default)
    firebaseApiKey: resolvePublicCred("provider_fb"),
    
  5. Remove the literal from .env.example (replace with comment-only documentation pointing readers here):

    # ── Provider (Google / Firebase / etc.) ──
    # Public OAuth credentials are baked into the code via
    # open-sse/utils/publicCreds.ts. Set these vars only to use your own.
    # PROVIDER_OAUTH_CLIENT_ID=
    # PROVIDER_OAUTH_CLIENT_SECRET=
    
  6. Update tests/unit/publicCreds.test.ts to add a shape assertion for the new key (verify format, not literal value — see existing tests for the pattern).

  7. Never add AIza… / GOCSPX-… / …apps.googleusercontent.com literals to test files. Use the FAKE_* constants built from .join("") fragments (see existing tests).

2. Consumers

  • Read from resolvePublicCred() / resolvePublicCredMulti() only — never call decodePublicCredBytes() directly outside the helper.
  • The helper is intentionally cheap (linear byte XOR) and safe to call at module-load time; defaults are computed once.
  • The env override always wins. If a user sets PROVIDER_OAUTH_CLIENT_SECRET=GOCSPX-myown, the helper passes that raw value straight through.

3. Forbidden patterns

Never do any of the following in production code (src/, open-sse/, electron/, bin/):

// BAD: literal value triggers Secret Scanning + Semgrep
clientSecret: process.env.PROVIDER_OAUTH_CLIENT_SECRET || "GOCSPX-realvalue",

// BAD: base64 of the literal — GitHub still detects since Feb/2025
clientSecret: process.env.PROVIDER_OAUTH_CLIENT_SECRET ||
  Buffer.from("R09DU1BYLXJlYWx2YWx1ZQ==", "base64").toString(),

// BAD: string concatenation that re-assembles the pattern at runtime
clientSecret: "GO" + "CS" + "PX-" + "realvalue",

// BAD: hex/ROT13 encoding — different obfuscation, same risk of detection
clientSecret: hexDecode("474f4353..."),

These all eventually trip a scanner. Use resolvePublicCred().

Never add literal credentials to .env.example. Users who need real upstream values can extract them from the public CLI themselves, or use their own OAuth registration.

Never dismiss a new secret-scanning alert without first checking whether the credential should be moved to this helper.

  • RAW_VALUE_PATTERN in publicCreds.ts enumerates the prefixes that trigger passthrough (retrocompat). Extend it only for documented public credential formats, never for proprietary secrets.
  • .env.example lives in CI's check-env-doc-sync script — when you remove a var here, make sure the docs match.
  • The npm run test:vitest and node --import tsx/esm --test tests/unit/publicCreds.test.ts suites must both stay green.

When NOT to use this helper

This helper is only for credentials that are:

  1. Distributed publicly by the upstream provider (CLI binary, browser bundle, official docs).
  2. Documented or strongly implied to be non-confidential (PKCE-protected, Firebase Web key, similar).

For everything else — operator-issued tokens, per-tenant secrets, your own OAuth app's client_secret, encryption keys, JWT secrets, database passwords — use env vars only (process.env.FOO, || fallback to empty / explicit error). These belong in .env and the encrypted credentials store, not in source.

References