Skip to content

feat(#728): allowlist dashboard-confirmed data-dir moves to the stack data root#734

Merged
VijitSingh97 merged 2 commits into
develop-v1.11from
feat/728-datadir-allowlist
Jul 21, 2026
Merged

feat(#728): allowlist dashboard-confirmed data-dir moves to the stack data root#734
VijitSingh97 merged 2 commits into
develop-v1.11from
feat/728-datadir-allowlist

Conversation

@VijitSingh97

Copy link
Copy Markdown
Collaborator

What

Closes #728. #719 made the four *_DATA_DIR moves confirm-gated, so a dashboard operator who types APPLY can now relocate a service's data dir. The destination was still checked only by assert_safe_dir — a blocklist that refuses the catastrophic roots (/, $HOME, bare mounts) but passes any other absolute path. At host-shell trust that is proportionate; at dashboard trust it lets a confirmed move target another user's home or another service's volume and have pithead mkdir/chown -R it and bind-mount it into a recreated container — a destination trust-escalation.

Control-vs-host split

control_approval_gate runs only for dashboard commits — it is called solely from control_commit (the control runner's commit path); the host ./pithead apply path never calls it and keeps rendering through parse_and_validate_configassert_safe_dir. So the gate itself is the control-channel signal; no new env var or flag is needed. The allowlist lives inside the gate, scoping it to control-originated moves automatically.

The allowlist

For a control-channel move, the destination is narrowed from a blocklist to an allowlist: permit only a path under

Anything else is refused even with the APPLY token and stays host-CLI only. Only explicit absolute paths are checked; auto/empty resolves to a stack default that is under a data root by construction. assert_safe_dir still runs at apply time.

Tests (tier-1 stack, tests/stack/run.sh)

  • A control-confirmed move under the data root ($C/data/monero-v2) applies with APPLY.
  • A move to an arbitrary non-blocklisted-but-non-allowed path ($SANDBOX/other-service-vol/monero) is refused even with APPLY, cleared from staged, config.json untouched.
  • The same path from the host shell still applies — the tighter rule is control-only.

Docs

SECURITY.md, docs/dashboard.md, CHANGELOG.md.

Note

This is a security change to the control gate — a security-reviewer pass is warranted.

🤖 Generated with Claude Code

VijitSingh97 and others added 2 commits July 21, 2026 16:10
… data root

#719 made the four *_DATA_DIR moves confirm-gated, so a dashboard operator who
types APPLY can now relocate a service's data dir. The destination was still
checked only by assert_safe_dir — a BLOCKLIST that refuses the catastrophic
roots but passes any other absolute path. At host-shell trust that is
proportionate; at dashboard trust it lets a confirmed move target another
user's home or another service's volume and have pithead mkdir/chown -R it and
bind-mount it into a recreated container — a destination trust-escalation.

control_approval_gate runs ONLY for dashboard commits (the host `apply` path
never calls it), so it is exactly where the control-only rule belongs. For a
control-channel move, narrow the destination from a blocklist to an ALLOWLIST:
permit only a path under the stack's own data root ($PWD/data) or a parent the
stack already keeps data in (each live *_DATA_DIR's parent — covers a #455
co-located data root). Anything else is refused even with the APPLY token and
stays host-CLI only. The host `./pithead apply` path keeps the wider blocklist.

Tier-1 stack tests: a control-confirmed move under the data root applies with
APPLY; a move to an arbitrary non-blocklisted-but-non-allowed path is refused
even with APPLY; the same path from the host shell still applies.

Docs: SECURITY.md, docs/dashboard.md, CHANGELOG.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…wildcard

Security review flagged the skip-condition `DYNAMIC_*` as broader than the
values that actually resolve to an in-root stack default. resolve_default
defaults exactly "" | auto | DYNAMIC_DATA | DYNAMIC_HOST | DYNAMIC_ID and passes
anything else (e.g. DYNAMIC_FOO) through literally. Mirror that exact set so the
allowlist skip can never swallow a non-sentinel, and pin the assert_safe_dir-
runs-first ordering that keeps `..`/relative dests from reaching this block.

Not exploitable before this change (a DYNAMIC_-prefixed string can't be
absolute, and assert_safe_dir refuses it in the dry-run re-derivation first) —
tightens a latent defense-in-depth mismatch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@VijitSingh97
VijitSingh97 merged commit bb9cf55 into develop-v1.11 Jul 21, 2026
15 checks passed
@VijitSingh97
VijitSingh97 deleted the feat/728-datadir-allowlist branch July 21, 2026 23:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant