PS Product SecurityKnowledge Base

๐Ÿงพ API Design and Contract Security

Intro: API security starts before runtime. Contract design determines what can be validated, what can be logged, what clients will assume, and how authorization mistakes get hidden or prevented.

Focus areas

  • OpenAPI quality and review
  • transport, auth, and error semantics
  • data model exposure
  • versioning and deprecation
  • contract linting in CI
  • authorization-aware contract review

Core idea

A useful API contract is not just documentation. It is a security control surface.

If the contract is weak or ambiguous, teams often try to โ€œbolt security on laterโ€ with gateway policy, WAF rules, or ad hoc handler logic. That usually creates drift between the documented API and the real API.

What older material got right

Older Swagger/OpenAPI-oriented guidance was correct about one important thing: the API definition itself is the starting point for API security.

The parts that need modernization are mostly around:

  • newer OpenAPI versions and validation workflows;
  • stronger treatment of authorization and object ownership;
  • treating the spec as a merge-gated artifact, not a documentation afterthought.

Old versus current framing

Older pattern Why it was common Better current framing
Swagger spec for docs only product teams wanted docs first OpenAPI as a reviewable security artifact
auth only in gateway config team ownership was split auth semantics in spec plus gateway enforcement
schema review only for syntax easiest way to start semantics + auth + data exposure + error behavior review
one-off API security review APIs changed more slowly spec linting and targeted authz checks on every meaningful change

Security domains to review in every contract

1. Transport

Define whether the API is intended only behind HTTPS and whether plaintext exposure is ever acceptable. In practice, the answer for public and sensitive internal APIs should be no.

2. Authentication

Make the scheme explicit:

  • OAuth 2.0 bearer token;
  • mTLS;
  • signed webhook secret;
  • internal workload identity;
  • session cookie if the API is browser-facing.

Avoid undocumented fallback auth behavior.

3. Authorization

This is where many APIs fail. Review:

  • object ownership;
  • tenant scoping;
  • admin-only routes;
  • support impersonation paths;
  • bulk operations;
  • export or report generation routes.

The contract should make it obvious which endpoints are privileged and what scope or role is expected.

4. Data validation

Define:

  • required fields;
  • enum boundaries;
  • maximum lengths;
  • numeric limits;
  • array size limits;
  • whether unknown fields are rejected.

5. Error behavior

Do not leak internals through error bodies. Prefer stable error codes and messages that help clients debug safely without exposing stack details or authorization logic.

Add business invariants to contract review

A contract can be syntactically correct and still support insecure business behavior. During review, capture:

  • forbidden state transitions;
  • object and workflow ownership assumptions;
  • negative or duplicate operations that must be rejected;
  • which values deserve stronger domain types instead of free-form strings.

This keeps API design aligned with secure-by-design thinking instead of treating the spec as syntax alone.

Practical review checklist

Ask these questions during contract review:

  • Is every operation tagged with the expected auth mechanism?
  • Are privileged routes easy to spot?
  • Are user-owned and tenant-owned objects clearly identified?
  • Are list, search, and export endpoints bounded?
  • Are file upload/download routes explicit about content type and validation?
  • Are deprecated endpoints still protected and monitored?
  • Can a reviewer tell what should be logged for sensitive operations?

Practical snippet โ€” OpenAPI security scheme

openapi: 3.1.0
info:
  title: Invoice API
  version: 1.2.0

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

security:
  - bearerAuth: []

paths:
  /v1/invoices/{invoiceId}:
    get:
      summary: Get one invoice
      operationId: getInvoice
      parameters:
        - in: path
          name: invoiceId
          required: true
          schema:
            type: string
            pattern: '^[a-f0-9-]{36}$'
      responses:
        '200':
          description: Invoice found
        '403':
          description: Caller is authenticated but not allowed to access this invoice
        '404':
          description: Invoice not found

Practical snippet โ€” Redocly rules config

apis:
  main:
    root: ./openapi.yaml

rules:
  operation-operationId: error
  security-defined: error
  no-http-verbs-in-paths: warn
  no-unused-components: warn
  path-parameters-defined: error
  spec: error

Run it with:

npx @redocly/cli lint openapi.yaml

Practical snippet โ€” authz-focused contract test example

import requests

def test_invoice_cannot_cross_tenant(api_base, tenant_a_token, tenant_b_invoice_id):
    r = requests.get(
        f"{api_base}/v1/invoices/{tenant_b_invoice_id}",
        headers={"Authorization": f"Bearer {tenant_a_token}"},
        timeout=10,
    )
    assert r.status_code in (403, 404)

This is the kind of simple targeted test that catches real authorization failures better than a pretty contract document alone.

Legacy versus current format note

Older but still common

swagger: '2.0'

Current preference

openapi: 3.1.0

Older Swagger 2.0 documents still exist and are still readable, but for new design work prefer a maintained OpenAPI workflow and lint rules that can express modern schema behavior more clearly.

Common mistakes

  • documenting auth in prose but not in the spec;
  • defining authentication and forgetting authorization;
  • allowing unbounded search, export, or batch operations;
  • treating nullable, enums, and additional fields loosely until runtime;
  • assuming the gateway will correct a weak contract.

---Author attribution: Ivan Piskunov, 2026 - Educational and defensive-engineering use.