Security¶
Threat model¶
?fields= is client-controlled input parsed on every request. The
package's security posture follows from that:
- Parsing is bounded.
MAX_FIELDS_LENGTH(default 2000 characters) rejects the raw string before any parsing work happens.MAX_DEPTH(default 6) bounds recursion depth in the parser, the serializer's tree resolution, and the optimizer's nested-Prefetchconstruction. Both are configurable — see Settings. - The parser never backtracks or loops on malformed input. It's a
single left-to-right pass; malformed input (unbalanced parentheses,
invalid characters, ambiguous include/exclude mixing) fails fast with
InvalidFieldsParameterErrorrather than looping or degrading to exponential behavior.tests/test_parser.py::TestPropertyBasedincludes ahypothesis-driven fuzz test and explicit adversarial-input regression tests for this. - Unknown field names never error by default. With
STRICT = False(the default), requesting a field that doesn't exist — including one deliberately hidden from a class of users by a permission check — is a silent no-op. This avoids using response codes as an oracle for probing which field names exist on a serializer. EnableSTRICT = Truedeliberately, and only for APIs where every possible caller is trusted with that information. - Write operations are protected from
fields=by default. DRF serializers share one field set between input validation and output rendering. Without a guard,?fields=titleon aPOSTwould silently remove every other writable field from validation, potentially bypassing required-field checks.SAFE_METHODS_ONLY(defaultTrue) prevents this by ignoringfields=entirely on unsafe methods (POST,PUT,PATCH,DELETE). Do not disable this unless you've specifically verified your write endpoints don't rely on required-field validation that a client could bypass by omission. - Query optimization never changes query results, only query shape.
optimize_queryset()addsselect_related/prefetch_related/only()— none of these change which rows are returned or what values they contain, only how the same result set is fetched. A permission check or queryset filter applied elsewhere inget_queryset()is unaffected. - No new attack surface for field-level authorization. This package
does not implement field-level permissions — if certain fields must be
hidden from certain users regardless of what they request, that's still
your application's responsibility (e.g. via a
get_fields()override after this mixin's, as shown in Common Patterns).?fields=can only narrow what a client sees from the fields the serializer would have exposed to them anyway; it never widens access.
Reporting a vulnerability¶
See SECURITY.md in the repository root.
Recommended production configuration¶
PARTIAL_RESPONSE_FIELDS = {
"MAX_DEPTH": 4, # tighter than the default 6, if your models are shallow
"MAX_FIELDS_LENGTH": 500, # tighter than the default 2000, for public APIs
"STRICT": False, # keep permissive unless every client is trusted
"SAFE_METHODS_ONLY": True, # keep enabled - do not disable without review
}
Dependency security¶
This package has exactly two runtime dependencies: Django and Django REST
Framework. Keep both current — see docs/deployment.md
for supported version ranges. Run pip audit regularly; there is no
package-specific secret material, credentials, or network I/O anywhere in
this codebase to audit beyond the standard Django/DRF security surface.