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('