From 6913f9d79c64bca5114401c650cf88f896ed03d5 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Wed, 28 Jan 2026 13:45:12 -0800
Subject: [PATCH 1/9] Fix: fix user checks in management scripts (#11928)
---
docker/management_script.sh | 7 ++++++-
docker/rootfs/usr/local/bin/convert_mariadb_uuid | 9 +++++++--
docker/rootfs/usr/local/bin/createsuperuser | 9 +++++++--
docker/rootfs/usr/local/bin/decrypt_documents | 9 +++++++--
docker/rootfs/usr/local/bin/document_archiver | 9 +++++++--
.../rootfs/usr/local/bin/document_create_classifier | 11 ++++++++++-
docker/rootfs/usr/local/bin/document_exporter | 9 +++++++--
docker/rootfs/usr/local/bin/document_fuzzy_match | 9 +++++++--
docker/rootfs/usr/local/bin/document_importer | 9 +++++++--
docker/rootfs/usr/local/bin/document_index | 9 +++++++--
docker/rootfs/usr/local/bin/document_renamer | 9 +++++++--
docker/rootfs/usr/local/bin/document_retagger | 9 +++++++--
docker/rootfs/usr/local/bin/document_sanity_checker | 9 +++++++--
docker/rootfs/usr/local/bin/document_thumbnails | 9 +++++++--
docker/rootfs/usr/local/bin/mail_fetcher | 9 +++++++--
docker/rootfs/usr/local/bin/manage_superuser | 9 +++++++--
docker/rootfs/usr/local/bin/prune_audit_logs | 9 +++++++--
17 files changed, 121 insertions(+), 32 deletions(-)
diff --git a/docker/management_script.sh b/docker/management_script.sh
index 91a6336d0..6d5e84549 100755
--- a/docker/management_script.sh
+++ b/docker/management_script.sh
@@ -7,6 +7,11 @@ cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
python3 manage.py management_command "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py management_command "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py management_command "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/convert_mariadb_uuid b/docker/rootfs/usr/local/bin/convert_mariadb_uuid
index 019c558f1..7adb0a1af 100755
--- a/docker/rootfs/usr/local/bin/convert_mariadb_uuid
+++ b/docker/rootfs/usr/local/bin/convert_mariadb_uuid
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py convert_mariadb_uuid "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py convert_mariadb_uuid "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py convert_mariadb_uuid "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py convert_mariadb_uuid "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/createsuperuser b/docker/rootfs/usr/local/bin/createsuperuser
index 2b56869f6..b91cee3c5 100755
--- a/docker/rootfs/usr/local/bin/createsuperuser
+++ b/docker/rootfs/usr/local/bin/createsuperuser
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py createsuperuser "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py createsuperuser "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py createsuperuser "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py createsuperuser "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/decrypt_documents b/docker/rootfs/usr/local/bin/decrypt_documents
index 27f0a21fe..65d035b70 100755
--- a/docker/rootfs/usr/local/bin/decrypt_documents
+++ b/docker/rootfs/usr/local/bin/decrypt_documents
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py decrypt_documents "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py decrypt_documents "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py decrypt_documents "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py decrypt_documents "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_archiver b/docker/rootfs/usr/local/bin/document_archiver
index 8d7771d26..4200aa7aa 100755
--- a/docker/rootfs/usr/local/bin/document_archiver
+++ b/docker/rootfs/usr/local/bin/document_archiver
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_archiver "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_archiver "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_archiver "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_archiver "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_create_classifier b/docker/rootfs/usr/local/bin/document_create_classifier
index 23acc6741..518551a4b 100755
--- a/docker/rootfs/usr/local/bin/document_create_classifier
+++ b/docker/rootfs/usr/local/bin/document_create_classifier
@@ -6,7 +6,16 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_create_classifier "$@"
+ python3 manage.py document_create_classifier "$@"
+elif [[ $(id -u) == 0 ]]; then
+ s6-setuidgid paperless python3 manage.py document_create_classifier "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_create_classifier "$@"
+else
+ echo "Unknown user."
+ exit 1
+fi
+er "$@"
elif [[ $(id -un) == "paperless" ]]; then
s6-setuidgid paperless python3 manage.py document_create_classifier "$@"
fi
diff --git a/docker/rootfs/usr/local/bin/document_exporter b/docker/rootfs/usr/local/bin/document_exporter
index d55f01d48..a82d70a16 100755
--- a/docker/rootfs/usr/local/bin/document_exporter
+++ b/docker/rootfs/usr/local/bin/document_exporter
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_exporter "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_exporter "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_exporter "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_exporter "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_fuzzy_match b/docker/rootfs/usr/local/bin/document_fuzzy_match
index c6e4edadc..b97c2a9ba 100755
--- a/docker/rootfs/usr/local/bin/document_fuzzy_match
+++ b/docker/rootfs/usr/local/bin/document_fuzzy_match
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_fuzzy_match "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_fuzzy_match "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_fuzzy_match "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_fuzzy_match "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_importer b/docker/rootfs/usr/local/bin/document_importer
index 07c92bb04..dbfb40a57 100755
--- a/docker/rootfs/usr/local/bin/document_importer
+++ b/docker/rootfs/usr/local/bin/document_importer
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_importer "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_importer "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_importer "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_importer "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_index b/docker/rootfs/usr/local/bin/document_index
index 47c893c10..b05f765da 100755
--- a/docker/rootfs/usr/local/bin/document_index
+++ b/docker/rootfs/usr/local/bin/document_index
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_index "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_index "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_index "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_index "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_renamer b/docker/rootfs/usr/local/bin/document_renamer
index 3406182ee..720edc0d8 100755
--- a/docker/rootfs/usr/local/bin/document_renamer
+++ b/docker/rootfs/usr/local/bin/document_renamer
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_renamer "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_renamer "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_renamer "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_renamer "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_retagger b/docker/rootfs/usr/local/bin/document_retagger
index b0d1047ff..6cbe03c19 100755
--- a/docker/rootfs/usr/local/bin/document_retagger
+++ b/docker/rootfs/usr/local/bin/document_retagger
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_retagger "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_retagger "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_retagger "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_retagger "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_sanity_checker b/docker/rootfs/usr/local/bin/document_sanity_checker
index d792124fc..8fff13a52 100755
--- a/docker/rootfs/usr/local/bin/document_sanity_checker
+++ b/docker/rootfs/usr/local/bin/document_sanity_checker
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_sanity_checker "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_sanity_checker "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_sanity_checker "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_sanity_checker "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/document_thumbnails b/docker/rootfs/usr/local/bin/document_thumbnails
index 71d80e00d..3c0f0de4c 100755
--- a/docker/rootfs/usr/local/bin/document_thumbnails
+++ b/docker/rootfs/usr/local/bin/document_thumbnails
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py document_thumbnails "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_thumbnails "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py document_thumbnails "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py document_thumbnails "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/mail_fetcher b/docker/rootfs/usr/local/bin/mail_fetcher
index 654c07389..762b850b9 100755
--- a/docker/rootfs/usr/local/bin/mail_fetcher
+++ b/docker/rootfs/usr/local/bin/mail_fetcher
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py mail_fetcher "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py mail_fetcher "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py mail_fetcher "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py mail_fetcher "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/manage_superuser b/docker/rootfs/usr/local/bin/manage_superuser
index a6e41168c..8f550cd1a 100755
--- a/docker/rootfs/usr/local/bin/manage_superuser
+++ b/docker/rootfs/usr/local/bin/manage_superuser
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py manage_superuser "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py manage_superuser "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py manage_superuser "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py manage_superuser "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
diff --git a/docker/rootfs/usr/local/bin/prune_audit_logs b/docker/rootfs/usr/local/bin/prune_audit_logs
index 04446df17..8a3ab3299 100755
--- a/docker/rootfs/usr/local/bin/prune_audit_logs
+++ b/docker/rootfs/usr/local/bin/prune_audit_logs
@@ -6,7 +6,12 @@ set -e
cd "${PAPERLESS_SRC_DIR}"
if [[ -n "${USER_IS_NON_ROOT}" ]]; then
- python3 manage.py prune_audit_logs "$@"
-elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py prune_audit_logs "$@"
+elif [[ $(id -u) == 0 ]]; then
s6-setuidgid paperless python3 manage.py prune_audit_logs "$@"
+elif [[ $(id -un) == "paperless" ]]; then
+ python3 manage.py prune_audit_logs "$@"
+else
+ echo "Unknown user."
+ exit 1
fi
From e4b861d76f2da302136cd2b10c26fcf2f213b974 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Thu, 29 Jan 2026 13:29:30 -0800
Subject: [PATCH 2/9] Fix: prevent note deletion outside doc
---
src/documents/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/documents/views.py b/src/documents/views.py
index a91ad8594..f6bec1f0d 100644
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -1099,7 +1099,7 @@ class DocumentViewSet(
):
return HttpResponseForbidden("Insufficient permissions to delete notes")
- note = Note.objects.get(id=int(request.GET.get("id")))
+ note = Note.objects.get(id=int(request.GET.get("id")), document=doc)
if settings.AUDIT_LOG_ENABLED:
LogEntry.objects.log_create(
instance=doc,
From 836c81e0371ca380b6ae675dc3972f357edf1eb0 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Fri, 30 Jan 2026 11:49:22 -0800
Subject: [PATCH 3/9] Chore: add note about ordering
---
.../app/components/manage/saved-views/saved-views.component.html | 1 +
1 file changed, 1 insertion(+)
diff --git a/src-ui/src/app/components/manage/saved-views/saved-views.component.html b/src-ui/src/app/components/manage/saved-views/saved-views.component.html
index 10487fec8..2f5ef3338 100644
--- a/src-ui/src/app/components/manage/saved-views/saved-views.component.html
+++ b/src-ui/src/app/components/manage/saved-views/saved-views.component.html
@@ -51,6 +51,7 @@
@if (displayFields) {
}
+ Note: ordering is not preserved
From c8c4c7c749f5531259c5fd4d8e0b7cae28216920 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Fri, 30 Jan 2026 12:14:18 -0800
Subject: [PATCH 4/9] Security: enforce permissions for post_document
---
src/documents/tests/test_api_documents.py | 11 +++++++++++
src/documents/views.py | 2 ++
2 files changed, 13 insertions(+)
diff --git a/src/documents/tests/test_api_documents.py b/src/documents/tests/test_api_documents.py
index f40ef157f..700f56568 100644
--- a/src/documents/tests/test_api_documents.py
+++ b/src/documents/tests/test_api_documents.py
@@ -1216,6 +1216,17 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
+ def test_upload_insufficient_permissions(self):
+ self.client.force_authenticate(user=User.objects.create_user("testuser2"))
+
+ with (Path(__file__).parent / "samples" / "simple.pdf").open("rb") as f:
+ response = self.client.post(
+ "/api/documents/post_document/",
+ {"document": f},
+ )
+
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
def test_upload_empty_metadata(self):
self.consume_file_mock.return_value = celery.result.AsyncResult(
id=str(uuid.uuid4()),
diff --git a/src/documents/views.py b/src/documents/views.py
index f6bec1f0d..5a0f83699 100644
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -1703,6 +1703,8 @@ class PostDocumentView(GenericAPIView):
parser_classes = (parsers.MultiPartParser,)
def post(self, request, *args, **kwargs):
+ if not request.user.has_perm("documents.add_document"):
+ return HttpResponseForbidden("Insufficient permissions")
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
From 5cc3c087d90c669ee1e3c2430fb1b2e67b007185 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Fri, 30 Jan 2026 13:55:55 -0800
Subject: [PATCH 5/9] Security: enforce ownership for permission updates
---
src/documents/serialisers.py | 14 ++++++
src/documents/tests/test_api_permissions.py | 53 +++++++++++++++++++++
2 files changed, 67 insertions(+)
diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py
index e96400eff..75e73d878 100644
--- a/src/documents/serialisers.py
+++ b/src/documents/serialisers.py
@@ -40,6 +40,7 @@ from guardian.utils import get_group_obj_perms_model
from guardian.utils import get_user_obj_perms_model
from rest_framework import fields
from rest_framework import serializers
+from rest_framework.exceptions import PermissionDenied
from rest_framework.fields import SerializerMethodField
from rest_framework.filters import OrderingFilter
@@ -436,6 +437,19 @@ class OwnedObjectSerializer(
return instance
def update(self, instance, validated_data):
+ user = getattr(self, "user", None)
+ is_superuser = user.is_superuser if user is not None else False
+ is_owner = instance.owner == user if user is not None else False
+ is_unowned = instance.owner is None
+
+ if (
+ ("owner" in validated_data and validated_data["owner"] != instance.owner)
+ or "set_permissions" in validated_data
+ ) and not (is_superuser or is_owner or is_unowned):
+ raise PermissionDenied(
+ _("Insufficient permissions."),
+ )
+
if "set_permissions" in validated_data:
self._set_permissions(validated_data["set_permissions"], instance)
self.validate_unique_together(validated_data, instance)
diff --git a/src/documents/tests/test_api_permissions.py b/src/documents/tests/test_api_permissions.py
index bc81dabe9..31b860745 100644
--- a/src/documents/tests/test_api_permissions.py
+++ b/src/documents/tests/test_api_permissions.py
@@ -441,6 +441,59 @@ class TestApiAuth(DirectoriesMixin, APITestCase):
self.assertTrue(checker.has_perm("change_document", doc))
self.assertIn("change_document", get_perms(group1, doc))
+ def test_document_permissions_change_requires_owner(self):
+ owner = User.objects.create_user(username="owner")
+ editor = User.objects.create_user(username="editor")
+ editor.user_permissions.add(
+ *Permission.objects.all(),
+ )
+
+ doc = Document.objects.create(
+ title="Ownered doc",
+ content="sensitive",
+ checksum="abc123",
+ mime_type="application/pdf",
+ owner=owner,
+ )
+
+ assign_perm("view_document", editor, doc)
+ assign_perm("change_document", editor, doc)
+
+ self.client.force_authenticate(editor)
+ response = self.client.patch(
+ f"/api/documents/{doc.pk}/",
+ json.dumps(
+ {
+ "set_permissions": {
+ "view": {
+ "users": [editor.id],
+ "groups": [],
+ },
+ "change": {
+ "users": None,
+ "groups": None,
+ },
+ },
+ },
+ ),
+ content_type="application/json",
+ )
+
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
+ self.client.force_authenticate(editor)
+ response = self.client.patch(
+ f"/api/documents/{doc.pk}/",
+ json.dumps(
+ {
+ "owner": editor.id,
+ },
+ ),
+ content_type="application/json",
+ )
+
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+
def test_dynamic_permissions_fields(self):
user1 = User.objects.create_user(username="user1")
user1.user_permissions.add(*Permission.objects.filter(codename="view_document"))
From 3e41d99a82555694a6a0d32c198391dba2315fb8 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Fri, 30 Jan 2026 17:59:55 -0800
Subject: [PATCH 6/9] Bump version to 2.20.6
---
pyproject.toml | 2 +-
src-ui/package.json | 2 +-
src-ui/src/environments/environment.prod.ts | 2 +-
src/paperless/version.py | 2 +-
uv.lock | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index ee1748cc3..e1693ead0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "paperless-ngx"
-version = "2.20.5"
+version = "2.20.6"
description = "A community-supported supercharged document management system: scan, index and archive all your physical documents"
readme = "README.md"
requires-python = ">=3.10"
diff --git a/src-ui/package.json b/src-ui/package.json
index 6d9046f65..dbd26cb8c 100644
--- a/src-ui/package.json
+++ b/src-ui/package.json
@@ -1,6 +1,6 @@
{
"name": "paperless-ngx-ui",
- "version": "2.20.5",
+ "version": "2.20.6",
"scripts": {
"preinstall": "npx only-allow pnpm",
"ng": "ng",
diff --git a/src-ui/src/environments/environment.prod.ts b/src-ui/src/environments/environment.prod.ts
index 9ebf29d16..3ce1d16cc 100644
--- a/src-ui/src/environments/environment.prod.ts
+++ b/src-ui/src/environments/environment.prod.ts
@@ -6,7 +6,7 @@ export const environment = {
apiVersion: '9', // match src/paperless/settings.py
appTitle: 'Paperless-ngx',
tag: 'prod',
- version: '2.20.5',
+ version: '2.20.6',
webSocketHost: window.location.host,
webSocketProtocol: window.location.protocol == 'https:' ? 'wss:' : 'ws:',
webSocketBaseUrl: base_url.pathname + 'ws/',
diff --git a/src/paperless/version.py b/src/paperless/version.py
index aeeee68e0..ec6eaed08 100644
--- a/src/paperless/version.py
+++ b/src/paperless/version.py
@@ -1,6 +1,6 @@
from typing import Final
-__version__: Final[tuple[int, int, int]] = (2, 20, 5)
+__version__: Final[tuple[int, int, int]] = (2, 20, 6)
# Version string like X.Y.Z
__full_version_str__: Final[str] = ".".join(map(str, __version__))
# Version string like X.Y
diff --git a/uv.lock b/uv.lock
index ac7763525..b09a025e0 100644
--- a/uv.lock
+++ b/uv.lock
@@ -2115,7 +2115,7 @@ wheels = [
[[package]]
name = "paperless-ngx"
-version = "2.20.5"
+version = "2.20.6"
source = { virtual = "." }
dependencies = [
{ name = "babel", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" },
From 4363567fa79cd7bbc40adcca58ee458dca27d9ef Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Fri, 30 Jan 2026 19:49:43 -0800
Subject: [PATCH 7/9] Remove commitish
---
.github/release-drafter.yml | 1 -
.github/workflows/ci.yml | 1 -
2 files changed, 2 deletions(-)
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
index 2b8169f24..89c8a96ea 100644
--- a/.github/release-drafter.yml
+++ b/.github/release-drafter.yml
@@ -44,7 +44,6 @@ include-labels:
- 'notable'
exclude-labels:
- 'skip-changelog'
-filter-by-commitish: true
category-template: '### $TITLE'
change-template: '- $TITLE @$AUTHOR ([#$NUMBER]($URL))'
change-title-escapes: '\<*_@'
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c1169f664..f8eb6c832 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -617,7 +617,6 @@ jobs:
version: ${{ steps.get_version.outputs.version }}
prerelease: ${{ steps.get_version.outputs.prerelease }}
publish: true # ensures release is not marked as draft
- commitish: ${{ github.sha }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload release archive
From c5bb5b237baffa6cf2d9077265ba0d2a413a4bcc Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Fri, 30 Jan 2026 22:19:48 -0800
Subject: [PATCH 8/9] Fix commitish config
---
.github/release-drafter.yml | 1 +
.github/workflows/ci.yml | 1 +
2 files changed, 2 insertions(+)
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
index 89c8a96ea..2b8169f24 100644
--- a/.github/release-drafter.yml
+++ b/.github/release-drafter.yml
@@ -44,6 +44,7 @@ include-labels:
- 'notable'
exclude-labels:
- 'skip-changelog'
+filter-by-commitish: true
category-template: '### $TITLE'
change-template: '- $TITLE @$AUTHOR ([#$NUMBER]($URL))'
change-title-escapes: '\<*_@'
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f8eb6c832..98f4272e3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -617,6 +617,7 @@ jobs:
version: ${{ steps.get_version.outputs.version }}
prerelease: ${{ steps.get_version.outputs.prerelease }}
publish: true # ensures release is not marked as draft
+ commitish: main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload release archive
From d27a5f688a2bd283d2b535fc04951f228d5c37c4 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 30 Jan 2026 23:34:22 -0800
Subject: [PATCH 9/9] Changelog v2.20.6 - GHA (#11952)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
---
docs/changelog.md | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/docs/changelog.md b/docs/changelog.md
index f222a7305..d6a87e299 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -1,5 +1,24 @@
# Changelog
+## paperless-ngx 2.20.6
+
+### Bug Fixes
+
+- Fix: extract all ids for nested tags [@shamoon](https://github.com/shamoon) ([#11888](https://github.com/paperless-ngx/paperless-ngx/pull/11888))
+- Fixhancement: change date calculation for 'this year' to include future documents [@shamoon](https://github.com/shamoon) ([#11884](https://github.com/paperless-ngx/paperless-ngx/pull/11884))
+- Fix: Running management scripts under rootless could fail [@stumpylog](https://github.com/stumpylog) ([#11870](https://github.com/paperless-ngx/paperless-ngx/pull/11870))
+- Fix: use correct field id for overrides [@shamoon](https://github.com/shamoon) ([#11869](https://github.com/paperless-ngx/paperless-ngx/pull/11869))
+
+### All App Changes
+
+
+3 changes
+
+- Fix: extract all ids for nested tags [@shamoon](https://github.com/shamoon) ([#11888](https://github.com/paperless-ngx/paperless-ngx/pull/11888))
+- Fixhancement: change date calculation for 'this year' to include future documents [@shamoon](https://github.com/shamoon) ([#11884](https://github.com/paperless-ngx/paperless-ngx/pull/11884))
+- Fix: use correct field id for overrides [@shamoon](https://github.com/shamoon) ([#11869](https://github.com/paperless-ngx/paperless-ngx/pull/11869))
+
+
## paperless-ngx 2.20.5
### Bug Fixes