Public Credentials Handling
Source of truth:
open-sse/utils/publicCreds.tsTests:tests/unit/publicCreds.test.tsLast 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
Section titled “Why this exists”- OAuth 2.0 for native apps (PKCE) — OAuth client_id / client_secret for installed apps are public; PKCE provides the actual security.
- Firebase 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 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.envkeep 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
Section titled “The mandatory pattern”1. Adding a new public credential
Section titled “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:
-
Generate the masked byte sequence:
Terminal window 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")))))' -
Add a new entry to
EMBEDDED_DEFAULTSinopen-sse/utils/publicCreds.tswith a neutral key name (<provider>_id,<provider>_alt,<provider>_fb, etc.). Do not use names likeclient_secretorapi_keyin the helper — those words trigger Semgrep generic-secret rules. -
Add a
keyof typeof EMBEDDED_DEFAULTSto the public type union (it is inferred automatically). -
In the consumer code, replace the hardcoded literal with:
// single env overrideclientSecret: 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"), -
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= -
Update
tests/unit/publicCreds.test.tsto add a shape assertion for the new key (verify format, not literal value — see existing tests for the pattern). -
Never add
AIza…/GOCSPX-…/…apps.googleusercontent.comliterals to test files. Use theFAKE_*constants built from.join("")fragments (see existing tests).
2. Consumers
Section titled “2. Consumers”- Read from
resolvePublicCred()/resolvePublicCredMulti()only — never calldecodePublicCredBytes()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
Section titled “3. Forbidden patterns”❌ Never do any of the following in production code (src/, open-sse/, electron/, bin/):
// BAD: literal value triggers Secret Scanning + SemgrepclientSecret: process.env.PROVIDER_OAUTH_CLIENT_SECRET || "GOCSPX-realvalue",
// BAD: base64 of the literal — GitHub still detects since Feb/2025clientSecret: process.env.PROVIDER_OAUTH_CLIENT_SECRET || Buffer.from("R09DU1BYLXJlYWx2YWx1ZQ==", "base64").toString(),
// BAD: string concatenation that re-assembles the pattern at runtimeclientSecret: "GO" + "CS" + "PX-" + "realvalue",
// BAD: hex/ROT13 encoding — different obfuscation, same risk of detectionclientSecret: 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
Section titled “Related controls”RAW_VALUE_PATTERNinpublicCreds.tsenumerates the prefixes that trigger passthrough (retrocompat). Extend it only for documented public credential formats, never for proprietary secrets..env.examplelives in CI’scheck-env-doc-syncscript — when you remove a var here, make sure the docs match.- The
npm run test:vitestandnode --import tsx/esm --test tests/unit/publicCreds.test.tssuites must both stay green.
When NOT to use this helper
Section titled “When NOT to use this helper”This helper is only for credentials that are:
- Distributed publicly by the upstream provider (CLI binary, browser bundle, official docs).
- 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
Section titled “References”- Google: OAuth 2.0 for native apps
- Firebase: API keys for client identification
- GitHub Secret Scanning supported secrets
- GitHub: base64 detection for tokens (Feb 2025)
- Commit introducing this helper:
1a39c31f— fix(security): mask public upstream creds + centralize error sanitization
HagiCode
HagiCode is an agentic coding workspace. Rich, convenient UI interactions make planning, agent execution, and project work approachable and easy to navigate, creating an engaging development experience.
Explore an engaging development experience through rich, convenient UI interactions.
- SmartOpenSpec workflows turn intent into an executable path, from proposal to design, tasks, and a shipped change.
- EfficientMulti-agent execution keeps research, implementation, and review moving in parallel, across several repositories and the Agent CLIs you already use.
- InterestingRich, convenient UI interactions make planning, agent execution, and project work approachable and easy to navigate.
Download for Windows
Share HagiCode
Turn ideas into shipped software.
HagiCode is an agentic coding workspace. OpenSpec workflows plan the change, multi-agent execution carries it out, and Hero Scenario makes the long run visible, so ideas become shipped software.
- Smart
- OpenSpec workflows turn intent into a path from proposal to shipped change.
- Efficient
- Multi-agent execution runs research, build, and review in parallel.
- Fun
- Rich UI interactions make planning, agent work, and projects easier to follow.
© 2026 HagiCode | Powered By hagilight-starlight@0.21.0 | Branch: main | Commit: de60bcd | Built:

