PROCYON

PRC008 — Unbounded input or output

Severitylow
OWASP API Security Top 10API4:2023 Unrestricted Resource Consumption

What it catches

Places where the spec lets one request send or receive an unbounded amount of data:

  • a request string without maxLength: the whole body, a property, or the items of a list. Not reported: strings with an enum, and formats whose length is fixed (date, date-time, time, uuid, ipv4, ipv6). byte (base64) is reported: it can be any size;
  • a request array without maxItems, including a body that is a list;
  • a number that sets how much work a request makes without maximum (or 3.1's numeric exclusiveMaximum), in a request body or a query parameter (the operation's, or its path's unless the operation redefines it): a name with limit, count, repeat, retry/retries, attempt, iteration, per page, page size, batch size, max results (Portuguese: limite, repeticao, tentativa, por pagina, tamanho pagina), in any spelling (pageSize, page_size, pagesize, number_of_repeats). In a query parameter, also top, take, depth and size (?size= is a page size; a body's size usually is not). Not reported: an enum; a word that makes it a value rather than an amount of work, after it (limitPrice, retryAfter, repeatEvery, depthMeters) or before it (creditLimit, rateLimit, stockCount, viewCount). Header parameters and numbers with no type are not checked. A paging parameter without a maximum makes paging useless: ?limit=10000000;
  • a GET returning an array with no paging query parameter (limit, page, per_page, page_size, size, cursor, offset, first, after, starting_after, take, top, skip, page_token, and OData's $top / $skip), on the operation or its path.

OpenAPI 3.1 nullable types count: type: [string, "null"] is a string. Read-only fields are skipped: the client cannot send them.

It is low: it does not fail the build with the default --fail-on high.

Why it matters

Limits are the first defence against requests built to exhaust the server: a 50 MB "name", a list of ten million IDs, or one GET that reads every row of a table. Declaring them in the spec also lets gateways and generated servers reject oversized requests before your code runs.

How to fix it

name:
  type: string
  maxLength: 200
ids:
  type: array
  maxItems: 100
number_of_repeats:
  type: integer
  maximum: 5

For lists, add paging parameters with a maximum page size:

parameters:
  - name: limit
    in: query
    schema:
      type: integer
      maximum: 100
  - name: cursor
    in: query
    schema:
      type: string
      maxLength: 200

How to ignore it

If the finding is fine on purpose, write an x-procyon-ignore on the operation, with the reason. The finding is still reported, under Ignored, and no longer fails the run. See ignoring a finding.

/reports/{id}:
  get:
    x-procyon-ignore:
      - rule: PRC008
        reason: "Report size is capped by the database view"

Examples

Reported, twice: the list has no paging parameter and note has no maxLength.

/orders:
  get:
    responses:
      "200":
        content:
          application/json:
            schema:
              type: array
              items:
                type: object
  post:
    requestBody:
      content:
        application/json:
          schema:
            properties:
              note:
                type: string

Not reported: the same operations with a limit query parameter that has a maximum, and note with maxLength: 500.