docs(06): create phase 6 plan — SSH mount, repo volumes, session-init
This commit is contained in:
390
.planning/phases/06-default-repos-ssh-mount/06-01-PLAN.md
Normal file
390
.planning/phases/06-default-repos-ssh-mount/06-01-PLAN.md
Normal file
@@ -0,0 +1,390 @@
|
||||
---
|
||||
phase: 06-default-repos-ssh-mount
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- ~/.hermes/config.yaml
|
||||
- ~/.hermes/.env
|
||||
- ~/.hermes/scripts/session-init.sh
|
||||
autonomous: true
|
||||
requirements:
|
||||
- REPO-01
|
||||
- REPO-02
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "New Hermes Docker container has SSH keys mounted at /root/.ssh/ — git clone to Bitbucket works without manual auth"
|
||||
- "DEFAULT_REPOS (rai-ops, rai-deployment, rai-devtools) are available at /workspace/<name> in every new session"
|
||||
- "Container restart does NOT lose repo mounts — repos persist because they're host-mounted, not cloned"
|
||||
- "SSH credentials are mounted read-only inside Docker — agent cannot modify keys even as root"
|
||||
- "session-init.sh logs repo mount status at shell start without blocking the agent prompt"
|
||||
- "DEFAULT_REPOS is configurable via .env — user adds/removes repos by editing one variable + docker_volumes"
|
||||
artifacts:
|
||||
- path: "~/.hermes/scripts/session-init.sh"
|
||||
provides: "Mount verification script for DEFAULT_REPOS at session start"
|
||||
min_lines: 25
|
||||
- path: "~/.hermes/config.yaml"
|
||||
provides: "Docker volume mounts (SSH keys + repos), shell_init_files, docker_forward_env"
|
||||
contains: "docker_volumes.*id_ed25519razer"
|
||||
- path: "~/.hermes/.env"
|
||||
provides: "DEFAULT_REPOS environment variable"
|
||||
contains: "DEFAULT_REPOS"
|
||||
key_links:
|
||||
- from: "~/.hermes/config.yaml (docker_volumes)"
|
||||
to: "~/.ssh/id_ed25519razer"
|
||||
via: "volume mount"
|
||||
pattern: "id_ed25519razer.*ro"
|
||||
- from: "~/.hermes/config.yaml (docker_volumes)"
|
||||
to: "~/Razer/rai-ops"
|
||||
via: "volume mount"
|
||||
pattern: "rai-ops.*rw"
|
||||
- from: "~/.hermes/config.yaml (shell_init_files)"
|
||||
to: "~/.hermes/scripts/session-init.sh"
|
||||
via: "shell_init_files path"
|
||||
pattern: "session-init.sh"
|
||||
- from: "~/.hermes/.env"
|
||||
to: "~/.hermes/config.yaml (docker_forward_env)"
|
||||
via: "env forwarding"
|
||||
pattern: "DEFAULT_REPOS"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Mount DEFAULT_REPOS (rai-ops, rai-deployment, rai-devtools) directly from host filesystem into every new Hermes Docker session with SSH credentials mounted read-only for git operations, plus a lightweight session-init.sh verification script.
|
||||
|
||||
Purpose: Eliminate the #1 user friction point — manually cloning repos every session. Host-direct mounts preserve existing git worktrees, branches, and uncommitted changes across container restarts (D-02). SSH keys are mounted per-file as read-only (`:ro`) to limit credential exposure (D-01).
|
||||
|
||||
Output:
|
||||
- `~/.hermes/scripts/session-init.sh` — non-blocking mount verification script
|
||||
- `~/.hermes/.env` — `DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools`
|
||||
- `~/.hermes/config.yaml` — 4 SSH key mounts, 3 repo mounts, shell_init_files, docker_forward_env additions
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/Users/bapung/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||
@/Users/bapung/.config/opencode/gsd-core/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@/Users/bapung/.planning/PROJECT.md
|
||||
@/Users/bapung/.planning/ROADMAP.md
|
||||
|
||||
# Current Hermes config for editing
|
||||
@/Users/bapung/.hermes/config.yaml
|
||||
@/Users/bapung/.hermes/.env
|
||||
|
||||
# SSH config for reference
|
||||
@/Users/bapung/.ssh/config
|
||||
|
||||
# Existing scripts for shebang/pattern consistency
|
||||
@/Users/bapung/.hermes/scripts/ngn-jira
|
||||
@/Users/bapung/.hermes/scripts/ngn-bitbucket
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Create session-init.sh mount verification script</name>
|
||||
<files>~/.hermes/scripts/session-init.sh</files>
|
||||
<read_first>
|
||||
~/.hermes/scripts/ngn-jira (shebang pattern)
|
||||
~/.hermes/scripts/ngn-bitbucket (shebang pattern)
|
||||
This file's RESEARCH.md §Code Examples for the reference implementation
|
||||
</read_first>
|
||||
<action>
|
||||
Create `~/.hermes/scripts/session-init.sh` with the following behavior (per D-03, D-04):
|
||||
|
||||
- **Shebang:** `#!/bin/bash`
|
||||
- **set options:** `set -uo pipefail` — deliberately NOT `-e` so missing repos don't abort the script (non-blocking session start per D-03)
|
||||
- **Reads DEFAULT_REPOS** from environment (forwarded via `docker_forward_env` per D-04)
|
||||
- **Splits comma-separated list** and trims whitespace from each entry
|
||||
- **For each repo:** checks `[ -d "/workspace/$repo/.git" ]` — confirm it's a valid git checkout, not just an empty dir
|
||||
- **Logs:**
|
||||
- `[session-init] ✓ repo — mounted at /workspace/repo` on success
|
||||
- `[session-init] ⚠ repo — NOT FOUND at /workspace/repo` on failure
|
||||
- **Summary:** "All DEFAULT_REPOS verified" vs "Some repos missing — check docker_volumes in config.yaml"
|
||||
- **Exit code:** Always 0 — session starts regardless (discretion area)
|
||||
- **Empty DEFAULT_REPOS guard:** If env var unset/empty, log "[session-init] DEFAULT_REPOS not set — skipping verification" and exit 0
|
||||
|
||||
**Do NOT** include:
|
||||
- `set -e` — would abort on first missing repo (blocks session start)
|
||||
- Any `git clone`, `git pull`, or network operations — script must complete in <1s (ROADMAP success criteria 5: 30s timeout)
|
||||
- Any file writes or modifications — this is a read-only checker
|
||||
|
||||
Make the script executable: `chmod +x ~/.hermes/scripts/session-init.sh`
|
||||
</action>
|
||||
<verify>
|
||||
<automated>bash -n ~/.hermes/scripts/session-init.sh 2>&1; chmod +x ~/.hermes/scripts/session-init.sh 2>/dev/null; stat -f "%Sp" ~/.hermes/scripts/session-init.sh | grep -q -- "-rwx"</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
1. Script passes bash syntax check (`bash -n` exits 0)
|
||||
2. Script is executable (`stat` shows `-rwx` permissions)
|
||||
3. Logic: does NOT use `set -e`, DOES use `set -uo pipefail`
|
||||
4. Guard: if DEFAULT_REPOS unset, exits 0 with log message (not error)
|
||||
5. Verification: `test -d "/workspace/$repo/.git"` (not just `test -d "/workspace/$repo"`)
|
||||
6. Always exits 0 regardless of mount status
|
||||
</acceptance_criteria>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Add DEFAULT_REPOS to .env and update config.yaml with mounts + shell_init_files + forward_env</name>
|
||||
<files>~/.hermes/.env, ~/.hermes/config.yaml</files>
|
||||
<read_first>
|
||||
~/.hermes/.env (full file — already read in planning context)
|
||||
~/.hermes/config.yaml (full file — already read in planning context)
|
||||
The RESEARCH.md §Code Examples for exact mount strings
|
||||
</read_first>
|
||||
<action>
|
||||
Make the following changes:
|
||||
|
||||
### A. `~/.hermes/.env` — Append DEFAULT_REPOS
|
||||
At the tail of the file, before the last line (HINDSIGHT_LLM_API_KEY) or after it, add:
|
||||
|
||||
```bash
|
||||
# DEFAULT_REPOS — repos mounted into every session workspace
|
||||
# Comma-separated list. Each repo must have a matching docker_volume entry in config.yaml.
|
||||
DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools
|
||||
```
|
||||
|
||||
Place this after the LAST existing section (after the "# ngn-agent: OpenRouter fallback" block, near where JIRA_API_TOKEN and HINDSIGHT_LLM_API_KEY are). Group it with the other ngn-agent-specific vars.
|
||||
|
||||
### B. `~/.hermes/config.yaml` — Append SSH key mounts to `terminal.docker_volumes`
|
||||
Add these 4 entries to the `docker_volumes` list (after the existing scripts mount). Each entry is a quoted string. Per D-01:
|
||||
|
||||
```yaml
|
||||
terminal:
|
||||
docker_volumes:
|
||||
# ... existing entries remain unchanged ...
|
||||
# SSH key mounts (read-only — per D-01)
|
||||
- /Users/bapung/.ssh/id_ed25519razer:/root/.ssh/id_ed25519razer:ro
|
||||
- /Users/bapung/.ssh/id_rsa:/root/.ssh/id_rsa:ro
|
||||
- /Users/bapung/.ssh/config:/root/.ssh/config:ro
|
||||
- /Users/bapung/.ssh/known_hosts:/root/.ssh/known_hosts:ro
|
||||
# Repo mounts (read-write — per D-02)
|
||||
- /Users/bapung/Razer/rai-ops:/workspace/rai-ops:rw
|
||||
- /Users/bapung/Razer/rai-deployment:/workspace/rai-deployment:rw
|
||||
- /Users/bapung/Razer/rai-devtools:/workspace/rai-devtools:rw
|
||||
```
|
||||
|
||||
**WHY `known_hosts` is included (research finding):** Without `/root/.ssh/known_hosts`, SSH prompts interactively for host key confirmation on first Bitbucket connection. Since the container is non-interactive, this hangs the clone. The host's `~/.ssh/known_hosts` already contains bitbucket.org host keys (verified: 3 keys present).
|
||||
|
||||
### C. `~/.hermes/config.yaml` — Set `terminal.shell_init_files`
|
||||
Change from `shell_init_files: []` to:
|
||||
|
||||
```yaml
|
||||
terminal:
|
||||
shell_init_files:
|
||||
- /usr/local/bin/session-init.sh
|
||||
```
|
||||
|
||||
Per D-03. The `~/.hermes/scripts/` directory is already mounted to `/usr/local/bin:ro`, so `/usr/local/bin/session-init.sh` resolves automatically.
|
||||
|
||||
### D. `~/.hermes/config.yaml` — Append `DEFAULT_REPOS` to `terminal.docker_forward_env`
|
||||
Change from `docker_forward_env: [JIRA_EMAIL, JIRA_API_TOKEN]` to include `DEFAULT_REPOS`:
|
||||
|
||||
```yaml
|
||||
terminal:
|
||||
docker_forward_env:
|
||||
- JIRA_EMAIL
|
||||
- JIRA_API_TOKEN
|
||||
- DEFAULT_REPOS # per D-04
|
||||
```
|
||||
|
||||
**Important YAML formatting rules:**
|
||||
- `docker_volumes` entries are list items under the existing key — append to the existing list, do NOT replace the entire list.
|
||||
- `shell_init_files` replaces the existing empty list `[]` with a new list containing one item.
|
||||
- `docker_forward_env` appends to the existing list — do NOT replace the existing entries.
|
||||
- Maintain consistent indentation (2-space YAML).
|
||||
- Preserve all other config keys untouched.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>
|
||||
# Verify config.yaml is valid YAML
|
||||
python3 -c "import yaml; c=yaml.safe_load(open('$HOME/.hermes/config.yaml')); volumes=c['terminal']['docker_volumes']; assert any('id_ed25519razer' in v for v in volumes), 'SSH key mount missing'; assert any('rai-ops' in v for v in volumes), 'Repo mount missing'; assert c['terminal']['shell_init_files'] == ['/usr/local/bin/session-init.sh'], 'shell_init_files wrong'; assert 'DEFAULT_REPOS' in c['terminal']['docker_forward_env'], 'DEFAULT_REPOS not in forward_env'" 2>&1
|
||||
|
||||
# Verify .env has DEFAULT_REPOS
|
||||
grep -q 'DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools' $HOME/.hermes/.env && echo "ENV: OK" || echo "ENV: MISSING"
|
||||
|
||||
# Verify SSH key entries are :ro (read-only)
|
||||
python3 -c "import yaml; c=yaml.safe_load(open('$HOME/.hermes/config.yaml')); vols=c['terminal']['docker_volumes']; ro=[v for v in vols if 'id_ed25519razer' in v]; assert ro and ro[0].endswith(':ro'), f'SSH key not :ro: {ro}'" 2>&1
|
||||
|
||||
# Verify repo entries are :rw (read-write)
|
||||
python3 -c "import yaml; c=yaml.safe_load(open('$HOME/.hermes/config.yaml')); vols=c['terminal']['docker_volumes']; rw=[v for v in vols if 'rai-ops' in v]; assert rw and rw[0].endswith(':rw'), f'Repo mount not :rw: {rw}'" 2>&1
|
||||
</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
1. `config.yaml` has 4 SSH key mount entries under `docker_volumes`, all `:ro`
|
||||
2. `config.yaml` has 3 repo mount entries (`rai-ops`, `rai-deployment`, `rai-devtools`) under `docker_volumes`, all `:rw`
|
||||
3. `config.yaml` `shell_init_files` is set to `["/usr/local/bin/session-init.sh"]`
|
||||
4. `config.yaml` `docker_forward_env` includes `DEFAULT_REPOS` alongside `JIRA_EMAIL` and `JIRA_API_TOKEN`
|
||||
5. `.env` has `DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools`
|
||||
6. All YAML is valid (python yaml parser confirms)
|
||||
7. Original config entries are preserved (not deleted)
|
||||
</acceptance_criteria>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Verify end-to-end — SSH auth, repo mounts, script execution via Docker test</name>
|
||||
<files>(no files modified — verification only)</files>
|
||||
<read_first>
|
||||
Phase research §Verified Findings (SSH auth, subpath mounts, known_hosts requirements)
|
||||
</read_first>
|
||||
<action>
|
||||
Run a Docker test container with the EXACT volume mounts defined in Task 2 to verify everything works end-to-end before the agent needs it.
|
||||
|
||||
Use the nikolaik/python-nodejs:python3.11-nodejs20 image (same as Hermes uses).
|
||||
|
||||
Docker command:
|
||||
```bash
|
||||
docker run --rm \
|
||||
-v ~/.ssh/id_ed25519razer:/root/.ssh/id_ed25519razer:ro \
|
||||
-v ~/.ssh/id_rsa:/root/.ssh/id_rsa:ro \
|
||||
-v ~/.ssh/config:/root/.ssh/config:ro \
|
||||
-v ~/.ssh/known_hosts:/root/.ssh/known_hosts:ro \
|
||||
-v ~/Razer/rai-ops:/workspace/rai-ops:rw \
|
||||
-v ~/Razer/rai-deployment:/workspace/rai-deployment:rw \
|
||||
-v ~/Razer/rai-devtools:/workspace/rai-devtools:rw \
|
||||
-v ~/.hermes/scripts:/usr/local/bin:ro \
|
||||
-e DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools \
|
||||
nikolaik/python-nodejs:python3.11-nodejs20 \
|
||||
bash -c '
|
||||
echo "=== 1. SSH Keys Mounted ==="
|
||||
ls -la /root/.ssh/ && echo ""
|
||||
|
||||
echo "=== 2. SSH Authentication ==="
|
||||
ssh -T git@bitbucket.org 2>&1 || true && echo ""
|
||||
|
||||
echo "=== 3. Repo Mounts ==="
|
||||
for repo in rai-ops rai-deployment rai-devtools; do
|
||||
if [ -d "/workspace/$repo/.git" ]; then
|
||||
echo " ✓ $repo — mounted git repo"
|
||||
else
|
||||
echo " ✗ $repo — NOT FOUND"
|
||||
fi
|
||||
done && echo ""
|
||||
|
||||
echo "=== 4. session-init.sh ==="
|
||||
session-init.sh && echo "exit code: $?" && echo ""
|
||||
|
||||
echo "=== 5. On-Demand Clone (REPO-02) ==="
|
||||
git clone --depth 1 git@bitbucket.org:razersw/rai-ansible.git /workspace/rai-ansible 2>&1 && \
|
||||
echo " ✓ Clone succeeded" || echo " ✗ Clone failed"
|
||||
'
|
||||
```
|
||||
|
||||
**Expected output:**
|
||||
- SSH keys present at `/root/.ssh/` with correct permissions
|
||||
- `ssh -T git@bitbucket.org` returns "authenticated via ssh key" (exit code 1 from SSH is expected — this is auth success, just no shell)
|
||||
- All 3 repos show `✓ repo — mounted git repo`
|
||||
- `session-init.sh` runs without error, shows all repos verified
|
||||
- `git clone` of an additional repo succeeds
|
||||
|
||||
**If ANY check fails:** Diagnose and fix:
|
||||
- SSH auth fails: check key permissions (need `600`/`700`), check `known_hosts` has bitbucket.org entries
|
||||
- Repo mount fails: verify the repo directories exist on host at `~/Razer/<name>/`
|
||||
- session-init.sh fails: debug the script logic
|
||||
- Clone fails: check SSH config file mounting
|
||||
|
||||
Also verify the parent `/workspace` mount mode (research Pitfall 1):
|
||||
```bash
|
||||
docker run --rm \
|
||||
-v /Users/bapung/Razer/ngn-agent:/workspace:rw \
|
||||
nikolaik/python-nodejs:python3.11-nodejs20 \
|
||||
mount | grep /workspace
|
||||
```
|
||||
This should show `rw` — NOT `ro`. If `ro`, the parent mount blocks subpath volume creation and the repo mounts will fail at container start.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>
|
||||
# Run the verification and check output for success signals
|
||||
TEST_OUTPUT=$(docker run --rm \
|
||||
-v ~/.ssh/id_ed25519razer:/root/.ssh/id_ed25519razer:ro \
|
||||
-v ~/.ssh/id_rsa:/root/.ssh/id_rsa:ro \
|
||||
-v ~/.ssh/config:/root/.ssh/config:ro \
|
||||
-v ~/.ssh/known_hosts:/root/.ssh/known_hosts:ro \
|
||||
-v ~/Razer/rai-ops:/workspace/rai-ops:rw \
|
||||
-v ~/Razer/rai-deployment:/workspace/rai-deployment:rw \
|
||||
-v ~/Razer/rai-devtools:/workspace/rai-devtools:rw \
|
||||
-v ~/.hermes/scripts:/usr/local/bin:ro \
|
||||
-e DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools \
|
||||
nikolaik/python-nodejs:python3.11-nodejs20 \
|
||||
bash -c 'ssh -T git@bitbucket.org 2>&1 | grep -q "authenticated" && echo "SSH_AUTH_OK" || echo "SSH_AUTH_FAIL"; for r in rai-ops rai-deployment rai-devtools; do [ -d "/workspace/$r/.git" ] && echo "REPO_${r}_OK" || echo "REPO_${r}_FAIL"; done; session-init.sh 2>&1 | grep -q "All DEFAULT_REPOS verified" && echo "SCRIPT_OK" || echo "SCRIPT_FAIL"; git clone --depth 1 git@bitbucket.org:razersw/rai-ansible.git /tmp/test-clone 2>&1 | grep -q "done" && echo "CLONE_OK" || echo "CLONE_FAIL"' 2>&1)
|
||||
echo "$TEST_OUTPUT"
|
||||
echo "$TEST_OUTPUT" | grep -q "SSH_AUTH_OK" && echo "PASS: SSH auth" || echo "FAIL: SSH auth"
|
||||
echo "$TEST_OUTPUT" | grep -q "REPO_rai-ops_OK" && echo "PASS: rai-ops mounted" || echo "FAIL: rai-ops mounted"
|
||||
echo "$TEST_OUTPUT" | grep -q "REPO_rai-deployment_OK" && echo "PASS: rai-deployment mounted" || echo "FAIL: rai-deployment mounted"
|
||||
echo "$TEST_OUTPUT" | grep -q "REPO_rai-devtools_OK" && echo "PASS: rai-devtools mounted" || echo "FAIL: rai-devtools mounted"
|
||||
echo "$TEST_OUTPUT" | grep -q "SCRIPT_OK" && echo "PASS: session-init.sh" || echo "FAIL: session-init.sh"
|
||||
echo "$TEST_OUTPUT" | grep -q "CLONE_OK" && echo "PASS: on-demand clone" || echo "FAIL: on-demand clone"
|
||||
</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
1. SSH auth succeeds — `authenticated via ssh key` response from Bitbucket
|
||||
2. All 3 DEFAULT_REPOS are mounted git repos at `/workspace/<name>` (`.git` directory present)
|
||||
3. `session-init.sh` runs and reports all repos verified
|
||||
4. On-demand `git clone` of additional repo succeeds (verifies REPO-02 capability)
|
||||
5. Parent `/workspace` mount is `:rw` (not `:ro`) — verified by mount table inspection
|
||||
</acceptance_criteria>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Assets
|
||||
| Asset | Sensitivity | Protection |
|
||||
|-------|-------------|------------|
|
||||
| SSH private keys (id_ed25519razer, id_rsa) | HIGH — Bitbucket access | Docker `:ro` mount prevents modification. Per-file mount (not full `~/.ssh/`) limits blast radius. |
|
||||
| SSH config | MEDIUM — auth routing info | Mounted `:ro` — read-only in container. |
|
||||
| Repo source code (rai-ops, rai-deployment, rai-devtools) | HIGH — proprietary code | Mounted `:rw` intentionally — agent needs to commit/push. Same exposure as having repo on host. |
|
||||
| `known_hosts` | LOW — public host keys | Mounted `:ro`. Read-only, prevents MitM host key injection. |
|
||||
|
||||
## Trust Boundaries
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| Host → Docker container | SSH keys cross from macOS host into container via volume mount. Docker enforces read-only at VFS level. |
|
||||
| Container → Bitbucket | Agent inside container authenticates to Bitbucket via mounted SSH keys. Network boundary. |
|
||||
|
||||
## STRIDE Threat Register
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-06-01 | Information Disclosure | SSH keys in Docker | mitigate | Keys mounted `:ro` — agent can read (required for auth) but cannot modify. Per-file mount (not full `~/.ssh/`) limits exposure to only the two declared keys. |
|
||||
| T-06-02 | Spoofing | Bitbucket auth via stolen key | accept | Key is user's personal SSH key. Deferred to future phase: per-repo deploy keys (deferred idea in CONTEXT.md). Current mitigation: key can only be read inside container, not exfiltrated to external hosts. |
|
||||
| T-06-03 | Tampering | Repo source code | accept | Repos mounted `:rw` by design — agent must create branches, commit, push. Same trust model as developer's local environment. |
|
||||
| T-06-04 | Tampering | `known_hosts` | mitigate | File mounted `:ro` — prevents agent from injecting fraudulent host keys. File is from trusted host source. |
|
||||
| T-06-SC | Tampering | Package installs | n/a | Zero packages installed. All changes are config edits + one shell script. |
|
||||
|
||||
## Residual Risk
|
||||
- SSH key is readable by agent inside container. If the agent is compromised via prompt injection, the key could be read. Mitigated by: (a) key permits only Bitbucket read/write to known repos, (b) no other services use the same key, (c) per-repo deploy keys deferred to future phase.
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
1. All 4 Python-based YAML assertions pass for config.yaml validity
|
||||
2. `grep` confirms DEFAULT_REPOS in .env
|
||||
3. Docker test container confirms SSH auth, repo mounts, script execution, and on-demand clone
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
1. ✅ SSH keys mounted (`:ro`) and Bitbucket auth works inside Docker (verified by container test)
|
||||
2. ✅ All 3 DEFAULT_REPOS mounted at `/workspace/<name>` (verified by container test)
|
||||
3. ✅ `session-init.sh` runs at shell start and reports mount status (verified by container test)
|
||||
4. ✅ On-demand git clone works inside Docker (verified by container test)
|
||||
5. ✅ `DEFAULT_REPOS` configurable via `.env` — one variable to add/remove repos
|
||||
6. ✅ All changes are additive (no existing config removed)
|
||||
7. ✅ Parent `/workspace` mount confirmed `:rw` (subpath volumes will work)
|
||||
</success_criteria>
|
||||
|
||||
## Artifacts This Phase Produces
|
||||
|
||||
| Artifact | Path | Type | Purpose |
|
||||
|----------|------|------|---------|
|
||||
| session-init.sh | `~/.hermes/scripts/session-init.sh` | Shell script | Verifies DEFAULT_REPOS mounts at session start |
|
||||
| DEFAULT_REPOS env var | `~/.hermes/.env` | Config | Defines which repos to verify |
|
||||
| SSH key mounts (4) | `~/.hermes/config.yaml` → `docker_volumes` | Config | Mounts id_ed25519razer, id_rsa, config, known_hosts as `:ro` |
|
||||
| Repo mounts (3) | `~/.hermes/config.yaml` → `docker_volumes` | Config | Mounts rai-ops, rai-deployment, rai-devtools as `:rw` |
|
||||
| shell_init_files entry | `~/.hermes/config.yaml` | Config | Triggers session-init.sh at shell start |
|
||||
| docker_forward_env entry | `~/.hermes/config.yaml` | Config | Forwards DEFAULT_REPOS into container |
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/06-default-repos-ssh-mount/06-01-SUMMARY.md` when done
|
||||
</output>
|
||||
Reference in New Issue
Block a user