diff --git a/docs/content/contributing/submitting-security-issues.md b/docs/content/contributing/submitting-security-issues.md
index 981c7fe4ce..b2d038b39c 100644
--- a/docs/content/contributing/submitting-security-issues.md
+++ b/docs/content/contributing/submitting-security-issues.md
@@ -21,3 +21,33 @@ We want to keep Traefik safe for everyone.
If you've discovered a security vulnerability in Traefik,
we appreciate your help in disclosing it to us in a responsible manner,
by creating a [security advisory](https://github.com/traefik/traefik/security/advisories).
+
+## Submission Quality Guidelines
+
+We have been receiving an increasing number of low-quality vulnerability reports that are not actual security issues.
+Many of these reports originate from AI/LLM tools and are submitted without any human validation or testing.
+This wastes the time of our security team and delays the handling of legitimate vulnerabilities.
+
+Before submitting a security advisory, you **must**:
+
+- **Carefully test and validate** the vulnerability yourself before submitting.
+ You must be able to demonstrate a working proof of concept with clear reproduction steps.
+- **Understand the impact** of the vulnerability and explain how it can be exploited in a realistic scenario.
+- **Verify that the issue is not a false positive**.
+ Ensure the behavior you are reporting is actually a security concern and not expected behavior.
+
+### Policy on AI-Generated Reports
+
+Security reports that are **directly generated by AI/LLM tools without proper human validation** will be **closed immediately**.
+
+Indicators of unvalidated AI-generated reports include (but are not limited to):
+
+- No working proof of concept or reproduction steps.
+- Generic or theoretical vulnerability descriptions with no evidence of actual testing.
+- Misunderstanding of Traefik's architecture or threat model.
+- Hallucinated code paths, configuration options, or behaviors that do not exist.
+
+**Contributors who repeatedly submit low-quality or unvalidated reports may have their accounts blocked.**
+
+We appreciate the work of security researchers who take the time to rigorously validate their findings.
+Quality over quantity helps keep Traefik safe for everyone.
diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md
index c1d613d459..1352c60dec 100644
--- a/docs/content/observability/access-logs.md
+++ b/docs/content/observability/access-logs.md
@@ -281,8 +281,10 @@ accessLog:
| `TLSVersion` | The TLS version used by the connection (e.g. `1.2`) (if connection is TLS). |
| `TLSCipher` | The TLS cipher used by the connection (e.g. `TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`) (if connection is TLS) |
| `TLSClientSubject` | The string representation of the TLS client certificate's Subject (e.g. `CN=username,O=organization`) |
- | `TraceId` | A consistent identifier for tracking requests across services, including upstream ones managed by Traefik, shown as a 32-hex digit string |
- | `SpanId` | A unique identifier for Traefik’s root span (EntryPoint) within a request trace, formatted as a 16-hex digit string. |
+ | `TraceId` | (Deprecated) A consistent identifier for tracking requests across services, including upstream ones managed by Traefik, shown as a 32-hex digit string |
+ | `SpanId` | (Deprecated) A unique identifier for Traefik’s root span (EntryPoint) within a request trace, formatted as a 16-hex digit string. |
+ | `trace_id` | OTel-conformant trace identifier for tracking requests across services, including upstream ones managed by Traefik, shown as a 32-hex digit string |
+ | `span_id` | OTel-conformant span identifier for Traefik’s root span (EntryPoint) within a request trace, formatted as a 16-hex digit string. |
## Log Rotation
diff --git a/docs/content/reference/routing-configuration/http/routing/rules-and-priority.md b/docs/content/reference/routing-configuration/http/routing/rules-and-priority.md
index 0291b9efe8..69712a373b 100644
--- a/docs/content/reference/routing-configuration/http/routing/rules-and-priority.md
+++ b/docs/content/reference/routing-configuration/http/routing/rules-and-priority.md
@@ -41,8 +41,8 @@ The `Header` and `HeaderRegexp` matchers allow matching requests that contain sp
| Behavior | Rule |
|-----------------------------------------------------------------|:------------------------------------------------------------------------|
| | ```Header(`Content-Type`, `application/yaml`)``` |
-| | ```HeaderRegexp(`Content-Type`, `^application/(json\|yaml)$`)``` |
-| | ```HeaderRegexp(`Content-Type`, `(?i)^application/(json\|yaml)$`)``` |
+| | ```HeaderRegexp(`Content-Type`, `^application/(json|yaml)$`)``` |
+| | ```HeaderRegexp(`Content-Type`, `(?i)^application/(json|yaml)$`)``` |
### Host and HostRegexp
@@ -58,8 +58,8 @@ These matchers will match the request's host in lowercase.
|-----------------------------------------------------------------|:------------------------------------------------------------------------|
| Match requests with `Host` set to `example.com`. | ```Host(`example.com`)``` |
| Match requests sent to any subdomain of `example.com`. | ```HostRegexp(`^.+\.example\.com$`)``` |
-| Match requests with `Host` set to either `example.com` or `example.org`. | ```HostRegexp(`^example\.(com\|org)$`)``` |
-| Match `Host` [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```HostRegexp(`(?i)^example\.(com\|org)$`)``` |
+| Match requests with `Host` set to either `example.com` or `example.org`. | ```HostRegexp(`^example\.(com|org)$`)``` |
+| Match `Host` [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```HostRegexp(`(?i)^example\.(com|org)$`)``` |
### Method
@@ -81,8 +81,8 @@ Path are always starting with a `/`, except for `PathRegexp`.
|-----------------------------------------------------------------|:------------------------------------------------------------------------|
| Match `/products` but neither `/products/shoes` nor `/products/`. | ```Path(`/products`)``` |
| Match `/products` as well as everything under `/products`, such as `/products/shoes`, `/products/` but also `/products-for-sale`. | ```PathPrefix(`/products`)``` |
-| Match both `/products/shoes` and `/products/socks` with and ID like `/products/shoes/31`. | ```PathRegexp(`^/products/(shoes\|socks)/[0-9]+$`)``` |
-| Match requests with a path ending in either `.jpeg`, `.jpg` or `.png`. | ```PathRegexp(`\.(jpeg\|jpg\|png)$`)``` |
+| Match both `/products/shoes` and `/products/socks` with and ID like `/products/shoes/31`. | ```PathRegexp(`^/products/(shoes|socks)/[0-9]+$`)``` |
+| Match requests with a path ending in either `.jpeg`, `.jpg` or `.png`. | ```PathRegexp(`\.(jpeg|jpg|png)$`)``` |
| Match `/products` as well as everything under `/products`, such as `/products/shoes`, `/products/` but also `/products-for-sale`, [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```PathRegexp(`(?i)^/products`)``` |
### Query and QueryRegexp
@@ -93,9 +93,9 @@ The `Query` and `QueryRegexp` matchers allow matching requests based on query pa
|-----------------------------------------------------------------|:------------------------------------------------------------------------|
| Match requests with a `mobile` query parameter set to `true`, such as in `/search?mobile=true`. | ```Query(`mobile`, `true`)``` |
| Match requests with a query parameter `mobile` that has no value, such as in `/search?mobile`. | ```Query(`mobile`)``` |
-| Match requests with a `mobile` query parameter set to either `true` or `yes`. | ```QueryRegexp(`mobile`, `^(true\|yes)$`)``` |
+| Match requests with a `mobile` query parameter set to either `true` or `yes`. | ```QueryRegexp(`mobile`, `^(true|yes)$`)``` |
| Match requests with a `mobile` query parameter set to any value (including the empty value). | ```QueryRegexp(`mobile`, `^.*$`)``` |
-| Match query parameters [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```QueryRegexp(`mobile`, `(?i)^(true\|yes)$`)``` |
+| Match query parameters [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```QueryRegexp(`mobile`, `(?i)^(true|yes)$`)``` |
### ClientIP
diff --git a/pkg/middlewares/accesslog/logdata.go b/pkg/middlewares/accesslog/logdata.go
index 451588b8a2..c9010fdd61 100644
--- a/pkg/middlewares/accesslog/logdata.go
+++ b/pkg/middlewares/accesslog/logdata.go
@@ -78,10 +78,15 @@ const (
// TLSClientSubject is the string representation of the TLS client certificate's Subject.
TLSClientSubject = "TLSClientSubject"
- // TraceID is the consistent identifier for tracking requests across services, including upstream ones managed by Traefik, shown as a 32-hex digit string.
+ // Deprecated: TraceID is the consistent identifier for tracking requests across services, including upstream ones managed by Traefik, shown as a 32-hex digit string.
TraceID = "TraceId"
- // SpanID is the unique identifier for Traefik’s root span (EntryPoint) within a request trace, formatted as a 16-hex digit string.
+ // Deprecated: SpanID is the unique identifier for Traefik’s root span (EntryPoint) within a request trace, formatted as a 16-hex digit string.
SpanID = "SpanId"
+
+ // OTelTraceID is the OTel-conformant log attribute for the trace identifier.
+ OTelTraceID = "trace_id"
+ // OTelSpanID is the OTel-conformant log attribute for the span identifier.
+ OTelSpanID = "span_id"
)
// These are written out in the default case when no config is provided to specify keys of interest.
@@ -126,6 +131,8 @@ func init() {
allCoreKeys[TLSVersion] = struct{}{}
allCoreKeys[TLSCipher] = struct{}{}
allCoreKeys[TLSClientSubject] = struct{}{}
+ allCoreKeys[OTelTraceID] = struct{}{}
+ allCoreKeys[OTelSpanID] = struct{}{}
}
// CoreLogData holds the fields computed from the request/response.
diff --git a/pkg/middlewares/accesslog/logger.go b/pkg/middlewares/accesslog/logger.go
index a9461828c3..f0c8300910 100644
--- a/pkg/middlewares/accesslog/logger.go
+++ b/pkg/middlewares/accesslog/logger.go
@@ -209,6 +209,8 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
if spanContext.HasTraceID() && spanContext.HasSpanID() {
logDataTable.Core[TraceID] = spanContext.TraceID().String()
logDataTable.Core[SpanID] = spanContext.SpanID().String()
+ logDataTable.Core[OTelTraceID] = spanContext.TraceID().String()
+ logDataTable.Core[OTelSpanID] = spanContext.SpanID().String()
}
}
diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go
index 28bbb811b1..557f1ef0c4 100644
--- a/pkg/middlewares/accesslog/logger_test.go
+++ b/pkg/middlewares/accesslog/logger_test.go
@@ -622,6 +622,8 @@ func TestLoggerJSON(t *testing.T) {
"StartUTC": assertNotEmpty(),
TraceID: assertString("01000000000000000000000000000000"),
SpanID: assertString("0100000000000000"),
+ OTelTraceID: assertString("01000000000000000000000000000000"),
+ OTelSpanID: assertString("0100000000000000"),
},
},
{
diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go
index d09e2e751b..6525a75c38 100644
--- a/pkg/provider/kubernetes/gateway/kubernetes.go
+++ b/pkg/provider/kubernetes/gateway/kubernetes.go
@@ -1093,24 +1093,13 @@ func findMatchingHostname(h1, h2 gatev1.Hostname) gatev1.Hostname {
}
trimmedH1 := strings.TrimPrefix(string(h1), "*")
- // root domain doesn't match subdomain wildcard.
- if trimmedH1 == string(h2) {
- return ""
- }
if !strings.HasSuffix(string(h2), trimmedH1) {
return ""
}
- return lessWildcards(h1, h2)
-}
-
-func lessWildcards(h1, h2 gatev1.Hostname) gatev1.Hostname {
- if strings.Count(string(h1), "*") > strings.Count(string(h2), "*") {
- return h2
- }
-
- return h1
+ // since h1 is a suffix of h2, we know h2 is the more specific host
+ return h2
}
func allowRoute(listener gatewayListener, routeNamespace, routeKind string) bool {
diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go
index 7147419098..aebd3dd2cc 100644
--- a/pkg/provider/kubernetes/gateway/kubernetes_test.go
+++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go
@@ -7702,6 +7702,13 @@ func Test_findMatchingHostnames(t *testing.T) {
want: []gatev1.Hostname{"toto.foo.com", "test.foo.com"},
wantOk: true,
},
+ {
+ desc: "Matching wildcard subsubdomain with listener wildcard subdomain",
+ listenerHostname: ptr.To(gatev1.Hostname("*.foo.com")),
+ routeHostnames: []gatev1.Hostname{"*.bar.foo.com"},
+ want: []gatev1.Hostname{"*.bar.foo.com"},
+ wantOk: true,
+ },
}
for _, test := range testCases {