Skip to content

Commit e5b724f

Browse files
committed
Remove wildcard allowedAirnodes from cache server
Require explicit airnode addresses — no wildcard mode. The cache server operator must declare which airnodes are trusted to push data. This prevents untrusted parties from injecting signed data from their own keys into the store.
1 parent ee6849e commit e5b724f

5 files changed

Lines changed: 8 additions & 55 deletions

File tree

book/docs/operators/cache-server.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ endpoints:
6767
6868
### `allowedAirnodes`
6969

70-
Either a list of allowed airnode addresses with their push auth tokens, or `'*'` to accept any airnode (still requires a
71-
non-empty bearer token).
70+
An explicit list of airnode addresses authorized to push data, each with an auth token for bearer authentication. Every
71+
airnode that pushes to this cache server must be listed — there is no wildcard mode. This ensures only trusted airnodes
72+
can populate the store.
7273

7374
### `endpoints`
7475

src/cache-server.test.ts

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -296,52 +296,6 @@ describe('cache server', () => {
296296
});
297297
});
298298

299-
// =============================================================================
300-
// Wildcard allowedAirnodes
301-
// =============================================================================
302-
describe('cache server with wildcard airnodes', () => {
303-
let server: CacheServerHandle;
304-
305-
let baseUrl: string;
306-
307-
beforeAll(() => {
308-
server = createCacheServer({
309-
config: makeConfig({
310-
allowedAirnodes: '*',
311-
endpoints: [{ path: '/data', delaySeconds: 0, auth: { type: 'free' } }],
312-
} as Partial<CacheServerConfig>),
313-
});
314-
baseUrl = `http://${server.hostname}:${String(server.port)}`;
315-
});
316-
317-
afterAll(() => {
318-
server.stop();
319-
});
320-
321-
test('accepts any airnode when allowedAirnodes is wildcard', async () => {
322-
const beacon = await makeSignedBeacon();
323-
const response = await postBeacons(baseUrl, TEST_AIRNODE, beacon, 'any-token');
324-
const body = (await response.json()) as { count: number };
325-
326-
expect(response.status).toBe(200);
327-
expect(body.count).toBeGreaterThanOrEqual(0);
328-
});
329-
330-
test('rejects empty bearer token even in wildcard mode', async () => {
331-
const beacon = await makeSignedBeacon();
332-
const response = await fetch(`${baseUrl}/beacons/${TEST_AIRNODE}`, {
333-
method: 'POST',
334-
headers: {
335-
'Content-Type': 'application/json',
336-
Authorization: 'Bearer ',
337-
},
338-
body: JSON.stringify(beacon),
339-
});
340-
341-
expect(response.status).toBe(401);
342-
});
343-
});
344-
345299
// =============================================================================
346300
// Rate limiting
347301
// =============================================================================

src/cache-server.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ function createBeaconIngestionStore(): BeaconIngestionStore {
7070
// =============================================================================
7171
// Push authentication (bearer token from airnode → cache server)
7272
//
73-
// Wildcard mode ('*') still requires a non-empty bearer token as a deliberate
74-
// intent marker — without it, the ingestion pipeline is exposed to
75-
// unauthenticated traffic.
73+
// Every push request must come from an explicitly allowed airnode address
74+
// with a matching auth token. There is no wildcard mode — the cache server
75+
// operator must declare which airnodes are trusted.
7676
// =============================================================================
7777
function authenticatePush(
7878
request: Request,
@@ -85,8 +85,6 @@ function authenticatePush(
8585
const token = authHeader.slice(7);
8686
if (token.length === 0) return 'Empty bearer token';
8787

88-
if (allowedAirnodes === '*') return undefined;
89-
9088
const allowed = allowedAirnodes.find((a) => a.address.toLowerCase() === airnodeAddress.toLowerCase());
9189
if (!allowed) return 'Airnode not in allowedAirnodes list';
9290

src/cli/commands/cache-server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const cacheServer = new Command('cache-server')
3535
endpoints: config.endpoints.length,
3636
});
3737

38-
const airnodeCount = config.allowedAirnodes === '*' ? 'any' : String(config.allowedAirnodes.length);
38+
const airnodeCount = String(config.allowedAirnodes.length);
3939
logger.info(`Config loaded: ${airnodeCount} allowed airnode(s), ${String(config.endpoints.length)} endpoint(s)`);
4040

4141
const server = createCacheServer({ config });

src/config/schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,6 @@ const cacheEndpointSchema = z.object({
191191
export const cacheServerConfigSchema = z.object({
192192
version: z.literal('1.0'),
193193
server: serverSchema,
194-
allowedAirnodes: z.union([z.literal('*'), z.array(allowedAirnodeSchema).min(1)]),
194+
allowedAirnodes: z.array(allowedAirnodeSchema).min(1),
195195
endpoints: z.array(cacheEndpointSchema).min(1),
196196
});

0 commit comments

Comments
 (0)