fluent/fluentd · error · Fluent::UnrecoverableError

Invalid path component detected in tag: #{metadata.tag}

Error message

Invalid path component detected in tag: #{metadata.tag}

What it means

At flush time (not configure time) `extract_placeholders` substitutes `${tag}` into a path and rejects tags matching `INVALID_PATH_COMPONENT_PATTERN = /\.\.[\/\\]|^[\/\\]/` — i.e. containing `../` (or `..\`) or starting with `/` or `\`. This blocks path traversal outside the target directory. It raises `Fluent::UnrecoverableError`, so the chunk is not retried: it goes to `<secondary>` if configured, otherwise it is discarded.

Source

Thrown at lib/fluent/plugin/output.rb:832

          str.sub(CHUNK_ID_PLACEHOLDER_PATTERN) {
            if chunk_passed
              dump_unique_id_hex(chunk.unique_id)
            else
              log.warn "${chunk_id} is not allowed in this plugin. Pass Chunk instead of metadata in extract_placeholders's 2nd argument"
            end
          }
        else
          rvalue = str.dup
          # strftime formatting
          if @chunk_key_time # this section MUST be earlier than rest to use raw 'str'
            @output_time_formatter_cache[str] ||= Fluent::Timezone.formatter(@timekey_zone, str)
            rvalue = @output_time_formatter_cache[str].call(metadata.timekey)
          end
          # ${tag}, ${tag[0]}, ${tag[1]}, ... , ${tag[-2]}, ${tag[-1]}
          if @chunk_key_tag
            if str.include?('${tag}')
              if metadata.tag.match?(INVALID_PATH_COMPONENT_PATTERN)
                raise Fluent::UnrecoverableError, "Invalid path component detected in tag: #{metadata.tag}"
              end

              rvalue = rvalue.gsub('${tag}', metadata.tag)
            end
            if CHUNK_TAG_PLACEHOLDER_PATTERN.match?(str)
              if metadata.tag.match?(INVALID_PATH_COMPONENT_PATTERN)
                raise Fluent::UnrecoverableError, "Invalid path component detected in tag: #{metadata.tag}"
              end

              hash = {}
              tag_parts = metadata.tag.split('.')
              tag_parts.each_with_index do |part, i|
                hash["${tag[#{i}]}"] = part
                hash["${tag[#{i-tag_parts.size}]}"] = part
              end
              rvalue = rvalue.gsub(CHUNK_TAG_PLACEHOLDER_PATTERN, hash)
            end
            if rvalue =~ CHUNK_TAG_PLACEHOLDER_PATTERN

View on GitHub (pinned to dd45c6e18d)

Solutions

  1. Sanitize tags at ingress: strip/replace `..`, leading `/`, and backslashes via `record_transformer`/`rewrite_tag_filter` or a custom parser before they reach tag-chunked outputs
  2. Configure `<secondary> @type secondary_file` so traversal-tagged chunks are captured to disk instead of dropped
  3. Avoid `${tag}` in paths for untrusted tags — chunk on a sanitized record field (`${key}`) validated upstream

Example fix

# before
<match **>
  @type file
  path /logs/${tag}/app.log
  <buffer tag>
    @type file
  </buffer>
</match>

# after (sanitize tag at rewrite time + keep a local safety net)
<match raw.**>
  @type rewrite_tag_filter
  <rule>
    key message
    pattern ^.*/.*$
    tag safe.${tag}
  </rule>
</match>
<match **>
  @type file
  path /logs/${tag}/app.log
  <buffer tag>
    @type file
  </buffer>
  <secondary>
    @type secondary_file
    directory /var/log/fluent/backup
  </secondary>
</match>
Defensive patterns

Strategy: fallback

Validate before calling

INVALID = %r{\.\.[/\\]|^[/\\]}
tag = event_meta.tag
warn_and_retag(tag) if tag.match?(INVALID)
# In config form, sanitize at ingress:
# <filter **> @type record_transformer <record> tag ${tag.gsub('..', '__')} </record> </filter>

Type guard

def safe_tag_for_path?(tag)
  !tag.match?(%r{\.\.[/\\]|^[/\\]})
end

Try / catch

# in a custom output wrapping path construction:
begin
  path = extract_placeholders(@path, chunk)
rescue Fluent::UnrecoverableError => e
  log.error "tag rejected by path traversal guard", error: e
  # let it propagate so the chunk routes to <secondary>; do not rescue-and-drop
  raise
end

Prevention

When it happens

Trigger: An output with `<buffer tag>` and a path containing `${tag}` (e.g. `path /logs/${tag}/app.log`) receives a record whose tag is `foo/../../etc`, `..%2F`-adjacent forms that decode to traversal, or an absolute-looking `/var/log/x`. The regex matches on `metadata.tag` at output.rb:832-835 and the unrecoverable error propagates from the flush.

Common situations: Ingesting user-controlled tags (webhook names, container/job names, syslog program names) into file paths; `rewrite_tag_filter` or record fields promoted to tags without sanitization; multi-tenant forwarders where tenants pick their own tag.

Related errors


AI-assisted analysis of fluent/fluentd@dd45c6e18d (2026-08-21). Data as JSON: /api/errors/a221f1d71de5f078. Report an issue: GitHub.