SCAMFREEZER / A live demonstration. The text on this page cannot be edited. FROZEN

A LIVE DEMONSTRATION FOR THE PEOPLE WHO ALREADY KNOW.

Every second wasted on a scammer is a potential victim saved.

Refund scammers all reach the same step. They open the developer tools, edit the bank dashboard’s HTML to show an inflated number, and tell the panicking victim they over-refunded. That step is preventable.

Open DevTools (⌘⌥I). Try to change a single character on this page.

Below is a page that looks like the kind of bank dashboard a scammer would doctor. The numbers are wrong on purpose. Try to fix them.

MERIDIAN FEDERAL

Welcome back.

PERSONAL CHECKING · ACCOUNT •••• 4127 · STATEMENT PERIOD APR 1 — APR 30

CURRENT BALANCE

$11,348.52

Last updated moments ago.

Recent activity

5 transactions
  • Today · 09:47 AM
    REFUND TECH SUPPORT INC.
    +$10,247.83
  • Today · 09:42 AM
    ACH DEBIT SUPPORT VENDOR REFUND
    −$100.00
  • May 1 · 08:15 AM
    DEPOSIT HENDERSON & CO. PAYROLL
    +$1,200.69
  • Apr 30
    UTILITY PG&E ELECTRIC SERVICE
    −$84.00
  • Apr 28
    CARD STARBUCKS #2814
    −$6.75

Right-click $10,247.83. Choose Edit as HTML. Type any number you like.

Meridian Federal is fictional. Any resemblance to a real institution is the point and the joke. No real bank endorses this page.

How is this possible? Can’t a scammer just turn it off?

The library installs a MutationObserver the moment the page loads. The observer is captured inside a function’s closure — meaning nothing in the browser’s developer tools can reach it to switch it off. There is no global handle, no exposed reference, no toggle in the DOM. Removing the script tag after the page has loaded changes nothing: the observer already exists in memory.

The two ways an attacker could neutralize it both fail in practice:

  1. Disable JavaScript entirely. But the page they’re trying to spoof depends on JavaScript to render — it would collapse in front of the victim, defeating the scam.
  2. Intercept before page load. They’d need to monkey-patch MutationObserver before any page code runs. Doable on their own machine; not doable inside a Zoom screen-share session with a panicking elder on the line.

The relevant code is roughly this:

export function freeze(targetNode) {
  const original = new Map();
  const originalHTML = targetNode.innerHTML;
  const observer = new MutationObserver((muts) => {
    for (const m of muts) {
      if (m.type === 'characterData') {
        m.target.textContent = original.get(m.target);
      } else if (m.type === 'childList') {
        targetNode.innerHTML = originalHTML;
      }
    }
  });
  observer.observe(targetNode, {
    characterData: true, childList: true, subtree: true,
  });
}

Once freeze() returns, observer is unreachable from outside. That is the whole trick.

What this isn’t.

A determined attacker on their own machine — with a custom browser extension, a userscript, or a pre-launch flag — can defeat almost any client-side defense, this one included. That is not the threat model.

The threat model is the standard refund-scam playbook: the scammer is on a Zoom or AnyDesk screen-share inside a victim’s browser, in real time, with a panicked target on the line. They reach for DevTools, type into the HTML, and need the change to stay visible long enough for the lie to land. That window is the one this library closes.

Adopt the stall.

textfreezer is roughly sixty lines of JavaScript. It works in any modern browser. Install it on every page where a number must not be doctored.

Reach the author.

For integration help, questions, or to flag something this demo missed. The form below is the only editable region on this page — go ahead, type.