This repository has been archived on 2026-06-24. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
enroll.sh/src/content/security.html
Miguel Jacq ff1ac0e01c
All checks were successful
CI / test (push) Successful in 2m27s
Fixes for a never-to-be-released, um, release
2026-06-22 20:26:45 +10:00

255 lines
16 KiB
HTML

---
title: "Security Design"
html_title: "Enroll Security"
description: "Security posture and safe workflows for Enroll outputs."
---
<header class="py-5 hero">
<div class="container py-3">
<div class="kicker mb-3"><i class="bi bi-shield-lock"></i> Security</div>
<h1 class="display-6 fw-bold mb-2">Safe by default. Powerful when you opt in.</h1>
<p class="lead mb-0">Enroll can touch sensitive files. This page helps you use it confidently.</p>
</div>
</header>
<main class="py-5">
<div class="container">
<div class="row g-4">
<div class="col-lg-8">
<div class="feature-card p-4">
<h2 class="h4 fw-bold mb-2">Default behavior</h2>
<p class="text-secondary mb-0">In normal mode, Enroll attempts to avoid harvesting likely secrets using a combination of path deny-lists, content sniffing, and size caps. This means you may see some files intentionally skipped.</p>
</div>
<div class="feature-card p-4 mt-4 border border-warning">
<div class="d-flex align-items-start gap-3">
<div class="icon-pill" style="background: rgba(255,193,7,0.20); border-color: rgba(255,193,7,0.45);"><i class="bi bi-exclamation-triangle"></i></div>
<div>
<h2 class="h4 fw-bold mb-2">The <code>--dangerous</code> flag</h2>
<p class="mb-2">This disables secret-safety checks. It can copy private keys, API tokens, DB passwords, TLS key material, etc.</p>
<p class="small text-secondary mb-0"><strong>Rule:</strong> if you use <code>--dangerous</code>, treat the output as sensitive data and plan secure storage before you run it. Don't store secrets in plaintext in a public place!</p>
</div>
</div>
</div>
<div class="feature-card p-4 mt-4">
<h2 class="h4 fw-bold mb-2">Encrypt bundles at rest with SOPS</h2>
<p class="text-secondary">You can install <a href="https://github.com/getsops/sops" target="_blank" rel="noopener noreferrer">SOPS</a> on your <code>$PATH</code>, then use <code>--sops</code> to write a single encrypted <code>.tar.gz.sops</code> file for harvests and/or manifests. This is meant for storage-at-rest and backups.</p>
<div class="codeblock terminal">
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#sec-sops"><i class="bi bi-clipboard"></i> Copy</button>
<pre class="mb-0"><code id="sec-sops"><span class="prompt">$</span> enroll harvest --out /tmp/enroll-harvest --dangerous --sops &lt;FINGERPRINT&gt;
<span class="prompt">$</span> enroll manifest --harvest /tmp/enroll-harvest/harvest.tar.gz.sops \
--target ansible --out /tmp/enroll-ansible --sops &lt;FINGERPRINT&gt;</code></pre>
</div>
<div class="alert alert-secondary mt-3 mb-0">
<div class="fw-semibold">Important</div>
<div class="small mb-0">In manifest <code>--sops</code> mode, you'll need to decrypt and extract the bundle before running <code>ansible-playbook</code>, <code>puppet apply</code>, or <code>salt-call</code>.</div>
</div>
</div>
<div class="feature-card p-4 mt-4">
<h2 class="h4 fw-bold mb-2">Filesystem hardening for root runs</h2>
<p class="text-secondary">Enroll is often run as root because it needs to inspect system state. Version 0.5.5 added stricter filesystem checks so root does not accidentally write harvests or generated output through an unsafe path.</p>
<div class="row g-3">
<div class="col-md-6">
<div class="callout p-4 h-100">
<div class="fw-semibold mb-2">Private output directories</div>
<p class="small text-secondary mb-0">Harvest and manifest outputs are created with restrictive permissions, and Enroll refuses to reuse an existing harvest directory unless that mode is explicitly expected for an internal workflow.</p>
</div>
</div>
<div class="col-md-6">
<div class="callout p-4 h-100">
<div class="fw-semibold mb-2">Symlink and parent checks</div>
<p class="small text-secondary mb-0">When running as root, Enroll checks path components and parent directories so output is not created below a parent controlled by another user. This reduces the risk of symlink and time-of-check/time-of-use path races.</p>
</div>
</div>
<div class="col-md-6">
<div class="callout p-4 h-100">
<div class="fw-semibold mb-2">Safer artifact handling</div>
<p class="small text-secondary mb-0">Captured files and generated artifacts are validated as regular files, with traversal, symlink, hardlink, and special-file cases rejected when bundles are validated or extracted.</p>
</div>
</div>
<div class="col-md-6">
<div class="callout p-4 h-100">
<div class="fw-semibold mb-2">Root <code>PATH</code> hygiene</div>
<p class="small text-secondary mb-0">For root runs, Enroll warns about unsafe <code>PATH</code> entries so helper commands are not resolved from relative, writable, or non-root-owned directories.</p>
</div>
</div>
</div>
<div class="alert alert-info mt-3 mb-0">
<div class="fw-semibold">Why this can feel strict</div>
<div class="small mb-0">A command that writes under a user-owned directory may be convenient, but it can also be hard to prove safe when the process is running as root. Prefer a root-owned output area such as <code>/var/tmp/enroll</code>, <code>/root</code>, or a fresh directory directly under the normal sticky <code>/tmp</code>.</div>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="callout p-4">
<div class="fw-semibold mb-2">Recommended workflow</div>
<ol class="small mb-0">
<li>Start with default mode (no <code>--dangerous</code>).</li>
<li>Add <code>--include-path</code> for a small set of extra files you genuinely want managed.</li>
<li>If you must capture secrets, use <code>--dangerous</code> <strong>and</strong> <code>--sops</code>.</li>
<li>Keep outputs out of public repos; review before committing.</li>
<li>Rotate credentials if you ever suspect they were captured or exposed.</li>
</ol>
</div>
<div class="callout p-4 mt-3">
<div class="fw-semibold mb-2">Storage ideas</div>
<ul class="small mb-0">
<li>Encrypted SOPS bundle stored in a password manager vault</li>
<li>Private git repo with additional encryption at rest</li>
<li>Offline backup in an encrypted volume</li>
</ul>
</div>
<div class="callout p-4 mt-3">
<div class="fw-semibold mb-2">Scope control</div>
<p class="small text-secondary mb-3">You can explicitly include or exclude paths. Excludes take precedence over includes.</p>
<div class="terminal"><pre class="mb-0"><code><span class="prompt">$</span> enroll harvest \
--out /tmp/enroll-harvest \
--include-path '/home/*/.profile' \
--exclude-path '/home/*/.ssh/**'</code></pre></div>
</div>
</div>
</div>
<hr class="my-5">
<div class="feature-card p-4">
<h2 class="h4 fw-bold mb-2">Threat model and security scope</h2>
<p class="text-secondary">Enroll is a command-line systems administration tool. It is designed to be run intentionally by an administrator, often as root, to inspect a host, harvest selected system state, and optionally generate or apply configuration-management output.</p>
<p class="text-secondary mb-0">That makes Enroll different from a web application, daemon, network service, or setuid helper. It is not intended to be a sandbox for hostile local users. Instead, it assumes an operator-controlled execution environment, while still adding defense-in-depth checks for common filesystem, path traversal, secret-handling, and command-resolution mistakes.</p>
<div class="row g-3 mt-2">
<div class="col-md-6">
<div class="callout p-4 h-100">
<div class="fw-semibold mb-2">Trusted operator assumptions</div>
<ul class="small text-secondary mb-0">
<li>If Enroll is run as root, the root user is assumed to control and understand the command line, environment, configuration file, and output location.</li>
<li>If an <code>enroll.ini</code> file is loaded, its location and contents are assumed to be owned, selected, and understood by the operator.</li>
<li>The operator is expected to understand the implications of options such as <code>--dangerous</code>, <code>--assume-safe-path</code>, <code>--sops</code>, <code>--enforce</code>, <code>--remote-host</code>, and <code>--remote-ssh-config</code>.</li>
<li>Harvest bundles used for <code>manifest</code>, <code>diff</code>, or <code>diff --enforce</code> are assumed to come from a trusted source unless the operator is deliberately inspecting them without applying them.</li>
</ul>
</div>
</div>
<div class="col-md-6">
<div class="callout p-4 h-100">
<div class="fw-semibold mb-2">Trusted local tools</div>
<p class="small text-secondary mb-0">Enroll invokes ordinary administrative tools such as SSH, <code>sudo</code>, SOPS, package managers, Docker, Podman, Flatpak, Snap, Ansible, Puppet, Salt, and system utilities. These are assumed to be the trusted tools the operator intended to execute.</p>
<p></p>
<p class="small text-secondary mb-0">If an attacker can replace or redirect those tools, the host already has a local trust-boundary problem outside Enroll's ability to fully solve.</p>
</div>
</div>
</div>
</div>
<div class="feature-card p-4 mt-4">
<h2 class="h4 fw-bold mb-2">What Enroll tries to protect against</h2>
<p class="text-secondary">Enroll still performs substantial hardening because privileged CLI tools can otherwise be easy to misuse. These controls are intended to protect careful administrators from common dangerous mistakes.</p>
<div class="row g-3">
<div class="col-md-6">
<ul class="small mb-0">
<li>Accidentally copying obvious secrets in default mode</li>
<li>Harvesting huge or unbounded file sets by mistake</li>
<li>Writing root-run output through unsafe symlinks, hardlinks, special files, or path-race situations</li>
<li>Extracting harvest tar members outside the intended bundle directory</li>
<li>Copying unsafe harvested artifacts into generated manifests</li>
</ul>
</div>
<div class="col-md-6">
<ul class="small mb-0">
<li>Resolving helper commands from suspicious <code>PATH</code> entries during root runs</li>
<li>Generating manifest commands where ordinary harvested values could become shell injection</li>
<li>Accepting unknown SSH host keys during remote harvests</li>
<li>Confusing one host's data with another host's data in multi-site outputs</li>
<li>Applying structurally unsafe harvest bundles</li>
</ul>
</div>
</div>
</div>
<div class="feature-card p-4 mt-4">
<h2 class="h4 fw-bold mb-2">What is out of scope</h2>
<p class="text-secondary">The following are normally considered local compromise, operator-controlled behavior, or trust-boundary failures rather than Enroll vulnerabilities by themselves:</p>
<div class="row g-3">
<div class="col-md-6">
<ul class="small mb-0">
<li>A malicious local user who can already control root's command line, shell environment, config file, working directory, <code>PATH</code>, or invoked binaries</li>
<li>A root user loading an <code>enroll.ini</code> file whose contents intentionally request dangerous behavior</li>
<li>A root user passing <code>--dangerous</code> and observing that Enroll may collect sensitive data</li>
<li>A root user passing <code>--assume-safe-path</code> and observing that Enroll does not prompt about <code>PATH</code> safety</li>
</ul>
</div>
<div class="col-md-6">
<ul class="small mb-0">
<li>A user applying generated Ansible, Puppet, or Salt output from a harvest bundle they do not trust</li>
<li>A user enforcing a malicious or manually edited harvest bundle with <code>diff --enforce</code></li>
<li>A user configuring an untrusted webhook, SSH proxy command, SOPS binary, or configuration-management tool</li>
<li>Reports that amount to: root can run Enroll with malicious options and make the system do dangerous things</li>
</ul>
</div>
</div>
<div class="alert alert-secondary mt-3 mb-0">
<div class="fw-semibold">Plain-language summary</div>
<div class="small mb-0">Enroll is an administrator's tool, not a local privilege boundary. If the operator's root execution environment is already attacker-controlled, Enroll cannot make that safe. Enroll does, however, try to reduce the damage from common path, permission, traversal, secret-handling, and manifest-generation mistakes.</div>
</div>
</div>
<div class="feature-card p-4 mt-4">
<h2 class="h4 fw-bold mb-2">Trusted harvests and enforcement</h2>
<p class="text-secondary">Harvest bundles should be treated as sensitive administrative artifacts. A harvest may contain hostnames, usernames, package lists, service state, filesystem metadata, configuration files, firewall snapshots, container image references, Flatpak/Snap state, and other operational details. In <code>--dangerous</code> mode it may contain substantially more sensitive material.</p>
<p class="text-secondary mb-0">Enroll validates harvest structure and artifact safety, but validation does not prove that the desired state represented by a harvest is safe to apply. Only run <code>manifest</code>, <code>diff</code>, or especially <code>diff --enforce</code> against bundles that came from hosts and people you trust.</p>
</div>
<div class="feature-card p-4 mt-4">
<h2 class="h4 fw-bold mb-2">Security report scope</h2>
<div class="row g-3">
<div class="col-md-6">
<div class="fw-semibold mb-2">Useful reports</div>
<ul class="small mb-0">
<li>Enroll captures clearly sensitive default-denied files without <code>--dangerous</code></li>
<li>Enroll follows a symlink or hardlink in a way that causes privileged file disclosure or overwrite</li>
<li>Enroll extracts a tar member outside the intended harvest directory</li>
<li>Enroll accepts an artifact path that escapes the artifact root</li>
<li>Ordinary harvested data can cause command injection in generated manifests</li>
<li>Enroll silently ignores a failed safety check and proceeds anyway</li>
</ul>
</div>
<div class="col-md-6">
<div class="fw-semibold mb-2">Normally out-of-scope reports</div>
<ul class="small mb-0">
<li>Root can configure Enroll to collect sensitive files</li>
<li>Root can pass <code>--dangerous</code> and collect dangerous data</li>
<li>Root can pass <code>--assume-safe-path</code> and bypass the root <code>PATH</code> warning</li>
<li>Root can point Enroll at a malicious config file</li>
<li>Root can enforce a malicious harvest bundle</li>
<li>A local attacker can influence Enroll after already controlling root's environment or binaries</li>
</ul>
</div>
</div>
</div>
<hr class="my-5">
<div class="feature-card p-4">
<h2 class="h4 fw-bold mb-2">Security researchers</h2>
<div class="row g-3">
<div class="col-md-12">
<div class="fw-semibold">Found a vulnerability in Enroll?</div>
<ul class="small mb-0">
<li>Please contact me using the <a href="https://nr.mig5.net/forms/mig5/contact" target="_blank" rel="noopener noreferrer">contact form</a> or via Signal (<code>mig5.55</code>)</li>
<li>My GPG public key is <a href="https://mig5.net/static/mig5.asc" target="_blank" rel="noopener noreferrer">here</a></li>
<li>Unfortunately, I cannot offer financial bounty rewards at this time, but you will receive full credit for valid security issues.</li>
</ul>
</div>
</div>
</div>
</div>
</main>