Skip to content

Commit 7086155

Browse files
Sync public snapshot from freebuff-private
Source: CodebuffAI/freebuff-private@95a3ae1fed2426a0f5a47ef71e51bcbc36060cc2
1 parent 2572dcf commit 7086155

3 files changed

Lines changed: 224 additions & 7 deletions

File tree

bun.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import { describe, expect, test } from 'bun:test'
2+
3+
import {
4+
fetchImpreziaChatAd,
5+
isImpreziaSandboxTester,
6+
} from '../imprezia-client'
7+
8+
import type { Logger } from '../../types/contracts/logger'
9+
10+
const SANDBOX_KEY = 'api_pub_sandbox_abc123'
11+
const PROD_KEY = 'api_pub_prod_abc123'
12+
13+
const logger: Logger = {
14+
debug: () => {},
15+
info: () => {},
16+
warn: () => {},
17+
error: () => {},
18+
}
19+
20+
const ad = {
21+
creative: {
22+
brandName: 'Imprezia',
23+
title: 'Developers. Earn money with your AI app.',
24+
description: 'Run ads like this, and get paid.',
25+
cta: 'Sponsored',
26+
},
27+
clickUrl: 'https://go-sandbox.imprezia.ai/go/tok',
28+
impression: {
29+
impressionUuid: 'uuid-1',
30+
beaconToken: { token: 't', issuedAt: 1, kid: 'k' },
31+
servedAt: '2026-08-22T23:47:09.005Z',
32+
publisherId: 'pub-1',
33+
},
34+
}
35+
36+
/** Records whether the upstream API was called at all. */
37+
function countingFetch() {
38+
let calls = 0
39+
const fetch = (async () => {
40+
calls += 1
41+
return new Response(JSON.stringify({ requestId: 'req_1', ad }), {
42+
status: 200,
43+
headers: { 'content-type': 'application/json' },
44+
})
45+
}) as unknown as typeof globalThis.fetch
46+
return { fetch, calls: () => calls }
47+
}
48+
49+
const request = {
50+
request: 'how do i cache api responses?',
51+
response: 'use a Map with a TTL, or Redis across processes.',
52+
sessionId: 's1',
53+
timestamp: '2026-08-22T23:35:00.000Z',
54+
sourceUrl: 'https://freebuff.com/chat',
55+
platformString: 'browser',
56+
deviceContext: {
57+
deviceType: 'desktop' as const,
58+
viewportWidth: 1280,
59+
viewportHeight: 800,
60+
},
61+
}
62+
63+
const call = (opts: {
64+
apiKey: string
65+
testMode: boolean
66+
allowSandbox?: boolean
67+
}) => {
68+
const { fetch, calls } = countingFetch()
69+
return fetchImpreziaChatAd({
70+
...opts,
71+
request,
72+
userAgent: 'UA',
73+
logger,
74+
fetch,
75+
}).then((result) => ({ result, upstreamCalls: calls() }))
76+
}
77+
78+
/**
79+
* The sandbox key serves Imprezia's own house ad, which renders exactly like
80+
* paid inventory. Letting one reach an ordinary production user would put an
81+
* ad for our ad vendor in the slot, indistinguishable from a real advertiser.
82+
*/
83+
describe('sandbox creatives in production', () => {
84+
test('are refused, without even calling upstream', async () => {
85+
const { result, upstreamCalls } = await call({
86+
apiKey: SANDBOX_KEY,
87+
testMode: false,
88+
})
89+
expect(result).toBeNull()
90+
// Refusing before the request also keeps test impressions off Imprezia's
91+
// ledger, not just off the page.
92+
expect(upstreamCalls).toBe(0)
93+
})
94+
95+
test('are served when a caller explicitly opts in', async () => {
96+
const { result } = await call({
97+
apiKey: SANDBOX_KEY,
98+
testMode: false,
99+
allowSandbox: true,
100+
})
101+
expect(result?.ad?.creative.brandName).toBe('Imprezia')
102+
})
103+
104+
test('the opt-in is irrelevant to a production key', async () => {
105+
for (const allowSandbox of [undefined, true]) {
106+
const { result } = await call({
107+
apiKey: PROD_KEY,
108+
testMode: false,
109+
allowSandbox,
110+
})
111+
expect(result?.ad).toBeTruthy()
112+
}
113+
})
114+
})
115+
116+
/**
117+
* The allowlist is one of two gates. The other -- that the session pinned
118+
* `?ads=imprezia` -- lives in the route, because only the client knows it.
119+
*/
120+
describe('isImpreziaSandboxTester', () => {
121+
const allowlist = 'tester@imprezia.ai, Second.Tester@Imprezia.AI'
122+
123+
test('admits a listed tester, case- and space-insensitively', () => {
124+
expect(
125+
isImpreziaSandboxTester({ email: 'tester@imprezia.ai', allowlist }),
126+
).toBe(true)
127+
// The list is pasted in by hand, and so is the account email.
128+
expect(
129+
isImpreziaSandboxTester({
130+
email: ' Second.Tester@imprezia.ai ',
131+
allowlist,
132+
}),
133+
).toBe(true)
134+
})
135+
136+
test('refuses anyone not on the list, exactly', () => {
137+
// Exact match, not substring: a lookalike domain must not slip through if
138+
// someone later "optimizes" this into an includes() scan.
139+
for (const email of [
140+
'someone@else.com',
141+
'tester@imprezia.ai.evil.com',
142+
'xtester@imprezia.ai',
143+
'tester@imprezia.aiX',
144+
]) {
145+
expect(isImpreziaSandboxTester({ email, allowlist })).toBe(false)
146+
}
147+
})
148+
149+
test('an unset allowlist admits nobody', () => {
150+
// The safe default: if the env var is never created, production behaves
151+
// exactly as it does with no allowlist feature at all.
152+
for (const value of [undefined, null, '', ' ', ',,']) {
153+
expect(
154+
isImpreziaSandboxTester({
155+
email: 'tester@imprezia.ai',
156+
allowlist: value,
157+
}),
158+
).toBe(false)
159+
}
160+
})
161+
162+
test('a session with no email is never a tester', () => {
163+
for (const email of [null, undefined, '']) {
164+
expect(isImpreziaSandboxTester({ email, allowlist })).toBe(false)
165+
}
166+
})
167+
168+
test('a blank email never matches a blank list entry', () => {
169+
// The fail-open this boundary is one line away from: a trailing comma or
170+
// an empty env value yields an empty entry, and a whitespace-only email
171+
// normalizes to empty too. Dropping the .filter(Boolean) that removes
172+
// those entries makes every one of these return true.
173+
for (const [email, list] of [
174+
[' ', 'tester@imprezia.ai,'],
175+
[' ', ''],
176+
['\t\n', ',,'],
177+
[' ', ' , tester@imprezia.ai '],
178+
]) {
179+
expect(isImpreziaSandboxTester({ email, allowlist: list })).toBe(false)
180+
}
181+
})
182+
})

common/src/util/imprezia-client.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,13 @@ export async function fetchImpreziaChatAd(params: {
6464
userAgent: string
6565
/** False in production. A sandbox key must not serve real users. */
6666
testMode: boolean
67+
/** One-request opt-in to sandbox creatives; see isImpreziaSandboxTester. */
68+
allowSandbox?: boolean
6769
logger: Logger
6870
fetch: typeof globalThis.fetch
6971
}): Promise<ImpreziaChatAdResult | null> {
70-
const { apiKey, request, userAgent, testMode, logger, fetch } = params
72+
const { apiKey, request, userAgent, testMode, allowSandbox, logger, fetch } =
73+
params
7174
const baseUrl = impreziaBaseUrlForKey(apiKey)
7275

7376
// Both halves are required and must be non-empty. A turn with an empty reply
@@ -77,9 +80,10 @@ export async function fetchImpreziaChatAd(params: {
7780
return null
7881
}
7982

80-
// A sandbox key serves Imprezia's own house creatives. They render like real
81-
// ads, so in production they are indistinguishable from live inventory.
82-
if (isImpreziaSandboxKey(apiKey) && !testMode) {
83+
// A sandbox key serves Imprezia's own house ad ("Developers. Earn money with
84+
// your AI app."), rendered exactly like a paid one — indistinguishable from
85+
// real inventory to an ordinary user. `allowSandbox` is the only way past.
86+
if (isImpreziaSandboxKey(apiKey) && !testMode && !allowSandbox) {
8387
logger.error(
8488
'[ads:imprezia] Refusing to serve: sandbox key in production. Swap in ' +
8589
'an api_pub_prod_ key before this can fill.',
@@ -171,3 +175,34 @@ export async function fetchImpreziaChatAd(params: {
171175
)
172176
return { requestId, ad }
173177
}
178+
179+
/**
180+
* May this account be shown Imprezia's SANDBOX creatives in production?
181+
*
182+
* Two separate gates have to line up for that to happen, and this is only the
183+
* second of them: the session must also have explicitly pinned Imprezia with
184+
* `?ads=imprezia`. Pinning alone does nothing (anyone can put that in a URL)
185+
* and being listed alone does nothing (a listed tester browsing normally still
186+
* sees ordinary inventory). Only the pair opens the door, so neither a stray
187+
* link nor a stale allowlist entry can leak a house ad into real traffic.
188+
*
189+
* The list is an env var rather than a constant because the people who need it
190+
* are at the ad partner, not on our team — adding one must not need a deploy.
191+
* Absent or empty means nobody, which is the safe default: if the variable
192+
* never gets created, production behaves exactly as it does today.
193+
*/
194+
export function isImpreziaSandboxTester(params: {
195+
email: string | null | undefined
196+
/** Comma-separated emails, from IMPREZIA_SANDBOX_TESTERS. */
197+
allowlist: string | null | undefined
198+
}): boolean {
199+
const { email, allowlist } = params
200+
if (!email) return false
201+
202+
const normalized = email.trim().toLowerCase()
203+
return (allowlist ?? '')
204+
.split(',')
205+
.map((entry) => entry.trim().toLowerCase())
206+
.filter(Boolean)
207+
.includes(normalized)
208+
}

0 commit comments

Comments
 (0)