diff --git a/src/auctions.ts b/src/auctions.ts index c66f0ea..b678075 100644 --- a/src/auctions.ts +++ b/src/auctions.ts @@ -173,14 +173,18 @@ export function createAuctionApp(house: AuctionHouse) { app.post('/new', async (c) => { const host = String(c.req.header('host') ?? new URL(c.req.url).host).trim().toLowerCase().replace(/:\d+$/, '') const origin = c.req.header('origin') - if (origin) { + const fetchSite = c.req.header('sec-fetch-site') + // Under Referrer-Policy: no-referrer (which this site sends) Chrome posts forms with + // Origin: null. Accept that only when the browser also says the post is same-origin. + if (origin === 'null') { + if (fetchSite !== 'same-origin') return c.text('Forbidden', 403) + } else if (origin) { try { if (new URL(origin).host.toLowerCase() !== host) return c.text('Forbidden', 403) } catch { return c.text('Forbidden', 403) } } - const fetchSite = c.req.header('sec-fetch-site') if (fetchSite && fetchSite !== 'same-origin') return c.text('Forbidden', 403) const theme = themeOf(c) const ip = c.env?.ip ?? c.req.header('x-real-ip') ?? 'unknown' diff --git a/test/auctions.test.ts b/test/auctions.test.ts index 790be21..38fb5fc 100644 --- a/test/auctions.test.ts +++ b/test/auctions.test.ts @@ -119,4 +119,22 @@ describe('Art Auctions House', () => { expect(await page.text()).toContain('data-theme="blue"') house.db.close() }) + + it('accepts the Origin: null a browser sends under no-referrer, only for same-origin posts', async () => { + const house = new AuctionHouse() + const app = createAuctionApp(house) + const post = (headers: Record, ip: string) => { + const form = new FormData() + form.set('title', 'Logo') + form.set('artist', 'Ada Artist') + form.set('permission', 'yes') + form.set('image', new File([png], 'logo.png', { type: 'image/png' })) + return app.fetch(new Request('https://auctions.pit.world/new', { method: 'POST', headers, body: form }), { ip }) + } + expect((await post({ origin: 'null', 'sec-fetch-site': 'same-origin' }, '203.0.113.20')).status).toBe(303) + expect((await post({ origin: 'null', 'sec-fetch-site': 'cross-site' }, '203.0.113.21')).status).toBe(403) + expect((await post({ origin: 'null' }, '203.0.113.22')).status).toBe(403) + expect(house.list()).toHaveLength(1) + house.db.close() + }) })