From 88ab65ef55852d7817d76381d481240c029e9c2b Mon Sep 17 00:00:00 2001 From: MorganOnCode <87934408+MorganOnCode@users.noreply.github.com> Date: Fri, 15 May 2026 10:54:56 +0000 Subject: [PATCH] chore: add favicon (closes audit #20) Adds an SVG favicon and serves /favicon.ico for legacy probes. Quiets the steady stream of /favicon.ico 404s in production logs. - landing/favicon.svg: 32x32 cardano-blue square with white ADA glyph - in landing/index.html head; modern browsers use this directly via the static plugin - /favicon.ico route in agent-discovery.ts serves the same SVG with image/svg+xml content type. Browsers that fall back from and crawlers that blindly probe /favicon.ico both get a 200 instead of the 404 that's been polluting logs - 2 new tests cover the route + content type Full suite: 34 files / 459 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- landing/favicon.svg | 4 ++++ landing/index.html | 3 +++ src/routes/agent-discovery.ts | 20 +++++++++++++++++++ tests/unit/routes/agent-discovery.test.ts | 24 ++++++++++++++++++++++- 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 landing/favicon.svg diff --git a/landing/favicon.svg b/landing/favicon.svg new file mode 100644 index 0000000..66194f9 --- /dev/null +++ b/landing/favicon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/landing/index.html b/landing/index.html index d786ea0..bb1c354 100644 --- a/landing/index.html +++ b/landing/index.html @@ -15,6 +15,9 @@ + + + diff --git a/src/routes/agent-discovery.ts b/src/routes/agent-discovery.ts index 68525d7..72b25e7 100644 --- a/src/routes/agent-discovery.ts +++ b/src/routes/agent-discovery.ts @@ -85,6 +85,15 @@ async function readSkillMd(): Promise { return cachedSkillMd; } +let cachedFaviconSvg: string | null = null; + +async function readFaviconSvg(): Promise { + if (cachedFaviconSvg === null) { + cachedFaviconSvg = await readFile(resolve(process.cwd(), 'landing/favicon.svg'), 'utf-8'); + } + return cachedFaviconSvg; +} + const agentDiscoveryRoutes: FastifyPluginCallback = (fastify, _options, done) => { fastify.get('/robots.txt', async (_req, reply) => { return reply.type('text/plain; charset=utf-8').status(200).send(ROBOTS_TXT); @@ -103,6 +112,17 @@ const agentDiscoveryRoutes: FastifyPluginCallback = (fastify, _options, done) => return reply.type('text/markdown; charset=utf-8').status(200).send(body); }); + // /favicon.ico — legacy fallback path that crawlers and old browsers probe + // unconditionally. Serving the same SVG (with the correct image/svg+xml + // content type) means the 200 satisfies the probe and stops the request, + // which kills the steady stream of /favicon.ico 404s in production logs. + // Modern browsers prefer the in + // landing/index.html and never hit this route. + fastify.get('/favicon.ico', async (_req, reply) => { + const body = await readFaviconSvg(); + return reply.type('image/svg+xml').status(200).send(body); + }); + done(); }; diff --git a/tests/unit/routes/agent-discovery.test.ts b/tests/unit/routes/agent-discovery.test.ts index 53be430..35bf566 100644 --- a/tests/unit/routes/agent-discovery.test.ts +++ b/tests/unit/routes/agent-discovery.test.ts @@ -1,4 +1,4 @@ -import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; @@ -26,6 +26,12 @@ describe('Agent-discovery routes', () => { originalCwd = process.cwd(); tmpDir = mkdtempSync(join(tmpdir(), 'cardano402-skill-')); writeFileSync(join(tmpDir, 'SKILL.md'), '# Test SKILL\n\nFixture body.\n'); + // The /favicon.ico route reads `${cwd}/landing/favicon.svg` -- fixture it. + mkdirSync(join(tmpDir, 'landing')); + writeFileSync( + join(tmpDir, 'landing', 'favicon.svg'), + '\n' + ); process.chdir(tmpDir); }); @@ -110,6 +116,22 @@ describe('Agent-discovery routes', () => { }); }); + describe('GET /favicon.ico', () => { + it('returns 200 with image/svg+xml content type (kills bot 404 noise)', async () => { + server = await createServer(); + const res = await server.inject({ method: 'GET', url: '/favicon.ico' }); + expect(res.statusCode).toBe(200); + expect(res.headers['content-type']).toContain('image/svg+xml'); + }); + + it('serves the SVG body so even bots that ignore get a valid image', async () => { + server = await createServer(); + const res = await server.inject({ method: 'GET', url: '/favicon.ico' }); + expect(res.body).toContain(''); + }); + }); + describe('GET /SKILL.md', () => { it('returns 200 with text/markdown content', async () => { server = await createServer();