security(offbox): validate host/user/repo before the ssh exec (option-injection guard)
Background commit review flagged command/option injection: operator-provided host/user/ repo_path flow into restic's ssh -s sftp command. Reject leading '-' (ssh option injection, e.g. -oProxyCommand) + metacharacters/traversal; OffboxConfigured fails closed on an invalid target. Companion test covers the injection cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HxLA1mZurFq9kt8hneFeCs
This commit is contained in:
@@ -265,6 +265,31 @@ func TestOffbox_SecretsAre0600(t *testing.T) {
|
||||
|
||||
// --- tiny test helpers ---
|
||||
|
||||
// TestOffbox_ValidateRejectsInjection is the security companion: host/user/repo values that could inject
|
||||
// an ssh option (leading '-' → e.g. -oProxyCommand) or a shell metacharacter must be REFUSED; a clean
|
||||
// target is accepted. A build without this guard would let a hostile target reach the ssh exec → FAIL.
|
||||
func TestOffbox_ValidateRejectsInjection(t *testing.T) {
|
||||
ok := &settings.OffboxTarget{Host: "nas.local", Port: 22, User: "felhom", RepoPath: "/srv/repo"}
|
||||
if err := ValidateOffboxTarget(ok); err != nil {
|
||||
t.Fatalf("clean target rejected: %v", err)
|
||||
}
|
||||
bad := []settings.OffboxTarget{
|
||||
{Host: "-oProxyCommand=touch /tmp/pwn", User: "felhom", RepoPath: "/srv/repo"}, // ssh option injection
|
||||
{Host: "nas;rm -rf /", User: "felhom", RepoPath: "/srv/repo"}, // metacharacters
|
||||
{Host: "nas.local", User: "-oProxyCommand=x", RepoPath: "/srv/repo"}, // user option injection
|
||||
{Host: "nas.local", User: "felhom", RepoPath: "/srv/repo; evil"}, // path metacharacters
|
||||
{Host: "nas.local", User: "felhom", RepoPath: "/srv/../etc"}, // traversal
|
||||
{Host: "nas local", User: "felhom", RepoPath: "/srv/repo"}, // space
|
||||
{Host: "nas.local", User: "felhom", RepoPath: "relative/path"}, // non-absolute
|
||||
}
|
||||
for i, b := range bad {
|
||||
bb := b
|
||||
if err := ValidateOffboxTarget(&bb); err == nil {
|
||||
t.Errorf("case %d (%+v) must be rejected", i, bb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runtimeIsUnix() bool { return os.PathSeparator == '/' }
|
||||
|
||||
func contains(ss []string, want string) bool {
|
||||
|
||||
Reference in New Issue
Block a user