Skip to content

Commit 0c807d0

Browse files
authored
Improve security. (#4)
1 parent f75dff6 commit 0c807d0

18 files changed

Lines changed: 573 additions & 95 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,13 @@ dvm list
8888
dvm rm <name> [--force]
8989
dvm ai create|setup|pull|models|use|status|host ...
9090
dvm agent setup|install|<name> ...
91-
dvm gpg create|install|revoke ...
91+
dvm gpg create|install|forget|revoke ...
9292
dvm doctor
9393
dvm completion zsh
9494
```
9595

96-
`dvm <name>` is a shortcut for `dvm enter <name>`.
96+
`dvm <name>` is a shortcut for `dvm enter <name>`. `dvm <name> <command...>` runs a
97+
single command in that VM, like `dvm ssh <name> <command...>`.
9798

9899
## Docs
99100

bin/dvm

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,13 @@ usage:
5050
dvm rm <name> [--force]
5151
dvm ai create|setup|pull|models|use|status|host ...
5252
dvm agent setup|install|<name> ...
53-
dvm gpg create|install|revoke ...
53+
dvm gpg create|install|forget|revoke ...
5454
dvm doctor
5555
dvm completion zsh
5656
5757
Shortcut:
5858
dvm <name> enters that VM.
59+
dvm <name> <command...> runs a command in that VM.
5960
HELP
6061
}
6162

@@ -82,7 +83,11 @@ dvm_main() {
8283
*)
8384
dvm_load_config
8485
if dvm_list_names | grep -Fxq "$cmd"; then
85-
dvm_enter "$cmd"
86+
if [ "$#" -eq 0 ]; then
87+
dvm_enter "$cmd"
88+
else
89+
dvm_ssh "$cmd" "$@"
90+
fi
8691
else
8792
dvm_help
8893
dvm_die "unknown command or VM: $cmd"

defaults/config.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ DVM_PACKAGES="${DVM_PACKAGES:-git openssh-clients gpg}"
2020
# Space-separated host scripts. Each script is piped into the VM and runs as the
2121
# guest user with DVM_NAME, DVM_VM_NAME, and DVM_CODE_DIR set.
2222
DVM_SETUP_SCRIPTS="${DVM_SETUP_SCRIPTS:-$DVM_CONFIG/setup.d/fedora.sh}"
23+
DVM_SETUP_ALL_JOBS="${DVM_SETUP_ALL_JOBS:-1}"
2324

2425
# Optional host dotfiles snapshot copied into the VM during `dvm setup`.
2526
# Keep this opt-in. DVM copies a snapshot before user setup scripts run; it does
@@ -34,11 +35,12 @@ DVM_AI_NAME="${DVM_AI_NAME:-ai}"
3435
DVM_AI_PACKAGES="${DVM_AI_PACKAGES:-llama-cpp curl}"
3536
DVM_AI_SERVER_CMD="${DVM_AI_SERVER_CMD:-llama-server}"
3637
DVM_AI_SERVICE_NAME="${DVM_AI_SERVICE_NAME:-dvm-llama.service}"
37-
DVM_AI_HOST="${DVM_AI_HOST:-0.0.0.0}"
38+
DVM_AI_HOST="${DVM_AI_HOST:-127.0.0.1}"
3839
DVM_AI_PORT="${DVM_AI_PORT:-8080}"
3940
DVM_AI_MODELS_DIR="${DVM_AI_MODELS_DIR:-$DVM_GUEST_HOME/models}"
4041
DVM_AI_DEFAULT_MODEL="${DVM_AI_DEFAULT_MODEL:-qwen25-coder-7b-q4}"
41-
# Space-separated alias=url entries. Aliases become model filenames in the VM.
42+
# Space-separated alias=url entries. URLs must use HTTPS. Aliases become model
43+
# filenames in the VM. Add an optional trailing #sha256:<hex> to verify downloads.
4244
DVM_AI_MODELS="${DVM_AI_MODELS:-qwen25-coder-7b-q4=https://huggingface.co/bartowski/Qwen2.5-Coder-7B-Instruct-GGUF/resolve/main/Qwen2.5-Coder-7B-Instruct-Q4_K_M.gguf?download=true}"
4345
DVM_AI_EXTRA_ARGS="${DVM_AI_EXTRA_ARGS:-}"
4446

docs/ai-tools.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ When you run `dvm agent <name> -- <command>`, DVM starts the VM and runs the com
6969
- exposes `DVM_CODE_DIR` read/write
7070
- provides private writable `/tmp` and `/var/tmp`
7171
- hides the normal VM user's home and binds `DVM_CODE_DIR` back into place
72+
- unshares PID, IPC, UTS, and cgroup namespaces
7273
- does not unshare networking, so hosted AI tools and package managers still work
7374

7475
This means the agent can run project commands such as:

docs/ai-vm.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,16 @@ Other useful knobs:
2424
DVM_AI_PACKAGES="llama-cpp curl"
2525
DVM_AI_SERVER_CMD="llama-server"
2626
DVM_AI_SERVICE_NAME="dvm-llama.service"
27-
DVM_AI_HOST="0.0.0.0"
27+
DVM_AI_HOST="127.0.0.1"
2828
DVM_AI_MODELS_DIR="$DVM_GUEST_HOME/models"
2929
DVM_AI_EXTRA_ARGS=""
3030
```
3131

32-
Model entries are space-separated `alias=url` pairs. Aliases become filenames in the
33-
VM, so `qwen=https://...` is saved as `qwen.gguf`.
32+
Model entries are space-separated `alias=url` pairs. URLs must use HTTPS. Aliases
33+
become filenames in the VM, so `qwen=https://...` is saved as `qwen.gguf`.
34+
If `DVM_AI_DEFAULT_MODEL` is set, it must match one of the configured aliases.
35+
Add `#sha256:<64-hex>` to a model entry to verify the downloaded file before it is
36+
installed.
3437

3538
## Create
3639

@@ -78,6 +81,10 @@ Print host URLs:
7881
dvm ai host
7982
```
8083

84+
By default llama-server listens on `127.0.0.1` inside the VM. Set
85+
`DVM_AI_HOST="0.0.0.0"` only if you intentionally want the service reachable on the
86+
guest VM network address.
87+
8188
Reapply service configuration:
8289

8390
```bash

docs/config.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ DVM_DISK="80GiB"
2121

2222
DVM_PACKAGES="git openssh-clients gpg helix ripgrep fd-find jq"
2323
DVM_SETUP_SCRIPTS="$DVM_CONFIG/setup.d/fedora.sh"
24+
DVM_SETUP_ALL_JOBS="1"
2425
DVM_DOTFILES_DIR="$HOME/.dotfiles"
2526
```
2627

@@ -60,6 +61,8 @@ Safety rules:
6061
- dotfiles sync is opt-in
6162
- source paths such as `/`, `$HOME`, `~/.ssh`, and `~/.gnupg` are refused
6263
- target paths must stay under `DVM_GUEST_HOME`
64+
- target paths must not be `DVM_GUEST_HOME` itself and must not contain `.` or `..`
65+
path segments
6366
- `.git`, `.ssh`, `.gnupg`, `.env`, and `secrets` are excluded by default
6467

6568
Example setup script:

docs/gpg.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,20 @@ dvm gpg install myapp ~/.local/share/dvm/gpg/myapp-secret-subkey.asc
4545
dvm gpg install myapp --signing-key <fingerprint>
4646
```
4747

48+
When installing the generated bundle recorded in DVM metadata, `dvm gpg install`
49+
removes that host-side secret bundle after a successful import. Pass `--keep-secret`
50+
if you need to retain the bundle temporarily:
51+
52+
```bash
53+
dvm gpg install myapp --keep-secret
54+
```
55+
56+
Forget a recorded generated bundle without installing it:
57+
58+
```bash
59+
dvm gpg forget myapp
60+
```
61+
4862
## Revoke
4963

5064
```bash
@@ -56,7 +70,6 @@ not:
5670

5771
- update GitHub, GitLab, or other remote services
5872
- remove old public keys from places where they were trusted
59-
- delete secret bundles from disk
6073
- change anything inside an already-deleted VM
6174

6275
Upload the updated public key wherever the old public key was trusted.

docs/vms.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ The shell starts in `DVM_CODE_DIR`, which defaults to `~/code` in the guest.
4141
Run a single command in the VM:
4242

4343
```bash
44+
dvm myapp uname -a
4445
dvm ssh myapp uname -a
4546
```
4647

@@ -67,6 +68,10 @@ dvm setup-all
6768
This is the intended way to refresh packages, config, and dotfiles snapshots. DVM does
6869
not remove packages automatically; removals should be explicit and manual.
6970

71+
`setup-all` continues after individual VM failures and prints a summary at the end.
72+
It runs sequentially by default. Set `DVM_SETUP_ALL_JOBS` higher than `1` to run setup
73+
for multiple VMs in parallel.
74+
7075
## List
7176

7277
```bash

lib/agent.sh

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,18 @@ sudo setfacl -m "u:$agent_user:rwx" "$code_dir"
6868
sudo setfacl -R -m "u:$agent_user:rwX" "$code_dir"
6969
sudo find "$code_dir" -type d -exec setfacl -d -m "u:$agent_user:rwx" {} +
7070
71+
add_safe_directory() {
72+
safe_dir="$1"
73+
if ! sudo -H -u "$agent_user" env HOME="$agent_home" \
74+
git config --global --get-all safe.directory 2>/dev/null | grep -Fxq "$safe_dir"; then
75+
sudo -H -u "$agent_user" env HOME="$agent_home" \
76+
git config --global --add safe.directory "$safe_dir" || true
77+
fi
78+
}
79+
7180
if command -v git >/dev/null 2>&1; then
72-
sudo -H -u "$agent_user" env HOME="$agent_home" \
73-
git config --global --add safe.directory "$code_dir/*" || true
81+
add_safe_directory "$code_dir"
82+
add_safe_directory "$code_dir/*"
7483
fi
7584
7685
printf 'agent user: %s\n' "$agent_user"
@@ -112,6 +121,10 @@ id -u "$agent_user" >/dev/null 2>&1 || {
112121
113122
bwrap_args=(
114123
--die-with-parent
124+
--unshare-pid
125+
--unshare-ipc
126+
--unshare-uts
127+
--unshare-cgroup
115128
--proc /proc
116129
--dev /dev
117130
--ro-bind / /

0 commit comments

Comments
 (0)