Harden network helpers and compat-dir mounts per security review

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 00:45:27 +02:00
co-authored by Claude Sonnet 5
parent 4c4bb1a83a
commit 598a585eb8
53 changed files with 1151 additions and 80 deletions
+23 -3
View File
@@ -23,6 +23,7 @@ struct Parsed {
Options opts;
std::optional<std::string> sha256;
bool yes = false;
bool iUnderstand = false; // --i-understand, required for relaxed / --net host
std::vector<std::string> positional; // up to maxPositional, the rest lands in tail
std::vector<std::string> tail;
};
@@ -59,6 +60,7 @@ Parsed parse(const std::vector<std::string>& rest, Allowed allow, std::size_t wa
p.opts.relaxed = (v == "relaxed");
}
else if (allow.yes && a == "--yes") p.yes = true;
else if (a == "--i-understand") p.iUnderstand = true;
else if (allow.sha256 && is(a, "--sha256")) p.sha256 = value(i, "--sha256");
else if (a.size() > 1 && a[0] == '-') throw std::runtime_error("unknown option " + a);
else if (p.positional.size() < wantPositional) p.positional.push_back(a);
@@ -70,6 +72,21 @@ Parsed parse(const std::vector<std::string>& rest, Allowed allow, std::size_t wa
return p;
}
void hardenNotice(const Container& c, const Parsed& p) {
bool relaxed = p.opts.relaxed.value_or(c.relaxed());
bool hostNet = (p.opts.net.has_value() ? *p.opts.net : c.net()) == NetMode::Host;
if ((relaxed || hostNet) && !p.iUnderstand) {
throw std::runtime_error("--hardening relaxed and --net host drop capability drop, seccomp filtering and/or "
"network isolation; pass --i-understand to confirm you accept the reduced sandboxing");
}
if (relaxed || hostNet) {
std::fprintf(stderr, "doos: warning: reduced sandboxing (%s%s%s)\n",
relaxed ? "no capability drop, no seccomp, full /etc" : "",
(relaxed && hostNet) ? "; " : "",
hostNet ? "shares the host network namespace" : "");
}
}
std::uint32_t parseCount(const std::string& text) {
if (text.empty() || text.size() > 9 || text.find_first_not_of("0123456789") != std::string::npos) {
throw std::runtime_error("invalid number '" + text + "'");
@@ -117,9 +134,10 @@ void Cli::usage(std::FILE* out) {
"options:\n"
" --image NAME start from an image instead of the host system (fetched on demand) [fixed]\n"
" --net slirp|pasta|host|none network, default slirp (pasta: host-like, private sockets) [fixed]\n"
" --ids single|subuid id mapping, default: subuid when available [fixed]\n"
" --hardening default|relaxed relaxed = no capability drop, no seccomp, full /etc [fixed]\n"
" --size SIZE writable-layer cap, 64m..1t, default 4g\n"
" --ids single|subuid id mapping, default: subuid when available [fixed]\n"
" --hardening default|relaxed relaxed = no capability drop, no seccomp, full /etc [fixed]\n"
" --i-understand confirm you accept reduced sandboxing when using relaxed or --net host\n"
" --size SIZE writable-layer cap, 64m..1t, default 4g\n"
" --memory SIZE memory cap, 64m..1t, default 8g\n"
" --pids N process cap, 16..4194304, default 4096\n"
" --cpus N cpu cap, 0.1..1024, default 0 (all cores)\n"
@@ -169,6 +187,7 @@ int Cli::cmdGetOrCreate(std::vector<std::string> rest) {
if (!p.tail.empty()) throw std::runtime_error("get_or_create takes only a name");
Store store = Store::fromEnvironment();
auto [c, created] = store.getOrCreate(p.positional[0], p.opts);
hardenNotice(c, p);
std::printf("%s %s\n", created ? "created" : "exists", c.name().c_str());
return 0;
}
@@ -178,6 +197,7 @@ int Cli::cmdAttach(std::vector<std::string> rest) {
Store store = Store::fromEnvironment();
auto [c, created] = store.getOrCreate(p.positional[0], p.opts);
(void)created;
hardenNotice(c, p);
return Sandbox(std::move(c), std::move(p.tail)).run();
}