Skip to content

graphql-shield has no fallbackRule → new types/fields are public-by-default #155

Description

@gennitdev

Problem

permissions.ts calls shield(rules, { debug: true, allowExternalErrors: true }) — with no fallbackRule. graphql-shield's default fallbackRule is allow, so any GraphQL type or field that isn't given an explicit rule is readable by everyone, including unauthenticated callers.

This makes the permission layer fail open: the security posture of a new type/field depends on someone remembering to add a shield rule for it. Nothing fails loudly if they don't — the field is just silently public.

Why it matters (concrete instance)

This is exactly how the ModerationProfile.User deanonymization leak happened (fixed in #154): a relationship edge back to User had no shield rule, so it inherited the implicit allow, letting anyone map a pseudonymous moderation profile to the real account. Every future relationship edge to User / Email / ModerationProfile (or any other sensitive node) has the same trapdoor.

Direction (not a trivial flip)

The safe end state is fallbackRule: deny (fail closed), so a new field must be explicitly opened rather than explicitly closed. But flipping it is not a one-line change: a large amount of the schema currently relies on the implicit allow fallback — e.g. the User block's "*": allow, ModerationProfile's "*": allow, and all the types with no block at all (Issue, Discussion, Event, Comment, Channel, ServerConfig, …). Flipping to deny without first enumerating and explicitly allowing those public read paths would break most read queries.

Suggested work

  • Enumerate the types/fields that are legitimately public and currently rely on the implicit allow.
  • Add explicit "*": allow (or field-level rules) to those types.
  • Set fallbackRule: deny in the shield(...) options.
  • Add a test asserting an unruled/typo'd field is denied (guards against regressions and proves the fallback is deny).
  • Consider a lint/CI check or a schema-vs-permissions diff that flags any type/field with no shield coverage.

Reference

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    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