no-unguarded-swagger
Detect SwaggerModule.setup running unconditionally in an application bootstrap
Detect
SwaggerModule.setup()running unconditionally in an application bootstrap
This rule detects SwaggerModule.setup() running unconditionally in an application bootstrap, which publishes every route, DTO shape and declared auth scheme to anonymous callers in production.
Rule Details
Swagger UI publishes every route, every DTO shape, every example payload and the declared authentication schemes. In development that is the entire point. Served from production it is a free, machine-readable map of the attack surface, at a fixed path, to anonymous callers — including the endpoints you have not finished securing yet.
Measured across ten high-star NestJS codebases: 9 of 16 SwaggerModule.setup
calls run straight-line in bootstrap() with no environment check, across 4
repositories. One of them is a code generator's main.template.ts, so the
shape is emitted into every service it produces.
OWASP Mapping
- OWASP Top 10 2021: A01:2021 - Broken Access Control
- CWE: CWE-200 - Exposure of Sensitive Information to an Unauthorized Actor
- CVSS: 5.3 (Medium)
❌ Incorrect
async function bootstrap() {
const app = await NestFactory.create(ApplicationModule);
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('/docs', app, document); // served on every boot
await app.listen(3000);
}✅ Correct
async function bootstrap() {
const app = await NestFactory.create(AppModule);
if (swaggerConfig.enabled) {
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup(swaggerConfig.path ?? 'api', app, document);
}
await app.listen(3000);
}Options
{
// Skip rule in test files (default: true)
allowInTests?: boolean;
}Scope
The rule reports only where it can see the whole bootstrap — a function that
also calls NestFactory.create. That distinction separated the 9 reportable
sites from 7 that are already correct.
Not reported:
// Any condition at all. Gating on a config flag is as correct as gating on
// NODE_ENV, and the rule has no business arguing about which.
if (config.swagger.enabled) { SwaggerModule.setup(...); }
isDev && SwaggerModule.setup(...);
// A helper taking `app`. The guard lives at the call site, in another file —
// immich, awesome-nest-boilerplate and novu all do exactly this.
export function setupSwagger(app: INestApplication) {
SwaggerModule.setup('docs', app, document);
}
// Building the document publishes nothing on its own.
const document = SwaggerModule.createDocument(app, options);When Not To Use It
- If the service is internal-only and its API surface is not sensitive.
- If Swagger is deliberately public — a documented developer portal, for instance. Disable the rule on that line rather than globally.
Known False Negatives
The guard is at the call site
Why: when Swagger is factored into a helper, whether it runs is decided by the caller, which this rule cannot see. It abstains rather than accuse.
// ❌ NOT DETECTED — and correctly so; the caller may or may not guard it
export function setupSwagger(app: INestApplication) {
SwaggerModule.setup('docs', app, document);
}Mitigation: check helper call sites by hand; there are few of them.
A condition that is always true
Why: presence of a condition is enough to abstain. Proving it can be false would mean evaluating it.
// ❌ NOT DETECTED
if (true) {
SwaggerModule.setup('docs', app, document);
}Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.