Trust model
Hostkeep trusts the owner who writes its configuration, and anyone who holds the access token (completely, today; see the known gaps).
Hostkeep does not trust anything the assistant sends: paths, file patterns, SQL, command parameters. It doesn't trust the file system layout either (links and junctions are treated as attacks), archive contents, or responses from the internet.
The seven walls
- 01 Gate. Hostkeep listens on the local machine only (127.0.0.1) by default. Every request needs the access token (compared in constant time) and an allowed host name, and is rate-limited, before its body is parsed. Remote access goes through a tunnel the owner controls.
- 02 Only enabled tools exist. Optional tool families (jobs, outbound fetch, exports, host access, desktop) are absent from the tool list unless the owner switches them on.
- 03 Projects. The assistant addresses files only as a project name plus a relative path. On Windows, file content operations run through a dedicated worker that pins folders by handle (see below).
- 04 Read-only by default. Writing is switched on per project and can be limited to sub-folders. Repository control paths such as
.gitand the owner's protected patterns stay read-only even in writable projects, including archive extraction. - 05 Tasks, not a shell. Commands are defined by the owner with typed parameters (string, integer, enum, relative path). Values that look like extra options are rejected unless explicitly allowed. Git runs with repository-defined hooks, filters, monitors, diff and signing programs disabled.
- 06 Secrets stay home. Every program Hostkeep starts receives an allow-listed environment without the access token. Outbound fetch is off by default; when enabled it allows HTTPS only, refuses private and special network ranges, connects to the exact address it checked, and re-checks every redirect.
- 07 Record. Every tool call, including rejected ones, produces one activity record without file contents, argument values or secrets. Records are SHA-256 hash-chained across daily files, so editing or deleting a record is detected. If writing the log fails, new changes and tasks are refused before they run, until logging recovers.
Project containment on Windows
The classic weakness of file tools is a race: a path is checked, then a folder in it is swapped for a link before the file is opened. Path checks alone can't close that window, and our own measurements showed it.
Hostkeep's answer is a fixed worker that pins every folder from the drive down to the target with an open handle that blocks renames and deletion, then opens each next folder and the file relative to the handle it already holds, never by full path. Windows refuses to resolve a name inside a folder that has been turned into a link this way, so nothing outside the project is reached. After each open, the worker re-checks the held folder and the file's real location, and rejects hard links on writes. Protected-path rules are applied to the real names Windows reports, so short-name aliases don't bypass them.
Evidence: a stress test performs 100,000 reads and overwrites while another process swaps project folders for links as fast as it can (43,220 swaps). Result: zero outside reads, zero changed outside files, zero leftover files. With short pauses between swaps, normal operations keep succeeding (about 900 reads and 1,000 writes in 20,000 attempts). Median contained read time: 0.57 ms.
Scope: this applies to file content operations (read, write, edit, copy, move, delete, hash, export) on local NTFS drives. Listings, search, archives and data helpers keep an earlier identity-check mitigation; see the known gaps.
Claims and evidence
Each claim maps to the function that enforces it and the named automated test that checks it. The full suite has 192 tests.
| Claim | Enforced by | Proven by |
|---|---|---|
| Host, token and rate checks run before a request body is parsed | src/server.mjs:createApp | unauthenticated large malformed bodies are rejected before parsing or rate-limit bypass |
| Disabled tool families are absent from the tool list | src/server.mjs:defineTools | limited configuration advertises only enabled tool families |
| Strict Windows file reads never accept outside content | src/windows-file-tools.mjs | strict public read never accepts outside content under inconsistent Node path observations |
| The file worker starts under Windows' default script policy without changing it | src/windows-file-io.mjs | fixed file worker initializes with Restricted child policy without changing persistent policy |
| Writable projects still protect control paths | src/security.mjs:assertWritable | writable roots reject every mutation of default and operator-protected paths |
| Archive extraction checks protected paths before writing | src/advanced-tools.mjs:archiveExtract | archive extraction checks every selected entry against protected paths before writing |
| Git never runs repository-defined programs | src/safe-git.mjs:runSafeGit | checkpoint and Git inspection never run repository-defined programs |
| Task parameters reject option-like values | src/command-parameters.mjs | default string parameters reject option-like and non-string values before process start |
| Child processes never receive credentials | src/child-environment.mjs | every project process route withholds credential variables and preload overrides |
| Outbound HTTPS connects to the checked address | src/outbound.mjs:downloadAsset | HTTPS connects to the vetted address while preserving the TLS hostname |
| Every tool call gets one content-free activity record | src/server.mjs:recordToolCall | every registered tool call has exactly one content-free audit record, including schema rejection |
| Edited or deleted activity records are detected | src/audit.mjs:verifyAuditFiles | editing or deleting an interior audit record reports the first broken record |
| If logging fails, new changes are refused before they run | src/server.mjs:observed | failed post-call audit preserves the result and blocks new mutation before its marker is written |
| Export links work once and are logged | src/server.mjs:createApp | one admitted HTTP export download consumes the link and is audited without token or contents |
| Written text stays within the configured size limit | src/tools.mjs:write | HTTP accepts a configured UTF-8 write even when JSON escaping exceeds five MiB |
| Fallback: an opened file with a different identity is rejected | src/security.mjs:openContained | an opened handle with a different file identity is rejected and closed before any read |
| Fallback: helper output is discarded if its file changes | src/security.mjs:withHelperIdentity | a named helper discards output if its file changes during work |
Known gaps
Until a gap is closed, don't rely on the protection it names. Each one has a planned fix on the roadmap.
| Gap | Status | Planned fix |
|---|---|---|
| No human approval before changes or tasks run | Open | Approval gate on a local page, optionally with a phone notification, so an assistant can't approve its own action (next phase) |
| Running tests on a writable project executes that project's code | Open | Task runs behind the same approval gate (next phase) |
| One access token: no per-client identity, scopes or expiry | Open | A separate key per client (next phase), then OAuth sign-in |
| Folder-swap race for listings, search, archives and data helpers | Mitigated | Closed for file content operations on local NTFS; the remaining tools keep an identity-check mitigation and move to the pinned worker later |
The activity log detects edits against the retained chain. It can't stop someone with full administrator access from rewriting every record; forwarding records off the machine is planned.
Advanced host access
For owners who need it, an optional host module can start processes and PowerShell outside project folders, and an optional desktop module can automate windows. Both are off by default and outside the project protections above: they run with the permissions of Hostkeep's Windows user. What they do provide: explicit opt-in, no privilege escalation, no double execution on retries, the token stripped from child environments and redacted from output, and a private job folder that rejects links. They will be left out of the pilot build.
Deployment advice
- Run Hostkeep as a standard user, not elevated. A dedicated low-privilege account is better still.
- Keep projects read-only unless writing is needed, and grant writes to specific sub-folders.
- Generate a long random token, keep it in the environment only, and rotate it if you suspect exposure.
- Don't expose the port directly. Use a tunnel with its own access control.
- Never name a project folder that contains credentials, backups or production data.
Report a vulnerability
Please email fay@hostkeep.tech with "Security report" in the subject. Include what you found and how to see it. Please don't post details publicly until a fix is available. Machine-readable contact: /.well-known/security.txt.