Skip to content

[REVIEW] api-security: add GraphQL batching, alias, and persisted-query evidence gates #1553

@alejandrorivas-pixel

Description

@alejandrorivas-pixel

Skill Being Reviewed

Skill name: api-security
Skill path: skills/appsec/api-security/

False Positive Analysis

Benign GraphQL API that can be over-reported as unrestricted resource consumption:

query OrderSummary($id: ID!) {
  order(id: $id) {
    id
    lineItems(first: 25) { nodes { sku quantity } }
  }
}

Evidence:

graphql_controls:
  max_depth: 6
  max_complexity: 500
  alias_limit: 10
  batch_operations_per_request: 1
  persisted_queries_required: true
  introspection: disabled_in_prod
  resolver_cost_overrides:
    Order.lineItems: 20
    Search.results: 50

Why this can be a false positive:
The current skill correctly says GraphQL needs depth, complexity, and batch query limits, but it does not require reviewers to preserve the actual enforcement evidence. A bounded query can be flagged as risky if the report does not capture whether the gateway/server enforces depth, complexity, aliases, operation count, persisted query allowlists, and resolver-specific costs.

Coverage Gaps

Missed variant 1: Alias fan-out bypasses endpoint rate limits

query {
  a1: login(email: "user@example.com", password: "guess1") { token }
  a2: login(email: "user@example.com", password: "guess2") { token }
  a3: login(email: "user@example.com", password: "guess3") { token }
}

Why it should be caught: A gateway-level request limit sees one HTTP request while the GraphQL executor performs many sensitive resolver calls. The skill mentions alias abuse but lacks evidence fields for alias count, operation count, and per-resolver throttling.

Missed variant 2: Persisted-query bypass allows arbitrary expensive documents

prod_policy:
  persisted_queries_required: true
observed:
  POST /graphql accepts raw query documents when sha256Hash is unknown
  introspection: disabled
  max_complexity: not logged

Why it should be caught: Disabling introspection is not enough if production accepts arbitrary non-persisted documents. The skill should require evidence that unknown persisted-query hashes are rejected and raw queries are blocked when the policy says persisted-only.

Missed variant 3: Complexity exists but expensive resolver fields have default cost

complexity_plugin: enabled
resolver_costs:
  User.orders: default_1
  Search.results: default_1
  Report.exportUrl: default_1

Why it should be caught: Default field costs can underprice database fan-out, exports, nested connection traversal, and third-party calls. Reviewers need a resolver-cost matrix, not just a yes/no complexity flag.

Edge Cases

  • Batched JSON arrays containing multiple GraphQL operations in one HTTP request.
  • GraphQL subscriptions where one connection creates long-lived server work and bypasses per-request accounting.
  • Federation/supergraph deployments where subgraph cost limits differ from router limits.
  • Field-level authorization performed in parent resolvers but bypassed by nested fields, aliases, fragments, or DataLoader batch fetches.

Remediation Quality

  • Fix resolves the vulnerability
  • Fix does not introduce new security issues
  • Fix does not break functionality
  • Issues found: Add GraphQL evidence gates for operation count, alias count, depth, complexity, resolver cost overrides, persisted-query enforcement, subscription limits, federation/subgraph parity, and field-level authorization evidence.

Comparison to Other Tools

Tool Catches this? Notes
OWASP GraphQL Cheat Sheet Partial Covers query limiting, batching, and authorization concepts; skill should operationalize evidence
Apollo persisted queries / safelisting Partial Mechanism exists but review must verify reject behavior for unknown hashes
GraphQL depth/complexity plugins Partial They enforce limits only if configured with accurate resolver costs
Semgrep/CodeQL Partial Can catch some resolver auth bugs, not runtime policy evidence

Overall Assessment

Strengths: The skill covers OWASP API Top 10, BOLA/BFLA, GraphQL resolver authorization, depth/complexity basics, and alias abuse.

Needs improvement: GraphQL controls are not represented in the structured output or fixtures. A review can say depth/complexity exists without proving operation/alias limits, persisted-query rejection, resolver cost calibration, subscriptions, or federation parity.

Priority recommendations:

  1. Add a GraphQL operation-control evidence table to the skill output.
  2. Add findings for alias/batch fan-out, persisted-query bypass, missing resolver-cost overrides, and subscription/federation limit gaps.
  3. Add benign/vulnerable fixtures for bounded persisted GraphQL, alias brute force, raw-query bypass, and default-cost expensive resolvers.

Sources Checked

Bounty Info

  • I have read and agree to the CONTRIBUTING.md bounty terms.
  • Preferred payment method: Payment details can be provided privately after maintainer acceptance.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions