Files
2026-07-13 13:39:12 +08:00

142 lines
7.5 KiB
Markdown

---
title: "Public Credentials Handling"
version: 3.8.40
lastUpdated: 2026-06-28
---
# Public Credentials Handling
> **Source of truth:** `open-sse/utils/publicCreds.ts`
> **Tests:** `tests/unit/publicCreds.test.ts`
> **Last updated:** 2026-06-28 — v3.8.40
> **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
- [OAuth 2.0 for native apps (PKCE)](https://developers.google.com/identity/protocols/oauth2/native-app) — OAuth client_id / client_secret for installed apps are public; PKCE provides the actual security.
- [Firebase API keys](https://firebase.google.com/docs/projects/api-keys) — Web client identifiers are public by design.
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 / Windsurf 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:
```bash
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:
```ts
// 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):
```dotenv
# ── 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/`):
```ts
// 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.
## Related controls
- `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](./COMPLIANCE.md), not in source.
## References
- [Google: OAuth 2.0 for native apps](https://developers.google.com/identity/protocols/oauth2/native-app)
- [Firebase: API keys for client identification](https://firebase.google.com/docs/projects/api-keys)
- [GitHub Secret Scanning supported secrets](https://docs.github.com/en/code-security/secret-scanning/introduction/supported-secret-scanning-patterns)
- [GitHub: base64 detection for tokens (Feb 2025)](https://github.blog/changelog/2025-02-14-secret-scanning-detects-base64-encoded-github-tokens/)
- Commit introducing this helper: `1a39c31f` — _fix(security): mask public upstream creds + centralize error sanitization_