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_PATTERNView on GitHub (pinned to dd45c6e18d)
Solutions
- 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
- Configure `<secondary> @type secondary_file` so traversal-tagged chunks are captured to disk instead of dropped
- 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
- Never feed unvalidated external strings (usernames, job ids, webhook names) into tags that reach ${tag} paths
- Configure <secondary> @type secondary_file so traversal-tagged chunks are preserved, not dropped
- Sanitize at the edge: strip '..' segments, leading '/', and backslashes when tags are created (rewrite_tag_filter, custom parser)
- Alert on UnrecoverableError in logs — each one is data diverted from its destination
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
- buffer space has too many data
- out_secondary_file: basename or directory has an incompatibl
- Parameter '#{name}: #{string}' doesn't have tag placeholder
- Parameter '#{name}: #{string}' has tag placeholders, but chu
- Invalid path component detected in #{matched}: #{replace}
AI-assisted analysis of fluent/fluentd@dd45c6e18d (2026-08-21).
Data as JSON: /api/errors/a221f1d71de5f078.
Report an issue: GitHub.