{"record":{"id":"4d27654502488e5b","repo":"fluent/fluentd","slug":"invalid-path-component-detected-in-matched-r","errorCode":null,"errorMessage":"Invalid path component detected in #{matched}: #{replace}","messagePattern":"Invalid path component detected in #(.+?): #(.+?)","errorType":"exception","errorClass":"Fluent::UnrecoverableError","httpStatus":null,"severity":"critical","filePath":"lib/fluent/plugin/output.rb","lineNumber":877,"sourceCode":"            else\n              log.warn \"${chunk_id} is not allowed in this plugin. Pass Chunk instead of metadata in extract_placeholders's 2nd argument\"\n            end\n          }\n\n          # Then, replace other ${chunk_key}s.\n          if !@chunk_keys.empty? && metadata.variables\n            hash = {'${tag}' => '${tag}'} # not to erase this wrongly\n            @chunk_keys.each do |key|\n              hash[\"${#{key}}\"] = metadata.variables[key.to_sym]\n            end\n\n            rvalue = rvalue.gsub(CHUNK_KEY_PLACEHOLDER_PATTERN) do |matched|\n              replace = hash.fetch(matched) do\n                log.warn \"chunk key placeholder '#{matched[2..-2]}' not replaced. template:#{str}\"\n                ''\n              end\n              if replace.to_s.match?(INVALID_PATH_COMPONENT_PATTERN)\n                raise Fluent::UnrecoverableError, \"Invalid path component detected in #{matched}: #{replace}\"\n              end\n\n              replace\n            end\n            # Check if the number of parent directory components (../) has increased due to variable substitution\n            if rvalue.match?(PARENT_DIRECTORY_PATTERN)\n              if rvalue.scan(PARENT_DIRECTORY_PATTERN).size > str.scan(PARENT_DIRECTORY_PATTERN).size\n                raise Fluent::UnrecoverableError, \"Invalid path component detected, replaced to: #{rvalue}\"\n              end\n            end\n          end\n\n          if rvalue =~ CHUNK_KEY_PLACEHOLDER_PATTERN\n            log.warn \"chunk key placeholder '#{$1}' not replaced. template:#{str}\"\n          end\n\n          rvalue\n        end","sourceCodeStart":859,"sourceCodeEnd":895,"githubUrl":"https://github.com/fluent/fluentd/blob/dd45c6e18dc7be33b5e5a0f0767bf46307ff5626/lib/fluent/plugin/output.rb#L859-L895","documentation":"Fluentd raises this Fluent::UnrecoverableError inside Output#extract_placeholders when a buffer path placeholder (e.g. ${key} or ${tag}) is replaced by a chunk-key value containing a path-escape component. The guard tests the substituted value against INVALID_PATH_COMPONENT_PATTERN (%r{\\.\\.[/\\\\]|^[/\\\\]}, lib/fluent/plugin/output.rb:48), i.e. '../', '..\\\\', or a leading '/'/'\\\\'. UnrecoverableError means the chunk can never be flushed and is eventually discarded, so the events in it are lost. The check exists to stop record/tag contents from writing outside the configured buffer directory.","triggerScenarios":"A buffered output configured with <buffer app> and path /var/log/fluent/${app}, where a record's 'app' value is '../../etc/x', 'a/../..', 'C:\\\\evil', or '/abs/path'; likewise ${tag} when the input tag starts with '/' or contains '../'. Extraction happens at buffer-chunk creation, so the error surfaces on write/flush attempts.","commonSituations":"Using unvalidated request paths, container names, hostnames, or log file names as chunk keys in file/s3 outputs; forwarding untrusted events whose fields feed path templates; upgrading to newer fluentd where this traversal guard (INVALID_PATH_COMPONENT_PATTERN) was introduced and previously-tolerated values now fail.","solutions":["Sanitize the chunk-key value before buffering: add a record_transformer/filter that strips '../', '..\\\\' and leading '/' (e.g. app ${record['app'].to_s.gsub(%r{\\.\\.[/\\\\]}, '_').sub(%r{^[/\\\\]}, '')}).","Stop using raw record values in path templates: use ${chunk_id} or a digest/hash of the value as the directory name.","Route records with malformed chunk-key values to a separate label/dead-letter output so only bad events are dropped.","If nested directories under the buffer path are genuinely required, pre-map values to a fixed safe directory list instead of interpolating raw values."],"exampleFix":"# before\n<match logs.**>\n  @type file\n  path /var/log/fluent/${app}\n  <buffer app>\n    @type file\n  </buffer>\n</match>\n# after: sanitize the chunk key before it becomes a path component\n<filter logs.**>\n  @type record_transformer\n  enable_ruby true\n  <record>\n    app ${record['app'].to_s.gsub(%r{\\.\\.[/\\\\]}, '_').sub(%r{^[/\\\\]}, '')}\n  </record>\n</filter>","handlingStrategy":"validation","validationCode":"# before events reach a buffered output with path placeholders:\nINVALID_PATH = %r{\\.\\.[/\\\\]|^[/\\\\]}\nif record['app'].to_s.match?(INVALID_PATH)\n  router.emit_stream('app.invalid', OneEventStream.new([[Fluent::EventTime.now, record]]))\nend","typeGuard":"def safe_path_component?(v)\n  v.is_a?(String) && !v.match?(%r{\\.\\.[/\\\\]|^[/\\\\]})\nend","tryCatchPattern":"rescue Fluent::UnrecoverableError => e\n  log.error 'buffer chunk dropped: invalid path component', error: e\n  # record to dead-letter; chunk is unrecoverable by design, do not retry","preventionTips":["Never interpolate raw record values into buffer path; prefer ${chunk_id} or a digest of the value.","Add a sanitize filter (record_transformer with gsub) ahead of any path-based output.","Run fluentd --dry-run after every config change.","Monitor num_errors and buffer retry counters to catch discarded chunks early."],"tags":["fluentd","buffer","path-traversal","unrecoverable-error","output-plugin"],"backgroundTag":"path-traversal-sanitization","analyzedSha":"dd45c6e18dc7be33b5e5a0f0767bf46307ff5626","analyzedAt":"2026-08-21T16:22:07.332Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}