An extension never touches the host
Your code mounts inside its own shadow root and reaches the rest of Tappify only throughtap.
It cannot read or write the host’s DOM, its router, its cookies or its storage. Copying,
downloading and leaving Tappify are host-drawn calls for exactly that reason:
An extension never sees a credential it was not given
Never a Tappify session credential, never a store credential, never another extension’s data. Credentials an owner supplies for your connector are held encrypted in Tappify’s vault, are never returned to the browser, and are read server-side only to forward to the server URL your manifest declares. The install token your server receives is RS256, five minutes long and audienced to your extension alone. The data API accepts none of them: there is no vendor-callable token for Tappify data, and every data call is made by the host on the owner’s own session.Nothing is written on the owner’s behalf without an approval card
tap.actions.run creates a pending run and renders the approval card: a card naming the scope, the
blast radius, whether the action can be undone and what it costs. Nothing reaches your server until
the owner approves it. There are no autonomous writes and no caller that bypasses the card — not the
assistant, which stops at the same pending run and lets the owner decide in chat, and not the
extension — and “always ask” is the default, which cannot be turned off for a scope that needs a
security review.
Anything a vendor produced carries the vendor tile
Every surface an extension renders on — widget, tab, page, row action — carries the vendor’s mark, drawn by the host outside your mount, and so does every entry an extension adds to a menu or to the project navigation. Owners always know which pixels and which numbers are not Tappify’s.The host draws every failure
Loading, failed, missing scope, paused, suspended and retired are Tappify’s cards, with Tappify’s copy. An extension that throws, times out or fails to load shows one card, with Retry and Report to vendor, and the rest of the dashboard is unaffected.Vendor text never carries authority over the assistant
Tool descriptions, skills, prompt templates, knowledge files and context blocks are size-capped, scanned at publish for text that tries to override Tappify’s own rules, and read by a reviewer for a public extension. They are data about a vendor’s product. An extension cannot render inside the assistant’s transcript; the assistant returns typed data and the host picks the card.Tappify never runs vendor code on its servers
An extension that acts runs its own server. Tappify calls it with a short-lived token audienced to that one extension and a body carrying the project, the filters and the owner’s saved documents. There are no hosted functions.An extension is never framed
Tappify never puts a vendor’s site in an iframe in the product. Expanding a widget or opening a row action re-mounts the component you shipped, at a different size, with the row astap.context.
And the switches behind them
- Bundles are content-addressed and immutable. A remote entry loads only from an origin on the allowlist, and only against the SHA-384 hash recorded for that release; an unverified extension-origin script is refused rather than loaded.
- At most three remote entries load at once per page, each with an eight-second ceiling, so a slow vendor never holds up the dashboard.
- Install tokens are RS256, five minutes, audienced to one extension, and the host mints a fresh one every four minutes for as long as a mount is open.
- Suspending an extension stops it loading everywhere inside one install refresh, about a minute.
- Uninstalling deletes the install and every document it held. Retiring revokes every install and keeps the documents exportable for thirty days before the same deletion runs.
- When a person leaves a workspace, their
user-scoped documents go with them, across every install in it.
Scopes
What an owner can grant, and which grants need a review of your company.