diff --git a/package-lock.json b/package-lock.json index e70cae3..cd67873 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@startup-api/cloudflare", - "version": "0.4.1", + "version": "0.4.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@startup-api/cloudflare", - "version": "0.4.1", + "version": "0.4.2", "license": "Apache-2.0", "dependencies": { "he": "^1.2.0", diff --git a/package.json b/package.json index af74f77..904f3cc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@startup-api/cloudflare", - "version": "0.4.1", + "version": "0.4.2", "license": "Apache-2.0", "publishConfig": { "access": "public" diff --git a/src/auth/AtprotoProvider.ts b/src/auth/AtprotoProvider.ts index 44229c8..7ca4c49 100644 --- a/src/auth/AtprotoProvider.ts +++ b/src/auth/AtprotoProvider.ts @@ -304,10 +304,12 @@ export class AtprotoProvider extends OAuthProvider { status, headers: { 'Content-Type': 'text/html; charset=utf-8', - // Same hardening as the auth error page: only same-origin styles/form, never framed, never cached - // (the re-rendered form reflects the user-supplied handle). - 'Content-Security-Policy': - "default-src 'none'; style-src 'self' 'unsafe-inline'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'", + // Hardening: same-origin styles only, never framed, never cached (the form reflects the + // user-supplied handle). NOTE: deliberately NO `form-action` directive — submitting this form + // hits our endpoint, which 302-redirects to the user's *own* authorization server (any PDS). + // `form-action` is enforced across the whole redirect chain, so `'self'` (or any fixed list) + // would block that cross-origin redirect and the login would silently fail. + 'Content-Security-Policy': "default-src 'none'; style-src 'self' 'unsafe-inline'; base-uri 'none'; frame-ancestors 'none'", 'X-Content-Type-Options': 'nosniff', 'Referrer-Policy': 'no-referrer', 'Cache-Control': 'no-store', diff --git a/test/atproto.spec.ts b/test/atproto.spec.ts index fffa45f..5fc72e6 100644 --- a/test/atproto.spec.ts +++ b/test/atproto.spec.ts @@ -101,6 +101,9 @@ describe('atproto provider', () => { // Auth page hardening: not framable, not cached (it can reflect the user's handle). expect(res.headers.get('Content-Security-Policy')).toContain("frame-ancestors 'none'"); expect(res.headers.get('Cache-Control')).toBe('no-store'); + // Must NOT set form-action: submitting this form 302-redirects to the user's own auth server + // (any PDS), and form-action is enforced across the redirect chain — restricting it breaks login. + expect(res.headers.get('Content-Security-Policy')).not.toContain('form-action'); const html = await res.text(); expect(html).toContain('name="handle"'); });