DoS protection
The squid GraphQL API server accepts the following optional start arguments to fend off heavy queries.
To enable the protection, add the corresponding flags to serve
and serve:prod
command definitions at commands.json
:
...
"serve": {
"description": "Start the GraphQL API server",
"cmd": ["squid-graphql-server", "--max-root-fields", "10", "--max-response-size", "1000"]
},
"serve:prod": {
"description": "Start the GraphQL API server in prod",
"cmd": ["squid-graphql-server", "--max-root-fields", "10", "--max-response-size", "1000", "--dumb-cache", "in-memory"]
},
...
When deploying to SQD Cloud, make sure to use serve:prod
to start the GraphQL api
service in the deployment manifest:
# ...
deploy:
# other services ...
api:
cmd: [ "sqd", "serve:prod" ]
--max-request-size <kb>
The argument limits the size of a request in kilobytes. It is set to 256kb
by default.
--max-root-fields <count>
The maximal allowed number of root-level queries in a single GraphQL request.
--max-response-size <nodes>
This option limits the estimated query response size and makes server return an error if it exceeds the provided value. Note that the estimated size depends only on the decorators in schema.graphql
and the requested fields.
The estimate is the product of the cardinality of the entity list and the response item weight.
The cardinality is estimated as the minimum of
- the
limit
argument of the query (Infinity
if not provided) @cardinality
value defined inschema.graphql
(if the requested entity type is decorated in the schema file,Infinity
otherwise)- the size of the argument list of the
_eq
andid_in
filters in thewhere
clause (if applicable)
In particular, if there are no @cardinality
decorators in schema.graphql
, the client queries must explicitly provide limits or where filters to pass through.
The response item weight is calculated recursively:
byteWeight
for each scalar field or1
if it's not decorated- for non-scalar fields, the estimated weight times the estimated cardinality (if it's a list)
- each non-leaf node in the query AST tree adds a weight of
1
In a nutshell, assuming that the schema file is properly decorated with @cardinality
and @byteWeight
, the estimated response size should roughly be at the same scale as the byte size of the query result.
--subscription-max-response-size <nodes>
Same as --max-response-size
but for live query subscriptions.
Example
Assume the schema is defined as follows, and the server is launched with --max-response-size 1000
.
type Foo @entity {
id: ID!
// a large string
bigField: String! @byteWeight(value: 1000.0)
bar: Bar!
}
type Bar @entity {
id: ID!
// bar.foos typically contain about 100 items
foos: [Foo!]! @derivedFrom(field: "bar") @cardinality(value: 100)
bazs: [Baz!]! @derivedFrom(field: "bar")
}
// there are around 100 entities of type Baz
type Baz @entity @cardinality(value: 100) {
id: ID!
bar: Bar!
}
The following queries will be bounced:
query A {
bars {
id
}
}
query B {
bars(limit: 1001) {
id
}
}
query C {
bars(limit: 100) {
id
foos(limit: 10) {
id
}
}
}
query D {
bars(limit: 10) {
id
foos {
id
}
}
}
query E {
foos(id_eq: "1") {
id
bigField
}
}
- The estimated cardinality of query A is
Infinity
- The estimated cardinality of query B
1001
and so the expected size exceeds the limit - The estimated cardinality of query C is
100
while the item size is13
, so the size is estimated to1300
. - The estimated cardinality of query D is
10
while the item size is103
, so the size is estimated to1030
. - The estimated cardinality of query E is
1
while the item size is1001
(due tobigField
having weight1000
).
At the same time, the following queries will go through:
query A {
bars(limit: 100) {
id
}
}
query B {
bars(limit: 3) {
id
foos {
id
}
bazs {
id
}
}
}
query C {
bars {
id
bar {
foos(where: { id_in ["1", "2" ]}) {
id
}
id
}
}
}