Skip to content

[TT-7519] add "original_path" and "listen_path" to observability#7791

Merged
mativm02 merged 16 commits into
masterfrom
TT-7519
Jun 9, 2026
Merged

[TT-7519] add "original_path" and "listen_path" to observability#7791
mativm02 merged 16 commits into
masterfrom
TT-7519

Conversation

@sedkis

@sedkis sedkis commented Feb 18, 2026

Copy link
Copy Markdown
Contributor
  • feat(original-path-analytics): context key, getter/setter, and capture point - step 01-01
  • feat(original-path-analytics): populate OriginalPath and ListenPath in handlers - step 01-02
  • feat(original-path-analytics): analytics worker validation and access log integration - step 02-01
  • feat(original-path-analytics): OTEL span attribute for tyk.original_path - step 02-02
  • feat(original-path-analytics): integration tests and backward compatibility verification - step 03-01
  • Add tracing integration tests

Description

Related Issue

Motivation and Context

How This Has Been Tested

Screenshots (if appropriate)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Refactoring or add test (improvements in base code or adds test coverage to functionality)

Checklist

  • I ensured that the documentation is up to date
  • I explained why this PR updates go.mod in detail with reasoning why it's required
  • I would like a code coverage CI quality gate exception and have explained why

Ticket Details

TT-7519
Status In Code Review
Summary [Innersource] Include original request path in analytics

Generated at: 2026-02-20 20:45:17

sedkis and others added 7 commits February 18, 2026 08:51
…e point - step 01-01

- Add OriginalRequestPath to ctx key enumeration after MCPRouting
- Add ctxSetOriginalRequestPath/ctxGetOriginalRequestPath in gateway/api.go
  following the existing ctxSetRequestStartTime pattern
- Capture r.URL.Path into context in handleWrapper.ServeHTTP() immediately
  after ctxSetRequestStartTime, before any middleware runs
- Acceptance test: getter/setter round-trip, empty context, encoded paths,
  trailing slashes
- Unit tests: 2 (1 parametrized with 3 cases + 1 empty context)
- Refactoring: L1+L2+L3 continuous

Step-ID: 01-01
Co-Authored-By: Claude <[email protected]>
…n handlers - step 01-02

- Bump tyk-pump dependency to local version with OriginalPath/ListenPath fields
- Success handler (RecordHit): populate OriginalPath from request context, ListenPath from API spec
- Error handler (HandleError): populate OriginalPath from request context, ListenPath from API spec
- Acceptance tests: walking skeleton (WS-1), auth failure error handler (EH-1)
- Unit tests: success handler mock (SH-1, SH-2), empty context graceful (SH-3), connection error (EH-2)
- Refactoring: L1+L2+L3 continuous

Step-ID: 01-02
Co-Authored-By: Claude <[email protected]>
… log integration - step 02-01

- Add leading "/" validation for OriginalPath in analytics recordWorker
  (guards empty string to avoid prepending "/" to "")
- Add WithOriginalPath method to accesslog.Record, following WithTraceID pattern
- Wire WithOriginalPath into BaseMiddleware.RecordAccessLog
- Add parametrized test TestRecordAccessLog_OriginalPath covering:
  AL-1: original_path present when context value is non-empty
  AL-2: original_path omitted when context value is empty

Acceptance test: TestRecordAccessLog_OriginalPath
Unit tests: 1 new (parametrized, 2 cases)
Refactoring: L1+L2+L3 continuous

Step-ID: 02-01
Co-Authored-By: Claude <[email protected]>
…ath - step 02-02

- Add OriginalPathSpanAttribute helper in internal/otel/otel.go using
  tyktrace.NewAttribute with key "tyk.original_path"
- Wire attribute into TraceMiddleware.ProcessRequest OTEL branch so every
  span carries the original client request path when non-empty
- Wire attribute into createMiddleware detailed tracing deferred span for
  consistency
- Acceptance test: OT-1 (attribute key/value) and OT-2 (empty path)
- Unit tests: 2 new (parametrized via table-driven test)
- Refactoring: none needed, minimal addition

Step-ID: 02-02
Co-Authored-By: Claude <[email protected]>
…bility verification - step 03-01

- Integration tests: INT-2 (no-strip), INT-3 (URL rewrite), INT-4 (combined strip+rewrite),
  INT-5 (root listen path), INT-6 (URL-encoded path), INT-7 (trailing slash)
- Backward compatibility: BC-1 (Path/RawPath unchanged), BC-2 (NormalisePath preserves OriginalPath)
- All 14 tests in original_path_analytics_test.go pass
- No production code changes

Step-ID: 03-01
Co-Authored-By: Claude <[email protected]>
# Conflicts:
#	gateway/middleware_test.go
#	go.mod
@sedkis
sedkis requested a review from a team as a code owner February 18, 2026 20:16
@sedkis sedkis changed the title TT 7519 [TT-7519] add "original_path" to observability Feb 18, 2026
@sedkis sedkis changed the title [TT-7519] add "original_path" to observability [TT-7519] add "original_path" and "listen_path" to observability Feb 18, 2026
@probelabs

probelabs Bot commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

This pull request introduces original_path and listen_path to the gateway's observability data, ensuring the unmodified client request path is captured and propagated to analytics, access logs, and OpenTelemetry traces.

Files Changed Analysis

The changes span 21 files, with a significant portion of the 664 new lines dedicated to testing. The addition of gateway/original_path_analytics_test.go (460 lines) and other smaller test files demonstrates a thorough approach to validation, covering various edge cases like path stripping, URL rewriting, and backward compatibility.

The core logic is concise and implemented as a vertical slice through the request pipeline:

  • The original path is captured at the request entry point (gateway/proxy_muxer.go).
  • A new context key, OriginalRequestPath, is defined in ctx/ctx.go to propagate the value.
  • The value is consumed by analytics handlers (gateway/handler_success.go, gateway/handler_error.go), access logging (internal/httputil/accesslog/record.go), and tracing middleware (gateway/middleware.go).
  • The go.mod is updated to a newer version of tyk-pump, which contains the updated AnalyticsRecord struct definition.

Architecture & Impact Assessment

  • What this PR accomplishes: It provides crucial visibility into the original client request path before any gateway-level modifications (like path stripping or URL rewrites). This enriches observability data, enabling more accurate debugging, metrics, and usage analysis.

  • Key technical changes introduced:

    1. Path Capture: The request's URL.Path is captured in proxy_muxer at the very beginning of the request lifecycle.
    2. Context Propagation: A new key, ctx.OriginalRequestPath, is used to store and pass the original path through the middleware chain.
    3. Analytics Enrichment: The AnalyticsRecord (from the tyk-pump dependency) is populated with the new OriginalPath and ListenPath fields.
    4. Access Log Integration: The structured access log now includes an original_path field.
    5. Trace Enhancement: OpenTelemetry spans are decorated with a tyk.original_path attribute for improved trace analysis.
  • Affected system components: The change touches the request handling entrypoint, context management, and the entire observability stack (analytics, logging, and tracing).

Request Path Observability Flow

graph TD
    A[Client Request<br>e.g., /api/v1/users] --> B{gateway/proxy_muxer.go};
    B --> C["Capture r.URL.Path<br>Store in request context via ctxSetOriginalRequestPath"];
    C --> D{"Middleware Chain<br>(Path Stripping, URL Rewrites, etc.)"};
    D --> E[Backend Request];

    subgraph Observability Layers
        C --|Read from context|--> F[Analytics Handlers];
        F --|Populate new fields|--> G[Analytics Record<br>OriginalPath: /api/v1/users<br>ListenPath: /api/v1/];

        C --|Read from context|--> H[Access Log Middleware];
        H --|Add new field|--> I[Access Log Record<br>original_path: /api/v1/users];

        C --|Read from context|--> J[Tracing Middleware];
        J --|Add new attribute|--> K[OTel Span<br>tyk.original_path: /api/v1/users];
    end
Loading

Scope Discovery & Context Expansion

This PR implements a well-contained vertical feature slice. It enhances the data quality of existing observability systems without altering core request processing logic. The impact extends from the gateway's entry point to all downstream observability consumers.

The update to the tyk-pump dependency in go.mod is critical, as it brings in the necessary changes to the analytics.AnalyticsRecord struct to support the new fields. The extensive tests, particularly the backward compatibility checks in gateway/original_path_analytics_test.go, confirm that existing fields like Path and RawPath are unaffected, ensuring this is a non-breaking change for existing analytics consumers.

Metadata
  • Review Effort: 3 / 5
  • Primary Label: feature

Powered by Visor from Probelabs

Last updated: 2026-06-09T16:38:54.587Z | Triggered by: pr_updated | Commit: 3f0c9fe

💡 TIP: You can chat with Visor using /visor ask <your question>

@probelabs

probelabs Bot commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

Security Issues (2)

Severity Location Issue
🟡 Warning gateway/proxy_muxer.go:70
The original request path is captured before any middleware processing. This means if the path contains sensitive information (e.g., session tokens, PII, API keys), it will be recorded in plaintext in logs, analytics, and traces. This bypasses any subsequent middleware that might have been designed to strip or obfuscate such sensitive data. The feature's design intentionally avoids normalization for this field, increasing the risk of persisting sensitive data.
💡 SuggestionWhile preserving the original path is the feature's goal, this behavior should be explicitly and prominently documented in security and operational guides. Administrators should be warned that this field is not sanitized and may contain sensitive data, so they can assess the risk and apply appropriate access controls to the downstream observability systems.
🟡 Warning gateway/proxy_muxer.go:70
The original request path is captured from user input (`r.URL.Path`) and propagated to downstream observability systems (logs, analytics, traces) without sanitization. This could lead to a stored Cross-Site Scripting (XSS) vulnerability if the consuming systems (e.g., analytics dashboards, log viewers) do not properly escape this value before rendering it in a UI.
💡 SuggestionEnsure that all downstream consumers of this data (like the Tyk Dashboard) treat the `original_path` field as untrusted user input and apply context-aware output encoding to prevent XSS. This responsibility should be clearly documented.

Architecture Issues (2)

Severity Location Issue
🟡 Warning gateway/api.go:3382
The getter function `ctxGetOriginalRequestPath` directly accesses the request context. This is inconsistent with the established pattern in this file where context access is centralized in the `getCtxValue` helper function to handle type assertions and nil checks uniformly.
💡 SuggestionRefactor `ctxGetOriginalRequestPath` to use the existing `getCtxValue` helper. This will improve consistency, centralize context-handling logic, and make the code more robust and maintainable.
🟡 Warning internal/httputil/accesslog/record.go:86
The `WithOriginalPath` function duplicates the logic for retrieving a value from the request context, which is already encapsulated in `gateway.ctxGetOriginalRequestPath`. This creates redundant code and tightly couples the `accesslog` package to the implementation details of how the original path is stored in the context.
💡 SuggestionTo improve separation of concerns and reduce duplication, `WithOriginalPath` should accept the original path as a string argument instead of the entire `*http.Request`. The caller (in `gateway/middleware.go`) should be responsible for retrieving the value from the context using `ctxGetOriginalRequestPath` and passing it to the access log recorder.

✅ Performance Check Passed

No performance issues found – changes LGTM.

Quality Issues (1)

Severity Location Issue
🟡 Warning gateway/middleware.go:144-146
The `tyk.original_path` OpenTelemetry span attribute is set redundantly. It is correctly set once when the main request span is created in `TraceMiddleware.ProcessRequest` (lines 91-93). This same logic is duplicated in the deferred function within `createMiddleware`, causing the attribute to be set again for each instrumented middleware in the chain. While this is functionally harmless, it is unnecessary code duplication.
💡 SuggestionRemove the redundant code from the `createMiddleware` function. The attribute is already set at the beginning of the request lifecycle in `TraceMiddleware`, which is sufficient.

Powered by Visor from Probelabs

Last updated: 2026-06-09T16:38:47.869Z | Triggered by: pr_updated | Commit: 3f0c9fe

💡 TIP: You can chat with Visor using /visor ask <your question>

@github-actions

github-actions Bot commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

API Changes

--- prev.txt	2026-02-20 20:46:05.845362158 +0000
+++ current.txt	2026-02-20 20:45:56.385325381 +0000
@@ -6166,7 +6166,9 @@
 	//
 	// Template Options:
 	//
+	// - `api_id` will include the API ID.
 	// - `api_key` will include the obfuscated or hashed key.
+	// - `api_name` will include the API name.
 	// - `circuit_breaker_state` will include the circuit breaker state when applicable.
 	// - `client_ip` will include the IP of the request.
 	// - `error_source` will include the source of an error (e.g., ReverseProxy).
@@ -6176,6 +6178,7 @@
 	// - `latency_total` will include the total latency of the request.
 	// - `method` will include the request method.
 	// - `org_id` will include the organization ID.
+	// - `original_path` will include the original request path before URL rewrites.
 	// - `path` will include the path of the request.
 	// - `protocol` will include the protocol of the request.
 	// - `remote_addr` will include the remote address of the request.
@@ -8583,6 +8586,8 @@
 	JSONRPCRoutingState
 	// MCPRouting indicates the request came via MCP JSON-RPC routing
 	MCPRouting
+	// OriginalRequestPath stores the original request path before any middleware modifications
+	OriginalRequestPath
 )
 # Package: ./dlpython
 
@@ -9719,11 +9724,6 @@
 
 func (s *APISpec) FireEvent(name apidef.TykEvent, meta interface{})
 
-func (a *APISpec) GetPRMConfig() *oas.ProtectedResourceMetadata
-    GetPRMConfig returns the Protected Resource Metadata configuration if the
-    API is an OAS API Definition (OAS API, MCP Proxy, Stream API) with PRM
-    enabled. Returns nil otherwise.
-
 func (a *APISpec) GetSessionLifetimeRespectsKeyExpiration() bool
     GetSessionLifetimeRespectsKeyExpiration returns a boolean to tell whether
     session lifetime should respect to key expiration or not. The global config
@@ -9793,7 +9793,7 @@
 
 func (k *AuthKey) Name() string
 
-func (k *AuthKey) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int)
+func (k *AuthKey) ProcessRequest(_ http.ResponseWriter, r *http.Request, _ interface{}) (error, int)
 
 type AuthORWrapper struct {
 	BaseMiddleware
@@ -11555,21 +11555,6 @@
 
 func (k *OrganizationMonitor) SetOrgSentinel(orgChan chan bool, orgId string)
 
-type PRMMiddleware struct {
-	*BaseMiddleware
-}
-    PRMMiddleware intercepts GET requests to the PRM well-known path and serves
-    the OAuth 2.0 Protected Resource Metadata document (RFC 9728). It runs
-    after MiddlewareContextVars (so $tyk_context.* is available) but before
-    authentication middlewares, allowing the endpoint to be accessed without
-    auth.
-
-func (m *PRMMiddleware) EnabledForSpec() bool
-
-func (m *PRMMiddleware) Name() string
-
-func (m *PRMMiddleware) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int)
-
 type PersistGraphQLOperationMiddleware struct {
 	*BaseMiddleware
 }

@sedkis

sedkis commented Feb 18, 2026

Copy link
Copy Markdown
Contributor Author

/review The original request path is captured before any middleware processing. This means if the path contains sensitive information (e.g., session tokens, PII, API keys), it will be recorded in plaintext in logs, analytics, and traces. This bypasses any subsequent middleware that might have been designed to strip or obfuscate such sensitive data from the request before it is recorded. The feature's design, as confirmed by tests, intentionally avoids normalization for this field, increasing the risk of persisting sensitive data.. this is already how this works today in rawPath

@sedkis

sedkis commented Feb 18, 2026

Copy link
Copy Markdown
Contributor Author

/review The go.mod file contains a replace directive that points to a local directory (../tyk-pump). This makes the project's build dependent on a specific local file structure, which breaks reproducible builds for CI/CD systems and other developers. Local replace directives should not be merged into the main branch.

i have reverted the changes to go.mod and "replace", i removed them

@probelabs

probelabs Bot commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

Powered by Visor from Probelabs

Last updated: 2026-02-18T20:55:05.737Z | Triggered by: sedkis | Commit: ee893a5

💡 TIP: You can chat with Visor using /visor ask <your question>

@probelabs

probelabs Bot commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

Thank you for confirming the removal of the local replace directive from go.mod. This resolves the concern about reproducible builds.


Powered by Visor from Probelabs

Last updated: 2026-02-18T20:23:54.270Z | Triggered by: sedkis | Commit: ee893a5

💡 TIP: You can chat with Visor using /visor ask <your question>

@probelabs

probelabs Bot commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

Thank you for pointing out this important security consideration. You are correct that original_path is captured before any middleware processing, which means it will not be sanitized by subsequent middleware.

This behavior is intentional and consistent with how the existing rawPath field works. The goal of this feature is to provide the unmodified, original request path for observability and debugging, especially in scenarios involving path stripping or URL rewriting.

The test TestOriginalPathAnalytics_BackwardCompat_NormalisePathDoesNotAffectOriginalPath in gateway/original_path_analytics_test.go confirms that normalization logic is deliberately not applied to OriginalPath to ensure the raw value is preserved.

We acknowledge the risk of persisting sensitive data, and this behavior requires that operators are aware of what information they are logging.

References:


Powered by Visor from Probelabs

Last updated: 2026-02-18T20:24:00.144Z | Triggered by: sedkis | Commit: ee893a5

💡 TIP: You can chat with Visor using /visor ask <your question>

@sedkis

sedkis commented Feb 18, 2026

Copy link
Copy Markdown
Contributor Author

/review The original request path is captured from user input (r.URL.Path) and propagated to downstream observability systems (logs, analytics, traces) without sanitization. This could lead to a stored Cross-Site Scripting (XSS) vulnerability if the consuming systems do not properly escape this value before rendering it in a U

WE follow existing implementation for RawPath and Path, we dont sanitize for XSS, we trust the UI to sanitize accordingly.

@probelabs

probelabs Bot commented Feb 18, 2026

Copy link
Copy Markdown
Contributor

Thank you for the clarification. You are correct that this approach aligns with the existing strategy for handling RawPath and Path, where the raw, unsanitized request path is captured at the gateway.

This ensures that the original, unmodified path is available for accurate logging and analytics. The responsibility for context-aware escaping and sanitization correctly lies with the downstream systems (e.g., Tyk Dashboard, logging UIs) that consume and render this observability data.

References:


Powered by Visor from Probelabs

Last updated: 2026-02-18T20:54:58.194Z | Triggered by: sedkis | Commit: ee893a5

💡 TIP: You can chat with Visor using /visor ask <your question>

@sedkis

sedkis commented Feb 20, 2026

Copy link
Copy Markdown
Contributor Author

CI needs to be re-triggered after PUMP is merged, and the go.mod bumped

@MFCaballero

Copy link
Copy Markdown
Contributor

@sedkis can you please resolve the conflicts? 🙏

@mativm02
mativm02 enabled auto-merge (squash) June 9, 2026 12:08
# Conflicts:
#	ci/tests/tracing/scenarios/tyk_versioned_403.yml
#	ctx/ctx.go
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🎯 Recommended Merge Targets

Based on JIRA ticket TT-7519: [Innersource] Include original request path in analytics

Fix Version: Tyk 5.14.0

⚠️ Warning: Expected release branches not found in repository

Required:

  • master - No matching release branches found. Fix will be included in future releases.

📋 Workflow

  1. Merge this PR to master first

@mativm02
mativm02 disabled auto-merge June 9, 2026 12:43
@mativm02 mativm02 added the deps-reviewed Dependency changes reviewed and approved for CI execution label Jun 9, 2026
@mativm02
mativm02 enabled auto-merge (squash) June 9, 2026 15:25
- errcheck: check errors from http.NewRequest and json.Unmarshal in
  original_path_analytics_test.go
- gci: drop duplicate tyk/ctx import (merge artifact) in middleware_test.go,
  use ctxpkg alias consistently

Clears new_reliability_rating bugs so the SonarCloud quality gate passes.
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

🚨 Jira Linter Failed

Commit: 3f0c9fe
Failed at: 2026-06-09 16:37:43 UTC

The Jira linter failed to validate your PR. Please check the error details below:

🔍 Click to view error details
failed to get Jira issue: failed to fetch Jira issue TT-7519: Issue does not exist or you do not have permission to see it.: request failed. Please analyze the request body for more details. Status code: 404

Next Steps

  • Ensure your branch name contains a valid Jira ticket ID (e.g., ABC-123)
  • Verify your PR title matches the branch's Jira ticket ID
  • Check that the Jira ticket exists and is accessible

This comment will be automatically deleted once the linter passes.

@sonarqubecloud

sonarqubecloud Bot commented Jun 9, 2026

Copy link
Copy Markdown

Quality Gate Passed Quality Gate passed

Issues
0 New issues
0 Accepted issues

Measures
0 Security Hotspots
95.7% Coverage on New Code
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

@mativm02
mativm02 merged commit 0852872 into master Jun 9, 2026
33 of 60 checks passed
@mativm02
mativm02 deleted the TT-7519 branch June 9, 2026 17:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deps-reviewed Dependency changes reviewed and approved for CI execution

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants