Skip to content

sftp-server - lock user in working directory (or prevent folder navigation) #605

Open

Description

@pestevao

Hi,

I'm using this example https://github.com/pkg/sftp/blob/master/examples/go-sftp-server/main.go to make some tests on a internal sftp-server for data syncronization on multiple servers.

I was abble to define some working directories per user, checking username and defined WithServerWorkingDirectory using serverOptions.

But the users can navigate to other folders using cd .. or cd /directory.

Some chance to lock users in the defined working directories?
I was thinking of compare the output for the current sftp folder and compare it with the working directory and throw some error to the sftp client but can find a way to do it.

Thank you, Pedro

Activity

  1. puellanivis commented on Nov 28, 2024

    @puellanivis
    Collaborator

    There is no way to guarantee that a user cannot get out of their working directory. You need to use a chroot to do that.

    The problem is that one can always do ln -s / root in a directory that you have write permission to, and voila, you can access the whole drive through a ${PWD}/root/ filename prefix, that will pass any such test you can think of to check a prefix on the filename.

  2. pestevao commented on Dec 2, 2024

    @pestevao
    Author

    True.
    But this will be a read only sftp so unable to write on it.

  3. puellanivis commented on Dec 3, 2024

    @puellanivis
    Collaborator

    I would recommend either implementing a RequestSever or from the new dev-v2 branch implementing the ServerHandler and you can then lock out accesses without a specific prefix.

  4. drakkan commented on Dec 3, 2024

    @drakkan
    Collaborator

    With Go 1.24 os.Root may help.

  5. cs8425 commented on Aug 15, 2025

    @cs8425

    I recently had a similar requirement.
    And I successfully done it by using sftp.RequestServer.
    I also tried the os.Root API in Go 1.24,
    but some operations still don’t have corresponding APIs,
    so path issues (Rename, Chmod, Chtimes) still need to be handled manually.
    ps. It seems that Go 1.25 has added the corresponding APIs, but I haven’t tried them yet.

    Additionally, during testing on the Windows platform, I discovered an issue:
    When using the os.Root API, directory junction won't work, even if the target does not escape,
    it still reports the error: "path escapes from parent".
    I’m still investigating whether this is a bug or expected behavior.

    here is the code (and repo):
    https://github.com/cs8425/go-sftpd/blob/main/sftpd_handler.go

  6. mansguiche commented on Oct 22, 2025

    @mansguiche

    When using the os.Root API, directory junction won't work, even if the target does not escape,
    it still reports the error: "path escapes from parent".

    I had the same issue. It seems like the r.Filepath always has a / prefixed even if the user didn't send one. I am not sure if this is intended or not, but it would be nice to have an option to receive the user's requested Filepath verbatim.

    To work around it I am manually stripping the prefixed / and any .. or similar upward traversal paths. This doesn't feel like a nice solution.

  7. puellanivis commented on Oct 22, 2025

    @puellanivis
    Collaborator

    So, I think the prefix is coming from here: https://github.com/pkg/sftp/blob/v1.13.9/request-server.go#L346

    Part of the problem is that while SFTP naturally inherits the concept of a current working directory from being a POSIX process, and there is no way to actually change that directory remotely from the client. So, while files can technically be accessed through relative path components like .. and ., it’s generally safest to access everything through absolute paths.

    You might want to check the dev-v2 branch. It’s basically complete rewrite, and a whole new API, but I think it’s a little less “cooked” inputs, that is, it decodes the encoding/ssh/filexfer.StatPacket and passes that directly into the Stat receiver method of the ServerHandler interface. Getting more use out of this dev branch, and we could get it to a level that we can release a v2.

  8. cs8425 commented on Oct 23, 2025

    @cs8425

    When using the os.Root API, directory junction won't work, even if the target does not escape,
    it still reports the error: "path escapes from parent".

    I had the same issue. It seems like the r.Filepath always has a / prefixed even if the user didn't send one. I am not sure if this is intended or not, but it would be nice to have an option to receive the user's requested Filepath verbatim.

    To work around it I am manually stripping the prefixed / and any .. or similar upward traversal paths. This doesn't feel like a nice solution.

    yes, I also add . too: https://github.com/cs8425/go-sftpd/blob/main/sftpd_handler.go#L41-L48

    but the issue is that using the os.Root API on windows platform, if root set to C:/test (map to /)
    and there is a directory junction link from C:/test/latest to C:/test/001 (/latest => /001),
    access anything in /latest (eg: /latest/build.zip) will always getting "path escapes from parent" error.
    Accessing /latest/build.zip (C:/test/latest/build.zip) should equivalent to access /001/build.zip (C:/test/001/build.zip), and without any error.

  9. mansguiche commented on Oct 23, 2025

    @mansguiche

    directory junction link

    Is that the same as a symbolic link? I think os.Root is deliberately designed to error on an absolute symlink.

    Methods on Root will follow symbolic links, but symbolic links may not reference a location outside the root. Symbolic links must not be absolute.
    https://pkg.go.dev/os#Root

  10. cs8425 commented on Oct 23, 2025

    @cs8425

    directory junction link

    Is that the same as a symbolic link? I think os.Root is deliberately designed to error on an absolute symlink.

    for directory, yes, almost same as a symbolic link on linux.
    for file, no, it only work for directory.

    Methods on Root will follow symbolic links, but symbolic links may not reference a location outside the root. Symbolic links must not be absolute.
    https://pkg.go.dev/os#Root

    oh, maybe this is the answer....
    I had try using DeviceIoControl to create a directory junction before,
    it reject to write a relative path, only accept absolute path,
    even mklink /J just only creat a directory junction with absolute path.
    Not sure if there are some different method to do with relative path version of directory junction.

  11. puellanivis commented on Oct 24, 2025

    @puellanivis
    Collaborator

    Because of the way directory junctions work (as a reparse point for a given path) they can only ever work via full absolute paths.

    You will need to use mklink /d to create a symbolic link that is more compatible with POSIX semantics, and allows for relative path symbolic links in Windows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions