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
)
endView on GitHub (pinned to e227c27540)
Solutions
- Build URLs in the documented order /:prefix/:version/:indirection/:key — e.g. GET /puppet/v3/facts/web1.example.com — URL-encoding only the key.
- Use literal indirection names: catalog, facts, node, report, file_content, file_metadata, status.
- Inspect the actual request path at the server (debug logging) for double slashes or percent-encoding introduced by proxies.
- 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
- Never build the indirection segment from user input; only the key varies per request.
- Keep path templates in one place and assert the segment matches /\w+/ before sending.
- Watch proxy rewrite rules that percent-encode or collapse slashes.
- Smoke-test custom clients against the documented v3 endpoints.
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
- 'put' requires a string 'body' argument
- 'post' requires a string 'body' argument
- A block is required
- Could not download module: %{message}
- No format matches the given format name or mime-type (%{form
AI-assisted analysis of puppetlabs/puppet@e227c27540 (2026-08-21).
Data as JSON: /api/errors/602328ddde1e1bd7.
Report an issue: GitHub.