Security and privacy¶
FreeDR is a single-host, single-user EDR. The service runs as LocalSystem, stores all evidence locally and serves its console only on the loopback interface. This page describes how access is controlled, how FreeDR protects itself, and what data can leave the host.
Local-only API¶
| Item | Value |
|---|---|
| Bind address | 127.0.0.1 only. Any other bind value in the [api] section of freedr.toml is rejected at startup (API may bind only to 127.0.0.1). |
| Default port | 29173 ([api] port) |
| Console URL | http://127.0.0.1:29173/ (plain HTTP on loopback; no TLS) |
| Host check | Every request must carry Host: 127.0.0.1:<port> exactly; localhost is rejected with 400 invalid request. |
| Origin check | If an Origin header is present it must be http://127.0.0.1:<port>. |
Every response carries Content-Security-Policy: default-src 'none'; script-src 'self';
...; frame-ancestors 'none', X-Frame-Options: DENY, X-Content-Type-Options:
nosniff, Referrer-Policy: no-referrer and Cache-Control: no-store.
Authentication¶
FreeDR uses one installation password and no accounts or roles (ADR-0062).
ADR-0031 is superseded
The earlier sessionless console (ADR-0031) and the token-file model (ADR-0002) no longer apply. Every API route except the login page and its static assets requires a session.
The password¶
- Set during installation (setup wizard, or
freedr-agentinstall prompts Set FreeDR password / Confirm FreeDR password). Unattended setup reads it from standard input. - At least 12 characters, at most 1,024 UTF-8 bytes. Never accepted on a command line.
- Stored only as a salted Argon2id verifier (19 MiB memory, 2 iterations, 1 lane) in
C:\ProgramData\FreeDR\password.json, inside the protected machine directory. - Reinstalling over existing data requires the existing password; installation never replaces it.
- There is no default password and no HTTP setup endpoint. If the password file is missing or invalid, the API fails closed.
Changing and resetting the password¶
- Change: Settings → Password & access → Change password
(
PUT /api/v1/auth/passwordwithcurrent_password,new_password,csrf_token). A wrong current password returns403 incorrect current password. A successful change signs out every session. - Forgotten password: there is no reset command, recovery key or unauthenticated
reset route. Setup asks for the existing password whenever
C:\ProgramData\FreeDR\password.jsonexists, so recovery means an administrator resets the installation (remove theFreeDRservice and delete the FreeDR machine directory from an elevated prompt, which also removes its evidence and cases) and installs again (ADR-0062). Setup's own uninstall needs the password. freedrctlcannot set or reset the password.
Sessions and cookies¶
| Property | Value |
|---|---|
| Login | POST /api/v1/auth/login {schema_version: 1, password} → token, expires_in, csrf_token |
| Session token | Random 256-bit, kept only in service memory |
| Browser cookie | freedr_session, HttpOnly; SameSite=Strict; Path=/; Max-Age=43200 (no Secure flag: loopback HTTP) |
| CLI | Same token as Authorization: Bearer ... |
| Lifetime | 12 hours, enforced server-side |
| Concurrent sessions | 16; the oldest is dropped when a 17th signs in |
| Ends when | Sign out (POST /api/v1/auth/logout), password change (all sessions), service restart (all sessions), expiry |
| Rate limit | 5 failed attempts per minute → 429 with Retry-After: 60; one password check runs at a time |
| Request size | Login, logout and password bodies ≤ 4 KiB |
| Session check | GET /api/v1/auth/session |
Health endpoints (/live, /ready, /version, /status) also require a session;
without one they return 401 and no details. Collection and detection keep running
while nobody is signed in.
CSRF protection¶
- Every state-changing request (settings, cases, rules, saved queries, response,
isolation, sign-out, password change) must include
csrf_tokenin its JSON body. The token is generated randomly at service start and returned by login,GET /api/v1/auth/sessionand authenticated reads. A wrong token returns403 invalid operation token. - Combined with
SameSite=Strictcookies and the exact Host/Origin checks, this blocks cross-site requests from other web pages. - JSON endpoints require
Content-Type: application/jsonand reject unknown fields.
What the session protects against, and what it does not
A valid session is the whole authority for every action, including response actions and host isolation. There is no second factor, per-action password or UAC prompt. Anyone who has the password, an unlocked signed-in browser, or a stolen session cookie can act. Administrators and SYSTEM, and code running inside the service, are outside this boundary.
freedrctl¶
freedrctl is a small command-line client. It reads no configuration or token files;
it prompts for the password (hidden) or reads one line with --password-stdin.
| Command | What it does |
|---|---|
freedrctl status |
GET /status |
freedrctl version |
GET /version |
freedrctl alerts |
GET /api/v1/detections/alerts |
freedrctl rules |
GET /api/v1/detections/rules |
freedrctl detection-health |
GET /api/v1/detections/health |
freedrctl service start \| stop \| status \| uninstall |
Windows service control; verifies the password locally against password.json (works while the API is down) and still needs Windows administrator rights |
freedrctl service install |
Refused: install by running freedr-agent (approve the UAC prompt) or the setup wizard |
Global options: --port <1-65535> (default 29173), --password-stdin. There is no
--host or --password option. API commands log in to 127.0.0.1 only, never follow
redirects or proxies, time out after 10 seconds and sign out when done.
Protected settings¶
Provider settings and credentials (Settings → Targeted enrichment and AI) are served
by GET/POST /api/v1/settings/enrichment (ADR-0058, simplified by ADR-0060).
- Saving applies immediately with a session and CSRF token. The UAC/approval-helper flow described in the original ADR-0058 is not present.
- Each save carries
expected_revision; a stale revision returns409 settings revision changed. - Credentials are write-only. Each slot (
abuse_ch,abuse_ipdb,virus_total,alien_vault_otx,grey_noise,cheaper_inference,open_ai,anthropic,deep_seek) can be kept, replaced or removed. The API only reports which slots are configured; keys are never returned, logged or stored in the browser. - Keys are encrypted with Windows DPAPI bound to the service account (LocalSystem) and stored in the protected control database.
- Every outbound provider starts disabled; enabling a keyed provider without its key is rejected.
Self-protection (tamper resistance)¶
Partial: user-mode only
FreeDR implements unsigned, user-mode tamper resistance (ADR-0063). It resists ordinary (non-administrator) local users and processes and accidental changes. It does not stop administrators, SYSTEM, kernel code or offline disk access. Driver, PPL, ELAM and code-signing protection are not available.
What is implemented:
- Process DACL: at startup the service process is locked so that only SYSTEM has full access; everyone else can only query limited information and wait. Ordinary callers cannot terminate, suspend, inject into or re-permission it.
- Service DACL: SYSTEM and elevated Administrators keep full control; everyone else
may only query. Ordinary users cannot stop, delete or reconfigure the
FreeDRservice. - Crash recovery: the service restarts after 5, 30 and 120 seconds, then stops retrying until a day passes without failure. Failures reported at startup (non-zero exit) are recovered like crashes. An intentional stop always stays stopped.
- Protected directories:
C:\ProgramData\FreeDRand itsdata,data\cold,configandrundirectories are SYSTEM-owned with full access only for SYSTEM and Administrators, and an OWNER RIGHTS entry so the installing user cannot silently re-permission files they created. Reparse points are rejected. - Drift repair: if the machine directory's DACL has gained extra allow entries
(for example after Explorer's "Continue" prompt), the service restores the
canonical DACL, re-verifies trusted files (
freedr.toml,password.json,ai-credential.dpapi, policy files, databases, Sysmon config) and raises the Medium self alertfreedr.self.machine-state-access-restored. A wrong owner, deny entries, a reparse point or a tampered trusted file stops startup instead. - Stop alert: after a requested stop, the next start raises
freedr.self.service-stoppedwith the stop time. - Startup failures are written to the service log and the Windows Application event
log (source
FreeDR). - Built-in rules are embedded in the executable; custom rules, toggles and exclusions live in the protected control database.
Qualification: one Windows 11 (build 22621) VM campaign passed on 2026-10-03 (docs/QA-REPORT-TAMPER-RESISTANCE-2026-10-03.md). Windows 10 and other builds are not qualified.
Threat model highlights¶
From docs/architecture/THREAT_MODEL.md and the ADRs above:
- Untrusted inputs: telemetry, rule content before validation, API input before authentication, AI output, cold archive files. Collectors cannot open storage; only the database worker does.
- Only
freedr-response-winchanges Windows state, and only through registered declarative response actions (see Response). - Hunt and detection SQL are read-only and cannot load extensions or reach files or the network (see Search and SQL Hunt).
- AI cannot run shell commands, write evidence, change settings or exclusions, or isolate the host. It can run a limited set of containment actions only when you turn on Let AI contain threats it finds (off by default; see Response).
Known residual risks (not mitigated today):
| Risk | Status |
|---|---|
| Administrator/SYSTEM compromise | Out of scope; can subvert all local controls. |
| Rule packs and updates are unsigned | Built-in rules are reported as unsigned; update authenticity is not enforced. |
| Response verdict integrity | Relies on the trusted local repository (KP-002). |
| Evidence at rest | Protected by ACLs, not encrypted by FreeDR. |
| Session theft | A stolen cookie or unlocked browser grants full authority until expiry or sign-out. |
| Telemetry loss under overload | Possible; reported in Health. |
Data privacy¶
Stored locally¶
All under C:\ProgramData\FreeDR (SYSTEM/Administrators only):
| Data | Location |
|---|---|
| Recent evidence | data\evidence.duckdb |
| Archived evidence | data\cold\ (Parquet) |
| Cases, alerts, rules, saved queries, response jobs, AI runs, settings, encrypted keys | data\control.sqlite3 |
| Password verifier | password.json |
| Internal AI harness credential (DPAPI) | ai-credential.dpapi |
| Configuration and policy | freedr.toml, config\ |
| Quarantine vault | Encrypted objects under the machine directory |
Evidence is kept for retention_days (default 90, editable in Settings → Evidence
lifecycle). FreeDR has no telemetry upload, cloud account or fleet server.
What can leave the host¶
Nothing is sent by default except the Sysmon download below. Everything else is opt-in.
| Feature | Default | Destination | What is sent |
|---|---|---|---|
| Sysmon bootstrap | At install, and from Settings → Sources, only if Sysmon is not installed | https://download.sysinternals.com/files/Sysmon.zip |
A download request |
| Threat-intel enrichment | All providers off | CIRCL hashlookup.circl.lu (no key); keyed: MalwareBazaar, ThreatFox, URLhaus (abuse.ch), AbuseIPDB, VirusTotal, AlienVault OTX, GreyNoise Community |
Only indicators the provider supports: SHA-256 hashes; public IPs; public domains; exact URLs (URLhaus). No file samples, command lines, private/reserved IPs, internal domains or credentials. |
| AI investigator and assists | Off | One selected provider: CheaperInference, OpenAI, Anthropic or DeepSeek (fixed HTTPS endpoints) | Bounded case evidence and tool results, redacted (see below) |
| Web research (AI or console) | Off (web_enabled) |
Parallel search https://search.parallel.ai/mcp, then public web pages it chooses to read |
Short sanitized search queries; page fetch requests |
Enrichment details (ADR-0058, ADR-0073):
- Fixed HTTPS endpoints, redirects disabled, bounded responses.
- Per-provider daily limits (default 100; VirusTotal at most 500/day and one request per 15 seconds). Results are cached as observations on the case.
- Automatic lookups run for alerts that open or join an open case and for events attached to such a case; they can also be requested manually or by the AI.
- Offline tags (LOLBAS, remote-access tools) and host prevalence/"new to host" facts are computed locally and send nothing.
AI details (ADR-0061, ADR-0064):
- Disabled until you enable it and enter that provider's key. Entering an AI key in the setup wizard enables AI unless you turn it off. Let AI contain threats it finds stays off unless you turn it on.
- When enabled, automatic investigation passes start for open cases of Medium severity or higher; you can also start runs manually. A daily token limit applies.
- Before anything is sent, FreeDR redacts: values of fields named like
password,secret,api_key,authorization,access_token,refresh_token,host_id,username; user and machine-specific Windows SIDs (well-known SIDs such as SYSTEM, built-in groups and RID 500 are kept, see AI overview); text containing credential markers (password=,token=,bearer,--password,api_key=, ...); and the user name inC:\Users\<name>\paths. Other evidence (command lines, file paths, hashes, domains, IPs) is sent as needed for the investigation. - Provider routes are not described as zero-data-retention; the provider's own data policy applies.
Web research details: at most three searches and three page reads per AI pass,
10-second timeout, 256 KiB of extracted text per page. Queries are limited to 200
characters and rejected if they contain private IPs, internal-looking domains
(.local, .corp, .lan, ...), localhost, or anything resembling a credential or
long opaque token. Page reads go only to public addresses (every DNS answer is
checked) and are parsed as inert text.
Windows alert notifications (ADR-0067) are local toast notifications and send nothing off the host.