Tap runs in your authenticated browser context with full Chrome API privileges. A compromise of Tap is a compromise of every service you're logged into. We take this seriously and the architecture is designed to keep that trust assumption from being violated.
If you find a vulnerability, please report it via GitHub Security Advisories or email security@taprun.dev. We acknowledge within 48 hours and aim to fix critical issues within 30 days. Bug bounties are not yet funded but we credit reporters in fix notes (unless you prefer anonymity).
Tap is interface infrastructure. To do its job, the Chrome extension and local daemon hold these capabilities:
- Read all cookies (incl. HttpOnly via
chrome.cookies) - Read DOM, localStorage, IndexedDB, sessionStorage on any page
- Inject scripts via
chrome.scripting.executeScript - Submit forms, click buttons, type text on your behalf
- Navigate to any URL, take screenshots
- Read network responses via CDP (Network.responseReceived)
- Open and close tabs, manage multi-tab sessions
Most SaaS tools (Notion, Linear, Figma) have data at rest in their servers. A breach leaks data — bad, but bounded. The attacker gets what the service stored.
Tap is different because it has execution capability in your authenticated browser. A compromise of Tap is closer to:
- A password manager extension breach (1Password, LastPass, Bitwarden)
- A crypto wallet extension breach (MetaMask, Phantom)
- A remote desktop tool with stored credentials (TeamViewer, AnyDesk)
The attacker doesn't get Tap's data. The attacker gets anything you were logged into when Tap was running: email, banking, GitHub, AWS, Slack, your company's SSO, your crypto wallet, your social media, your saved passwords.
This framing matters because it justifies architectural choices that look paranoid for a typical SaaS but are baseline for tools with this blast radius.
These are hard rules. Code that violates them is rejected at PR review. Tests in tap-core enforce a subset automatically.
Any server → client message must be structured data validated against a strict schema. Free-form code, scripts, or "patches" containing executable strings are forbidden. The local daemon rejects anything that doesn't match the allowed schema.
Why: a compromised Tap server should NOT be able to ship arbitrary JavaScript that runs in your browser. The server's worst case is "attacker can suggest bad selectors" — not "attacker can run any code".
The daemon is outbound-only. No long-poll where the server pushes work, no webhook the server calls, no reverse SSH, no cloud cron that wakes the local agent. Every server interaction is client-initiated pull.
Why: this is the structural property that prevents "compromised Tap server → remote control of every Tap user's browser". If the server can't initiate, the worst attacker can do is poison content the client voluntarily fetches — and that content goes through invariant #1's schema firewall.
.tap.js files can contain selectors, API endpoints, auth tokens,
business logic, and scraping targets — they are user IP and may contain
credentials. We do not offer cloud sync of ~/.tap/taps/.
When multi-machine sync is built (Pro feature roadmap), it will be end-to-end encrypted client-side (Bitwarden model: server holds opaque ciphertext blobs, encryption keys never leave user machines).
Free tier never touches Tap servers (except license validation). Hacker tier never touches Tap servers (except license validation). Pro tier (current v1 design) still never touches Tap servers beyond license validation.
If a future feature needs server compute, it must be:
- Per-feature opt-in with explicit user consent at activation time
- Documented in this file before shipping
- Justified against invariants 1-3
Every Pro feature must have a 100% local mode. Users who never want their machine to phone home — for privacy, regulatory, or paranoid reasons — can pay for Pro and get the features listed on the pricing page. "Local-only" is a tier of trust we offer, not a degraded experience.
┌──────────────────────────────────────────────────────────┐
│ Your machine │
│ │
│ ┌──────────┐ stdio ┌─────────┐ HTTP localhost │
│ │ CLI │ ─────────► │ Daemon │ ◄─────────────┐ │
│ │ MCP │ │ :9333 │ │ │
│ │ client │ │ :9334 │ │ │
│ └──────────┘ └─────────┘ │ │
│ ▲ │ │
│ │ HTTP localhost │ │
│ │ │ │
│ ┌──────────┐ │ │
│ │ Chrome │ │ │
│ │ Extension│ │ │
│ └──────────┘ │ │
│ │ │ │
│ │ chrome.* APIs │ │
│ ▼ │ │
│ ┌────────────────┐ │ │
│ │ Your browser │ │ │
│ │ (auth state) │ │ │
│ └────────────────┘ │ │
└────────────────────────────────────────────┬───────────┘
│
│ HTTPS, outbound only
▼
┌──────────────────────────────┐
│ api.taprun.dev │
│ /activate /validate │
│ │
│ Sees: license key, machine │
│ fingerprint, tier │
│ Does NOT see: tap source, │
│ page data, browse │
│ history, cookies, AI │
│ prompts, anything else │
└──────────────────────────────┘
The local daemon only binds 127.0.0.1. There is no remote network exposure. Every component on your machine trusts every other component on your machine — that trust boundary is the OS user account. Remote attackers must first achieve local code execution to reach Tap, at which point they've already won at a lower layer.
Tap functions execute in a Deno Worker with zero permissions:
Worker (permissions: "none")
tap.click("Submit") → postMessage → main thread → runtime
The Worker cannot:
- Read files (
Deno.readFile,Deno.readTextFile) - Make network requests (
fetch,Deno.connect) - Run subprocesses (
Deno.Command) - Read environment variables (
Deno.env) - Import dynamic modules (
import())
All interface operations proxy through the tap.* handle via
postMessage. The handle is the only escape hatch from the Worker, and
it routes through the daemon → extension → browser path, where each
hop can apply its own policy.
Disable with --no-sandbox for debugging only.
Every PR to tap-skills is checked by CI. Violations block merge:
| Rule | Blocks | Why |
|---|---|---|
| no-eval | eval() (tap.eval allowed) |
Code injection |
| no-dynamic-code | new Function() |
Hidden logic |
| no-base64 | atob() |
Obfuscated payloads |
| no-websocket | new WebSocket() |
Persistent exfiltration |
| no-xhr | XMLHttpRequest |
Bypass fetch checks |
| no-dynamic-import | import() (ObjC.import allowed) |
Load external code |
| no-exfiltration | fetch() to unrelated domains |
Data theft |
tap contribute runs these same checks locally before creating a PR.
~/.tap/
taps/ your forged taps; LOCAL ONLY, no git remote
.gitignore blocks all non-.tap.js files
skills/ git clone of tap-skills (community taps)
contribute adds only specific .tap.js files,
never git add -A
playwright/ browser sessions (cookies, tokens, auth state)
NOT inside any git repo, never synced
logs/ execution logs — NOT tracked by git
| Property | Status |
|---|---|
| macOS code signing | ✅ Release binaries are signed via codesign |
| SHA256SUMS | ✅ install.sh verifies binary + extension hash |
| Daemon binds 127.0.0.1 only | ✅ No remote network exposure |
Local-only daemon ports (:9333/:9334) |
✅ Enforced by daemon code |
| Chrome Extensions API first | ✅ chrome.scripting is undetectable; CDP only for input events |
| No telemetry | ✅ Zero analytics, zero tracking, zero phone-home |
| No remote code execution | ✅ .tap.js loaded from local disk only |
We're honest about what's not yet done:
| Gap | Risk | Roadmap |
|---|---|---|
| No reproducible builds | Compromised build environment could ship malicious binary unnoticed | v0.12 |
| SHA256SUMS not GPG-signed | If GitHub release signing is compromised, both binary AND hash file can be replaced | v0.12 |
| AI-generated tap code runs without explicit approval | A malicious site could embed prompt injection that causes forge AI to generate exfiltration code; sandbox still applies, but the user runs it without reviewing | v0.12 (first-run approval gate) |
| No third-party security audit | Architecture has not been formally reviewed | v0.13 (hire firm) |
| No bug bounty | Reporters are credited but not paid | v0.14 (after audit) |
| Linux binaries unsigned | macOS uses codesign, Windows uses Authenticode (planned), Linux has no equivalent yet |
v0.14 (Sigstore) |
- Remote code execution via crafted
.tap.jsfiles - Sandbox escape from Worker isolation
- Credential exfiltration via community taps or AI-generated taps
- Data leakage through git repos (
taps/,skills/) - Privilege escalation beyond Chrome extension permissions
- Cross-tab data leakage in multi-tab scenarios
- Daemon binding to non-localhost addresses without explicit user consent
- Update mechanism integrity (binary or skill update poisoning)
- License validation bypass enabling tier escalation
- Prompt injection in
capturepage-inspection content that causes AI to generate malicious tap code
- Vulnerabilities in websites that taps interact with
- Denial of service against the local daemon
- Issues requiring a physically present attacker
- Issues requiring an attacker who already has local code execution (they've already won)
- Vulnerabilities in third-party dependencies that don't affect Tap's threat model (we'll still investigate; they're just lower priority)
- Issues in user-installed plugins (
~/.tap/plugins/); plugins are fully trusted by design and users opt-in by installing them
Do NOT open a public issue.
Use one of:
- GitHub Security Advisories (preferred — handles disclosure flow automatically)
- Email security@taprun.dev
Please include:
- Description of the vulnerability
- Steps to reproduce (proof of concept, even minimal, helps a lot)
- Impact assessment from your perspective
- Affected versions
- Your name and contact (for credit; anonymous reports are also welcome)
We commit to:
- Acknowledge receipt within 48 hours
- Initial triage within 7 days (severity assessment, planned fix path)
- Fix critical issues within 30 days (high severity within 60, medium within 90)
- Credit you in the fix notes unless you prefer anonymity
- Coordinate disclosure timing with you — we won't publish details before you're ready, and we won't sit on a fix indefinitely either
| Version | Item | Status |
|---|---|---|
| v0.11 | SHA256SUMS verification in install.sh | ✅ shipped |
| v0.11 | Pro tier locked to 100% local (no Tap-hosted AI proxy) | ✅ shipped |
| v0.11 | This document | ✅ shipped |
| v0.12 | First-run approval gate for AI-generated taps | planned |
| v0.12 | GPG-signed SHA256SUMS | planned |
| v0.12 | Reproducible builds | planned |
| v0.13 | Sigstore / cosign signing of releases | planned |
| v0.13 | Third-party security audit | planned |
| v0.14 | Bug bounty program | planned |
Most security policies are written after an incident, by lawyers, to limit liability. We wrote this before shipping any server-side features, by engineers, to constrain ourselves.
The five architectural invariants above are not aspirations. They are rules we have agreed not to break, even when it would be convenient, even when a feature would be cooler with a server, even when a competitor ships something flashy that requires breaking them. If we ever do break them, we will say so loudly in the release notes — and we will have thought hard about why first.
If you read this and think we're being paranoid, that's the intended reaction. The blast radius of a tool that operates your authenticated browser justifies paranoia.
If you read this and think we're missing something, please tell us.