{"record":{"id":"c382843633c88f38","repo":"puppetlabs/puppet","slug":"indirection-indirection-name-does-not-match-u","errorCode":null,"errorMessage":"Indirection '%{indirection_name}' does not match url prefix '%{url_prefix}'","messagePattern":"Indirection '%(.+?)' does not match url prefix '%(.+?)'","errorType":"http","errorClass":"Puppet::Network::HTTP::Error::HTTPBadRequestError","httpStatus":400,"severity":"error","filePath":"lib/puppet/network/http/api/indirected_routes.rb","lineNumber":74,"sourceCode":"  end\n\n  def uri2indirection(http_method, uri, params)\n    # the first field is always nil because of the leading slash,\n    indirection_type, version, indirection_name, key = uri.split(\"/\", 5)[1..]\n    url_prefix = \"/#{indirection_type}/#{version}\"\n    environment = params.delete(:environment)\n\n    if indirection_name !~ /^\\w+$/\n      raise Puppet::Network::HTTP::Error::HTTPBadRequestError, _(\"The indirection name must be purely alphanumeric, not '%{indirection_name}'\") % { indirection_name: indirection_name }\n    end\n\n    # this also depluralizes the indirection_name if it is a search\n    method = indirection_method(http_method, indirection_name)\n\n    # check whether this indirection matches the prefix and version in the\n    # request\n    if url_prefix != IndirectionType.url_prefix_for(indirection_name)\n      raise Puppet::Network::HTTP::Error::HTTPBadRequestError, _(\"Indirection '%{indirection_name}' does not match url prefix '%{url_prefix}'\") % { indirection_name: indirection_name, url_prefix: url_prefix }\n    end\n\n    indirection = Puppet::Indirector::Indirection.instance(indirection_name.to_sym)\n    unless indirection\n      raise Puppet::Network::HTTP::Error::HTTPNotFoundError.new(\n        _(\"Could not find indirection '%{indirection_name}'\") % { indirection_name: indirection_name },\n        Puppet::Network::HTTP::Issues::HANDLER_NOT_FOUND\n      )\n    end\n\n    unless environment\n      raise Puppet::Network::HTTP::Error::HTTPBadRequestError, _(\"An environment parameter must be specified\")\n    end\n\n    unless Puppet::Node::Environment.valid_name?(environment)\n      raise Puppet::Network::HTTP::Error::HTTPBadRequestError, _(\"The environment must be purely alphanumeric, not '%{environment}'\") % { environment: environment }\n    end\n","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/network/http/api/indirected_routes.rb#L56-L92","documentation":"Puppet's indirected REST routes validate that the URL prefix of a request matches the indirection being requested. IndirectionType maps certificate, certificate_request, certificate_status and certificate_revocation_list to the 'puppet-ca/v1' prefix and everything else to 'puppet/v3'. When the handler receives a mismatched pair, it rejects the request with HTTP 400 before the indirection is ever consulted.","triggerScenarios":"Requesting a CA indirection under the master prefix, e.g. GET /puppet/v3/certificate/ca, or a master indirection under the CA prefix, e.g. GET /puppet-ca/v1/catalog/mynode. Also produced by custom mounts or reverse proxies that rewrite the /puppet/v3 or /puppet-ca/v1 path segments so url_prefix no longer equals IndirectionType.url_prefix_for(indirection_name).","commonSituations":"Hand-written curl or rest-client calls that hardcode '/puppet' for every endpoint; middleware/proxies stripping or rewriting the versioned path prefix; scripts ported from the legacy v2 URL scheme (/production/catalog/...) that guess the new prefix; custom Rack/WEBrick servers registering routes with the wrong prefix for the served indirections.","solutions":["Use the correct prefix: certificate, certificate_request, certificate_status, certificate_revocation_list go under /puppet-ca/v1/..., all other indirections under /puppet/v3/...","Check proxy and rewrite rules in front of the Puppet master so the full /puppet/v3 or /puppet-ca/v1 path reaches the handler intact","When writing a custom handler, register routes via Puppet::Network::HTTP::Route.path('/puppet') vs Route.path('/puppet-ca') to match the indirections you serve","Enable Puppet debug logging ('Routes Registered') to see the exact url_prefix the handler receives for the failing request"],"exampleFix":"# before\ncurl -k \"https://puppet.example.com:8140/puppet/v3/certificate/ca?environment=production\"\n\n# after\n# -k \"https://puppet.example.com:8140/puppet-ca/v1/certificate/ca?environment=production\"","handlingStrategy":"validation","validationCode":"CA_INDIRECTIONS = %w[certificate certificate_request certificate_status certificate_revocation_list].freeze\n\ndef prefix_for(indirection)\n  CA_INDIRECTIONS.include?(indirection) ? 'puppet-ca/v1' : 'puppet/v3'\nend\n\ndef indirected_uri(indirection, key)\n  \"/#{prefix_for(indirection)}/#{indirection}/#{key}\"\nend","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Derive the URL prefix from the indirection name instead of hardcoding /puppet","Prefer Puppet's own client (Puppet.runtime[:http] or puppet agent) over hand-rolled HTTP calls","Keep fronting proxies from rewriting the /puppet or /puppet-ca path segments"],"tags":["http","routing","indirection","rest-api"],"backgroundTag":"http-route-mismatch","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}