Security and data
This page is for the IT and security team deciding whether a design team can use Bridge. It is written from the code. Each section starts in plain words; the fold under it names the files that do it, so a developer can check every line. Where something is not in place, it says so.
For the person-level version (what Bridge keeps about you, and how to delete it), see Privacy.
What runs where
Bridge’s functions and its database run in Frankfurt, Germany, and the daily backup is kept in Paris, France. Pages are static files served from Vercel’s global network. GitHub, the email provider and your AI provider are services Bridge uses as they are; Bridge does not choose where they run.
Your design system lives in your team’s own GitHub repository, and stays there. Bridge reads it on GitHub and sends changes back as branches and pull requests. The default branch only changes when a pull request merges on GitHub, under your repository’s own rules.
| Piece | Runs on | Talks to |
|---|---|---|
| Bridge Studio pages | Static pages on Vercel’s global network, run in the person’s browser | GitHub’s API directly, with the person’s own GitHub sign-in; Bridge’s functions |
| Bridge functions (/api, /mcp, /oauth) | Vercel Functions in Frankfurt, Germany | Upstash Redis (records), GitHub, Resend (sign-in and invite emails), Brevo (work notification emails), Vercel Blob (daily backup, screenshots of private explorations) |
| Figma plugin | Figma desktop, on the designer’s computer | api.github.com and bridge.floriangouloubi.com only |
| Connector for your AI assistant | A Vercel Function at /mcp | GitHub, as the person who connected it; answers your assistant |
| CLI and local MCP server | Your own machine, from npm | GitHub with a token you pass, or files on disk. No Bridge server |
| The AI model | Your company’s own AI account (Claude or another MCP client) | Bridge’s connector, when someone connects it |
- Public addresses and the daily backup job:
apps/docs-site/vercel.json(rewrites of/mcp,/oauth/*and/.well-known/*toapi/mcp; crons on/api/account?export=1and/api/account?digest=1). - Storage: Upstash Redis over its REST API,
api/_lib/redis.ts. Email: Resend for access emails, Brevo for work notifications, both inapi/_lib/email.ts; who gets a notification and when:api/_lib/notify.ts. Backup: Vercel Blob withaccess: 'private',api/account/index.ts; screenshots of private explorations in the same private store,api/_lib/private-shots.ts. - Regions: functions run in
fra1(Frankfurt), set byregionsinapps/docs-site/vercel.json. The Upstash database’s primary region is Frankfurt, with no read copies elsewhere. The Vercel Blob backup store is in Paris (cdg1). These last two are settings of the stores, not of the code. - CLI tokens:
packages/cli/src/auth.ts. A write only uses a token passed on purpose (GITHUB_TOKEN,--token, or--use-gh-auth).
What Bridge stores
Bridge keeps records about people and teams, not your design system. Everything below lives in one Upstash Redis database, except the session cookie (in the browser), and the backup and the screenshots of private explorations (Vercel Blob). Bridge encrypts the GitHub tokens it keeps (for the AI connector and the plugin hand-off). Other records are not encrypted by Bridge itself, beyond what the storage provider does.
| What | How long | How it is kept |
|---|---|---|
| Account: email (as typed, and its canonical spelling: one account per mailbox, so a Gmail address with or without dots or a “+tag” is one account), name, linked GitHub login and id, creation date | Until the person deletes it | Record in Redis, keyed by the canonical spelling |
| Session cookie (in the browser): email, version, expiry | 30 days | Signed with HMAC-SHA256; HttpOnly, Secure, SameSite=Lax |
| Team membership: email, name, role, who added them | Until an admin removes them | Record in Redis |
| Pending invite: email, role, team, who invited, dates | 7 days, then only the team name and inviter name for 30 more days | The link itself: only its SHA-256 |
| Request to join a team: name and email | 14 days | Shown to that team’s admins only |
| One-time sign-in code by email | 10 minutes, 5 tries | Only its SHA-256 |
| Email sign-in links for Library readers and email members | 15 minutes, once | Only its SHA-256 |
| Which branches and issues an email member made through Bridge | 1 year | Record in Redis |
| Work notifications for an email member: what happened (kind, title, who did it, team, link), their email choices and time zone | 30 days, or until the daily summary sends them; choices and time zone with the account | Record in Redis; names and titles, never a comment; the unsubscribe link is sealed (AES-256-GCM), never the address in clear |
| Short-lived copies of GitHub reads for team pages | 24 hours for most answers, only to ask GitHub whether they changed (shown without asking GitHub for 30 seconds, 10 minutes while GitHub’s change notices arrive, and for 20 minutes at most while GitHub is busy); 24 hours for public names and pictures, for answers that never change and for file contents | Cache in Redis |
| The Bridge GitHub App’s read-only token for a repository, shared by Bridge’s functions | 50 minutes at most (GitHub ends it after an hour) | Encrypted (AES-256-GCM) |
| AI connector connection: team repository, app name, GitHub login, display name, dates, the person’s GitHub tokens | 90 days after last use | GitHub tokens encrypted (AES-256-GCM); the rest as a record |
| AI connector tokens Bridge issues to the assistant | 1 hour (access), 30 days (refresh, single use), 2 minutes (authorization code) | Only their SHA-256 |
| Figma plugin sign-in hand-off: team and GitHub access token | 5 minutes at most, removed when the plugin collects it | Code and poll secret: only their SHA-256. The token: encrypted (AES-256-GCM) |
| Fix session: the steps the team’s AI took on one component | 7 days after the last step | Names, never emails |
| Daily backup of accounts, members, invites and team records | 30 backups | Private Vercel Blob store |
| Screenshots of the explorations of private products, shown to the team’s readers only | 90 days after the capture, the image replaced by each new one | Private Vercel Blob store; Redis keeps only when and of which commit |
Backups hold accounts, members, invites and team records. They leave out sign-in codes and the AI connector’s connections and tokens. A deleted account can stay in a backup until that backup ages out.
- Accounts, sessions and codes:
api/_lib/account.ts,api/account/index.ts(codes compared in constant time on their SHA-256, capped per email, per network and per day; backup patternsEXPORT_PATTERNS, 30 kept). Every record keyed by an email uses its canonical spelling,api/_lib/canonical-email.ts: lower case, and for Gmail (gmail.com, googlemail.com) without dots or a “+tag” before the @; other providers keep their dots and “+”. Two accounts made under two spellings of one Gmail mailbox before that rule are merged at the next sign-in,api/_lib/account-merge.ts: the older account keeps its identity, two different linked GitHub accounts are never merged. - Cookies:
v1.<claims>.<HMAC-SHA256>underLIBRARY_SESSION_SECRET, with a purpose in the MAC so one feature’s cookie never passes for another,api/_lib/session.ts. Every request reads the account again: “Sign out everywhere” bumps a version and ends every session. - Connector tokens:
api/_lib/oauth.ts. Bridge’s own tokens are opaque random values; only their SHA-256 is stored. The person’s GitHub access and refresh tokens are sealed with AES-256-GCM under a key derived with HKDF-SHA256 fromLIBRARY_SESSION_SECRETfor that purpose only, with the connection id as additional data,api/_lib/sealed.ts. - Members, invites, join requests:
api/_lib/members.ts,api/invites/index.ts,api/_lib/teams.ts. Emails and network addresses used for rate limits are counted under an HMAC, never in clear. - Cache:
api/_lib/team-read.ts(KEEP_S,ETAG_KEEP_S,BLOB_KEEP_S,USER_KEEP_S). Plugin hand-off:api/plugin-link/index.ts(the token sealed the same way, with its own key purpose, bound to the link). Fix sessions:api/_lib/fix-sessions.ts.
What Bridge does not store
- Passwords. There are none: people sign in with GitHub or with a one-time code sent to their email.
- A lasting copy of your design system. Tokens, components and docs stay in your repository; Bridge keeps only the short-lived copies above.
- Studio’s GitHub sign-in. It stays in the person’s browser (local storage). Bridge’s token function swaps GitHub’s one-time code for a token and returns it without storing or logging it.
- The Figma plugin’s GitHub sign-in. It stays on that computer, in Figma’s plugin storage, never in the Figma file.
- AI keys or AI conversations. Bridge’s servers hold no AI key. The connector sees the tool calls your assistant makes, not the conversation.
- Analytics or tracking. The site has no analytics script. Bridge’s functions log nothing about people; the only log lines are blocked-content reports from browsers and a warning when the backup is not set up. The hosting platform keeps its own request logs.
- Token swap:
api/github/token.ts(same-origin POST only,Cache-Control: no-store). Browser storage keybridge.github.token.direct:src/scripts/github-session.ts. - Plugin storage:
figma.clientStorageinapps/figma-plugin/src/main/github-token.ts. - Logs:
api/csp-report/index.tslogs Content-Security-Policy reports;onExportinapi/account/index.tswarns whenBLOB_READ_WRITE_TOKENis missing;redisRestinapi/_lib/redis.tslogs why a storage call failed (Upstash’s error and the command names, never the token, keys or values). No otherconsolecall inapps/docs-site/api.
Access and permissions
GitHub decides who can do what. People with GitHub act as themselves, with their own sign-in, and never get more than their own access to the repository. People without GitHub go through Bridge, with narrower limits (below). Bridge adds review rules on top; your branch protection on GitHub still applies.
The GitHub App
Bridge uses one GitHub App, Bridge Direct. A repository admin installs it on the design system repository only (“Only select repositories”). One consent on GitHub covers reading, suggestions and saving changes.
- Bridge Direct opens branches and pull requests, and files suggestions as issues: Contents, Pull requests and Issues read and write, Administration read and write (read only is possible if your admins turn branch protection on themselves), Checks, Commit statuses, Metadata and Organization members read. Optional: Workflows read and write, only so Studio can open the pull request that adds the automatic measure workflow (.github/workflows/bridge-measure.yml); without it, Studio gives that file to commit from GitHub.
- It asks one account permission: Email addresses, read only, so an invite reaches the person it was sent to.
Bridge’s server holds Bridge Direct’s private key. With it, the server can act on every repository where Bridge Direct is installed, within the permissions above. It uses that for a few jobs only: changing collaborators when someone accepts an invite, serving a private Library to readers without GitHub, carrying out changes for email members, shipping token value changes, and removing a deleted person’s name from their requests. Each time it asks GitHub for a token limited to one repository, often to fewer permissions, and never stores it.
Roles
- Can edit: write access on GitHub. Reviewer: write access, plus listed as a design or tech owner in
bridge.config.json. Admin: admin on GitHub. - People without GitHub can join by email as Can edit or Admin, never Reviewer: approvals happen on GitHub. An email member’s changes go through Bridge with strict limits: design files only (tokens, docs, specs), on their own branches, never the default branch, never a merge or a review, 300 writes a day by default.
Reviews and merging
- Every change is a pull request with a risk tier. Studio, the connector and the CLI read the same verdict: checks pass, the approvals the tier needs (one owner for low risk; a design owner and a tech owner otherwise), no changes requested, no conflict.
- A person merges, with write access and the right to ship. GitHub never lets authors approve their own pull request.
- Your AI assistant acts as the person who connected it, and never merges through Bridge’s connector: the merge tool is neither offered nor accepted there. A person merges, from Studio or GitHub. A pull request the assistant opens is authored by the person who connected it, so someone else has to approve it.
- One exception, by design: token value changes. Once the checks pass, Bridge approves and merges a pull request that only changes token values, from a branch of the repository itself (never a fork). A push from the Figma plugin counts as design-approved only when its author, with write access, asks for it, and Bridge has signed it for that pull request, that author and that exact commit; any new commit needs a new signature. A value change opened anywhere else needs a design owner’s approval on its latest commit. Bridge checks the changed files again at the latest commit before it ships. Renamed or removed tokens, code and guardrail files always wait for a developer.
- Owner override, for solo maintainers: off unless
bridge.config.jsonturns it on, for listed owners only, only when approvals are the one thing missing. It needs a written reason (20 characters at least by default), posted on the pull request before the merge and repeated in the merge commit. If GitHub refuses the merge, the comment says “Not merged”.
- App permissions:
docs/SIGN-IN.md. Installation tokens, minted per request and per repository:api/_lib/github-app.ts(used byapi/invites,api/library-access,api/contribute,api/ship,api/account). The read-only token of team reads is shared by Bridge’s functions while it is valid, sealed in Redis:api/_lib/shared-token.ts. - Caller checks:
api/_lib/team-caller.tsreads the person and their permission from GitHub on every call; nothing the browser says about itself is trusted. - Email member proxy policy (deny by default):
api/contribute/index.ts. - Review verdict, merge and owner override:
packages/core/src/review-gate.ts,packages/core/src/branch-actions.ts,bridge_merge_branchinpackages/mcp/src/server.ts(local CLI and MCP only; the hosted connector refuses it,HOSTED_REFUSED_TOOLSinapi/mcp/index.ts), rules indocs/GOVERNANCE.md. - Token value shipping:
api/ship/index.tsandapi/_lib/figma-ship.ts. The signed Figma mark (HMAC-SHA256 over repository, pull request, head commit and author, with a key derived for that purpose only):api/_lib/figma-mark.ts.
Network and APIs
- The Figma plugin may only reach
https://api.github.comandhttps://bridge.floriangouloubi.com. Figma enforces that list. - Studio pages send a strict Content-Security-Policy: scripts from the site itself (and Vercel’s preview toolbar) only, no embedding of Bridge in other sites, no plugins. Pages may call GitHub’s API and, only when a person adds their own key for drafting docs, Anthropic’s API.
- Every Bridge function answers with
Cache-Control: no-store. Calls that change something must come from a page of the site itself (Origin checked), except the ones the Figma plugin and AI assistants make, which carry a token instead of a cookie. - The connector at
/mcponly answers with a Bridge token, issued through OAuth 2.1 with PKCE (S256), for one person, one team and one app. Tokens travel in headers and bodies, never in a URL.
| Address | Who may call it | Other sites |
|---|---|---|
/api/account, /api/entry, /api/invites | A Bridge account session, or a GitHub sign-in checked with GitHub | Refused |
/api/contribute | An email member’s session, or a GitHub sign-in that can read the repository (reads of a public demo team: anyone) | Refused |
/api/github-webhook | GitHub only: each delivery is signed with HMAC SHA-256 under the app’s webhook secret, checked before anything is read. It only marks cached reads to check again; it keeps repository names and times, never the payload | Refused (no signature) |
/api/library-access | Library readers, with an email link session; admins, with a GitHub sign-in checked with GitHub, to change who reads | Refused |
/api/github/token | Studio’s sign-in page only; stores nothing | Refused |
/api/plugin-link | The plugin, with a one-time code and its secret; the approval from Studio only | Allowed for the plugin’s steps |
/api/ship | A GitHub sign-in that can read the repository, or a team member’s session | Allowed with a GitHub token |
/mcp, /oauth/*, /api/fix-sessions | An AI assistant with a Bridge token; app registration capped at 30 per network per hour. Fix sessions: a GitHub sign-in that can read the repository | Allowed, token only |
/api/storybook | Anyone; reads a public Storybook index, guarded against requests to private networks | Not offered |
- Plugin domains:
networkAccess.allowedDomainsinapps/figma-plugin/manifest.json. - Page headers:
Content-Security-Policy(frame-ancestors 'none',object-src 'none',base-uri 'none', hashed inline scripts),X-Content-Type-Options,Referrer-Policyinapps/docs-site/vercel.json, built bysrc/lib/csp.ts.vercel.liveis Vercel’s preview toolbar. - Origin checks:
sameOriginandSECURITY_HEADERSinapi/_lib/http.ts. Open CORS (Access-Control-Allow-Origin: *) is set inapi/mcp(withapi/fix-sessions),api/plugin-linkandapi/ship, which take a bearer token or a one-time code from other sites, never a cookie. - OAuth: metadata per RFC 8414 and RFC 9728, client registration by metadata document or RFC 7591 (public clients only), exact redirect match (any port on localhost), the
resourcechecked,issin the redirect, the request bound to the browser that started it:api/_lib/oauth.ts,api/mcp/index.ts. - Storybook fetcher guards (https on 443 only, public addresses only after DNS, 3 redirects, 5 s, 5 MB):
api/storybook/index.ts.
AI
Bridge runs no AI model and its servers hold no AI key. The AI is your team’s own assistant, in your company’s own account: Claude, or any assistant that can use an MCP connector. What your company has agreed with that provider is what applies.
- Connecting. A person adds Bridge’s connector address in their assistant, signs in to Bridge, picks a team, and confirms on GitHub. Bridge checks with GitHub that this person can write to the team’s repository.
- What the assistant receives. What that person could read on GitHub in that one repository: tokens, components, docs and guidelines, change history, reviews and comments, requests and the state of branches.
- What it can do. What the person could do, under their GitHub account: start branches, open draft pull requests, comment and review. It never merges, and never writes straight to the default branch.
- What Bridge sees. The tool calls, not the conversation. For three component fix tools, it keeps the steps (what was read, each attempt’s result, the pull request) for 7 days so the team can follow along.
- The Figma plugin never calls an AI provider and has no place for an AI key. “Ask Claude to fix it” opens the team’s own Claude in the browser.
- Optional, per person. On a Library page, someone may paste their own Anthropic API key to draft docs. It stays in that browser and goes only to Anthropic’s API, with that page’s text.
- Connector:
api/mcp/index.tsruns the MCP server (packages/mcp/src/server.ts, every tool listed there exceptbridge_merge_branch, which it neither lists nor runs) with the person’s GitHub App user token, renewed on the server. Push access checked at sign-in (permissions.pushoradmin). - No AI key in the plugin, tested:
apps/figma-plugin/test/push-drafts.test.ts. - Browser-only key for docs drafting:
src/scripts/docs-ai.ts.
Subprocessors and DPA
The companies below handle data for Bridge. GitHub is not on the list: it is your team’s own provider, under your own agreement with GitHub, and Bridge works inside your repository there. Your AI provider is the same: your company’s own account.
| Company | What it does for Bridge | Where | Data it handles |
|---|---|---|---|
| Vercel Inc. (United States) | Hosts the site and runs Bridge’s functions; keeps the daily backup (Vercel Blob) | Functions in Frankfurt, Germany. Backup in Paris, France. Static pages from Vercel’s global network | Everything Bridge stores passes through its functions; the backup holds accounts, members, invites and team records |
| Upstash Inc. (United States) | The Redis database that holds Bridge’s records | Frankfurt, Germany | The records in the table above |
| Resend Inc. (United States) | Sends sign-in codes, sign-in links and invites by email | Not chosen by Bridge | The recipient’s email address and the email’s text |
| Brevo (Sendinblue SAS, France) | Sends the emails about a person’s own work (a question for them, something ready for their look, what shipped) and the daily summary | European Union | The recipient’s email address and the email’s text: names and titles, never a comment |
All three are companies based in the United States, even where the data sits in Europe. A data processing agreement (GDPR article 28) is available as a template, with this list as its annex. It is a starting point to review and sign together, not a signed or certified document.
Deletion and offboarding
- Remove a person. GitHub people: remove them from the repository on GitHub. Email members: an admin removes them in Studio, Settings, Members, which ends their sessions at once.
- Remove an AI connection. In Studio, Settings, Claude: the person who connected it, or a team admin, removes it. Its tokens stop working at once. A connection unused for 90 days ends on its own.
- Remove Bridge from a repository. A repository admin uninstalls the Bridge apps on GitHub (repository or organization settings, GitHub Apps). Each person can also revoke their own sign-in: GitHub, Settings, Applications, Authorized GitHub Apps. GitHub then refuses every token of that sign-in, including the ones Bridge keeps for their AI connections.
- Delete an account. In Studio, the menu with your name, Account settings, Delete my account. It removes the account, its memberships, join requests and sessions at once, and ends every AI connection the person made, with the GitHub tokens kept for them. Changes the person sent stay in the team’s history, signed “A former member”. Backups age out within 30 days.
- Delete a team. A team made in Bridge: an admin opens Settings, Danger zone, Delete this team, and types the team’s name to confirm. Everyone on it loses access at once; its members, invites, requests to join, AI connections, name and settings go from Bridge’s storage at once.
- Disconnect a team from Bridge. A team connected to a repository: an admin opens Settings, Danger zone, Disconnect this team, and types the team’s name. Bridge forgets the same things as for a deleted team. The repository, its history and its settings stay on GitHub: Bridge never changes them. People with access to the repository on GitHub can still open it in Bridge until a repository admin uninstalls the Bridge apps (see above).
- Remove your design system. Nothing to delete on Bridge’s side: it lives in your repository. Short-lived copies expire on their own within 24 hours.
What deleting or disconnecting a team leaves: copies of what Bridge read from GitHub expire on their own within 24 hours, Claude’s work in progress on a component within 7 days, notices already in people’s daily summaries within 30 days, and backups within 30 days.
- Account deletion:
onDeleteinapi/account/index.tsandapi/_lib/former-member.ts. It ends the person’s connections first:revokeAccountGrantsinapi/_lib/oauth.ts. - Team deletion and disconnection:
delete-teaminapi/entry/index.ts(forgetTeam), admins only, checked on the server. It calls GitHub only to check that the person is an admin, never to write. - Connection removal:
view=disconnectinapi/mcp/index.ts,revokeGrantinapi/_lib/oauth.ts. A GitHub token that no longer renews ends the connection on its next use (githubTokenOf).
Audit the code, report a problem
All of Bridge is public in one repository: the site, its functions, the connector, the Figma plugin, the CLI and the MCP server are under the Functional Source License (FSL-1.1-MIT, each version becomes MIT after two years), and the components and tokens are MIT. Your team can read every line above, run the tests (npm test) and run its own copy.
Found a security problem? Report it privately through GitHub’s security advisory form for the repository. Please do not put the details in a public issue. There is no dedicated security email.
- Source: github.com/the-bridge-company/ai-design-system-demo. License:
LICENSE. - Running your own copy: the GitHub Apps and environment variables are listed in
docs/SIGN-IN.mdanddocs/HOSTED.md.
Not in place yet
Said plainly, so nobody has to ask. Ask on GitHub if one of these blocks you; there are no dates.
- No certification or outside audit: no SOC 2, no ISO 27001, no penetration test report.
- No signed data processing agreement yet: a template is ready to review and sign with your company.
- No single sign-on (SAML, OIDC, Okta, Google Workspace). Sign-in is GitHub or a one-time email code.
- No choice of data region per team: every team’s records are in Frankfurt. The email provider’s region is not chosen by Bridge.
- No step-by-step self-hosting guide, though the code can run on your own Vercel project.
- No encryption with your own keys. Bridge encrypts only the GitHub tokens it keeps.
- One server secret does several jobs: it signs sessions and is the source of the keys that encrypt the kept GitHub tokens and sign Figma pushes (each key derived for one purpose). Rotating it signs everyone out and ends every AI connection.
- No audit log of Bridge’s own actions beyond what GitHub records (commits, reviews, comments).
- No uptime commitment or support agreement.