You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -65,6 +65,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
65
65
66
66
### Security
67
67
68
+
-**`RemoveAnnotationLabelsFromLabelsetMutation` allowed any user to strip labels from public labelsets** (`config/graphql/label_mutations.py`): The resolver used `Q(creator=user) | Q(is_public=True)` with no further check, so `is_public` (a read flag) effectively granted write access to non-owners. Replaced with `user_has_permission_for_obj(user, labelset, PermissionTypes.UPDATE, include_group_permissions=True)`. Denial raises `LabelSet.DoesNotExist` so the response is byte-identical to the not-found case (no IDOR information leak). Coverage: `test_remove_labels_rejects_non_owner_even_when_labelset_is_public`.
69
+
-**`CreateLabelForLabelsetMutation` ignored guardian UPDATE grants** (`config/graphql/label_mutations.py`): The resolver was creator-only, so collaborators with explicit `update_labelset` guardian permission (or a group grant) could not add labels even though the consolidated permissioning guide says edit rights on a `LabelSet` should permit add/remove/edit of its labels. Now uses the same `PermissionTypes.UPDATE` gate as `Remove`, with the matching IDOR-safe deny path. Coverage: `test_non_owner_with_explicit_update_permission_can_create`. Both mutations now have a dedicated `LabelSet.DoesNotExist` handler that logs at WARNING (not ERROR) so auth probes don't pollute logs with stack traces.
68
70
-**Cross-corpus structural-annotation leak in `CoreAnnotationVectorStore`** (`opencontractserver/llms/vector_stores/core_vector_stores.py:296-326,371-413`): The corpus-wide retrieval path (`corpus_id` set, `document_id=None`) returned every structural annotation in the database regardless of corpus. Two collaborating defects caused the leak:
69
71
1.`Q(structural=True)` in the corpus-only branch had **no corpus constraint** — parser-produced structural annotations have `Annotation.document_id = corpus_id = NULL`, so corpus membership is only knowable through `structural_set → Document.structural_annotation_set (reverse FK) → DocumentPath.corpus_id`, a join the previous code did not perform.
70
72
2. The `check_corpus_deletion` block (default `True`) added `Q(document_id__in=active_doc_ids)`, and `__in` lookups never match `NULL`, so structural annotations were silently dropped on the production-default path. Bypassing this filter with `check_corpus_deletion=False` exposed defect #1 directly.
0 commit comments