Context
Surfaced during H-041 security review. The deploy SSH key (DEPLOY_SSH_KEY GitHub secret) is currently added to authorized_keys without a command= restriction. A compromised key grants a full interactive shell as adminuser — not just the ability to run deploy.sh. Combined with adminuser owning /opt/havenhold, this allows modifying deploy.sh before the sudoers-allowed invocation, creating a path from key compromise to root.
This is not a regression introduced by H-041 — it is a known hardening gap to address in a follow-up pass.
Scope
Three independent hardening layers, in priority order:
1. Deploy key forced-command wrapper (highest value)
Add a command= restriction to the deploy key's authorized_keys entry. A bare command="sudo /usr/bin/bash /opt/havenhold/infra/deploy.sh" breaks scp (which uses scp -t <path> as the remote command). The solution is a wrapper script that:
- Allows
scp -t /opt/havenhold/server/.env and scp -t /opt/havenhold/.env (exact target paths only)
- Allows
sudo /usr/bin/bash /opt/havenhold/infra/deploy.sh
- Allows the
curl health check invocation
- Rejects everything else
The authorized_keys entry becomes:
command="/opt/havenhold/infra/deploy-shell-wrapper.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 <key>
The wrapper inspects $SSH_ORIGINAL_COMMAND and either allows the exact known commands or exits non-zero.
2. Reduce adminuser ownership blast radius
Change /opt/havenhold ownership from adminuser:adminuser to root:havenhold (or a narrower user) where possible, so a compromised adminuser shell cannot freely modify the repo or deploy script. The deploy script itself should be owned by root and only writable by root.
3. GitHub Environment protection for deploy secrets
Add an environment: production key to the deploy job in ci.yml and configure the GitHub environment with:
- Required reviewer before deploy runs
- Restricted to
main branch only
This adds a human gate and also adds an environment claim to the OIDC token, allowing the IAM trust policy to be tightened further.
Acceptance Criteria
Deploy Gate
None — security hardening, no feature changes.
Notes
- The wrapper approach must be designed carefully before implementation; a quick patch risks breaking automated deploys silently.
- Codex review note: "A strict command='...' restriction on the deploy key will break your current scp steps unless you also implement a wrapper that safely allows the scp subcommands."
- Reference: H-041 security review observation.
Context
Surfaced during H-041 security review. The deploy SSH key (
DEPLOY_SSH_KEYGitHub secret) is currently added toauthorized_keyswithout acommand=restriction. A compromised key grants a full interactive shell asadminuser— not just the ability to rundeploy.sh. Combined withadminuserowning/opt/havenhold, this allows modifyingdeploy.shbefore the sudoers-allowed invocation, creating a path from key compromise to root.This is not a regression introduced by H-041 — it is a known hardening gap to address in a follow-up pass.
Scope
Three independent hardening layers, in priority order:
1. Deploy key forced-command wrapper (highest value)
Add a
command=restriction to the deploy key'sauthorized_keysentry. A barecommand="sudo /usr/bin/bash /opt/havenhold/infra/deploy.sh"breaksscp(which usesscp -t <path>as the remote command). The solution is a wrapper script that:scp -t /opt/havenhold/server/.envandscp -t /opt/havenhold/.env(exact target paths only)sudo /usr/bin/bash /opt/havenhold/infra/deploy.shcurlhealth check invocationThe
authorized_keysentry becomes:The wrapper inspects
$SSH_ORIGINAL_COMMANDand either allows the exact known commands or exits non-zero.2. Reduce
adminuserownership blast radiusChange
/opt/havenholdownership fromadminuser:adminusertoroot:havenhold(or a narrower user) where possible, so a compromisedadminusershell cannot freely modify the repo or deploy script. The deploy script itself should be owned by root and only writable by root.3. GitHub Environment protection for deploy secrets
Add an
environment: productionkey to the deploy job inci.ymland configure the GitHub environment with:mainbranch onlyThis adds a human gate and also adds an
environmentclaim to the OIDC token, allowing the IAM trust policy to be tightened further.Acceptance Criteria
authorized_keysdeploy key entry usescommand=with a wrapper that allowlists exact SCP targets and deploy command only/opt/havenhold/infra/deploy.shis owned by root, notadminuserenvironment: productionwith required reviewer configureddocs/deployment.md)Deploy Gate
None — security hardening, no feature changes.
Notes