Who's allowed to talk?

postMessage
Switch level: Level 1 — strict compare (protected) Level 2 — regex check (cracked)
send into the frame (available again here — always fails at L1)
Method — level 1: what a real check looks like
  1. Press Run: the frame's listener compares e.origin to https://frames.example-app.com — equality, character for character — before anything renders. Our-origin parent (this page) is not that origin ⇒ "you're not allowed to send from here — origin was: https://stuxnet.pro". Protection working as designed: the check values the browser's attested sender origin, not anything the sender claims in a payload.
  2. That's the whole defense in one line, and it's why the story's real trusted sender passed in the lesson: whoever the receiver has allowed gets exactly one callback — no more.
  3. The lesson's honesty note: even working origin checks rarely eliminate the data path — they trim WHO can talk. Manual event.origin comparison is the strict invocation; developers who write it by pattern (below) lose the guarantee.
Method — level 2: the regex, and the crack
  1. The pattern: /^(https?):\/\/[a-z]+\.stuxnet\.pro/ — "starts with http(s) then any subdomain of stuxnet.pro". This page's parent (stuxnet.pro) matches trivially — press Run and the message renders.
  2. Now the crack, spelled: the pattern has no $ anchor, so anything at the end still matches. https://sub.stuxnet.pro glows… and so does https://sub.stuxnet.pro.evil.test. Take the pattern to regex101.com yourself — the lesson's exact drill — and watch the glow that shouldn't be there.
  3. The attacker's move mirrors what a real report needs: own a domain (evil.test in ours), host a page, embed the victim frame, and post from an origin that pattern-matches. Then the receiver renders — and the payload executes inside the victim site's document. Framework habit identical to the CORS lab's: your host merely contains the trusted string.
  4. The second classic mistake, seen in the lesson right after fixing the anchor: leave the dots unescaped (pattern like /^(https?):\/\/[a-z]+.stuxnet.pro$/) and . means any character — borderline cousins like subXstuxnet.pro pattern-match. Escape the dots, or better, compare strings, not patterns.
  5. Note the asymmetry worth reporting: the receiver-side check (this lab) some devs pattern-tune; the sender-side targetOrigin (next lab) others forget entirely. Both must hold for the bridge to be safe.

Fineprint on the pattern itself: even the "corrected" anchored pattern rejects the apex https://stuxnet.pro (nothing matches [a-z]+. before it) — exactly the kind of breakage real apps routinely bow to by loosening the pattern again. Protection code that has no tests develops soft spots like this.

Spoiler — the bypass, hosted as an attacker would run it
<!-- host at https://x.stuxnet.pro.evil.test/ — the name is the exploit -->
<iframe src="https://stuxnet.pro/pm/lab30/frame.html?level=2" id="f"></iframe>
<script>
  const f = document.getElementById('f');
  setTimeout(() => f.contentWindow.postMessage(
    {message: '<img src=x onerror=alert(document.domain)>'}, '*'), 800);
</script>

The frame's check pattern-matches our page's origin (x.stuxnet.pro.evil.test contains [a-z]+.stuxnet.pro) so the payload renders — and the alert's domain is stuxnet.pro's: execution happened in the victim app's frame, not ours. That origin-scoped-but-victim-executed result is the same shape as any worrying framed submission reported, only now carried inside the sanctioned bridge.

Spoiler — detector habit: reading listeners like a sink
hunt: grep for addEventListener('message'   → every finding is a channel
      who checks e.origin?                  → none: Lab 29 shape. too loose: Lab 30 L2 (regex cracks)
      who renders e.data?                   → innerHTML / document.write = sink (Lab 29)
      sender half? grep postMessage\(       → does IT pin a targetOrigin? (Lab 31)

The source view lesson from the evasion set applies verbatim: read the receiver's listen block first, then the sender's post block — one line each usually shows whether trust is claimed or given. (Our classroom limits: postMessage moves in the browser, so the two curls that proved CORS don't apply here — the POC is a hosted page, exactly like the attacker framing we just showed.)