Unauthenticated Content Feed Leaks Full Paywalled and Unpublished Articles
The Risk
An attacker could read the full text of paid, subscriber-only news articles for free, with no account and no login of any kind. The same hidden address also handed out finished articles that had not been published yet, before they were meant to go live. Anyone could collect this content automatically, day after day, as fresh paid articles rotated through. It was the equivalent of a side door that quietly served the full newspaper to anyone who knew where to knock, while the front door still asked everyone else to pay.
The Vulnerability
The publisher's website backed its homepage with a first-party content feed served from the same domain. One endpoint on that feed returned the complete body HTML of premium articles to any anonymous client, with no cookie, bearer token, or API key attached.
This was an outlier. The public article pages correctly enforced the paywall: an anonymous reader received only the title and a short lead of roughly two to three dozen words, not the body. Every sibling endpoint on the same internal feed required authentication. This single endpoint did not, and it returned the entire article body, so it was a full paywall bypass rather than a teaser leak.
Two design choices made it worse:
- The endpoint also returned finished content that had no live public page yet. At least one record was served in full while its public article route never resolved, meaning unpublished editorial content was disclosed before, or independently of, its intended release.
- The response was served publicly cacheable through the content delivery network (a short
max-ageon apubliccache directive), so the gated content sat in shared caches rather than behind any access check.
The scope was bounded, and worth stating plainly so the finding is not oversold. The endpoint ignored its article_id parameter and always returned only its current curated set, around two dozen records at any moment. An attacker could not request a specific, older, or arbitrary premium article that was not already in that set.
The Attack
The whole chain needed nothing more than an HTTP client. There was no login step to bypass because there was no login at all.
Confirming the paywall, then walking through it
- Load a premium article as an anonymous visitor and confirm the body is not served: the page carries the paywall and subscribe markers, and a distinctive phrase from deep in the article appears zero times in the returned HTML.
- Call the content feed endpoint with no cookies, login, bearer token, or API key, and read the full body for that same article. The anonymous web page exposed only a short lead; the unauthenticated feed returned the complete body.
Full current exposure
A single unauthenticated request returned the entire current set: roughly two dozen full premium article bodies spanning the most recent weeks, on the order of twelve thousand words of paid content in one response. The set was a rolling window keyed to the freshest articles enrolled in the homepage content feature, with a small number added per day, so the newest premium pieces were continuously exposed and could be harvested by an automated unauthenticated poller running on a schedule.
Pre-publication disclosure
One record in the set was a finished article returned in full by the feed, several hundred words, marked as published, while its public article route never served a page. Requesting that route returned a redirect that pointed back at itself, an endless loop, so the article was never reachable to the public. The feed disclosed it anyway.
The filter is ignored
The endpoint accepted an article_id query parameter but did not honour it. An unfiltered request and a request filtered to a single article returned an identical set of records. This is what bounded the impact: the attacker took whatever was in the current curated window and could not pivot to an arbitrary back-catalogue article. A same-origin script pasted into the browser console on a real premium page could also pull the feed and render the full body inside the genuine article layout, confirming the leaked text was the live paywalled content and not a separate snippet.
The Impact
The result was continuous, unauthenticated exposure of a rolling subset of full premium articles, plus occasional pre-publication articles, to anyone on the internet.
- Revenue. The premium paywall, the reason a reader subscribes, was bypassable for the freshest paid articles with a single unauthenticated request.
- Confidentiality. Finished but not-yet-public editorial content was disclosed ahead of its release, a separate harm from the subscription bypass.
- Scale over time. Because the window rolled forward as new articles were enrolled, a scheduled poller would accumulate the bulk of new premium output without ever authenticating.
The impact was not catalogue-wide and not targetable to a specific chosen article, which is why this was reported as High rather than Critical. It was a continuous leak of the current premium set, not a key to the entire archive.
Remediation
- Require authentication and a subscription-entitlement check on the content feed endpoint, matching every sibling endpoint on the same feed.
- Enforce the paywall server-side in the feed itself. Return only the content the requesting user is entitled to, rather than relying on the public page layout to hide a body the API freely hands out.
- Exclude unpublished and pre-publication records from any publicly reachable feed, gated on the same published state that controls the public route.
- Stop serving gated content with public cache directives. Premium bodies should never be eligible for shared or edge caching.
- Audit the full feed surface for any other endpoint that is missing the authentication and entitlement checks its siblings enforce.