Close review gaps in graphene→strawberry migration (FK-traversal IDOR, rate bucket, served-validation test)#2152
Conversation
The graphene->strawberry migration (#2139) dropped per-row visibility filtering on singular to-one FK object fields. graphene-django auto- converted such FKs (when the target type overrode get_queryset) into a permission-filtered resolver, so an invisible target resolved to null; the strawberry port declared them as plain getattr fields, leaking the target row's fields across a permission boundary (e.g. a structural AnnotationType.corpus or a cross-corpus CorpusReferenceType.targetDocument pointing at a private corpus/document). - Add config/graphql/core/relay.py::resolve_visible_fk, applying the target type's registered get_node/get_queryset visibility hook; route the affected nullable FK fields through it across annotation/agent/ conversation/corpus/document/extract/research/social/user types. - Close the non-null DocumentPathType.document leak at the list level: _get_queryset_DocumentPathType now enforces MIN(document, corpus), the same semantic CorpusType.documents uses (issue #1682). - Wrap the get_node hook path in get_node_from_global_id with the same malformed-pk guard the default path already had. - drf_mutation/drf_deletion pass group="mutate" so DRF-routed mutations share the write rate-limit bucket instead of a separate "<lambda>" one. - Pin the served-schema security rules: a new test drives a too-deep query and an introspection query through schema.execute_sync (the real AddValidationRules extension path), not the decorative validation_rules list. - Declare graphql-relay explicitly in requirements; remove the dead ALLOW_GRAPHQL_DEBUG setting; correct the stale MessageType relay docstring and migration doc. Verified: test_fk_visibility_traversal (new, 10 tests), schema parity (zero shape change), singular-node IDOR, served-validation, and the versioning/DocumentPath/structural/mentions regression suites all green.
ReviewThis PR does solid, well-tested follow-up work on the graphene→strawberry migration: it identifies a real and serious bug class (singular FK fields skipping the target type's permission hook), adds Major: the FK-traversal fix is incomplete — several fields with the same bug remain unfilteredThe PR is careful to call out which excluded fields are deliberate no-ops ( But there's a large second group of plain FK fields that point at types with real, documented, per-object permission checks (
Lower-confidence/narrower cases also worth a look: None of this is a knock on the approach — Minor
Nice-to-haves confirmed correct
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
535c42b
into
claude/graphene-strawberry-migration-eyvj57
Stacks on top of the migration branch (
claude/graphene-strawberry-migration-eyvj57, PR #2139) and closes the gaps found reviewing that migration. Base is the migration branch so this diff is only the fixes.1. High — singular FK-object traversal lost its visibility filter (systematic)
graphene-django auto-converted a to-one FK whose target type overrode
get_querysetinto a permission-filtered resolver (convert_field_to_djangomodel→target.get_node), so an invisible FK target resolved tonull. The strawberry port declared these as plain getattr fields, leaking the target row's fields across a permission boundary. Node/connection/list paths already funnel throughapply_type_get_queryset; singular FK fields never did.config/graphql/core/relay.py::resolve_visible_fk, which applies the target type's registeredget_node/get_querysethook (returnsNonefor an invisible/missing/malformed target).annotation/agent/conversation/corpus/document/extract/research/social/usertypes — including the confirmed-exploitable edges:AnnotationType.corpus,RelationshipType.corpus/document,CorpusReferenceType.targetDocument/targetCorpus/targetAnnotation,ConversationType.chatWithCorpus/chatWithDocument,MessageType.sourceDocument,DocumentType.parent/sourceDocument,CorpusType.parent/memoryDocument.DocumentPathType.documentcan't resolve tonull, so its leak (DocumentPath membership is corpus-as-gate — a public corpus lists paths for its private documents too) is closed at the list level:_get_queryset_DocumentPathTypenow enforcesMIN(document, corpus), matchingCorpusType.documents(issue Reconcile CorpusObjsService corpus-as-gate vs GraphQL MIN-permission semantic #1682).NoteType.*,DocumentRelationshipType.*, corpus-gated*.corpus, same-parentparent) are intentionally left as fast plain fields — filtering them is a no-op that only adds per-row queries.2. Major — served-schema validation wiring was untested
Depth-limiting and prod introspection-blocking are enforced by the
AddValidationRulesextension inschema.py, but the existing tests only call graphql-core'svalidate(..., validation_rules)against the exported (decorative) list — they'd stay green if the extension were dropped. AddedTestServedSchemaExecutesValidationRules, which drives a too-deep query and an introspection query throughschema.execute_sync(the real extension path).3. Minor
drf_mutation/drf_deletionnow passgroup="mutate"— the ratelimit decorator was wrapping alambda, so the ~9 DRF-routed mutations bucketed under"<lambda>"instead of the shared"mutate"write counter (roughly doubling combined write budget). No guard was dropped.get_node_from_global_idwraps theget_nodehook path in the same malformed-pk guard as the default path, soint(pk)hooks return the unified not-found instead of a rawValueError.graphql-relay==3.2.0explicitly (imported directly by ~20 modules, previously only transitive viadjango-graphql-jwt).ALLOW_GRAPHQL_DEBUGsetting; corrected the staleMessageTyperelay docstring + migration doc, which described singular node resolution as unfiltered despite the shipped IDOR hook.Verification
Run in-container against the strawberry schema:
test_fk_visibility_traversal(new, 10 tests) — bothresolve_visible_fkbranches, the DocumentPath MIN filter, and an end-to-endCorpusType.parent→nullfor a non-owner.test_schema_parity— green (zero schema-shape change from the ~32 field conversions).test_singular_node_idor, served-validation classes — green.test_document_versioning_graphql,test_corpus_cards_structural_document_resolution,test_versioning_paths_audit,test_mentions— 95 tests green.black,isort,flake8clean; no inline Tier-0 introduced (E001 unaffected).Two changelog fragments added (
2139-fk-traversal-idor.security.md,2139-review-followups.fixed.md).Generated by Claude Code