Skip to content
Report library
Purpose / Development

Golang Graphql Skill Security Audit

What the author says it does (original text)

Implements GraphQL APIs in Golang using gqlgen or graphql-go. Apply when building GraphQL servers, designing schemas, writing resolvers, handling subscriptions, or integrating GraphQL with existing Go HTTP services. Also apply when the codebase imports `github.com/99designs/gqlgen` or `github.com/graph-gophers/graphql-go`.

Independent security check

Security risks found

Files checked
5
Risks found
5
Could it run dangerous commands?Looks for programs run straight after downloading, remote control of your computer, and hidden commands.Risks found: 1
Medium risk

Project setup executes a remotely resolved, unpinned Go module

Source references: 1
What we found

The setup example directly runs `github.com/99designs/gqlgen init` and then obtains the tool with `@latest`. This is not a reproducible version pin and executes or introduces whatever upstream code is current at download time.

Why this matters

If the latest upstream release is compromised, incompatible, or changes over time, downloaded code runs in the development environment and may generate files or alter module dependencies.

The setup tells the agent to fetch and execute gqlgen through `go run` without a version, then explicitly adds the tool using `@latest`. If followed, the code executed or added to the project can change with upstream releases, reducing reproducibility and increasing supply-chain exposure. A user can ask the author for a reviewed, pinned version and a verification method.

references/gqlgen.md:21In the instructionsOpen original file
```bash# Bootstrap a new projectgo run github.com/99designs/gqlgen init# Pin the tool in go.mod for reproducible generation (Go 1.24+)go get -tool github.com/99designs/gqlgen@latest```
Could it expose your files or keys?Looks for uploads of files containing passwords or keys, and keys written directly in the code.Risks found: 1
Medium risk

The error sanitizer passes every gqlerror through unchanged, allowing internal-detail disclosure

Source references: 3
What we found

Although the documentation says raw internal errors must never be returned, the example directly returns anything convertible to `*gqlerror.Error` and sanitizes only other errors. The comment that it is “already formatted” is an unchecked assumption.

Why this matters

If a resolver, directive, or dependency wraps SQL text, service responses, paths, or other internal details in a gqlerror, those details may be sent directly to API clients.

The Skill correctly warns against returning internal errors, yet the presenter treats every error convertible to `*gqlerror.Error` as already formatted and returns it unchanged, without checking its message or extensions for SQL, stack, path, or other sensitive details. A gqlerror created by an internal component could bypass sanitization. Users can ask that only explicitly marked client-safe errors pass through and all others be replaced and logged server-side.

SKILL.md:158In the instructionsOpen original file
## Error HandlingNever return raw internal errors — they leak SQL messages, stack traces, or service internals to clients.```go// gqlgen — custom ErrorPresenter strips internal detailssrv.SetErrorPresenter(func(ctx context.Context, err error) *gqlerror.Error {    var gqlErr *gqlerror.Error    if errors.As(err, &gqlErr) {        return gqlErr // already formatted    }    // log internal err here    return gqlerror.Errorf("internal error") // safe client message})
Show 2 other places
references/gqlgen.md:181In the instructionsOpen original file
```gosrv.SetErrorPresenter(func(ctx context.Context, err error) *gqlerror.Error {    var gqlErr *gqlerror.Error    if errors.As(err, &gqlErr) {        return gqlErr    }    log.Ctx(ctx).Error("resolver error", "err", err)    return gqlerror.Errorf("internal server error")})
SKILL.md:160In the instructionsOpen original file
Never return raw internal errors — they leak SQL messages, stack traces, or service internals to clients.```go// gqlgen — custom ErrorPresenter strips internal detailssrv.SetErrorPresenter(func(ctx context.Context, err error) *gqlerror.Error {    var gqlErr *gqlerror.Error    if errors.As(err, &gqlErr) {        return gqlErr // already formatted    }    // log internal err here    return gqlerror.Errorf("internal error") // safe client message})
Could it delete files or keep running?Looks for broad file deletion, disk overwrites, and programs set to start automatically.No risks found
Could it bypass safety checks?Looks for skipped website security checks, excessive file access, or actions that skip your approval.Risks found: 3
Medium risk

Describing APQ as query allow-listing can lead to a false production-security decision

Source references: 3
What we found

The Skill says gqlgen's APQ extension can reject arbitrary query strings, but ordinary APQ lets clients submit and cache query hashes; it is not the same as a static list of pre-approved operations. The production example only enables an APQ cache.

Why this matters

A user may believe only approved operations are accepted while attackers can still register or submit new queries. The complexity limit remains useful, but it does not provide allow-list access control.

The document presents gqlgen's APQ extension as a production allow-list that can reject arbitrary query strings, but the production snippet only configures an APQ cache and shows no preregistration or approved-hash enforcement. A user could therefore wrongly conclude that only reviewed queries are accepted. The author should distinguish APQ from a true persisted-query allow-list and show how unknown queries are rejected.

SKILL.md:240In the instructionsOpen original file
For graph-gophers: `graphql.MaxDepth(10)` and `graphql.MaxParallelism(10)` options at `ParseSchema` time.**Query allow-listing:** in production, consider persisted queries (gqlgen APQ extension) to reject arbitrary query strings.
Show 2 other places
references/gqlgen.md:279In the instructionsOpen original file
srv.SetQueryCache(lru.New[*ast.QueryDocument](1000))if os.Getenv("ENV") != "production" {    srv.Use(extension.Introspection{})}srv.Use(extension.AutomaticPersistedQuery{Cache: lru.New[string](100)})srv.Use(extension.FixedComplexityLimit(200))```
SKILL.md:242In the instructionsOpen original file
**Query allow-listing:** in production, consider persisted queries (gqlgen APQ extension) to reject arbitrary query strings.
Medium risk

The “production” WebSocket setup omits the origin and connection-authentication controls shown elsewhere

Source references: 2
What we found

The subscription example explicitly configures `CheckOrigin` and an `InitFunc` that validates a token, but the later Production Handler Setup uses a bare `transport.Websocket` and neither retains those controls nor states that outer middleware supplies them.

Why this matters

If the production snippet is treated as a complete public configuration and no other layer provides equivalent checks, cross-site connection attempts or unauthenticated clients may reach the subscription endpoint and hold long-lived connections.

The subscription section demonstrates an origin restriction and token validation during WebSocket initialization, but the later “Production Handler Setup” enables a bare WebSocket transport without either control. If copied directly, cross-origin and subscription authentication protections may be absent; the snippet does not identify equivalent outer middleware. A user can ask for a complete production example containing `CheckOrigin`, `InitFunc`, or clearly documented equivalent controls.

references/gqlgen.md:199In the instructionsOpen original file
```gosrv.AddTransport(transport.Websocket{    KeepAlivePingInterval: 10 * time.Second,    Upgrader: websocket.Upgrader{        // Restrict to your own origin in production; true here is dev-only.        CheckOrigin: func(r *http.Request) bool {            return r.Header.Get("Origin") == "https://app.example.com"        },    },    InitFunc: func(ctx context.Context, initPayload transport.InitPayload) (context.Context, *transport.InitPayload, error) {        // auth at connection time        token := initPayload.Authorization()        user, err := validateToken(token)        if err != nil {            return ctx, nil, err        }        return context.WithValue(ctx, userKey, user), &initPayload, nil    },})
Show 1 other places
references/gqlgen.md:269In the instructionsOpen original file
## Production Handler Setup```gosrv := handler.New(es)srv.AddTransport(transport.Options{})srv.AddTransport(transport.GET{})srv.AddTransport(transport.POST{})srv.AddTransport(transport.MultipartForm{MaxUploadSize: 10 << 20, MaxMemory: 5 << 20})srv.AddTransport(transport.Websocket{KeepAlivePingInterval: 10 * time.Second})
Medium risk

Tool permissions include unrestricted git subcommands and curl destinations

Source references: 2
What we found

The permission declaration allows `Bash(git:*)` and `Bash(curl:*)` together with file writes, agents, and WebFetch. The stated GraphQL implementation purpose does not require every git-mutating operation or curl requests to arbitrary destinations.

Why this matters

If untrusted project content misdirects the Skill, or agent behavior departs from the task, these permissions could modify branches or remotes, remove untracked files, or transmit project data externally. The declaration alone does not show that such actions occurred.

This is an active capability declaration, not an example. In addition to project reads and writes, it permits any `git` subcommand, arbitrary `curl` arguments, WebFetch, and Agent use. Some network and version-control access may be legitimate for GraphQL development, but git is not restricted to read-only operations and curl has no destination limit; if invoked, these powers could alter repository state, contact remote systems, or transmit accessible data. Users can remove unnecessary permissions or require narrow read-only commands and trusted destinations.

SKILL.md:18In the instructionsOpen original file
    skill-library-version: "0.17.89"allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*) Bash(git:*) Agent WebFetch mcp__context7__resolve-library-id mcp__context7__query-docs Bash(curl:*) Bash(godig:*) Bash(gopls:*) LSP mcp__gopls__*paths:
Show 1 other places
SKILL.md:2In the instructionsOpen original file
---name: golang-graphqldescription: "Implements GraphQL APIs in Golang using gqlgen or graphql-go. Apply when building GraphQL servers, designing schemas, writing resolvers, handling subscriptions, or integrating GraphQL with existing Go HTTP services. Also apply when the codebase imports `github.com/99designs/gqlgen` or `github.com/graph-gophers/graphql-go`."user-invocable: false
Could it mislead the AI or hide text?Checks the skill instructions for requests to ignore you, influence the report, or hide text in invisible characters.No risks found
Could it change links or payment recipients without asking?Looks for forced referral or payment changes combined with instructions to hide the change.No risks found

Inside this skill

8 instruction sections

This Skill guides the design and implementation of Go GraphQL services, including schemas, resolvers, DataLoaders, authentication, subscriptions, testing, and production configuration. The supplied source consists of guidance and examples, with no bundled executable scripts.

View source
SKILL.md:2In the instructionsOpen original file
---name: golang-graphqldescription: "Implements GraphQL APIs in Golang using gqlgen or graphql-go. Apply when building GraphQL servers, designing schemas, writing resolvers, handling subscriptions, or integrating GraphQL with existing Go HTTP services. Also apply when the codebase imports `github.com/99designs/gqlgen` or `github.com/graph-gophers/graphql-go`."user-invocable: false
SKILL.md:259In the instructionsOpen original file
## Deep Dives- **[gqlgen reference](./references/gqlgen.md)** — codegen workflow, `gqlgen.yml`, DataLoaders, Federation v2, directives- **[graphql-go reference](./references/graphql-go.md)** — reflection resolver model, type mapping, tracing- **[Testing](./references/testing.md)** — gqlgen client harness, gqltesting, httptest patterns

In build and review modes, the Skill directs the assistant to launch agents that scan the user's codebase for resolver conventions or security issues, so activation may place a broader portion of the project into additional agent contexts.

View source
SKILL.md:25In the instructionsOpen original file
**Modes:**- **Build mode** — generating new schemas, resolvers, or server setup: follow the skill's sequential instructions; launch a background agent to grep for existing resolver patterns and naming conventions before generating new code.- **Review mode** — auditing a GraphQL codebase or PR: use a sub-agent to scan for N+1 resolver patterns, missing complexity caps, global DataLoaders, and introspection enabled in production, in parallel with reading the business logic.

The documentation includes explicit safeguards such as per-request DataLoaders, query-complexity limits, disabling introspection in production, and handling connection cancellation in subscriptions.

View source
SKILL.md:111In the instructionsOpen original file
Each `User.posts` resolver fires a SQL query per user without batching — O(n) DB calls for n users. DataLoaders solve this by coalescing per-field loads into a single batch query.**Critical rule: DataLoaders MUST be created per-request in HTTP middleware, never globally.** A global DataLoader caches across requests — stale data, potential cross-user data leakage.
SKILL.md:225In the instructionsOpen original file
## Performance and SafetyProduction GraphQL servers require explicit limits. Without them, a single deeply nested query exhausts CPU and memory.```go// gqlgen — wire these into every production handlersrv := handler.NewDefaultServer(es)srv.Use(extension.FixedComplexityLimit(200)) // max cost per query// Gate introspection — only in non-production environmentsif os.Getenv("ENV") != "production" {    srv.Use(extension.Introspection{})}```
Start here · InstructionsSKILL.md
golang-graphql
Lines connect the instruction file to its sections, not an observed execution order. Select a section to read the source. 4 more sections are available in the original file.

File reference map

References: 3
Files making referencesReferenced content
Lines show actual file references, not execution order. Select a node to highlight its connections and inspect the files and source locations. Dashed lines include files that still need locating.
Files and check records5 files

Coverage and gaps

Content covered in each file

These are the source ranges included in this check, not a guarantee that every issue has been resolved.

  • SKILL.mdFull text included
  • references/gqlgen.mdFull text included
  • references/graphql-go.mdFull text included
  • references/testing.mdFull text included
  • evals/evals.jsonFull text included

This report is for the version above. We read the available code and instructions without running the skill or checking extra packages it installs. This is not a promise of safety: a different version or setup may behave differently.

  • SKILL.mdInstructions
  • evals/evals.jsonSupporting file
  • references/gqlgen.mdSupporting file
  • references/graphql-go.mdSupporting file
  • references/testing.mdSupporting file

Operations mentioned in code and instructions

Connect to websites
SKILL.md:12In the instructionsOpen original file
    emoji: "🔮"    homepage: https://github.com/samber/cc-skills-golang    requires:
SKILL.md:18In the instructionsOpen original file
    skill-library-version: "0.17.89"allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*) Bash(git:*) Agent WebFetch mcp__context7__resolve-library-id mcp__context7__query-docs Bash(curl:*) Bash(godig:*) Bash(gopls:*) LSP mcp__gopls__*paths:
SKILL.md:276In the instructionsOpen original file
- [gqlgen](https://github.com/99designs/gqlgen)- [graph-gophers/graphql-go](https://github.com/graph-gophers/graphql-go)
Run commands
SKILL.md:18In the instructionsOpen original file
    skill-library-version: "0.17.89"allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*) Bash(git:*) Agent WebFetch mcp__context7__resolve-library-id mcp__context7__query-docs Bash(curl:*) Bash(godig:*) Bash(gopls:*) LSP mcp__gopls__*paths:
references/gqlgen.md:21In the instructionsOpen original file
```bash# Bootstrap a new project
references/gqlgen.md:31In the instructionsOpen original file
```bash# Regenerate after every schema change
Read keys or account settings
SKILL.md:235In the instructionsOpen original file
// Gate introspection — only in non-production environmentsif os.Getenv("ENV") != "production" {    srv.Use(extension.Introspection{})
references/gqlgen.md:280In the instructionsOpen original file
srv.SetQueryCache(lru.New[*ast.QueryDocument](1000))if os.Getenv("ENV") != "production" {    srv.Use(extension.Introspection{})
Read files
references/gqlgen.md:240In the instructionsOpen original file
Resolver receives `graphql.Upload{File io.Reader, Filename string, Size int64, ContentType string}`.
Lines read
1,208
File checksum (to compare versions)
88deffa1081d12cae29bd07b5c47867b0d832a72298a0fb1763e8c044e5b21c7