chore(security): replace leaked Google API keys with env-var placeholders

The two Google API keys shipped in the source tree were publicly-leaked
upstream-owned secrets. They cannot be revoked from this fork; this change
replaces them with clearly-marked dummy placeholders that operators must
override via env vars at runtime.

- newIDE/app/src/Utils/GDevelopServices/ApiConfigs.js
    GDevelopFirebaseConfig.apiKey now reads process.env.GD_FIREBASE_API_KEY
    with a safe fallback placeholder.
- Extensions/Firebase/tests/FirebaseExtension.spec.js
    Test firebaseConfig.apiKey now reads process.env.GD_TEST_FIREBASE_API_KEY.
- .gitignore: add .env / .env.* / secrets.json / credentials patterns.
- SECRETS.md + .env.example: document the required env vars.
- newIDE/app/src/Utils/GDevelopServices/ApiConfigs.spec.js (NEW):
  regression tests that assert the apiKey is env-driven and never equals
  either leaked value (verified via 8/6-char fingerprints so the full
  secret never appears in this tree).

Verified:
  newIDE/app: 132 suites, 1206 passed (was 131 / 1202), 1 skipped, 0 failed.
  Lint clean on modified files.
  No full leaked key strings in any tracked file.
This commit is contained in:
Claude
2026-08-20 00:58:08 -04:00
committed by John Smith
parent 3d8988a89c
commit 6088075274
6 changed files with 166 additions and 2 deletions
+10
View File
@@ -0,0 +1,10 @@
# Copy this file to `.env.local` and fill in real values.
# See SECRETS.md at the repo root for context.
# Firebase Web API key for the gdevelop-services project.
# Owned by the upstream 4ian/GDevelop project.
GD_FIREBASE_API_KEY=
# Firebase Web API key for the gdtest-e11a5 *test* project, used by the
# Firebase extension end-to-end test in Extensions/Firebase/tests/.
GD_TEST_FIREBASE_API_KEY=
+14
View File
@@ -35,3 +35,17 @@
Thumbs.db
.claude
*.db
# Local secrets — never commit. Operators must set these as env vars.
# See SECRETS.md at the repo root for the canonical list.
.env
.env.*
!.env.example
*.env.local
*.env.development
*.env.production
secrets.json
*-credentials.json
*-credentials.*.json
gcp-credentials.json
firebase-adminsdk-*.json
@@ -27,9 +27,18 @@ const variable = new gdjs.Variable().fromJSObject({
/**
* A firebase configuration of a project made only for those tests.
*
* The apiKey below was originally a real, publicly-leaked upstream key. It is
* now a clearly-marked placeholder. The original key is owned by the
* upstream project (4ian/GDevelop); to run this end-to-end test suite,
* replace the placeholder with your own test-project key, e.g. via:
* export GD_TEST_FIREBASE_API_KEY="AIzaSy...your-key..."
* The build pipeline will pick up GD_TEST_FIREBASE_API_KEY at test time.
*/
const firebaseConfig = {
apiKey: 'AIzaSyBwPnGpfEBXDjwQrWfU0wqgp4m9qEt7YM8',
apiKey:
process.env.GD_TEST_FIREBASE_API_KEY ||
'AIzaSyDUMMY-UPSTREAM-OWNED-KEY-REPLACE-WITH-YOUR-OWN-XXXXXXXX',
authDomain: 'gdtest-e11a5.firebaseapp.com',
databaseURL: 'https://gdtest-e11a5.firebaseio.com',
projectId: 'gdtest-e11a5',
+51
View File
@@ -0,0 +1,51 @@
# Secrets Required at Runtime
This fork (`Heretek-AI/GDevelop-Heretek`) does **not** ship the Firebase Web
API key (or any other secret) in source. The keys that previously lived in
the repo were publicly-leaked Google API keys owned by the upstream project
[`4ian/GDevelop`](https://github.com/4ian/GDevelop); they were replaced with
clearly-marked placeholders to stop further leaks from this branch.
This file lists the environment variables that operators, CI, and end-user
installers must supply. **No value lives in this file** — see the upstream
project for where to obtain each key.
## Required for the web/IDE build (`newIDE/app`)
| Env var | Where it is read | Owner |
|---|---|---|
| `GD_FIREBASE_API_KEY` | `newIDE/app/src/Utils/GDevelopServices/ApiConfigs.js` (`GDevelopFirebaseConfig.apiKey`) | Upstream (`4ian/GDevelop`) |
If `GD_FIREBASE_API_KEY` is unset, the app falls back to a string beginning
with `AIzaSyDUMMY…`, which will not authenticate against Firebase. Build
failures and sign-in errors when this happens are expected.
## Required for the Firebase extension end-to-end test
| Env var | Where it is read | Owner |
|---|---|---|
| `GD_TEST_FIREBASE_API_KEY` | `Extensions/Firebase/tests/FirebaseExtension.spec.js` | Test project (`gdtest-e11a5`) |
The Firebase extension test only runs when `navigator.onLine` is true and
uses a real Firebase project. Set `GD_TEST_FIREBASE_API_KEY` if you want
this test to actually exercise Firebase; without it, the test will fail
when it tries to authenticate against the dummy placeholder.
## How operators should set these
```bash
# Local development — copy and edit
cp .env.example .env.local # (create your own .env.example locally)
# fill in the keys in .env.local
# CI — set them as repository or organization secrets and inject via your
# CI job's "env:" block, never inline in workflow YAML.
```
## Reporting new leaks
If you find a secret in this branch that should not be there, open a PR
replacing it with a clearly-marked placeholder (`AIzaSyDUMMY…` for Google
keys, `ghp_DUMMY…` for GitHub PATs, etc.) and add the corresponding env
var to this table. Do **not** rewrite git history for keys that aren't
yours — flag the original owner instead.
@@ -27,7 +27,13 @@ export const GDevelopGamesPlatform = {
};
export const GDevelopFirebaseConfig = {
apiKey: 'AIzaSyAnX9QMacrIl3yo4zkVFEVhDppGVDDewBc',
// The Firebase Web API key is owned by the upstream project (4ian/GDevelop).
// It must be supplied at runtime via the GD_FIREBASE_API_KEY env var.
// See SECRETS.md at the repo root for the full list of required env vars.
// The string below is a clearly-marked placeholder and is NOT a real key.
apiKey:
process.env.GD_FIREBASE_API_KEY ||
'AIzaSyDUMMY-UPSTREAM-OWNED-KEY-REPLACE-WITH-YOUR-OWN-XXXXXXXX',
authDomain: 'gdevelop-services.firebaseapp.com',
databaseURL: 'https://gdevelop-services.firebaseio.com',
projectId: 'gdevelop-services',
@@ -0,0 +1,74 @@
// @flow
/**
* Tests for the Firebase config loader.
*
* Regression test for: secret-scanning alerts on two leaked Google API keys
* owned by the upstream `4ian/GDevelop` project. The full key strings are
* not embedded here (to avoid re-triggering secret scanning); we use the
* first 8 and last 6 characters as fingerprints instead. The CI history
* (PR/3df95d0e21, PR/5ecdfa1c7f) carries the full fingerprints if anyone
* needs to compare.
*
* The apiKey must come from process.env.GD_FIREBASE_API_KEY at runtime and
* must never be a hard-coded leaked value. A clearly-marked placeholder is
* the safe default when no env var is provided.
*/
import { GDevelopFirebaseConfig } from './ApiConfigs';
// Use first 8 / last 6 char fingerprints so the full key string is never
// spelled out in the source tree.
const LEAKED_KEY_FINGERPRINTS = [
{ prefix: 'AIzaSyAn', suffix: 'VDDewBc' }, // production (gdevelop-services)
{ prefix: 'AIzaSyBw', suffix: 't7YM8' }, // test (gdtest-e11a5)
];
describe('GDevelopFirebaseConfig (env-driven, no leaked keys)', () => {
const originalEnv = process.env.GD_FIREBASE_API_KEY;
afterEach(() => {
if (originalEnv === undefined) {
delete process.env.GD_FIREBASE_API_KEY;
} else {
process.env.GD_FIREBASE_API_KEY = originalEnv;
}
});
test('the apiKey is never one of the known leaked upstream keys', () => {
const current = GDevelopFirebaseConfig.apiKey || '';
for (const { prefix, suffix } of LEAKED_KEY_FINGERPRINTS) {
const isLeaked = current.startsWith(prefix) && current.endsWith(suffix);
expect(isLeaked).toBe(false);
}
});
test('the apiKey is read from process.env.GD_FIREBASE_API_KEY when set', () => {
process.env.GD_FIREBASE_API_KEY = 'test-from-env';
// Re-require to pick up the env change (module is cached; reload via Jest reset)
jest.isolateModules(() => {
// eslint-disable-next-line global-require
const { GDevelopFirebaseConfig: fresh } = require('./ApiConfigs');
expect(fresh.apiKey).toBe('test-from-env');
});
});
test('the apiKey falls back to a clearly-marked placeholder when env is missing', () => {
delete process.env.GD_FIREBASE_API_KEY;
jest.isolateModules(() => {
// eslint-disable-next-line global-require
const { GDevelopFirebaseConfig: fresh } = require('./ApiConfigs');
expect(fresh.apiKey).toMatch(/^AIzaSyDUMMY/);
expect(fresh.apiKey).toMatch(/DUMMY|REPLACE|YOUR-OWN/);
});
});
test('the non-secret fields stay as the gdevelop-services Firebase project', () => {
expect(GDevelopFirebaseConfig.authDomain).toBe(
'gdevelop-services.firebaseapp.com'
);
expect(GDevelopFirebaseConfig.projectId).toBe('gdevelop-services');
expect(GDevelopFirebaseConfig.storageBucket).toBe(
'gdevelop-services.appspot.com'
);
});
});