From b78e7e3d3e4d30a712e0ef5cdb215324cee067db Mon Sep 17 00:00:00 2001 From: MigueloMadeira Date: Mon, 26 Oct 2026 16:17:41 +0000 Subject: [PATCH 1/2] RDODCP-1692: log refused outbound connections at info level With --disable-forward, refused outbound channels were only logged at debug level, so they never showed up without -v. Log them at info level with the requested target, quoted because it comes from the client. --- README.md | 1 + share/tunnel/tunnel_out_ssh.go | 3 +- share/tunnel/tunnel_out_ssh_test.go | 50 +++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 share/tunnel/tunnel_out_ssh_test.go diff --git a/README.md b/README.md index 008663cc..614b6d7e 100644 --- a/README.md +++ b/README.md @@ -546,6 +546,7 @@ Since WebSockets support is required: - client exits non-zero when `--max-retry-count` is exhausted; new `--min-retry-interval` (default 1s); `socks5://` accepted for `--proxy` - `go install` builds report their real version; sessions and failed logins are logged at info level - new `--disable-forward` server flag refuses connections through forward (non-reverse) remotes; this flag is specific to this fork and is not in upstream + - refused outbound connections are logged at info level with the requested target (`Denied outbound connection to ""`) - releases now ship goreleaser-built multi-arch Docker images to GHCR and Docker Hub with correctly stamped versions; releasing is two-stage — tagging builds a draft GitHub release plus version-tagged images, and publishing the draft promotes the Docker `latest` / `X` / `X.Y` tags ### Upgrading to 1.12 diff --git a/share/tunnel/tunnel_out_ssh.go b/share/tunnel/tunnel_out_ssh.go index a54f3e10..1c3fa192 100644 --- a/share/tunnel/tunnel_out_ssh.go +++ b/share/tunnel/tunnel_out_ssh.go @@ -34,7 +34,8 @@ func (t *Tunnel) handleSSHChannels(ctx context.Context, chans <-chan ssh.NewChan func (t *Tunnel) handleSSHChannel(ctx context.Context, ch ssh.NewChannel) { if !t.Config.Outbound { - t.Debugf("Denied outbound connection") + //info-level so refusals are visible without -v; the target is client-controlled, so quote it + t.Infof("Denied outbound connection to %q", string(ch.ExtraData())) ch.Reject(ssh.Prohibited, "Denied outbound connection") return } diff --git a/share/tunnel/tunnel_out_ssh_test.go b/share/tunnel/tunnel_out_ssh_test.go new file mode 100644 index 00000000..aba4b965 --- /dev/null +++ b/share/tunnel/tunnel_out_ssh_test.go @@ -0,0 +1,50 @@ +package tunnel + +import ( + "bytes" + "strings" + "testing" + + "github.com/jpillora/chisel/share/cio" + "golang.org/x/crypto/ssh" +) + +type rejectedChannel struct { + extra string + reason ssh.RejectionReason + message string +} + +func (c *rejectedChannel) Accept() (ssh.Channel, <-chan *ssh.Request, error) { + panic("channel should not be accepted") +} + +func (c *rejectedChannel) Reject(reason ssh.RejectionReason, message string) error { + c.reason, c.message = reason, message + return nil +} + +func (c *rejectedChannel) ChannelType() string { return "chisel" } +func (c *rejectedChannel) ExtraData() []byte { return []byte(c.extra) } + +func TestDeniedOutboundConnectionLoggedAtInfo(t *testing.T) { + var logs bytes.Buffer + logger := cio.NewLoggerFlag("server", 0) + logger.Info = true + logger.SetOutput(&logs) + tun := &Tunnel{Config: Config{Logger: logger, Outbound: false}} + + ch := &rejectedChannel{extra: "example.com:80\nserver: forged"} + tun.handleSSHChannel(nil, ch) + + if ch.reason != ssh.Prohibited || ch.message != "Denied outbound connection" { + t.Fatalf("unexpected rejection: %v %q", ch.reason, ch.message) + } + const want = "server: Denied outbound connection to \"example.com:80\\nserver: forged\"\n" + if got := logs.String(); got != want { + t.Fatalf("unexpected log:\n got: %q\nwant: %q", got, want) + } + if strings.Count(logs.String(), "\n") != 1 { + t.Fatalf("client-controlled target created extra log lines: %q", logs.String()) + } +} From 1c799c90db22830d1fc5321d699d642b8768c876 Mon Sep 17 00:00:00 2001 From: MigueloMadeira Date: Mon, 26 Oct 2026 16:32:23 +0000 Subject: [PATCH 2/2] RDODCP-1692: drop redundant check from refusal log test The exact log match already proves the refusal is logged on a single line. --- share/tunnel/tunnel_out_ssh_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/share/tunnel/tunnel_out_ssh_test.go b/share/tunnel/tunnel_out_ssh_test.go index aba4b965..1b8ef9db 100644 --- a/share/tunnel/tunnel_out_ssh_test.go +++ b/share/tunnel/tunnel_out_ssh_test.go @@ -2,7 +2,6 @@ package tunnel import ( "bytes" - "strings" "testing" "github.com/jpillora/chisel/share/cio" @@ -44,7 +43,4 @@ func TestDeniedOutboundConnectionLoggedAtInfo(t *testing.T) { if got := logs.String(); got != want { t.Fatalf("unexpected log:\n got: %q\nwant: %q", got, want) } - if strings.Count(logs.String(), "\n") != 1 { - t.Fatalf("client-controlled target created extra log lines: %q", logs.String()) - } }