graphw00f: identifying the GraphQL engine behind an endpoint
graphw00f is GraphQL Server Engine Fingerprinting utility for software security professionals looking to learn more about what technology is behind a given GraphQL endpoint.
At a glance
- What is it?
- graphw00f is a Python command line utility that sends a mix of benign and malformed GraphQL queries to an endpoint and works out which server implementation is answering. It is aimed at security professionals and anyone who needs to know what technology sits behind a GraphQL URL before deciding what to do next.
- Who is it for?
- graphw00f suits a security engineer who has a GraphQL URL and wants to know whether it is Apollo, Hasura, WPGraphQL or a custom server, and a platform team auditing a fleet of endpoints. It is not a substitute for a GraphQL-aware scanner: it identifies a product and points at a threat matrix document, it does not test authorizations, and it does not probe for injection.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 160 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A malformed query is the probe, not a banner check
graphw00f does not read a server banner and look it up in a table. The mechanism described in the project documentation is behavioural. It sends a mix of benign and malformed queries to the target, then compares how the server answers to how the known implementations answer. Because each GraphQL server implementation has its own habits around queries, mutations and subscriptions, two servers that both claim to speak GraphQL still return distinguishable error text, status codes and response shapes. That difference is what the tool keys on, and the project frames the activity under CWE-200, the exposure of information to an unauthorized actor. The upstream inspiration is wafw00f, which does the same kind of thing for web application firewalls, so the design will feel familiar to anyone who has used that family of tool. The practical consequence is that graphw00f works against servers that deliberately hide their identity, and it also means the tool inherits the blind spots of any signature approach. A proxy, a CDN or a custom gateway that rewrites error responses can flatten the differences the tool depends on. If the target sits behind a layer that normalises GraphQL errors, expect an inconclusive result rather than a wrong one, and re-run against the origin if you have the authority to do so.
Detect mode hunts for the path when you do not know it
The command line exposes two working modes, and the distinction matters more than it first appears. The -f flag is fingerprint mode, for the case where you already know where GraphQL lives, for example a path of /graphql on a known host. The -d flag is detect mode, for the case where you have only a host and a port and the tool has to work out whether GraphQL is present and where it is mounted. The two can be combined in one invocation, in which case detection runs first and the fingerprinting pass follows on whatever the detection stage settles on. The tool supports both GET based and POST based querying, so an endpoint that only accepts one of the two methods is still reachable. A -w option takes the path to a list of custom GraphQL endpoints, which is how you point the detection stage at a set of paths you already suspect rather than letting it guess. There is no counterpart for a list of full URLs, so if you have many hosts rather than many paths, you are expected to loop over them yourself and aggregate the CSV output.
Thirty seven engines spanning a dozen language ecosystems
The detection coverage is broad, and that breadth is the main argument for the tool. The list includes Graphene, Ariadne, Strawberry and Tartiflette in Python; Apollo, GraphQL Yoga, Directus and Dgraph in the TypeScript and JavaScript space; graphql-go, gqlgen, Inigo and jaal in Go; WPGraphQL, GraphQL API for Wordpress, Gato GraphQL, graphql-php and Lighthouse in PHP; graphql-ruby and Agoo in Ruby; graphql-java and HyperGraphQL in Java; Hasura and morpheus-graphql in Haskell; Juniper, pg_graphql and tailcall in Rust; Sangria and Caliban in Scala; GraphQL.NET and Hot Chocolate on .NET; absinthe-graphql in Elixir; Lacinia in Clojure; Flutter in Dart; Diana.jl in Julia; ballerina-graphql on Ballerina; and AWS AppSync as a hosted product. The -l flag prints this list at runtime rather than making you read the documentation. Note what is absent: there is no generic fallback that reports the endpoint as GraphQL with an unknown engine, so a server built on a stack that is not listed will most likely come back as unrecognised rather than as a specific product. That is a gap worth knowing about before you read too much into a negative result.
The threat matrix link is a lookup, not a scan
Once a fingerprint succeeds, the tool prints a link to an attack surface document from the GraphQL Threat Matrix project, which the tool uses as its technology security matrix database. The intent is that the engineer reads the entry for the identified technology to see how mature it is, what security features it offers, whether those features are on by default, and whether any CVEs apply. Two things to be clear about. First, this is a pointer to a static document keyed by product, not a result produced by testing your endpoint. The same link comes back for every instance of that product, whether or not the deployment has been hardened. Second, the matrix lives in a separate repository, so the accuracy of the output depends on that database being current for the version you are actually running. A read of a threat matrix entry tells you what the technology can do; it does not tell you what the deployment in front of you is configured to allow. Treat the link as a research starting point, then verify the specific configuration yourself.
Cloning the repository and fingerprinting a known path
The project has two prerequisites, python3 and the requests library, and the documented install path is a clone rather than a package index entry. The README gives the clone command as the first step:
git clone https://github.com/dolevf/graphw00f.gitThe entry point is main.py at the top level of the repository, next to conf.py, version.py, the graphw00f package and the static assets. Once the source is in place, fingerprint mode against a path you already know looks like this:
python3 main.py -f -t https://demo.hypergraphql.org:8484/graphqlThe README records what a successful run looks like. The tool reports that it is checking whether GraphQL is available, that it found GraphQL, that it is attempting to fingerprint, and finally names the discovered engine. On the documented example the engine is HyperGraphQL, and the run also prints the technology language, the project homepage, and a link to the attack surface document for that engine. A run that ends with a discovered engine name and a matrix link is a successful fingerprint. If instead the tool never gets past the availability check, the usual causes are a path that does not resolve, a gateway that requires authentication you have not supplied, or an endpoint that answers only over one HTTP method.
Header handling in conf.py, and the command line override
Configuration is deliberately thin. The project documentation states that there are not a lot of configurations required, and points to conf.py at the top level of the repository for the two cases that come up in practice, custom headers and custom cookies. The file in the repository carries these defaults:
# Custom Headers
HEADERS = {'User-Agent':'graphw00f'}# Custom Cookies
COOKIES = {"PHPSESS":"DEADBEEF"}Those two blocks are the whole of the documented configuration surface, and the example values are placeholders rather than a working session. The precedence rule matters: using --user-agent adds a User-Agent key regardless of whether conf.py already has one, and where the file does have one, the command line parameter overrides it. The -H option adds individual headers for a single run, with the documented example being an Authorization header carrying a bearer token, which is what you need for a target behind an API gateway. Because conf.py is read from the working directory, two engineers scanning different hosts with different credentials will overwrite each other's settings unless they keep separate copies of the file. Editing conf.py directly for every target is a clumsy way to manage that, and the -H and -u flags exist to avoid it.
Proxies, timeouts and CSV output for repeated runs
Several options exist for running the tool against infrastructure that is not a plain public host. The -p flag takes an HTTP or HTTPS proxy URL in the form http://user:pass@host:port, and -T sets a request timeout in seconds. The -r flag stops the tool following redirections given by 3xx responses, which is useful when a redirect is itself the interesting signal or when a redirect would otherwise bounce you out of the scope you were given. Results can be written to a file in CSV form with -o, and a custom user-agent can be supplied with -u, which overrides the value from the headers configuration. The -H flag passes individual headers on the command line, with the documented example being an Authorization header carrying a bearer token. The full option list is long enough that reading it from the tool itself is more reliable than remembering it:
Usage: main.py -d -f -t http://example.com
Options:
-h, --help show this help message and exit
-r, --noredirect Do not follow redirections given by 3xx responses
-t URL, --target=URL target url with the path
-f, --fingerprint fingerprint mode
-d, --detect detect mode
-p PROXY, --proxy=PROXY
HTTP(S) proxy URL in the form
http://user:pass@host:port
-T TIMEOUT, --timeout=TIMEOUT
Request timeout in secondsThe -v flag prints the current version and exits, which is worth capturing in the CSV alongside results if you intend to keep the findings as evidence.
BSD-3-Clause terms and a last push on 2026-05-16
The licence field on the repository says BSD-3-Clause, a permissive licence with a notice requirement and no copyleft obligation on code that links against it. That keeps the tool straightforward to use inside an internal security workflow, or to vendor and adjust for an engagement, subject to keeping the notice intact. On upkeep, the last push was on 2026-05-16, and the published releases are 1.2.1 on 2025-04-15, 1.2.0 on 2025-04-14 and 1.1.19 on 2024-11-28. Two observations follow. Recent commit activity is not matched by a stream of tagged releases, so the two signals move at different speeds and you should not assume a new tag for every fix. The example output printed in the documentation also still shows the banner for version 1.0.7, which is older than the current release line, so treat the version string in that example as a record of the documentation rather than as the version the current source would report. If you need a specific behaviour, pin to a release tag and record that tag in your notes.
Editorial conclusion
graphw00f suits a security engineer who has a GraphQL URL and wants to know whether it is Apollo, Hasura, WPGraphQL or a custom server, and a platform team auditing a fleet of endpoints. It is not a substitute for a GraphQL-aware scanner: it identifies a product and points at a threat matrix document, it does not test authorizations, and it does not probe for injection. Before relying on it, run the list of technologies it can detect with the -l flag against your own stack, confirm that your endpoint answers over GET or POST in the way the tool expects, and check that the conf.py header handling matches whatever your gateway requires, since a 401 response can easily be mistaken for a failed fingerprint.
Frequently asked questions
How do I install graphw00f?
The documented prerequisites are python3 and requests, and the project installs by cloning the repository with git clone https://github.com/dolevf/graphw00f.git. The entry point is main.py, and no package index install is documented in the project.
What is the difference between the detect and fingerprint modes in graphw00f?
Fingerprint mode, selected with -f, targets an endpoint where the GraphQL path is already known. Detect mode, selected with -d, works out whether GraphQL is present and where it lives on a host, and the two can be combined in one run so that detection feeds the fingerprinting pass.
How does graphw00f identify the GraphQL engine?
It sends a mix of benign and malformed queries and compares the responses against the known behaviour of individual implementations, since servers differ in how they answer queries, mutations and subscriptions. The tool can attempt to identify 37 listed technologies, covering Python, Go, PHP, Java, Ruby, Rust, .NET, Scala, Haskell, Elixir, Clojure, Dart, Julia, Ballerina and hosted services such as AWS AppSync.
Can I authenticate to a protected GraphQL endpoint with graphw00f?
Yes. The conf.py file at the top level of the repository holds custom headers and cookies, and the -H option passes individual headers on the command line, with the documented example being an Authorization bearer token. The -u user-agent option overrides any user-agent already set in the headers configuration.
Does graphw00f test for GraphQL vulnerabilities?
No, the tool fingerprints rather than probes for flaws. It identifies the server implementation and then prints a link to the matching entry in the GraphQL Threat Matrix, a reference document describing what security features that technology offers and whether they are on by default, so it points you at documentation rather than producing a vulnerability finding.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/dolevf-graphw00f)Was this useful?