Skip to content

🐛 Fixed members-only 403 leaking when llms.txt is disabled#28260

Merged
ErisDS merged 1 commit into
mainfrom
llms-txt-flag-off-review
May 29, 2026
Merged

🐛 Fixed members-only 403 leaking when llms.txt is disabled#28260
ErisDS merged 1 commit into
mainfrom
llms-txt-flag-off-review

Conversation

@ErisDS
Copy link
Copy Markdown
Member

@ErisDS ErisDS commented May 29, 2026

Ref

Follow-up to the llms.txt work (labs flag llmsTxt).

Problem

The per-post .md markdown routes (/:slug.md) are registered unconditionally in the static-pages and collection routers, so the entry controller relies on a runtime check to gate the feature. That check ran in the wrong order — the members-only 403 for non-public posts was returned before the llmsService.isEnabled() check:

if (res.routerOptions.isMarkdownRequest) {
    if (entry.visibility !== 'public') {
        return res.status(403)...; // fired BEFORE the flag check
    }
    const llmsService = getLlmsService(req);
    if (!llmsService || !llmsService.isEnabled()) {
        return res.redirect(302, canonical);
    }
    return serveMarkdown(res, entry);
}

So with the llmsTxt labs flag off, requesting a paid/members post's .md URL still returned a feature-specific 403 text/markdown "Members-only content" body — leaking llms.txt behaviour (and revealing a post's existence/visibility) when the feature is supposed to be fully disabled.

This was found while verifying that nothing in the feature works with the flag off. Everything else gated correctly (/llms.txt → 302 redirect, Accept: text/markdown content negotiation → normal HTML, discovery headers absent); only non-public .md requests leaked.

Request (flag OFF) Before After
Public post .md 302 → canonical 302 → canonical
Paid post .md 403 members-only markdown 302 → canonical
Members post .md 403 members-only markdown 302 → canonical

Fix

Move the isEnabled() check ahead of the visibility check, so a disabled feature always falls back to the canonical 302 redirect — the same graceful degradation already used for public .md requests — regardless of visibility. The members-only 403 is preserved when the feature is enabled.

Tests

Added a markdown requests (llms.txt) block to the entry controller unit tests, including a regression test that fails on the old ordering:

  • does not return 403 for non-public posts when the feature is disabled (regression)
  • redirects public posts to canonical when disabled
  • returns 403 for non-public posts when enabled
  • serves markdown for public posts when enabled

Verified live against a dev instance with the flag off: all .md requests (public/paid/members) now 302-redirect to canonical, with no feature-specific 403.

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 29, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e30d1035-3f1f-4f61-bd6e-8a74ebb53119

📥 Commits

Reviewing files that changed from the base of the PR and between 506b6cc and e66ac6d.

📒 Files selected for processing (2)
  • ghost/core/core/frontend/services/routing/controllers/entry.js
  • ghost/core/test/unit/frontend/services/routing/controllers/entry.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • ghost/core/test/unit/frontend/services/routing/controllers/entry.test.js

Walkthrough

The PR reorders the entry controller's .md request path so llmsService presence and isEnabled are checked before visibility enforcement. If llmsService is missing/disabled the handler redirects to the canonical entry URL (preserving path/search) instead of returning a markdown 403. If llmsService is enabled, members-only posts receive a 403 markdown response and public posts render markdown. A new unit test suite covers enabled/disabled states and public vs non-public visibilities.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main fix: a bug where members-only 403 errors were leaking when the llms.txt feature is disabled, which aligns with the primary change in the changeset.
Description check ✅ Passed The description thoroughly explains the problem, the fix, and test coverage, all directly related to the changeset's objective of reordering the llmsService.isEnabled() check before the visibility check.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch llms-txt-flag-off-review

Comment @coderabbitai help to get the list of available commands and usage tips.

The `.md` markdown routes are registered unconditionally, so the entry
controller relies on a runtime check to gate the llms.txt feature. That
check ran in the wrong order: the members-only 403 for non-public posts
was returned *before* the `llmsService.isEnabled()` check.

As a result, requesting a paid/members post's `.md` URL returned a
feature-specific `403 text/markdown` response even when the `llmsTxt`
labs flag was disabled, leaking llms.txt behaviour (and revealing a
post's existence/visibility) when the feature should be fully off.

Moved the `isEnabled()` check ahead of the visibility check so a disabled
feature always falls back to a 302 redirect to the canonical URL — the
same graceful degradation already used for public `.md` requests —
regardless of post visibility.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ErisDS ErisDS force-pushed the llms-txt-flag-off-review branch from 506b6cc to e66ac6d Compare May 29, 2026 19:33
@ErisDS ErisDS merged commit f469635 into main May 29, 2026
50 checks passed
@ErisDS ErisDS deleted the llms-txt-flag-off-review branch May 29, 2026 20:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant