Audit

Read it yourself, with Claude

This is an unsigned zip from the internet that you are about to run on the pages where your wallet is connected. Nothing on this site proves anything. The code does, and it is small enough to read in one sitting.

Why bother

A content script on a trading site can read and rewrite everything on the page. A malicious one could swap a mint address at the moment you click, exfiltrate what it sees, or wait for a wallet prompt. That holds for every extension you sideload, including this one. Chrome shows you a permission list at install time and nothing else.

So do not take “no analytics, no account” from a marketing page. Check it. An LLM makes this cheap: the whole extension is ~35 KB of JavaScript, which fits in a single prompt with room to spare.

What you get to read

This changes what the audit is worth, so read it before the rest: the zip contains bundled, minified JavaScript rather than the original source tree. Bun minifies both entry points at build time.

File in the zipWhat it is
manifest.jsonPlain JSON. Permissions, hosts, entry points. Read this first.
background.js~20 KB minified. Service worker: polling, SSE, filters, notifications.
content.js~15 KB minified. The panel injected into the five trading sites.
options/, popup/HTML, CSS and their bundles. Settings UI.
icons/Four PNGs.

Minification strips the variable names and leaves the rest: string literals, URLs, and every chrome.* call stay intact and greppable, and an LLM reads them about as well as you would read the source. One check is out of reach today, the reproducible build: nobody publishes the source repository, so there is nothing to rebuild and diff these bundles against. Treat the shipped code as the only artifact that exists.

1 · Verify the download

Hash the zip and compare it with the checksum this page was built with. On macOS or Linux:

shasum -a 256 ~/Downloads/fomo-extension-latest.zip

Then unzip it somewhere you can point a terminal at: unzip fomo-extension-latest.zip -d fomo-extension.

Honest reading of that check: it proves the file you have is the file this page describes. It does not prove the file is safe, and it is not independent, since the same site serves the zip and the checksum, so anyone who can change one can change the other. All it catches is a corrupted or swapped download in transit.

2 · Read the manifest

Sixty seconds, no tools. Open manifest.json and check these four keys against the table further down. Everything the extension is allowed to do starts here: a bundle cannot reach a host the manifest never asked for, and cannot touch a site outside content_scripts.matches.

  • permissions: anything beyond storage, alarms, notifications is a question worth asking.
  • host_permissions: one origin, no more. <all_urls> here would mean it can fetch from anywhere with your cookies.
  • content_scripts.matches: the pages it runs on. Five entries, all trading terminals.
  • externally_connectable, content_security_policy: absent. A relaxed CSP would be one way to load remote code later.

3 · Hand it to Claude

Open a terminal in the unzipped folder and run claude (or paste the two bundles into any Claude chat, they fit). Ask an adversarial question. “Is this safe?” invites a yes; “show me the mechanism” does not.

This folder is an unpacked Chrome MV3 extension I am about to sideload.
The JS is minified. I did not write it and I do not trust it.

Enumerate, with the exact line or string literal as evidence:
1. Every network destination: every fetch/XHR/WebSocket/EventSource URL,
   including ones built from concatenation or from stored settings.
2. Every chrome.* API it calls, and what each one is used for.
3. What data is read from the pages it runs on, and where that data goes.
4. Anything that runs code it did not ship with: eval, new Function,
   innerHTML/insertAdjacentHTML, script injection, remote imports.
5. Everything written to chrome.storage, and whether any of it is
   ever sent off-device.

If you cannot tell from the code, say "cannot tell" instead of guessing.
List anything a wallet-draining or data-harvesting extension would do
that this one does NOT do, and anything it does that you cannot
explain from the stated purpose.

Then push on the answer. Good follow-ups: “the panel renders text from a remote API; trace how that text reaches the DOM”, “does anything read window.ethereum, window.solana, or any wallet object?”, “does the content script ever send page data to the background worker?”, “which URLs come from settings I control, and what stops one of those from being any host at all?”

You can grep the same questions yourself in a few seconds, and it is worth doing both, since a grep that returns nothing is a stronger signal than a model saying nothing is there:

grep -oE 'https?://[^"'"'"' )]+' *.js */*.js | sort -u
grep -c 'eval(\|new Function\|innerHTML' *.js */*.js

What the answer should be

Ground truth for v0.1.0, so you have something to compare Claude's answer against. A mismatch means one of us is wrong, and either way you should not install it until you know which.

QuestionExpected finding
Outbound requestsThree, and only three destinations: terminal.fomoscan.sh/api/feed/public/hydrate (the public feed); <your stream service>/v1/stream and /v1/events, which are empty unless you type a URL into settings; and <that same service>/v1/release for the update check. No analytics, no telemetry, no error reporting.
Credentials on those requestscredentials: "omit" on every fetch, so Chrome attaches no cookies for those origins.
chrome.* APIsstorage, alarms, notifications, runtime messaging, action.setBadgeText, and tabs.create. Note that tabs.create needs no permission: it opens a URL and cannot read your other tabs.
Data read from the pageNone. The content script writes a panel into a shadow root and reads the hostname to decide where a token click should open. It does not scrape the site, and it does not touch wallet objects.
Dynamic codeZero hits for eval, new Function, innerHTML, insertAdjacentHTML, and no remote script loads. Feed text reaches the DOM through textContent.
StorageSettings in chrome.storage.sync (mirrored to local), cached feed and update state in local. None of it is uploaded anywhere.
What a stream service is toldOnly coarse query parameters: views, kinds, minFollowers, minUsd, minMcap, maxMcap, requireAccount, limit, since, plus your IP, as with any HTTP request. The extension applies your whitelist, blacklist and muted tokens locally and never sends them.

In direct mode no stream service sits in the loop: your browser polls the public FOMO endpoint on your interval, and that is the entire network footprint. Pick that mode if you want the smallest thing to trust.

4 · Watch it at runtime

Reading code tells you what should happen; watching the browser tells you what does. An LLM cannot run this check for you, and it is the one that catches a lie in the audit above.

  • Go to chrome://extensions, enable developer mode, and click service worker under FOMO Extension. Its DevTools Network tab shows every request the background worker makes. Leave it open for a few minutes. You should see the same URL repeating on your poll interval, and nothing else.
  • On a trading site, open DevTools → Network, filter by domain, and confirm the page is not suddenly talking to hosts it did not before.
  • Check chrome.storage contents from the service worker console: await chrome.storage.local.get(null).

What this cannot prove

The limits that apply here:

  • Claude can be wrong. It can miss a code path, misread minified control flow, or answer from the shape of the code rather than the code. Treat it as a fast reader. Where it matters, grep for the literal yourself.
  • No independent build to compare against. There is no public source repo, so nobody can check right now that the shipped bundle matches the source. The audit covers the artifact alone.
  • The checksum is self-referential. Same origin serves the zip and the hash. It catches transit corruption and says nothing about a compromised site.
  • It is a snapshot of one version. Sideloaded builds never auto-update, which is in your favour, since nothing changes under you. The moment you install a newer zip by hand, your audit covers the old one.
  • Server behaviour is invisible. If you point the extension at a stream service, you can see what it sends and nothing about what the server does afterwards. Same for the upstream FOMO API. Client-side auditing ends at the network boundary.
  • Nobody vouches for the feed content. The extension renders what the API returns. Code review cannot tell you whether a callout is a good trade or a coordinated one.

If it does not check out

Do not install it, and say so. A specific finding, with the string you found it at, is worth more than a bad feeling. Short of that, the middle options are real:

  • Install it in a separate Chrome profile that has no wallet extension. The panel works the same, and a content script cannot reach a wallet that is not in the profile.
  • Keep the feed source on direct and leave the stream service URL empty. Then the only host it ever contacts is the public FOMO endpoint.
  • Turn off the per-site toggles for sites you would rather it never load on. The content script still matches the URL, but the panel does not mount.
The build this page describes

v0.1.0 · 34 KB · sha256 912b860c763ada66… · built 2026-08-26 01:19 UTC

Download the zip

The user guide covers what every setting does, including the feed-source and per-site switches mentioned above.