Smart Order Capture
Trust & safety

How the denylist works (and why it has three layers)

Defense-in-depth for the categories of automation we refuse to enable: server validation, marketplace review, and a compiled-in on-device gate.

EngineeringsmartordercaptureMay 8, 20265 min read
A red padlock resting on a black keyboard — the blocked-by-default posture the denylist enforces.

A common question we get is: "if you don't want people to automate X, why not just refuse to ship it?" The honest answer is that we do refuse to ship it — three different times, in three different places, because any single layer can fail.

Layer 1 — Server-side, at workflow save

Every workflow JSON document passes through Zod validation when the user clicks Save. Part of that validation walks the node graph and collects every packageName reference. If any name appears in our denylist, the API returns a BAD_REQUEST with the offending package called out in the error. The builder UI highlights the node so the user knows exactly which step to remove.

This is the cheapest layer to enforce and catches accidental references early. It also produces an audit_log row tagged denylist.violation so we can spot patterns.

Layer 2 — Marketplace review worker

Templates submitted to the marketplace are re-scanned by a background worker before going public. The scan is the same logic the API runs, plus a static analysis pass that looks for patterns we don't pre-block but want to flag (e.g. CAPTCHA selectors, common ad-network method names). Anything that hits is unpublished and queued for human moderation.

Layer 3 — On-device, in the Kotlin interpreter

This is the load-bearing layer. Just before dispatching any UI action that targets a package, the Kotlin interpreter checks the package against a compiled-in Denylist object. That object is generated from the same source-of-truth file as the server-side check via scripts/ts-to-kotlin.ts; CI fails if the two ever drift.

Why is on-device the load-bearing layer? Because anyone with the source could spin up their own API or run an older client. The device can never be tricked into bypassing the gate as long as the APK we ship is the APK that runs. We sign every release.

How additions get on the list

New entries come from three sources: pattern recognition in our abuse queue, reports to abuse@smartordercapture.com, and our own product judgment when a new category of misuse appears in the wild. Additions are batched into the next Android release and announced in the changelog.

We don't accept "delist this app for me" requests. If you want to automate something on the list, smartordercapture is not the right tool.