Claude Cursor GitHub Copilot Skill

graphql-client-security-review

Statically review GraphQL client configuration (Apollo Client, and urql/similar clients by analogy) for production-enabled devtools/introspection exposure, a normalized cache left uncleared across user sessions, missing persisted-query allowlisting against client-driven query abu

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download vincentchuwaichow-vanguard-frontier-agentic-skills_frontend_graphql-client-security-review-febe32a.zip · 13 KB
Part of vincentchuwaichow/vanguard-frontier-agentic — 293 skills

Install

skills CLI npx skills add https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/tree/master/skills/frontend/graphql-client-security-review
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vincentchuwaichow-vanguard-frontier-agentic@llmmart
Git git clone https://github.com/VincentChuWaiChow/vanguard-frontier-agentic.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole vincentchuwaichow/vanguard-frontier-agentic collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

GraphQL Client Security Review

Purpose

Review GraphQL client setup code -- client construction, link chains, and cache configuration -- for five documented, security-critical defect classes: production-reachable devtools/schema introspection, a normalized cache that survives a user logout or account switch, client-driven query abuse with no server-side allowlist, an authorization header attached with no accompanying CSRF protection, and sensitive fields written into the normalized cache with no masking policy. This skill exists so the review stays anchored to these five concrete, greppable sinks instead of drifting into a general "is this GraphQL app well-architected" review.

When to use

Use this skill when the user asks to:

  • review an Apollo Client (or comparable GraphQL client) instantiation for production security posture,
  • assess whether a logout/session-switch flow correctly clears client-side GraphQL cache state,
  • investigate a report that one user's cached GraphQL data appeared in another user's session on the same device,
  • audit a GraphQL client's link chain for unallowlisted, client-driven query risk (excessive nesting, alias-based amplification, arbitrary ad hoc documents reaching the server),
  • check whether an auth-header link (SetContextLink or equivalent middleware) exposes GraphQL mutations to CSRF,
  • review cache typePolicies (or equivalent) for whether sensitive fields (payment data, cvv, SSNs, tokens) are masked before being normalized into the cache.

Do not use this skill for:

  • GraphQL server-side schema/resolver authorization review (field-level permission checks, resolver-level access control) -- this skill is scoped to the client, not the server; the server-side defect class needs a different review entirely,
  • general Apollo Client / urql architecture or performance review with no security angle (cache normalization strategy quality, fetch-policy tuning, pagination design) -- use a general frontend-architecture review for that,
  • a bug that requires live traffic reproduction (capturing a real cross-user cache leak, load-testing a query-cost exploit against a live server) to confirm exploitation -- static analysis proves the structural risk in the client configuration, not that it has already been exploited in production.

Context7 Documentation Protocol

  • Resolve the Apollo Client library ID with resolve-library-id (matched result: /apollographql/apollo-client) before citing any client-configuration, cache-reset, or link-chain claim.
  • /apollographql/apollo-client is Apollo Client's own docs-and-source repository. Use query-docs against it to ground claims about connectToDevTools behavior, resetStore/clearStore semantics, SetContextLink header-injection patterns, and PersistedQueryLink allowlisting -- these are repo evidence when confirmed there.
  • urql is not currently resolvable in Context7. Treat urql-specific findings as documentation-based (urql's own published docs) or inference (reasoning by analogy from Apollo Client's documented behavior), and say so explicitly -- never state a urql API detail as repo evidence without a Context7-confirmed source.
  • Read package.json first to confirm which GraphQL client (and version) is actually in use before applying any client-specific API name -- resetStore/clearStore and SetContextLink are Apollo Client APIs; do not apply them to a urql or graphql-request codebase without confirming the equivalent construct first.
  • If Context7 is unavailable, fall back to the official_docs URLs in this skill's metadata.json and label the claim documentation-based, unverified against current release.

Lean operating rules

  • All five defect classes default to HIGH severity. This is a security-scoped skill: do not downgrade a production-reachable devtools finding, an uncleared cache after logout, an unallowlisted query surface, a CSRF-less auth header, or an unmasked sensitive cached field to MEDIUM just because it has not been observed exploited yet -- the risk is in the structure, not in whether someone has already hit it.
  • Trace every finding to a concrete file:line and a concrete data-flow or configuration path. A finding that says "the cache might leak" or "this could be a CSRF risk" without showing the specific connectToDevTools literal, the specific logout handler missing resetStore/clearStore, or the specific header-construction code is not a valid finding -- it is a guess.
  • connectToDevTools set to an unconditional true (or simply always-on with no environment gate) is a HIGH finding regardless of whether the current deployment happens to be a staging environment -- flag the structural exposure, not just today's environment. connectToDevTools correctly gated on a build-time environment check (development-only) is not a finding.
  • Do not approve a logout or account-switch handler unless it visibly calls client.resetStore() or client.clearStore() (or the equivalent for the client library in use) on the exact path between the auth-invalidation call and the navigation/redirect that follows. A cache-clearing call that exists elsewhere in the codebase, in a different handler, does not clear this bar for the specific logout path under review.
  • Check every link-chain construction for a PersistedQueryLink (or equivalent allowlisting mechanism) between the application's operations and the transport link. A client wired directly to a raw HTTP link with no persisted-query allowlist is a MEDIUM-to-HIGH finding depending on whether the endpoint is public/unauthenticated (higher) or requires an authenticated session to reach (lower, but still a finding) -- an authenticated session does not eliminate cost-abuse risk from a legitimate-but-malicious authenticated client.
  • Check every SetContextLink/auth-header-injection link for whether it attaches an anti-CSRF header (commonly x-csrf-token or an equivalent named header) alongside the authorization header. A token-only header construction with no CSRF header is a MEDIUM-to-HIGH finding when the same token can also travel via an automatically-attached credential (a cookie-backed session, a same-site credentialed fetch) -- if the token is exclusively attached by explicit client code with no cookie-based fallback anywhere in the auth flow, state that explicitly and do not manufacture a CSRF finding where the attack surface does not exist.
  • Check cache typePolicies (or equivalent field-policy mechanism) for every sensitive field type (payment data, cvv, SSNs, auth tokens, other PII) that flows through a query in scope. A sensitive field with no masking read() policy, normalized into an InMemoryCache (or equivalent) with no field-level exclusion, is a HIGH finding -- it is visible in the devtools cache inspector for the life of the cache entry regardless of whether devtools happen to be enabled in the current build.
  • Never execute, build, or run application code, and never send live GraphQL requests, as part of this review; this is a static-review skill (Read/Grep/Glob only).
  • Load only the reference needed for the concern in scope.

References

Load these only when needed:

Response minimum

Return, at minimum:

  • the client construction, link chain, cache configuration, and/or logout handler(s) in scope,
  • ranked findings with file:line evidence, defect category (devtools-exposure, cache-not-cleared, unallowlisted-query-surface, csrf-less-auth-header, or unmasked-sensitive-field), the concrete configuration or data-flow trace, and a fix sketch matching Apollo Client's documented pattern,
  • for every cache-clearing finding, an explicit statement of whether resetStore() or clearStore() is present on the traced logout path -- never approve on the assumption one exists elsewhere,
  • for every sensitive-field finding, an explicit statement of whether a typePolicies masking read() policy is present for that exact field -- never approve on the assumption the field is handled safely downstream,
  • evidence level per finding (repo evidence, documentation-based, or inference), with structural risk findings explicitly labeled as structural risk, not as confirmed-exploited,
  • verdict (approve / approve-with-notes / block),
  • open questions or scope the review could not cover (e.g., "confirming actual query-cost abuse requires live load testing against the GraphQL server, not static review of the client").
Files (vanguard-frontier-agentic)
  • references
    • cache-lifecycle-and-query-surface.md 6.7 KB
      # Cache Lifecycle and Query-Surface Risk
      
      Use this reference when reviewing cache clearing on logout, `typePolicies` masking of sensitive fields, or `PersistedQueryLink` allowlisting of the client's query surface.
      
      ## What people get wrong
      
      The naive assumption is:
      
      > "We call the logout API and redirect to `/login` -- the user is logged out, so we're done."
      
      Wrong for a normalized-cache GraphQL client. Apollo Client's `InMemoryCache` (and equivalents) persists query results across the lifetime of the JavaScript process, not just the lifetime of a single request. If the server-side session is invalidated but the client-side cache is never told to forget what it knows, the previous user's cached data remains resolvable by whatever renders next -- a shared device, a fast re-login by a different account, or a client-side-only account switch with no full page reload. Redirecting to a login page does not clear an in-memory cache; only an explicit call does.
      
      A parallel naive assumption applies to the query surface:
      
      > "Our GraphQL endpoint requires authentication, so a client can't do anything malicious with it."
      
      Also wrong. Requiring authentication limits *who* can send a query, not *what* they can send once authenticated. A legitimate, authenticated client (or a compromised/malicious one using valid credentials) can still send arbitrarily deep, alias-heavy, or otherwise expensive queries if nothing on the client or server constrains the query surface to a known-safe set.
      
      ## Officially grounded rules
      
      Apollo Client's own networking/authentication documentation states directly (`repo evidence` via Context7 `/apollographql/apollo-client`):
      
      - **Reset the store on logout.** The documented pattern calls `client.resetStore()` from the logout handler, immediately after the application's own logout call, specifically so the UI reflects the logged-out state and stale data from the previous session is not retained.
      - **`resetStore()` clears the cache and refetches active queries; `clearStore()` clears the cache without refetching.** Choose based on whether an immediate refetch of currently-mounted queries is desired (`resetStore()`) or whether the app is about to unmount/navigate away entirely and a refetch would be wasted work (`clearStore()`). Either call clears the previous session's normalized cache entries -- the choice is about refetch behavior, not about whether the cache gets cleared.
      - **`PersistedQueryLink` allowlists the client's query surface.** Chaining a `PersistedQueryLink` ahead of the transport link (e.g. `persistedQueryLink.concat(httpLink)`) restricts the operations the client can send to a build-time manifest of pre-registered, reviewed query documents, rather than allowing arbitrary ad hoc documents to reach the server.
      
      ## Non-negotiable design rules
      
      ### 1. Trace the logout path to its actual end, not to the redirect
      
      Do not treat a call to `navigate('/login')` or `window.location.assign('/login')` as evidence the session is cleaned up. Follow the handler line by line: does it call `resetStore()`/`clearStore()` on the client instance *before or as part of* the logout flow? A redirect with no cache-clearing call leaves the cache populated for whatever renders next in that same JS process.
      
      ### 2. A cache-clearing call elsewhere in the codebase does not clear this finding
      
      If `resetStore()` is called somewhere in the app (e.g., in a settings-reset flow unrelated to auth), that does not establish the logout handler under review clears the cache. Trace the specific handler.
      
      ### 3. Persisted-query allowlisting is a structural control, not a performance optimization
      
      Treat the absence of `PersistedQueryLink` (or an equivalent allowlist) as a security finding, not merely a caching/performance suggestion, when the endpoint accepts arbitrary client-authored documents. The finding's severity scales with reachability: a public, unauthenticated endpoint with no allowlist is higher severity than an authenticated one, but authentication alone does not clear the finding.
      
      ### 4. Sensitive fields need field-level masking, not just cache-level access control
      
      A GraphQL client cache has no user-level access control of its own -- anything normalized into it is readable by any code running in that JS context (including the devtools inspector, if enabled). The only client-side control for a sensitive field is a `typePolicies` (or equivalent) `read()` policy that strips the value before it is stored, or simply never requesting the field in the first place.
      
      ## Minimal safe implementation patterns
      
      Cache clearing on logout (`repo evidence`, Apollo Client's own authentication docs):
      
      ```javascript
      async function handleLogout(client) {
        await logoutAPI();
        await client.resetStore(); // clears cache, refetches active queries
        navigate('/login');
      }
      ```
      
      Persisted-query allowlisting on the link chain:
      
      ```javascript
      import { PersistedQueryLink } from "@apollo/client/link/persisted-queries";
      
      const persistedQueryLink = new PersistedQueryLink({
        generatePersistedQueryIdsFromManifest: () =>
          import("./persisted-query-manifest.json")
      });
      
      const client = new ApolloClient({
        link: persistedQueryLink.concat(httpLink),
        cache: new InMemoryCache()
      });
      ```
      
      Masking a sensitive field via `typePolicies`:
      
      ```javascript
      const cache = new InMemoryCache({
        typePolicies: {
          CreditCard: {
            fields: {
              cvv: {
                read() {
                  return undefined; // never persisted into the normalized cache
                }
              }
            }
          }
        }
      });
      ```
      
      ## Adversarial checklist
      
      Before clearing a logout/session-teardown handler:
      
      - Does the handler call `resetStore()` or `clearStore()` (or the client-specific equivalent) on the exact path being reviewed -- not somewhere else in the codebase?
      - Is the cache-clearing call ordered so it actually runs before the user could plausibly see stale cached data (before or immediately after the redirect, not in a code path that could be skipped)?
      - If the app supports multiple concurrent sessions/accounts (account switching without a full logout), does switching accounts also clear or scope the cache appropriately?
      
      Before clearing a query-surface finding:
      
      - Is there a `PersistedQueryLink` (or equivalent) between application code and the transport link, or can arbitrary documents reach the server?
      - If no allowlist exists, is the endpoint gated by anything else that meaningfully bounds query cost (server-side query complexity/depth limiting)? If the review scope is client-only, state explicitly that server-side limits are out of scope and unverified.
      
      Before clearing a sensitive-field finding:
      
      - Does the field have a `typePolicies` `read()` masking policy on the exact type/field being queried?
      - Is the field requested by any query in scope at all -- if not, state explicitly that it was checked and found not reachable, rather than omitting it silently.
      
    • devtools-and-auth-header-risk.md 6.1 KB
      # Devtools Exposure and CSRF-less Auth Headers
      
      Use this reference when reviewing `connectToDevTools` configuration or a `SetContextLink`/auth-header-injection link for CSRF protection.
      
      ## What people get wrong
      
      The naive assumption for devtools is:
      
      > "The Apollo Client Devtools extension only does anything if someone actually opens it, so leaving `connectToDevTools` on is harmless."
      
      Wrong. `connectToDevTools` controls whether the client *establishes the bridge* the devtools extension connects to -- it is the exposure surface, independent of whether anyone happens to have the extension open at a given moment. An unconditional `connectToDevTools: true` in a production bundle means any user (or any script running in that browser context) with the extension installed can inspect the full schema via introspection and browse the entire normalized cache, including anything a sensitive-field masking review would otherwise catch.
      
      The naive assumption for auth headers is:
      
      > "We attach the token as an `authorization` header, not a cookie, so CSRF doesn't apply to us."
      
      Partially wrong. CSRF is a risk specifically when a credential is attached to a request *automatically*, without explicit action by the calling code, most commonly via a cookie the browser sends on every same-origin (or lenient SameSite) request. If the `authorization` header is the *only* mechanism carrying the credential, and nothing else in the auth flow relies on a cookie, CSRF genuinely may not apply -- but this must be confirmed by tracing the full auth flow, not assumed from the header name alone. Many real apps use a hybrid: a cookie-backed refresh mechanism or a cookie-backed session alongside header-based bearer tokens, at which point the header alone is not the whole story.
      
      ## Officially grounded rules
      
      Apollo Client's own documentation states directly (`repo evidence` via Context7 `/apollographql/apollo-client`):
      
      - **`connectToDevTools` explicitly enables the devtools bridge**, and Apollo Client's own developer-tooling documentation shows it being set for use in production builds as an explicit, deliberate override of the client's default (development-only) behavior -- meaning the safe default is to *not* set it to an unconditional `true`.
      - **`SetContextLink` (and the older `ApolloLink`-based context-setting pattern) is the documented mechanism for attaching an `authorization` header** to every outgoing request, retrieving the token via an async lookup and merging it into `prevContext.headers`. Apollo Client's documentation demonstrates the auth-header pattern itself but does not prescribe a CSRF-token header as part of the core library -- CSRF protection is application-level responsibility layered on top of the same context-setting mechanism.
      
      ## Non-negotiable design rules
      
      ### 1. Any unconditional `connectToDevTools: true` is a finding, regardless of stated deployment target
      
      Do not accept "this is only for our staging environment" as clearing the finding unless the review can independently verify the specific file/build path never reaches production. The construct itself -- not the current deployment claim -- is what a static review evaluates.
      
      ### 2. Trace the full auth flow before ruling a CSRF finding in or out
      
      Do not judge a `SetContextLink` auth-header link in isolation. Check: is the token *ever* also carried by a cookie anywhere in this app's auth flow (a refresh-token cookie, a session cookie set alongside the bearer-token flow)? If yes, the header-only mutation-request pattern with no CSRF header is a genuine finding, because an attacker's cross-site form/fetch could ride the cookie's default browser-attached credential even though the header is not itself auto-attached. If the token is exclusively attached by explicit client-side code with no cookie-backed fallback anywhere in the flow, state this explicitly as the reason no CSRF finding applies -- do not silently omit the check.
      
      ### 3. A CSRF header must be on the same context-construction path as the auth header
      
      If an anti-CSRF header exists in the codebase but is added by a different link, a different request path, or a different client instance than the one under review, that does not clear the finding for the specific `SetContextLink` construction being reviewed.
      
      ## Minimal safe implementation patterns
      
      Devtools gated to development only (`repo evidence`, contrasted directly against Apollo Client's own production-enablement example):
      
      ```javascript
      const client = new ApolloClient({
        uri: "/graphql",
        cache: new InMemoryCache(),
        connectToDevTools: process.env.NODE_ENV === 'development'
      });
      ```
      
      Auth header with CSRF protection attached on the same path:
      
      ```typescript
      import { SetContextLink } from "@apollo/client/link/context";
      
      const withToken = new SetContextLink(async (prevContext, operation) => {
        const token = await AsyncTokenLookup();
        const csrfToken = await getCsrfToken();
        return {
          headers: {
            ...prevContext.headers,
            authorization: `Bearer ${token}`,
            'x-csrf-token': csrfToken
          }
        };
      });
      ```
      
      ## Adversarial checklist
      
      Before clearing a devtools finding:
      
      - Is `connectToDevTools` gated on an environment check that demonstrably evaluates to `false` in a production build, or is it an unconditional literal?
      - If gated, does the gate reference a value the review can actually verify (a well-known bundler env substitution) rather than a custom flag whose production value is unknown?
      
      Before clearing a CSRF finding on an auth-header link:
      
      - Does any part of this app's auth flow rely on a cookie (refresh token, session identifier) alongside or instead of the header?
      - Is there a named anti-CSRF header (e.g. `x-csrf-token`) attached on the exact same context-construction path as the `authorization` header, or only "a CSRF utility exists somewhere in this codebase"?
      - Would a state-changing GraphQL mutation succeed if only the automatically-attached credential (if any) were present, with no explicit client-side header set by an attacker's cross-site request?
      
      If any answer is unclear or reveals a gap, the finding stands at HIGH (devtools) or MEDIUM-to-HIGH (CSRF, per reachability) -- do not soften it to "worth double-checking."
      
    • workflow-and-output.md 7.8 KB
      # Review Workflow and Findings Contract
      
      Use this reference for the step-by-step review procedure and the required output shape. Load the other two references only for the specific defect class the client code under review actually raises.
      
      ## Prerequisites
      
      - Read `package.json` first to confirm which GraphQL client library (Apollo Client, urql, graphql-request, or other) and version are in use. Apollo Client APIs (`connectToDevTools`, `resetStore`, `clearStore`, `SetContextLink`, `PersistedQueryLink`) do not transfer 1:1 to other clients -- confirm the equivalent construct before applying Apollo-specific guidance to a non-Apollo codebase.
      - Identify every place the client is instantiated (`new ApolloClient({...})` or equivalent) -- a codebase may construct more than one client (e.g., an authenticated client and a public client), and each needs its own review pass.
      
      ## Workflow
      
      1. **Locate every client instantiation.** For each, read the full construction call, including every option passed.
      2. **Check `connectToDevTools`.** Is it an unconditional `true`, absent (defaulting per the client's own behavior), or gated on an environment check? See `references/devtools-and-auth-header-risk.md`.
      3. **Locate every logout / account-switch / session-teardown handler.** For each, trace whether `resetStore()` or `clearStore()` (or the equivalent for the client in use) is called on the path between the auth-invalidation call and any subsequent navigation. See `references/cache-lifecycle-and-query-surface.md`.
      4. **Trace the link chain for persisted-query allowlisting.** Locate the `link` option passed to the client. Determine whether a `PersistedQueryLink` (or equivalent allowlisting link) sits between the application code and the transport link, or whether arbitrary client-authored query documents reach the transport link unfiltered. See `references/cache-lifecycle-and-query-surface.md`.
      5. **Trace every auth-header-injection link** (`SetContextLink`, an `ApolloLink` middleware, or equivalent). Determine whether an anti-CSRF header (e.g. `x-csrf-token`) is attached alongside the `authorization` header, and whether the same auth token could also be attached automatically via a cookie-backed session. See `references/devtools-and-auth-header-risk.md`.
      6. **Enumerate cache `typePolicies` (or equivalent field-policy config) against every sensitive field reachable by a query in scope.** For fields carrying payment data, `cvv`, SSNs, tokens, or other PII, check for a masking `read()` policy that prevents the raw value from being normalized into the cache. See `references/cache-lifecycle-and-query-surface.md`.
      7. **Produce ranked findings** using the output contract below.
      
      ## Decision tree
      
      - `connectToDevTools: true` (or any construction that is unconditionally on, with no environment gate) → **HIGH** finding, devtools/introspection exposure. Cite Apollo Client's own devtools-configuration documentation (`repo evidence` via Context7 `/apollographql/apollo-client`).
      - `connectToDevTools` gated on a build-time environment check (e.g. `process.env.NODE_ENV === 'development'`) → not a finding.
      - A logout/session-teardown handler invalidates the server-side session but never calls `resetStore()`/`clearStore()` on that same path → **HIGH** finding, cross-user cache exposure risk on the client. The risk is structural (shared device, fast re-login, profile switch) regardless of whether it has been reported as an incident.
      - A logout handler calls `resetStore()`/`clearStore()` on the exact traced path → not a finding.
      - Client's link chain wires application operations directly to a transport link (`HttpLink` or equivalent) with no `PersistedQueryLink` (or equivalent allowlist) in between → **MEDIUM-to-HIGH** finding depending on reachability (public/unauthenticated endpoint is higher; requiring an authenticated session is still a finding, since a legitimate-but-malicious authenticated client can still abuse an unallowlisted surface).
      - Client's link chain includes a persisted-query allowlisting link between application code and the transport link → not a finding.
      - Auth-header-injection link attaches `authorization` with no anti-CSRF header, and the same token or an equivalent session credential can also travel automatically (cookie-backed) → **MEDIUM-to-HIGH** finding depending on whether the mutation surface is state-changing.
      - Auth-header-injection link attaches both `authorization` and an anti-CSRF header (e.g. `x-csrf-token`), or the token is exclusively attached by explicit client code with no cookie-based fallback anywhere in the auth flow (state this explicitly) → not a finding.
      - A sensitive field (payment data, `cvv`, SSN, token, other PII) reachable by an in-scope query has no masking `read()` policy in `typePolicies` (or equivalent) and is normalized into the cache as-is → **HIGH** finding, unmasked sensitive data cached -- visible in the devtools inspector for the life of the entry regardless of the current devtools-enablement finding.
      - The same sensitive field has a masking `read()` policy (or the field is never requested by any in-scope query) → not a finding, but state this explicitly rather than omitting the field from the review.
      
      ## Output contract
      
      Every response from this skill must return:
      
      1. **Scope** -- the client instantiation(s), link chain(s), cache configuration, and/or logout handler(s) reviewed.
      2. **Ranked findings** -- each with file:line, defect category (`devtools-exposure` / `cache-not-cleared` / `unallowlisted-query-surface` / `csrf-less-auth-header` / `unmasked-sensitive-field`), the concrete configuration or data-flow trace (naming every hop), and a fix sketch matching Apollo Client's documented pattern.
      3. **Cache-clearing status per logout finding** -- an explicit statement of whether `resetStore()`/`clearStore()` is present on the traced path; never infer one exists elsewhere.
      4. **Masking status per sensitive-field finding** -- an explicit statement of whether a `typePolicies` masking policy is present for that exact field; never infer downstream handling is safe.
      5. **Evidence level per finding** -- `repo evidence`, `documentation-based`, or `inference`. Label structural risk findings as structural risk explicitly -- do not imply confirmed exploitation without live evidence (e.g., a captured cross-user cache read, a load-test reproduction of query-cost abuse).
      6. **Verdict** -- approve / approve-with-notes / block.
      7. **Open questions or out-of-scope items** -- e.g., "confirming actual query-cost abuse requires live load testing against the GraphQL server, not static review," or "server-side field-level authorization is out of scope for this client-focused skill -- recommend a server-side resolver review if that surface is in question."
      
      ## When to push back
      
      Push back if the user asks to:
      
      - approve `connectToDevTools: true` because "it's only in the staging build" without a visible environment gate on that exact literal -- staging today is production tomorrow, and the construct itself is what is being reviewed,
      - approve a logout handler because "the cache clears itself on reload" -- a reload is not guaranteed (SPA navigation without a full page reload, a shared-device profile switch) and the handler under review is what must demonstrably clear it,
      - treat an authenticated-only GraphQL endpoint as immune to unallowlisted-query-surface risk -- an authenticated session does not prevent a legitimate-but-malicious client from sending expensive, unallowlisted queries,
      - clear a CSRF finding on the assumption that "the token is in a header, so cookies aren't involved" without checking whether any part of the auth flow also relies on a cookie-backed session,
      - skip masking review for a sensitive field because "we'll disable devtools in production" -- the unmasked cache entry is a data-exposure risk independent of whether devtools happen to be reachable in the environment being reviewed.
      
  • metadata.json 2.1 KB
    {
      "id": "graphql-client-security-review",
      "name": "GraphQL Client Security Review",
      "type": "skill",
      "provider": "frontend",
      "harnesses": [
        "claude-code",
        "cursor",
        "codex",
        "gemini",
        "kiro",
        "other"
      ],
      "summary": "Statically reviews Apollo Client (and urql-equivalent) GraphQL client configuration for production devtools/introspection exposure, normalized cache not cleared across user sessions, missing persisted-query allowlisting against client-driven query abuse, auth headers attached without CSRF protection, and sensitive fields normalized into the cache unmasked -- grounded in Apollo Client's own configuration, authentication, and caching documentation.",
      "source_type": "original",
      "official_docs": [
        "https://www.apollographql.com/docs/react/development-testing/developer-tooling",
        "https://www.apollographql.com/docs/react/networking/authentication",
        "https://www.apollographql.com/docs/react/api/link/apollo-link-context",
        "https://www.apollographql.com/docs/react/caching/advanced-topics",
        "https://www.apollographql.com/docs/react/api/link/persisted-queries",
        "https://owasp.org/www-project-top-ten/"
      ],
      "security_notes": "This skill's entire scope is security-critical: production-enabled devtools expose full schema introspection and the normalized cache inspector, an uncleared cache after logout is a cross-user/cross-tenant data-exposure defect, unallowlisted client-driven queries are a denial-of-service/cost-abuse vector, an auth header with no CSRF token weakens mutation-endpoint protection, and unmasked sensitive fields in the normalized cache are exposed to the devtools inspector for the life of the cache entry. Every finding in this skill defaults to HIGH severity unless proven otherwise with concrete configuration evidence. Static-review-only skill: it reads and greps GraphQL client setup, link chains, and cache configuration but never executes, builds, or runs application code, and never sends live GraphQL requests.",
      "last_verified": "2026-07-03",
      "path": "skills/frontend/graphql-client-security-review",
      "author": "github: VincentChuWaiChow",
      "version": "0.1.0"
    }
    
  • SKILL.md 9.6 KB
    ---
    name: graphql-client-security-review
    description: Statically review GraphQL client configuration (Apollo Client, and urql/similar clients by analogy) for production-enabled devtools/introspection exposure, a normalized cache left uncleared across user sessions, missing persisted-query allowlisting against client-driven query abuse, auth headers attached with no CSRF protection, and sensitive fields cached unmasked -- grounded in Apollo Client's own configuration and security-relevant documentation.
    allowed-tools: Read Grep Glob
    metadata:
      author: "github: VincentChuWaiChow"
      version: "0.1.0"
      updated: "2026-07-03"
      category: security
    ---
    
    # GraphQL Client Security Review
    
    ## Purpose
    
    Review GraphQL client setup code -- client construction, link chains, and cache configuration -- for five documented, security-critical defect classes: production-reachable devtools/schema introspection, a normalized cache that survives a user logout or account switch, client-driven query abuse with no server-side allowlist, an authorization header attached with no accompanying CSRF protection, and sensitive fields written into the normalized cache with no masking policy. This skill exists so the review stays anchored to these five concrete, greppable sinks instead of drifting into a general "is this GraphQL app well-architected" review.
    
    ## When to use
    
    Use this skill when the user asks to:
    
    - review an Apollo Client (or comparable GraphQL client) instantiation for production security posture,
    - assess whether a logout/session-switch flow correctly clears client-side GraphQL cache state,
    - investigate a report that one user's cached GraphQL data appeared in another user's session on the same device,
    - audit a GraphQL client's link chain for unallowlisted, client-driven query risk (excessive nesting, alias-based amplification, arbitrary ad hoc documents reaching the server),
    - check whether an auth-header link (`SetContextLink` or equivalent middleware) exposes GraphQL mutations to CSRF,
    - review cache `typePolicies` (or equivalent) for whether sensitive fields (payment data, `cvv`, SSNs, tokens) are masked before being normalized into the cache.
    
    Do not use this skill for:
    
    - GraphQL server-side schema/resolver authorization review (field-level permission checks, resolver-level access control) -- this skill is scoped to the client, not the server; the server-side defect class needs a different review entirely,
    - general Apollo Client / urql architecture or performance review with no security angle (cache normalization strategy quality, fetch-policy tuning, pagination design) -- use a general frontend-architecture review for that,
    - a bug that requires live traffic reproduction (capturing a real cross-user cache leak, load-testing a query-cost exploit against a live server) to confirm exploitation -- static analysis proves the structural risk in the client configuration, not that it has already been exploited in production.
    
    ## Context7 Documentation Protocol
    
    - Resolve the Apollo Client library ID with `resolve-library-id` (matched result: `/apollographql/apollo-client`) before citing any client-configuration, cache-reset, or link-chain claim.
    - `/apollographql/apollo-client` is Apollo Client's own docs-and-source repository. Use `query-docs` against it to ground claims about `connectToDevTools` behavior, `resetStore`/`clearStore` semantics, `SetContextLink` header-injection patterns, and `PersistedQueryLink` allowlisting -- these are `repo evidence` when confirmed there.
    - urql is not currently resolvable in Context7. Treat urql-specific findings as `documentation-based` (urql's own published docs) or `inference` (reasoning by analogy from Apollo Client's documented behavior), and say so explicitly -- never state a urql API detail as `repo evidence` without a Context7-confirmed source.
    - Read `package.json` first to confirm which GraphQL client (and version) is actually in use before applying any client-specific API name -- `resetStore`/`clearStore` and `SetContextLink` are Apollo Client APIs; do not apply them to a urql or graphql-request codebase without confirming the equivalent construct first.
    - If Context7 is unavailable, fall back to the `official_docs` URLs in this skill's `metadata.json` and label the claim `documentation-based, unverified against current release`.
    
    ## Lean operating rules
    
    - All five defect classes default to HIGH severity. This is a security-scoped skill: do not downgrade a production-reachable devtools finding, an uncleared cache after logout, an unallowlisted query surface, a CSRF-less auth header, or an unmasked sensitive cached field to MEDIUM just because it has not been observed exploited yet -- the risk is in the structure, not in whether someone has already hit it.
    - Trace every finding to a concrete file:line and a concrete data-flow or configuration path. A finding that says "the cache might leak" or "this could be a CSRF risk" without showing the specific `connectToDevTools` literal, the specific logout handler missing `resetStore`/`clearStore`, or the specific header-construction code is not a valid finding -- it is a guess.
    - `connectToDevTools` set to an unconditional `true` (or simply always-on with no environment gate) is a HIGH finding regardless of whether the current deployment happens to be a staging environment -- flag the structural exposure, not just today's environment. `connectToDevTools` correctly gated on a build-time environment check (development-only) is not a finding.
    - Do not approve a logout or account-switch handler unless it visibly calls `client.resetStore()` or `client.clearStore()` (or the equivalent for the client library in use) on the exact path between the auth-invalidation call and the navigation/redirect that follows. A cache-clearing call that exists elsewhere in the codebase, in a different handler, does not clear this bar for the specific logout path under review.
    - Check every link-chain construction for a `PersistedQueryLink` (or equivalent allowlisting mechanism) between the application's operations and the transport link. A client wired directly to a raw HTTP link with no persisted-query allowlist is a MEDIUM-to-HIGH finding depending on whether the endpoint is public/unauthenticated (higher) or requires an authenticated session to reach (lower, but still a finding) -- an authenticated session does not eliminate cost-abuse risk from a legitimate-but-malicious authenticated client.
    - Check every `SetContextLink`/auth-header-injection link for whether it attaches an anti-CSRF header (commonly `x-csrf-token` or an equivalent named header) alongside the `authorization` header. A token-only header construction with no CSRF header is a MEDIUM-to-HIGH finding when the same token can also travel via an automatically-attached credential (a cookie-backed session, a same-site credentialed fetch) -- if the token is exclusively attached by explicit client code with no cookie-based fallback anywhere in the auth flow, state that explicitly and do not manufacture a CSRF finding where the attack surface does not exist.
    - Check cache `typePolicies` (or equivalent field-policy mechanism) for every sensitive field type (payment data, `cvv`, SSNs, auth tokens, other PII) that flows through a query in scope. A sensitive field with no masking `read()` policy, normalized into an `InMemoryCache` (or equivalent) with no field-level exclusion, is a HIGH finding -- it is visible in the devtools cache inspector for the life of the cache entry regardless of whether devtools happen to be enabled in the current build.
    - Never execute, build, or run application code, and never send live GraphQL requests, as part of this review; this is a static-review skill (Read/Grep/Glob only).
    - Load only the reference needed for the concern in scope.
    
    ## References
    
    Load these only when needed:
    
    - [Review workflow and findings contract](references/workflow-and-output.md) -- use for the step-by-step review procedure, the five-defect-class decision tree, and the required output shape.
    - [Cache lifecycle and query-surface risk](references/cache-lifecycle-and-query-surface.md) -- load only when reviewing cache clearing on logout, `typePolicies` masking of sensitive fields, or `PersistedQueryLink` allowlisting of the client's query surface.
    - [Devtools exposure and CSRF-less auth headers](references/devtools-and-auth-header-risk.md) -- load only when reviewing `connectToDevTools` configuration or a `SetContextLink`/auth-header-injection link for CSRF protection.
    
    ## Response minimum
    
    Return, at minimum:
    
    - the client construction, link chain, cache configuration, and/or logout handler(s) in scope,
    - ranked findings with file:line evidence, defect category (`devtools-exposure`, `cache-not-cleared`, `unallowlisted-query-surface`, `csrf-less-auth-header`, or `unmasked-sensitive-field`), the concrete configuration or data-flow trace, and a fix sketch matching Apollo Client's documented pattern,
    - for every cache-clearing finding, an explicit statement of whether `resetStore()` or `clearStore()` is present on the traced logout path -- never approve on the assumption one exists elsewhere,
    - for every sensitive-field finding, an explicit statement of whether a `typePolicies` masking `read()` policy is present for that exact field -- never approve on the assumption the field is handled safely downstream,
    - evidence level per finding (`repo evidence`, `documentation-based`, or `inference`), with structural risk findings explicitly labeled as structural risk, not as confirmed-exploited,
    - verdict (approve / approve-with-notes / block),
    - open questions or scope the review could not cover (e.g., "confirming actual query-cost abuse requires live load testing against the GraphQL server, not static review of the client").
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related