From 42ad94600b009b2eaa066f91638c202830a88f4a Mon Sep 17 00:00:00 2001 From: Bagas Purwa Sentika Date: Sun, 14 Jun 2026 22:14:22 +0800 Subject: [PATCH] =?UTF-8?q?docs(06):=20create=20phase=206=20plan=20?= =?UTF-8?q?=E2=80=94=20SSH=20mount,=20repo=20volumes,=20session-init?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .planning/PROJECT.md | 2 +- .planning/ROADMAP.md | 11 +- .../06-default-repos-ssh-mount/06-01-PLAN.md | 390 ++++++++++++++++++ 3 files changed, 398 insertions(+), 5 deletions(-) create mode 100644 .planning/phases/06-default-repos-ssh-mount/06-01-PLAN.md diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md index abce618..31496ec 100644 --- a/.planning/PROJECT.md +++ b/.planning/PROJECT.md @@ -28,7 +28,7 @@ The agent must NEVER mutate real infrastructure beyond what the limited IAM role ### Active - [ ] **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 - [ ] **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 diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 8e38a4b..f4ac50f 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -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. - [ ] **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 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 ### 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) **Requirements**: REPO-01, REPO-02 **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 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 -**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 **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 | | 4. Skills & Integrations | v1.0 | — | Complete | 2026-06-14 | | 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 | - | | 8. Cron Reporting | v1.1 | 0/TBD | Not started | - | diff --git a/.planning/phases/06-default-repos-ssh-mount/06-01-PLAN.md b/.planning/phases/06-default-repos-ssh-mount/06-01-PLAN.md new file mode 100644 index 0000000..42cd291 --- /dev/null +++ b/.planning/phases/06-default-repos-ssh-mount/06-01-PLAN.md @@ -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/ 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" +--- + + +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 + + + +@/Users/bapung/.config/opencode/gsd-core/workflows/execute-plan.md +@/Users/bapung/.config/opencode/gsd-core/templates/summary.md + + + +@/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 + + + + + + Task 1: Create session-init.sh mount verification script + ~/.hermes/scripts/session-init.sh + + ~/.hermes/scripts/ngn-jira (shebang pattern) + ~/.hermes/scripts/ngn-bitbucket (shebang pattern) + This file's RESEARCH.md §Code Examples for the reference implementation + + + 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` + + + 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" + + + 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 + + + + + Task 2: Add DEFAULT_REPOS to .env and update config.yaml with mounts + shell_init_files + forward_env + ~/.hermes/.env, ~/.hermes/config.yaml + + ~/.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 + + + 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. + + + + # 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 + + + + 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) + + + + + Task 3: Verify end-to-end — SSH auth, repo mounts, script execution via Docker test + (no files modified — verification only) + + Phase research §Verified Findings (SSH auth, subpath mounts, known_hosts requirements) + + + 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//` + - 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. + + + + # 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" + + + + 1. SSH auth succeeds — `authenticated via ssh key` response from Bitbucket + 2. All 3 DEFAULT_REPOS are mounted git repos at `/workspace/` (`.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 + + + + + + +## 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. + + + +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 + + + +1. ✅ SSH keys mounted (`:ro`) and Bitbucket auth works inside Docker (verified by container test) +2. ✅ All 3 DEFAULT_REPOS mounted at `/workspace/` (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) + + +## 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 | + + +Create `.planning/phases/06-default-repos-ssh-mount/06-01-SUMMARY.md` when done +