Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
82a5a4f
ci: bump actions/setup-go from 1 to 6
dependabot[bot] Apr 29, 2026
752390b
deps: bump github.com/hanwen/go-fuse/v2 from 2.9.0 to 2.10.1
dependabot[bot] Apr 29, 2026
2288450
deps: bump golang.org/x/term from 0.42.0 to 0.43.0
dependabot[bot] May 10, 2026
51bcf86
deps: bump golang.org/x/crypto from 0.50.0 to 0.51.0
dependabot[bot] May 10, 2026
eae9839
deps: bump golang.org/x/sys from 0.43.0 to 0.44.0
dependabot[bot] May 10, 2026
d6cde09
deps: bump golang.org/x/net from 0.53.0 to 0.54.0
dependabot[bot] May 10, 2026
ca5f9f1
update.
blacknon May 16, 2026
733f4a3
Merge pull request #64 from blacknon/dependabot/go_modules/golang.org…
blacknon May 16, 2026
00f533d
Merge branch 'v0.1.35' into dependabot/go_modules/github.com/hanwen/g…
Copilot May 16, 2026
ced09ab
Restore AGENTS.md merge context
Copilot May 16, 2026
8f04698
Merge pull request #63 from blacknon/dependabot/go_modules/github.com…
blacknon May 16, 2026
6b4d3b0
Merge branch 'v0.1.35' into dependabot/go_modules/golang.org/x/crypto…
Copilot May 16, 2026
107b753
Merge pull request #65 from blacknon/dependabot/go_modules/golang.org…
blacknon May 16, 2026
cf67842
Merge pull request #62 from blacknon/dependabot/github_actions/action…
blacknon May 16, 2026
14ca3e9
Merge branch 'v0.1.35' into dependabot/go_modules/golang.org/x/sys-0.…
Copilot May 16, 2026
291ae63
Merge branch 'v0.1.35' into dependabot/go_modules/golang.org/x/sys-0.…
Copilot May 16, 2026
6d6cf2c
Merge pull request #66 from blacknon/dependabot/go_modules/golang.org…
blacknon May 16, 2026
5441c6f
Merge branch 'v0.1.35' into dependabot/go_modules/golang.org/x/net-0.…
Copilot May 16, 2026
42cb554
Merge pull request #67 from blacknon/dependabot/go_modules/golang.org…
blacknon May 16, 2026
4bb60d6
update. AGENTS.md
blacknon May 16, 2026
7188330
update. add test
blacknon May 16, 2026
00937b3
update.
blacknon May 16, 2026
b70e6ea
update.
blacknon May 16, 2026
3376519
update.
blacknon May 16, 2026
d178cdd
update.
blacknon May 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test_and_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Set up Go 1.22
uses: actions/setup-go@v1
uses: actions/setup-go@v6
with:
go-version: 1.22

Expand Down
25 changes: 16 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ AGENTS.md

- ユーザー向け挙動、CLI オプション、設定方法、インストール手順を変えた場合は、対応する README / docs も更新してください。

---

## 補足

### NFS / lsshfs compatibility patch
### 注意事項

#### NFS / lsshfs compatibility patch

このリポジトリでは、`lsshfs` を macOS 上で NFS backend として利用した際に発生する以下の問題を回避するため、`go-nfs` と `go-billy` を `internal/third_party` 配下でローカル管理している。

Expand All @@ -36,7 +40,7 @@ AGENTS.md
- `Error applying attributes: Operation not supported: permission denied`
- mount 先に不要な一時ファイルや 0 byte ファイルが残ることがある

#### 現在の依存構成
##### 現在の依存構成

`go.mod` では以下の `replace` を使っている。

Expand All @@ -45,9 +49,10 @@ AGENTS.md

この構成は意図的なものであり、安易に upstream 版へ戻してはいけない。

#### 具体的な修正内容
##### 具体的な修正内容

###### 1. `nfs_sftpfs.go`

##### 1. `nfs_sftpfs.go`
`NewChangeSFTPFS()` の返り値を、単純な `temporal.New(chroot.New(...))` ではなく、`billy.Change` を維持する wrapper に変更している。

変更意図:
Expand All @@ -65,7 +70,8 @@ AGENTS.md

これらは `sftp.Client` へ forward する。

##### 2. `internal/third_party/github.com/willscott/go-nfs/nfs_oncreate.go`
###### 2. `internal/third_party/github.com/willscott/go-nfs/nfs_oncreate.go`

`CREATE_EXCLUSIVE` を即 `NFSStatusNotSupp` で失敗させないようにしている。

変更意図:
Expand All @@ -76,19 +82,20 @@ AGENTS.md

さらに、attribute apply に失敗した場合は、作りかけファイルを `Remove()` して cleanup するようにしている。

##### 3. `internal/third_party/github.com/go-git/go-billy/v5`
###### 3. `internal/third_party/github.com/go-git/go-billy/v5`

このディレクトリは `replace` 先として内部保持している。
現時点では `go-sshlib` 側の wrapper で `Change` 問題を吸収しているが、依存整合性と将来の patch 管理のため、`go-billy` も `internal/third_party` 側で固定している。

#### 重要な注意
##### 重要な注意

以下は勝手に変更しないこと。

- `go.mod` の `replace` の削除
- `internal/third_party` 配下の削除
- upstream 版への安易な差し戻し
- `nfs_sftpfs.go` の `changeChrootFS` wrapper の除去
- `nfs_oncreate.go` の `exclusive create` 回避処理の除去
- `internal/third_party/github.com/willscott/go-nfs/nfs_oncreate.go` の `exclusive create` 回避処理の除去

これらを戻すと、`lsshfs` で以下の不具合が再発する可能性が高い。

Expand All @@ -97,7 +104,7 @@ AGENTS.md
- attribute apply エラー
- macOS 上での不安定な file operation

#### 変更時のルール
##### 変更時のルール

この周辺を変更する場合は、少なくとも以下を確認すること。

Expand Down
64 changes: 64 additions & 0 deletions auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,70 @@ func TestCreateControlPersistAuthMethodsRemovesTransientKeyFile(t *testing.T) {
}
}

func TestCleanupControlPersistTransientFilesRemovesDuplicates(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "transient-key")
if err := os.WriteFile(path, []byte("secret"), 0o600); err != nil {
t.Fatalf("os.WriteFile() error = %v", err)
}

cleanupControlPersistTransientFiles([]string{path, path, ""})

if _, err := os.Stat(path); !os.IsNotExist(err) {
t.Fatalf("transient file still exists: stat err = %v", err)
}
}

func TestControlPersistAuthMethodKeyNil(t *testing.T) {
key, ok := controlPersistAuthMethodKey(nil)
if ok {
t.Fatalf("controlPersistAuthMethodKey(nil) = %#v, true; want false", key)
}
}

func TestLookupControlPersistAuthMethodUnknown(t *testing.T) {
auth := ssh.Password("secret")
if _, ok := lookupControlPersistAuthMethod(auth); ok {
t.Fatal("lookupControlPersistAuthMethod() unexpectedly found unregistered auth method")
}
}

func TestValidateControlPersistAuthDefinitionsMissingFields(t *testing.T) {
tests := []struct {
name string
defs []controlPersistAuthMethodDefinition
want string
}{
{
name: "password requires value",
defs: []controlPersistAuthMethodDefinition{{Type: "password"}},
want: "password auth requires Password",
},
{
name: "publickey requires key path",
defs: []controlPersistAuthMethodDefinition{{Type: "publickey"}},
want: "publickey auth requires KeyPath",
},
{
name: "pkcs11 requires provider",
defs: []controlPersistAuthMethodDefinition{{Type: "pkcs11"}},
want: "pkcs11 auth requires PKCS11Provider",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateControlPersistAuthDefinitions(tt.defs)
if err == nil {
t.Fatal("validateControlPersistAuthDefinitions() error = nil, want non-nil")
}
if !strings.Contains(err.Error(), tt.want) {
t.Fatalf("validateControlPersistAuthDefinitions() error = %v, want %q", err, tt.want)
}
})
}
}

func writeTempPrivateKey(t *testing.T) string {
t.Helper()

Expand Down
93 changes: 93 additions & 0 deletions common_proxy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package sshlib

import (
"os/user"
"path/filepath"
"testing"

"golang.org/x/net/proxy"
)

func TestGetAbsPathExpandsHomePrefix(t *testing.T) {
usr, err := user.Current()
if err != nil || usr == nil || usr.HomeDir == "" {
t.Skip("current user home directory is unavailable")
}

got := getAbsPath("~/sshlib-test")
want := filepath.Join(usr.HomeDir, "sshlib-test")
if got != want {
t.Fatalf("getAbsPath(%q) = %q, want %q", "~/sshlib-test", got, want)
}
}

func TestGetAbsPathPreservesTildeInsideWindowsShortNameStylePath(t *testing.T) {
input := filepath.Join("tmp", "RUNNERC~1", "sshlib")
got := getAbsPath(input)
want, err := filepath.Abs(input)
if err != nil {
t.Fatalf("filepath.Abs() error = %v", err)
}
if got != want {
t.Fatalf("getAbsPath(%q) = %q, want %q", input, got, want)
}
}

func TestCreateHttpProxyDialerIncludesAddressPortAndAuth(t *testing.T) {
p := &Proxy{
Type: "http",
Addr: "proxy.example.com",
Port: "8080",
User: "alice",
Password: "secret",
Forwarder: &stubContextDialer{},
}

dialer, err := p.CreateHttpProxyDialer()
if err != nil {
t.Fatalf("CreateHttpProxyDialer() error = %v", err)
}

httpDialer, ok := dialer.(*httpProxy)
if !ok {
t.Fatalf("CreateHttpProxyDialer() type = %T, want *httpProxy", dialer)
}
if httpDialer.host != "proxy.example.com:8080" {
t.Fatalf("CreateHttpProxyDialer() host = %q, want %q", httpDialer.host, "proxy.example.com:8080")
}
if !httpDialer.haveAuth || httpDialer.username != "alice" || httpDialer.password != "secret" {
t.Fatalf("CreateHttpProxyDialer() auth = have:%t user:%q pass:%q", httpDialer.haveAuth, httpDialer.username, httpDialer.password)
}
if httpDialer.forward != p.Forwarder {
t.Fatal("CreateHttpProxyDialer() did not preserve custom forwarder")
}
}

func TestCreateHttpProxyDialerDefaultsToDirectForwarder(t *testing.T) {
p := &Proxy{
Type: "http",
Addr: "proxy.example.com",
}

dialer, err := p.CreateHttpProxyDialer()
if err != nil {
t.Fatalf("CreateHttpProxyDialer() error = %v", err)
}

httpDialer, ok := dialer.(*httpProxy)
if !ok {
t.Fatalf("CreateHttpProxyDialer() type = %T, want *httpProxy", dialer)
}
if httpDialer.forward != proxy.Direct {
t.Fatal("CreateHttpProxyDialer() should default to proxy.Direct forwarder")
}
}

func TestProxyRoutePortOrDefault(t *testing.T) {
if got := (ProxyRoute{Type: "ssh"}).portOrDefault(); got != "22" {
t.Fatalf("portOrDefault() = %q, want %q", got, "22")
}
if got := (ProxyRoute{Type: "http", Port: "8080"}).portOrDefault(); got != "8080" {
t.Fatalf("portOrDefault() = %q, want %q", got, "8080")
}
}
Loading
Loading