✨ Improved protection against spam member signups#29319
Conversation
ref #20767 Request integrity validation has been available behind an opt-in config flag since 2024 and is now ready to protect installations by default.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughAdds the default Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run ghost:test:ci:integration |
✅ Succeeded | 2m 43s | View ↗ |
nx run ghost:test:integration |
✅ Succeeded | 2m 52s | View ↗ |
nx run ghost:test:legacy |
✅ Succeeded | 2m 56s | View ↗ |
nx run @tryghost/admin:build |
✅ Succeeded | 2m 34s | View ↗ |
nx run ghost:test:e2e |
✅ Succeeded | 2m 26s | View ↗ |
nx run-many --target=build --projects=tag:publi... |
✅ Succeeded | <1s | View ↗ |
nx run ghost-monorepo:lint:boundaries |
✅ Succeeded | 7s | View ↗ |
nx run-many -t lint -p ghost,ghost-monorepo |
✅ Succeeded | 23s | View ↗ |
Additional runs (3) |
✅ Succeeded | ... | View ↗ |
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗
☁️ Nx Cloud last updated this comment at 2026-07-14 17:53:36 UTC
ref #20767 Enabling integrity verification by default requires member API acceptance requests to include valid tokens before exercising their intended behavior.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
ghost/core/test/e2e-api/members/send-magic-link.test.js (1)
15-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMake the agent patch more robust against arbitrary requests.
This monkey-patch applies to all POST requests made by the agent and assumes
bodyis always provided when.body()is called. To prevent unexpected test errors if a test sends an empty body or makes a POST request to another endpoint, consider scoping the interception to the magic-link URL and providing a fallback for thebodyargument.♻️ Proposed defensive refactor
- agent.post = (url, options) => { - const request = post(url, options); - const setBody = request.body.bind(request); - - request.body = body => { - const integrityToken = Object.hasOwn(body, 'integrityToken') - ? body.integrityToken - : membersService.requestIntegrityTokenProvider.create(); - - return setBody({...body, integrityToken}); - }; - - return request; - }; + agent.post = (url, options) => { + const request = post(url, options); + + if (typeof url === 'string' && !url.includes('/send-magic-link')) { + return request; + } + + const setBody = request.body.bind(request); + + request.body = body => { + const payload = body || {}; + const integrityToken = Object.hasOwn(payload, 'integrityToken') + ? payload.integrityToken + : membersService.requestIntegrityTokenProvider.create(); + + return setBody({...payload, integrityToken}); + }; + + return request; + };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ghost/core/test/e2e-api/members/send-magic-link.test.js` around lines 15 - 33, Update addIntegrityTokenToPostRequests so the patched agent.post interception only modifies requests targeting the magic-link endpoint, leaving other POST requests unchanged. Make the request.body wrapper tolerate an omitted or undefined body by defaulting to an empty object before checking or adding integrityToken.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@ghost/core/test/e2e-api/members/send-magic-link.test.js`:
- Around line 15-33: Update addIntegrityTokenToPostRequests so the patched
agent.post interception only modifies requests targeting the magic-link
endpoint, leaving other POST requests unchanged. Make the request.body wrapper
tolerate an omitted or undefined body by defaulting to an empty object before
checking or adding integrityToken.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 8c596820-d975-465d-b632-812b369f806a
📒 Files selected for processing (1)
ghost/core/test/e2e-api/members/send-magic-link.test.js
ref #20767 Acceptance requests now obtain tokens through the public endpoint, matching the frontend flow without coupling tests to the server token provider.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
ghost/core/test/e2e-api/members/send-magic-link.test.js (1)
24-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winGuard against null or undefined
bodyparameters.If a future test calls
.body()without arguments or passesnull,Object.hasOwnwill throw aTypeError. Adding a defensive check prevents potential test suite crashes. As a bonus,{...undefined, integrityToken: token}gracefully evaluates to{ integrityToken: token }, ensuring the token is still sent even if the test omits a body.🛠️ Proposed refactor
- request.body = body => { - const token = Object.hasOwn(body, 'integrityToken') ? body.integrityToken : integrityToken; - return setBody({...body, integrityToken: token}); - }; + request.body = body => { + const token = body && Object.hasOwn(body, 'integrityToken') ? body.integrityToken : integrityToken; + return setBody({...body, integrityToken: token}); + };🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ghost/core/test/e2e-api/members/send-magic-link.test.js` around lines 24 - 27, Update the body override callback to safely handle null or undefined body values before calling Object.hasOwn, while preserving any explicitly provided integrityToken and injecting the default token otherwise. Ensure omitted bodies still produce a request body containing integrityToken.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@ghost/core/test/e2e-api/members/send-magic-link.test.js`:
- Around line 24-27: Update the body override callback to safely handle null or
undefined body values before calling Object.hasOwn, while preserving any
explicitly provided integrityToken and injecting the default token otherwise.
Ensure omitted bodies still produce a request body containing integrityToken.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b913e680-35d9-40fe-8dca-d1f6b02cc4af
📒 Files selected for processing (1)
ghost/core/test/e2e-api/members/send-magic-link.test.js
ref #20767 (comment) The provider needs the theme session secret, so it must be created after settings are initialized rather than when the service module is loaded.
|
Thanks. Now that it's official, will it become documented here: To give folks a hint under what conditions they might want to disable it? It seems like according to @cathysarisky there may be some integrations with the "magic link" endpoint in the wild that may break when its enabled: https://www.spectralwebservices.com/blog/sign-in-changes-afoot/ |

What changed
Enabled
verifyRequestIntegrityin Ghost's default configuration.Updated the members magic-link acceptance tests to fetch integrity tokens through the public Members API and include them in protected requests, while retaining explicit coverage for missing and invalid tokens.
Why
Request integrity validation has been available behind an opt-in flag since 2024, but leaving it disabled by default means installations do not benefit from the spam protection unless operators discover and configure the undocumented flag themselves.
With this change, member requests require the integrity token that Portal and Signup Form already send. Installations can still explicitly override the setting if needed.
Ref #20767
Testing
pnpm nx run ghost:test:e2e -- test/e2e-api/members/send-magic-link.test.js(46 tests passed)pnpm exec eslint test/e2e-api/members/send-magic-link.test.jspnpm test:single test/unit/shared/config/loader.test.js(8 tests passed)pnpm test:single test/unit/server/services/members/middleware.test.js(19 tests passed)git diff --check