Skip to content

feat: container security hardening#14

Merged
dean0x merged 6 commits intomainfrom
feat/container-security-hardening
Feb 21, 2026
Merged

feat: container security hardening#14
dean0x merged 6 commits intomainfrom
feat/container-security-hardening

Conversation

@dean0x
Copy link
Owner

@dean0x dean0x commented Feb 21, 2026

Summary

Hardens container invocation without breaking agent usability. The developer user (UID 1000) never needs Linux capabilities, so all hardening targets root-level operations the agent shouldn't be doing.

  • --cap-drop ALL + selective --cap-add NET_ADMIN only for allowlist mode
  • --security-opt no-new-privileges prevents privilege escalation
  • --pids-limit 4096 prevents fork bombs (agent workloads fit comfortably)
  • Default network changed from host to bridge — isolates container from host services while preserving full internet access
  • Stopped container removal after interactive sessions — prevents credential persistence in podman inspect
  • Network presets (--network-preset dev/registries) with built-in allowlists for GitHub, npm, crates.io, PyPI, AI APIs
  • Interactive network prompt on first run when no network config exists, with option to save preference
  • capsh --drop=cap_net_admin after iptables setup in allowlist mode — agent can't flush firewall rules
  • libcap added to base Dockerfile for capsh binary

Agent operation impact

Operation Impact
npm install, cargo build, git push No impact
Run dev server, tests, edit files No impact
Access host localhost services Blocked by bridge default (use --network host)
iptables -F in allowlist mode Blocked (cap dropped after setup)

Migration from v1.x

Network default changed

The default network mode is now bridge (previously host). Bridge still provides full internet access — npm install, cargo build, git push, AI API calls all work identically. The only difference is localhost isolation.

If your workflow requires access to host localhost services, restore previous behavior with:

mino run --network host -- <command>

Or permanently:

mino config set container.network host

Capability restrictions

All containers now run with --cap-drop ALL. If you use custom images that require specific Linux capabilities, you may see permission errors. Allowlist mode (--network-allow, --network-preset) automatically adds CAP_NET_ADMIN.

Container cleanup

Interactive sessions now remove containers after exit. Use mino logs <session> for session history instead of podman logs.

PID limits

Container processes are limited to 4096 PIDs. This is generous for all normal agent workloads but may affect pathological fork patterns.

Files changed (8)

File Changes
src/orchestration/podman.rs ContainerConfig + cap_drop, security_opt, pids_limit
src/orchestration/native_podman.rs push_container_args with cap-drop before cap-add ordering
src/orchestration/orbstack_runtime.rs Mirror of native runtime changes
src/cli/commands/run.rs Hardening wiring, container removal, network prompt
src/config/schema.rs Default bridge, network_preset field
src/cli/args.rs --network-preset flag
src/network.rs Preset resolver, expanded resolve_network_mode, capsh drop
images/base/Dockerfile libcap package

Test plan

  • 209 unit tests pass (9 new for presets, arg ordering, capsh, TOML upsert)
  • 10 integration tests pass
  • 0 clippy warnings
  • cargo fmt -- --check passes
  • Manual: mino run -- npm install succeeds with bridge networking
  • Manual: mino run --network-preset dev -- curl -I https://registry.npmjs.org succeeds
  • Manual: mino run --network-preset dev -- curl -I https://evil.com blocked
  • Manual: After interactive exit, podman ps -a shows no stopped container
  • Manual: mino run --network host -- curl http://localhost:8080 restores host access

Dean Sharon added 2 commits February 21, 2026 10:11
…ult, presets

Harden container invocation without breaking agent usability:

- Drop all Linux capabilities (--cap-drop ALL), add back NET_ADMIN only
  for network allowlist mode
- Prevent privilege escalation (--security-opt no-new-privileges)
- Limit PID count to 4096 (--pids-limit, prevents fork bombs)
- Change default network from host to bridge (isolated from host services)
- Remove stopped containers after interactive sessions (prevents
  credential persistence in podman inspect)
- Add network presets (--network-preset dev/registries) with built-in
  allowlists for common development destinations
- Add interactive network selection prompt on first run when no network
  config is set, with option to save preference
- Drop CAP_NET_ADMIN via capsh after iptables setup in allowlist mode
- Add libcap to base Dockerfile for capsh binary
- Fix misleading --network help text: "host (default)" -> "bridge (default)"
  to match actual config default changed in this feature branch
- Fix stale NetworkMode::Host doc comment that still said "default"
- Consolidate push_container_args/push_podman_args into ContainerConfig::push_args
  (removes duplicate implementations from NativePodmanRuntime and OrbStackRuntime)
- Remove now-redundant cap_drop/security_opt/pids_limit tests from runtime files
  (tests live in podman.rs where the shared implementation is)
@dean0x
Copy link
Owner Author

dean0x commented Feb 21, 2026

Code Review: Container Security Hardening (Consensus from 4 reviewers)

Status: REQUEST_CHANGES — 3 blocking issues, 5 should-fix items

The security hardening direction is correct and well-executed. --cap-drop ALL, no-new-privileges, pids-limit, default bridge, and container removal are all meaningful improvements. However, three issues need fixing before merge.


🔴 BLOCKING ISSUES

1. capsh fallback silently retains CAP_NET_ADMIN — /src/network.rs:311-315

Problem: When capsh is unavailable, the iptables wrapper fallback runs the user command with CAP_NET_ADMIN still active. An agent could execute iptables -F to bypass the entire allowlist enforcement.

Fix: Fail hard or emit a clear warning:

script.push_str("; else echo 'mino: WARNING: capsh not found, CAP_NET_ADMIN retained by user process' >&2; exec");

Consider making capsh a hard requirement like iptables already is.


2. --network host with --network-preset silently overridden — /src/network.rs:187-196

Problem: When both flags are specified, the preset wins silently. The --network-allow path (line 177-181) warns about this conflict, but the preset path does not.

Fix: Add the same warning for consistency:

if cli_network == Some("host") {
    tracing::warn!(
        "--network host overridden to bridge because --network-preset was specified"
    );
}

3. write_network_to_config duplicates write_layers_to_config/src/cli/commands/run.rs:1027-1076

Problem: Near-identical TOML read-parse-merge-write flow in both functions. Any bug fix to one path must be replicated in the other.

Fix: Extract a shared helper:

async fn upsert_container_toml_key(
    path: &Path, 
    key: &str, 
    value: toml::Value
) -> MinoResult<()>

Both functions become single-line calls.


⚠️ SHOULD-FIX ITEMS

4. Container removal missing in detached mode/src/cli/commands/run.rs:316-371

  • Your comment on line 426 states the purpose: "Remove stopped container to prevent credential persistence."
  • Detached containers inject the same credentials and should also be removed.
  • Fix: Make mino stop call runtime.remove() after stopping.

5. Missing SaveTarget::Local in network save prompt/src/cli/commands/run.rs:988-1024

  • Layer save prompt offers Local/Global/None; network prompt only offers Global/None.
  • Users should be able to save network settings per-project to .mino.toml.

6. No tests for is_default_network/src/cli/commands/run.rs:915-922

  • This gate function has 6 conditions and zero tests (compare: is_default_image has 3 tests).
  • Add tests to prevent regressions on default network value changes.

7. No tests for write_network_to_config/src/cli/commands/run.rs:1027-1076

  • TOML merge logic and error paths untested. Include: new file creation, merge into existing config, merge with pre-existing [container] table, error on non-table root.

8. resolve_network_mode signature is fragile/src/network.rs:157-163

  • 6 positional parameters of similar types make swapping cli_preset and config_preset possible without compiler error.
  • Fix: Introduce a NetworkResolutionInput struct with named fields.

Reviewer Consensus

Finding Reviewers Confidence
capsh fallback bypass Security, Architecture, Quality HIGH (3/4)
--network host preset override Security, Quality, Architecture HIGH (3/4)
TOML config write duplication Quality, Architecture, Security HIGH (3/4)
Missing Local save option Quality, Architecture HIGH (2/4)
resolve_network_mode signature Architecture, Quality HIGH (2/4)
Test gaps Quality MEDIUM (1/4)

Scores

Reviewer Score Rec
Security 6/10 CHANGES_REQUESTED
Architecture 6/10 CHANGES_REQUESTED
Performance 8/10 APPROVED
Quality 5/10 CHANGES_REQUESTED

Fix the 3 blocking issues and address code quality concerns, then this is ready to merge.

- Fail hard when capsh is missing instead of silently retaining
  CAP_NET_ADMIN (security: prevents iptables flush bypass)
- Add warning when --network host is overridden by --network-preset
- Refactor resolve_network_mode to use NetworkResolutionInput struct
  (eliminates 6 positional params of similar types)
- Update module docstring to reflect four network modes + presets
- Eliminate duplicated command iteration in generate_iptables_wrapper
- Add clap value_parser for --network-preset validation
- Add TODO for detached container credential cleanup
- Wire project_dir through to prompt_save_network for .mino.toml support
Repository owner locked and limited conversation to collaborators Feb 21, 2026
Repository owner unlocked this conversation Feb 21, 2026
@dean0x dean0x merged commit 27c0041 into main Feb 21, 2026
6 checks passed
@dean0x dean0x deleted the feat/container-security-hardening branch February 21, 2026 09:15
Repository owner locked as resolved and limited conversation to collaborators Feb 21, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant