For the complete documentation index, see llms.txt. Every page here is also available as Markdown: add .md to its URL.

Pass review

The categories safety review rejects, and what to fix before submitting

This is what review is looking for. If you're publishing, read it before you submit — most findings are unintentional.

The scan below doesn't run when you submit. It runs when a reviewer opens your app and starts it — see How we review apps before they list.

What the scan checks

Nine categories are scanned and published on your listing, each marked passed or flagged:

CategoryWhat it covers
CryptominingMining code, in any disguise
Dynamic code executioneval(), the Function constructor, loading WebAssembly
Network & data exfiltrationnavigator.sendBeacon, WebSocket connections, and forms that submit to another domain
External code loadingScripts loaded from another origin, importScripts, service worker registration
DOM injection / XSSdocument.write, raw innerHTML assignment
Redirects & navigationCode that sends the user to another page
Sensitive data accessCookies, reading the environment, and saving values with sensitive-looking names (tokens, keys, passwords) to local storage
Powerful browser APIsWeb Crypto, SharedArrayBuffer
Encoding & obfuscation signalsDeliberately obfuscated code

A tenth category, leftover debug output, is checked but kept internal — it's a code-quality note, not something a buyer needs.

Some things are recorded for the reviewer but never raise a category on their own: plain fetch() and XMLHttpRequest calls, ordinary local storage use, postMessage, iframes, geolocation, notification requests and plain Base64. They are common in legitimate apps, so the reviewer sees them in context rather than as a flag.

A flag is not a rejection. Most of these categories cover things a legitimate app can do on purpose: a live multiplayer game opens a WebSocket, an app that keeps you signed in stores a session token. The scan finds them; the review decides what they mean in context. A flagged category on a live listing means a person looked and cleared it.

What turns a finding into a problem is a mismatch with what your listing says. Sending user data to a third party is fine if that's the app's stated purpose and it's disclosed; it's a rejection if the listing doesn't mention it.

What the human review adds

The scan is mechanical. Two things it can't decide are decided by a person:

Content policy. Whether the app breaks the platform's rules. The Charter's bright lines are the outer boundary — no weaponization, no exploitation, no deception at scale, no surveillance. See the Charter.

Whether the app does what it claims. A working app that isn't the app the listing describes is a problem no pattern scan will catch.

If your app has tools

An app can carry tools (see Add tools to your app). They go through the same scan and then through a person. The scan parses tools.js, checks every tool has a handler and every handler a tool, and flags network calls, dynamic code, imports and host objects. A person at Panoply reads each tool's description, schema and reach, and approval waits until they have. Change tools.js after the scan and the listing needs a fresh scan and a fresh read.

What buyers see

The report on your listing shows which categories were checked and whether each passed or was flagged, the reviewer's note, and the review date. It never shows the detection rules, the patterns, or a count of matches — a published match count is a resubmit-and-watch-the-number oracle for anyone trying to evade the scan. See How we review apps before they list.

Practical advice

Most rejections come from things nobody meant to do:

  • Analytics you forgot about. A third-party tracker bundled into your build reads as exfiltration.
  • A stray API call. Debug code pointing at your dev server, still in the shipped bundle.
  • Credentials in the source. Never ship a key in client-side code — it's visible to anyone who buys the app. If your app needs an AI key, use the buyer's, held in the Key Vault: Connect your own AI keys.
  • Dependencies you didn't audit. You're responsible for what your dependencies do. Check what you're pulling in.

Test the built output before submitting. What review sees is what builds, not what's in your editor.