puppetlabs/puppet · error · Puppet::Network::HTTP::Error::HTTPBadRequestError

The indirection name must be purely alphanumeric, not '%{ind

Error message

The indirection name must be purely alphanumeric, not '%{indirection_name}'

What it means

Puppet's HTTP layer routes requests as /:prefix/:version/:indirection/:key (the URI is split on '/' and the third segment is the indirection name, e.g. 'catalog' or 'facts'). Before any lookup, uri2indirection requires that segment to match /^\w+$/ — letters, digits and underscore only. A segment containing dashes, dots or spaces (encoded or not), or a segment shifted by double slashes, yields HTTP 400 with this message rather than a 404.

Source

Thrown at lib/puppet/network/http/api/indirected_routes.rb:65

      trusted_information: Puppet::Context::TrustedInformation.remote(params[:authenticated], params[:node], certificate),
    }
    if params[:environment]
      overrides[:current_environment] = params[:environment]
    end

    Puppet.override(overrides) do
      send("do_#{method}", indirection, key, params, request, response)
    end
  end

  def uri2indirection(http_method, uri, params)
    # the first field is always nil because of the leading slash,
    indirection_type, version, indirection_name, key = uri.split("/", 5)[1..]
    url_prefix = "/#{indirection_type}/#{version}"
    environment = params.delete(:environment)

    if indirection_name !~ /^\w+$/
      raise Puppet::Network::HTTP::Error::HTTPBadRequestError, _("The indirection name must be purely alphanumeric, not '%{indirection_name}'") % { indirection_name: indirection_name }
    end

    # this also depluralizes the indirection_name if it is a search
    method = indirection_method(http_method, indirection_name)

    # check whether this indirection matches the prefix and version in the
    # request
    if url_prefix != IndirectionType.url_prefix_for(indirection_name)
      raise Puppet::Network::HTTP::Error::HTTPBadRequestError, _("Indirection '%{indirection_name}' does not match url prefix '%{url_prefix}'") % { indirection_name: indirection_name, url_prefix: url_prefix }
    end

    indirection = Puppet::Indirector::Indirection.instance(indirection_name.to_sym)
    unless indirection
      raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(
        _("Could not find indirection '%{indirection_name}'") % { indirection_name: indirection_name },
        Puppet::Network::HTTP::Issues::HANDLER_NOT_FOUND
      )
    end

View on GitHub (pinned to e227c27540)

Solutions

  1. Build URLs in the documented order /:prefix/:version/:indirection/:key — e.g. GET /puppet/v3/facts/web1.example.com — URL-encoding only the key.
  2. Use literal indirection names: catalog, facts, node, report, file_content, file_metadata, status.
  3. Inspect the actual request path at the server (debug logging) for double slashes or percent-encoding introduced by proxies.
  4. Prefer Puppet's own client/indirection APIs over hand-rolled HTTP.

Example fix

# before
GET /puppet/v3/facts%20host/web1.example.com   → 400, indirection must be alphanumeric

# after
GET /puppet/v3/facts/web1.example.com
Defensive patterns

Strategy: validation

Validate before calling

INDIRECTIONS = %w[catalog facts node report file_content file_metadata status].freeze
indirection = path.split('/', 5)[3]
raise ArgumentError, 'malformed indirection' unless indirection =~ /^\w+$/ && INDIRECTIONS.include?(indirection)
response = client.get('/puppet/v3/' + indirection + '/' + ERB::Util.url_encode(key))

Try / catch

resp = client.get(path)
case resp
when Net::HTTPBadRequest
  # 400 'indirection name must be purely alphanumeric' → fix the URL template; do not retry
  raise "malformed REST path: #{path}"
end

Prevention

When it happens

Trigger: Hand-crafted requests such as GET /puppet/v3/facts%20host/web1, /puppet/v3/node-report/x (dash), or //puppet/v3/catalog/web1 where the split leaves a malformed third field; any URL builder that interpolates user data into the indirection position.

Common situations: Custom scripts or curl calls with wrong URL layout; proxies and rewrite rules mangling the path (encoding, duplicate slashes); confusion about segment order; client libraries targeting a different REST layout than the server's.

Related errors


AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21). Data as JSON: /api/errors/602328ddde1e1bd7. Report an issue: GitHub.