Skip to content

Commit 83c167a

Browse files
authored
Merge pull request #223 from xixu-me/codex/propose-fix-for-flathub-cache-poisoning
fix(flathub): scope rewritten descriptor cache by origin
2 parents a0312c1 + c76dcca commit 83c167a

File tree

2 files changed

+46
-5
lines changed

2 files changed

+46
-5
lines changed

src/index.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@ async function handleRequest(request, env, ctx) {
175175

176176
// Check if this is a Hugging Face API request
177177
const isHF = isHuggingFaceAPIRequest(request, url);
178+
const shouldVaryCacheByOrigin =
179+
platform === 'flathub' && isFlatpakReferenceFilePath(effectivePath);
180+
const cacheTargetUrl = shouldVaryCacheByOrigin
181+
? `${targetUrl}${targetUrl.includes('?') ? '&' : '?'}__xget_origin=${encodeURIComponent(url.origin)}`
182+
: targetUrl;
178183
const canUseCache = request.method === 'GET' || request.method === 'HEAD';
179184
const shouldPassthroughRequest =
180185
isGit || isGitLFS || isDocker || isAI || isHF || !canUseCache;
@@ -199,7 +204,7 @@ async function handleRequest(request, env, ctx) {
199204
) {
200205
try {
201206
// For Range requests, try cache match first
202-
const cacheKey = new Request(targetUrl, {
207+
const cacheKey = new Request(cacheTargetUrl, {
203208
method: 'GET',
204209
headers: request.headers
205210
});
@@ -211,7 +216,7 @@ async function handleRequest(request, env, ctx) {
211216
// If Range request missed cache, try with original request to see if we have full content cached
212217
const rangeHeader = request.headers.get('Range');
213218
if (rangeHeader) {
214-
const fullContentKey = new Request(targetUrl, {
219+
const fullContentKey = new Request(cacheTargetUrl, {
215220
method: 'GET', // Always use GET method for cache key consistency
216221
headers: new Headers(
217222
[...request.headers.entries()].filter(
@@ -631,15 +636,15 @@ async function handleRequest(request, env, ctx) {
631636
) {
632637
const rangeHeader = request.headers.get('Range');
633638
const cacheKey = rangeHeader
634-
? new Request(targetUrl, {
639+
? new Request(cacheTargetUrl, {
635640
method: 'GET',
636641
headers: new Headers(
637642
[...request.headers.entries()].filter(
638643
([k]) => k.toLowerCase() !== 'range'
639644
)
640645
)
641646
})
642-
: new Request(targetUrl, { method: 'GET' });
647+
: new Request(cacheTargetUrl, { method: 'GET' });
643648

644649
try {
645650
if (ctx && typeof ctx.waitUntil === 'function') {
@@ -652,7 +657,7 @@ async function handleRequest(request, env, ctx) {
652657

653658
if (rangeHeader && response.status === 200) {
654659
const rangedResponse = await cache.match(
655-
new Request(targetUrl, {
660+
new Request(cacheTargetUrl, {
656661
method: 'GET',
657662
headers: request.headers
658663
})

test/unit/flathub-rewrite.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,42 @@ describe('Flathub Response Rewriting', () => {
9494
expect(body).toContain('RuntimeRepo=https://example.com/flathub/repo/flathub.flatpakrepo');
9595
});
9696

97+
it('uses host-scoped cache keys for rewritten Flathub descriptors', async () => {
98+
const cacheEntries = new Map();
99+
100+
vi.stubGlobal('caches', {
101+
default: {
102+
match: vi.fn(async request => cacheEntries.get(request.url) || null),
103+
put: vi.fn(async (request, response) => {
104+
cacheEntries.set(request.url, response.clone());
105+
})
106+
}
107+
});
108+
109+
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(
110+
async () =>
111+
new Response(`[Flatpak Repo]\nUrl=https://dl.flathub.org/repo/`, {
112+
status: 200,
113+
headers: { 'Content-Type': 'application/octet-stream' }
114+
})
115+
);
116+
117+
const responseA = await worker.fetch(
118+
new Request('https://mirror-a.example/flathub/repo/flathub.flatpakrepo'),
119+
{},
120+
executionContext
121+
);
122+
const responseB = await worker.fetch(
123+
new Request('https://mirror-b.example/flathub/repo/flathub.flatpakrepo'),
124+
{},
125+
executionContext
126+
);
127+
128+
expect(fetchSpy).toHaveBeenCalledTimes(2);
129+
expect(await readUtf8Text(responseA)).toContain('Url=https://mirror-a.example/flathub/repo/');
130+
expect(await readUtf8Text(responseB)).toContain('Url=https://mirror-b.example/flathub/repo/');
131+
});
132+
97133
it('does not rewrite binary repository metadata like summary files', async () => {
98134
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
99135
new Response('summary-binary-payload', {

0 commit comments

Comments
 (0)