@@ -220,9 +220,16 @@ impl SimpleHttpConfig {
220220 )
221221 . with_standard_vector_source_metadata ( ) ;
222222
223- // for metadata that is added to the events dynamically from config options
223+ // For metadata that is added to the events dynamically from config options.
224224 if log_namespace == LogNamespace :: Legacy {
225- schema_definition = schema_definition. unknown_fields ( Kind :: bytes ( ) ) ;
225+ // Custom auth programs can inject any VRL value, not just bytes; widen the unknown
226+ // field kind accordingly so schema-aware downstream components don't reject events.
227+ let unknown_kind = if matches ! ( self . auth, Some ( HttpServerAuthConfig :: Custom { .. } ) ) {
228+ Kind :: any ( )
229+ } else {
230+ Kind :: bytes ( )
231+ } ;
232+ schema_definition = schema_definition. unknown_fields ( unknown_kind) ;
226233 }
227234
228235 schema_definition
@@ -426,6 +433,12 @@ struct SimpleHttpSource {
426433 log_namespace : LogNamespace ,
427434}
428435
436+ /// Built-in `http_server` metadata keys written by `enrich_events` with a trusted value.
437+ /// Auth enrichment must not overwrite these; all other keys are auth-derived and may override
438+ /// client-supplied metadata from decoded events (e.g. a native-codec request).
439+ const RESERVED_HTTP_SERVER_METADATA_KEYS : & [ & str ] =
440+ & [ "path" , "host" , "headers" , "query_parameters" ] ;
441+
429442impl HttpSource for SimpleHttpSource {
430443 /// Enriches the log events with metadata for the `request_path` and for each of the headers.
431444 /// Non-log events are skipped.
@@ -529,23 +542,43 @@ impl HttpSource for SimpleHttpSource {
529542 /// Both namespaces use insert-if-empty semantics so auth enrichment never
530543 /// overwrites built-in source metadata (`path`, `host`, `headers`, …) that
531544 /// `enrich_events` already populated.
532- /// Legacy namespace: inserted into the event body.
533- /// Vector namespace: inserted into event metadata under `http_server.<field>`.
545+ /// Vector namespace: inserted into event metadata under `http_server.<field>` for
546+ /// all event types (Log, Metric, Trace).
547+ /// Legacy namespace: inserted into the Log event body only (Metric/Trace are skipped).
534548 fn inject_auth_enrichment ( & self , events : & mut [ Event ] , enrichment : ObjectMap ) {
535549 for event in events. iter_mut ( ) {
536- if let Event :: Log ( log) = event {
537- for ( key, value) in & enrichment {
538- match self . log_namespace {
539- LogNamespace :: Vector => {
540- log. try_insert (
541- (
542- PathPrefix :: Metadata ,
543- path ! ( SimpleHttpConfig :: NAME ) . concat ( path ! ( key. as_str( ) ) ) ,
544- ) ,
550+ match self . log_namespace {
551+ LogNamespace :: Vector => {
552+ // metadata_mut() dispatches to Log, Metric, and Trace so every
553+ // decoded event type receives the auth enrichment fields.
554+ let meta = event. metadata_mut ( ) . value_mut ( ) ;
555+ for ( key, value) in & enrichment {
556+ let key_str = key. as_str ( ) ;
557+ if RESERVED_HTTP_SERVER_METADATA_KEYS . contains ( & key_str) {
558+ // Built-in source key: skip if already written by enrich_events.
559+ if meta
560+ . get ( path ! ( SimpleHttpConfig :: NAME ) . concat ( path ! ( key_str) ) )
561+ . is_none ( )
562+ {
563+ meta. insert (
564+ path ! ( SimpleHttpConfig :: NAME ) . concat ( path ! ( key_str) ) ,
565+ value. clone ( ) ,
566+ ) ;
567+ }
568+ } else {
569+ // Auth-derived key: overwrite any client-supplied value so
570+ // clients cannot spoof fields the auth program sets.
571+ meta. insert (
572+ path ! ( SimpleHttpConfig :: NAME ) . concat ( path ! ( key_str) ) ,
545573 value. clone ( ) ,
546574 ) ;
547575 }
548- LogNamespace :: Legacy => {
576+ }
577+ }
578+ LogNamespace :: Legacy => {
579+ // Legacy enrichment targets the event body; only Log events have one.
580+ if let Event :: Log ( log) = event {
581+ for ( key, value) in & enrichment {
549582 log. try_insert ( ( PathPrefix :: Event , path ! ( key. as_str( ) ) ) , value. clone ( ) ) ;
550583 }
551584 }
@@ -1824,6 +1857,111 @@ mod tests {
18241857 ) ;
18251858 }
18261859
1860+ #[ test]
1861+ fn inject_auth_enrichment_overwrites_client_supplied_metadata_in_vector_namespace ( ) {
1862+ use crate :: { codecs:: DecodingConfig , sources:: util:: HttpSource as _} ;
1863+ use vector_lib:: codecs:: BytesDeserializerConfig ;
1864+ use vrl:: value:: KeyString ;
1865+
1866+ let decoder = DecodingConfig :: new (
1867+ BytesDecoderConfig :: new ( ) . into ( ) ,
1868+ BytesDeserializerConfig :: new ( ) . into ( ) ,
1869+ LogNamespace :: Vector ,
1870+ )
1871+ . build ( )
1872+ . unwrap ( )
1873+ . with_log_namespace ( LogNamespace :: Vector ) ;
1874+
1875+ let source = super :: SimpleHttpSource {
1876+ headers : vec ! [ ] ,
1877+ query_parameters : vec ! [ ] ,
1878+ path_key : OptionalValuePath :: none ( ) ,
1879+ host_key : OptionalValuePath :: none ( ) ,
1880+ decoder,
1881+ log_namespace : LogNamespace :: Vector ,
1882+ } ;
1883+
1884+ let mut log = LogEvent :: default ( ) ;
1885+ // Simulate a client pre-populating an auth-derived key (e.g. via native codec).
1886+ log. insert (
1887+ (
1888+ PathPrefix :: Metadata ,
1889+ path ! ( SimpleHttpConfig :: NAME ) . concat ( path ! ( "tenant_id" ) ) ,
1890+ ) ,
1891+ "attacker" ,
1892+ ) ;
1893+
1894+ let mut events = vec ! [ Event :: Log ( log) ] ;
1895+ let mut enrichment = ObjectMap :: new ( ) ;
1896+ enrichment. insert ( KeyString :: from ( "tenant_id" ) , Value :: from ( "trusted" ) ) ;
1897+
1898+ source. inject_auth_enrichment ( & mut events, enrichment) ;
1899+
1900+ let Event :: Log ( log) = & events[ 0 ] else {
1901+ panic ! ( "expected log event" ) ;
1902+ } ;
1903+ assert_eq ! (
1904+ log. get( (
1905+ PathPrefix :: Metadata ,
1906+ path!( SimpleHttpConfig :: NAME ) . concat( path!( "tenant_id" ) ) ,
1907+ ) ) ,
1908+ Some ( & Value :: from( "trusted" ) ) ,
1909+ "auth enrichment must overwrite client-supplied metadata for auth-derived keys"
1910+ ) ;
1911+ }
1912+
1913+ #[ test]
1914+ fn inject_auth_enrichment_applies_to_non_log_events_in_vector_namespace ( ) {
1915+ use crate :: { codecs:: DecodingConfig , sources:: util:: HttpSource as _} ;
1916+ use vector_lib:: {
1917+ codecs:: BytesDeserializerConfig ,
1918+ event:: { Metric , MetricKind , MetricValue } ,
1919+ } ;
1920+ use vrl:: value:: KeyString ;
1921+
1922+ let decoder = DecodingConfig :: new (
1923+ BytesDecoderConfig :: new ( ) . into ( ) ,
1924+ BytesDeserializerConfig :: new ( ) . into ( ) ,
1925+ LogNamespace :: Vector ,
1926+ )
1927+ . build ( )
1928+ . unwrap ( )
1929+ . with_log_namespace ( LogNamespace :: Vector ) ;
1930+
1931+ let source = super :: SimpleHttpSource {
1932+ headers : vec ! [ ] ,
1933+ query_parameters : vec ! [ ] ,
1934+ path_key : OptionalValuePath :: none ( ) ,
1935+ host_key : OptionalValuePath :: none ( ) ,
1936+ decoder,
1937+ log_namespace : LogNamespace :: Vector ,
1938+ } ;
1939+
1940+ let metric = Metric :: new (
1941+ "requests" ,
1942+ MetricKind :: Incremental ,
1943+ MetricValue :: Counter { value : 1.0 } ,
1944+ ) ;
1945+ let mut events = vec ! [ Event :: Metric ( metric) ] ;
1946+
1947+ let mut enrichment = ObjectMap :: new ( ) ;
1948+ enrichment. insert ( KeyString :: from ( "tenant_id" ) , Value :: from ( "t-456" ) ) ;
1949+
1950+ source. inject_auth_enrichment ( & mut events, enrichment) ;
1951+
1952+ let Event :: Metric ( metric) = & events[ 0 ] else {
1953+ panic ! ( "expected metric event" ) ;
1954+ } ;
1955+ assert_eq ! (
1956+ metric
1957+ . metadata( )
1958+ . value( )
1959+ . get( path!( SimpleHttpConfig :: NAME ) . concat( path!( "tenant_id" ) ) , ) ,
1960+ Some ( & Value :: from( "t-456" ) ) ,
1961+ "auth enrichment must be written to non-log event metadata"
1962+ ) ;
1963+ }
1964+
18271965 impl ValidatableComponent for SimpleHttpConfig {
18281966 fn validation_configuration ( ) -> ValidationConfiguration {
18291967 let config = Self {
0 commit comments