feat(webdav): add tag-based file exclusion (#710)

Hide sensitive files/folders from the WebDAV MCP tool surface by
tagging them with a configured Nextcloud system tag. Defence-in-depth
control for users who connect LLMs to accounts holding contracts,
medical records, credentials, etc.

A new EXCLUDED_TAGS env var (comma-separated tag names, empty by
default) gates an exclusion layer that runs at the start of every
WebDAV tool call: tag names are resolved to tag IDs, those IDs are
expanded to the set of tagged paths, then listings/searches are
filtered and read/write/delete/move/copy operations on excluded paths
raise ToolError. Tagged folders exclude their descendants via prefix
match. Empty EXCLUDED_TAGS disables the feature entirely.

The threat model is preventing accidental data exfiltration via the
LLM tool surface — not hiding files from a determined operator. The
docs explicitly recommend creating exclusion tags with
user_assignable=false so the credentials the MCP server uses cannot
remove the tag.

Implementation:

- config.py: add `excluded_tags` to _DEFAULTS, Settings, and the
  _field_map alongside other comma-separated env vars.
- client/webdav.py: get_files_by_tag now requests <d:resourcetype/>
  and surfaces is_directory so tagged directories can recursively
  exclude descendants.
- server/tag_exclusion.py (new): get_excluded_tag_names,
  get_excluded_file_paths, is_path_excluded.
- server/webdav.py: exclusion guards in all 11 WebDAV tools;
  read/write/create/delete/move/copy raise ToolError, list/search
  tools silently filter excluded entries. Existing f-string log
  calls converted to lazy %-style.
- tests: 17 new unit tests covering path-matching edge cases
  (shared-prefix non-match, descendants of excluded dirs), tag-name
  parsing, and get_excluded_file_paths with mocked WebDAV; 1 new
  client test asserting <d:resourcetype/> -> is_directory parsing.
- docs/configuration.md: new "Tag-Based File Exclusion" section with
  per-tool effect table, security guidance, and per-call cost note.
- README.md: feature mention under Key Features.

Closes #710.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-05-06 12:12:54 +02:00
co-authored by Claude Opus 4.7
parent b23f7d9534
commit 22ed9e99a0
8 changed files with 528 additions and 4 deletions
+81
View File
@@ -576,6 +576,87 @@ docker-compose up
---
## Tag-Based File Exclusion (Optional)
Some files (contracts, medical records, credentials, private notes) should
never be exposed to an LLM, even when the assistant has valid credentials
for the account. The MCP server can hide such files from all WebDAV tools
based on **Nextcloud system tags** (the same collaborative tags users
manage from the Nextcloud UI).
### Setup
Set `EXCLUDED_TAGS` to a comma-separated list of system tag names:
```bash
EXCLUDED_TAGS=confidential,no-ai,private
```
Then create the tags in Nextcloud (one-time, as admin):
```bash
docker compose exec app php occ tag:add 'no-ai' --user-visible=true --user-assignable=false
```
`--user-assignable=false` is **strongly recommended** for the threat model
this feature is designed to address — see *Security considerations* below.
Tag any file or folder with one of these tags from the Nextcloud UI to
hide it from the MCP tools.
Empty (`EXCLUDED_TAGS=""`, the default) disables the feature entirely.
### Behaviour
When `EXCLUDED_TAGS` is set, every WebDAV MCP tool resolves the configured
tag names to file paths and applies the following:
| Tool | Effect on tagged paths |
|------|------------------------|
| `nc_webdav_list_directory` | Excluded files/folders are omitted from listings |
| `nc_webdav_read_file` | Raises `ToolError` (access denied) |
| `nc_webdav_write_file` | Raises `ToolError` (access denied) |
| `nc_webdav_create_directory` | Blocked inside excluded paths |
| `nc_webdav_delete_resource` | Raises `ToolError` (access denied) |
| `nc_webdav_move_resource` | Blocked when source **or** destination is excluded |
| `nc_webdav_copy_resource` | Blocked when source **or** destination is excluded |
| `nc_webdav_search_files` | Excluded files are filtered from results |
| `nc_webdav_find_by_name` | Excluded files are filtered from results |
| `nc_webdav_find_by_type` | Excluded files are filtered from results |
| `nc_webdav_list_favorites` | Excluded files are filtered from results |
Tagging a **folder** hides the folder itself **and** every descendant
recursively, via path-prefix match.
### Security considerations
The threat model is **preventing accidental data exfiltration via the LLM
tool surface**, not hiding files from a determined operator. Specifically:
- Create exclusion tags with `user_assignable=false` so the credentials
the MCP server uses cannot remove the tag from a file (and thereby
bypass the exclusion). With `user_assignable=true`, any user — including
the one whose credentials the MCP server uses — can untag a file.
- Optionally set `user_visible=false` if the exclusion tag itself is
sensitive metadata.
- The exclusion is enforced at the MCP tool layer only. Direct WebDAV /
Nextcloud client access still sees the files; this feature does not
alter Nextcloud's underlying access control.
### Performance note
The excluded path set is resolved per WebDAV tool call (1 PROPFIND for
each tag name + 1 REPORT per tag). For typical setups (a handful of
tagged files under one or two tag names) the overhead is negligible.
Caching may be added in a future release.
### Scope
This feature only covers WebDAV file operations. Notes, Calendar,
Contacts, Deck, etc. are not filtered, because they use ID-based APIs
rather than file paths.
---
## Loading Environment Variables
After creating your `.env` file, load the environment variables: