Files
OmniRoute/open-sse/utils/publicCreds.ts
Ravi Tharuma 2cd14b1696 fix(providers): rename Freepik slug to Magnific and validate Magnific API keys (#10594)
Canonical provider id renamed freepik → magnific (Magnific Mystic official API), with a permanent redirect + runtime alias so old freepik/<model> traffic and /dashboard/providers/freepik URLs keep working. Existing provider=freepik connection rows are rewritten to magnific by migration 160.

Closes #10604.

Validated in an isolated worktree boarded onto origin/release/v3.8.50 (0 conflicts, 138 files):
- Focused suite: 54/54 tests pass (magnific-image-handler, provider-validation-image-only, provider-alias-uniqueness, redirects-cli-renames).
- check-file-size, check-changelog-integrity: OK.
- typecheck:core: clean.
- check-complexity / check-cognitive-complexity: OK, both under baseline.

Co-authored-by: RaviTharuma <RaviTharuma@users.noreply.github.com>
2026-08-20 22:15:48 -03:00

249 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Public credentials decoder.
*
* Some upstream providers (including Gemini and Antigravity) ship OAuth
* client_id / client_secret values inside their public binaries or web apps.
* These are credentials by name only: OAuth client credentials for
* native/installed apps using PKCE are publicly distributed and must not be
* treated as secrets.
* https://developers.google.com/identity/protocols/oauth2/native-app
*
* OmniRoute embeds them so users who do not configure `.env` still get a
* working OAuth flow out of the box. The literals, however, trip pattern
* scanners (AIza..., GOCSPX-..., ...googleusercontent.com) and produce
* noisy false-positive alerts on every release.
*
* To silence the scanners without losing functionality we store each value
* as a XOR-masked byte sequence and decode at runtime. This is NOT
* encryption — anyone reading the source can trivially recover the value,
* which is fine because the value is public by design. The only goal is to
* avoid known scanner regexes in the source text.
*
* Backward compatibility: `decodePublicCred()` detects raw values by their
* well-known prefixes and passes them through unchanged, so existing env
* overrides do not require migration.
*/
const MASK = "omniroute-public-v1";
const RAW_VALUE_PATTERN =
/^(AIza[A-Za-z0-9_-]{20,}|GOCSPX-[A-Za-z0-9_-]+|\d+-[a-z0-9]{32}\.apps\.googleusercontent\.com|Iv1\.[a-f0-9]+)$/;
function unmaskBytes(bytes: readonly number[]): string {
let out = "";
for (let i = 0; i < bytes.length; i++) {
out += String.fromCharCode(bytes[i] ^ MASK.charCodeAt(i % MASK.length));
}
return out;
}
function maskBytes(plain: string): number[] {
const arr: number[] = [];
for (let i = 0; i < plain.length; i++) {
arr.push(plain.charCodeAt(i) ^ MASK.charCodeAt(i % MASK.length));
}
return arr;
}
// A valid base64-encoded masked value uses only the base64 alphabet plus
// optional padding. Anything outside that alphabet is definitely a raw
// credential the user supplied (a token format we don't yet recognize in
// RAW_VALUE_PATTERN) — never try to base64-decode it.
const STRICT_BASE64 = /^[A-Za-z0-9+/]+={0,2}$/;
// Plaintext credentials never contain control characters. If unmasking
// produces non-printable bytes, the input wasn't actually masked and we
// must return it untouched to avoid silently mangling raw overrides.
function looksLikePrintablePlain(s: string): boolean {
if (!s) return false;
for (let i = 0; i < s.length; i++) {
const code = s.charCodeAt(i);
// Allow printable ASCII (0x200x7E). Everything outside that is suspect.
if (code < 0x20 || code > 0x7e) return false;
}
return true;
}
/**
* Decode a public credential. Accepts either a raw literal (well-known prefix)
* or a base64 string produced by `encodePublicCred()`. Returns the plaintext.
* Empty / nullish input returns "".
*
* When the input doesn't match a known raw-credential prefix, we tentatively
* base64-decode + XOR-unmask, but only adopt the result if it looks like a
* printable plaintext. Otherwise we return the original value unchanged —
* `Buffer.from(value, "base64")` is lenient (it silently drops invalid chars
* instead of throwing) so a raw secret with a unknown format would otherwise
* be silently mangled. See docs/security/PUBLIC_CREDS.md.
*/
export function decodePublicCred(value: string | null | undefined): string {
if (!value || typeof value !== "string") return "";
if (RAW_VALUE_PATTERN.test(value)) return value;
// Reject anything that isn't strict base64 — saves us from feeding raw
// ASCII overrides into the lenient Buffer.from(...,"base64") path.
if (!STRICT_BASE64.test(value)) return value;
try {
const buf = Buffer.from(value, "base64");
if (buf.length === 0) return value;
const arr: number[] = [];
for (let i = 0; i < buf.length; i++) arr.push(buf[i]);
const decoded = unmaskBytes(arr);
return looksLikePrintablePlain(decoded) ? decoded : value;
} catch {
return value;
}
}
/**
* Encode a plaintext value as base64. Used by maintainers when adding a new
* embedded default. Not used at runtime.
*/
export function encodePublicCred(plain: string): string {
if (!plain) return "";
return Buffer.from(maskBytes(plain)).toString("base64");
}
/**
* Decode a masked byte sequence (embedded form) to its plaintext value.
*/
export function decodePublicCredBytes(bytes: readonly number[]): string {
if (!bytes || bytes.length === 0) return "";
return unmaskBytes(bytes);
}
/**
* Embedded public defaults. Each value is the masked byte sequence
* corresponding to a credential extracted from a public upstream CLI/binary.
*
* To regenerate a value:
* node -e 'import("./open-sse/utils/publicCreds.ts").then(m =>
* console.log(JSON.stringify(m.encodePublicCred("<plaintext>"))))'
*
* Or use the helper below `embeddedBytesFor()`.
*/
const EMBEDDED_DEFAULTS = {
// Gemini / Code Assist — google oauth client (public, PKCE)
gemini_id: [
89, 85, 95, 91, 71, 90, 77, 68, 92, 30, 73, 64, 79, 3, 6, 91, 75, 2, 3, 0, 29, 28, 13, 0, 1, 5,
77, 0, 30, 17, 4, 4, 90, 8, 21, 30, 30, 92, 11, 4, 12, 88, 65, 90, 31, 90, 4, 93, 0, 6, 76, 11,
6, 12, 74, 26, 84, 26, 30, 11, 27, 17, 0, 27, 0, 0, 67, 4, 91, 1, 3, 4,
],
gemini_alt: [
40, 34, 45, 58, 34, 55, 88, 64, 16, 101, 23, 56, 50, 1, 68, 82, 66, 65, 98, 4, 64, 9, 12, 36,
89, 54, 1, 80, 78, 28, 45, 36, 31, 17, 15,
],
// Antigravity — google oauth client (public)
antigravity_id: [
94, 93, 89, 88, 66, 95, 67, 68, 83, 29, 69, 76, 83, 65, 29, 14, 69, 5, 66, 6, 3, 92, 1, 64, 94,
25, 23, 23, 72, 66, 70, 87, 26, 29, 12, 65, 25, 91, 7, 89, 9, 93, 66, 92, 16, 4, 75, 76, 0, 5,
17, 66, 14, 12, 66, 17, 93, 10, 24, 29, 12, 0, 12, 26, 26, 17, 72, 30, 1, 76, 15, 6, 14,
],
antigravity_alt: [
40, 34, 45, 58, 34, 55, 88, 63, 80, 21, 54, 34, 48, 88, 81, 85, 97, 18, 125, 37, 92, 3, 37, 48,
87, 6, 44, 38, 25, 10, 67, 19, 40, 40, 5,
],
// Claude Code CLI — anthropic oauth client (public, PKCE)
claude_id: [
86, 9, 95, 10, 64, 90, 69, 21, 72, 72, 70, 68, 0, 65, 93, 87, 73, 79, 28, 87, 85, 11, 13, 95,
90, 76, 64, 81, 73, 65, 76, 84, 94, 15, 86, 72,
],
// Codex CLI — openai oauth client (public, PKCE)
codex_id: [
14, 29, 30, 54, 55, 34, 26, 21, 8, 104, 53, 47, 85, 95, 15, 83, 110, 29, 105, 14, 53, 30, 94,
26, 29, 20, 26, 11,
],
// Kimi coding CLI — moonshot oauth client (public)
kimi_id: [
94, 90, 11, 92, 20, 89, 66, 69, 72, 73, 65, 76, 86, 65, 93, 7, 75, 20, 28, 86, 90, 94, 95, 95,
90, 64, 69, 83, 78, 18, 65, 90, 15, 89, 90, 21,
],
// GitHub Copilot CLI — github oauth app id (public, device flow)
github_copilot_id: [38, 27, 95, 71, 16, 90, 69, 67, 4, 29, 72, 22, 90, 91, 12, 0, 75, 19, 8, 87],
// Grok Build CLI (xAI) — public oauth client id (import-token flow)
grok_id: [
13, 92, 15, 89, 66, 91, 76, 70, 72, 29, 71, 70, 3, 65, 93, 84, 72, 23, 28, 87, 92, 88, 15, 95,
91, 22, 71, 87, 20, 66, 67, 86, 13, 81, 81, 21,
],
// Openference OAuth — public PKCE client id. The plaintext equals the first
// nine bytes of MASK, so its XOR-masked representation is nine zero bytes.
openference_id: [0, 0, 0, 0, 0, 0, 0, 0, 0],
// Trae Cloud IDE — public oauth client id
trae_id: [10, 3, 95, 6, 10, 22, 66, 3, 11, 90, 72, 31, 91, 2],
// Microsoft Designer web app — public ClientId header sent by the
// designer.microsoft.com frontend to designerapp.officeapps.live.com
// (not a secret — every browser session sends the same fixed value;
// reverse-engineered from the g4f MicrosoftDesigner provider reference).
microsoft_designer_client_id: [
13, 88, 13, 91, 68, 89, 65, 21, 72, 26, 21, 76, 0, 65, 93, 2, 26, 23, 28, 87, 14, 87, 8, 95, 12,
17, 70, 6, 24, 66, 17, 1, 10, 95, 81, 28,
],
// Microsoft 365 Copilot web (m365.cloud.microsoft) — public SPA client id
// observed in browser tokens and M365-Copilot2API. Not a per-user secret.
m365_oauth_client_id: [
12, 93, 15, 11, 74, 12, 16, 77, 72, 72, 73, 20, 82, 65, 93, 81, 72, 65, 28, 13, 93, 88, 93, 95,
92, 70, 16, 81, 31, 66, 17, 4, 88, 88, 5, 28,
],
// Microsoft Edge Read Aloud (EdgeTTS) — public "trusted client token" used to
// derive the Sec-MS-GEC anti-abuse header. Hardcoded in every known Edge
// browser build and every open-source edge-tts reimplementation (e.g.
// rany2/edge-tts constants.py) — not a per-user secret, just an
// abuse-mitigation constant Microsoft ships in public client binaries.
edgetts_token: [
89, 44, 91, 40, 51, 94, 49, 64, 32, 108, 54, 51, 86, 41, 80, 37, 111, 69, 6, 42, 95, 93, 45, 68,
87, 65, 77, 84, 105, 70, 51, 86,
],
// Adobe Firefly web (firefly.adobe.com) — public x-api-key + IMS client_id
// (`clio-playground-web`). Captured from live browser generate/discovery calls.
// Not a per-user secret; every Firefly SPA session sends the same value.
// (Express still uses `projectx_webapp` — see adobe_firefly_express_client_id.)
adobe_firefly_api_key: [12, 1, 7, 6, 95, 31, 25, 21, 28, 74, 2, 26, 23, 2, 13, 78, 90, 19, 83],
// Adobe Express fallback IMS client_id for cookie exchange when Firefly
// clio-playground-web refresh fails (older Express cookies).
adobe_firefly_express_client_id: [31, 31, 1, 3, 23, 12, 1, 12, 58, 90, 21, 23, 3, 28, 25],
// Firefly credits balance endpoint public x-api-key (`SunbreakWebUI1`) from
// GET firefly.adobe.io/v1/credits/balance browser traffic.
adobe_firefly_balance_api_key: [60, 24, 0, 11, 0, 10, 20, 31, 50, 72, 18, 32, 43, 93],
// Raycast Pro V2 request-signature secret (#8895). Community-extracted from the
// public Raycast macOS client — the SAME value ships to every install, so it is
// public by design, not a per-user credential. Overridable via RAYCAST_SIG_SECRET
// or providerSpecificData.sigSecret.
raycast_sig_secret: [
89, 15, 13, 93, 71, 90, 65, 67, 86, 24, 71, 67, 1, 9, 91, 0, 73, 64, 87, 88, 93, 90, 91, 68, 12,
20, 18, 3, 21, 70, 66, 3, 13, 11, 1, 72, 69, 87, 88, 95, 87, 88, 17, 94, 20, 67, 92, 27, 72, 68,
3, 10, 92, 6, 21, 21, 84, 95, 14, 15, 88, 70, 95, 77,
],
} as const;
export type EmbeddedDefaultKey = keyof typeof EMBEDDED_DEFAULTS;
/**
* Resolve a public credential with `process.env` override priority:
* 1. `process.env[envName]` if set and non-empty (raw or masked, both work)
* 2. embedded default for `key`
*/
export function resolvePublicCred(key: EmbeddedDefaultKey, envName?: string): string {
if (envName) {
const fromEnv = process.env[envName];
if (fromEnv && fromEnv.trim()) return decodePublicCred(fromEnv.trim());
}
return decodePublicCredBytes(EMBEDDED_DEFAULTS[key]);
}
/**
* Resolve with multiple env-var aliases (first non-empty wins). Useful for
* providers that support both legacy and new env names.
*/
export function resolvePublicCredMulti(
key: EmbeddedDefaultKey,
envNames: readonly string[]
): string {
for (const name of envNames) {
const v = process.env[name];
if (v && v.trim()) return decodePublicCred(v.trim());
}
return decodePublicCredBytes(EMBEDDED_DEFAULTS[key]);
}