Skip to content

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_addresses on 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.toml

An 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_hosts by 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-new records 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-https uses 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, perl or sh, 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

  1. Read the profile and everything its depends_on entries resolve to.
  2. Set --allowed-commands to the executables the profile needs. refpoint check lists them.
  3. Record host keys in known_hosts before the first run.
  4. 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.