{"id":13396798,"url":"https://github.com/cyphar/filepath-securejoin","last_synced_at":"2025-05-14T19:06:02.269Z","repository":{"id":49543508,"uuid":"90438444","full_name":"cyphar/filepath-securejoin","owner":"cyphar","description":"Proposed filepath.SecureJoin implementation","archived":false,"fork":false,"pushed_at":"2025-03-06T18:34:07.000Z","size":274,"stargazers_count":103,"open_issues_count":3,"forks_count":20,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-05-03T03:12:14.833Z","etag":null,"topics":["filesystem","golang","proposal","symlink"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cyphar.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-05-06T04:59:53.000Z","updated_at":"2025-04-01T15:54:34.000Z","dependencies_parsed_at":"2024-01-15T22:42:46.091Z","dependency_job_id":"f0440dd1-37c7-4c0d-bf57-3d8f590f6c87","html_url":"https://github.com/cyphar/filepath-securejoin","commit_stats":{"total_commits":109,"total_committers":8,"mean_commits":13.625,"dds":"0.35779816513761464","last_synced_commit":"17264db7e6783eaabdddc20d34bf5293b38ba48f"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyphar%2Ffilepath-securejoin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyphar%2Ffilepath-securejoin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyphar%2Ffilepath-securejoin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cyphar%2Ffilepath-securejoin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cyphar","download_url":"https://codeload.github.com/cyphar/filepath-securejoin/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254050546,"owners_count":22006314,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["filesystem","golang","proposal","symlink"],"created_at":"2024-07-30T18:01:03.434Z","updated_at":"2025-05-14T19:05:59.531Z","avatar_url":"https://github.com/cyphar.png","language":"Go","funding_links":[],"categories":["Library"],"sub_categories":[],"readme":"## `filepath-securejoin` ##\n\n[![Go Documentation](https://pkg.go.dev/badge/github.com/cyphar/filepath-securejoin.svg)](https://pkg.go.dev/github.com/cyphar/filepath-securejoin)\n[![Build Status](https://github.com/cyphar/filepath-securejoin/actions/workflows/ci.yml/badge.svg)](https://github.com/cyphar/filepath-securejoin/actions/workflows/ci.yml)\n\n### Old API ###\n\nThis library was originally just an implementation of `SecureJoin` which was\n[intended to be included in the Go standard library][go#20126] as a safer\n`filepath.Join` that would restrict the path lookup to be inside a root\ndirectory.\n\nThe implementation was based on code that existed in several container\nruntimes. Unfortunately, this API is **fundamentally unsafe** against attackers\nthat can modify path components after `SecureJoin` returns and before the\ncaller uses the path, allowing for some fairly trivial TOCTOU attacks.\n\n`SecureJoin` (and `SecureJoinVFS`) are still provided by this library to\nsupport legacy users, but new users are strongly suggested to avoid using\n`SecureJoin` and instead use the [new api](#new-api) or switch to\n[libpathrs][libpathrs].\n\nWith the above limitations in mind, this library guarantees the following:\n\n* If no error is set, the resulting string **must** be a child path of\n  `root` and will not contain any symlink path components (they will all be\n  expanded).\n\n* When expanding symlinks, all symlink path components **must** be resolved\n  relative to the provided root. In particular, this can be considered a\n  userspace implementation of how `chroot(2)` operates on file paths. Note that\n  these symlinks will **not** be expanded lexically (`filepath.Clean` is not\n  called on the input before processing).\n\n* Non-existent path components are unaffected by `SecureJoin` (similar to\n  `filepath.EvalSymlinks`'s semantics).\n\n* The returned path will always be `filepath.Clean`ed and thus not contain any\n  `..` components.\n\nA (trivial) implementation of this function on GNU/Linux systems could be done\nwith the following (note that this requires root privileges and is far more\nopaque than the implementation in this library, and also requires that\n`readlink` is inside the `root` path and is trustworthy):\n\n```go\npackage securejoin\n\nimport (\n\t\"os/exec\"\n\t\"path/filepath\"\n)\n\nfunc SecureJoin(root, unsafePath string) (string, error) {\n\tunsafePath = string(filepath.Separator) + unsafePath\n\tcmd := exec.Command(\"chroot\", root,\n\t\t\"readlink\", \"--canonicalize-missing\", \"--no-newline\", unsafePath)\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\texpanded := string(output)\n\treturn filepath.Join(root, expanded), nil\n}\n```\n\n[libpathrs]: https://github.com/openSUSE/libpathrs\n[go#20126]: https://github.com/golang/go/issues/20126\n\n### New API ###\n\nWhile we recommend users switch to [libpathrs][libpathrs] as soon as it has a\nstable release, some methods implemented by libpathrs have been ported to this\nlibrary to ease the transition. These APIs are only supported on Linux.\n\nThese APIs are implemented such that `filepath-securejoin` will\nopportunistically use certain newer kernel APIs that make these operations far\nmore secure. In particular:\n\n* All of the lookup operations will use [`openat2`][openat2.2] on new enough\n  kernels (Linux 5.6 or later) to restrict lookups through magic-links and\n  bind-mounts (for certain operations) and to make use of `RESOLVE_IN_ROOT` to\n  efficiently resolve symlinks within a rootfs.\n\n* The APIs provide hardening against a malicious `/proc` mount to either detect\n  or avoid being tricked by a `/proc` that is not legitimate. This is done\n  using [`openat2`][openat2.2] for all users, and privileged users will also be\n  further protected by using [`fsopen`][fsopen.2] and [`open_tree`][open_tree.2]\n  (Linux 5.2 or later).\n\n[openat2.2]: https://www.man7.org/linux/man-pages/man2/openat2.2.html\n[fsopen.2]: https://github.com/brauner/man-pages-md/blob/main/fsopen.md\n[open_tree.2]: https://github.com/brauner/man-pages-md/blob/main/open_tree.md\n\n#### `OpenInRoot` ####\n\n```go\nfunc OpenInRoot(root, unsafePath string) (*os.File, error)\nfunc OpenatInRoot(root *os.File, unsafePath string) (*os.File, error)\nfunc Reopen(handle *os.File, flags int) (*os.File, error)\n```\n\n`OpenInRoot` is a much safer version of\n\n```go\npath, err := securejoin.SecureJoin(root, unsafePath)\nfile, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC)\n```\n\nthat protects against various race attacks that could lead to serious security\nissues, depending on the application. Note that the returned `*os.File` is an\n`O_PATH` file descriptor, which is quite restricted. Callers will probably need\nto use `Reopen` to get a more usable handle (this split is done to provide\nuseful features like PTY spawning and to avoid users accidentally opening bad\ninodes that could cause a DoS).\n\nCallers need to be careful in how they use the returned `*os.File`. Usually it\nis only safe to operate on the handle directly, and it is very easy to create a\nsecurity issue. [libpathrs][libpathrs] provides far more helpers to make using\nthese handles safer -- there is currently no plan to port them to\n`filepath-securejoin`.\n\n`OpenatInRoot` is like `OpenInRoot` except that the root is provided using an\n`*os.File`. This allows you to ensure that multiple `OpenatInRoot` (or\n`MkdirAllHandle`) calls are operating on the same rootfs.\n\n\u003e **NOTE**: Unlike `SecureJoin`, `OpenInRoot` will error out as soon as it hits\n\u003e a dangling symlink or non-existent path. This is in contrast to `SecureJoin`\n\u003e which treated non-existent components as though they were real directories,\n\u003e and would allow for partial resolution of dangling symlinks. These behaviours\n\u003e are at odds with how Linux treats non-existent paths and dangling symlinks,\n\u003e and so these are no longer allowed.\n\n#### `MkdirAll` ####\n\n```go\nfunc MkdirAll(root, unsafePath string, mode int) error\nfunc MkdirAllHandle(root *os.File, unsafePath string, mode int) (*os.File, error)\n```\n\n`MkdirAll` is a much safer version of\n\n```go\npath, err := securejoin.SecureJoin(root, unsafePath)\nerr = os.MkdirAll(path, mode)\n```\n\nthat protects against the same kinds of races that `OpenInRoot` protects\nagainst.\n\n`MkdirAllHandle` is like `MkdirAll` except that the root is provided using an\n`*os.File` (the reason for this is the same as with `OpenatInRoot`) and an\n`*os.File` of the final created directory is returned (this directory is\nguaranteed to be effectively identical to the directory created by\n`MkdirAllHandle`, which is not possible to ensure by just using `OpenatInRoot`\nafter `MkdirAll`).\n\n\u003e **NOTE**: Unlike `SecureJoin`, `MkdirAll` will error out as soon as it hits\n\u003e a dangling symlink or non-existent path. This is in contrast to `SecureJoin`\n\u003e which treated non-existent components as though they were real directories,\n\u003e and would allow for partial resolution of dangling symlinks. These behaviours\n\u003e are at odds with how Linux treats non-existent paths and dangling symlinks,\n\u003e and so these are no longer allowed. This means that `MkdirAll` will not\n\u003e create non-existent directories referenced by a dangling symlink.\n\n### License ###\n\nThe license of this project is the same as Go, which is a BSD 3-clause license\navailable in the `LICENSE` file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyphar%2Ffilepath-securejoin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcyphar%2Ffilepath-securejoin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyphar%2Ffilepath-securejoin/lists"}