Security and data handling
Sift holds an API key into your Kindo tenant and decides, on its own, that some events do not need an agent run. So the credential is worth protecting and the decisions are worth making hard to corrupt. This page describes how Sift handles accounts, secrets and data, what it sends out of the deployment, how long it keeps what it stores, and what it does not do.
Accounts and passwords
Section titled “Accounts and passwords”- Passwords are hashed with bcrypt at cost 12.
- A password must be at least 12 characters. One longer than 72 bytes is refused rather than silently cut short, which is what bcrypt would otherwise do.
- A sign-in for a username that does not exist takes as long as one that does, so timing does not reveal which accounts exist.
- The first admin account is seeded from
SIFT_DEFAULT_ADMIN_USERNAMEandSIFT_DEFAULT_ADMIN_PASSWORD(admin/adminby default). While any active account still has the shipped password, the console shows a banner that cannot be dismissed and the system status raises a critical warning. Production mode refuses to start with the shipped password in.env. - There is no single sign-on, no multi-factor authentication and no password complexity rule beyond the minimum length.
Sessions
Section titled “Sessions”- A session is a signed token (HS256, with a key derived from
SIFT_SECRET_KEY) in a cookie namedsift_session:httpOnly,SameSite=Lax, andSecureeverywhere except development mode. - It lasts
SIFT_SESSION_TTL_HOURS(12) unless ended sooner. - Sessions end on the server. Signing out ends every session of that account, on every device. Changing the password ends every other one. An admin changing an account’s role, disabling it or re-enabling it ends all of its sessions.
- No single session can be ended on its own.
- Every request that changes something must carry the session’s CSRF token in the
X-CSRF-Tokenheader. The token is bound into the signed session, so it cannot be forged without the signing key.
Sign-in throttling
Section titled “Sign-in throttling”Failed sign-ins are counted in Redis over a 15-minute window: 10 per username and 30 per client address. Either limit refuses further attempts until the window passes. A wrong current password on the change-password form counts against the same username limit. There is no permanent lockout, which would let anyone lock a named operator out.
The client address is the one your trusted proxy recorded, chosen by
SIFT_TRUSTED_PROXY_HOPS. See Serve over HTTPS.
Sift has three roles, and the server enforces them on every API route, whatever the console shows.
| Role | Can |
|---|---|
| Admin | Everything an operator can, plus users, the Kindo connection, which recipes new filters may use, webhook endpoints and the decision model. |
| Operator | Create and run filters, train, and approve gold sets and thresholds. |
| Reviewer | Label gold sets and answer the verdict check, and read everything else about filters. |
Approvals need an operator or admin; the server refuses a reviewer. See Users and roles.
Stored secrets
Section titled “Stored secrets”| Secret | How it is kept |
|---|---|
| The Kindo API key | Encrypted (Fernet) with a key derived from SIFT_SECRET_KEY. No response ever returns it; the console shows its last four characters. |
| Each webhook endpoint’s URL, optional header value and signing secrets | Encrypted the same way, with a separately derived key. No response returns them. |
| Passwords | bcrypt hashes only. |
SIFT_SECRET_KEY, SIFT_METRICS_TOKEN | Only in .env on the host. Not in the database or any backup. |
Changing SIFT_SECRET_KEY makes every stored secret unreadable. Decryption fails loudly
rather than returning garbage, and an admin must enter the secrets again.
The secrets are decrypted in memory when used, so anyone who can run code in a backend container can read them. Encryption protects the database and its backups, not the running process.
Every log line passes through a filter that removes values whose key looks like a secret (password, token, API key, authorization, cookie and similar), as a backstop.
Network exposure
Section titled “Network exposure”- One published port. Only
sift-webis published. The API and the object store are reachable only inside the Compose network. - Response headers. Every response carries
X-Content-Type-Options: nosniff,X-Frame-Options: DENYandReferrer-Policy: no-referrer, and over HTTPSStrict-Transport-Security. - The object store refuses anonymous requests. It always runs with the key from
SEAWEEDFS_ACCESS_KEYandSEAWEEDFS_SECRET_KEY, and its admin UI and WebDAV are off. - Metrics are off unless
SIFT_METRICS_TOKENis set, and then need that token. - Inside the stack, traffic is plain. There is no TLS between nginx, the API and the datastores.
- Production mode refuses published or weak credentials at start. See the production checklist.
Content from outside is treated as data
Section titled “Content from outside is treated as data”Events, tickets and agent transcripts carry text written by others, including attackers. Sift reads them into model prompts when Sift’s assistant proposes a filter, when a gold set is built and when an agent is analyzed. So:
- That content enters a prompt only inside a delimited data block, never as instructions.
- Sift’s assistant and the analyzer have no tools that change anything. They propose; a person applies.
- Their answers must fit a fixed schema, so an injected instruction has no field to act through.
- The console shows such content as plain text, with no HTML, Markdown or links rendered.
- Long content is cut: each event quoted in a gold-set build is at most
SIFT_GOLD_MAX_EVENT_CHARS(2,000) characters.
This limits what injected text can do. It does not stop it from arguing for a bad filter; the person who approves the gold set and the threshold is that defence.
What leaves the deployment
Section titled “What leaves the deployment”| Destination | When | What it carries |
|---|---|---|
| Your Kindo API | Once an admin connects Kindo | Source reads (tool name, parameters, watermark). Triggers: the event itself, up to SIFT_MAX_TRIGGER_INPUT_CHARS (32,000) characters, and nothing about its score. Model calls for Sift’s assistant, gold-set builds and agent analysis: sampled events, agent transcripts and what a person types. Reads of agents, runs, conversations, models and integrations. |
| Webhook receivers | Only if an admin adds one, up to ten | The alert itself: Sift’s own sentence about the condition, its code, the filter’s id and state, and a link. Never event content, scores, verdicts or the filter’s intent. The filter’s name only if Include filter names is on for that endpoint. HTTPS only outside development, signed, and checked against private and metadata addresses at every send. |
| The model download base URL | First start, unless the weights are staged | Nothing from your data: a download of the pinned weights. |
| TypeSafe’s Jev API | Only with SIFT_JEV_ENABLED | The fields a filter’s Decide stage reads, at most SIFT_DECISIONS_MAX_STATE_CHARS (4,000) characters per event. See The decision model. |
Sift sends no telemetry, and the object store’s telemetry is switched off. What Kindo keeps of what it is sent is governed by your tenant, not by Sift.
How long data is kept
Section titled “How long data is kept”Sweeps that run daily or nightly enforce these windows.
| Data | Kept | Setting (default) |
|---|---|---|
| Event content | Cleared after the window; the event’s record and scores remain | SIFT_EVENT_PAYLOAD_RETENTION_DAYS (30) |
| Content of events that carry a training label | This long from arrival instead. In shadow every event is labelled. | SIFT_LABELLED_PAYLOAD_RETENTION_DAYS (30) |
| Filtered events (which event, its score, the model, the time; never content) | Deleted after the window. Must be at least the payload window. | SIFT_FILTERED_EVENTS_RETENTION_DAYS (400) |
| Poll log | Deleted after the window | SIFT_POLL_LOG_RETENTION_DAYS (14) |
| Bake-off candidate scores | Deleted after the window | SIFT_CANDIDATE_SCORE_RETENTION_DAYS (30) |
| Files of retired models | Deleted this long after retirement | SIFT_ARTIFACT_RETENTION_DAYS (30) |
| Trigger attempts | 30 days | fixed |
| Webhook deliveries and cleared alerts | 30 days | fixed |
| Assistant conversations, with their sampled events (Redis) | 8 hours after the last change | fixed |
Kept with no window: scores, labels, agent runs and what each agent returned, the audit log, gold sets with the events their reviewers saw, and analysis reports. Some of these hold copies of event content that the payload windows do not reach. Filters are retired, never deleted. Backups keep whatever the database held when they were taken.
What Sift does not do
Section titled “What Sift does not do”- Single sign-on, multi-factor authentication or an external identity provider.
- A Content-Security-Policy header.
- Encryption at rest beyond the stored secrets: Postgres, the object store and backups are the host’s to encrypt.
- TLS between its own containers.
- Certificate issuance or renewal.
- More than one tenant. Sift is built for one organization behind a trusted network perimeter.