Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Security and audit

How a server decides who is calling, what they may do, and what it records. Report vulnerabilities as described in the security policy; the security model is there too.

Warning

A server without tokens, single sign-on or platform tokens accepts every caller. It refuses to listen beyond 127.0.0.1 in that state unless you pass --allow-anonymous; never do that on a network you don’t control.

Authentication and permissions

Without --tokens, --oidc-issuer or platform tokens the server accepts everyone, and so it listens only on a loopback address unless --allow-anonymous (PANDO_ALLOW_ANONYMOUS=1) says otherwise. In production, set at least one. With OpenID Connect, the claim named by --oidc-claim (for example email) becomes the principal that access rules refer to.

Every --oidc-require CLAIM=VALUE (repeatable, or comma-separated in PANDO_OIDC_REQUIRE) must match the token exactly; booleans and numbers compare as written (email_verified=true). Issuers that sign tokens for anyone refuse to start without a rule that binds your tenant: GitHub Actions (https://token.actions.githubusercontent.com) needs repository_owner, repository_owner_id, repository, repository_id, enterprise or enterprise_id; Google (https://accounts.google.com) needs hd. For other providers, pin the tenant the same way (tid=…, org_id=…) when the issuer serves more than your organisation.

pandod server --oidc-issuer https://token.actions.githubusercontent.com --oidc-audience pando \
  --oidc-require repository_owner=acme ...

A token is refused when its principal is a --platform-admins login (unless that login is also given with --oidc-admin), or starts with agent/ or bot/, so an identity provider that lets people choose their name can’t make anyone an administrator or an agent. Unknown signing keys refresh the key set at most once a minute.

Access rules grant read and write per directory and per branch. Principals who can read only part of the repository get path-scoped checkouts, search and history, with changes outside their scope removed. --acl FILE (or PANDO_ACL) names a JSON file of rules:

{
  "default":    { "write": ["*"], "refs": ["user/*"] },
  "principals": {
    "release-bot": { "refs": ["main", "release/*"] },
    "api-team":    { "write": ["services/api", "libs/api-client"], "refs": ["main"] }
  }
}

A principal’s grants are the union of default and its own entry. read and write list path prefixes (* is everything); refs lists branch names or prefix/*. When no entry mentions read, everyone may read everything.

Audit log

--audit-log FILE (or PANDO_AUDIT_LOG) appends one JSON line per event: who read or changed what, from which address, with what result. It is off by default because it grows with every request. Events go to one file per UTC day, FILE.YYYY-MM-DD; files older than --audit-retention-days (default 365) are deleted. Behind a reverse proxy on the same host, add --trust-proxy so the address recorded is the client’s, not the proxy’s.

Retention for hosted services. Keep at least 365 days. In India, CERT-In’s 2022 directions require 180 days of logs kept within India, and the DPDP Rules require one year from May 2027; other jurisdictions have their own rules, so check yours. Copy the daily files to durable storage in the required region (the same backup job as the databases works) rather than relying on one disk.

Clocks. Timestamps are UTC (RFC 3339) from the host clock, so keep it synchronised: run systemd-timesyncd or chrony against reliable servers. CERT-In asks Indian service providers to synchronise with the NIC (samay1.nic.in, samay2.nic.in) or NPL (time.nplindia.org) NTP servers; elsewhere use your cloud’s time service or pool.ntp.org.

{"timestamp":"2026-10-16T09:06:02.695Z","kind":"land","principal":"alice","ref":"main","changeset":"0844d2dc…","landed":"0844d2dc…","rebased":false}
{"timestamp":"2026-10-16T09:06:02.702Z","kind":"read_file","principal":"ci","rev":"main","path":"README"}
{"timestamp":"2026-10-16T09:06:02.702Z","kind":"rpc","principal":"ci","peer":"10.0.4.17","method":"/pando.v1.Browse/ReadFile","status":"0","ms":0}
{"timestamp":"2026-10-16T09:06:58.112Z","kind":"auth_failed","peer":"10.0.9.2","presented":true,"reason":"invalid token"}
kindRecorded for
rpcevery authenticated request: principal, client address, method, gRPC status, duration
auth_failedevery rejected request: client address, whether a token was presented, why (never the token)
landeach land: branch, changeset, and the landed id or the refusal
set_refbranch moves through the refs API
read_file, list_dir, checkout, findcontent reads, with revision and paths
log, blamehistory queries, with revision and path
lock, unlockfile locks taken and released
account_created, sign_in, sign_up, accept_invitation, create_token, …changes to accounts, tokens, organisations, access, reviews and webhooks (platform servers), with a target

Audit events never appear in the normal log, and the normal log never appears in the audit file.