After Dark Systems / Service Automation HubCompletely Programmable Infrastructure

OpenSAH / Farm tool guides

Out of Danger.

A fail-closed policy wrapper for commands such as rm. It checks arguments and protected paths, then runs the real executable only when policy allows.

Available Tag v0.1.0 MIT

What it is

Out of Danger is one small binary that you symlink under the names of the commands you want to guard. When it is invoked as rm, it reads its policy, decides, and then replaces itself with the configured real executable only if the operation is allowed. Anything doubtful is refused: a malformed policy, an unreadable lock file or an invalid real path stops the command rather than letting it through.

Use it to put a safety rail on the normal command path of a workstation or server, for example to stop a recursive delete of /, of the current directory, or of a tree you have marked as protected.

What it is not. It is not a security boundary. The real executable stays accessible, and running it directly (for example /bin/rm) bypasses the wrapper by design. It prevents mistakes made through the normal command path; it does not contain a hostile user.

Requirements

  • A Rust toolchain with Cargo (the project builds with the stable toolchain; CI runs on Ubuntu and macOS).
  • No third-party runtime or build dependencies.
  • A POSIX system where commands are found through PATH.

Build and install

Build from a clone of the public repository:

git clone https://github.com/afterdarksys/outofdanger.git
cd outofdanger
cargo build --release

The binary is target/release/outofdanger. Check it:

target/release/outofdanger version
target/release/outofdanger validate --config examples/outofdanger.conf

version prints outofdanger 0.1.0 for the tagged release, and validate prints valid: 1 commands for the example policy.

Install without replacing system binaries

Do not replace operating-system binaries. Install the wrapper in its own directory and put the command symlinks in a dedicated directory earlier in PATH:

install -d "$HOME/.local/lib/outofdanger" "$HOME/.local/bin/outofdanger-wrappers"
install -m 0755 target/release/outofdanger "$HOME/.local/lib/outofdanger/outofdanger"
"$HOME/.local/lib/outofdanger/outofdanger" link \
  --config "$PWD/examples/outofdanger.conf" \
  --dir "$HOME/.local/bin/outofdanger-wrappers"

Then set OUT_OF_DANGER_CONFIG to the policy you deployed and prepend the wrapper directory to PATH. link creates only missing links and refuses to replace any existing path.

For a system-wide install, use the same layout under /usr/local, keep each real path pointed at the operating-system executable, and deploy the policy as /etc/outofdanger.conf, the default location. Avoid renaming package-managed binaries: an upgrade can replace them and silently bypass or break the wrapper.

Quick start

Try a policy decision without installing anything. check evaluates and prints the decision; it never executes the command.

$ target/release/outofdanger check --config examples/outofdanger.conf --command rm -- -rf /
deny: rm (built-in guard recursive-root (use `permit rm recursive-root` to override))
$ echo $?
10
$ target/release/outofdanger check --config examples/outofdanger.conf --command rm -- /tmp/scratch.txt
allow: rm (command default)

Replace check with run to evaluate and then execute the real command when it is allowed. Once the symlinks are on PATH, a plain rm goes through the same decision.

The policy file

The policy is a plain text file. The default path is /etc/outofdanger.conf; OUT_OF_DANGER_CONFIG or --config overrides it. The example in the repository:

# Every command needs an absolute real path and an explicit default.
# Rules are evaluated top-to-bottom; the first match wins.
# `*` matches within one argument and `**` matches zero or more arguments.

command rm /bin/rm
default rm allow

protect rm /etc
protect rm /var/lib

# Optional controls:
# max-operands rm 20
# guard rm recursive-cwd
# lock-file rm /etc/outofdanger.STOP

Required lines

  • command NAME ABSOLUTE_REAL_PATH names a guarded command and the real executable it runs.
  • default NAME allow|deny is the decision when no rule matches.

Argument rules

Ordered allow and deny rules take the form ACTION NAME ARG_PATTERN..., and the first matching rule wins. * and ? match characters inside one argument; the token ** matches zero or more complete arguments. Values may be single- or double-quoted, and # starts a comment outside quotes.

Safety controls

These run before the ordered argument rules, so an allow rule cannot bypass them.

LineEffect
(built in)Recursive removal of /, //, relative paths that resolve to root and equivalent spellings is denied. Only permit rm recursive-root disables this guard.
protect rm PATHDenies removing the directory itself or anything under it, and recursive removal of its ancestors. Relative operands and existing symlink prefixes are resolved, including missing leaf paths. PATH must be absolute.
max-operands rm NDenies removing more than N operands, recursive or not. Flags do not count; arguments after -- do.
guard rm recursive-cwdDenies recursive removal of the current directory or any ancestor, such as rm -rf ..
lock-file NAME PATHDenies every invocation of NAME while PATH exists, including as a dangling symlink. Remove the file to resume. Use a dedicated path in a directory you control.

The tree, operand-count and working-directory controls support rm only. Other commands use argument rules and the optional lock file. The names outofdanger and ood are reserved for the management interface.

Commands and exit codes

outofdanger check [--config PATH] --command NAME -- ARGS...
outofdanger run [--config PATH] --command NAME -- ARGS...
outofdanger validate [--config PATH]
outofdanger commands [--config PATH]
outofdanger link [--config PATH] --dir PATH
outofdanger help
outofdanger version
  • check prints allow or deny with the reason, and never executes.
  • run evaluates, then executes the real command if allowed.
  • validate checks policy syntax and every real command target without executing them. Missing targets, non-executable files, directories, and links back to the wrapper (including hard links) fail.
  • commands lists each configured command, its real path and its default.
  • link creates a symlink per configured command in --dir, and refuses to replace anything that already exists.

An allowed check exits 0. A denied operation exits 10. Configuration and usage errors exit 2.

Limits

  • The built-in root guard and the path and count guards understand rm. Other commands get argument rules and emergency locks.
  • Argument globs are lexical. They do not resolve symlinks, shell expansions, working-directory-relative paths or command-specific meaning. Use protect for filesystem trees.
  • Only invocations that resolve to a wrapper symlink on PATH are controlled.
  • Resolution is conservative: a symlink operand can be denied even when rm would only unlink the link. Paths can change between evaluation and execution, so this is not atomic filesystem enforcement.
  • validate checks executable mode bits, not ACLs or mount-level execution restrictions.

How it fits with the other tools

Out of Danger is standalone. It does not read or write the state of the other farm tools. It guards the commands an operator types on one machine. vpscfgfarm is the gate for commands sent to a fleet, and oos manages disk cleanup with its own refusal rules.

Built from the public outofdanger repository (README.md, SECURITY.md, examples/outofdanger.conf, src/main.rs help text) at commit 411a1fc.