Playing with GraphQL


What’s that …. ?
GraphQL is a powerful query language and runtime for APIs that allows clients to request exactly the data they need nothing more, nothing less through a single endpoint, unlike REST which often requires multiple endpoints and can lead to over-fetching or under-fetching data.
Imagine it as a wise librarian who, instead of handing you a whole shelf of books you didn’t ask for, gives you only the exact chapters and pages you request. GraphQL operates using a type-safe schema that defines the structure of data and supports nested queries, enabling clients to fetch complex, related data in a single call. It’s introspective, meaning clients can query the API for its own structure, making it self-documenting and developer-friendly. However, with this flexibility comes responsibility: improper implementation can expose vulnerabilities such as overly deep queries (causing denial of service), unauthorized data exposure, or introspection abuse. By applying query depth limits, field-level access control, and secure plugin practices, developers can harness GraphQL’s full potential for efficient, scalable, and elegant API design in modern applications.
Why to use GraphQL instead of REST / SOAP APIs ?…
Okay ……. the story behind it
Once upon a time, in the vast kingdom of DataLand, there lived many wise wizards who guarded ancient scrolls and powerful data spells. But the people of DataLand had a problem.
The RESTful Scrollmasters :
Every time someone needed knowledge - say, about a user, their posts, and the comments - they had to visit multiple scrollmasters:
Go to the User Wizard to get the user's details.
Then, find the Post Scrollkeeper to fetch that user’s posts.
Finally, run to the Comment Sorcerer to see the comments.
Each wizard gave you too much or too little information. Sometimes you asked for just a name, and they gave you the entire life history. Other times, they missed important bits, and you had to make more trips.
People called this the RESTful era -not because it was relaxing, but because each scroll (endpoint) was fixed, and you had to go on a quest for every tiny thing.
Enter GraphQL: The All-Knowing Librarian
One day, a new character arrived in town a magical librarian named GraphQL (pronounced "Graph-Queue-Ell"). Unlike the old scrollmasters, GraphQL sat in a single library and said:
“Ask me exactly what you want, and I shall give you only that - no more, no less.”
At first, the villagers were confused.
"How does that work? You don’t have multiple scrolls?"
GraphQL smiled and opened a beautiful book made of types, fields, and schemas. It looked like this:
type User {
id: ID
name: String
posts: [Post]
}
type Post {
title: String
content: String
}
The villagers were amazed when they saw how easy it was to cast a query spell:
{
user(id: 1) {
name
posts {
title
}
}
}
With that one request, GraphQL summoned the user's name and their post titles from thin air - in a single trip!
No more running to multiple scrollmasters. No more over-fetching or under-fetching.
GraphQL vs REST APIs : A Detailed Comparison
Feature | GraphQL | REST API |
Data Fetching | Client specifies exactly what data it needs (no over/under-fetching). | Fixed data structures; may over-fetch or require multiple calls. |
Endpoint Structure | Single endpoint (e.g., /graphql ). | Multiple endpoints (e.g., /users , /users/1/posts ). |
Query Flexibility | Highly flexible; clients define shape and depth of the response. | Predefined responses; changes require new endpoints or versioning. |
Versioning | No need for versioning; evolve schema with deprecations. | Requires versioning (e.g., /v1/users , /v2/users ). |
Performance | Efficient for complex/nested data; may need query complexity control. | Simple to cache; better for flat, predictable resources. |
Schema & Type Safety | Strongly typed schema; introspection makes it self-documenting. | No formal schema (unless using OpenAPI/Swagger); documentation separate. |
Tooling Support | Excellent tooling (GraphQL Playground, Apollo, Codegen, etc.). | Mature ecosystem; tools like Postman, Swagger are widely used. |
Caching | Complex; requires custom or persisted queries. | Easy with HTTP caching (ETag, Cache-Control). |
Real-Time Support | Built-in support via subscriptions (WebSocket-based). | Not native; needs polling or external tech like WebSockets. |
Learning Curve | Steeper due to schemas, resolvers, and query language. | Easier for beginners familiar with HTTP verbs and status codes. |
Security Considerations | Risks include query abuse, field-level leakage, introspection misuse. | More mature controls (but still prone to overexposure if not handled well). |
Use Case Suitability | Best for apps with complex relationships and dynamic frontends (e.g., SPAs). | Ideal for simple CRUD apps and RESTful resource-based architectures. |
Okay ..! Now as we know how important and efficient is GraphQL , now let’s deep dive into the GraphQL architecture:
Firstly let’s discuss about the GraphQL Client and GraphQL Server:
GraphQL Client : The frontend or external consumer (browser, mobile app, IoT, etc.) sends structured GraphQL queries to the server, specifying exactly what data and relationships it needs.
Popular Clients:
Apollo Client
Relay
Apollo-android
Apollo-iOS etc.
GraphQL Servers : This is the brain of the architecture. It typically includes:
Schema: A strongly typed definition (written in SDL - Schema Definition Language) that describes the types, queries, mutations, and relationships available.
type User { id: ID! name: String! posts: [Post] } type Query { getUser(id: ID!): User }
Resolvers: Functions that fulfill the queries and fetch data from underlying sources. Each field in the schema has a corresponding resolver.
const resolvers = { Query: { getUser: (parent, args, context) => db.getUserById(args.id), }, User: { posts: (user) => db.getPostsByUser(user.id), } };
Execution Engine: Parses the incoming query, validates it against the schema, and then invokes the appropriate resolvers to assemble the final response.
Popular Servers:
Apollo Server
graphql-js
Express-graphql
graphql-php etc.
Typical Flow of a GraphQL Request:
Client sends a query to a single endpoint (e.g.,
/graphql
) with the desired fields.Server validates the query against the schema.
Server executes resolvers for each field in the query, potentially calling different data sources.
GraphQL assembles the response matching the exact structure requested.
Client receives JSON with only the data it asked for.
GraphQL Scalar Types
Default Scalar Types :
Int : A signed 32-bit integer
Float : A signed double-precision floating-point value
String : A UTF-8 Character sequence
Boolean : true or false
ID : The ID scalar type represents a unique identifier. The ID type is serialized in the same way as a string. However defining it as an ID signifies that it is not intended to be human-readable.
Custom scalar types can also be defined using scalar. Ex: (scalar Date). For these custom scalars type checking must be done manually.
GraphQL Operation Types :
GraphQL typically has the below operations:
Quary
Mutation
Subscription
Quary :
In GraphQL, a query operation is used to fetch or read data from the server, similar to a GET
request in REST but far more efficient and flexible. Instead of hitting multiple endpoints for different resources, GraphQL uses a single endpoint where the client specifies exactly what fields and nested data it needs. This avoids over-fetching and under-fetching by allowing the client to shape the structure of the response. A typical query operation can retrieve deeply nested and related data in one request, such as fetching a user’s name, email, and their latest posts with titles and timestamps. Queries are strongly typed and validated against a schema, ensuring consistency and predictability in responses. They form one of the three core operation types in GraphQL alongside mutations (for modifying data) and subscriptions (for real-time updates)—and are central to building efficient, modern APIs.
A query is used to perform READ or FETCH operations only.
Operation Type : query or mutation or subscription.
Operation Name : Can be user defined and is optional.
Variable Definitions : variable type must be defined in the operation name before passing it to as field arguments.
Mutation :
In GraphQL, a mutation is a type of operation used to modify server-side data, such as creating, updating, or deleting records. While queries are used to fetch data (read-only), mutations are used when the client wants to cause a side effect on the backend. Each mutation is defined in the schema just like a query but typically accepts input arguments and may return the affected data as a response. For example, to create a new user, a client might send a mutation like:
mutation {
createUser(name: "Alice", email: "alice@example.com") {
id
name
email
}
}
This operation both creates the user and returns the created user's data. Mutations are executed sequentially, meaning they are run one after another to preserve data consistency, unlike queries which may be resolved in parallel. Just like queries, mutations are strongly typed and validated against the schema, and their inputs can be structured using custom input
types. Overall, mutations are the mechanism through which GraphQL enables data changes, making them a critical part of any full-featured GraphQL API.
Fragment :
In GraphQL, a fragment is a powerful way to modularize and reuse field selections across multiple queries, mutations, or subscriptions, particularly when dealing with complex or deeply nested schemas. Rather than repeating the same set of fields every time you query a specific type (like User
, Post
, or Comment
), you define a fragment once and apply it wherever that type is used, keeping your code DRY (Don’t Repeat Yourself), consistent, and easier to maintain. For example, if both the user
and post.author
fields need the same data, such as id
, name
, and email
, you can define a fragment like fragment userFields on User { id name email }
, and then reference it in your query using the spread syntax (...userFields
). A complete query might look like:
fragment userFields on User {
id
name
email
}
query GetUserAndAuthor {
user(id: 1) {
...userFields
}
post(id: 42) {
title
author {
...userFields
}
}
}
This ensures that any updates to the required user fields—such as adding a profilePicture
—need only be made in the fragment definition, not in every individual query. Fragments can also be nested or used within inline fragments for polymorphic types like interfaces or unions (e.g., ... on AdminUser
), which makes them invaluable when working with flexible or shared data structures. Additionally, GraphQL clients like Apollo use fragments heavily to tie frontend components directly to the data they depend on, making UI development more modular and aligned with API responses. Overall, fragments are essential for optimizing query readability, consistency, and reusability in scalable GraphQL applications.
Let’s expand further into advanced fragment usage, including:
Inline Fragments
Fragments on Union/Interface Types
Conditional Data Fetching
Practical Apollo Client Example
Inline Fragments
Inline fragments allow you to conditionally select fields based on the object’s actual type - useful when querying polymorphic types like interfaces or unions.
Example: Suppose you have a SearchResult
that could be either a User
or a Post
.
Schema :
union SearchResult = User | Post
type User {
id: ID!
name: String!
}
type Post {
id: ID!
title: String!
}
Query :
query Search($text: String!) {
search(text: $text) {
... on User {
id
name
}
... on Post {
id
title
}
}
}
This tells GraphQL:
Search might return different types. For Users, get name; for Posts, get title.
Use Case: Ideal when the API returns multiple types in a single list (like in search, feeds, logs, etc.).
Fragments on Interfaces or Unions
You can also name and reuse fragments for interfaces and union types just like with object types.
Schema :
interface Animal {
id: ID!
name: String!
}
type Dog implements Animal {
breed: String!
}
type Cat implements Animal {
livesLeft: Int!
}
Fragment :
fragment AnimalDetails on Animal {
id
name
... on Dog {
breed
}
... on Cat {
livesLeft
}
}
Query :
query {
animals {
...AnimalDetails
}
}
This is type-safe polymorphism - GraphQL ensures only fields valid for each type are returned.
Conditional Data Fetching with Fragments
Fragments are executed only when the type matches, which makes them ideal for optional or dynamic field selection.
You can also dynamically add or skip fragments in client-side code (e.g., Apollo) based on runtime variables.
Apollo Client Fragment Example (React)
Here’s how you might use fragments in React + Apollo:
Fragment File (Fragment.js) :
import { gql } from '@apollo/client';
export const USER_FIELDS = gql`
fragment UserFields on User {
id
name
email
profilePicture
}
`;
Query with Fragment :
import { gql, useQuery } from '@apollo/client';
import { USER_FIELDS } from './fragments';
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
...UserFields
}
}
${USER_FIELDS}
`;
function UserComponent({ id }) {
const { loading, data } = useQuery(GET_USER, { variables: { id } });
if (loading) return <p>Loading...</p>;
return <div>{data.user.name}</div>;
}
Benefits:
Keeps GraphQL logic modular.
Allows React components to declare their data requirements using fragments.
Simplifies testing and scaling in larger apps.
Now most important topic for pentesting : GraphQL Introspection
GraphQL introspection is a powerful built-in feature of the GraphQL specification that allows clients to query the schema itself. This means clients can ask the server questions like:
What types are available?
What fields does a type have?
What arguments do those fields accept?
What operations (queries, mutations) are supported?
Think of it like an API to your API - a way to explore the structure, types, and capabilities of the GraphQL service without needing external documentation.
Why Introspection Exists
Self-documentation: Introspection allows tools like GraphQL Playground, Apollo Studio, Insomnia, and GraphiQL to auto-suggest fields and types as you type.
Tooling support: Enables features like autocomplete, validation, and live schema exploration.
Dynamic clients: Allows frontend apps to adapt to schema changes dynamically (used heavily in Relay/Apollo).
How Introspection Works
GraphQL exposes introspection system types such as:
__schema
__type
__typename
__field
__directive
These are available as meta-fields and can be queried like normal GraphQL fields.
Example Introspection Queries :
Get All Query Types
{ __schema { queryType { name } } }
💡 Returns the name of the root query type (usually "Query").
List All Types in the Schema
{ __schema { types { name kind } } }
💡 This lists all types including scalars, enums, objects, interfaces, etc.
Describe a Specific Type
{ __type(name: "User") { name fields { name type { name kind } } } }
💡 This gives you all the fields of the
User
type, along with their data types.Get Enum Values
{ __type(name: "Role") { name enumValues { name description } } }
💡 Useful for frontend dropdowns or form builders.
Get Input Type Fields (Used in Mutations)
{ __type(name: "CreateUserInput") { name inputFields { name type { name kind } } } }
Leveraging Introspection
An attacker can leverage this feature to enumerate more about the implemented system, sensitive nodes, and their fields in the existing schema.
Once the schema is extracted, attacker can map application/client generated GraphQL requests with the backend schema and enumerate for sensitive fields.
Attacker can then proceed to exfiltrate sensitive data by manipulating the client-generated query documents.
Now Let’s think like an attacker … Ooh Sorry …like a pentester
How to identify the GraphQL endpoint ?
It’s simple…
Proxy the application traffic to identify the GraphQL endpoint.
A typical GraphQL endpoint could be as below
https://website.com/graphql
https://website.com/_/graphql
https://website.com/_grapqhl
https://website.com/graphql/console
Developer can configure a custom endpoint as well. :)
Input to a GraphQL endpoint will always be in JSON format.
GraphQL supports both GET and POST request methods, mostly POST method will be used.
Okay! Now let’s talk more about GraphQL Mutation queries :
How to check if introspection is enabled or not ?
Pass the below JSON data to a GraphQL endpoint, check if you get any JSON response containing names of all “queryType”s present in the schema. (The below query structure are to be passed in Burp. If testing from any GQL clients such as Altair GraphQL/GraphiQL, only the string value of “query” variable should be used)
{"query": "query {__schema {queryType {name}}}"}
Pass any of the below introspection queries to extract complete GraphQL API schema.
Introspection Query Number 1 :
{"query": "query IntrospectionQuery{__schema{queryType{name}mutationType{name}subscriptionType{name}types{...FullType}directives{name description locations args{...InputValue}}}}fragment FullType on __Type{kind name description fields(includeDeprecated:true){name description args{...InputValue}type{...TypeRef}isDeprecated deprecationReason}inputFields{...InputValue}interfaces{...TypeRef}enumValues(includeDeprecated:true){name description isDeprecated deprecationReason}possibleTypes{...TypeRef}}fragment InputValue on __InputValue{name description type{...TypeRef}defaultValue}fragment TypeRef on __Type{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name}}}}}}}}"}
Introspection Query Number 2 :
{"query": "query IntrospectionQuery{__schema{queryType{name}mutationType{name}subscriptionType{name}types{...FullType}directives{name description args{...InputValue}onOperation onFragment onField}}}fragment FullType on __Type{kind name description fields(includeDeprecated:true){name description args{...InputValue}type{...TypeRef}isDeprecated deprecationReason}inputFields{...InputValue}interfaces{...TypeRef}enumValues(includeDeprecated:true){name description isDeprecated deprecationReason}possibleTypes{...TypeRef}}fragment InputValue on __InputValue{name description type{...TypeRef}defaultValue}fragment TypeRef on __Type{kind name ofType{kind name ofType{kind name ofType{kind name}}}}"}
Enumeration & Exploitation Methodology
Methodology :
Enumerate/Extract the GraphQL Schema from Introspection.
Create a Mapping from the extracted Schema.
Identifying the workflow of GraphQL within the application.
Separate the State Change GraphQL(mutation) requests and query requests.
Identify the potential injection parameters.
Cross reference the GraphQL request and its parameters with the extracted schema to extract sensitive information if any.
Extracting the Schema :
Ways to extract the GraphQL API schema in JSON format:
Manually through Burp using introspection query.
GraphQL Client (Altair).
InQL
Now, let’s see how can we extract the schema by this 3 ways :-
Extracting the Schema through Burp (Method 1) :
First thing is to find the GQL endpoints.
After that, send the introspection queries to retrieve the schemas as shown below.
All right ! Now let’s see how to extract the Schema through GraphQL clients (Method number 2) :
Let’s talk about Altair GraphQL Client
A tool to interact with the GraphQL API’s.
Automatically tries to fetch the schema using introspection and provide a documentation.
Easy to add a query/mutation right from the schema with just a click.
Load the schema into client or download the extracted schema as SDL and many more .
Here are the links for Chrome and Firefox extensions, you can always check .
Okay! Now it’s time to extract the Schema through InQL (Method 3) :
InQL is an open-source GraphQL security testing tool designed to help security researchers and penetration testers explore and analyze GraphQL endpoints for vulnerabilities. It automates GraphQL introspection, query generation, and reconnaissance, making it easier to discover misconfigurations, sensitive operations, or poorly protected fields.
How InQL Helps in Hacking GraphQL APIs
Run introspection on the target endpoint (if enabled).
Parse and visualize all available types, queries, and mutations.
Generate example queries automatically, even for nested or complex structures.
Send test queries directly through Burp or export them to modify and fuzz manually.
Spot risky operations (like
deleteUser
,resetPassword
, etc.) and test for improper authorization.Find over-permissive resolvers, injection points, and logic flaws.
Example Usage (CLI):
inql -t https://target.com/graphql
This pulls the introspection schema and generates .graphql
files you can test with.
Here is the official GitHub repo link to install the InQL.
Visualizing GraphQL API Schema : with the help of GraphQL Voyager
As GraphQL APIs grow in complexity—with deeply nested types, interlinked queries, and custom inputs—understanding their structure becomes more challenging than reading a Swagger file. That's where GraphQL Voyager steps in, turning your API schema into an interactive visual graph that looks and feels like an ER diagram, but for your API. This is not just a luxury for frontend developers—it's a power tool for backend architects, testers, and security researchers alike.
GraphQL Voyager works by performing a standard introspection query on your GraphQL endpoint. This query retrieves the entire type system: object types, fields, inputs, enums, queries, mutations, interfaces, and unions. The result is rendered as a navigable graph, where types appear as nodes and relationships between fields and types form the edges. For example, clicking on a User
type will immediately show how it connects to Posts
, Comments
, or related nested types—helping you understand the data flow and design patterns at a glance.
To use Voyager, you can either:
- Paste your schema JSON file into the live demo site
Or run it locally using:
npx graphql-cli introspect --endpoint https://your-api.com/graphql > schema.json
npx graphql-voyager schema.json
This spins up a local interface that’s especially handy for internal documentation, developer onboarding, or even penetration testing. Security engineers often use Voyager during reconnaissance to identify suspicious mutations (like deleteUser
, resetPassword
, or grantRole
) and analyze how deeply sensitive types are exposed through nested query paths.
Below are the screenshots for reference :
Ok Visualization is done. Now What ?
Now that we have the visualized form of schema.
Navigate through out the application across all functionalities.
Note down all the queries/mutations that the client/application generates.
Map the queries against the schema.
Look for sensitive fields in the schema and try to pull those by manipulating the queries.
Check for debugging mode —> abc.com/graphql?debug=1
Finding Injection Points:
GraphQL Raider or InQL can help identifying the injection points in any query document.
Look at inline variables and variables JSON object.
Try for IDOR’s and all injection vulnerabilities.
Check validations for custom scalars
Finding the variable entry points and test for all injection attacks
Look for inline variables and variables JSON object.
If you have the schema check for the scalar type of each variable and try for all injection related attacks.
- IDOR: Below screenshot shows an IDOR (Insecure Direct Object Reference) vulnerability in a GraphQL API where the
team_member_id
is a Base64-encoded string (gid://...
). By decoding it, an attacker can extract the numeric ID (e.g.,43794
) and change it to access or modify other users' data without authorization.
SQL Injection: Below screenshot demonstrates a SQL Injection vulnerability in a GraphQL API where the embedded_submission_form_uuid
parameter is not properly sanitized. An attacker injects SQL like 1%27%3BSELECT%20...SLEEP(3)--
to delay responses. The varying response times (e.g., 5.7s, 10.5s) confirm time-based blind SQLi, allowing data extraction through timing inference.
Mapping the schema and look for sensitive fields
Map the application's generated queries against the visualized schema to identify any sensitive fields.
The application will typically only query the data it intends to display, but there may be other sensitive fields present in the schema that are not exposed in the UI and you're simply unaware of them.
Below mentioned screenshot shows that sensitive fields can be extracted if present In the schema.
Checking for Authentication & Authorization validation :
Authentication & Authorization bugs can be commonly found in GraphQL API’s.
Check if queries are processed without auth token/cookies.
Authorization-related logic should be written by the developer. High chances for Authorization-related vulns.
If you cannot access something directly, try go around.
Check for possible race conditions to bypass access controls
Authentication Check:
Are unauthenticated users able to access the GraphQL endpoint?
Does the server reject requests without a valid token?
Are introspection queries accessible without auth?
Test 1: Access endpoint without token
curl -X POST https://target.com/graphql -d '{"query":"{ me { email } }"}'
Expected: Server should return a
401 Unauthorized
or an auth error.
Test 2: Use expired or invalid token
Authorization: Bearer invalid_or_expired_token
Expected: Should return error or deny access.
Authorization Check
Can a regular user access admin-only queries or mutations?
Can a user modify or view resources belonging to others (IDOR)?
Are sensitive fields like
isAdmin
,permissions
, orroles
exposed in the schema?
Test 1: Mutation Access Control
mutation {
deleteUser(userId: "12345") {
success
}
}
If you're not an admin and this works authorization is broken.
Test 2: Query Other Users' Data (IDOR)
query {
user(id: "9999") {
email
role
}
}
If you can read other users’ data, it’s an access control flaw.
Test 3: Overfetching sensitive fields
query {
me {
id
email
isAdmin
passwordHash
}
}
If fields like
isAdmin
,permissions
,token
, orpasswordHash
are accessible they should be protected or removed.
GraphQL Batching Attack :
A GraphQL Batching Attack exploits the feature that allows sending multiple queries in a single HTTP request, usually via an array of operations. While batching is meant to improve performance by reducing round-trips, if authorization is weak or misapplied per request instead of per operation, attackers can abuse it to bypass access control or enumerate data at scale.
Let’s understand how this vulnerability arises :
Instead of sending one query like:
{ "query": "{ me { email } }" }
An attacker sends:
[
{ "query": "{ user(id: 1) { email } }" },
{ "query": "{ user(id: 2) { email } }" },
{ "query": "{ user(id: 3) { email } }" }
]
Boom…….!!!!!! All of these execute in one HTTP request.
Example : If any application is using GraphQL queries for Login, OTP verification etc., then try for batching attacks.
Why It's Dangerous
Auth checks may be done per request, not per query so all batched queries execute with the attacker’s privileges.
It allows mass enumeration of user data or resources.
It can bypass rate limiting if controls are tied to request count, not query count.
Can aid in bulk exploitation of IDORs or privilege escalation.
What if Introspection is disabled ?
introspection disabled != End of the game
Try generating/guessing variables based on the existing variable patterns using trial and error method.
Try all Injection-related Vulnerabilities.
Try for authentication- and authorization-related vulnerabilities.
Review the application business logic and try for business logic vulnerabilities.
Try batching attacks if possible.
Cheet Sheet
Conclusion :
GraphQL’s flexibility and powerful query capabilities make it a favorite for modern API development—but that same power can introduce unique security challenges. As we've explored, GraphQL schemas often expose more than intended, and without proper authentication, authorization, and input validation, the attack surface can grow rapidly.
Effective GraphQL pentesting involves mapping the schema, testing queries and mutations, identifying hidden or sensitive fields, and exploiting misconfigurations such as IDORs, introspection leaks, over-fetching, or batching attacks. Tools like GraphQL Voyager, InQL, and Burp Suite help illuminate hidden paths and vulnerabilities in ways traditional REST testing cannot.
Ultimately, securing GraphQL APIs requires a mindset shift: every field, not just endpoint, must be validated. As developers and security professionals, staying ahead means treating GraphQL with the same rigor and caution as any other critical surface of your application.
Subscribe to my newsletter
Read articles from INDRAYAN SANYAL directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

INDRAYAN SANYAL
INDRAYAN SANYAL
A cybersecurity consultant with over 3 years of experience, I specialize in assessing web applications, APIs, mobile applications, and more from a black/grey box perspective. My responsibilities include identifying vulnerabilities in source code and providing clients with action plans to protect their organizations against cyber threats.