graphql-sentinel 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +530 -0
- package/dist/cli.cjs +1834 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1811 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +1822 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +176 -0
- package/dist/index.d.ts +176 -0
- package/dist/index.js +1768 -0
- package/dist/index.js.map +1 -0
- package/package.json +95 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mark Stuart
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
# graphql-sentinel
|
|
2
|
+
|
|
3
|
+
[](https://github.com/mstuart/graphql-sentinel/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/graphql-sentinel)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
Comprehensive GraphQL security scanner and runtime shield. Detect vulnerabilities in your GraphQL API and protect it at runtime with validation rules and rate limiting.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
Scan any GraphQL endpoint for security vulnerabilities:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx graphql-sentinel scan https://api.example.com/graphql
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install graphql-sentinel graphql
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
- Node.js >= 18.0.0
|
|
26
|
+
- `graphql` >= 16.0.0 (peer dependency)
|
|
27
|
+
- `graphql-yoga` >= 5.0.0 (optional, for Yoga plugin)
|
|
28
|
+
- `@apollo/server` >= 4.0.0 (optional, for Apollo plugin)
|
|
29
|
+
- TypeScript >= 5.0 (optional, for type definitions)
|
|
30
|
+
|
|
31
|
+
Fully written in TypeScript with complete type exports for all public APIs.
|
|
32
|
+
|
|
33
|
+
## CLI Usage
|
|
34
|
+
|
|
35
|
+
### Scan an endpoint
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Basic scan with terminal output
|
|
39
|
+
graphql-sentinel scan https://api.example.com/graphql
|
|
40
|
+
|
|
41
|
+
# JSON output
|
|
42
|
+
graphql-sentinel scan https://api.example.com/graphql --format json
|
|
43
|
+
|
|
44
|
+
# HTML report saved to file
|
|
45
|
+
graphql-sentinel scan https://api.example.com/graphql --format html --output report.html
|
|
46
|
+
|
|
47
|
+
# SARIF report for GitHub Security tab
|
|
48
|
+
graphql-sentinel scan https://api.example.com/graphql --format sarif --output report.sarif.json
|
|
49
|
+
|
|
50
|
+
# Security dashboard
|
|
51
|
+
graphql-sentinel scan https://api.example.com/graphql --format dashboard --output dashboard.html
|
|
52
|
+
|
|
53
|
+
# With custom headers
|
|
54
|
+
graphql-sentinel scan https://api.example.com/graphql -H "Authorization: Bearer token123"
|
|
55
|
+
|
|
56
|
+
# Run specific checks only
|
|
57
|
+
graphql-sentinel scan https://api.example.com/graphql --checks introspection,csrf,depth-limit,auth-bypass
|
|
58
|
+
|
|
59
|
+
# Custom timeout per check (in ms)
|
|
60
|
+
graphql-sentinel scan https://api.example.com/graphql --timeout 15000
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The CLI exits with code `1` if any critical or high severity issues are found, making it suitable for CI/CD pipelines.
|
|
64
|
+
|
|
65
|
+
### Start a security proxy
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Basic proxy with depth limiting
|
|
69
|
+
graphql-sentinel proxy https://upstream-api.example.com/graphql --max-depth 10
|
|
70
|
+
|
|
71
|
+
# Full shield configuration
|
|
72
|
+
graphql-sentinel proxy https://upstream-api.example.com/graphql \
|
|
73
|
+
--port 4000 \
|
|
74
|
+
--max-depth 10 \
|
|
75
|
+
--max-complexity 1000 \
|
|
76
|
+
--max-aliases 15 \
|
|
77
|
+
--disable-introspection \
|
|
78
|
+
--rate-limit-window 60000 \
|
|
79
|
+
--rate-limit-max 100
|
|
80
|
+
|
|
81
|
+
# Forward auth headers to upstream
|
|
82
|
+
graphql-sentinel proxy https://upstream-api.example.com/graphql \
|
|
83
|
+
-H "X-API-Key: secret"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Security Checks
|
|
87
|
+
|
|
88
|
+
| Check | Severity | Description |
|
|
89
|
+
|-------|----------|-------------|
|
|
90
|
+
| `introspection` | Medium | Detects if introspection is enabled, exposing the full schema |
|
|
91
|
+
| `depth-limit` | High | Tests for absence of query depth limits (DoS vector) |
|
|
92
|
+
| `batch-attack` | Medium | Checks if batch queries are accepted (amplification attacks) |
|
|
93
|
+
| `field-suggestion` | Low | Detects field suggestions in error messages (schema enumeration) |
|
|
94
|
+
| `alias-overloading` | Medium | Tests if unlimited aliases are accepted (DoS vector) |
|
|
95
|
+
| `csrf` | High | Checks if queries are accepted via GET requests (CSRF risk) |
|
|
96
|
+
| `auth-bypass` | High | Tests for authorization bypass by sending unauthenticated requests |
|
|
97
|
+
|
|
98
|
+
### Authorization Bypass Detection
|
|
99
|
+
|
|
100
|
+
The `auth-bypass` check tests your endpoint for missing or improperly configured authorization:
|
|
101
|
+
|
|
102
|
+
1. Sends a request without any auth headers
|
|
103
|
+
2. Sends a request with an empty Authorization header
|
|
104
|
+
3. Sends a request with an invalid Bearer token
|
|
105
|
+
4. If auth headers are provided, compares authenticated vs unauthenticated responses
|
|
106
|
+
|
|
107
|
+
If any unauthenticated request returns data, it flags a potential bypass. Public APIs (no auth configured) are reported as `info` severity rather than failures.
|
|
108
|
+
|
|
109
|
+
## Shield Middleware
|
|
110
|
+
|
|
111
|
+
Protect your GraphQL server at runtime with validation rules.
|
|
112
|
+
|
|
113
|
+
### GraphQL Yoga
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { createYoga, createSchema } from 'graphql-yoga';
|
|
117
|
+
import { useSentinelShield } from 'graphql-sentinel';
|
|
118
|
+
|
|
119
|
+
const yoga = createYoga({
|
|
120
|
+
schema: createSchema({ /* ... */ }),
|
|
121
|
+
plugins: [
|
|
122
|
+
useSentinelShield({
|
|
123
|
+
maxDepth: 10,
|
|
124
|
+
maxComplexity: 1000,
|
|
125
|
+
maxAliases: 15,
|
|
126
|
+
disableIntrospection: true,
|
|
127
|
+
rateLimit: { window: 60000, max: 100 },
|
|
128
|
+
}),
|
|
129
|
+
],
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Apollo Server
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { ApolloServer } from '@apollo/server';
|
|
137
|
+
import { sentinelApolloPlugin } from 'graphql-sentinel';
|
|
138
|
+
|
|
139
|
+
const server = new ApolloServer({
|
|
140
|
+
typeDefs,
|
|
141
|
+
resolvers,
|
|
142
|
+
plugins: [
|
|
143
|
+
sentinelApolloPlugin({
|
|
144
|
+
maxDepth: 10,
|
|
145
|
+
maxComplexity: 1000,
|
|
146
|
+
disableIntrospection: true,
|
|
147
|
+
}),
|
|
148
|
+
],
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Express Middleware
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import express from 'express';
|
|
156
|
+
import { GraphQLSchema } from 'graphql';
|
|
157
|
+
import { sentinelMiddleware } from 'graphql-sentinel';
|
|
158
|
+
|
|
159
|
+
const app = express();
|
|
160
|
+
app.use(express.json());
|
|
161
|
+
|
|
162
|
+
// Apply before your GraphQL middleware
|
|
163
|
+
app.use('/graphql', sentinelMiddleware(schema, {
|
|
164
|
+
maxDepth: 10,
|
|
165
|
+
maxAliases: 15,
|
|
166
|
+
disableIntrospection: true,
|
|
167
|
+
}));
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Field-Level Authorization
|
|
171
|
+
|
|
172
|
+
Enforce fine-grained authorization at the field level using GraphQL validation rules:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
import { createShield, createFieldAuthRule } from 'graphql-sentinel';
|
|
176
|
+
|
|
177
|
+
const shield = createShield({
|
|
178
|
+
maxDepth: 10,
|
|
179
|
+
fieldAuth: {
|
|
180
|
+
rules: {
|
|
181
|
+
'Query.users': { requireAuth: true, roles: ['admin'] },
|
|
182
|
+
'Query.user': { requireAuth: true, permissions: ['read:users'] },
|
|
183
|
+
'Mutation.deleteUser': { requireAuth: true, roles: ['admin'] },
|
|
184
|
+
'User.email': { requireAuth: true },
|
|
185
|
+
},
|
|
186
|
+
extractContext: (context) => {
|
|
187
|
+
// Extract user info from your GraphQL context
|
|
188
|
+
const user = (context as any)?.user;
|
|
189
|
+
if (!user) return null;
|
|
190
|
+
return {
|
|
191
|
+
authenticated: true,
|
|
192
|
+
roles: user.roles || [],
|
|
193
|
+
permissions: user.permissions || [],
|
|
194
|
+
};
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Use with graphql's validate()
|
|
200
|
+
const errors = validate(schema, parse(query), shield.validationRules);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The `createFieldAuthRule` can also be used standalone:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { createFieldAuthRule } from 'graphql-sentinel';
|
|
207
|
+
|
|
208
|
+
const rule = createFieldAuthRule({
|
|
209
|
+
rules: {
|
|
210
|
+
'Query.sensitiveData': { requireAuth: true, roles: ['admin'] },
|
|
211
|
+
},
|
|
212
|
+
extractContext: (ctx) => /* ... */,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Add to your validation rules array
|
|
216
|
+
const errors = validate(schema, document, [rule]);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Proxy Mode
|
|
220
|
+
|
|
221
|
+
Run graphql-sentinel as a standalone reverse proxy that enforces security rules before forwarding requests to your upstream GraphQL server:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
import { createProxyServer, startProxy } from 'graphql-sentinel';
|
|
225
|
+
|
|
226
|
+
// Quick start
|
|
227
|
+
await startProxy({
|
|
228
|
+
target: 'https://upstream-api.example.com/graphql',
|
|
229
|
+
port: 4000,
|
|
230
|
+
shield: {
|
|
231
|
+
maxDepth: 10,
|
|
232
|
+
maxComplexity: 1000,
|
|
233
|
+
maxAliases: 15,
|
|
234
|
+
disableIntrospection: true,
|
|
235
|
+
rateLimit: { window: 60000, max: 100 },
|
|
236
|
+
},
|
|
237
|
+
headers: { 'X-API-Key': 'upstream-key' },
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Or get the raw http.Server for custom configuration
|
|
241
|
+
const server = createProxyServer({
|
|
242
|
+
target: 'https://upstream-api.example.com/graphql',
|
|
243
|
+
port: 4000,
|
|
244
|
+
shield: { maxDepth: 10 },
|
|
245
|
+
});
|
|
246
|
+
server.listen(4000);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The proxy:
|
|
250
|
+
- Parses and validates all incoming GraphQL queries against shield rules
|
|
251
|
+
- Blocks queries that exceed depth, complexity, or alias limits
|
|
252
|
+
- Blocks introspection queries when configured
|
|
253
|
+
- Enforces rate limiting per client IP
|
|
254
|
+
- Forwards valid queries to the upstream server
|
|
255
|
+
- Handles CORS headers automatically
|
|
256
|
+
- Returns `400` for blocked queries with detailed error messages
|
|
257
|
+
- Returns `429` for rate-limited requests
|
|
258
|
+
|
|
259
|
+
## Report Formats
|
|
260
|
+
|
|
261
|
+
### Terminal
|
|
262
|
+
|
|
263
|
+
ANSI-colored output for terminal/CLI usage.
|
|
264
|
+
|
|
265
|
+
### JSON
|
|
266
|
+
|
|
267
|
+
Machine-readable JSON output of the full scan report.
|
|
268
|
+
|
|
269
|
+
### HTML
|
|
270
|
+
|
|
271
|
+
Self-contained HTML report with styled results.
|
|
272
|
+
|
|
273
|
+
### SARIF (Static Analysis Results Interchange Format)
|
|
274
|
+
|
|
275
|
+
SARIF 2.1.0 compliant output for integration with GitHub's Security tab:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
graphql-sentinel scan https://api.example.com/graphql --format sarif --output results.sarif
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Upload to GitHub Security tab:
|
|
282
|
+
|
|
283
|
+
```yaml
|
|
284
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
285
|
+
with:
|
|
286
|
+
sarif_file: results.sarif
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Dashboard
|
|
290
|
+
|
|
291
|
+
A rich, interactive security dashboard with:
|
|
292
|
+
|
|
293
|
+
- **Security posture score** (0-100) weighted by severity
|
|
294
|
+
- **Executive summary** suitable for management reporting
|
|
295
|
+
- **Category breakdown** (Authorization, DoS, Information Disclosure)
|
|
296
|
+
- **Expandable check details** with remediation guidance
|
|
297
|
+
- **Vulnerability timeline** tracking when multiple reports are provided
|
|
298
|
+
- **localStorage persistence** for building history across browser sessions
|
|
299
|
+
- Dark theme with professional styling, fully self-contained (no external dependencies)
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
graphql-sentinel scan https://api.example.com/graphql --format dashboard --output dashboard.html
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Programmatic usage with multiple reports for timeline tracking:
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
import { generateDashboard, runScan } from 'graphql-sentinel';
|
|
309
|
+
|
|
310
|
+
const reports = [previousReport, currentReport];
|
|
311
|
+
const html = generateDashboard(reports, { title: 'My API Security Dashboard' });
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
## GitHub Action
|
|
315
|
+
|
|
316
|
+
Use graphql-sentinel as a reusable GitHub Action in your CI/CD pipelines:
|
|
317
|
+
|
|
318
|
+
```yaml
|
|
319
|
+
jobs:
|
|
320
|
+
security-scan:
|
|
321
|
+
runs-on: ubuntu-latest
|
|
322
|
+
steps:
|
|
323
|
+
- uses: mstuart/graphql-sentinel/.github/actions/scan@main
|
|
324
|
+
with:
|
|
325
|
+
endpoint: 'https://api.example.com/graphql'
|
|
326
|
+
format: 'sarif'
|
|
327
|
+
fail-on-severity: 'high'
|
|
328
|
+
headers: |
|
|
329
|
+
Authorization: Bearer ${{ secrets.API_TOKEN }}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Action Inputs
|
|
333
|
+
|
|
334
|
+
| Input | Required | Default | Description |
|
|
335
|
+
|-------|----------|---------|-------------|
|
|
336
|
+
| `endpoint` | Yes | - | GraphQL endpoint URL to scan |
|
|
337
|
+
| `format` | No | `terminal` | Output format (terminal, json, html, sarif) |
|
|
338
|
+
| `checks` | No | all | Comma-separated list of checks to run |
|
|
339
|
+
| `fail-on-severity` | No | `high` | Minimum severity to fail the build |
|
|
340
|
+
| `headers` | No | - | Headers, one per line ("Key: Value") |
|
|
341
|
+
| `timeout` | No | `10000` | Timeout per check in milliseconds |
|
|
342
|
+
|
|
343
|
+
### Action Outputs
|
|
344
|
+
|
|
345
|
+
| Output | Description |
|
|
346
|
+
|--------|-------------|
|
|
347
|
+
| `report` | Path to the generated report file |
|
|
348
|
+
| `passed` | Whether the scan passed (`true`/`false`) |
|
|
349
|
+
|
|
350
|
+
The action automatically uploads the report as a build artifact named `sentinel-security-report`.
|
|
351
|
+
|
|
352
|
+
## Programmatic API
|
|
353
|
+
|
|
354
|
+
### Scanner
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
import { runScan } from 'graphql-sentinel';
|
|
358
|
+
|
|
359
|
+
const report = await runScan({
|
|
360
|
+
endpoint: 'https://api.example.com/graphql',
|
|
361
|
+
headers: { Authorization: 'Bearer token' },
|
|
362
|
+
checks: ['introspection', 'depth-limit', 'csrf', 'auth-bypass'],
|
|
363
|
+
timeout: 10000,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
console.log(`Found ${report.summary.failed} issues`);
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Shield (Standalone)
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
import { createShield } from 'graphql-sentinel';
|
|
373
|
+
import { validate, parse } from 'graphql';
|
|
374
|
+
|
|
375
|
+
const shield = createShield({
|
|
376
|
+
maxDepth: 10,
|
|
377
|
+
maxComplexity: 1000,
|
|
378
|
+
maxAliases: 15,
|
|
379
|
+
disableIntrospection: true,
|
|
380
|
+
rateLimit: { window: 60000, max: 100 },
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// Use validation rules with graphql's validate()
|
|
384
|
+
const errors = validate(schema, parse(query), shield.validationRules);
|
|
385
|
+
|
|
386
|
+
// Use rate limiter
|
|
387
|
+
if (shield.rateLimiter) {
|
|
388
|
+
const { allowed, remaining } = shield.rateLimiter.check(clientIp, queryCost);
|
|
389
|
+
if (!allowed) {
|
|
390
|
+
throw new Error('Rate limit exceeded');
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Report Generation
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
import { runScan, generateReport, generateDashboard, generateSarifReport } from 'graphql-sentinel';
|
|
399
|
+
|
|
400
|
+
const report = await runScan({ endpoint: 'https://api.example.com/graphql' });
|
|
401
|
+
|
|
402
|
+
// Terminal output with ANSI colors
|
|
403
|
+
console.log(generateReport(report, 'terminal'));
|
|
404
|
+
|
|
405
|
+
// JSON
|
|
406
|
+
const json = generateReport(report, 'json');
|
|
407
|
+
|
|
408
|
+
// Self-contained HTML
|
|
409
|
+
const html = generateReport(report, 'html');
|
|
410
|
+
|
|
411
|
+
// SARIF for GitHub Security tab
|
|
412
|
+
const sarif = generateReport(report, 'sarif');
|
|
413
|
+
|
|
414
|
+
// Dashboard with timeline tracking
|
|
415
|
+
const dashboard = generateDashboard([report], { title: 'Security Dashboard' });
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Configuration Reference
|
|
419
|
+
|
|
420
|
+
### ScannerConfig
|
|
421
|
+
|
|
422
|
+
| Option | Type | Default | Description |
|
|
423
|
+
|--------|------|---------|-------------|
|
|
424
|
+
| `endpoint` | `string` | required | GraphQL endpoint URL |
|
|
425
|
+
| `headers` | `Record<string, string>` | `undefined` | Custom HTTP headers |
|
|
426
|
+
| `checks` | `string[]` | all checks | List of check names to run |
|
|
427
|
+
| `timeout` | `number` | `10000` | Timeout per check in milliseconds |
|
|
428
|
+
|
|
429
|
+
### ShieldConfig
|
|
430
|
+
|
|
431
|
+
| Option | Type | Default | Description |
|
|
432
|
+
|--------|------|---------|-------------|
|
|
433
|
+
| `maxDepth` | `number` | `undefined` | Maximum query nesting depth |
|
|
434
|
+
| `maxComplexity` | `number` | `undefined` | Maximum query complexity score |
|
|
435
|
+
| `maxAliases` | `number` | `undefined` | Maximum number of aliases per query |
|
|
436
|
+
| `disableIntrospection` | `boolean` | `false` | Block introspection queries |
|
|
437
|
+
| `costLimit` | `number` | `undefined` | Maximum query cost |
|
|
438
|
+
| `rateLimit.window` | `number` | `undefined` | Rate limit window in milliseconds |
|
|
439
|
+
| `rateLimit.max` | `number` | `undefined` | Maximum cost per window |
|
|
440
|
+
| `fieldAuth` | `FieldAuthConfig` | `undefined` | Field-level authorization rules |
|
|
441
|
+
|
|
442
|
+
### ProxyConfig
|
|
443
|
+
|
|
444
|
+
| Option | Type | Default | Description |
|
|
445
|
+
|--------|------|---------|-------------|
|
|
446
|
+
| `target` | `string` | required | Upstream GraphQL endpoint URL |
|
|
447
|
+
| `port` | `number` | `4000` | Proxy listening port |
|
|
448
|
+
| `shield` | `ShieldConfig` | required | Shield configuration |
|
|
449
|
+
| `headers` | `Record<string, string>` | `undefined` | Headers to forward to upstream |
|
|
450
|
+
| `cors` | `boolean` | `true` | Enable CORS headers |
|
|
451
|
+
|
|
452
|
+
### FieldAuthConfig
|
|
453
|
+
|
|
454
|
+
| Option | Type | Description |
|
|
455
|
+
|--------|------|-------------|
|
|
456
|
+
| `rules` | `Record<string, FieldAuthRule>` | Map of `TypeName.fieldName` to auth rules |
|
|
457
|
+
| `extractContext` | `(context) => UserContext \| null` | Function to extract user context |
|
|
458
|
+
|
|
459
|
+
### FieldAuthRule
|
|
460
|
+
|
|
461
|
+
| Option | Type | Description |
|
|
462
|
+
|--------|------|-------------|
|
|
463
|
+
| `requireAuth` | `boolean` | Whether authentication is required |
|
|
464
|
+
| `roles` | `string[]` | Required roles (any match grants access) |
|
|
465
|
+
| `permissions` | `string[]` | Required permissions (any match grants access) |
|
|
466
|
+
|
|
467
|
+
## Comparison with graphql-armor
|
|
468
|
+
|
|
469
|
+
[graphql-armor](https://github.com/Escape-Technologies/graphql-armor) is an excellent runtime-only shield. graphql-sentinel provides a broader security toolkit:
|
|
470
|
+
|
|
471
|
+
| Feature | graphql-sentinel | graphql-armor |
|
|
472
|
+
|---------|-----------------|---------------|
|
|
473
|
+
| Runtime shield (depth, complexity, aliases) | Yes | Yes |
|
|
474
|
+
| Security scanner (7 automated checks) | Yes | No |
|
|
475
|
+
| CLI for CI/CD pipelines | Yes | No |
|
|
476
|
+
| SARIF reports for GitHub Security tab | Yes | No |
|
|
477
|
+
| Interactive security dashboard | Yes | No |
|
|
478
|
+
| Reverse proxy mode | Yes | No |
|
|
479
|
+
| Reusable GitHub Action | Yes | No |
|
|
480
|
+
| Field-level authorization | Yes | No |
|
|
481
|
+
| Express middleware | Yes | No |
|
|
482
|
+
|
|
483
|
+
Choose graphql-armor if you only need runtime protection. Choose graphql-sentinel if you also want scanning, reporting, CI integration, or proxy deployment.
|
|
484
|
+
|
|
485
|
+
## API Reference
|
|
486
|
+
|
|
487
|
+
### Scanner
|
|
488
|
+
|
|
489
|
+
- `runScan(config: ScannerConfig): Promise<ScanReport>` - Run security checks against an endpoint
|
|
490
|
+
|
|
491
|
+
### Shield
|
|
492
|
+
|
|
493
|
+
- `createShield(config: ShieldConfig): Shield` - Create shield with validation rules and rate limiter
|
|
494
|
+
- `createDepthLimitRule(maxDepth?: number)` - Create depth limit validation rule
|
|
495
|
+
- `createComplexityRule(config?: ComplexityConfig)` - Create complexity validation rule
|
|
496
|
+
- `createAliasLimitRule(maxAliases?: number)` - Create alias limit validation rule
|
|
497
|
+
- `createIntrospectionControlRule()` - Create introspection blocking rule
|
|
498
|
+
- `createRateLimiter(config: RateLimitConfig)` - Create sliding window rate limiter
|
|
499
|
+
- `createFieldAuthRule(config: FieldAuthConfig)` - Create field-level authorization rule
|
|
500
|
+
|
|
501
|
+
### Proxy
|
|
502
|
+
|
|
503
|
+
- `createProxyServer(config: ProxyConfig): http.Server` - Create proxy server instance
|
|
504
|
+
- `startProxy(config: ProxyConfig): Promise<http.Server>` - Create and start proxy server
|
|
505
|
+
|
|
506
|
+
### Plugins
|
|
507
|
+
|
|
508
|
+
- `useSentinelShield(config?: ShieldConfig)` - GraphQL Yoga plugin
|
|
509
|
+
- `sentinelApolloPlugin(config?: ShieldConfig)` - Apollo Server plugin
|
|
510
|
+
- `sentinelMiddleware(schema, config?: ShieldConfig)` - Express middleware
|
|
511
|
+
|
|
512
|
+
### Reporter
|
|
513
|
+
|
|
514
|
+
- `generateReport(report: ScanReport, format: 'json' | 'terminal' | 'html' | 'sarif' | 'dashboard'): string` - Generate formatted report
|
|
515
|
+
- `generateSarifReport(report: ScanReport): string` - Generate SARIF 2.1.0 report
|
|
516
|
+
- `generateDashboard(reports: ScanReport[], config?): string` - Generate security dashboard
|
|
517
|
+
- `calculatePostureScore(results: ScanResult[]): number` - Calculate security posture score (0-100)
|
|
518
|
+
|
|
519
|
+
## Contributing
|
|
520
|
+
|
|
521
|
+
1. Fork the repository
|
|
522
|
+
2. Create your feature branch (`git checkout -b feature/my-feature`)
|
|
523
|
+
3. Run tests (`npm test`)
|
|
524
|
+
4. Commit your changes (`git commit -am 'feat: add my feature'`)
|
|
525
|
+
5. Push to the branch (`git push origin feature/my-feature`)
|
|
526
|
+
6. Open a Pull Request
|
|
527
|
+
|
|
528
|
+
## License
|
|
529
|
+
|
|
530
|
+
[MIT](LICENSE)
|