Merge pull request #224 from xixu-me/codex/fix-cache-poisoning-vulnerability-in-pypi-rewrite

fix(pypi): avoid caching origin-bound rewritten PyPI index pages
This commit is contained in:
xixu-me authored and GitHub committed 2026-03-16 12:58:33 +08:00
commit 6d8efc0ce6
2 files changed
+26

No files matched your search

+5
View File
@@ -555,6 +555,7 @@ async function handleRequest(request, env, ctx) {
/** @type {string | ReadableStream<Uint8Array> | null} */
let responseBody = response.body;
let rewrittenContentLength = null;
let hasOriginBoundRewrite = false;
if (
shouldRewriteTextResponse(
@@ -575,6 +576,7 @@ async function handleRequest(request, env, ctx) {
);
responseBody = rewrittenText;
rewrittenContentLength = new TextEncoder().encode(rewrittenText).byteLength;
hasOriginBoundRewrite = platform === 'pypi';
}
const headers = new Headers(response.headers);
@@ -586,6 +588,8 @@ async function handleRequest(request, env, ctx) {
if (!isGit && !isGitLFS && !isDocker && !isAI && !isHF) {
if (!canUseCache) {
headers.set('Cache-Control', 'no-store');
} else if (hasOriginBoundRewrite) {
headers.set('Cache-Control', 'no-store');
} else if (hasSensitiveHeaders) {
headers.set('Cache-Control', 'private, no-store');
const existingVary = headers.get('Vary');
@@ -629,6 +633,7 @@ async function handleRequest(request, env, ctx) {
!isDocker &&
!isAI &&
!isHF &&
!hasOriginBoundRewrite &&
!hasSensitiveHeaders &&
request.method === 'GET' &&
response.ok &&
+21
View File
@@ -64,6 +64,27 @@ describe('Worker regression coverage', () => {
expect(clearTimeoutSpy).toHaveBeenCalledWith(timeoutToken);
});
it('does not cache host-bound PyPI rewritten HTML responses', async () => {
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response('<a href="https://files.pythonhosted.org/packages/demo.whl">demo</a>', {
status: 200,
headers: { 'Content-Type': 'text/html; charset=utf-8' }
})
);
const response = await worker.fetch(
new Request('https://mirror.example/pypi/simple/demo/'),
{},
executionContext
);
const body = await response.text();
expect(response.status).toBe(200);
expect(body).toContain('https://mirror.example/pypi/files/packages/demo.whl');
expect(response.headers.get('Cache-Control')).toBe('no-store');
expect(cacheDefault.put).not.toHaveBeenCalled();
});
it('forwards body and content type for configured non-protocol POST requests', async () => {
/** @type {{ url: string, method: string | undefined, body: string | null, contentType: string | null, cf: unknown }} */
let observed = {