These pages describe PrivacyFence 4.6.1. Download it · What changed
Configuration reference¶
PrivacyFence reads two configuration files:
settings.yaml: your policy and the daemon's own settings. It carries no secrets. Most of it is edited from PrivacyFence Settings; this page lists every key, including the ones with no control there.org_config.json: the organization config bundle. It holds the app credentials for each connected service and, for an organization deployment, the server and identity-provider settings. An administrator builds it withscripts/build_org_bundle.pyand people install it from Settings > General > Organization Configuration (or it is placed on the server by hand).
Where both files live on each platform is in Platform support. Both are owned by PrivacyFence's service account on a packaged install, so editing either by hand needs administrator rights.
The command-line options of privacyfence-app are at the end of this page
(Command-line options).
How settings.yaml is read¶
- First start. If
settings.yamldoes not exist, the daemon creates it from the packaged example (src/privacyfence/resources/settings.yaml.example). The Seeded column below is the value that example writes. - Missing keys. A key that is absent (or commented out) takes the Code default. Where the two columns differ, a fresh install behaves as Seeded and an install whose file lacks the key behaves as Code default.
- When changes apply. A change made from Settings applies straight away. A hand edit to the file applies at the next daemon start.
- Validation. An invalid privacy-filter value, step-up
scopeorbatch, or an unreadable file stops the daemon at start with a configuration error naming the key. Auto-accept rules it cannot read are dropped, never treated as a match. - Organization mode. The server's own
settings.yamlis install-wide:privacy, the other*_privacygroups,pii_detection,loggingandweb.approvalsapply to everyone, andweb.mcp.enabledmust betrue. Each person also gets asettings.yamlof their own, of which only theauto_acceptsection is used. Keys marked local mode only have no effect on an organization server.
Earlier policy sections¶
A settings.yaml that contains an auto_accept_rules or auto_accept_grants section (even an
empty one) is refused at start with a configuration error naming the section. Rules in those
sections are not converted: remove the section and recreate the rules on the Auto-accept page.
If the same file also carries the top-level marker migrated_to_policy_v2: true, its rules are
already in the auto_accept section. The daemon then removes auto_accept_rules,
auto_accept_grants and the marker, rewrites the file, logs a warning naming what it removed, and
starts. If the rewrite fails, it starts anyway and tries again next time. See
ADR 0041 and
ADR 0047.
settings.yaml keys¶
Privacy filter¶
Six groups, one per kind of data, each with a default_policy and per-category values. Each value
is allow (pass unchanged), redact (replace with a placeholder that gives only the length; for a
list-shaped category, same as block) or block (replace with [BLOCKED BY PRIVACY FILTER]). The
filter applies before you see the data on a card, and it is a minimum: approvals still apply. Edited
from Settings > Privacy Filter. Details: Approvals and policy.
Code defaults: a category that is not listed takes its group's default_policy. A group that is
missing altogether is allow for everything in local mode and block for everything on an
organization server.
| Key | Seeded | Covers |
|---|---|---|
privacy.default_policy |
block |
Gmail: any category not listed below |
privacy.categories.body |
allow |
Message body text |
privacy.categories.metadata |
allow |
Sender, recipients, date, subject |
privacy.categories.attachments |
block |
Attachment metadata (downloading content is a separate, gated tool) |
privacy.categories.thread_history |
allow |
Earlier messages in a thread |
drive_privacy.default_policy |
block |
Drive, Sheets and Docs: any category not listed below |
drive_privacy.categories.file_content |
allow |
Document text or bytes |
drive_privacy.categories.file_metadata |
allow |
Name, owners, times, sharing, from drive_get_file_metadata |
drive_privacy.categories.file_list |
allow |
Names and ids in list and search results |
drive_privacy.categories.folder_structure |
allow |
Folder listings |
slack_privacy.default_policy |
block |
Slack: any category not listed below |
slack_privacy.categories.message_content |
allow |
Message text |
slack_privacy.categories.user_identity |
allow |
User names, emails, real names |
slack_privacy.categories.channel_list |
allow |
Channel names and metadata |
slack_privacy.categories.thread_content |
allow |
Thread replies |
slack_privacy.categories.dm_list |
allow |
Who you have one-to-one DMs with |
slack_privacy.categories.group_chat_list |
allow |
Group DM listings (id, name, participants) |
contacts_privacy.default_policy |
block |
Contacts: any category not listed below |
contacts_privacy.categories.notes |
block |
The free-text notes (biography) field. Name, email, phone, organization and job title always pass. |
tasks_privacy.default_policy |
block |
Tasks: any category not listed below |
tasks_privacy.categories.notes |
block |
A task's free-text notes. Title, due date and status always pass. |
confluence_privacy.default_policy |
block |
Confluence: any category not listed below |
confluence_privacy.categories.search_excerpt |
block |
The page excerpt confluence_search and confluence_cql_search return with each match |
confluence_privacy.categories.attachments |
block |
Attachment metadata |
file_metadata and file_list overlap (both can show a file's name and owners). If one is allow
and the other is not, the daemon logs a warning at start.
Connector behaviour¶
| Key | Type | Code default | Seeded | What it does |
|---|---|---|---|---|
calendar.free_busy_full_event_details |
bool | true |
true |
calendar_get_free_busy returns event titles, times and status for a colleague when your account can already see them (busy/free only otherwise). false always returns busy/free only. Settings > Privacy Filter. |
gmail.append_signature_to_drafts |
bool | false |
false |
Append your Gmail signature (the one Gmail stores for the sending address) to drafts. Each draft tool's include_signature argument overrides it per call. The signature is shown on the card. Settings > Privacy Filter. |
connectors.<name>.enabled |
bool | true |
not set | false turns a connector off. <name> is one of gmail, drive, calendar, contacts, tasks, apps_script, slack, salesforce, jira, confluence, telegram. Written by Disable/Enable on Settings > Connectors. A connector also needs its organization config and a sign-in before it runs. |
PII detection¶
The PII check scans content shown on a card for likely personal data (bank account numbers, card numbers, national ID and tax numbers and similar, in English, German and Hungarian; email addresses and phone numbers are not detected) and asks for a second confirmation when it finds some. See PII detection keywords and Approvals and policy.
| Key | Type | Code default | Seeded | What it does |
|---|---|---|---|---|
pii_detection.enabled |
bool | true |
true |
Turns the PII check on. Settings > General > PII Detection Gate. |
pii_detection.detect_ip_addresses |
bool | true |
true |
Also flag IP addresses. Same card. |
pii_detection.detect_financial_figures |
bool | true |
true |
Also flag currency amounts. Same card. |
pii_detection.audit_match_details |
bool | false |
false |
Record the matched text in the audit log's PII match details column: redacted for value-bearing categories (bank and card numbers, IDs) on an approved request, a fixed "details hidden" text on a denied one. For a bounded trial only; no Settings control; needs a restart. |
Auto-accept rules¶
The auto_accept section holds the rules that let a gated call run without a card. It is written
by the Always allow button, Settings > Auto-accept and privacyfence_propose_policy_change,
which all validate the rule and compute its id; prefer them to hand-editing. The scopes, verbs and
conditions a rule can use are in Approvals and policy.
| Key | Type | Code default | Seeded | What it is |
|---|---|---|---|---|
auto_accept.version |
int | — | 2 |
Schema version. Always 2. |
auto_accept.rules |
list | empty (no rules) | one rule, below | The rules. A call auto-accepts if any rule matches. |
auto_accept.rules[].id |
string | — | r-d76028ec0b |
Stable id computed from the rule's predicate, value and conditions. |
auto_accept.rules[].predicate |
string | — | always_allow |
The scope selector. |
auto_accept.rules[].value |
any | — | null |
The resource ids or names the scope matches, or null for a scope without a value. |
auto_accept.rules[].operations |
list | — | [contacts.edit] |
Internal operation keys the rule covers. |
auto_accept.rules[].conditions |
list | [] |
[[no_contact_info_change, null]] |
[name, value] pairs that must all hold. |
The seeded rule auto-accepts contact edits that change no email address or phone number. A missing
or empty auto_accept section means nothing auto-accepts.
Logging and updates¶
| Key | Type | Code default | Seeded | What it does |
|---|---|---|---|---|
logging.level |
string | INFO |
INFO |
DEBUG, INFO, WARNING, ERROR or CRITICAL. Settings > Audit Log, applied immediately. |
logging.file |
string | logs/privacyfence.log |
logs/privacyfence.log |
Daemon log file. A relative path is resolved against PrivacyFence's data directory. |
update_check.enabled |
bool | true |
true |
Once a day, check GitHub Releases for a newer version and show a banner. Never downloads or installs anything; a network failure is logged, not shown. Settings > General > Check for Updates. Local mode only. |
update_check.include_beta |
bool | false |
false |
Also offer pre-releases. Same card. |
Web server and MCP (local mode only unless noted)¶
| Key | Type | Code default | Seeded | What it does |
|---|---|---|---|---|
web.port |
int | 8765 |
8765 |
Port of the one local server that carries Approvals, Settings and /mcp. It listens on localhost only. An organization server takes its port from the bundle's server.port. |
web.mcp.enabled |
bool | false |
true |
Serve /mcp. With it off, no AI system can reach PrivacyFence at all. Must be true on an organization server. |
web.settings.enabled |
bool | false |
true |
Serve Settings (/settings). With it off, the only way to change anything is to edit the files by hand. |
web.settings.allow_quit |
bool | true |
true |
Whether Quit PrivacyFence on Settings > About works. It always asks for confirmation first. |
web.notifications.enabled |
bool | true |
true |
Browser notifications for a new pending approval while the Approvals tab is open but not focused. Nothing is sent through a push service. |
web.notifications.detail |
string | minimal |
standard |
What a notification about a single approval may say: minimal ("1 approval pending"), standard (adds connector, tool and direction), detailed (adds the card's title line, which can contain gated content, e.g. a recipient). A notification about several approvals always uses minimal. Settings > General > Approval Notifications. |
web.approvals.hold_window_seconds |
number | 30 |
not set | How long a gated call waits for your decision before returning approval_pending. Local and organization mode. |
web.approvals.pending_ttl_seconds |
number | 900 (15 min) |
not set | How long an undecided approval stays pending before it expires. Local and organization mode. |
web.approvals.ledger_ttl_seconds |
number | 300 (5 min) |
not set | How long a decision stays available for the AI system to repeat its call and collect it. A decided write is released once. Local and organization mode. |
web.approvals.max_pending |
int | 50 |
not set | Most approvals pending at once, across everyone. Local and organization mode. |
web.approvals.max_pending_per_principal |
int | 20 |
not set | Most approvals pending at once for one person. Local and organization mode. |
web.approvals.adaptive_hold |
bool | true |
not set | Once one of your approvals is pending, return approval_pending for later gated calls straight away instead of waiting hold_window_seconds each. Local and organization mode. |
Local file transfers¶
| Key | Type | Code default | Seeded | What it does |
|---|---|---|---|---|
file_bridge.max_download_bytes |
int | 200000000 (200 MB) |
not set (section present, key commented out) | Largest file a download tool delivers in local mode. The file is held in memory before it is handed over, so raise it only with memory to spare. See How PrivacyFence works. |
Passkey step-up (local mode)¶
Step-up asks for a fresh passkey (Face ID, Touch ID, Windows Hello) before an approving decision is
released, and before a sensitive settings change (rules, grants, privacy policy, PII settings).
Denying never needs one. Passkeys are enrolled at /security, linked from
Settings > General > Security > Manage passkeys. An organization server reads the bundle's
step_up section instead (below). Details: Approvals and policy and
Security and compliance.
| Key | Type | Code default | Seeded | What it does |
|---|---|---|---|---|
step_up.enabled |
bool | true on a packaged install, false otherwise |
not set (commented out) | Require a passkey at decide time for the approvals scope covers. With nothing enrolled and require_passkey off, decisions go through without one. |
step_up.scope |
string | writes_and_pii_reads |
not set (commented out) | writes (write approvals), writes_and_pii_reads (also reads the PII check flagged), or writes_and_reads (every gated read too). An auto-accepted read never asks. Needs a restart. |
step_up.require_passkey |
bool | true on a packaged install, false otherwise |
not set (commented out) | Make the passkey mandatory: with nothing enrolled, approving decisions and sensitive settings changes are refused (a banner points to /security) rather than released. The daemon still starts. Setting it to true on an install that is not privilege-separated stops the daemon at start. |
step_up.rp_id |
string | localhost |
localhost |
WebAuthn relying-party id. Leave as is. |
step_up.rp_name |
string | PrivacyFence |
PrivacyFence |
Name shown in your system's passkey prompt. |
step_up.batch |
string | single_assertion |
single_assertion |
single_assertion: one passkey prompt releases a whole selected batch on Approvals, bound to exactly that selection. per_item: a batch containing anything that needs step-up is refused as a whole, and each item is decided from its own card. |
"Packaged install" means the macOS .pkg, the Windows installer or the .deb, which are
privilege-separated. A source checkout or a pip/pipx install defaults both keys off. A value
written in the file always wins over these defaults, in either direction.
The Turn on button. Once at least one passkey is enrolled, Settings > General > Security
shows Step-up for approvals with a Turn on button. It writes step_up.enabled: true and
step_up.require_passkey: true together and takes effect immediately, with no restart. There is
no control to turn step-up off: set step_up.require_passkey (and step_up.enabled) to false in
the file and restart. Changing scope or batch also needs a file edit and a restart. The button
is not shown on an organization server.
AI system names¶
| Key | Type | Code default | Seeded | What it does |
|---|---|---|---|---|
agent_overrides.<client name> |
string | none | not set (commented out) | Label an MCP client whose handshake name PrivacyFence does not recognise as a known AI system. The value is an AI system id such as claude-code or claude (the ids the Audit Log shows); an unknown id is ignored with a warning. It relabels only: the entry is still recorded as claimed (client_info, Not verified). Local mode only; needs a restart. See How PrivacyFence works. |
Keys the daemon does not read¶
| Key | Seeded | Note |
|---|---|---|
policy.engine |
v1 |
Present in the seeded file; ignored. Every call is evaluated against the auto_accept section. |
Organization config bundle (org_config.json)¶
Build the bundle with scripts/build_org_bundle.py (Python standard library only; --sign-key
and --generate-signing-key also need the cryptography package). Pass only the options for the
services you have registered: a connector is offered only if its section is in the bundle. Install
it from Settings > General > Organization Configuration. Per-service registration steps are in
Google Cloud setup, Slack setup,
Salesforce setup and Atlassian setup; Telegram needs no
bundle entry (Telegram setup). The server side of an organization deployment is
in Organization deployment.
How the daemon treats the file:
- Absent: local mode, and every connector that needs organization credentials is unavailable.
- Unreadable, not valid JSON, or not a JSON object: the daemon refuses to start.
- Signed: the first signed bundle an install reads pins its signing key; from then on every
bundle, unsigned ones included, must verify against that key or the daemon refuses to start. To
accept a new key, an administrator deletes the pinned
org_config_signing_pubkey.txtin the organization config directory. "mode": "org": the bundle must be signed.
Build options¶
| Option | Default | Writes | Notes |
|---|---|---|---|
--org-name NAME |
none | org_name |
Shown to people after they install the bundle. |
-o, --output PATH |
org_config.json |
Written with mode 0600. |
|
--merge |
off | Update the bundle already at --output instead of starting from empty. Any existing signature is dropped; pass --sign-key again. |
|
--google-client-secret PATH |
none | google |
The OAuth client JSON downloaded from Google Cloud: a Desktop app client for local mode, a Web application client for organization mode. The inner block is stored as-is. Covers Gmail, Drive, Calendar, Contacts, Tasks and Apps Script. |
--slack-client-id, --slack-client-secret |
none | slack.client_id, slack.client_secret |
Give both or neither. |
--slack-scopes SCOPE … |
built-in list | slack.user_scopes |
Override the Slack user-token scopes. Usually left unset. |
--salesforce-consumer-key, --salesforce-consumer-secret |
none | salesforce.consumer_key, salesforce.consumer_secret |
Give both or neither. |
--salesforce-login-url URL |
https://login.salesforce.com |
salesforce.login_url |
https://test.salesforce.com for a sandbox. |
--atlassian-client-id, --atlassian-client-secret |
none | atlassian.client_id, atlassian.client_secret |
Jira and Confluence. Give both or neither. |
--enable-unattended-sessions / --disable-unattended-sessions |
not written (off) | unattended_sessions.enabled |
Allow privacyfence_begin_unattended_session. See How PrivacyFence works. Applies in both modes. |
--mode {local,org} |
not written (local) | mode |
org needs --server-issuer-url, --idp-issuer, --idp-client-id, --idp-client-secret and --sign-key. local removes every organization-only section from a merged bundle. |
--server-issuer-url URL |
none | server.issuer_url |
The server's public origin, e.g. https://pf.example.com. Must be an absolute http(s) URL with a host name. Register <issuer-url>/oauth/idp/callback and <issuer-url>/oauth/idp/login-callback with your identity provider. |
--server-bind-host HOST |
127.0.0.1 |
server.bind_host |
Loopback only, for a reverse proxy on the same host. Change it only when the proxy runs elsewhere. |
--server-port PORT |
8765 |
server.port |
|
--server-tls-cert PATH, --server-tls-key PATH |
none | server.tls.cert_file, server.tls.key_file |
Terminate TLS in PrivacyFence itself. Give both or neither; leave both unset when the proxy terminates TLS. |
--server-trusted-proxy IP |
none | server.trusted_proxies |
Repeatable. X-Forwarded-For/X-Forwarded-Proto are honoured only from these addresses. |
--idp-issuer URL |
none | idp.issuer |
OIDC issuer; <issuer>/.well-known/openid-configuration must be reachable. |
--idp-client-id ID, --idp-client-secret SECRET |
none | idp.client_id, idp.client_secret |
PrivacyFence's client registration at the identity provider. |
--idp-admin-group-claim CLAIM |
none (nobody is admin) | idp.admin_group_claim |
ID-token claim that marks administrators. |
--idp-admin-group-value VALUE |
none | idp.admin_group_values |
Repeatable. A value of that claim that makes someone an administrator. |
--idp-step-up-acr-value ACR |
none | idp.step_up_acr_values |
Repeatable. acr_values to request when step-up falls back to signing in again at the identity provider; without it, plain re-authentication. |
--authz-allowed-domain DOMAIN |
none (any domain) | authz.allowed_domains |
Repeatable. Admit only people whose email is at one of these domains. |
--authz-groups-claim CLAIM |
none | authz.groups_claim |
ID-token claim checked against --authz-required-group. |
--authz-required-group VALUE |
none (no group needed) | authz.required_groups |
Repeatable. Admit only people whose groups claim contains one of these. Needs --authz-groups-claim. |
--step-up-enabled / --step-up-disabled |
not written (off) | step_up.enabled |
Require a passkey, or a fresh sign-in at the identity provider, before an approving decision is released. |
--step-up-scope {writes,writes_and_pii_reads,writes_and_reads} |
not written (writes_and_pii_reads) |
step_up.scope |
Same meaning as in settings.yaml. |
--step-up-rp-id DOMAIN |
not written (the issuer URL's host name) | step_up.rp_id |
WebAuthn relying-party id. |
--step-up-rp-name NAME |
not written (PrivacyFence) |
step_up.rp_name |
Name in the passkey prompt. |
--step-up-require-passkey / --step-up-no-require-passkey |
not written (off) | step_up.require_passkey |
Refuse, rather than fall back to an identity-provider sign-in, when a person has no passkey enrolled. |
--downloads-inline-max-bytes BYTES |
not written (8000000, 8 MB) |
download_delivery.inline_max_bytes |
Downloads up to this size come back inside the tool result. 0 sends every download through a one-time link. |
--downloads-link-ttl-seconds SECONDS |
not written (300) |
download_delivery.link_ttl_seconds |
How long a one-time download link can be claimed. Must be above 0. |
--downloads-disable-staging |
not written (staging on) | download_delivery.allow_disk_staging: false |
Refuse a download too large to return inline instead of staging it (encrypted) for a link. |
--agent-links / --no-agent-links |
not written (on) | download_delivery.agent_links |
On: a staged download is a link the AI system can fetch itself (/mcp-files/fetch/…). Off: only a signed-in browser can fetch it (/downloads/…). |
--enable-audit-forwarding / --disable-audit-forwarding |
not written (off) | audit_forwarding.enabled |
Forward every audit entry to a syslog server or an HTTPS endpoint as well. The local audit log stays the authoritative record. |
--audit-forwarding-kind {syslog,http} |
not written (syslog) |
audit_forwarding.kind |
|
--audit-forwarding-syslog-host HOST |
none | audit_forwarding.syslog.host |
Required when forwarding to syslog. |
--audit-forwarding-syslog-port PORT |
not written (6514) |
audit_forwarding.syslog.port |
|
--audit-forwarding-syslog-protocol {udp,tcp} |
not written (tcp) |
audit_forwarding.syslog.protocol |
|
--audit-forwarding-http-url URL |
none | audit_forwarding.http.url |
Required when forwarding over HTTP. Must start with https://. |
--audit-forwarding-http-bearer-token-env VAR |
none | audit_forwarding.http.bearer_token_env |
Name of an environment variable the daemon reads a bearer token from when sending. The token itself is never in the bundle. |
--generate-signing-key PATH |
Write a new Ed25519 private key to PATH (mode 0600), print its public key, and exit without building a bundle. Keep the key; losing it means installs that pinned it cannot accept a new bundle until the pin is deleted by hand. |
||
--sign-key PATH |
none | signing_public_key, signature |
Sign the bundle. Required with --mode org; recommended otherwise. |
The organization-only options (--server-*, --idp-*, --authz-*, --step-up-*,
--downloads-*, --agent-links, --audit-forwarding-*) are refused unless the bundle is in
organization mode (--mode org, or --merge into a bundle that already has it).
Bundle keys not written by build_org_bundle.py¶
| Key | Written by | What it does |
|---|---|---|
version |
always 1 |
Bundle format version. |
generated_at |
build time, UTC | When the bundle was built. |
rooms, rooms_synced_at |
scripts/sync_room_directory.py |
The meeting-room list calendar_list_rooms returns, and when it was synced. Without it, calendar_list_rooms returns an empty list. |
step_up.batch |
hand edit | single_assertion (default) or per_item, as in settings.yaml. |
The Google, Slack, Salesforce and Atlassian redirect URIs to register for an organization deployment are listed in Organization deployment.
Command-line options¶
privacyfence-app is the daemon's executable. On a packaged install the service starts it; the
options below are for running it by hand.
| Option | What it does |
|---|---|
--config PATH |
Read settings.yaml from PATH instead of its default location. |
--print-sign-in-link |
Print a one-time sign-in link to Approvals and exit. See How PrivacyFence works. Local mode only. |
--print-mcp-token |
Print this OS account's MCP token (creating it the first time) and exit. See How PrivacyFence works. Local mode only. |
--gmail-oauth, --drive-oauth, --calendar-oauth, --contacts-oauth, --tasks-oauth, --apps-script-oauth |
Sign in to that Google service in the browser from the command line, save the token, and exit. |
--slack-oauth, --salesforce-oauth, --atlassian-oauth |
The same for Slack, Salesforce, and Jira plus Confluence. |
--telegram-setup |
Sign in to Telegram interactively (phone number and code) and exit. |
The sign-in options are for an install that is not privilege-separated (a source checkout or a
pip/pipx install). On a packaged install the daemon refuses to run as your account, so they
fail; connect services from Settings > Connectors instead (see
Connecting a service). They need the organization config bundle
installed first, except --telegram-setup.