These pages describe PrivacyFence 7.0.2. Download it · What changed
How PrivacyFence works¶
PrivacyFence sits between an MCP-compatible AI system (Claude Desktop, Claude Code, ChatGPT, claude.ai or Gemini Enterprise through an organization deployment) and the services it can reach for you: Gmail, Google Drive, Calendar, Contacts, Tasks, Apps Script, Slack, Telegram, Salesforce, Jira and Confluence. The AI system never holds your credentials for those services. It asks PrivacyFence, and PrivacyFence decides whether the request runs straight away, needs your approval first, or is filtered.
This page explains the moving parts. For installing, see Getting started. For what each tool does and which gate it goes through, see the Tools reference. For how approvals, rules and filters behave, see Approvals and policy.
The pieces¶
| Piece | What it is | Where you see it |
|---|---|---|
| Daemon | The background service. It holds the connector sign-ins, evaluates policy, keeps pending approvals and the audit log, and serves the web pages and the MCP endpoint on one local port (8765 by default, web.port). |
Nowhere directly. It runs as a system service on a packaged install. |
| Companion app | Your way into the daemon's web pages. On macOS it is a menu-bar icon, on Windows a tray icon; on Linux it is the PrivacyFence entry in the applications menu, whose right-click actions open Settings, show a new recovery code, and start, stop or check the service. It opens pages in your browser already signed in, starts and stops the service, and shows your recovery code. | Menu bar, tray, or applications menu. |
| Web pages | Approvals (/approvals), where pending requests wait for you, and Settings (/settings), where you connect services and manage rules, the privacy filter, passkeys and the audit log. |
Your browser, at http://localhost:8765. |
| MCP endpoint | /mcp on the same port, speaking MCP over Streamable HTTP. Every AI system reaches PrivacyFence through it. |
Your AI system's connector list. |
| Claude Desktop extension | PrivacyFence.mcpb, a small Node.js shim Claude Desktop runs. It connects Claude Desktop to /mcp and reads and writes local files on the daemon's behalf. |
Claude Desktop's extensions. |
The companion menu (macOS and Windows) has a status line, Open Approvals, Open Settings, one of Start PrivacyFence… / Restart PrivacyFence… / Stop PrivacyFence… (depending on whether the service is running), Service Details…, Copy AI Client Token, New Recovery Code… and Quit Companion. Quitting the companion leaves the daemon running.
On a packaged install the daemon runs under its own service account, so the AI system (which runs as you) cannot read PrivacyFence's credentials, rules or audit log, or approve its own requests. The layout and the reasons are in Security and compliance. Where each file lives on each platform is in Platform support.
Opening PrivacyFence opens Approvals¶
Clicking PrivacyFence itself (the app in /Applications on macOS, the Start Menu entry on Windows,
the applications-menu entry on Linux) opens Approvals through the companion on every platform:
- If a companion is running, it asks you to allow opening the page, then opens it.
- If no companion is running (macOS and Windows), the click starts the tray or menu-bar icon and opens Approvals from it, with no extra dialog.
Settings is one click away from the page, or from Open Settings in the companion. See ADR 0031.
If the companion cannot be reached (an SSH session, a tray icon that failed to start), run
privacyfence-app --print-sign-in-link in your own terminal. It prints a one-time link to
Approvals. If the companion is running, it asks you to confirm first and the link can approve;
if nothing confirms it, the link is view-only: it shows what is pending but cannot release
anything. The link is used up by its first visit.
Local mode and organization mode¶
- Local mode (desktop mode) is what the macOS, Windows and Linux installers set up; it is
deprecated in PrivacyFence 7 and removed in 8
(Moving to organization mode). One daemon on your own computer serves you. The web pages and
/mcplisten onlocalhostonly, so an AI system on another machine, including claude.ai, cannot reach it. - Organization mode is one Linux server that serves many people. People sign in through the
organization's identity provider, AI systems authorize through PrivacyFence's own OAuth 2.1
server, and each person's connectors, rules and approvals are kept separate. claude.ai can reach
PrivacyFence only this way. See Organization deployment. It runs from
the
ghcr.io/privacyfence/privacyfence-orgDocker image configured by environment variables (Organization deployment with Docker), or from PyPI under systemd. - Single-user mode is organization mode with no identity provider: one administrator, served only
on
localhost, who signs in with a passkey after a one-time setup code. It is how the Docker image starts on your own computer (Run it on this computer). It is no protection against an agent that can rundockeron that computer (Single-user mode).
Which mode a daemon runs in is set by PRIVACYFENCE_MODE (local by default, org in the Docker
image), or by a legacy organization config bundle's mode key. See
Configuration reference.
How an AI system connects¶
There are two ways in, and each AI client has its own setup page:
- Local mode. Claude Desktop uses PrivacyFence's extension,
PrivacyFence.mcpb, which the macOS DMG and the Windows installer ship. The extension (the shim) waits for the daemon, asks it for your MCP token over the local control channel (theMINT MCPcommand), reads the/mcpURL from the handoff directory'smcp_urlfile, relays MCP messages, and reads and writes local files for the tools that need them (see Files). Any other client that speaks MCP over Streamable HTTP, Claude Code among them, connects to/mcpdirectly with a bearer token that--print-mcp-tokenmints through the sameMINT MCPrequest. The companion's Copy AI Client Token menu item, and Copy token on the Settings page's AI clients section, put the same token on your clipboard instead; the Settings page asks the companion to do it, so the token is never in a web response (ADR 0104). There is one token per OS account, with its own connectors, rules and approvals (ADR 0008), and the control channel answers only the accounts the install serves. - Organization mode. There is no local token and no extension. The client discovers
PrivacyFence's authorization server from
/mcp, registers itself as an OAuth client, you sign in through the organization's identity provider in the browser, and the client receives its own access token.
| AI client | Local mode | Organization mode | Setup |
|---|---|---|---|
| Claude Desktop | the extension | custom connector | Connect Claude Desktop |
| Claude Code | /mcp with a bearer token |
OAuth sign-in | Connect Claude Code |
| ChatGPT | ChatGPT desktop, /mcp with a bearer token (checked on macOS) |
Developer Mode app with OAuth sign-in | Connect ChatGPT |
| claude.ai | not reachable | custom connector | Connect claude.ai |
| Gemini Enterprise | not reachable | custom MCP server data store | Connect Gemini Enterprise |
Any other MCP client that supports Streamable HTTP connects the way Claude Code does: with the same URL and bearer token in local mode, or with OAuth 2.1 and dynamic client registration in organization mode.
Scoped tokens¶
Instead of its own credential, a client can hold a scoped token created in Settings: a named bearer token with an expiry, its own rules and a list of tools. It sees only those tools, and a call it makes is auto-accepted only through one of its own rules; otherwise it is denied at once, with no approval card. A box on the token lets covered Gmail, Slack and Drive file content reads return personal data, which the scan still reports in the audit log. Revoking it affects no other client. In organization mode the administrator must turn the feature on. Claude can also propose a token for you to review and create (ADR 0250). See Scoped tokens.
What the AI system is told¶
When a client connects, PrivacyFence's MCP initialize answer includes short instructions: what
PrivacyFence is, that a tool list with no connector tools means the install is not set up yet (not
that PrivacyFence is irrelevant), and to call privacyfence_status before the first
PrivacyFence-governed action. It also advertises tool-list change notifications: when you connect,
disable or re-enable a service in Settings, connected clients are told to refresh their tool list.
The tool list holds one tool per enabled, signed-in connector tool, plus the privacyfence_*
tools below: eight always, and privacyfence_propose_scoped_token only where token proposals are on (always on a local install, by the administrator's choice in organization mode). Each connector tool carries MCP annotations that say what it does: a read is
read-only and idempotent; a write is neither. Only the three tools that delete something,
calendar_delete_event (an event), drive_sheets_delete_dimensions (rows or columns of a
spreadsheet) and peek_delete_page (a Peek page with its comments and visit history), are marked
destructive; an overwrite is a write, not a deletion.
These annotations are always true. PrivacyFence is a privacy tool and does not tell an AI system anything false, so there is no setting, header or extension that advertises a write as read-only.
Annotations are hints for the client's own interface, not a security boundary: the gate is what decides. A client may use them to ask for its own confirmation before a write, in front of PrivacyFence's approval. If you do not want that second question, tell the client to always allow PrivacyFence's tools; Claude Desktop and Claude Code both offer it (see Connect Claude Desktop and Connect Claude Code). Nothing is less protected when you do: every call still goes through PrivacyFence's gate, and PrivacyFence's approval card is the confirmation that decides.
The reasoning is in ADR 0086 and ADR 0089.
What happens on a tool call¶
Every connector tool has a gate, fixed in code:
auto: runs straight away and is recorded in the audit log.review(reads) andpopup(writes): run straight away only if an auto-accept rule covers the call. Otherwise PrivacyFence creates a pending approval, shown as a card on Approvals and, if you allow it, as a browser notification.
The Tools reference lists every tool's gate. A gated call waits up to
30 seconds (web.approvals.hold_window_seconds) for you to decide. If you decide in time, the
call returns its result as usual. If not, it returns straight away with:
{"status": "approval_pending", "approval_id": "…", "url": "http://localhost:8765/approvals/…",
"expires_at": "…", "pending_count": 1, "binder_url": null, "message": "…"}
The message tells the AI system to send you the url rather than wait silently. Once a request
from you is already pending, later gated calls return this result immediately instead of each
waiting 30 seconds (web.approvals.adaptive_hold). When more than one is pending, binder_url
points at a page where you can decide them together.
After you approve, the AI system repeats the original call with the same arguments and gets the
result; if an argument differs or the content the card showed has changed, the repeat gets a new card. While the approval is pending, the call does nothing: no data is returned, no file is
handed out and no change is made. A pending approval nobody decides expires after 15 minutes
(web.approvals.pending_ttl_seconds).
In the audit log, the approval_pending row, the row for your decision and, for a download, the
row for the file being fetched share one request_id. A decision the AI system never came back for
is logged as expired with what you decided and when (expired_decision, decided_at), so it
reads differently from a card nobody answered
(ADR 0093). The limits are in the
Configuration reference; cards, the PII check, passkey step-up and
rules are in Approvals and policy.
PrivacyFence's own tools¶
PrivacyFence adds eight tools of its own, plus privacyfence_propose_scoped_token where token proposals are on (always on a local install, by the administrator's choice in organization mode). They are not connector tools and have no gate. Unlike
connector tools, each carries its real MCP annotations.
| Tool | What it does | Required arguments | Annotations |
|---|---|---|---|
privacyfence_status |
Reports the mode, whether setup is complete, and each connector's state (enabled, authenticated, blocked_by: no_org_config, not_authenticated or a short reason), plus what to tell you next. It never returns a sign-in link: in local mode it tells the AI system to send you to the companion's Open Settings; in organization mode, to your administrator. |
reason |
read-only, idempotent |
privacyfence_check_policy |
Predicts whether a specific call would auto-accept (auto_accept), needs a human (requires_review), or depends on content it cannot see yet (unknown), and names the rule that would match. When a call would not auto-accept, suggested_rules lists the rules that would cover it (scope, value and verb), derived from the call's own arguments; a value of null means it is whatever the call reaches at run time. A value of [] means the rule takes none (the Slack rules for mentions of you, your conversations and one-to-one DMs), and requires_args names arguments the call must also pass (member_only for a search under the conversations rule); Slack is not asked. Scoped tokens get it too. Makes no call and has no side effects. pii_gate_may_apply is true for review tools. pii_gate says whether the PII scan of real content can still stop the call: none (it never reaches the scan), allowed_by_token (the caller's scoped token lifts it), allowed_unless_inline (a Drive download, lifted unless the file comes back inside the result) or applies; pii_gate_detail says why. |
connector, tool, reason (plus args) |
read-only, idempotent |
privacyfence_list_policy |
Lists every auto-accept rule (id, a readable sentence, verbs, the tools it covers) and the scope catalogue that privacyfence_propose_policy_change accepts. |
reason |
read-only, idempotent |
privacyfence_propose_policy_change |
Proposes adding, updating or removing a rule. Always opens a confirmation on Approvals that you must accept, and needs the same passkey step-up as changing a rule in Settings. Refused in an unattended session. | operation (add, update, remove), reason |
not read-only, non-destructive, idempotent |
privacyfence_await_approval |
Waits for one or more pending approvals and reports each one's status (pending, approved, denied, expired, unknown), never content; a denial's intent and note, when you gave them, come under denial_feedback (see When you deny a call). Waits at most 120 seconds per call (default 30). |
approval_ids |
read-only, idempotent |
privacyfence_begin_unattended_session |
Declares this connection a scheduled run with nobody watching. See Unattended sessions. | reason |
not read-only, non-destructive, idempotent |
privacyfence_end_unattended_session |
Clears that declaration for this connection. | reason |
not read-only, non-destructive, idempotent |
privacyfence_propose_scoped_token |
Proposes a scoped token for a routine, built from the calls approved in this session (from_session) or from rules and tools it names. Returns a review link at once and nothing else: you review and can only narrow it in Settings → Scoped tokens, confirm by typing its name, and copy the token yourself. Claude never receives the token or learns whether it was created. Not offered to a scoped token, refused in an unattended session. See ADR 0250. |
reason, label |
not read-only, non-destructive, not idempotent |
privacyfence_create_upload_slot |
Gives a client without the extension a one-time URL to upload a local file to. See Files. | filename, reason (plus size_bytes) |
not read-only, non-destructive, not idempotent |
reason is one sentence from the AI system on why it is making the call. It is logged, and it is
self-reported and unverified, exactly like the reason every gated connector tool takes.
Files¶
Some tools read or write files on your computer: drive_upload_file (local_path),
drive_download_file (destination_dir), the gmail_*_with_attachments tools, and the Gmail and
Confluence attachment downloads. On a packaged install the daemon runs as its own account and
cannot open files in your home folder, so the file travels a different way depending on the
client (ADR 0007,
ADR 0028):
- Claude Desktop with the extension. The shim reads or writes the file as you and passes the bytes to or from the daemon.
- Any other client. For an upload, the AI system calls
privacyfence_create_upload_slot, thenPUTs the file to the returned URL (noAuthorizationheader: the URL is the one-time credential; up to 50 MB, valid for 10 minutes, single use), then passes the returnedupload_idto the tool (drive_upload_file'supload_id, or"upload:<upload_id>"inattachments). For a download, the tool returns a one-time link instead of writing todestination_dir. - Exports. The export tools (
grist_export_records,grist_export_sql,salesforce_export_report,jira_export_issues) write a file todestination_dirand return only counts and a hash. On an organization-managed install nothing is saved: the result carries a one-time download link instead, unless the organization turned offdownload_delivery.allow_disk_staging; an organization can also turn the export tools off entirely withdownload_delivery.allow_exports: false(ADR 0197) (ADR 0160). -
An install that is not privilege-separated (a source checkout, a
pip/pipxinstall): the daemon runs as you and reads and writes the file itself. -
Organization mode. Small downloads come back inside the tool result, base64-encoded; larger ones become a short-lived, one-time link. By default "small" means the whole tool result fits 100,000 bytes, a file of about 75 KB, because claude.ai and Claude Desktop truncate a tool result over about 150,000 characters (ADR 0092). A link only works for a client that can reach the server: claude.ai needs the host on its domain allowlist (ADR 0097). The limits are organization bundle settings (
download_delivery.*, see Configuration reference).
In local mode a downloaded file is held in the daemon's memory before it is handed over, so every
local-mode download is limited to 200 MB by default (file_bridge.max_download_bytes).
Uploading or creating a slot approves nothing: the card for the tool that uses the file is where you decide.
Which AI system is asking¶
Several AI systems can share one PrivacyFence. Each card and each audit-log entry names the AI
system that made the request, and says how far that name can be trusted
(ADR 0035,
ADR 0224).
The audit log records four fields: agent_id, agent_name, agent_version and agent_source.
agent_source |
Trust | Recorded when |
|---|---|---|
oauth_client |
attested | Organization mode only: the OAuth client registered only with a vendor's own redirect URI (claude.ai, ChatGPT, Gemini Enterprise), so its tokens were obtained by that vendor. No administrator step; see AI systems. This is the only attested source. |
client_info |
claimed | The name the client itself sent: the MCP handshake's clientInfo.name, an OAuth client's registered name, or a local agent_overrides relabel. |
"" (empty) |
unknown | No usable name. Recorded as unknown, never guessed. |
A call made with a scoped token is recorded as the
agent scoped-token:<id>, named after the token, with source override (attested). The value
endpoint is reserved and never recorded.
- On a local install every card, approvals-list row and Audit Log row shows the caller as Undetected, with no name and no badge, because nothing there can tell AI systems apart (ADR 0088). The fields above are still recorded.
- In organization mode a card calls a claimed name exactly that: the header says the caller says it is that system and marks it Not verified, and the rest of the card says "the AI system" (ADR 0036).
agent_overridesinsettings.yamlmaps a client name PrivacyFence does not recognise to a known AI system. It changes only the recorded name, not what the card shows, and is always recorded asclient_info, because any program on your computer can send that name (ADR 0037).- Attribution is reporting only. The same call gets the same decision, the same rule match and the same data whatever name the client gives.
privacyfence_*tool calls are not attributed; their audit entries leave the four fields empty.
Unattended sessions¶
A scheduled run (for example a routine that fires overnight) has nobody to answer a card. The AI
system can call privacyfence_begin_unattended_session at the start of such a run. For the rest of
that connection:
- a gated call that no auto-accept rule covers is denied immediately instead of waiting, and
audited as
denied_unattended, so "no human was asked" is never confused with "a human said no"; - so is a call a rule covers but the PII check would still route to a human;
- everything that auto-accepts still auto-accepts; the flag never widens what runs;
privacyfence_propose_policy_changeis refused.
The flag ends with privacyfence_end_unattended_session or when the connection closes. The tool
works only when the administrator turns it on (the unattended-sessions switch under Admin → Features;
it is stored as unattended_sessions.enabled in org/admin_config.json, and a change needs a restart);
otherwise it returns an error. The flag is
advisory: it changes what happens when nothing authorizes a call, never what is authorized
(ADR 0015). Pair it with
privacyfence_check_policy to plan which steps can run unattended. A scheduled data sync that needs no AI model at all is run by privacyfence-sync.