Security whitepaper
This page summarises Porticol's threat model and data flows for a security review. It names what Porticol protects, where each boundary sits and the controls on it. Each control is held in place by a test in the repository. For a shorter overview, see the security page.
What is protected
- Request and response content: the collections, environments and history a person keeps, and the traffic Porticol sends and receives.
- Secret values: variables marked secret, tokens that auth schemes hold, keys in the keychain, and the server's encrypted columns and its installation key.
- Identity: sessions and refresh tokens, the server's audit chain, and the licence of a team server.
The desktop app
Signed out, with reports off, the only Porticol service the desktop app contacts on its own is the update server. Requests leave from your computer, not from Porticol's servers. Collections, environments and history are kept in one SQLite file in the data folder.
- The interface is contained. It renders untrusted response content, so it reaches the rest of the app through one command bridge, and its content security policy forbids it any network access or inline script of its own. It has no access to files. Response bodies are shown as text, never as markup. The end-to-end tests run the interface under the same policy and fail on any violation.
- Secrets stay in the keychain. Secret values are released only to send a request or open a session, and masked, longest first, in history, transcripts, session events, run reports and error messages. Scripts see secrets as opaque values. Where Linux has no keychain service, secrets are kept in a file encrypted with XChaCha20-Poly1305 under an Argon2id key from a passphrase.
- TLS is verified by default, and a failed verification names the certificate.
- Reports are off by default. Crash and usage reports are sent only after you turn them on, never hold request content, URLs, headers, variables or file paths, and can be previewed exactly as they would be sent.
Updates
One signing key signs every update. The app reads nothing in an update manifest before its signature verifies over the bytes it fetched, refuses a manifest meant for another channel, and never offers or installs an older version than the one running. Each file's signature, and the version it was signed for, is checked before anything is replaced. Updates come only from the update server the build names, over HTTPS.
The server
Porticol Cloud and a team server are the same server in two modes. They differ in configuration, not in code.
- Every route declares the permission it needs, and the server reads it from that declaration alone. A test fails when a route is public by accident.
- Organisations are kept apart by row-level security in PostgreSQL. A test calls every route as a member of one organisation with another's ids, and fails when a route has neither such a case nor a written reason for having none.
- Secret columns and backups are encrypted under the installation's key, which the deployment holds in a file of its own. A backup is checked after it is written.
- The audit log is chained by hash and checked for tampering. A test fails when a route neither writes an audit entry nor records why it doesn't.
- Sign-in is rate limited and locks out after repeated failures. Session cookies are HttpOnly. API tokens are shown once and kept as hashes.
- Logs hold no secrets. A test sends passwords and every kind of token through the server and finds none of them in its logs or audit log. The server sends no telemetry.
API tokens
A token holds exactly the scopes it was made with, checked before the operation runs. An organisation's token acts in the one workspace it was made for and answers not found for anything outside it. It reaches the published API and nothing else, whatever its scopes, and stops working once the person who made it no longer administers the organisation. See the REST API reference.
Shared secrets
Shared secret values are encrypted end to end. Each desktop has an X25519 key pair whose private half never leaves it. Each workspace has a random 256-bit data key, wrapped for every device and recovery key that may read it. Values are sealed with XChaCha20-Poly1305, bound to their workspace, environment, name and key version, so a value copied anywhere else doesn't open. The server stores public keys, wrapped keys and sealed values, and nothing it holds opens any of them. When a member leaves or a device is signed out, a remaining desktop replaces the key and seals every value again in one transaction. See Shared secrets.
Requests the server sends
The server sends requests of its own for monitors, runs started on the server, SIEM export and alerts, and, when a team server's operator turns it on, for the web client. Each goes through one guard:
- The host is resolved once, every address it resolves to is checked, and the connection goes to the address that was checked.
- Loopback, private, link-local and cloud metadata addresses are refused, unless a team server's operator allows private addresses.
- Redirects are never followed, and the size of an answer is capped.
- Alerts go only to destinations an administrator has allowed.
A customer's requests leave Porticol Cloud only in monitors and in runs started on the
server through the REST API or porticol-cli --remote. Scripts in them run with time
and memory limits.
The browser and the local agent
The web client is served with a content security policy that allows scripts only from its own origin. The local agent lets it send from a person's computer, and so answers only on loopback, only to a web client's origin, and only once the person at the computer has paired the browser with a single-use code. It keeps a hash of the browser's token, never the token, and serves only the commands that reach the network.
Identity
Organisations can require single sign-on over SAML or OpenID Connect for their verified domains and set a sign-in policy: a second factor, session limits, password length and allowed networks. Removing a person signs them out of every session and device. See Identity and access.
Recovery
Porticol Cloud takes an encrypted backup of its database every day, checks each one, and keeps copies away from the server. A team server takes encrypted backups on the schedule its operator sets and checks each one.
Supply chain
Dependencies are checked against security advisories on every build, and a new advisory fails it. Every parser is fuzzed on a schedule. Release builds are checksummed and come with a CycloneDX bill of materials, and the app and the team server list every third-party licence they include.
Out of scope
- A compromised operating system account, which can read the keychain as the person can.
- A server a person chooses to send requests to: what it answers is contained by the interface boundary above.
Report a vulnerability
Read the vulnerability disclosure policy.