{"id":47727095,"url":"https://github.com/mstuart/graphql-sentinel","last_synced_at":"2026-04-02T20:48:40.368Z","repository":{"id":346011419,"uuid":"1158314616","full_name":"mstuart/graphql-sentinel","owner":"mstuart","description":"Comprehensive GraphQL security scanner and runtime shield","archived":false,"fork":false,"pushed_at":"2026-03-21T20:22:13.000Z","size":330,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-22T09:20:15.496Z","etag":null,"topics":["apollo","depth-limit","graphql","graphql-security","graphql-yoga","rate-limit","scanner","security","shield","typescript","vulnerability-scanner"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mstuart.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-15T06:28:17.000Z","updated_at":"2026-03-21T20:20:39.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mstuart/graphql-sentinel","commit_stats":null,"previous_names":["mstuart/graphql-sentinel"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/mstuart/graphql-sentinel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mstuart%2Fgraphql-sentinel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mstuart%2Fgraphql-sentinel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mstuart%2Fgraphql-sentinel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mstuart%2Fgraphql-sentinel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mstuart","download_url":"https://codeload.github.com/mstuart/graphql-sentinel/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mstuart%2Fgraphql-sentinel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31316007,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apollo","depth-limit","graphql","graphql-security","graphql-yoga","rate-limit","scanner","security","shield","typescript","vulnerability-scanner"],"created_at":"2026-04-02T20:48:39.700Z","updated_at":"2026-04-02T20:48:40.347Z","avatar_url":"https://github.com/mstuart.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# graphql-sentinel\n\n[![CI](https://github.com/mstuart/graphql-sentinel/actions/workflows/ci.yml/badge.svg)](https://github.com/mstuart/graphql-sentinel/actions/workflows/ci.yml)\n[![npm version](https://img.shields.io/npm/v/graphql-sentinel.svg)](https://www.npmjs.com/package/graphql-sentinel)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nComprehensive GraphQL security scanner and runtime shield. Detect vulnerabilities in your GraphQL API and protect it at runtime with validation rules and rate limiting.\n\n## Quick Start\n\nScan any GraphQL endpoint for security vulnerabilities:\n\n```bash\nnpx graphql-sentinel scan https://api.example.com/graphql\n```\n\n## Installation\n\n```bash\nnpm install graphql-sentinel graphql\n```\n\n## Requirements\n\n- Node.js \u003e= 18.0.0\n- `graphql` \u003e= 16.0.0 (peer dependency)\n- `graphql-yoga` \u003e= 5.0.0 (optional, for Yoga plugin)\n- `@apollo/server` \u003e= 4.0.0 (optional, for Apollo plugin)\n- TypeScript \u003e= 5.0 (optional, for type definitions)\n\nFully written in TypeScript with complete type exports for all public APIs.\n\n## CLI Usage\n\n### Scan an endpoint\n\n```bash\n# Basic scan with terminal output\ngraphql-sentinel scan https://api.example.com/graphql\n\n# JSON output\ngraphql-sentinel scan https://api.example.com/graphql --format json\n\n# HTML report saved to file\ngraphql-sentinel scan https://api.example.com/graphql --format html --output report.html\n\n# SARIF report for GitHub Security tab\ngraphql-sentinel scan https://api.example.com/graphql --format sarif --output report.sarif.json\n\n# Security dashboard\ngraphql-sentinel scan https://api.example.com/graphql --format dashboard --output dashboard.html\n\n# With custom headers\ngraphql-sentinel scan https://api.example.com/graphql -H \"Authorization: Bearer token123\"\n\n# Run specific checks only\ngraphql-sentinel scan https://api.example.com/graphql --checks introspection,csrf,depth-limit,auth-bypass\n\n# Custom timeout per check (in ms)\ngraphql-sentinel scan https://api.example.com/graphql --timeout 15000\n```\n\nThe CLI exits with code `1` if any critical or high severity issues are found, making it suitable for CI/CD pipelines.\n\n### Start a security proxy\n\n```bash\n# Basic proxy with depth limiting\ngraphql-sentinel proxy https://upstream-api.example.com/graphql --max-depth 10\n\n# Full shield configuration\ngraphql-sentinel proxy https://upstream-api.example.com/graphql \\\n  --port 4000 \\\n  --max-depth 10 \\\n  --max-complexity 1000 \\\n  --max-aliases 15 \\\n  --disable-introspection \\\n  --rate-limit-window 60000 \\\n  --rate-limit-max 100\n\n# Forward auth headers to upstream\ngraphql-sentinel proxy https://upstream-api.example.com/graphql \\\n  -H \"X-API-Key: secret\"\n```\n\n## Security Checks\n\n| Check | Severity | Description |\n|-------|----------|-------------|\n| `introspection` | Medium | Detects if introspection is enabled, exposing the full schema |\n| `depth-limit` | High | Tests for absence of query depth limits (DoS vector) |\n| `batch-attack` | Medium | Checks if batch queries are accepted (amplification attacks) |\n| `field-suggestion` | Low | Detects field suggestions in error messages (schema enumeration) |\n| `alias-overloading` | Medium | Tests if unlimited aliases are accepted (DoS vector) |\n| `csrf` | High | Checks if queries are accepted via GET requests (CSRF risk) |\n| `auth-bypass` | High | Tests for authorization bypass by sending unauthenticated requests |\n\n### Authorization Bypass Detection\n\nThe `auth-bypass` check tests your endpoint for missing or improperly configured authorization:\n\n1. Sends a request without any auth headers\n2. Sends a request with an empty Authorization header\n3. Sends a request with an invalid Bearer token\n4. If auth headers are provided, compares authenticated vs unauthenticated responses\n\nIf any unauthenticated request returns data, it flags a potential bypass. Public APIs (no auth configured) are reported as `info` severity rather than failures.\n\n## Shield Middleware\n\nProtect your GraphQL server at runtime with validation rules.\n\n### GraphQL Yoga\n\n```typescript\nimport { createYoga, createSchema } from 'graphql-yoga';\nimport { useSentinelShield } from 'graphql-sentinel';\n\nconst yoga = createYoga({\n  schema: createSchema({ /* ... */ }),\n  plugins: [\n    useSentinelShield({\n      maxDepth: 10,\n      maxComplexity: 1000,\n      maxAliases: 15,\n      disableIntrospection: true,\n      rateLimit: { window: 60000, max: 100 },\n    }),\n  ],\n});\n```\n\n### Apollo Server\n\n```typescript\nimport { ApolloServer } from '@apollo/server';\nimport { sentinelApolloPlugin } from 'graphql-sentinel';\n\nconst server = new ApolloServer({\n  typeDefs,\n  resolvers,\n  plugins: [\n    sentinelApolloPlugin({\n      maxDepth: 10,\n      maxComplexity: 1000,\n      disableIntrospection: true,\n    }),\n  ],\n});\n```\n\n### Express Middleware\n\n```typescript\nimport express from 'express';\nimport { GraphQLSchema } from 'graphql';\nimport { sentinelMiddleware } from 'graphql-sentinel';\n\nconst app = express();\napp.use(express.json());\n\n// Apply before your GraphQL middleware\napp.use('/graphql', sentinelMiddleware(schema, {\n  maxDepth: 10,\n  maxAliases: 15,\n  disableIntrospection: true,\n}));\n```\n\n### Field-Level Authorization\n\nEnforce fine-grained authorization at the field level using GraphQL validation rules:\n\n```typescript\nimport { createShield, createFieldAuthRule } from 'graphql-sentinel';\n\nconst shield = createShield({\n  maxDepth: 10,\n  fieldAuth: {\n    rules: {\n      'Query.users': { requireAuth: true, roles: ['admin'] },\n      'Query.user': { requireAuth: true, permissions: ['read:users'] },\n      'Mutation.deleteUser': { requireAuth: true, roles: ['admin'] },\n      'User.email': { requireAuth: true },\n    },\n    extractContext: (context) =\u003e {\n      // Extract user info from your GraphQL context\n      const user = (context as any)?.user;\n      if (!user) return null;\n      return {\n        authenticated: true,\n        roles: user.roles || [],\n        permissions: user.permissions || [],\n      };\n    },\n  },\n});\n\n// Use with graphql's validate()\nconst errors = validate(schema, parse(query), shield.validationRules);\n```\n\nThe `createFieldAuthRule` can also be used standalone:\n\n```typescript\nimport { createFieldAuthRule } from 'graphql-sentinel';\n\nconst rule = createFieldAuthRule({\n  rules: {\n    'Query.sensitiveData': { requireAuth: true, roles: ['admin'] },\n  },\n  extractContext: (ctx) =\u003e /* ... */,\n});\n\n// Add to your validation rules array\nconst errors = validate(schema, document, [rule]);\n```\n\n## Proxy Mode\n\nRun graphql-sentinel as a standalone reverse proxy that enforces security rules before forwarding requests to your upstream GraphQL server:\n\n```typescript\nimport { createProxyServer, startProxy } from 'graphql-sentinel';\n\n// Quick start\nawait startProxy({\n  target: 'https://upstream-api.example.com/graphql',\n  port: 4000,\n  shield: {\n    maxDepth: 10,\n    maxComplexity: 1000,\n    maxAliases: 15,\n    disableIntrospection: true,\n    rateLimit: { window: 60000, max: 100 },\n  },\n  headers: { 'X-API-Key': 'upstream-key' },\n});\n\n// Or get the raw http.Server for custom configuration\nconst server = createProxyServer({\n  target: 'https://upstream-api.example.com/graphql',\n  port: 4000,\n  shield: { maxDepth: 10 },\n});\nserver.listen(4000);\n```\n\nThe proxy:\n- Parses and validates all incoming GraphQL queries against shield rules\n- Blocks queries that exceed depth, complexity, or alias limits\n- Blocks introspection queries when configured\n- Enforces rate limiting per client IP\n- Forwards valid queries to the upstream server\n- Handles CORS headers automatically\n- Returns `400` for blocked queries with detailed error messages\n- Returns `429` for rate-limited requests\n\n## Report Formats\n\n### Terminal\n\nANSI-colored output for terminal/CLI usage.\n\n### JSON\n\nMachine-readable JSON output of the full scan report.\n\n### HTML\n\nSelf-contained HTML report with styled results.\n\n### SARIF (Static Analysis Results Interchange Format)\n\nSARIF 2.1.0 compliant output for integration with GitHub's Security tab:\n\n```bash\ngraphql-sentinel scan https://api.example.com/graphql --format sarif --output results.sarif\n```\n\nUpload to GitHub Security tab:\n\n```yaml\n- uses: github/codeql-action/upload-sarif@v3\n  with:\n    sarif_file: results.sarif\n```\n\n### Dashboard\n\nA rich, interactive security dashboard with:\n\n- **Security posture score** (0-100) weighted by severity\n- **Executive summary** suitable for management reporting\n- **Category breakdown** (Authorization, DoS, Information Disclosure)\n- **Expandable check details** with remediation guidance\n- **Vulnerability timeline** tracking when multiple reports are provided\n- **localStorage persistence** for building history across browser sessions\n- Dark theme with professional styling, fully self-contained (no external dependencies)\n\n```bash\ngraphql-sentinel scan https://api.example.com/graphql --format dashboard --output dashboard.html\n```\n\nProgrammatic usage with multiple reports for timeline tracking:\n\n```typescript\nimport { generateDashboard, runScan } from 'graphql-sentinel';\n\nconst reports = [previousReport, currentReport];\nconst html = generateDashboard(reports, { title: 'My API Security Dashboard' });\n```\n\n## GitHub Action\n\nUse graphql-sentinel as a reusable GitHub Action in your CI/CD pipelines:\n\n```yaml\njobs:\n  security-scan:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: mstuart/graphql-sentinel/.github/actions/scan@main\n        with:\n          endpoint: 'https://api.example.com/graphql'\n          format: 'sarif'\n          fail-on-severity: 'high'\n          headers: |\n            Authorization: Bearer ${{ secrets.API_TOKEN }}\n```\n\n### Action Inputs\n\n| Input | Required | Default | Description |\n|-------|----------|---------|-------------|\n| `endpoint` | Yes | - | GraphQL endpoint URL to scan |\n| `format` | No | `terminal` | Output format (terminal, json, html, sarif) |\n| `checks` | No | all | Comma-separated list of checks to run |\n| `fail-on-severity` | No | `high` | Minimum severity to fail the build |\n| `headers` | No | - | Headers, one per line (\"Key: Value\") |\n| `timeout` | No | `10000` | Timeout per check in milliseconds |\n\n### Action Outputs\n\n| Output | Description |\n|--------|-------------|\n| `report` | Path to the generated report file |\n| `passed` | Whether the scan passed (`true`/`false`) |\n\nThe action automatically uploads the report as a build artifact named `sentinel-security-report`.\n\n## Programmatic API\n\n### Scanner\n\n```typescript\nimport { runScan } from 'graphql-sentinel';\n\nconst report = await runScan({\n  endpoint: 'https://api.example.com/graphql',\n  headers: { Authorization: 'Bearer token' },\n  checks: ['introspection', 'depth-limit', 'csrf', 'auth-bypass'],\n  timeout: 10000,\n});\n\nconsole.log(`Found ${report.summary.failed} issues`);\n```\n\n### Shield (Standalone)\n\n```typescript\nimport { createShield } from 'graphql-sentinel';\nimport { validate, parse } from 'graphql';\n\nconst shield = createShield({\n  maxDepth: 10,\n  maxComplexity: 1000,\n  maxAliases: 15,\n  disableIntrospection: true,\n  rateLimit: { window: 60000, max: 100 },\n});\n\n// Use validation rules with graphql's validate()\nconst errors = validate(schema, parse(query), shield.validationRules);\n\n// Use rate limiter\nif (shield.rateLimiter) {\n  const { allowed, remaining } = shield.rateLimiter.check(clientIp, queryCost);\n  if (!allowed) {\n    throw new Error('Rate limit exceeded');\n  }\n}\n```\n\n### Report Generation\n\n```typescript\nimport { runScan, generateReport, generateDashboard, generateSarifReport } from 'graphql-sentinel';\n\nconst report = await runScan({ endpoint: 'https://api.example.com/graphql' });\n\n// Terminal output with ANSI colors\nconsole.log(generateReport(report, 'terminal'));\n\n// JSON\nconst json = generateReport(report, 'json');\n\n// Self-contained HTML\nconst html = generateReport(report, 'html');\n\n// SARIF for GitHub Security tab\nconst sarif = generateReport(report, 'sarif');\n\n// Dashboard with timeline tracking\nconst dashboard = generateDashboard([report], { title: 'Security Dashboard' });\n```\n\n## Configuration Reference\n\n### ScannerConfig\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `endpoint` | `string` | required | GraphQL endpoint URL |\n| `headers` | `Record\u003cstring, string\u003e` | `undefined` | Custom HTTP headers |\n| `checks` | `string[]` | all checks | List of check names to run |\n| `timeout` | `number` | `10000` | Timeout per check in milliseconds |\n\n### ShieldConfig\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `maxDepth` | `number` | `undefined` | Maximum query nesting depth |\n| `maxComplexity` | `number` | `undefined` | Maximum query complexity score |\n| `maxAliases` | `number` | `undefined` | Maximum number of aliases per query |\n| `disableIntrospection` | `boolean` | `false` | Block introspection queries |\n| `costLimit` | `number` | `undefined` | Maximum query cost |\n| `rateLimit.window` | `number` | `undefined` | Rate limit window in milliseconds |\n| `rateLimit.max` | `number` | `undefined` | Maximum cost per window |\n| `fieldAuth` | `FieldAuthConfig` | `undefined` | Field-level authorization rules |\n\n### ProxyConfig\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `target` | `string` | required | Upstream GraphQL endpoint URL |\n| `port` | `number` | `4000` | Proxy listening port |\n| `shield` | `ShieldConfig` | required | Shield configuration |\n| `headers` | `Record\u003cstring, string\u003e` | `undefined` | Headers to forward to upstream |\n| `cors` | `boolean` | `true` | Enable CORS headers |\n\n### FieldAuthConfig\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `rules` | `Record\u003cstring, FieldAuthRule\u003e` | Map of `TypeName.fieldName` to auth rules |\n| `extractContext` | `(context) =\u003e UserContext \\| null` | Function to extract user context |\n\n### FieldAuthRule\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `requireAuth` | `boolean` | Whether authentication is required |\n| `roles` | `string[]` | Required roles (any match grants access) |\n| `permissions` | `string[]` | Required permissions (any match grants access) |\n\n## Comparison with graphql-armor\n\n[graphql-armor](https://github.com/Escape-Technologies/graphql-armor) is an excellent runtime-only shield. graphql-sentinel provides a broader security toolkit:\n\n| Feature | graphql-sentinel | graphql-armor |\n|---------|-----------------|---------------|\n| Runtime shield (depth, complexity, aliases) | Yes | Yes |\n| Security scanner (7 automated checks) | Yes | No |\n| CLI for CI/CD pipelines | Yes | No |\n| SARIF reports for GitHub Security tab | Yes | No |\n| Interactive security dashboard | Yes | No |\n| Reverse proxy mode | Yes | No |\n| Reusable GitHub Action | Yes | No |\n| Field-level authorization | Yes | No |\n| Express middleware | Yes | No |\n\nChoose graphql-armor if you only need runtime protection. Choose graphql-sentinel if you also want scanning, reporting, CI integration, or proxy deployment.\n\n## API Reference\n\n### Scanner\n\n- `runScan(config: ScannerConfig): Promise\u003cScanReport\u003e` - Run security checks against an endpoint\n\n### Shield\n\n- `createShield(config: ShieldConfig): Shield` - Create shield with validation rules and rate limiter\n- `createDepthLimitRule(maxDepth?: number)` - Create depth limit validation rule\n- `createComplexityRule(config?: ComplexityConfig)` - Create complexity validation rule\n- `createAliasLimitRule(maxAliases?: number)` - Create alias limit validation rule\n- `createIntrospectionControlRule()` - Create introspection blocking rule\n- `createRateLimiter(config: RateLimitConfig)` - Create sliding window rate limiter\n- `createFieldAuthRule(config: FieldAuthConfig)` - Create field-level authorization rule\n\n### Proxy\n\n- `createProxyServer(config: ProxyConfig): http.Server` - Create proxy server instance\n- `startProxy(config: ProxyConfig): Promise\u003chttp.Server\u003e` - Create and start proxy server\n\n### Plugins\n\n- `useSentinelShield(config?: ShieldConfig)` - GraphQL Yoga plugin\n- `sentinelApolloPlugin(config?: ShieldConfig)` - Apollo Server plugin\n- `sentinelMiddleware(schema, config?: ShieldConfig)` - Express middleware\n\n### Reporter\n\n- `generateReport(report: ScanReport, format: 'json' | 'terminal' | 'html' | 'sarif' | 'dashboard'): string` - Generate formatted report\n- `generateSarifReport(report: ScanReport): string` - Generate SARIF 2.1.0 report\n- `generateDashboard(reports: ScanReport[], config?): string` - Generate security dashboard\n- `calculatePostureScore(results: ScanResult[]): number` - Calculate security posture score (0-100)\n\n## Contributing\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/my-feature`)\n3. Run tests (`npm test`)\n4. Commit your changes (`git commit -am 'feat: add my feature'`)\n5. Push to the branch (`git push origin feature/my-feature`)\n6. Open a Pull Request\n\n## License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmstuart%2Fgraphql-sentinel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmstuart%2Fgraphql-sentinel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmstuart%2Fgraphql-sentinel/lists"}