Beginner levelFreebounty: $2.000

API keys exposed in public HTML — $2,000 without a single bypass

A productivity platform shipped a third-party CMS token in window.CONFIG. Anyone with DevTools could download paid templates. How to hunt this pattern in any SPA app.

Gorka El BochiMay 9, 202610 min

Quick answer

Some bugs require no fuzzing and no chains: just reading what the app ships to the client. A productivity and notes platform injected a third-party CMS token into its HTML with read permissions over all of its marketing content — including download URLs for paid templates. A single authenticated HTTP request, zero bypasses, $2,000 bounty.


The window.CONFIG object

The affected application is built in React (wrapped in Electron for desktop). As in most SPAs, when the page loads the server injected a global config object into the HTML:

javascript
window.CONFIG = {
  googleCaptchaSiteKey: "...",
  facebookPixelId: "...",
  contentful: {
    spaceId: "XXXXXXXXXXXXXX",
    ACCESS_TOKEN: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"  // this shouldn't be here
  },
  ...
}

This object was fully visible in the page source. No authentication, no special tooling. Ctrl+U and it's right there.

Among the third-party keys traveling in this object was one for Contentful — the CMS the platform used to manage marketing content, including the template marketplace.


What an exposed Contentful API key allows

Contentful exposes a REST API. With spaceId + ACCESS_TOKEN you can query any content in the space with no restriction beyond the token itself:

sql
window.CONFIG in the public HTML
    ↓
Extract spaceId + ACCESS_TOKEN
    ↓
GET /spaces/{SPACE_ID}/environments/master/content_types?access_token=TOKEN
    ↓
Enumerate every content type in the space
    ↓
Identify the "template" content type with price and url fields
    ↓
GET entries filtered by fields.price >= 100Full list of paid templates with a direct download URL

The exact requests

Step 1 — Enumerate available content types:

http
GET https://cdn.contentful.com/spaces/{SPACE_ID}/environments/master/content_types
    ?access_token={ACCESS_TOKEN}

The response returns every content type defined. Among them: person, category, template.

Step 2 — Filter templates priced ≥ $100:

http
GET https://cdn.contentful.com/spaces/{SPACE_ID}/environments/master/entries
    ?access_token={ACCESS_TOKEN}
    &content_type=template
    &fields.price[gte]=100

For each template the response included: title, category, price and a direct download URL, regardless of whether the template was paid.

No extra step. No exploit. Just reading the API response with credentials the application itself handed out.


Root cause

The Contentful token should be a backend-only key: the server uses it to build the content it serves to the client, but it should never reach the client directly. By including it in window.CONFIG, any user could run the same queries as the server, with no restrictions.

sql
Build / Deploy
    ↓ ACCESS_TOKEN injected into window.CONFIG

Browser / client
    ↓ Public HTML accessible without authentication
    ↓ Token visible in any DevTools

Contentful API
    ↓ No origin or scope restriction
    ↓ Read access to the entire space

Where to hunt this pattern

window.CONFIG, window.__INITIAL_STATE__, window.__NEXT_DATA__, window.APP_CONFIG — these are the most common names. Any React, Vue or Next.js app that hydrates state on the client is a candidate.

What to look for inside:

  • Third-party API keys: Contentful, Algolia, Sanity, Cloudinary, Firebase, Mapbox, Stripe (publishable vs secret key, watch out)...
  • Tokens with broad read permissions over resources that shouldn't be public.
  • Internal parameters that reveal data structure, IDs, or undocumented endpoints.

The flow is always the same: Ctrl+U or DevTools → search for CONFIG, TOKEN, API_KEY, SECRET → evaluate what each key found lets you do against the corresponding service's API.


Impact

Free download of any paid template in the marketplace. In this specific report, templates priced from $100 up to several hundred. Access without a paying account, without bypasses, with a single HTTP request authenticated with the credentials the app itself handed out.

Reported in May 2024. Fixed in under 24 hours. $2,000 bounty.


Practice recon of client-side tokens and abuse of poorly restricted third-party APIs: Information Disclosure labs.

Practice this in a lab

Information Disclosure

Solve

Keep learning · free account

Save your progress, unlock advanced payloads and rank your flags.

Create account

Related articles

hunters training
711

hunters training

labs from real reports
55

labs from real reports

completions
1,205

completions

in bounties practiced
$213,970

in bounties practiced

46 flags captured this week·Real reports from HackerOne · Bugcrowd · Intigriti·No commitment·Free Academy