fix: harden SCP path and patch several leaks#101
Open
appleboy wants to merge 2 commits into
Open
Conversation
- Upgrade golang.org/x/crypto to v0.51.0 - Upgrade golang.org/x/sys to v0.44.0 - Bump examples to easyssh-proxy v1.5.2
- Reject SCP target paths with shell or SCP-protocol metacharacters to prevent remote command injection - Close the proxyClient SSH connection when its target client closes or times out - Fix sshAgent socket leak caused by variable shadowing in getSSHConfig - Resolve data race on the shared err variable in Stream reader goroutines - Break Stream readers on any read error so client.Close unblocks them on timeout - Add ErrFingerprintMismatch and ErrInvalidTargetFile sentinel errors - Cover the new shellQuote helper and WriteFile validation with unit tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two security-relevant defects in the SSH helper, plus four resource/concurrency bugs, are addressed in
easyssh.go. The largest change is hardeningScp/WriteFile: the remote target path was previously passed unescaped through the user's shell (scp -tr %s) and the filename was written directly into the SCP control stream, allowing both remote command injection and SCP-protocol record injection. Additional fixes close aproxyClientleak that persisted on every successful proxied connect, asshAgentsocket leak caused by:=shadowing insidegetSSHConfig, a data race on the sharederrvariable insideStream, and aStreamreader goroutine that infinite-spun whenclient.Close()returned a non-EOF read error.AI Authorship
easyssh.go,easyssh_test.go— all edits in commit14141baChange classification
This library is the SSH transport for drone-ssh, drone-scp, and similar CI tooling. A regression here propagates to every downstream caller's deploy pipeline.
Plan reference
No
plan.md— change is a/simplify+/security-reviewsweep prompted in conversation. Scope was confined toeasyssh.goandeasyssh_test.go; no public type signatures changed.Verification
TestShellQuote(10 cases) andTestWriteFileRejectsInvalidTargetName(4 cases) pin the two security helpersgo vet ./...cleangofmt -l easyssh.gocleango test -race -run='TestGetKeyFile|TestWrongRawKey|TestShellQuote|TestWriteFileRejectsInvalidTargetName' ./...— PASSmake ssh-server+make test) — NOT RUN in this session. Must be executed before merge. The changes most needing this coverage are theStreamreader/timeout rework and theproxyClientclose-on-Wait goroutine, neither of which is exercised by the new unit tests.client.Wait()goroutine, which is a 1:1 lifetime mirror of the returned client)Manual verification steps for the reviewer
Verifiability check
shellQuotequoting idiom;WriteFilerejected-char rationale)shellQuotecorrectness from the test table aloneErrFingerprintMismatch,ErrInvalidTargetFile) make new failure modes observable to callers viaerrors.IsSecurity check
"Run Command Timeout: …"); new errors are sentinel valuesInsecureIgnoreHostKey()is still the default whenFingerprintis unset. Several tests rely on this. Flipping requires a v2 / major bump.UseInsecureCipherstill ungated.timeout ...time.Durationsemantics unchanged.Risk & rollback
Risk
Streamreader goroutine refactor changes the loop exit condition fromerrors.Is(err, io.EOF)only to "any error". For a well-behaved remote this is identical. For pathological readers that briefly returnio.ErrShortBufferor wrapped errors that are recoverable, behaviour is now stricter (we exit) — but this case did not exist before because the pre-change loop would spin sending empty strings to a no-longer-drained channel, so any deviation here is a bug fix not a regression.client.Wait()goroutine attached to proxied connections will keep one goroutine alive per active proxied session. This is matched to the lifetime of the caller's*ssh.Client, so callers who already correctlyClose()their client see no change. Callers who never close their client previously leaked the proxyClient socket too — now they additionally leak one goroutine. Same root cause.WriteFilefilename validation returnsErrInvalidTargetFilefor paths that previously would have produced an opaque remote error (or worse, succeeded as an injection). This is observable behaviour change; downstream callers that pass\n/\r/\x00in target paths should be vanishingly rare but exist hypothetically.Rollback
git revert 14141ba. No data migration, no schema, no config flags to clean up.Reviewer guide
Read carefully (AI-authored, security-/concurrency-sensitive, no human pre-review):
easyssh.go:482-547—WriteFilevalidation +shellQuotehelper. This is the SCP-injection fix; verify the quoting handles every metacharacter you can think of.easyssh.go:316-321— the newgo func() { _ = client.Wait(); _ = proxyClient.Close() }(). Check that the proxyClient is not closed prematurely or accessed after close in any error path.easyssh.go:295-314— the four new_ = proxyClient.Close()calls on timeout / dial-error / NewClientConn-error paths. Confirm no double-close.easyssh.go:350-444— theStreamrefactor (scanner helper,closeBothclosure, ctx-aware send select). This rewrites the goroutine lifecycle; the data-race fix and the EOF→any-error change live here.easyssh.go:176-179— thesshAgentshadowing fix ingetSSHConfig. One-line change but verify the newsshAgent = sockactually flows into the returned closer.Spot-check OK (mechanical changes, well-covered by signatures + tests):
easyssh.go:26-39—var → constplus three new sentinel errors. Additive.easyssh.go:196-204— fingerprint mismatch now returnsErrFingerprintMismatch. Error string preserved.easyssh.go:438—%v → %won the timeout error. String output unchanged (already confirmed against the pinned test).easyssh_test.go:670-708— the two new tests.Commits in this PR
14141ba— the work described above (118 lines added / 68 deleted across 2 files)459d2f8—chore(deps): upgrade third-party dependencies(already unpushed; bumpsgolang.org/x/cryptoto v0.51.0 andgolang.org/x/systo v0.44.0)