Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
It provides protection against malicious grapqhl requests (resource exhaustion). Despite its name it can be used with graphql (pure), graphene, strawberry. It is implemented via a custom ValidationRule, supports error reporting and early bail out strategies as well as limits for single fields
pip install graphene-protector
This adds to django the following setting:
Integrate with:
graphene:
# schema.py
# replace normal Schema import with:
from graphene_protector.django.graphene import Schema
schema = Schema(query=Query, mutation=Mutation)
and add in django settings to GRAPHENE
GRAPHENE = {
...
"SCHEMA": "path.to.schema",
}
or strawberry:
# schema.py
# replace normal Schema import with:
from graphene_protector.django.strawberry import Schema
schema = Schema(query=Query, mutation=Mutation)
manual way (note: import from django for including defaults from settings)
from graphene_protector.django.graphene import Schema
# or
# from graphene_protector.django.strawberry import Schema
schema = Schema(query=Query)
result = schema.execute(query_string)
manual way with custom default Limits
from graphene_protector import Limits
from graphene_protector.django.graphene import Schema
# or
# from graphene_protector.django.strawberry import Schema
schema = graphene.Schema(query=Query, limits=Limits(complexity=None))
result = schema.execute(
query_string
)
limits keyword with Limits object is supported.
from graphene_protector import Limits
from graphene_protector.graphene import Schema
# or
# from graphene_protector.strawberry import Schema
schema = Schema(query=Query, limits=Limits(depth=20, selections=None, complexity=100))
result = schema.execute(query_string)
from graphene_protector import LimitsValidationRule
from graphql.type.schema import Schema
schema = Schema(
query=Query,
)
query_ast = parse("{ hello }")
self.assertFalse(validate(schema, query_ast, [LimitsValidationRule]))
or with custom defaults
from graphene_protector import Limits, LimitsValidationRule
from graphql.type.schema import Schema
class CustomLimitsValidationRule(LimitsValidationRule):
default_limits = Limits(depth=20, selections=None, complexity=100)
schema = Schema(
query=Query,
)
query_ast = parse("{ hello }")
self.assertFalse(validate(schema, query_ast, [LimitsValidationRule]))
strawberry extension variant
from graphene_protector import Limits
from graphene_protector.strawberry import CustomGrapheneProtector
from strawberry import Schema
schema = Schema(query=Query, extensions=[CustomGrapheneProtector(Limits(depth=20, selections=None, complexity=100))])
result = schema.execute(query_string)
or with custom defaults via Mixin
from graphene_protector import Limits, SchemaMixin, LimitsValidationRule
from graphql.type.schema import Schema
class CustomSchema(SchemaMixin, Schema):
protector_default_limits = Limits(depth=20, selections=None, complexity=100)
schema = CustomSchema(
query=Query,
)
query_ast = parse("{ hello }")
self.assertFalse(validate(schema, query_ast, [LimitsValidationRule]))
strawberry variant with mixin (uses protector_per_operation_validation in contrast to the official graphene-protector strawberry schema)
from graphene_protector import Limits, SchemaMixin, default_path_ignore_pattern
from strawberry import Schema
class CustomSchema(SchemaMixin, Schema):
protector_default_limits = Limits(depth=20, selections=None, complexity=100)
protector_path_ignore_pattern = default_path_ignore_pattern
schema = CustomSchema(query=Query)
result = schema.execute(query_string)
Note: for the mixin method all variables are prefixed in schema with protector_
. Internally the get_protector_
methods are used and mapped on the validation context. The extracted functions can be customized via the protector_decorate_graphql_schema
method.
A Limits object has following attributes:
they overwrite django settings if specified.
Sometimes single fields should have different limits:
from graphene_protector import Limits
person1 = Limits(depth=10)(graphene.Field(Person))
Limits are passthroughs for missing parameters
There is also a novel technique named gas: you can assign a field a static value or dynamically calculate it for the field
The decorator is called gas_usage
from graphene_protector import gas_usage
person1 = gas_usage(10)(graphene.Field(Person))
# dynamic way:
person2 = gas_usage(lambda **kwargs: 10)(graphene.Field(Person))
see tests for more examples
to disable checks for one operation use check_limits=False (works for: execute, execute_async (if available), subscribe (if available)):
from graphene_protector import Limits
from graphene_protector.graphene import Schema
schema = Schema(query=Query, limits=Limits(depth=20, selections=None, complexity=100))
result = schema.execute(query_string, check_limits=False)
Usefull for debugging or working around errors
This is a feature for ignoring some path parts in calculation but still traversing them.
It is useful for e.g. relay which inflates the depth significant and can cause problems with complexity
Currently it is set to edges/node$
which reduces the depth of connections by one.
If you want to ignore all children on a path then remove $ but be warned: it can be a long path and it is still traversed.
The path the regex matches agains is composed like this: fieldname/subfields/...
.
Other examples are:
node$|id$
for ignoring id fields in selection/complexity count and reducing the depth by 1 when seeing a node fieldpage_info|pageInfo
for ignoring page info in calculation (Note: you need only one, in case auto_snakecase=True only pageInfo
)Note: items prefixed with __
(internal names) are always ignored and not traversed.
Note: if auto_snakecase is True, the path components are by default camel cased (overwritable via explicit camelcase_path
)
Note: gas is excluded from path ignoring
Gas should be a positive integer. Negative integers are possible but the evaulation is stopped whenever the counter is above the limit so this is not reliable
The gas usage is annotated with the gas_usage decorator. A function can be passed which receives the following keyword arguments:
On the validation rule the validation is stopped by default when an error is found
This default can be overwritten and it is modified for the django code pathes.
Whenever DEBUG is active a full validation happens, otherwise the shortcut is used.
See the source-code how to change your schema to have a custom hook for deciding if a full validation is done.
In addition the path_ignore_pattern
and limits
attributes can be also changed dynamically.
The validation rule uses some protector_
prefixed methods from the schema.
With this you can customize the default behaviour.
It is used by the django mixin to read the settings (see django) and to react on DEBUG with full_validation
I am open for new ideas. If you want some new or better algorithms integrated just make a PR
Path ignoring is ignored for the gas calculation (gas is always explicit). Therefor there is no way to stop when an open path was found (all children are ignored).
This project uses a "stack free" recursive approach. Instead of calling recursively, generators are used to remember the position and to continue.
Note: graphql itself will fail because they are not using a stack free approach. For graphql there was a limit around 200 depth. The graphql tree cannot be constructed so there is no way to evaluate this.
FAQs
Protects graphene, graphql or strawberry against malicious queries
We found that graphene-protector demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.