REST Lens
← Back to All Rules
Rule 5

GET/POST Tunneling

Category: HTTP Methods

Description

GET and POST methods should not be used to tunnel other CRUD operations (create, update, delete) that have their own dedicated HTTP methods.

What It Checks

This rule prevents HTTP method tunneling by validating that GET and POST endpoints are not misused to perform operations that should use PUT, PATCH, or DELETE methods. It specifically checks:

  • Operation summaries on GET and POST methods for keywords like "create", "update", "delete", "remove", or "add"
  • Uses AI-powered analysis to determine if the operation description truly indicates tunneling (to avoid false positives)
  • Ensures that CRUD operations use their semantically correct HTTP methods

The rule only flags violations when:

  1. The endpoint uses GET or POST
  2. The operation summary contains CRUD operation keywords
  3. AI analysis confirms the operation is actually tunneling another HTTP method

Why It Matters

Using the correct HTTP method is fundamental to RESTful API design:

  • Semantic correctness: HTTP methods have well-defined meanings. GET should be safe and idempotent, POST should create new resources, DELETE should remove resources, etc.
  • Caching and safety: Browsers and proxies cache GET requests and treat them as safe (no side effects). Using GET for destructive operations breaks this contract.
  • Security: Tunneling DELETE operations through GET creates CSRF vulnerabilities since GET requests can be triggered by simply loading an image or clicking a link.
  • Standards compliance: Violates HTTP/1.1 specification (RFC 7231) and REST architectural constraints. Proper method usage enables middleware, load balancers, and API gateways to work correctly.

Examples

Good ✅

paths:
  /users:
    get:
      summary: List all users
      responses:
        '200':
          description: Array of users

    post:
      summary: Create a new user
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/User'
      responses:
        '201':
          description: User created

  /users/{userId}:
    get:
      summary: Get user details
      responses:
        '200':
          description: User object

    put:
      summary: Replace user
      requestBody:
        required: true
      responses:
        '200':
          description: User updated

    patch:
      summary: Update user fields
      requestBody:
        required: true
      responses:
        '200':
          description: User updated

    delete:
      summary: Delete user
      responses:
        '204':
          description: User deleted

  /users/{userId}/activate:
    post:
      summary: Activate user account
      responses:
        '200':
          description: Account activated

Bad ❌

paths:
  /users/delete/{userId}:
    get:  # Should use DELETE /users/{userId}
      summary: Delete a user
      responses:
        '200':
          description: User deleted

  /users/create:
    get:  # Should use POST /users with request body
      summary: Create a new user
      parameters:
        - name: name
          in: query
        - name: email
          in: query
      responses:
        '200':
          description: User created

  /users/{userId}/update:
    post:  # Should use PUT or PATCH /users/{userId}
      summary: Update user details
      requestBody:
        required: true
      responses:
        '200':
          description: User updated

  /users/{userId}/remove:
    post:  # Should use DELETE /users/{userId}
      summary: Remove a user from the system
      responses:
        '200':
          description: User removed

  /products/add:
    get:  # Should use POST /products
      summary: Add a new product
      parameters:
        - name: name
          in: query
        - name: price
          in: query
      responses:
        '200':
          description: Product added

Configuration

This rule has no configuration options. It is either enabled or disabled.

How to Fix

  1. Identify the true operation: Determine what the endpoint actually does (create, read, update, delete)

  2. Use the correct HTTP method:

    # Before: GET tunneling DELETE
    GET /users/delete/{userId}
    
    # After: Proper DELETE method
    DELETE /users/{userId}
    
  3. Move data from query parameters to request body (for create/update operations):

    # Before: GET tunneling POST with query params
    GET /users/create?name=John&[email protected]
    
    # After: POST with request body
    POST /users
    Content-Type: application/json
    {
      "name": "John",
      "email": "[email protected]"
    }
    
  4. Remove operation verbs from URIs:

    # Before
    POST /users/{userId}/update
    
    # After
    PATCH /users/{userId}
    # or
    PUT /users/{userId}
    
  5. For controller-style actions, if the operation doesn't map to standard CRUD, use POST to a sub-resource:

    # Acceptable for non-CRUD operations
    POST /users/{userId}/activate
    POST /users/{userId}/reset-password
    POST /orders/{orderId}/cancel
    

Related Rules

  • Rule 3: CRUDy URIs - Prevents CRUD operation names in URI paths
  • Rule 7: POST for Creation - Ensures POST is used for creating resources in collections
  • Rule 53: Create Update Return - Validates proper response codes for creation and update operations