Security model
The trust boundaries, what each one enforces, and where the boundary is not.
On this page
refpoint reads a target and never changes it. It connects with the credentials you give it, runs read-only operations, and reports what came back. Two inputs are untrusted throughout: the profile that says what to run, and the target that answers. Each boundary below has a default, and most have an opt-out that must be written down.
| Boundary | Default | Opt-out |
|---|---|---|
| Commands on a target | Refused unless both the operator and the profile allow the executable | --allowed-commands '*' |
| HTTP to private addresses | Refused after the name is resolved | --allow-private-http plus allow_private_addresses on the check |
| SSH host key | Verified against known_hosts; a changed key is refused |
--ssh-known-hosts-file=insecure |
| WinRM over plain HTTP | Refused unless the peer negotiates message sealing | --winrm-allow-unencrypted |
| WinRM over TLS | Certificate verified | --winrm-insecure |
| Reads from a target | 64 MiB per file, 16 MiB per stream; over the cap is an error | None |
| Config file | ./config.toml, then ~/.refpoint/config.toml; the path loaded is printed first |
None |
The three transport opt-outs warn on the run they loosen. None is a default.
A profile is code
A profile runs commands with your credentials on your fleet. Review one you did
not write as you would a script you are about to run as root. A depends_on
entry extends that review to whatever it resolves to: dependency paths are
unconfined by design, so profiles can compose.
Everything below bounds what a mistake can do. None of it contains a hostile profile author.
What a profile can make refpoint do
Run commands
Two lists gate the command resource, and both must permit every executable in
the line.
| Gate | Set by | Empty means |
|---|---|---|
| Operator ceiling | --allowed-commands |
Nothing is allowed |
| Profile declaration | allowed_commands in the profile block |
The ceiling governs alone |
The profile can narrow the ceiling, never widen it. * is an explicit entry
someone has to write. A refusal names the gate that closed, so the message
reaches whoever can change it. Here the operator allowed only true:
! [mix-02] Fails impact: 1.0
! always-fail[command: false]
error: command "false" blocked: executable "false" is not in allowed_commands (true)A blocked command is an errored control, so the run exits 2. The command allowlist guide covers the parser and its limits.
Make HTTP requests
http checks are made from the host running refpoint, not from the target.
Without a gate, a profile could use the runner to probe the runner’s own
network. Two gates, both required:
- Operator ceiling
--allow-private-http. - Check opt-in
allow_private_addresseson the check.
Without both, the hostname is resolved before anything connects and refused if
any address is loopback, private, link-local, unspecified or interface-local
multicast. Resolving first catches a public name pointing at 127.0.0.1.
Every redirect hop is checked the same way, the chain stops after 3 hops, and a
response body is read to 1 MiB.
Supply profile files
| Input | Limit |
|---|---|
| Profile file | 4 MiB |
| Bracket nesting | 200 levels |
| Evaluated string | 256 KiB |
The nesting cap prevents a stack overflow, which is fatal and which no recover catches.
- Expression functions are pure. No filesystem, no environment, no clock. A profile evaluates to the same thing on a laptop and in a pipeline.
- Control characters are refused in resource addresses and block labels, since both appear verbatim in logs and reports.
- Dependency cycles are refused.
- The parsers are fuzzed. The variable, waiver and inventory file parsers have fuzz targets that run in CI.
Change the configuration
Configuration is read from ./config.toml first, then
~/.refpoint/config.toml. A config file inside a repository you cloned
governs a run started from that directory. It can change targets, disable host
key verification or widen the allowed commands.
Every run prints the absolute path it loaded to stderr before anything else:
config loaded: /home/you/work/baseline/config.tomlAn unexpected path on that line is a finding, not a detail.
What a target can make refpoint do
| Input | Protection |
|---|---|
| Text | Sanitised where it is rendered. Only sanitised text reaches the terminal renderer; the type that renderer accepts cannot be constructed any other way. HTML output is escaped by the template engine. |
| Volume | 64 MiB per file read, 16 MiB per output stream. Exceeding a cap is an error, never a truncation. |
| A panic | Contained. A panic in a resource provider becomes that control’s error; a panic during a target’s scan becomes that target’s error. |
Output from a target can carry terminal control sequences, and anything that can repaint a screen can hide a failure. A quietly shortened stream could turn a failure into a pass. One bad answer does not end a fleet-wide run.
Transport
- SSH host keys are verified against
known_hostsby default. The key algorithms offered are limited to those recorded for that host, so a server cannot steer the handshake to an algorithm whose key you never pinned.--ssh-known-hosts-file=accept-newrecords an unknown host on first contact and still refuses a changed key. - WinRM over plain HTTP is pre-flighted. A peer that cannot negotiate
message sealing is refused, because the password would otherwise cross the
network in cleartext.
--winrm-httpsuses port 5986.
Credentials
| Secret | Handling |
|---|---|
| SSH key file | Bytes zeroed once parsed |
| SSH, WinRM and sudo passwords | Cleared from the in-memory configuration once the transports are open |
--sudo without a password |
Runs sudo -n. A command that needs a password fails at once instead of hanging on a prompt |
--sudo with a password |
Runs sudo -S with the prompt suppressed. The password is read from stdin and stays off the command line |
~/.refpoint |
Created 0700 |
| Config files, reports, audit logs, generated keys | Created 0600 and re-set when opened, because an existing file keeps its mode. refpoint keygen refuses to overwrite a key |
Where the boundary is not
Three limits, each a property of the design rather than a gap awaiting a fix.
- The allowlist works on executable names. It cannot see inside an
interpreter. Allow
awk,find,perlorsh, and anything is allowed. - The opt-outs are real. Each entry in the opt-out column of the first table switches a protection off. They exist for the cases that need them.
- A profile you have not read is code you have not read. The bounds above limit accidents, not intent.
Before production
- Read the profile and everything its
depends_onentries resolve to. - Set
--allowed-commandsto the executables the profile needs.refpoint checklists them. - Record host keys in
known_hostsbefore the first run. - Check the
config loaded:line on the first run.
Reporting a vulnerability
Report a suspected vulnerability privately to security@refpoint.dev rather than in a public issue. The repository’s security policy says what to include.
Related: the command allowlist, evidence and attestation, release integrity.