') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Overview · kay-schecker/graphql-php · GitHub
Skip to content

Security: kay-schecker/graphql-php

Security

docs/security.md

Protection Against Malicious Queries

GraphQL allows a large degree of dynamism and flexibility for clients to control what happens on the server during query execution. Malicious clients may abuse this by sending very deep and complex queries whose execution exhausts server resources.

At a basic level, it is recommended to limit the resources a single HTTP request can use through PHP settings such as:

In addition, graphql-php offers security mechanisms that are specific to GraphQL.

Query Complexity Analysis

This is a port of Query Complexity Analysis in Sangria.

Complexity analysis is a separate validation rule which calculates query complexity score before execution. Every field in the query gets a default score 1 (including ObjectType nodes). Total complexity of the query is the sum of all field scores. For example, the complexity of introspection query is 109.

If this score exceeds a threshold, a query is not executed and an error is returned instead.

Complexity analysis is disabled by default. You may enable it by setting a maximum query complexity:

useGraphQL\GraphQL;
useGraphQL\Validator\Rules\QueryComplexity;
useGraphQL\Validator\DocumentValidator;
$rule = newQueryComplexity(100);
DocumentValidator::addRule($rule);
GraphQL::executeQuery(/*...*/);

This will set the rule globally. Alternatively, you can provide validation rules per execution.

To customize field score add complexity function to field definition:

useGraphQL\Type\Definition\Type;
useGraphQL\Type\Definition\ObjectType;
$type = newObjectType([
'name' => 'MyType',
'fields' => [
'someList' => [
'type' => Type::listOf(Type::string()),
'args' => [
'limit' => [
'type' => Type::int(),
'defaultValue' => 10
]
],
'complexity' => fn (int$childrenComplexity, array$args): int => $childrenComplexity * $args['limit'],
]
]
]);

Limiting Query Depth

This is a port of Limiting Query Depth in Sangria.

This is a simpler approach that limits the nesting depth a query can have. For example, the depth of the default introspection query is 7.

This rule is disabled by default. You may enable it by setting a maximum query depth:

useGraphQL\GraphQL;
useGraphQL\Validator\Rules\QueryDepth;
useGraphQL\Validator\DocumentValidator;
$rule = newQueryDepth(10);
DocumentValidator::addRule($rule);
GraphQL::executeQuery(/*...*/);

This will set the rule globally. Alternatively, you can provide validation rules per execution.

Disabling Introspection

Introspection is a mechanism for fetching schema structure. It is used by tools like GraphiQL for auto-completion, query validation, etc.

Introspection is enabled by default. It means that anybody can get a full description of your schema by sending a special query containing meta fields __type and __schema .

If you are not planning to expose your API to the general public, it makes sense to disable this feature.

GraphQL PHP provides you separate validation rule which prohibits queries that contain __type or __schema fields. To disable introspection, add following rule:

useGraphQL\GraphQL;
useGraphQL\Validator\Rules\DisableIntrospection;
useGraphQL\Validator\DocumentValidator;
$rule = newDisableIntrospection(DisableIntrospection::ENABLED);
DocumentValidator::addRule($rule);
GraphQL::executeQuery(/*...*/);

This will set the rule globally. Alternatively, you can provide validation rules per execution.

There aren't any published security advisories