fluent/fluentd · critical · Fluent::UnrecoverableError

Invalid path component detected, replaced to: #{rvalue}

Error message

Invalid path component detected, replaced to: #{rvalue}

What it means

Companion guard to error 220 in Output#extract_placeholders: after all placeholder substitution, it rescans the finished path (rvalue) with PARENT_DIRECTORY_PATTERN (%r{\.\.[/\\]}) and raises Fluent::UnrecoverableError if the '../' count grew compared to the original template (str). This catches indirect escapes that the per-value check misses, e.g. a substituted value that is exactly '..' (no trailing slash) combining with adjacent template text to form extra '../' segments. As an UnrecoverableError it discards the affected buffer chunk.

Source

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

            @chunk_keys.each do |key|
              hash["${#{key}}"] = metadata.variables[key.to_sym]
            end

            rvalue = rvalue.gsub(CHUNK_KEY_PLACEHOLDER_PATTERN) do |matched|
              replace = hash.fetch(matched) do
                log.warn "chunk key placeholder '#{matched[2..-2]}' not replaced. template:#{str}"
                ''
              end
              if replace.to_s.match?(INVALID_PATH_COMPONENT_PATTERN)
                raise Fluent::UnrecoverableError, "Invalid path component detected in #{matched}: #{replace}"
              end

              replace
            end
            # Check if the number of parent directory components (../) has increased due to variable substitution
            if rvalue.match?(PARENT_DIRECTORY_PATTERN)
              if rvalue.scan(PARENT_DIRECTORY_PATTERN).size > str.scan(PARENT_DIRECTORY_PATTERN).size
                raise Fluent::UnrecoverableError, "Invalid path component detected, replaced to: #{rvalue}"
              end
            end
          end

          if rvalue =~ CHUNK_KEY_PLACEHOLDER_PATTERN
            log.warn "chunk key placeholder '#{$1}' not replaced. template:#{str}"
          end

          rvalue
        end
      end

      def emit_events(tag, es)
        # actually this method will be overwritten by #configure
        if @buffering
          emit_buffered(tag, es)
        else
          emit_sync(tag, es)

View on GitHub (pinned to dd45c6e18d)

Solutions

  1. Remove '..' segments from the path template itself and use absolute, flat buffer paths.
  2. Reject or rewrite chunk-key values equal to '..' or containing '..' in a filter before buffering (record['a'] == '..' ? '_' : record['a']).
  3. Replace user-controlled path segments with ${chunk_id} or a computed digest.
  4. Add a config-time lint: assert that path.scan(%r{\.\.[/\\]}).size plus any substituted value's count never exceeds the template's.

Example fix

# before
path /var/log/fluent/${tenant}/../archive
# after
path /var/log/fluent/archive/${tenant}
Defensive patterns

Strategy: validation

Validate before calling

# count guard mirroring the core check: substituted value must not add '../' segments
PARENT = %r{\.\.[/\\]}
raise_if_escape = value.scan(PARENT).size > 0 || value == '..'
# rewrite instead of raising:
value = '_' if value == '..'
value = value.gsub(PARENT, '_')

Type guard

def safe_template_value?(v)
  v.is_a?(String) && v != '..' && !v.match?(%r{\.\.[/\\]})
end

Try / catch

rescue Fluent::UnrecoverableError => e
  log.error 'chunk discarded, path escapes buffer dir', replaced: e.message
  # ship the raw record to a fallback label for manual repair

Prevention

When it happens

Trigger: Template path /srv/${a}/../share with a record where chunk key a='..' yields /srv/../../share (2 vs 1 '../' occurrences) and raises. Also values like 'x..' or '..y' inside templates that already contain '../' when substitution increases the count.

Common situations: Buffer path templates that legitimately contain '..' segments combined with user-controlled chunk keys; symlink-style path templates ('/var/log/${svc}/current') fed with values like '..'; post-upgrade configs that previously resolved outside the buffer directory without complaint.

Related errors


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