Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ All notable changes to this project will be documented in this file.
- Showed four Field Notes on the homepage (one featured plus three) so a new post no longer pushes an entry off the page.

### Fixed
- Kept production secret scanning strict without treating the public
`@google/genai` SDK's OAuth schema field name as a leaked credential. The
smoke test now requires a value-shaped `client_secret` assignment before
failing, matching the container smoke scanner.
- Added the missing canonical and Open Graph URLs to the Real World Reasoning
Agent page so production metadata verification recognizes
`https://ryanbaumann.dev/real-world-reasoning-agent/` as its sole owner.
Expand Down
14 changes: 14 additions & 0 deletions LEARNINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,20 @@ Evidence: Social cards were regenerated with `ryanbaumann.dev`; Lab metadata now
Use next time: Treat a domain move as a dependency inventory, not a string replacement. Check generated text in images, absolute asset URLs, deployment health targets, OAuth origins, email senders, analytics, API referrers, and search ownership before changing DNS.


## 2026-07-27 - Secret scans must distinguish identifiers from values

Context: Production smoke rejected the Real World Reasoning browser bundle
because the public Gemini SDK contains the OAuth schema identifier
`client_secret`, even though no credential value was present.
Learning: A browser secret scan should match a credential-shaped assignment,
not a field name alone. Keep the rule aligned across local and production smoke
and cover both the allowed schema identifier and a denied long value.
Evidence: `findServerSecretMarker()` accepts the SDK's
`oauth2:client_credentials` schema text and rejects an assigned long
`client_secret` fixture; the focused production-smoke test passes.
Use next time: When a dependency adds an authentication schema, inspect the
matched bytes before excluding the asset or weakening the scan.

## 2026-07-27 - Production smoke must reach metadata gates after config gates

Context: Two deploys built and routed healthy Cloud Run revisions but stopped
Expand Down
10 changes: 10 additions & 0 deletions scripts/lib/production-smoke.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
export const SERVER_SECRET_PATTERNS = [
['OAuth client secret value', /client_secret["']?\s*[:=]\s*["'][^"'\\\s]{12,}/i],
['PEM private key block', /-----BEGIN [A-Z ]*PRIVATE KEY-----/],
['Stripe-style live secret key', /sk_live_[0-9A-Za-z]+/],
];

export function findServerSecretMarker(text) {
return SERVER_SECRET_PATTERNS.find(([, pattern]) => pattern.test(text));
}

export function expectedPublicAppNames(apps, rootAppCompatibilityName = '') {
const rootApps = apps.filter((app) => app.path === '/');
if (rootAppCompatibilityName && rootApps.length !== 1) {
Expand Down
7 changes: 3 additions & 4 deletions scripts/smoke-production.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { readFileSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

import { expectedPublicAppNames } from './lib/production-smoke.mjs';
import { expectedPublicAppNames, findServerSecretMarker } from './lib/production-smoke.mjs';

const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const apps = JSON.parse(readFileSync(resolve(REPO_ROOT, 'apps.json'), 'utf8'))
Expand Down Expand Up @@ -142,9 +142,8 @@ async function main() {

await check('served assets contain no server-secret markers', async () => {
const combined = servedText.join('\n');
for (const pattern of [/client_secret/i, /-----BEGIN [A-Z ]*PRIVATE KEY-----/, /sk_live_[0-9A-Za-z]+/]) {
if (pattern.test(combined)) throw new Error(`matched ${pattern}`);
}
const marker = findServerSecretMarker(combined);
if (marker) throw new Error(`matched ${marker[0]}`);
});

await check('Strava bundle contains OAuth authorize flow', async () => {
Expand Down
8 changes: 7 additions & 1 deletion scripts/test/smoke-production.test.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import test from 'node:test';

import { expectedPublicAppNames } from '../lib/production-smoke.mjs';
import { expectedPublicAppNames, findServerSecretMarker } from '../lib/production-smoke.mjs';

const apps = [
{ name: 'fieldwork', path: '/' },
Expand All @@ -22,3 +22,9 @@ test('production smoke rejects an ambiguous compatibility override', () => {
/requires exactly one public root app/,
);
});

test('production secret scan allows schema field names but rejects assigned secret values', () => {
assert.equal(findServerSecretMarker('type: "oauth2:client_credentials"; value.client_secret != null'), undefined);
assert.equal(findServerSecretMarker('client_secret: "fixture"'), undefined);
assert.equal(findServerSecretMarker('client_secret: "actual-secret-value-123"')?.[0], 'OAuth client secret value');
});
Loading