Class: Gitlab::Middleware::JsonValidation

Inherits:
Object
  • Object
show all
Defined in:
lib/gitlab/middleware/json_validation.rb

Overview

JsonValidation middleware provides JSON request validation with configurable limits.

Features:

  • Global validation limits for all JSON requests

  • Route-specific limits that override global defaults for matching paths

  • Multiple validation modes: :enforced, :logging, :disabled

  • Configurable limits for depth, array size, hash size, total elements, and body size

Constant Summary collapse

RACK_ENV_METADATA_KEY =
"gitlab.json.validation.metadata"
COLLECT_EVENTS_PATH =
%r{
  \A/-/collect_events\z
}xi
TERRAFORM_STATE_PATH =
%r{
  \A/api/v4/projects/
  (?<id>
  [a-zA-Z0-9%-._]{1,255}
  )/terraform/state/
}xi
NPM_INSTANCE_PACKAGES_PATH =
%r{
  \A/api/v4/packages/npm/-/npm/v1/security/
  (?:(?:advisories/bulk)|(?:audits/quick))\z
}xi
NPM_GROUP_PACKAGES_PATH =
%r{
  \A/api/v4/groups/
  (?<id>
  [a-zA-Z0-9%-._]{1,255}
  )/-/packages/npm/-/npm/v1/security/
  (?:(?:advisories/bulk)|(?:audits/quick))\z
}xi
NPM_PROJECT_PACKAGES_PATH =
%r{
  \A/api/v4/projects/
  (?<id>
  [a-zA-Z0-9%-._]{1,255}
  )/packages/npm/-/npm/v1/security/
  (?:(?:advisories/bulk)|(?:audits/quick))\z
}xi
INTERNAL_API_PATH =
%r{
  \A/api/v4/internal/
}xi
DUO_WORKFLOW_PATH =
%r{
  \A/api/v4/ai/duo_workflows/workflows/
}xi
DEFAULT_LIMITS =
{
  # Rack::Utils uses a depth of 32 by default
  max_depth: ENV.fetch('GITLAB_JSON_MAX_DEPTH', 32).to_i,
  max_array_size: ENV.fetch('GITLAB_JSON_MAX_ARRAY_SIZE', 50000).to_i,
  max_hash_size: ENV.fetch('GITLAB_JSON_MAX_HASH_SIZE', 50000).to_i,
  max_total_elements: ENV.fetch('GITLAB_JSON_MAX_TOTAL_ELEMENTS', 100000).to_i,
  # Disabled by default because some endpoints upload large payloads
  max_json_size_bytes: ENV.fetch('GITLAB_JSON_MAX_JSON_SIZE_BYTES', 0).to_i,
  # Supported modes: enforced, disabled, logging
  mode: ENV.fetch('GITLAB_JSON_VALIDATION_MODE', 'enforced').downcase.to_sym
}.freeze
ROUTE_CONFIGS =
[
  # Stricter limits for collect_events endpoint
  {
    regex: COLLECT_EVENTS_PATH,
    methods: i[post],
    limits: DEFAULT_LIMITS.merge({
      max_json_size_bytes: 10.megabytes
    })
  },
  # The application setting max_terraform_state_size_bytes limits this file size already
  {
    regex: TERRAFORM_STATE_PATH,
    methods: i[post],
    limits: {
      max_depth: 64,
      max_array_size: 50000,
      max_hash_size: 50000,
      max_total_elements: 250000,
      max_json_size_bytes: 50.megabytes,
      mode: :logging
    }
  },
  # CompressedJson middleware limits NPM sizes already
  {
    regex: NPM_INSTANCE_PACKAGES_PATH,
    methods: i[post],
    limits: {
      max_depth: 32,
      max_array_size: 50000,
      max_hash_size: 50000,
      max_total_elements: 250000,
      max_json_size_bytes: 50.megabytes,
      mode: :enforced
    }
  },
  {
    regex: NPM_GROUP_PACKAGES_PATH,
    methods: i[post],
    limits: {
      max_depth: 32,
      max_array_size: 50000,
      max_hash_size: 50000,
      max_total_elements: 250000,
      max_json_size_bytes: 50.megabytes,
      mode: :enforced
    }
  },
  {
    regex: NPM_PROJECT_PACKAGES_PATH,
    methods: i[post],
    limits: {
      max_depth: 32,
      max_array_size: 50000,
      max_hash_size: 50000,
      max_total_elements: 250000,
      max_json_size_bytes: 50.megabytes,
      mode: :enforced
    }
  },
  # Internal APIs
  {
    regex: INTERNAL_API_PATH,
    methods: i[post],
    limits: {
      max_depth: 32,
      max_array_size: 50000,
      max_hash_size: 50000,
      max_total_elements: 0, # Regularly exceeds 10,000, disable for now
      max_json_size_bytes: 10.megabytes,
      mode: :enforced
    }
  },
  # Duo Workflow API
  {
    regex: DUO_WORKFLOW_PATH,
    methods: i[post],
    limits: {
      max_depth: 32,
      max_array_size: 5000,
      max_hash_size: 5000,
      max_total_elements: 0, # Regularly exceeds 10,000, disable for now
      max_json_size_bytes: 25.megabytes,
      mode: :enforced
    }
  }
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(app, options = {}) ⇒ JsonValidation

Returns a new instance of JsonValidation.



154
155
156
157
158
# File 'lib/gitlab/middleware/json_validation.rb', line 154

def initialize(app, options = {})
  @app = app
  @default_limits = options[:default_limits] ? DEFAULT_LIMITS.merge(options[:default_limits]) : DEFAULT_LIMITS
  @route_config_map = build_route_config_map(options[:route_limits])
end

Instance Method Details

#call(env) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/gitlab/middleware/json_validation.rb', line 160

def call(env)
  return @app.call(env) if global_disabled?

  request = Rack::Request.new(env)

  return @app.call(env) unless json_request?(request)

  limits = limits_for_request(request)
  return @app.call(env) if disabled_mode?(limits)

  allow_if_validated(env, request, limits)
end