Home/Blog/Cybersecurity/API Documentation Security: Protecting Sensitive Information in OpenAPI Specs
Cybersecurity

API Documentation Security: Protecting Sensitive Information in OpenAPI Specs

Secure your API documentation by protecting sensitive endpoints, sanitizing examples, and implementing proper access controls for Swagger/OpenAPI specs.

By Inventive HQ Team
API Documentation Security: Protecting Sensitive Information in OpenAPI Specs

API documentation is essential for developers but can become a security liability if it exposes sensitive information. This guide covers securing OpenAPI/Swagger specifications and documentation portals.

Documentation Security Risks

┌─────────────────────────────────────────────────────────────────────────────┐
│                  INFORMATION DISCLOSED IN API DOCS                           │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  PUBLIC DOCS LEAKING...                    ATTACKER USES FOR...             │
│  ─────────────────────────────────────────────────────────────────────────  │
│                                                                              │
│  • Admin endpoint paths                    → Direct exploitation            │
│  • Internal endpoint patterns              → Endpoint enumeration           │
│  • Authentication mechanisms               → Auth bypass research           │
│  • Parameter validation rules              → Boundary testing               │
│  • Rate limit thresholds                   → DoS planning                   │
│  • Error message formats                   → Error-based attacks            │
│  • Data model structures                   → SQL/NoSQL injection            │
│  • Real example data (PII)                 → Social engineering             │
│  • Version numbers                         → Known CVE exploitation         │
│  • Internal hostnames                      → Network mapping                │
│                                                                              │
│  DOCUMENTATION IS RECONNAISSANCE                                            │
│  Attackers read your docs before attacking your API                        │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Tiered Documentation Strategy

Separate Specs for Different Audiences

# public-openapi.yaml - External developers
openapi: 3.1.0
info:
  title: MyApp Public API
  version: 1.0.0

paths:
  /users:
    get:
      summary: List users
      # Public endpoints only
  /products:
    get:
      summary: List products

# No admin endpoints, no internal routes
# internal-openapi.yaml - Internal developers only
openapi: 3.1.0
info:
  title: MyApp Internal API
  version: 1.0.0

paths:
  # Public endpoints
  /users:
    get:
      summary: List users

  # Internal endpoints marked
  /admin/users:
    x-internal: true
    get:
      summary: Admin user management

  /internal/metrics:
    x-internal: true
    get:
      summary: System metrics

  /debug/cache:
    x-internal: true
    delete:
      summary: Clear cache

Build-Time Filtering

// scripts/filter-openapi.ts
import { OpenAPIV3 } from 'openapi-types';
import yaml from 'js-yaml';
import fs from 'fs';

function filterInternalEndpoints(spec: OpenAPIV3.Document): OpenAPIV3.Document {
  const filtered = { ...spec, paths: {} };

  for (const [path, pathItem] of Object.entries(spec.paths || {})) {
    // Skip paths marked as internal
    if ((pathItem as any)['x-internal']) {
      continue;
    }

    // Skip individual operations marked as internal
    const filteredPathItem: OpenAPIV3.PathItemObject = {};
    for (const [method, operation] of Object.entries(pathItem || {})) {
      if (method.startsWith('x-')) continue;
      if ((operation as any)['x-internal']) continue;

      filteredPathItem[method as keyof OpenAPIV3.PathItemObject] = operation;
    }

    if (Object.keys(filteredPathItem).length > 0) {
      filtered.paths[path] = filteredPathItem;
    }
  }

  // Also filter schemas marked as internal
  if (filtered.components?.schemas) {
    for (const [name, schema] of Object.entries(filtered.components.schemas)) {
      if ((schema as any)['x-internal']) {
        delete filtered.components.schemas[name];
      }
    }
  }

  return filtered;
}

// Generate public spec
const internalSpec = yaml.load(
  fs.readFileSync('./openapi-internal.yaml', 'utf8')
) as OpenAPIV3.Document;

const publicSpec = filterInternalEndpoints(internalSpec);

fs.writeFileSync(
  './public/openapi.yaml',
  yaml.dump(publicSpec)
);

console.log('Generated filtered public OpenAPI spec');

Sanitizing Examples

Fake Data Guidelines

# ❌ BAD - Real or realistic data
examples:
  user:
    value:
      id: 12847293
      email: [email protected]
      phone: +1-555-123-4567
      ssn: "123-45-6789"
      address: "123 Main St, San Francisco, CA 94102"
      api_key: "sk_live_51ABC123DEF456..."

# ✅ GOOD - Obviously fake data
examples:
  user:
    value:
      id: 123
      email: [email protected]
      phone: "+1-555-000-0000"
      ssn: "000-00-0000"
      address: "742 Evergreen Terrace, Springfield"
      api_key: "your-api-key-here"

Automated Example Generation

import { faker } from '@faker-js/faker';

// Generate safe fake data for examples
function generateSafeExample(schema: any): any {
  if (schema.type === 'string') {
    if (schema.format === 'email') {
      return '[email protected]';
    }
    if (schema.format === 'uri') {
      return 'https://example.com/path';
    }
    if (schema.format === 'uuid') {
      return '00000000-0000-0000-0000-000000000000';
    }
    if (schema.format === 'date-time') {
      return '2025-01-01T00:00:00Z';
    }
    if (schema.enum) {
      return schema.enum[0];
    }
    return 'string';
  }

  if (schema.type === 'integer' || schema.type === 'number') {
    return schema.minimum || 0;
  }

  if (schema.type === 'boolean') {
    return true;
  }

  if (schema.type === 'array') {
    return [generateSafeExample(schema.items)];
  }

  if (schema.type === 'object') {
    const obj: any = {};
    for (const [key, propSchema] of Object.entries(schema.properties || {})) {
      obj[key] = generateSafeExample(propSchema);
    }
    return obj;
  }

  return null;
}

CI Check for Sensitive Data

// scripts/check-openapi-pii.ts
import yaml from 'js-yaml';
import fs from 'fs';

const PII_PATTERNS = [
  /\b[A-Za-z0-9._%+-]+@(?!example\.com)[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/i, // Real emails
  /\b\d{3}-\d{2}-\d{4}\b/, // SSN format (not 000-00-0000)
  /\bsk_live_\w+/, // Live Stripe keys
  /\b(password|secret|token)["']?\s*[:=]\s*["'][^"']+["']/i, // Credentials
  /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/, // Internal IPs
  /\b10\.\d+\.\d+\.\d+/, // Private IPs
  /\b192\.168\.\d+\.\d+/, // Private IPs
];

function checkForPII(obj: any, path: string = ''): string[] {
  const issues: string[] = [];

  if (typeof obj === 'string') {
    for (const pattern of PII_PATTERNS) {
      if (pattern.test(obj)) {
        issues.push(`Potential PII at ${path}: "${obj.slice(0, 50)}..."`);
      }
    }
  } else if (Array.isArray(obj)) {
    obj.forEach((item, i) => {
      issues.push(...checkForPII(item, `${path}[${i}]`));
    });
  } else if (typeof obj === 'object' && obj !== null) {
    for (const [key, value] of Object.entries(obj)) {
      issues.push(...checkForPII(value, `${path}.${key}`));
    }
  }

  return issues;
}

// Run check
const spec = yaml.load(fs.readFileSync('./openapi.yaml', 'utf8'));
const issues = checkForPII(spec);

if (issues.length > 0) {
  console.error('PII detected in OpenAPI spec:');
  issues.forEach(issue => console.error(`  - ${issue}`));
  process.exit(1);
}

console.log('No PII detected in OpenAPI spec');

Securing Swagger UI

Disable in Production

import express from 'express';
import swaggerUi from 'swagger-ui-express';
import swaggerDocument from './openapi.json';

const app = express();

// Only enable Swagger UI in development
if (process.env.NODE_ENV !== 'production') {
  app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
}

// In production, serve static docs or redirect to docs site
if (process.env.NODE_ENV === 'production') {
  app.get('/docs', (req, res) => {
    res.redirect('https://docs.example.com');
  });
}

Authenticated Swagger UI

import basicAuth from 'express-basic-auth';

// Protect Swagger UI with authentication
const swaggerAuth = basicAuth({
  users: { admin: process.env.SWAGGER_PASSWORD! },
  challenge: true,
  realm: 'API Documentation',
});

app.use('/docs', swaggerAuth, swaggerUi.serve, swaggerUi.setup(swaggerDocument));

Read-Only Mode (Disable Try It Out)

const swaggerOptions = {
  swaggerOptions: {
    supportedSubmitMethods: [], // Disable all "Try it out" methods
    // Or limit to safe methods only:
    // supportedSubmitMethods: ['get', 'head', 'options'],
  },
};

app.use(
  '/docs',
  swaggerUi.serve,
  swaggerUi.setup(swaggerDocument, swaggerOptions)
);

Access Control for Documentation

Tiered Access

interface DocsUser {
  id: string;
  tier: 'public' | 'partner' | 'internal';
}

const specs = {
  public: publicOpenApiSpec,
  partner: partnerOpenApiSpec,
  internal: internalOpenApiSpec,
};

app.get('/openapi.json', authenticate, (req, res) => {
  const user = req.user as DocsUser | undefined;

  // Default to public spec for unauthenticated
  const tier = user?.tier || 'public';
  const spec = specs[tier];

  res.json(spec);
});

app.use('/docs', authenticate, (req, res, next) => {
  const user = req.user as DocsUser | undefined;
  const tier = user?.tier || 'public';

  // Serve Swagger UI with appropriate spec
  swaggerUi.setup(specs[tier])(req, res, next);
});

Robots.txt and Noindex

# robots.txt
User-agent: *
Disallow: /docs/
Disallow: /swagger/
Disallow: /openapi.json
Disallow: /api-docs/
Disallow: /redoc/
<!-- Documentation pages -->
<meta name="robots" content="noindex, nofollow">
// Middleware to add noindex header
app.use('/docs', (req, res, next) => {
  res.setHeader('X-Robots-Tag', 'noindex, nofollow');
  next();
});

Security Scheme Documentation

Proper Security Scheme Examples

components:
  securitySchemes:
    # ✅ GOOD - Clear without over-revealing
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        JWT token obtained from /auth/login endpoint.
        Include in Authorization header: `Bearer <token>`

    # ✅ GOOD - API Key documentation
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        API key provided in your developer dashboard.
        Contact [email protected] to request access.

    # ❌ BAD - Too much detail
    OAuth2Bad:
      type: oauth2
      description: |
        OAuth2 with authorization code flow.
        Client secret is validated using HMAC-SHA256.
        Tokens are stored in Redis with 1-hour TTL.
        Bypass rate limiting by setting X-Internal: true header.

Error Message Sanitization

# OpenAPI error response documentation

components:
  schemas:
    # ✅ GOOD - Generic error without implementation details
    Error:
      type: object
      properties:
        error:
          type: string
          description: Error code
          example: "VALIDATION_ERROR"
        message:
          type: string
          description: Human-readable message
          example: "Invalid email format"
        request_id:
          type: string
          description: Correlation ID for support
          example: "req_abc123"

    # ❌ BAD - Exposes implementation
    BadError:
      type: object
      properties:
        error:
          example: "MongoError: E11000 duplicate key error"
        stack:
          example: "at UserModel.save (/app/src/models/user.js:45:12)"
        query:
          example: "SELECT * FROM users WHERE id = '1; DROP TABLE users;--'"

Documentation Review Checklist

┌─────────────────────────────────────────────────────────────────────────────┐
│                    DOCUMENTATION SECURITY CHECKLIST                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  SENSITIVE ENDPOINTS                                                        │
│  [ ] Admin endpoints not in public docs                                     │
│  [ ] Debug/test endpoints excluded                                          │
│  [ ] Internal-only routes filtered                                          │
│  [ ] Deprecated endpoints marked (not hidden)                               │
│                                                                              │
│  EXAMPLE DATA                                                               │
│  [ ] No real email addresses                                                │
│  [ ] No real phone numbers                                                  │
│  [ ] No real addresses or locations                                         │
│  [ ] No real API keys or tokens                                             │
│  [ ] No real user IDs or account numbers                                    │
│  [ ] Placeholder tokens clearly fake                                        │
│                                                                              │
│  SECURITY DETAILS                                                           │
│  [ ] No implementation details exposed                                      │
│  [ ] No version numbers of dependencies                                     │
│  [ ] No internal hostnames or IPs                                           │
│  [ ] Rate limits documented generically                                     │
│  [ ] Error formats don't reveal internals                                   │
│                                                                              │
│  ACCESS CONTROL                                                             │
│  [ ] Swagger UI disabled or protected in prod                               │
│  [ ] OpenAPI spec access controlled                                         │
│  [ ] Documentation pages have noindex                                       │
│  [ ] robots.txt blocks crawlers                                             │
│                                                                              │
│  REVIEW PROCESS                                                             │
│  [ ] Security review before publishing                                      │
│  [ ] CI checks for PII in examples                                          │
│  [ ] Separate specs for different audiences                                 │
│  [ ] Regular audits of published docs                                       │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Best Practices

  1. Separate specs - Different detail levels for different audiences
  2. Filter at build time - Remove internal endpoints before publishing
  3. Fake examples - Obviously fake data in all examples
  4. Automate checks - CI pipeline scans for PII
  5. Protect Swagger UI - Auth or disable in production
  6. Block crawlers - robots.txt and noindex
  7. Minimal security details - Document what, not how
  8. Review process - Security review before publishing
  9. Version consistency - Docs match deployed API version
  10. Monitor exposure - Alert on indexed documentation pages

Next Steps

Frequently Asked Questions

Find answers to common questions

API documentation can expose sensitive information including internal endpoint structures, authentication mechanisms and their weaknesses, data models revealing business logic, admin/debug endpoints, valid parameter ranges for fuzzing, example data containing real information, error message formats useful for exploitation, and rate limit thresholds. Attackers use documentation for reconnaissance before attacking.

It depends on your API type. Public APIs (developer platforms) need public docs for adoption. Internal APIs should have private docs behind authentication. Even for public APIs, consider tiered access—basic docs public, detailed admin endpoints authenticated. Never expose internal-only endpoints in public docs. Use separate OpenAPI specs for public vs internal documentation.

Maintain separate OpenAPI specs for different audiences: public spec excludes admin/internal endpoints, partner spec includes more detail, internal spec has everything. Use spec filtering at build time or runtime to strip sensitive operations. Mark internal endpoints with x-internal extension and filter before publishing. Never rely on documentation hiding for security—still implement proper authorization.

Never include: real API keys, tokens, or credentials; actual customer data or PII; internal IP addresses or hostnames; database schemas or connection strings; specific version numbers of vulnerable dependencies; detailed error messages showing stack traces; rate limit bypass techniques; and security control implementation details. Use clearly fake placeholder values.

Use obviously fake data: [email protected] (not real emails), 555-xxx-xxxx phone numbers, fake addresses from TV shows, sequential IDs (123, 456), lorem ipsum for text. For tokens, use clearly invalid formats like "your-api-key-here". Never copy-paste from production. Automate example generation from schemas with faker libraries. Review examples in CI/CD for PII patterns.

For public APIs, exposing /openapi.json is common and helps developers. For internal APIs, protect it behind authentication or don't expose it at all. Consider: does the spec reveal more than your public docs? Internal specs often contain sensitive details. If you expose the spec, ensure it's the public-filtered version, not your complete internal spec.

Options: disable Swagger UI entirely in production (serve static docs instead), protect Swagger UI behind authentication, use a separate documentation subdomain with access controls, or limit Swagger UI to non-sensitive endpoints only. Never expose Swagger UI's "Try it out" feature authenticated as a privileged user—attackers can execute operations.

Mark deprecated endpoints clearly with deprecated: true in OpenAPI. Include deprecation date and sunset date in description. Point to replacement endpoints. Keep deprecated endpoints in docs until fully sunset so existing users can reference them. After sunset, remove from public docs but keep in internal archives. Never silently remove documentation.

The security schemes section documents authentication requirements—OAuth flows, API key locations, bearer tokens. It helps developers implement auth correctly (reducing insecure implementations) and enables automated security testing tools to authenticate properly. Don't over-document—specify what's needed without revealing implementation vulnerabilities or bypass techniques.

Add robots.txt rules to block crawlers from /docs and /swagger paths. Use noindex meta tags on documentation pages. Require authentication to access docs (authenticated pages aren't indexed). Use non-guessable URLs if docs must be "public but obscure." Monitor search results for your API paths and request removal of any indexed sensitive pages.

Don't wait for a breach to act

Get a free security assessment. Our experts will identify your vulnerabilities and create a protection plan tailored to your business.