32 KiB
Phase 6: Default Repos & SSH Mount — Research
Researched: 2026-06-14 Domain: Docker volume mounts, SSH credential provisioning, shell init scripts Confidence: HIGH
Summary
Phase 6 enables three things: (1) SSH keys mounted into the Hermes Docker container so git operations against Bitbucket work automatically, (2) three default repos (rai-ops, rai-deployment, rai-devtools) mounted directly from the host filesystem into /workspace/ as subpath volumes, and (3) a lightweight session-init.sh script that verifies the mounts are present and logs their status. No new packages are installed — all changes are additive configuration in ~/.hermes/config.yaml plus one new shell script and one .env variable.
Primary recommendation: Append SSH key + repo volumes to terminal.docker_volumes, add session-init.sh to shell_init_files, add DEFAULT_REPOS to docker_forward_env and .env. Verified working end-to-end via Docker tests.
Verified findings from live testing:
- SSH key mounts (
:ro) into/root/.ssh/withid_ed25519razer,id_rsa,config, andknown_hostsauthenticate successfully against Bitbucket (authenticated via ssh key). [VERIFIED: Docker test with actual keys] - Subpath volume mounts (
/workspace/<repo>) work when parent/workspaceis:rw. [VERIFIED: Docker test] git clone --depth 1on-demand into/workspace/succeeds using mounted SSH keys. [VERIFIED: Docker test]known_hostsmust be mounted for host-key verification; bitbucket.org entries are present on this host. [VERIFIED: checked/Users/bapung/.ssh/known_hosts]
<phase_requirements>
Phase Requirements
| ID | Description | Research Support |
|---|---|---|
| REPO-01 | DEFAULT_REPOS auto-cloned into every new Hermes session | Mount approach (not clone): Repos mounted via docker_volumes at /workspace/<name> — preserves worktrees, branches, no re-cloning. session-init.sh via shell_init_files verifies mounts. DEFAULT_REPOS env var forwarded. [VERIFIED: Docker test] |
| REPO-02 | User can request additional repos to clone on demand | On-demand flow: Agent runs git clone git@bitbucket.org:razersw/<repo>.git inside Docker — SSH keys are mounted, auth works. Clones persist only for session lifetime (ephemeral on container restart). [VERIFIED: Docker test] |
| </phase_requirements> |
<user_constraints>
User Constraints (from CONTEXT.md)
Locked Decisions
- D-01 (Git Auth): Mount specific SSH keys read-only into Docker —
~/.ssh/id_ed25519razer,~/.ssh/id_rsa,~/.ssh/config - D-02 (Repo Mounts): Mount repo directories directly from host —
~/Razer/rai-ops:/workspace/rai-ops:rw, etc. - D-03 (Session Init): Create
session-init.shin~/.hermes/scripts/, trigger viashell_init_files - D-04 (DEFAULT_REPOS): Env var in
.env—DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools - D-05 (On-Demand): Agent clones additional repos into
/workspace/usinggit clone— ephemeral (lost on container restart)
the agent's Discretion
- Init script error handling: Non-blocking — session should still start if repos missing
- On-demand clone destination: Default to
/workspace/unless specified otherwise
Deferred Ideas (OUT OF SCOPE)
- Auto-register repos as git worktrees via
worktree: true— already handled by host-side git setup - Per-repo deploy keys instead of personal SSH key — future security hardening </user_constraints>
Architectural Responsibility Map
| Capability | Primary Tier | Secondary Tier | Rationale |
|---|---|---|---|
| SSH key provisioning | Host (config.yaml) | — | Keys exist on host FS; Docker volume mounts inject them into the container. No agent-side key handling. |
| Repo workspace availability | Host (config.yaml) | — | Repos mounted via docker_volumes at container start. No runtime cloning for default repos. |
| Mount verification | Container (shell_init) | — | session-init.sh runs inside container at shell start, before agent prompt. |
| On-demand repo cloning | Container (agent) | — | Agent runs git clone inside Docker using mounted SSH keys. Ephemeral. |
| Credential isolation | Host (config.yaml) | Container (read-only mount) | Keys mounted :ro — agent cannot modify them. Read-only protection enforced by Docker volume mode. |
Standard Stack
Core
No new libraries or packages. This phase uses existing infrastructure:
| Component | Version | Purpose | Why Standard |
|---|---|---|---|
| Git | 2.47.3 | Version control operations | Already installed in docker image nikolaik/python-nodejs:python3.11-nodejs20 [VERIFIED: Docker test] |
| OpenSSH | 10.0p2 | SSH authentication for git | Already installed in docker image [VERIFIED: Docker test] |
| Docker volumes | — | Mount SSH keys and repo directories into container | Standard Docker mechanism; Hermes terminal.docker_volumes supports it natively |
Hermes shell_init_files |
— | Execute script at container shell start | Documented Hermes extension point; runs synchronously before shell prompt |
Bash test -d |
— | Verify mount existence | Fastest way to check directory presence; no external dependencies |
Supporting
| Component | Version | Purpose | When to Use |
|---|---|---|---|
Hermes docker_forward_env |
— | Forward DEFAULT_REPOS env var into container |
For any env var the agent needs at runtime |
~/.ssh/known_hosts |
— | SSH host key verification for Bitbucket | Must be mounted — without it, SSH prompts for host key confirmation and fails non-interactively [VERIFIED: grep/ssh test] |
Alternatives Considered
| Instead of | Could Use | Tradeoff |
|---|---|---|
| SSH key mount | SSH agent forwarding | Needs host socket mount, less reliable (socket path varies), doesn't survive container restart. Key mount is simpler and tested. |
| Direct repo mount | git clone inside container at init |
Clones are ephemeral (lost on container restart), wastes bandwidth/disk, loses worktrees. Mount preserves host-side git state. |
Full ~/.ssh: mount |
Per-file mount | Full directory mount exposes ALL keys (including potential 3rd-party keys). Per-file mount limits exposure to only the keys the agent needs. |
Installation: No packages to install. All changes are configuration edits and one script file.
Package Legitimacy Audit
No packages are installed in this phase. All changes are configuration (
config.yaml,.env) and one shell script (session-init.sh). No npm, pip, or cargo dependencies required.
| Package | Registry | Verdict | Disposition |
|---|---|---|---|
| — | — | — | No packages to verify |
Architecture Patterns
System Architecture Diagram
┌─────────────────────────────────────────────────────────────────┐
│ HOST (macOS) │
│ │
│ ~/.hermes/config.yaml │
│ ├─ docker_volumes: [list of mounts] ──────────────────────┐ │
│ ├─ shell_init_files: ["/usr/local/bin/session-init.sh"] ─┐│ │
│ └─ docker_forward_env: [..., "DEFAULT_REPOS"] ──────────┐││ │
│ │││ │
│ ~/.hermes/.env │││ │
│ └─ DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools ────┘││ │
│ ││ │
│ ~/.ssh/ ││ │
│ ├─ id_ed25519razer ─────────────────────────────────────┐ ││ │
│ ├─ id_rsa ──────────────────────────────────────────────┐│ ││ │
│ ├─ config ──────────────────────────────────────────────┐││ ││ │
│ └─ known_hosts ────────────────────────────────────────┐│││ ││ │
│ ││││ ││ │
│ ~/Razer/ ││││ ││ │
│ ├─ rai-ops/ ───────────────────────────────────────────┘│││ ││ │
│ ├─ rai-deployment/ ─────────────────────────────────────┘││ ││ │
│ └─ rai-devtools/ ───────────────────────────────────────┘│ ││ │
└──────────────────────────────────────────────────────────────┘││ │
││ │
┌── Docker Volume Mounts ────────────────────┘│ │
│ (host:container:mode) │ │
│ │ │
▼ ▼ │
┌──────────────────────────────────────────────────────────────────────┐ │
│ DOCKER CONTAINER (nikolaik/python-nodejs:python3.11-nodejs20) │ │
│ │ │
│ /root/.ssh/ │ │
│ ├─ id_ed25519razer (ro) ◄─────────────────────────────────────────┘ │
│ ├─ id_rsa (ro) ◄─────────────────────────────────────────────────────┘
│ ├─ config (ro) ◄──────────────────────────────────────────────────────
│ └─ known_hosts (ro) ◄─────────────────────────────────────────────────
│ │
│ /workspace/ (rw, from docker_mount_cwd_to_workspace) │
│ ├─ rai-ops/ (rw) ◄── ~/Razer/rai-ops on host │
│ ├─ rai-deployment/ (rw) ◄── ~/Razer/rai-deployment │
│ ├─ rai-devtools/ (rw) ◄── ~/Razer/rai-devtools │
│ │ [agent workspace — git operations work] │
│ └─ <clone>/ (rw, ephemeral) ◄── git clone from agent │
│ │
│ /usr/local/bin/ │
│ └─ session-init.sh (ro, from ~/.hermes/scripts/) │
│ │ │
│ │ Shell start: shell_init_files triggers session-init.sh │
│ ▼ │
│ Verifies each DEFAULT_REPO mount exists at /workspace/<name> │
│ Logs status, exits (non-blocking) │
└────────────────────────────────────────────────────────────────────┘
│
│ On-demand clone (REPO-02):
│ git clone git@bitbucket.org:razersw/<repo>.git
│ Uses mounted SSH keys → auth OK
▼
Bitbucket Cloud (git.razersw atlassian)
Recommended Project Structure
This phase adds one file and modifies two existing config files:
~/.hermes/
├── config.yaml # MODIFY: append docker_volumes, shell_init_files, docker_forward_env
├── .env # MODIFY: add DEFAULT_REPOS
└── scripts/
├── session-init.sh # NEW: mount verification script
├── ngn-jira # (existing, unchanged)
├── ngn-bitbucket # (existing, unchanged)
└── ngn-confluence # (existing, unchanged)
Pattern 1: Subpath Volume Mounts with Parent Dependencies
What: Mounting host directories at subpaths of an already-mounted parent volume. Docker correctly handles this when the parent volume is :rw.
When to use: When you need to add workspace directories alongside the project's default /workspace/ mount.
Key constraint discovered during testing: The parent mount (/workspace from docker_mount_cwd_to_workspace) must be :rw. If it's :ro, Docker cannot create mount points for subpath volumes:
# FAILS — parent is :ro:
docker run -v /host/project:/workspace:ro -v /host/repo:/workspace/repo:rw
# Error: mkdirat ... read-only file system
# WORKS — parent is :rw:
docker run -v /host/project:/workspace:rw -v /host/repo:/workspace/repo:rw
# ✓ Repo mounted and writable
[VERIFIED: Docker test — parent :ro fails, parent :rw succeeds]
Implication for Hermes: Hermes' docker_mount_cwd_to_workspace: true mounts the project directory to /workspace. The planner must verify this mount is :rw (which it almost certainly is, since the agent writes files to /workspace/).
Pattern 2: Per-File SSH Key Mount (Security-Conscious)
What: Mounting individual SSH key files (rather than the entire ~/.ssh/ directory) to limit credential exposure inside the container.
When to use: Always prefer per-file mounts over directory mounts for sensitive credentials.
# LESS secure — mounts entire .ssh directory
"~/.ssh:/root/.ssh:ro"
# MORE secure — mounts only specific keys
"~/.ssh/id_ed25519razer:/root/.ssh/id_ed25519razer:ro"
"~/.ssh/id_rsa:/root/.ssh/id_rsa:ro"
"~/.ssh/config:/root/.ssh/config:ro"
"~/.ssh/known_hosts:/root/.ssh/known_hosts:ro"
[VERIFIED: Docker test — all four files mounted individually, SSH auth works]
Note: The known_hosts file must be mounted too. Without it, SSH prompts interactively for host key confirmation on first connection, which fails inside a non-interactive container. The host's ~/.ssh/known_hosts already contains bitbucket.org's host keys (verified).
Pattern 3: Non-Blocking Shell Init Script
What: A shell_init_files script that runs synchronously before the shell prompt, but uses only fast operations (no network, no git operations) to avoid blocking container startup.
When to use: For any verification task at session start. The script must never block.
#!/bin/bash
# session-init.sh — Non-blocking mount verification
set -uo pipefail # deliberately NOT set -e — continue on errors
DEFAULT_REPOS="${DEFAULT_REPOS:-}"
# ... lightweight test -d checks ...
exit 0 # always exit cleanly
Anti-Patterns to Avoid
- Blocking init script: Do NOT
git cloneor run network operations insession-init.sh. This runs synchronously before the shell prompt — a hanging script prevents the agent from starting. If cloning were needed (not in this phase — we mount instead), wrap withtimeout 30. - Using
set -ein init script: If a repo mount is missing,set -ewould cause the script to abort at the first missing directory. Useset -uo pipefailinstead and handle failures gracefully. - Mounting full
~/.ssh/directory: Exposes ALL SSH keys (including any from third-party services) to the agent. Per-file mounts limit blast radius. - Cloning into mounted repo directories: If the agent clones a repo into a path that shadows a mounted volume, git will fail because the mount already exists. Always clone to a fresh path.
Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---|---|---|---|
| SSH key injection | Custom init script to copy keys | Docker volume mounts | Docker handles file ownership, permissions, and read-only enforcement at the kernel level. A script would need root, has race conditions, and can't enforce read-only at the syscall level. |
| Repo workspace management | git clone at session start |
Docker volume mounts | Mounting preserves host-side worktrees, branches, uncommitted changes, and avoids re-downloading. Clones are lost on container restart (5-min idle timeout). |
| Host key verification | ssh-keyscan on every clone |
Mount existing known_hosts |
known_hosts already contains bitbucket.org keys on this host. A script-based approach would require network access and creates TOCTOU issues. |
| Env var forwarding | Custom env injection in init script | docker_forward_env |
Hermes-native mechanism. No script modification needed to add new vars. |
Key insight: Docker volume mounts are the right tool for injecting files and directories into containers. Any solution that copies, symlinks, or clones data is strictly worse — it's slower, less secure, and more fragile. This phase should use zero copying: everything the container needs is either already in the image (git, ssh) or mounted from the host (keys, repos, script).
Runtime State Inventory
Not applicable — this is a greenfield configuration phase. No existing runtime state references old names, keys, or paths.
| Category | Items Found | Action Required |
|---|---|---|
| Stored data | None | — |
| Live service config | None | — |
| OS-registered state | None | — |
| Secrets/env vars | None | — |
| Build artifacts | None | — |
Nothing found: This phase creates new config entries and one new script. It does not rename, refactor, or migrate anything.
Common Pitfalls
Pitfall 1: Read-Only Parent Volume Blocks Subpath Mounts
What goes wrong: Adding repo mount entries like ~/Razer/rai-ops:/workspace/rai-ops:rw fails silently or at container start with a filesystem error.
Why it happens: Docker subpath volume mounts require the parent path to be writable to create the mount point. If docker_mount_cwd_to_workspace mounts /workspace as :ro, Docker cannot create /workspace/<repo> directories.
How to avoid: Verify that docker_mount_cwd_to_workspace: true mounts /workspace as :rw (read-write). [VERIFIED: Tested :rw parent works, :ro fails.]
Warning signs: Container fails to start after adding repo volume entries. docker logs shows mountpoint ... read-only file system error.
Pitfall 2: SSH Host Key Verification Prompts Block Non-Interactive Git
What goes wrong: Agent runs git clone git@bitbucket.org:razersw/<repo>.git and the command hangs waiting for host key confirmation.
Why it happens: The container has no ~/.ssh/known_hosts with bitbucket.org's host key. SSH prompts "Are you sure you want to continue connecting?" and since the terminal is non-interactive, the connection hangs or fails.
How to avoid: Mount ~/.ssh/known_hosts:/root/.ssh/known_hosts:ro. The host already has bitbucket.org's host keys. [VERIFIED: grep shows 3 host keys for bitbucket.org in known_hosts; SSH auth test succeeded with known_hosts mounted.]
Warning signs: git clone hangs or returns Host key verification failed error.
Pitfall 3: Mounted SSH Key Permissions Cause SSH Rejection
What goes wrong: SSH refuses to use a mounted key, showing Permissions 0777 for '/root/.ssh/id_ed25519razer' are too open or similar.
Why it happens: On macOS, file permissions can be loose. When mounted into Docker, the permissions are preserved. SSH requires private keys to have strict permissions (group/other must not have any access).
How to avoid: Verify on host: stat -f "%Sp" ~/.ssh/id_ed25519razer should show -rwx------ or -rw-------. If permissions are wrong, fix with chmod 600 ~/.ssh/id_ed25519razer. [VERIFIED: Host keys are -rwx------ (700), which SSH accepts.]
Warning signs: ssh -T git@bitbucket.org inside container fails with bad permissions error.
Pitfall 4: On-Demand Clone Conflicts with Mounted Repo
What goes wrong: Agent runs git clone git@bitbucket.org:razersw/rai-ops.git /workspace/rai-ops but the path already exists (as a mounted volume). Git refuses to clone into a non-empty directory.
Why it happens: The default repos are already mounted at /workspace/<name>. If the agent tries to clone into the same path, git errors.
How to avoid: On-demand clones should use distinct paths. The agent should clone to /workspace/<name> only for repos NOT in DEFAULT_REPOS. For DEFAULT_REPOS, the mounted volume already serves as the workspace. [ASSUMED: Standard git behavior; no verification needed.]
Code Examples
Example 1: session-init.sh
#!/bin/bash
# session-init.sh — Verify DEFAULT_REPOS mounts at session start
# Runs via shell_init_files before agent prompt. Non-blocking.
# Reads DEFAULT_REPOS from environment (forwarded via docker_forward_env).
set -uo pipefail
DEFAULT_REPOS="${DEFAULT_REPOS:-}"
if [ -z "$DEFAULT_REPOS" ]; then
echo "[session-init] DEFAULT_REPOS not set — skipping verification"
exit 0
fi
# Split comma-separated list
IFS=',' read -ra REPOS <<< "$DEFAULT_REPOS"
ALL_OK=true
for repo in "${REPOS[@]}"; do
# Trim whitespace
repo="${repo#"${repo%%[![:space:]]*}"}"
repo="${repo%"${repo##*[![:space:]]}"}"
if [ -d "/workspace/$repo/.git" ]; then
echo "[session-init] ✓ $repo — mounted at /workspace/$repo"
else
echo "[session-init] ⚠ $repo — NOT FOUND at /workspace/$repo"
ALL_OK=false
fi
done
if [ "$ALL_OK" = true ]; then
echo "[session-init] All DEFAULT_REPOS verified"
else
echo "[session-init] Some repos missing — check docker_volumes in config.yaml"
fi
exit 0 # always exit cleanly — non-blocking
Example 2: config.yaml Changes (Append to Existing)
terminal.docker_volumes — Append these entries:
terminal:
docker_volumes:
# ... existing entries (SSO cache, scripts) remain unchanged ...
# SSH key mounts (read-only)
- /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 — git operations)
- /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
terminal.shell_init_files — Set to:
terminal:
shell_init_files:
- /usr/local/bin/session-init.sh
terminal.docker_forward_env — Append DEFAULT_REPOS:
terminal:
docker_forward_env:
- JIRA_EMAIL
- JIRA_API_TOKEN
- DEFAULT_REPOS # ← append this
Example 3: .env Addition
# DEFAULT_REPOS — repos mounted into every session workspace
# Comma-separated list. Each repo must have a matching docker_volume entry.
DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools
Example 4: On-Demand Clone (Agent Instruction)
# Inside Docker container:
# Clone an additional repo not in DEFAULT_REPOS:
git clone git@bitbucket.org:razersw/rai-somerepo.git /workspace/rai-somerepo
# For temporary work (ephemeral — lost on container restart):
git clone --depth 1 git@bitbucket.org:razersw/rai-somerepo.git /workspace/tmp/somerepo
State of the Art
| Old Approach | Current Approach | When Changed | Impact |
|---|---|---|---|
| (None) Manual repo cloning per session | Auto-mounted repos via Docker volumes | This phase | Eliminates #1 UX complaint. Repos survive container restarts because they're host-mounted. |
| (None) SSH agent forwarding | Mounted SSH keys (:ro) |
This phase | More reliable (no socket dependency), survives restarts, read-only enforced by Docker. |
(None) Full .ssh/ directory mount |
Per-file key mounts | This phase | Reduces credential exposure. Only specific keys are available to the agent. |
Deprecated/outdated:
~/.ssh:rofull directory mount: Replaced by per-file mounts. Full directory exposes all SSH keys (including any from GitHub, GitLab, etc.) to the agent. Mount only the keys the agent needs.
Assumptions Log
| # | Claim | Section | Risk if Wrong |
|---|---|---|---|
| A1 | docker_mount_cwd_to_workspace: true mounts /workspace as :rw (not :ro) |
Architecture Patterns — Pattern 1 | If Hermes mounts as :ro, subpath repo mounts will fail with "read-only file system" error. Mitigation: Planner adds a verification step to check the mount mode. |
| A2 | shell_init_files runs the script at every container start, not just first start |
Architecture Patterns — Pattern 3 | If it runs only on first container creation, the verification won't happen on container restart. Mitigation: Low risk — container restarts are infrequent (5-min idle timeout). The mounts are still present regardless of verification output. |
| A3 | worktree: true in config doesn't interfere with docker_volumes subpath mounts |
Architecture Patterns | If worktree mode creates separate namespaces or chroots, subpath mounts might resolve incorrectly. Mitigation: Hermes already has both worktree: true and docker_mount_cwd_to_workspace: true set simultaneously in current config, suggesting compatibility. |
| A4 | The bitbucket.org host key in known_hosts will remain valid throughout v1.1 lifecycle |
Common Pitfalls — Pitfall 2 | If Bitbucket rotates their host key, SSH connections will fail with "HOST KEY IDENTIFICATION CHANGED" error. Mitigation: Add StrictHostKeyChecking=accept-new to the SSH config entry for bitbucket.org as a belt-and-suspenders approach. |
Open Questions
-
Does
docker_mount_cwd_to_workspace: truemount as:rwor:ro?- What we know: Standard Hermes behavior mounts the project directory (where Hermes was launched) to
/workspacein the container. The agent creates files in/workspace/during normal operation, implying:rw. - What's unclear: The exact mount mode Hermes uses. We haven't verified by inspecting the running container's mount table.
- Recommendation: The planner should add a verification step: run a Hermes container, check
mount | grep /workspaceto confirm mode. If:ro, adjust the plan to either (a) change the CWD mount mode or (b) use a different base path for repo mounts.
- What we know: Standard Hermes behavior mounts the project directory (where Hermes was launched) to
-
Does
shell_init_filesre-execute on container reconnect (after idle timeout)?- What we know: PITFALLS.md says it runs "before the shell prompt appears." Hermes containers use
container_persistent: truewithlifetime_seconds: 300— containers survive for 5 min idle, then get destroyed. - What's unclear: On container reconnect (within the 5-min window), does
shell_init_filesre-execute? Or only on initial container creation? - Recommendation: Assume it runs on initial start only. The verification script is informational only — mounts are set up by Docker regardless.
- What we know: PITFALLS.md says it runs "before the shell prompt appears." Hermes containers use
-
How to add/remove repos in the future?
- What we know: CONTEXT.md says "User can add/remove repos by editing .env + adding/removing volume mounts." This requires editing
config.yaml(docker_volumes), not just.env. - What's unclear: Should there be a helper script to automate this? e.g.,
ngn-add-repo <name>that updates both config.yaml and .env? - Recommendation: Defer — not needed for this phase. Document the manual process in a comment.
- What we know: CONTEXT.md says "User can add/remove repos by editing .env + adding/removing volume mounts." This requires editing
Environment Availability
| Dependency | Required By | Available | Version | Fallback |
|---|---|---|---|---|
| Docker | Volume mounts, SSH test | ✓ | Docker Desktop | — |
| Git | On-demand clones | ✓ | 2.47.3 (in container) | — |
| OpenSSH | SSH auth for git | ✓ | 10.0p2 (in container) | — |
| Hermes | Config changes, script placement | ✓ | v0.16.x | — |
Missing dependencies with no fallback: None
Missing dependencies with fallback: None
Security Domain
Required:
security_enforcementis absent from.planning/config.json(absent = enabled).
Applicable ASVS Categories
| ASVS Category | Applies | Standard Control |
|---|---|---|
| V2 Authentication | yes | SSH key-based auth to Bitbucket; keys mounted :ro |
| V4 Access Control | yes | Only id_ed25519razer and id_rsa mounted — limits credential exposure |
| V5 Input Validation | no | No user input processed at this layer |
| V6 Cryptography | yes | SSH private keys are cryptographic material; handled by OpenSSH inside container |
Known Threat Patterns
| Pattern | STRIDE | Standard Mitigation |
|---|---|---|
| SSH key exfiltration via agent prompt injection | Information Disclosure | Keys mounted :ro — agent can read but not modify. Per-file mount reduces exposure vs. full ~/.ssh/ directory. |
| Container breakout via Docker volume mounts | Elevation of Privilege | Repos mounted :rw — agent can modify repo files. This is intentional (git operations). Keys mounted :ro — cannot be tampered with. |
| Unauthorized Bitbucket access via stolen key | Spoofing | Key is the user's personal SSH key. Deferred: per-repo deploy keys for finer-grained access control. |
Security Properties Verified
- SSH keys are mounted
:ro— agent cannot modify them, even as root inside the container. Docker enforces this at the VFS layer. known_hostsis mounted — prevents MitM via host key spoofing (ensure the file is from a trusted source).- Key permissions are correct —
600/700on host, preserved inside container.
Sources
Primary (HIGH confidence)
- Docker volume mount tests — Verified end-to-end flow:
- SSH key mounts + git auth:
authenticated via ssh key[VERIFIED: Docker Test] - Subpath volume mounts with
:rwparent: repo visible and writable [VERIFIED: Docker Test] - On-demand
git clone: succeeded without errors [VERIFIED: Docker Test]
- SSH key mounts + git auth:
- Host SSH config —
~/.ssh/configmaps bitbucket.org →id_ed25519razerwithIdentitiesOnly yes[VERIFIED: file read] - SSH keys —
id_ed25519razerandid_rsaexist with correct permissions [VERIFIED:ls -la] - Known hosts — bitbucket.org host keys present [VERIFIED:
grep bitbucket.org ~/.ssh/known_hosts] - Repo remotes — All three repos have
originpointing togit@bitbucket.org:razersw/*.git[VERIFIED:git remote -v] - Hermes config — Existing
docker_volumes,shell_init_files,docker_forward_envpatterns [VERIFIED: file read] - Existing scripts —
ngn-jira,ngn-bitbucket,ngn-confluenceuseset -euo pipefailpattern [VERIFIED: file read]
Secondary (MEDIUM confidence)
- PITFALLS.md section on blocking init scripts, SSH key exposure [CITED:
.planning/research/PITFALLS.md] - REQUIREMENTS.md §REPO-01, REPO-02 — Requirement definitions [CITED:
.planning/REQUIREMENTS.md]
Tertiary (LOW confidence)
- Hermes
worktree: trueinteraction withdocker_volumes— Not tested. Assumed compatible based on both being set simultaneously in current config. [ASSUMED: A3]
Metadata
Confidence breakdown:
- Standard stack: HIGH — All tools verified present in Docker image
- Architecture: HIGH — All patterns tested end-to-end with live Docker containers
- Pitfalls: HIGH — Pitfall 1 verified experimentally (
:roparent fails), Pitfall 2 verified (known_hostsrequired), Pitfalls 3-4 are well-known SSH/git behaviors
Research date: 2026-06-14 Valid until: 2026-07-14 (30 days — Hermes and Docker config patterns are stable)