Files
gh-agentsecops-secopsagentkit/skills/appsec/api-spectral/references/custom_rules_guide.md
2025-11-29 17:51:02 +08:00

12 KiB

Spectral Custom Rules Development Guide

This guide covers creating custom security rules for Spectral to enforce organization-specific API security standards.

Table of Contents

Rule Structure

Every Spectral rule consists of:

rules:
  rule-name:
    description: Human-readable description
    severity: error|warn|info|hint
    given: JSONPath expression targeting specific parts of spec
    then:
      - field: property to check (optional)
        function: validation function
        functionOptions: function-specific options
    message: Error message shown when rule fails

Severity Levels

  • error: Critical security issues that must be fixed
  • warn: Important security recommendations
  • info: Best practices and suggestions
  • hint: Style guide and documentation improvements

JSONPath Expressions

Basic Path Selection

# Target all paths
given: $.paths[*]

# Target all GET operations
given: $.paths[*].get

# Target all HTTP methods
given: $.paths[*][get,post,put,patch,delete]

# Target security schemes
given: $.components.securitySchemes[*]

# Target all schemas
given: $.components.schemas[*]

Advanced Filters

# Filter by property value
given: $.paths[*][?(@.security)]

# Filter objects by type
given: $.components.schemas[?(@.type == 'object')]

# Filter parameters by location
given: $.paths[*][*].parameters[?(@.in == 'query')]

# Regular expression matching
given: $.paths[*][*].parameters[?(@.name =~ /^(id|.*_id)$/i)]

# Nested property access
given: $.paths[*][*].responses[?(@property >= 400)]

Built-in Functions

truthy / falsy

Check if field exists or doesn't exist:

# Require field to exist
then:
  - field: security
    function: truthy

# Require field to not exist
then:
  - field: additionalProperties
    function: falsy

pattern

Match string against regex pattern:

# Match HTTPS URLs
then:
  function: pattern
  functionOptions:
    match: "^https://"

# Ensure no sensitive terms
then:
  function: pattern
  functionOptions:
    notMatch: "(password|secret|api[_-]?key)"

enumeration

Restrict to specific values:

# Require specific auth types
then:
  field: type
  function: enumeration
  functionOptions:
    values: [apiKey, oauth2, openIdConnect]

length

Validate string/array length:

# Minimum description length
then:
  field: description
  function: length
  functionOptions:
    min: 10
    max: 500

schema

Validate against JSON Schema:

# Require specific object structure
then:
  function: schema
  functionOptions:
    schema:
      type: object
      required: [error, message]
      properties:
        error:
          type: string
        message:
          type: string

alphabetical

Ensure alphabetical ordering:

# Require alphabetically sorted tags
then:
  field: tags
  function: alphabetical

Security Rule Examples

Prevent PII in URL Parameters

no-pii-in-query-params:
  description: Query parameters must not contain PII
  severity: error
  given: $.paths[*][*].parameters[?(@.in == 'query')].name
  then:
    function: pattern
    functionOptions:
      notMatch: "(?i)(ssn|social.?security|credit.?card|password|passport|driver.?license|tax.?id|national.?id)"
  message: "Query parameter names suggest PII - use request body instead"

Require API Key for Authentication

require-api-key-security:
  description: APIs must use API key authentication
  severity: error
  given: $.components.securitySchemes
  then:
    function: schema
    functionOptions:
      schema:
        type: object
        minProperties: 1
        patternProperties:
          ".*":
            anyOf:
              - properties:
                  type:
                    const: apiKey
              - properties:
                  type:
                    const: oauth2
              - properties:
                  type:
                    const: openIdConnect
  message: "API must define apiKey, OAuth2, or OpenID Connect security"

Enforce Rate Limiting Headers

rate-limit-headers-present:
  description: Responses should include rate limit headers
  severity: warn
  given: $.paths[*][get,post,put,patch,delete].responses[?(@property == '200' || @property == '201')].headers
  then:
    function: schema
    functionOptions:
      schema:
        type: object
        anyOf:
          - required: [X-RateLimit-Limit]
          - required: [X-Rate-Limit-Limit]
  message: "Include rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining) in success responses"

Detect Missing Authorization for Sensitive Operations

sensitive-operations-require-security:
  description: Sensitive operations must have security requirements
  severity: error
  given: $.paths[*][post,put,patch,delete]
  then:
    - field: security
      function: truthy
  message: "Write operations must have security requirements defined"

Prevent Verbose Error Messages

no-verbose-error-responses:
  description: Error responses should not expose internal details
  severity: warn
  given: $.paths[*][*].responses[?(@property >= 500)].content.application/json.schema.properties
  then:
    function: schema
    functionOptions:
      schema:
        type: object
        not:
          anyOf:
            - required: [stack_trace]
            - required: [stackTrace]
            - required: [debug_info]
            - required: [internal_message]
  message: "5xx error responses should not expose stack traces or internal details"

Require Audit Fields in Schemas

require-audit-fields:
  description: Data models should include audit fields
  severity: info
  given: $.components.schemas[?(@.type == 'object' && @.properties)]
  then:
    function: schema
    functionOptions:
      schema:
        type: object
        properties:
          properties:
            type: object
            anyOf:
              - required: [created_at, updated_at]
              - required: [createdAt, updatedAt]
  message: "Consider adding audit fields (created_at, updated_at) to data models"

Detect Insecure Content Types

no-insecure-content-types:
  description: Avoid insecure content types
  severity: warn
  given: $.paths[*][*].requestBody.content
  then:
    function: schema
    functionOptions:
      schema:
        type: object
        not:
          required: [text/html, text/xml, application/x-www-form-urlencoded]
  message: "Prefer application/json over HTML, XML, or form-encoded content types"

Validate JWT Security Configuration

jwt-proper-configuration:
  description: JWT bearer authentication should be properly configured
  severity: error
  given: $.components.securitySchemes[?(@.type == 'http' && @.scheme == 'bearer')]
  then:
    - field: bearerFormat
      function: pattern
      functionOptions:
        match: "^JWT$"
  message: "Bearer authentication should specify 'JWT' as bearerFormat"

Require CORS Documentation

cors-options-documented:
  description: CORS preflight endpoints should be documented
  severity: warn
  given: $.paths[*]
  then:
    function: schema
    functionOptions:
      schema:
        type: object
        if:
          properties:
            get:
              type: object
        then:
          properties:
            options:
              type: object
              required: [responses]
  message: "Document OPTIONS method for CORS preflight requests"

Prevent Numeric IDs in URLs

prefer-uuid-over-numeric-ids:
  description: Use UUIDs instead of numeric IDs to prevent enumeration
  severity: info
  given: $.paths.*~
  then:
    function: pattern
    functionOptions:
      notMatch: "\\{id\\}|\\{.*_id\\}"
  message: "Consider using UUIDs instead of numeric IDs to prevent enumeration attacks"

Testing Custom Rules

Create Test Specifications

# test-specs/valid-auth.yaml
openapi: 3.0.0
info:
  title: Valid API
  version: 1.0.0
components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
security:
  - apiKey: []
# test-specs/invalid-auth.yaml
openapi: 3.0.0
info:
  title: Invalid API
  version: 1.0.0
components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
security:
  - basicAuth: []

Test Rules

# Test custom ruleset
spectral lint test-specs/valid-auth.yaml --ruleset .spectral-custom.yaml

# Expected: No errors

spectral lint test-specs/invalid-auth.yaml --ruleset .spectral-custom.yaml

# Expected: Error about HTTP Basic auth

Automated Testing Script

#!/bin/bash
# test-rules.sh - Test custom Spectral rules

RULESET=".spectral-custom.yaml"
TEST_DIR="test-specs"
PASS=0
FAIL=0

for spec in "$TEST_DIR"/*.yaml; do
    echo "Testing: $spec"

    if spectral lint "$spec" --ruleset "$RULESET" > /dev/null 2>&1; then
        if [[ "$spec" == *"valid"* ]]; then
            echo "  ✓ PASS (correctly validated)"
            ((PASS++))
        else
            echo "  ✗ FAIL (should have detected issues)"
            ((FAIL++))
        fi
    else
        if [[ "$spec" == *"invalid"* ]]; then
            echo "  ✓ PASS (correctly detected issues)"
            ((PASS++))
        else
            echo "  ✗ FAIL (false positive)"
            ((FAIL++))
        fi
    fi
done

echo ""
echo "Results: $PASS passed, $FAIL failed"

Best Practices

1. Start with Built-in Rules

Extend existing rulesets instead of starting from scratch:

extends: ["spectral:oas", "spectral:asyncapi"]

rules:
  # Add custom rules here
  custom-security-rule:
    # ...

2. Use Descriptive Names

Rule names should clearly indicate what they check:

# Good
no-pii-in-query-params:
require-https-servers:
jwt-bearer-format-required:

# Bad
check-params:
security-rule-1:
validate-auth:

3. Provide Actionable Messages

# Good
message: "Query parameters must not contain PII (ssn, credit_card) - use request body instead"

# Bad
message: "Invalid parameter"

4. Choose Appropriate Severity

# error - Must fix (security vulnerabilities)
severity: error

# warn - Should fix (security best practices)
severity: warn

# info - Consider fixing (recommendations)
severity: info

# hint - Nice to have (style guide)
severity: hint

5. Document Rule Rationale

rules:
  no-numeric-ids:
    description: |
      Use UUIDs instead of auto-incrementing numeric IDs in URLs to prevent
      enumeration attacks where attackers can guess valid IDs sequentially.
      This follows OWASP API Security best practices for API1:2023.
    severity: warn
    # ...

6. Use Rule Overrides for Exceptions

# Allow specific paths to violate rules
overrides:
  - files: ["**/internal-api.yaml"]
    rules:
      require-https-servers: off

  - files: ["**/admin-api.yaml"]
    rules:
      no-http-basic-auth: warn  # Downgrade to warning

7. Organize Rules by Category

# .spectral.yaml - Main ruleset
extends:
  - .spectral-auth.yaml        # Authentication rules
  - .spectral-authz.yaml       # Authorization rules
  - .spectral-data.yaml        # Data protection rules
  - .spectral-owasp.yaml       # OWASP mappings

8. Version Control Your Rulesets

# Track ruleset evolution
git log -p .spectral.yaml

# Tag stable ruleset versions
git tag -a ruleset-v1.0 -m "Production-ready security ruleset"

Additional Resources