{"id":13466440,"url":"https://github.com/hacdias/webdav","last_synced_at":"2026-10-18T23:44:56.467Z","repository":{"id":38400405,"uuid":"99202994","full_name":"hacdias/webdav","owner":"hacdias","description":"A simple and standalone WebDAV server.","archived":false,"fork":false,"pushed_at":"2026-10-10T08:27:37.000Z","size":373,"stargazers_count":5906,"open_issues_count":15,"forks_count":562,"subscribers_count":33,"default_branch":"main","last_synced_at":"2026-10-10T10:21:52.822Z","etag":null,"topics":["filesystem","go","http","server","webdav"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hacdias.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","accessibility":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"claude":null,"gemini":null,"cursor":null,"copilot":null,"dco":null,"cla":null,"disclosure":null},"funding":{"github":"hacdias","liberapay":"hacdias","ko_fi":"hacdias"}},"created_at":"2017-08-03T07:18:14.000Z","updated_at":"2026-10-10T09:34:27.000Z","dependencies_parsed_at":"2026-10-10T10:14:10.482Z","dependency_job_id":null,"html_url":"https://github.com/hacdias/webdav","commit_stats":{"total_commits":132,"total_committers":25,"mean_commits":5.28,"dds":0.6136363636363636,"last_synced_commit":"d418bd26616d513b5ae4365fef9525fc8d4f269d"},"previous_names":[],"tags_count":74,"template":false,"template_full_name":null,"purl":"pkg:github/hacdias/webdav","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hacdias%2Fwebdav","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hacdias%2Fwebdav/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hacdias%2Fwebdav/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hacdias%2Fwebdav/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hacdias","download_url":"https://codeload.github.com/hacdias/webdav/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hacdias%2Fwebdav/sbom","scorecard":{"id":452025,"data":{"date":"2025-08-11","repo":{"name":"github.com/hacdias/webdav","commit":"1753aa294554732b33700d1c3126912c4b388fbf"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 1/20 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":10,"reason":"21 commit(s) and 10 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/build.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/build.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/docker.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/docker.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/docker.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/docker.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker.yml:30: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/docker.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker.yml:36: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/docker.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker.yml:44: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/docker.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker.yml:58: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/docker.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/lint.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/lint.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/lint.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/lint.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/lint.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/lint.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/releaser.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/releaser.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/releaser.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/releaser.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/releaser.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/releaser.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/test.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/hacdias/webdav/test.yml/main?enable=pin","Warn: containerImage not pinned by hash: Dockerfile:1","Info:   0 out of   9 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   8 third-party GitHubAction dependencies pinned","Info:   0 out of   1 containerImage dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Info: jobLevel 'contents' permission set to 'read': .github/workflows/docker.yml:16","Warn: no topLevel permission defined: .github/workflows/build.yml:1","Warn: no topLevel permission defined: .github/workflows/docker.yml:1","Warn: no topLevel permission defined: .github/workflows/lint.yml:1","Warn: topLevel 'contents' permission set to 'write': .github/workflows/releaser.yml:9","Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v5.8.0 not signed: https://api.github.com/repos/hacdias/webdav/releases/219836053","Warn: release artifact v5.7.5 not signed: https://api.github.com/repos/hacdias/webdav/releases/219265679","Warn: release artifact v5.7.4 not signed: https://api.github.com/repos/hacdias/webdav/releases/206455167","Warn: release artifact v5.7.3 not signed: https://api.github.com/repos/hacdias/webdav/releases/199776158","Warn: release artifact v5.7.2 not signed: https://api.github.com/repos/hacdias/webdav/releases/194977755","Warn: release artifact v5.8.0 does not have provenance: https://api.github.com/repos/hacdias/webdav/releases/219836053","Warn: release artifact v5.7.5 does not have provenance: https://api.github.com/repos/hacdias/webdav/releases/219265679","Warn: release artifact v5.7.4 does not have provenance: https://api.github.com/repos/hacdias/webdav/releases/206455167","Warn: release artifact v5.7.3 does not have provenance: https://api.github.com/repos/hacdias/webdav/releases/199776158","Warn: release artifact v5.7.2 does not have provenance: https://api.github.com/repos/hacdias/webdav/releases/194977755"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/docker.yml:12"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 13 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-19T08:25:42.987Z","repository_id":38400405,"created_at":"2025-08-19T08:25:42.987Z","updated_at":"2025-08-19T08:25:42.987Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":344872684,"owners_count":38343425,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-10-03T21:59:58.778Z","status":"online","status_checked_at":"2026-10-18T02:00:06.448Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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","go","http","server","webdav"],"created_at":"2024-07-31T15:00:44.209Z","updated_at":"2026-10-18T23:44:56.460Z","avatar_url":"https://github.com/hacdias.png","language":"Go","funding_links":["https://github.com/sponsors/hacdias","https://liberapay.com/hacdias","https://ko-fi.com/hacdias"],"categories":["Go","Uncategorized","Servers","Tools","Web","Web 应用","server","Repositories"],"sub_categories":["Uncategorized","Standalone","Web Servers and Proxies"],"readme":"# webdav\n\n[![Version](https://img.shields.io/github/release/hacdias/webdav.svg?style=flat-square)](https://github.com/hacdias/webdav/releases/latest)\n[![Docker Pulls](https://img.shields.io/docker/pulls/hacdias/webdav?style=flat-square)](https://hub.docker.com/r/hacdias/webdav)\n\nA simple and standalone [WebDAV](https://en.wikipedia.org/wiki/WebDAV) server.\n\n## Install\n\nFor a manual install, please refer to the [releases](https://github.com/hacdias/webdav/releases) page and download the correct binary for your system. Alternatively, you can build or install it from source using the Go toolchain. You can either clone the repository and execute `go build`, or directly install it, using:\n\n```\ngo install github.com/hacdias/webdav/v5@latest\n```\n\n### Homebrew\n\nIf you have Homebrew available on your system, you can also install `webdav` using it: \n\n```\nbrew install webdav\n```\n\n### Docker\n\nDocker images are provided on both [GitHub's registry](https://github.com/hacdias/webdav/pkgs/container/webdav) and [Docker Hub](https://hub.docker.com/r/hacdias/webdav). You can pull the images using one of the following two commands. Note that this commands pull the latest released version. You can use specific tags to pin specific versions, or use `main` for the development branch.\n\n```bash\n# GitHub Registry\ndocker pull ghcr.io/hacdias/webdav:latest\n\n# Docker Hub\ndocker pull hacdias/webdav:latest\n```\n\n## Usage\n\nFor usage information regarding the CLI, run `webdav --help`.\n\n### Container\n\nTo run the container, you can refer to the `compose.yml` file which provides a minimal setup. Additionally, you also need to create a configuration file, as [explained below](#configuration).\n\nThe equivalent Docker command to the aforementioned compose file would be as follows:\n\n```bash\ndocker run \\\n  -p 6065:6065 \\\n  -v ./config.yml:/config.yml:ro \\\n  -v ./data:/data \\\n  ghcr.io/hacdias/webdav -c /config.yml\n```\n\nIf you are using [fail2ban](#fail2ban-setup), it would be helpful to add the parameters listed below. They will assist in analyzing \nthe log.\n\n```bash\n--log-driver journald \\\n--name webdav \\\n```\n\n### Partial updates\n\nThis server supports partial file updates compatible with SabreDAV's `PATCH` extension. This is not an official WebDAV specification. Requests must use the `application/x-sabredav-partialupdate` content type, include `Content-Length`, and provide the target range in `X-Update-Range`.\n\nSupported `X-Update-Range` values are:\n\n- `bytes=start-end`\n- `bytes=start-`\n- `bytes=-N`\n- `append`\n\nFor clients that use it, the server also supports partial `PUT` requests with `Content-Range`, for example `Content-Range: bytes 6-8/*`. This is an extra compatibility path and should be treated as a client/server agreement.\n\n## Configuration\n\nThe configuration can be provided as a YAML, JSON or TOML file. Below is an example of a YAML configuration file with \nall the options available, as well as what they mean. Top-level keys use camelCase and keys nested inside a section use snake_case.\n\n```yaml\naddress: 0.0.0.0\nport: 6065\n\n# TLS-related settings if you want to enable TLS directly.\ntls: false\ncert: cert.pem\nkey: key.pem\n\n# Prefix to apply to the WebDAV path-ing. Default is '/'.\nprefix: /\n\n# Enable or disable debug logging. Default is 'false'.\ndebug: false\n\n# Disable sniffing the files to detect their content type. Default is 'false'.\nnoSniff: false\n\n# Whether the server runs behind a trusted proxy or not. When this is true,\n# the header X-Forwarded-For will be used for logging the remote addresses\n# of logging attempts (if available).\nbehindProxy: false\n\n# The directory that will be able to be accessed by the users when connecting.\n# This directory will be used by users unless they have their own 'directory' defined.\n# By default it points to the working directory. In the case of the compose file above,\n# that is /data.\ndirectory: /data\n\n# Alternatively, replace 'directory' with 'directories' to expose multiple\n# directories as virtual root entries. This option is mutually exclusive with\n# 'directory' in the same scope. Rules should include the virtual mount name,\n# such as /media/public/access/.\n# directories:\n#   - media: /data/media\n#   - /data/archive\n#   - name: backups\n#     path: /data/backups\n\n# The default permissions for users. This is a case insensitive option. Possible\n# permissions: C (Create), R (Read), U (Update), D (Delete). You can combine multiple\n# permissions. For example, to allow to read and create, set \"RC\". Default is \"R\".\n# LOCK counts as a write: it needs U on a path that exists and C on one that does\n# not, since locking a path that does not exist creates it.\n# Being overwritten counts as well: a COPY or MOVE onto an existing file replaces\n# it and needs U, while one onto an existing collection removes everything it\n# holds and needs D, on the collection and on every path under it.\npermissions: R\n\n# The default permissions rules for users. Default is none. Rules are applied\n# from last to first, that is, the first rule that matches the request, starting\n# from the end, will be applied to the request. Rule paths are always relative to\n# the user's directory.\nrules: []\n\n# The behavior of redefining the rules for users. It can be:\n# - overwrite: when a user has rules defined, these will overwrite any global\n#   rules already defined. That is, the global rules are not applicable to the\n#   user.\n# - append: when a user has rules defined, these will be appended to the global\n#   rules already defined. That is, for this user, their own specific rules will\n#   be checked first, and then the global rules.\n# Default is 'overwrite'.\nrulesBehavior: overwrite\n\n# Logging configuration\nlog:\n  # Logging format ('console', 'json'). Default is 'console'.\n  format: console\n  # Enable or disable colors. Default is 'true'. Only applied if format is 'console'.\n  colors: true\n  # Logging outputs. You can have more than one output. Default is only 'stderr'.\n  outputs:\n    - stderr\n\n# CORS configuration\ncors:\n  # Whether or not CORS configuration should be applied. Default is 'false'.\n  enabled: true\n  credentials: true\n  # Allow Private Network Access preflight requests. Default is 'false'.\n  allow_private_network: false\n  # The following are the default CORS settings when it is enabled.\n  allowed_hosts:\n    - '*'\n  allowed_headers:\n    - Authorization\n    - Content-Type\n    - Content-Range\n    - Depth\n    - Destination\n    - If\n    - Lock-Token\n    - Overwrite\n    - TimeOut\n    - Translate\n    - X-Update-Range\n  allowed_methods:\n    - COPY\n    - DELETE\n    - GET\n    - HEAD\n    - LOCK\n    - UNLOCK\n    - MKCOL\n    - MOVE\n    - OPTIONS\n    - PATCH\n    - POST\n    - PROPFIND\n    - PROPPATCH\n    - PUT\n  exposed_headers: []\n\n# HTML Directory Listing configuration\nlisting:\n  # Serve an HTML listing on GET requests to a collection, instead of the\n  # PROPFIND response. Sort with ?C=N|M|S (name, modified, size) and ?O=A|D\n  # (ascending, descending). Default is 'false'.\n  enabled: true\n  # Hide the parent directory (../) link. Default is 'false'.\n  hide_parent_dir: false\n  # Show the \"Index of \u003cpath\u003e\" title. Ignored with a custom header. Default is 'true'.\n  show_path: true\n  # Custom HTML replacing the page shell: header up to and including \u003cbody\u003e,\n  # footer from \u003c/body\u003e. Both must be set, inline or from a file.\n  # header: \"\u003c!DOCTYPE html\u003e\u003chtml\u003e\u003chead\u003e\u003ctitle\u003eWebDAV\u003c/title\u003e\u003c/head\u003e\u003cbody\u003e\"\n  # header_file: /etc/webdav/header.html\n  # footer: \"\u003c/body\u003e\u003c/html\u003e\"\n  # footer_file: /etc/webdav/footer.html\n\n# You define here the list of users.\n# Basic authentication is automatically be configured when users are detected \n# below, else there will be no authentication.\n# Customize to your needs and don't forget to comment out the users you don't need.\nusers:\n  # Example 'admin' user with plaintext password.\n  - username: admin\n    password: admin\n\n  # Example 'john' user with bcrypt encrypted password, with custom directory.\n  # Tip: you can generate a bcrypt-encrypted password by using the 'webdav bcrypt'\n  # command lint utility, or htpasswd on Linux.\n  - username: john\n    password: \"{bcrypt}$2y$10$zEP6oofmXFeHaeMfBNLnP.DO8m.H.Mwhd24/TOX2MWLxAExXi4qgi\"\n    directory: /data/john\n  # Example user whose details will be picked up from the environment.\n  - username: \"{env}ENV_USERNAME\"\n    password: \"{env}ENV_PASSWORD\"\n  # Example user with advanced control over his permissions\n  - username: basic\n    password: basic\n    permissions: CRUD # Override default permissions.\n    rules:\n      # With this rule, the user CANNOT access {user directory}/some/files.\n      - path: /some/file\n        permissions: none\n      # With this rule, the user CAN create, read, update and delete within\n      # {user directory}/public/access.\n      - path: /public/access/\n        permissions: CRUD\n      # With this rule, the user CAN read and update all files ending with .js.\n      # It uses a regular expression.\n      - regex: \"^.+.js$\"\n        permissions: RU\n  # Example user for android SeedVault backuping\n  - username: android\n    password: \"{bcrypt}$2y$10$zEP6oofmXFeHaeMfBNLnP.DO8m.H.Mwhd24/TOX2MWLxAExXi4qgi\"\n    directory: /data/android\n    permissions: CRUD\n\n# If you're delegating the authentication to a different service, you can proxy\n# the username using basic authentication, and then disable webdav's password\n# check using the option:\n# noPassword: true\n```\n\n### Rules\n\nRules are matched against the request path after dot segments have been resolved, so `/public/../secret/file` is matched as `/secret/file`. The last rule that matches wins.\n\nA `path` rule is a prefix match. A rule written with a trailing slash also covers the collection it names, so `path: /secret/` applies to a request for `/secret` as well. Such a rule can only restrict that collection: acting on the collection itself also requires the permissions that apply outside the rule, since the operation takes place in the parent collection.\n\nA `regex` rule is matched literally against the path, and gets none of the above handling. In particular `regex: \"^/secret/\"` does **not** match a request for `/secret` itself. Write `regex: \"^/secret(/|$)\"` if you want to cover the collection too.\n\nRules apply to every path an operation touches, not only the one it names. Collection listings leave out entries the rules deny, copying a collection leaves those entries behind, and a `MOVE` or `DELETE` that would act on a denied descendant is refused outright.\n\nOverwriting a destination is authorized for what it destroys. A `COPY` or `MOVE` onto an existing destination replaces it: RFC4918 has `MOVE` perform a `DELETE` with `Depth: infinity` on the destination first, and requires an overwritten collection to end up with exactly the membership the source had, so either way whatever was there is gone. Replacing a file needs `U` on it, the same permission `PUT` needs. Replacing a collection removes everything it holds, so it needs `D` on that collection and on every path beneath it: a rule withholding `D` anywhere under a destination refuses the overwrite outright, even where it grants `C` and `U`.\n\nRules follow the case sensitivity of the file system, which each served directory is probed for at startup. Where names are case-insensitive, as on APFS and NTFS, `path: /secret/` also covers `/SECRET/`, a `regex` is matched against the folded path as well as the path as written, and Unicode normal forms count as one name. Elsewhere rules are matched exactly, since `/secret` and `/SECRET` are then different directories.\n\n### CORS\n\nThe `allowed_*` properties are optional, the default value for each of them will be `*`. `exposed_headers` is optional as well, but is not set if not defined. Setting `allow_private_network` to `true` to allow Private-Network-Access preflight requests. Setting `credentials` to `true` will allow you to:\n\n1. Use `withCredentials = true` in javascript.\n2. Use the `username:password@host` syntax.\n\n## Caveats\n\n### Reverse Proxy Service\n\nWhen using a reverse proxy implementation, like Caddy, Nginx, or Apache, note that you need to forward the correct headers in order to avoid 502 errors.\n\n#### Nginx Configuration Example\n\n```nginx\nlocation / {\n  proxy_pass http://127.0.0.1:8080;\n  proxy_set_header X-Real-IP $remote_addr;\n  proxy_set_header REMOTE-HOST $remote_addr;\n  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n  proxy_set_header Host $host;\n  proxy_redirect off;\n\n  # Ensure COPY and MOVE commands work by rewriting the Destination header to\n  # contain only the path, e.g. /test.txt. Note that the captured group already\n  # includes the leading slash: adding another one would produce a Destination\n  # such as //test.txt, which is parsed as a host name and rejected.\n  set $dest $http_destination;\n  if ($http_destination ~ \"^https?://[^/]+(?\u003cpath\u003e/.*)$\") {\n    set $dest $path;\n  }\n  proxy_set_header Destination $dest;\n}\n```\n\n#### Caddy Configuration Example\n\n```Caddyfile\nexample.com {\n    tls internal # for local development\n    # tls name@email.com # so that Caddy gets certs for you via Letsencrypt\n\n    # Rewrites destination to remove host and include only the path e.g. /test.txt\n    @hasDest header_regexp dest ^https?://[^/]+(.*)$\n    header @hasDest Destination {re.dest.1}\n\n    # if running on the same network in docker you can just point to the service name e.g. webdav:6065\n    reverse_proxy 127.0.0.1:6065 {\n        header_up X-Real-IP {remote_host}\n        header_up REMOTE-HOST {remote_host}\n    }\n}\n```\n\n#### Serving Under a Subpath\n\nIf the server is not served from the root of the domain, do not strip the subpath in the reverse proxy. The server needs to see it: `PROPFIND` responses contain the full path of each resource, and clients reject the ones that fall outside of the URL they requested. Pass the subpath through and set [`prefix`](#configuration) accordingly, so that the server strips it itself and adds it back to the responses:\n\n```yaml\nprefix: /webdav\n```\n\nWith Caddy, that means using `handle` instead of `handle_path`, as the latter strips the matched prefix before proxying:\n\n```Caddyfile\nexample.com {\n    @hasDest header_regexp dest ^https?://[^/]+(.*)$\n    header @hasDest Destination {re.dest.1}\n\n    handle /webdav* {\n        reverse_proxy 127.0.0.1:6065 {\n            header_up X-Real-IP {remote_host}\n            header_up REMOTE-HOST {remote_host}\n        }\n    }\n}\n```\n\nWith Nginx, use a `location` block for the subpath and keep `proxy_pass` without a trailing path, as a trailing path would replace the prefix:\n\n```nginx\nlocation /webdav {\n  proxy_pass http://127.0.0.1:6065;\n  # ... the remaining headers, as above.\n}\n```\n\nBoth the request path and the `Destination` header must carry the prefix. A request without it is answered with `400 Bad Request`.\n\n## Examples\n\n### Systemd\n\nExample configuration of a [`systemd`](https://en.wikipedia.org/wiki/Systemd) service:\n\n```conf\n[Unit]\nDescription=WebDAV\nAfter=network.target\n\n[Service]\nType=simple\nUser=root\nExecStart=/usr/bin/webdav --config /opt/webdav.yml\nRestart=on-failure\n\n[Install]\nWantedBy=multi-user.target\n```\n\n### Fail2Ban Setup\n\nTo add security against brute-force attacks in your WebDAV server, you can configure Fail2Ban to ban IP addresses after a set number of failed login attempts.\n\n#### Filter Configuration\n\nCreate a new filter rule under `filter.d/webdav.conf`:\n\n```ini\n[INCLUDES]\nbefore = common.conf\n\n[Definition]\n# Failregex to match \"invalid password\" and extract remote_address only\nfailregex = ^.*invalid password\\s*\\{.*\"remote_address\":\\s*\"\u003cHOST\u003e:\\d+\"\\s*\\}\n            ^.*invalid username\\s*\\{.*\"remote_address\":\\s*\"\u003cHOST\u003e:\\d+\"\\s*\\}\n\nignoreregex =\n```\n\nThis configuration will capture invalid login attempts and extract the IP address to ban.\n\n#### Jail Configuration\n\nIn `jail.d/webdav.conf`, define the jail that monitors your WebDAV log for failed login attempts:\n\n```ini\n[webdav]\n\nenabled = true\nport = [your_port]\nfilter = webdav\nlogpath = [your_log_path]\nbanaction = iptables-allports\nignoreself = false\n```\n\n- Replace `[your_port]` with the port your WebDAV server is running on.\n- Replace `[your_log_path]` with the path to your WebDAV log file.\n\nIf you use it with Docker and `--log-driver journald`, replace `logpath` with `journalmatch = CONTAINER_NAME=[your_container_name]`\n\n#### Final Steps\n\n1. Restart Fail2Ban to apply these configurations:\n\n   ```bash\n   sudo systemctl restart fail2ban\n   ```\n\n2. Verify that Fail2Ban is running and monitoring your WebDAV logs:\n\n   ```bash\n   sudo fail2ban-client status webdav\n   ```\n\nWith this setup, Fail2Ban will automatically block IP addresses that exceed the allowed number of failed login attempts.\n\n## Contributing\n\nFeel free to open an issue or a pull request.\n\n## License\n\n[MIT License](LICENSE) © [Henrique Dias](https://hacdias.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhacdias%2Fwebdav","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhacdias%2Fwebdav","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhacdias%2Fwebdav/lists"}