docs(06): create phase 6 plan — SSH mount, repo volumes, session-init

This commit is contained in:
2026-06-14 22:14:22 +08:00
parent 0abadd2743
commit 42ad94600b
3 changed files with 398 additions and 5 deletions

View File

@@ -28,7 +28,7 @@ The agent must NEVER mutate real infrastructure beyond what the limited IAM role
### Active ### Active
- [ ] **MEM-01**: Hindsight memory provider enabled for cross-session recall - [ ] **MEM-01**: Hindsight memory provider enabled for cross-session recall
- [ ] **REPO-01**: DEFAULT_REPOS auto-cloned into new sessions via shell_init_files - [ ] **REPO-01**: DEFAULT_REPOS mounted into new sessions from host filesystem
- [ ] **REPO-02**: On-demand additional repo cloning during session - [ ] **REPO-02**: On-demand additional repo cloning during session
- [ ] **SKIL-04**: Main ngn-agent session skill — session init, Jira ticket creation, doc loading, session-end updates - [ ] **SKIL-04**: Main ngn-agent session skill — session init, Jira ticket creation, doc loading, session-end updates
- [ ] **CRON-01**: Daily session summary report delivered via Telegram - [ ] **CRON-01**: Daily session summary report delivered via Telegram

View File

@@ -22,7 +22,7 @@
**Milestone Goal:** Productionize session workspace with default repos, upgrade to persistent cross-session memory (hindsight), and operationalize daily reporting with session lifecycle management. **Milestone Goal:** Productionize session workspace with default repos, upgrade to persistent cross-session memory (hindsight), and operationalize daily reporting with session lifecycle management.
- [ ] **Phase 5: Hindsight Memory Provider** — Enable cross-session persistent memory with entity-aware recall via Hindsight Cloud API - [ ] **Phase 5: Hindsight Memory Provider** — Enable cross-session persistent memory with entity-aware recall via Hindsight Cloud API
- [ ] **Phase 6: Default Repos & SSH Mount**Auto-clone DEFAULT_REPOS into every new session with secure credential mounting - [ ] **Phase 6: Default Repos & SSH Mount**Mount DEFAULT_REPOS into every new session workspace with direct host filesystem mounts + SSH credential mounting
- [ ] **Phase 7: Main Session Skill** — Session lifecycle orchestration skill covering init-to-close workflow - [ ] **Phase 7: Main Session Skill** — Session lifecycle orchestration skill covering init-to-close workflow
- [ ] **Phase 8: Cron Reporting** — Daily session summaries, stale session archiving, and Jira integration - [ ] **Phase 8: Cron Reporting** — Daily session summaries, stale session archiving, and Jira integration
@@ -43,7 +43,7 @@ Plans:
- [ ] 05-01-PLAN.md — Hindsight Memory Provider: Create config.json, set env var, activate provider, verify - [ ] 05-01-PLAN.md — Hindsight Memory Provider: Create config.json, set env var, activate provider, verify
### Phase 6: Default Repos & SSH Mount ### Phase 6: Default Repos & SSH Mount
**Goal**: Default repos auto-cloned into every new session with secure credential mounting for git operations **Goal**: Default repos mounted directly from host filesystem into every new session workspace with secure SSH credential mounting for git operations
**Depends on**: Nothing (independent from Phase 5) **Depends on**: Nothing (independent from Phase 5)
**Requirements**: REPO-01, REPO-02 **Requirements**: REPO-01, REPO-02
**Success Criteria** (what must be TRUE): **Success Criteria** (what must be TRUE):
@@ -52,7 +52,10 @@ Plans:
3. User can request additional repo cloning mid-session and agent clones it via git command in Docker 3. User can request additional repo cloning mid-session and agent clones it via git command in Docker
4. SSH credentials are mounted read-only inside Docker (verified by container inspection) 4. SSH credentials are mounted read-only inside Docker (verified by container inspection)
5. Session init script completes within 30s timeout and doesn't block agent startup 5. Session init script completes within 30s timeout and doesn't block agent startup
**Plans**: TBD **Plans**: 1 plan
Plans:
- [ ] 06-01-PLAN.md — SSH key + repo volume mounts, session-init.sh, DEFAULT_REPOS config
### Phase 7: Main Session Skill ### Phase 7: Main Session Skill
**Goal**: Full session lifecycle orchestration skill covering the `initial-plan.md` workflow — init to close **Goal**: Full session lifecycle orchestration skill covering the `initial-plan.md` workflow — init to close
@@ -87,6 +90,6 @@ Plans:
| 3. Telegram Gateway | v1.0 | — | Complete | 2026-06-14 | | 3. Telegram Gateway | v1.0 | — | Complete | 2026-06-14 |
| 4. Skills & Integrations | v1.0 | — | Complete | 2026-06-14 | | 4. Skills & Integrations | v1.0 | — | Complete | 2026-06-14 |
| 5. Hindsight Memory Provider | v1.1 | 0/1 | Not started | - | | 5. Hindsight Memory Provider | v1.1 | 0/1 | Not started | - |
| 6. Default Repos & SSH Mount | v1.1 | 0/TBD | Not started | - | | 6. Default Repos & SSH Mount | v1.1 | 0/1 | Not started | - |
| 7. Main Session Skill | v1.1 | 0/TBD | Not started | - | | 7. Main Session Skill | v1.1 | 0/TBD | Not started | - |
| 8. Cron Reporting | v1.1 | 0/TBD | Not started | - | | 8. Cron Reporting | v1.1 | 0/TBD | Not started | - |

View 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 &lt;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>