Comprehensive GraphQL security scanner and runtime shield. Detect vulnerabilities in your GraphQL API and protect it at runtime with validation rules and rate limiting.
Scan any GraphQL endpoint for security vulnerabilities:
npx graphql-sentinel scan https://api.example.com/graphqlnpm install graphql-sentinel graphql- Node.js >= 18.0.0
graphql>= 16.0.0 (peer dependency)graphql-yoga>= 5.0.0 (optional, for Yoga plugin)@apollo/server>= 4.0.0 (optional, for Apollo plugin)- TypeScript >= 5.0 (optional, for type definitions)
Fully written in TypeScript with complete type exports for all public APIs.
# Basic scan with terminal output
graphql-sentinel scan https://api.example.com/graphql
# JSON output
graphql-sentinel scan https://api.example.com/graphql --format json
# HTML report saved to file
graphql-sentinel scan https://api.example.com/graphql --format html --output report.html
# SARIF report for GitHub Security tab
graphql-sentinel scan https://api.example.com/graphql --format sarif --output report.sarif.json
# Security dashboard
graphql-sentinel scan https://api.example.com/graphql --format dashboard --output dashboard.html
# With custom headers
graphql-sentinel scan https://api.example.com/graphql -H "Authorization: Bearer token123"
# Run specific checks only
graphql-sentinel scan https://api.example.com/graphql --checks introspection,csrf,depth-limit,auth-bypass
# Custom timeout per check (in ms)
graphql-sentinel scan https://api.example.com/graphql --timeout 15000The CLI exits with code 1 if any critical or high severity issues are found, making it suitable for CI/CD pipelines.
# Basic proxy with depth limiting
graphql-sentinel proxy https://upstream-api.example.com/graphql --max-depth 10
# Full shield configuration
graphql-sentinel proxy https://upstream-api.example.com/graphql \
--port 4000 \
--max-depth 10 \
--max-complexity 1000 \
--max-aliases 15 \
--disable-introspection \
--rate-limit-window 60000 \
--rate-limit-max 100
# Forward auth headers to upstream
graphql-sentinel proxy https://upstream-api.example.com/graphql \
-H "X-API-Key: secret"| Check | Severity | Description |
|---|---|---|
introspection |
Medium | Detects if introspection is enabled, exposing the full schema |
depth-limit |
High | Tests for absence of query depth limits (DoS vector) |
batch-attack |
Medium | Checks if batch queries are accepted (amplification attacks) |
field-suggestion |
Low | Detects field suggestions in error messages (schema enumeration) |
alias-overloading |
Medium | Tests if unlimited aliases are accepted (DoS vector) |
csrf |
High | Checks if queries are accepted via GET requests (CSRF risk) |
auth-bypass |
High | Tests for authorization bypass by sending unauthenticated requests |
The auth-bypass check tests your endpoint for missing or improperly configured authorization:
- Sends a request without any auth headers
- Sends a request with an empty Authorization header
- Sends a request with an invalid Bearer token
- If auth headers are provided, compares authenticated vs unauthenticated responses
If any unauthenticated request returns data, it flags a potential bypass. Public APIs (no auth configured) are reported as info severity rather than failures.
Protect your GraphQL server at runtime with validation rules.
import { createYoga, createSchema } from 'graphql-yoga';
import { useSentinelShield } from 'graphql-sentinel';
const yoga = createYoga({
schema: createSchema({ /* ... */ }),
plugins: [
useSentinelShield({
maxDepth: 10,
maxComplexity: 1000,
maxAliases: 15,
disableIntrospection: true,
rateLimit: { window: 60000, max: 100 },
}),
],
});import { ApolloServer } from '@apollo/server';
import { sentinelApolloPlugin } from 'graphql-sentinel';
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
sentinelApolloPlugin({
maxDepth: 10,
maxComplexity: 1000,
disableIntrospection: true,
}),
],
});import express from 'express';
import { GraphQLSchema } from 'graphql';
import { sentinelMiddleware } from 'graphql-sentinel';
const app = express();
app.use(express.json());
// Apply before your GraphQL middleware
app.use('/graphql', sentinelMiddleware(schema, {
maxDepth: 10,
maxAliases: 15,
disableIntrospection: true,
}));Enforce fine-grained authorization at the field level using GraphQL validation rules:
import { createShield, createFieldAuthRule } from 'graphql-sentinel';
const shield = createShield({
maxDepth: 10,
fieldAuth: {
rules: {
'Query.users': { requireAuth: true, roles: ['admin'] },
'Query.user': { requireAuth: true, permissions: ['read:users'] },
'Mutation.deleteUser': { requireAuth: true, roles: ['admin'] },
'User.email': { requireAuth: true },
},
extractContext: (context) => {
// Extract user info from your GraphQL context
const user = (context as any)?.user;
if (!user) return null;
return {
authenticated: true,
roles: user.roles || [],
permissions: user.permissions || [],
};
},
},
});
// Use with graphql's validate()
const errors = validate(schema, parse(query), shield.validationRules);The createFieldAuthRule can also be used standalone:
import { createFieldAuthRule } from 'graphql-sentinel';
const rule = createFieldAuthRule({
rules: {
'Query.sensitiveData': { requireAuth: true, roles: ['admin'] },
},
extractContext: (ctx) => /* ... */,
});
// Add to your validation rules array
const errors = validate(schema, document, [rule]);Run graphql-sentinel as a standalone reverse proxy that enforces security rules before forwarding requests to your upstream GraphQL server:
import { createProxyServer, startProxy } from 'graphql-sentinel';
// Quick start
await startProxy({
target: 'https://upstream-api.example.com/graphql',
port: 4000,
shield: {
maxDepth: 10,
maxComplexity: 1000,
maxAliases: 15,
disableIntrospection: true,
rateLimit: { window: 60000, max: 100 },
},
headers: { 'X-API-Key': 'upstream-key' },
});
// Or get the raw http.Server for custom configuration
const server = createProxyServer({
target: 'https://upstream-api.example.com/graphql',
port: 4000,
shield: { maxDepth: 10 },
});
server.listen(4000);The proxy:
- Parses and validates all incoming GraphQL queries against shield rules
- Blocks queries that exceed depth, complexity, or alias limits
- Blocks introspection queries when configured
- Enforces rate limiting per client IP
- Forwards valid queries to the upstream server
- Handles CORS headers automatically
- Returns
400for blocked queries with detailed error messages - Returns
429for rate-limited requests
ANSI-colored output for terminal/CLI usage.
Machine-readable JSON output of the full scan report.
Self-contained HTML report with styled results.
SARIF 2.1.0 compliant output for integration with GitHub's Security tab:
graphql-sentinel scan https://api.example.com/graphql --format sarif --output results.sarifUpload to GitHub Security tab:
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarifA rich, interactive security dashboard with:
- Security posture score (0-100) weighted by severity
- Executive summary suitable for management reporting
- Category breakdown (Authorization, DoS, Information Disclosure)
- Expandable check details with remediation guidance
- Vulnerability timeline tracking when multiple reports are provided
- localStorage persistence for building history across browser sessions
- Dark theme with professional styling, fully self-contained (no external dependencies)
graphql-sentinel scan https://api.example.com/graphql --format dashboard --output dashboard.htmlProgrammatic usage with multiple reports for timeline tracking:
import { generateDashboard, runScan } from 'graphql-sentinel';
const reports = [previousReport, currentReport];
const html = generateDashboard(reports, { title: 'My API Security Dashboard' });Use graphql-sentinel as a reusable GitHub Action in your CI/CD pipelines:
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: mstuart/graphql-sentinel/.github/actions/scan@main
with:
endpoint: 'https://api.example.com/graphql'
format: 'sarif'
fail-on-severity: 'high'
headers: |
Authorization: Bearer ${{ secrets.API_TOKEN }}| Input | Required | Default | Description |
|---|---|---|---|
endpoint |
Yes | - | GraphQL endpoint URL to scan |
format |
No | terminal |
Output format (terminal, json, html, sarif) |
checks |
No | all | Comma-separated list of checks to run |
fail-on-severity |
No | high |
Minimum severity to fail the build |
headers |
No | - | Headers, one per line ("Key: Value") |
timeout |
No | 10000 |
Timeout per check in milliseconds |
| Output | Description |
|---|---|
report |
Path to the generated report file |
passed |
Whether the scan passed (true/false) |
The action automatically uploads the report as a build artifact named sentinel-security-report.
import { runScan } from 'graphql-sentinel';
const report = await runScan({
endpoint: 'https://api.example.com/graphql',
headers: { Authorization: 'Bearer token' },
checks: ['introspection', 'depth-limit', 'csrf', 'auth-bypass'],
timeout: 10000,
});
console.log(`Found ${report.summary.failed} issues`);import { createShield } from 'graphql-sentinel';
import { validate, parse } from 'graphql';
const shield = createShield({
maxDepth: 10,
maxComplexity: 1000,
maxAliases: 15,
disableIntrospection: true,
rateLimit: { window: 60000, max: 100 },
});
// Use validation rules with graphql's validate()
const errors = validate(schema, parse(query), shield.validationRules);
// Use rate limiter
if (shield.rateLimiter) {
const { allowed, remaining } = shield.rateLimiter.check(clientIp, queryCost);
if (!allowed) {
throw new Error('Rate limit exceeded');
}
}import { runScan, generateReport, generateDashboard, generateSarifReport } from 'graphql-sentinel';
const report = await runScan({ endpoint: 'https://api.example.com/graphql' });
// Terminal output with ANSI colors
console.log(generateReport(report, 'terminal'));
// JSON
const json = generateReport(report, 'json');
// Self-contained HTML
const html = generateReport(report, 'html');
// SARIF for GitHub Security tab
const sarif = generateReport(report, 'sarif');
// Dashboard with timeline tracking
const dashboard = generateDashboard([report], { title: 'Security Dashboard' });| Option | Type | Default | Description |
|---|---|---|---|
endpoint |
string |
required | GraphQL endpoint URL |
headers |
Record<string, string> |
undefined |
Custom HTTP headers |
checks |
string[] |
all checks | List of check names to run |
timeout |
number |
10000 |
Timeout per check in milliseconds |
| Option | Type | Default | Description |
|---|---|---|---|
maxDepth |
number |
undefined |
Maximum query nesting depth |
maxComplexity |
number |
undefined |
Maximum query complexity score |
maxAliases |
number |
undefined |
Maximum number of aliases per query |
disableIntrospection |
boolean |
false |
Block introspection queries |
costLimit |
number |
undefined |
Maximum query cost |
rateLimit.window |
number |
undefined |
Rate limit window in milliseconds |
rateLimit.max |
number |
undefined |
Maximum cost per window |
fieldAuth |
FieldAuthConfig |
undefined |
Field-level authorization rules |
| Option | Type | Default | Description |
|---|---|---|---|
target |
string |
required | Upstream GraphQL endpoint URL |
port |
number |
4000 |
Proxy listening port |
shield |
ShieldConfig |
required | Shield configuration |
headers |
Record<string, string> |
undefined |
Headers to forward to upstream |
cors |
boolean |
true |
Enable CORS headers |
| Option | Type | Description |
|---|---|---|
rules |
Record<string, FieldAuthRule> |
Map of TypeName.fieldName to auth rules |
extractContext |
(context) => UserContext | null |
Function to extract user context |
| Option | Type | Description |
|---|---|---|
requireAuth |
boolean |
Whether authentication is required |
roles |
string[] |
Required roles (any match grants access) |
permissions |
string[] |
Required permissions (any match grants access) |
graphql-armor is an excellent runtime-only shield. graphql-sentinel provides a broader security toolkit:
| Feature | graphql-sentinel | graphql-armor |
|---|---|---|
| Runtime shield (depth, complexity, aliases) | Yes | Yes |
| Security scanner (7 automated checks) | Yes | No |
| CLI for CI/CD pipelines | Yes | No |
| SARIF reports for GitHub Security tab | Yes | No |
| Interactive security dashboard | Yes | No |
| Reverse proxy mode | Yes | No |
| Reusable GitHub Action | Yes | No |
| Field-level authorization | Yes | No |
| Express middleware | Yes | No |
Choose graphql-armor if you only need runtime protection. Choose graphql-sentinel if you also want scanning, reporting, CI integration, or proxy deployment.
runScan(config: ScannerConfig): Promise<ScanReport>- Run security checks against an endpoint
createShield(config: ShieldConfig): Shield- Create shield with validation rules and rate limitercreateDepthLimitRule(maxDepth?: number)- Create depth limit validation rulecreateComplexityRule(config?: ComplexityConfig)- Create complexity validation rulecreateAliasLimitRule(maxAliases?: number)- Create alias limit validation rulecreateIntrospectionControlRule()- Create introspection blocking rulecreateRateLimiter(config: RateLimitConfig)- Create sliding window rate limitercreateFieldAuthRule(config: FieldAuthConfig)- Create field-level authorization rule
createProxyServer(config: ProxyConfig): http.Server- Create proxy server instancestartProxy(config: ProxyConfig): Promise<http.Server>- Create and start proxy server
useSentinelShield(config?: ShieldConfig)- GraphQL Yoga pluginsentinelApolloPlugin(config?: ShieldConfig)- Apollo Server pluginsentinelMiddleware(schema, config?: ShieldConfig)- Express middleware
generateReport(report: ScanReport, format: 'json' | 'terminal' | 'html' | 'sarif' | 'dashboard'): string- Generate formatted reportgenerateSarifReport(report: ScanReport): string- Generate SARIF 2.1.0 reportgenerateDashboard(reports: ScanReport[], config?): string- Generate security dashboardcalculatePostureScore(results: ScanResult[]): number- Calculate security posture score (0-100)
- Fork the repository
- Create your feature branch (
git checkout -b feature/my-feature) - Run tests (
npm test) - Commit your changes (
git commit -am 'feat: add my feature') - Push to the branch (
git push origin feature/my-feature) - Open a Pull Request