Skip to content

Conversation

@renuka-fernando
Copy link
Contributor

@renuka-fernando renuka-fernando commented Jan 22, 2026

Purpose

Fix #753
Discussion: #748

Summary by CodeRabbit

  • New Features

    • Added a CLI option to support a system-level policy manifest lock.
  • Bug Fixes

    • Removed per-route request header mode setting from policy engine configuration.
  • Improvements

    • Unified and enhanced per-request logging across many policies for better observability.
  • Dependencies

    • Bumped multiple internal libraries and tooling to newer minor/patch versions.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 22, 2026

Walkthrough

Introduces standardized policy logging by injecting a *slog.Logger into policy factories and adding helpers for request-scoped loggers; updates many policies to accept and use the logger. Removes request_header_mode configuration and hardcodes ext_proc processing modes. Multiple module dependency bumps and a VSCode launch argument were added.

Changes

Cohort / File(s) Summary
Logging infra & API
sdk/gateway/policy/v1alpha/interface.go, sdk/gateway/policy/v1alpha/logger.go, gateway/policy-engine/internal/registry/registry.go
Added logger utilities (EnsureLogger, WithRequestID), extended PolicyFactory signature to accept logger *slog.Logger, and changed registry to create/pass policy-scoped loggers to factories.
Policy implementations (signatures & logging)
gateway/policies/.../*.go, gateway/system-policies/analytics/v0.1.0/analytics.go, gateway/policy-engine/...
Many policies gained logger *slog.Logger fields; GetPolicy signatures now accept logger; replaced global slog calls with policy- or request-scoped loggers and threaded requestID into helper calls. Tests updated where applicable.
Policy module dependency bumps
gateway/policies/*/*/go.mod, gateway/sample-policies/*/*/go.mod, gateway/policy-engine/go.mod, sdk/go.mod, cli/it/go.mod, gateway/it/go.mod
Bumped github.com/wso2/api-platform/sdk across modules and updated various indirect dependencies (Prometheus, AWS SDK, redis, milvus, yaml v2, etc.).
Request header mode removal & xDS change
gateway/configs/config.yaml, gateway/gateway-controller/pkg/config/config.go, gateway/gateway-controller/pkg/xds/translator.go, kubernetes/.../*, kubernetes/helm/.../*
Removed request_header_mode configuration and its validation from configs; translator now hardcodes ext_proc ProcessingMode fields (request/response header/body/trailer modes).
Policy factory wiring & registry usage
gateway/policy-engine/internal/registry/..., sdk/gateway/policy/v1alpha/interface.go
Registry now constructs a logger per policy (slog.Default().With("policy", name)) and invokes factories with (metadata, params, logger), updating the factory contract.
Tooling / launch config
.vscode/launch.json
Added -system-manifest-lock CLI arg to Gateway Builder launch configuration.

Sequence Diagram(s)

sequenceDiagram
    participant Registry as Registry
    participant LoggerSvc as Logger Creator
    participant Factory as PolicyFactory
    participant Policy as Policy Instance
    participant Request as Request Handler

    Registry->>LoggerSvc: create logger with policy name<br/>slog.Default().With("policy", name)
    LoggerSvc-->>Registry: *slog.Logger (policy-scoped)
    Registry->>Factory: invoke Factory(metadata, params, logger)
    Factory->>Policy: initialize policy with logger field
    Policy-->>Factory: return policy instance
    Factory-->>Registry: policy ready
    Registry-->>Request: return configured policy

    Request->>Policy: OnRequest(ctx, params)
    Policy->>Policy: derive scoped logger<br/>WithRequestID(p.logger, ctx.RequestID)
    Policy-->>Request: RequestAction (logs via scoped logger)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I stitched a logger, neat and bright,

policy names tucked into light.
Request IDs hop into view,
Each policy speaks in tones true.
Logs aligned — a carrot of delight!

🚥 Pre-merge checks | ✅ 2 | ❌ 3
❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is minimal, providing only a reference to the linked issue (#753) and discussion (#748) without covering required template sections like Goals, Approach, User Stories, Documentation, Testing, Security checks, or Test environment. Complete the PR description by filling out all required sections from the template: Goals, Approach, User Stories, Documentation, Automation tests (unit and integration), Security checks, Samples, Related PRs, and Test environment.
Out of Scope Changes check ⚠️ Warning Most changes are scoped to the logging standardization objective. However, some policy files include behavioral changes (e.g., sentence count and word count guardrails with new validation logic) that go beyond logger injection, and configuration removals of request_header_mode appear unrelated to logging standardization. Clarify whether non-logging behavioral changes in guardrails and request_header_mode removals are necessary for the logging standardization feature or should be separated into distinct PRs for clearer review scope.
Docstring Coverage ⚠️ Warning Docstring coverage is 46.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately reflects the main objective of the PR: implementing standardized logging for policy developers by injecting a pre-configured logger.
Linked Issues check ✅ Passed The PR successfully implements the main objectives from issue #753: injecting a logger parameter into PolicyFactory, updating GetPolicy signatures across all policies to accept a logger, using policy.EnsureLogger and policy.WithRequestID for consistent logging, and updating SDK dependencies.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@renuka-fernando renuka-fernando changed the title Stream Standardized Logging for Policy Developers Jan 22, 2026
@renuka-fernando renuka-fernando marked this pull request as ready for review January 22, 2026 10:35
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
gateway/policies/advanced-ratelimit/v0.1.0/ratelimit.go (1)

264-279: Critical: logger field not assigned in struct initialization.

The logger parameter is accepted in GetPolicy but never assigned to the returned RateLimitPolicy struct. This will cause a nil pointer dereference panic when OnRequest or OnResponse calls policy.WithRequestID(p.logger, ctx.RequestID).

🐛 Proposed fix
 	// Return configured policy instance
 	return &RateLimitPolicy{
+		logger:         logger,
 		quotas:         quotas,
 		routeName:      routeName,
 		apiId:          apiId,
gateway/policies/model-weighted-round-robin/v0.1.0/weightedroundrobin.go (1)

528-556: Consider whether exposing internal error details in responses is acceptable.

The error responses include raw error messages (e.g., err.Error() at lines 533, 543, 554) which could expose internal implementation details to clients. While helpful for debugging during development, this may not be desired in production.

If this is intentional for this policy's use case, consider documenting that decision. Otherwise, consider using generic error messages for client-facing responses while logging the detailed errors.

gateway/system-policies/analytics/v0.1.0/analytics.go (1)

60-107: Logger injection is currently a no-op (and singleton blocks request-scoped logging).

Line 65 adds a logger parameter, but it’s ignored and slog.* is still used. Returning the shared ins also prevents request-scoped logger usage and would be racy if you later set the logger on it. Please instantiate a policy per GetPolicy call and route logs through the injected logger (with a safe fallback).

🔧 Proposed fix
-type AnalyticsPolicy struct{}
+type AnalyticsPolicy struct {
+	logger *slog.Logger
+}
 
-var ins = &AnalyticsPolicy{}
-
 func GetPolicy(
 	metadata policy.PolicyMetadata,
 	params map[string]interface{},
 	logger *slog.Logger,
 ) (policy.Policy, error) {
-	return ins, nil
+	if logger == nil {
+		logger = slog.Default()
+	}
+	return &AnalyticsPolicy{logger: logger}, nil
 }
@@
 func (a *AnalyticsPolicy) OnRequest(ctx *policy.RequestContext, params map[string]interface{}) policy.RequestAction {
-	slog.Info("Analytics system policy: OnRequest called")
+	l := a.logger
+	if l == nil {
+		l = slog.Default()
+	}
+	l.Info("Analytics system policy: OnRequest called")
@@
 	default:
-		slog.Error("Invalid API kind")
+		l.Error("Invalid API kind")
 	}
 	return nil
 }
@@
 func (p *AnalyticsPolicy) OnResponse(ctx *policy.ResponseContext, params map[string]interface{}) policy.ResponseAction {
-	slog.Info("Analytics system policy: OnResponse called")
+	l := p.logger
+	if l == nil {
+		l = slog.Default()
+	}
+	l.Info("Analytics system policy: OnResponse called")
@@
-	slog.Debug("API kind: ", "apiKind", apiKind)
+	l.Debug("API kind: ", "apiKind", apiKind)
@@
-		slog.Debug("Provider template param from policy: ", "providerTemplate", providerTemplate)
+		l.Debug("Provider template param from policy: ", "providerTemplate", providerTemplate)
@@
-				slog.Warn("Failed to extract LLM token info", "error", err)
+				l.Warn("Failed to extract LLM token info", "error", err)
@@
-				slog.Debug("Extracted LLM token info",
+				l.Debug("Extracted LLM token info",
 					"promptTokens", tokenInfo.PromptTokens,
 					"completionTokens", tokenInfo.CompletionTokens,
 					"totalTokens", tokenInfo.TotalTokens,
 					"remainingTokens", tokenInfo.RemainingTokens,
 					"requestModel", tokenInfo.RequestModel,
 					"responseModel", tokenInfo.ResponseModel,
 					"providerName", tokenInfo.ProviderName,
 					"providerDisplayName", tokenInfo.ProviderDisplayName,
 				)
@@
 	default:
-		slog.Error("Invalid API kind")
+		l.Error("Invalid API kind")
 	}
gateway/policies/prompt-decorator/v0.1.0/promptdecorator.go (1)

169-172: Use a client-error status for empty request bodies.

An empty body is a client input issue; returning 500 misclassifies it as a server fault. Consider a 4xx response for this branch.

🛠️ Suggested fix (example with 400)
-	if ctx.Body == nil || len(content) == 0 {
-		return p.buildErrorResponse("Empty request body", nil)
-	}
+	if ctx.Body == nil || len(content) == 0 {
+		return policy.ImmediateResponse{
+			StatusCode: 400,
+			Headers: map[string]string{
+				"Content-Type": "application/json",
+			},
+			Body: []byte(`{"type":"PROMPT_DECORATOR_ERROR","message":"Empty request body"}`),
+		}
+	}
gateway/policies/semantic-cache/v0.1.0/semanticcache.go (1)

55-63: Guard against nil logger to prevent panic during initialization and request logging.

GetPolicy stores the logger directly without validation and immediately calls p.logger.Debug at line 89. If a nil logger is passed, this causes an immediate panic. Additionally, OnRequest (line 318) and OnResponse (line 403) call policy.WithRequestID(p.logger, ...), which also panics if the logger is nil since WithRequestID does not guard against nil receivers. Default to slog.Default() to ensure a safe fallback.

🛠️ Suggested fix
-	p := &SemanticCachePolicy{
-		logger: logger,
-	}
+	if logger == nil {
+		logger = slog.Default()
+	}
+	p := &SemanticCachePolicy{
+		logger: logger,
+	}

Also applies to: 89-89

gateway/policies/mcp-auth/v0.1.0/mcp-auth.go (1)

41-58: Incomplete logger integration: logger parameter is received but not stored or used consistently.

The GetPolicy function accepts a logger parameter but:

  1. McpAuthPolicy struct has no logger field to store it
  2. Only line 56 uses the injected logger; all other logging throughout the file uses the global slog package directly (lines 80, 90, 94, 121, 162, 166, 192, 223, 232, 235, etc.)

This defeats the PR's objective of standardized logging with automatic policy identification in logs.

Proposed fix: Add logger field and use it throughout
-type McpAuthPolicy struct{}
+type McpAuthPolicy struct {
+	logger *slog.Logger
+}

-var ins = &McpAuthPolicy{}
+var ins *McpAuthPolicy

 func GetPolicy(
 	metadata policy.PolicyMetadata,
 	params map[string]any,
 	logger *slog.Logger,
 ) (policy.Policy, error) {
+	if ins == nil {
+		ins = &McpAuthPolicy{logger: logger}
+	}
 	logger.Debug("GetPolicy called")
 	return ins, nil
 }

Then replace all slog.Debug(...) calls with p.logger.Debug(...) throughout the file.

🤖 Fix all issues with AI agents
In `@gateway/policies/advanced-ratelimit/v0.1.0/go.mod`:
- Line 8: Update the SDK dependency versions across all policy modules so they
are consistent with advanced-ratelimit's go.mod
(github.com/wso2/api-platform/sdk v0.3.3); locate each policy's go.mod (e.g.,
basic-ratelimit, jwt-auth, mcp-auth, sentence-count-guardrail, url-guardrail,
word-count-guardrail, mcp-authz) and change any occurrences of v0.3.0 or v0.3.2
to v0.3.3, ensuring any local replace directives remain correct and running go
mod tidy in each module to verify dependency resolution.

In `@gateway/policies/aws-bedrock-guardrail/v0.1.0/awsbedrockguardrail.go`:
- Around line 61-73: GetPolicy stores the provided logger into
AWSBedrockGuardrailPolicy without nil-checking which can cause panics when later
calling p.logger.Debug or policy.WithRequestID; update GetPolicy to test if
logger == nil and if so set logger = slog.Default() (or return an error), then
assign it to the AWSBedrockGuardrailPolicy instance; also apply the same
nil-guard/default to other constructors/places that create
AWSBedrockGuardrailPolicy or call policy.WithRequestID so all uses (e.g., places
referenced by AWSBedrockGuardrailPolicy and policy.WithRequestID) never hold a
nil logger.

In `@gateway/policies/jwt-auth/v0.1.0/jwtauth.go`:
- Around line 98-105: GetPolicy currently ignores the provided logger and
returns the singleton ins that uses package-level slog calls in
OnRequest/OnResponse and helpers; modify the policy to carry a scoped logger by
adding a logger field to the policy instance returned by GetPolicy (e.g., set
ins.logger = logger or better: construct a new policy instance that embeds the
existing JWKS cache but has its own logger), replace all slog.* usages in
OnRequest, OnResponse and helper functions to use that instance logger, and if
ins must remain shared for the JWKS cache, extract the cache into a separate
struct (e.g., jwksCache) so you can instantiate per-policy wrappers with their
own logger while reusing the shared cache.

In `@gateway/policies/mcp-auth/v0.1.0/mcp-auth.go`:
- Around line 162-163: The policy forwards authentication to jwtauth.GetPolicy
but passes slog.Default() instead of the policy's provided logger, losing
context; update the MCP auth policy to store the incoming logger on the policy
struct (e.g., add a logger field on the struct used by GetPolicy creation) and
pass that stored logger to jwtauth.GetPolicy (replace slog.Default() with the
struct logger reference), ensuring the call that creates jwtPolicy (jwtPolicy, _
:= jwtauth.GetPolicy(...)) uses the forwarded logger so delegated logs retain
the policy-specific context.

In `@gateway/policies/mcp-authz/v0.1.0/mcp-authz.go`:
- Around line 77-83: GetPolicy receives a request-scoped logger but never stores
it on the McpAuthzPolicy, causing downstream methods to use global slog; modify
GetPolicy to set the provided logger on the McpAuthzPolicy instance and update
the policy struct to include a logger field (e.g., logger *slog.Logger), then
replace calls to slog.Debug()/global logger in OnRequest and all helper methods
with p.logger.Debug()/p.logger.<level> so they use the stored request-scoped
logger throughout the policy lifecycle.

In `@gateway/policies/url-guardrail/v0.1.0/go.mod`:
- Line 5: go.mod declares dependency github.com/wso2/api-platform/sdk at v0.3.2
but go.sum still has hashes for v0.3.1; update the module metadata by running a
module fetch/repair (for example run go get
github.com/wso2/api-platform/sdk@v0.3.2 and then go mod tidy) to regenerate
go.sum so the sdk version in go.mod and go.sum match; verify the updated entries
for github.com/wso2/api-platform/sdk now reference v0.3.2.

In `@gateway/system-policies/analytics/v0.1.0/go.mod`:
- Line 5: The go.mod was bumped to require github.com/wso2/api-platform/sdk
v0.3.2 but go.sum still only has v0.3.1 checksums; update go.sum by fetching the
new module checksums (e.g., run go get github.com/wso2/api-platform/sdk@v0.3.2
or go mod tidy / go mod download) so the v0.3.2 entries are added, then commit
the updated go.sum alongside the change to go.mod.
🧹 Nitpick comments (9)
gateway/policies/model-weighted-round-robin/v0.1.0/weightedroundrobin.go (2)

495-513: Consider deferring logger creation to reduce allocations.

The logger created at line 496 is only used in the default case, while all other branches call helper methods that create their own request-scoped loggers. This results in an unnecessary allocation for the common paths.

♻️ Suggested optimization
 func (p *ModelWeightedRoundRobinPolicy) modifyRequestModel(ctx *policy.RequestContext, newModel string, requestID string) policy.RequestAction {
-	log := policy.WithRequestID(p.logger, requestID)
 	location := p.params.RequestModel.Location
 	identifier := p.params.RequestModel.Identifier

 	switch location {
 	case "payload":
 		return p.modifyModelInPayload(ctx, newModel, identifier, requestID)
 	case "header":
 		return p.modifyModelInHeader(ctx, newModel, identifier, requestID)
 	case "queryParam":
 		return p.modifyModelInQueryParam(ctx, newModel, identifier, requestID)
 	case "pathParam":
 		return p.modifyModelInPathParam(ctx, newModel, identifier, requestID)
 	default:
+		log := policy.WithRequestID(p.logger, requestID)
 		log.Debug("Unsupported location", "location", location)
 		return policy.UpstreamRequestModifications{}
 	}
 }

650-655: Optional: Pre-compile regex pattern during policy initialization.

The regex pattern is compiled on every request, but since it comes from configuration and doesn't change, it could be pre-compiled once in GetPolicy and stored in the policy struct. This would avoid repeated compilation overhead for high-traffic scenarios.

gateway/gateway-controller/pkg/xds/translator.go (1)

1849-1854: The hardcoded processing modes are appropriate for the target Envoy version.

The chosen modes align well with the project's target Envoy version (1.36.0+). FULL_DUPLEX_STREAMED for body processing was introduced in Envoy 1.34.0, so there are no version compatibility concerns.

Consider adding a brief inline comment explaining why these specific modes were chosen (e.g., "streaming body support enables policy inspection and modification").

sdk/gateway/policy/v1alpha/logger.go (1)

5-16: Consider nil-safety for WithRequestID to match defensive patterns used elsewhere in the codebase.

The slog.Logger.With() method is not nil-safe and will panic if called on a nil logger. While current usage passes initialized loggers from PolicyFactory, adding a defensive guard aligns with nil-check patterns found in other parts of the codebase (e.g., gateway/gateway-controller/pkg/utils/api_key.go, combined_cache.go).

♻️ Suggested defensive guard
 func WithRequestID(logger *slog.Logger, requestID string) *slog.Logger {
+	if logger == nil {
+		logger = slog.Default()
+	}
 	return logger.With("requestId", requestID)
 }
gateway/policies/basic-auth/v0.1.0/basicauth.go (1)

20-33: Consider emitting auth outcome via the injected logger (without credentials).

The policy now receives a standardized logger but doesn’t use it. A small debug log on success/failure (without usernames/passwords) would align with the PR’s logging objective and help trace auth behavior.

💡 Example (no credential logging)
func (p *BasicAuthPolicy) handleAuthSuccess(ctx *policy.RequestContext, username string) policy.RequestAction {
+	if p.logger != nil {
+		log := policy.WithRequestID(p.logger, ctx.RequestID)
+		log.Debug("Basic auth success")
+	}
 	// Set metadata indicating successful authentication
 	ctx.Metadata[MetadataKeyAuthSuccess] = true
 	ctx.Metadata[MetadataKeyAuthUser] = username
 	ctx.Metadata[MetadataKeyAuthMethod] = "basic"
@@
func (p *BasicAuthPolicy) handleAuthFailure(ctx *policy.RequestContext, allowUnauthenticated bool, realm string, reason string) policy.RequestAction {
+	if p.logger != nil {
+		log := policy.WithRequestID(p.logger, ctx.RequestID)
+		log.Debug("Basic auth failed", "reason", reason, "allowUnauthenticated", allowUnauthenticated)
+	}
 	// Set metadata indicating failed authentication
 	ctx.Metadata[MetadataKeyAuthSuccess] = false
 	ctx.Metadata[MetadataKeyAuthMethod] = "basic"
gateway/policies/json-schema-guardrail/v0.1.0/jsonschemaguardrail.go (1)

146-146: Consider type-safe alternatives to interface{} return with type assertions.

The current pattern returns interface{} from validatePayload and relies on type assertions in callers. While currently safe (the isResponse flag ensures correct types), this is fragile for future maintenance.

Consider splitting into validateRequestPayload and validateResponsePayload methods that return the correct types directly, or using generics.

Also applies to: 159-159

gateway/policies/modify-headers/v0.1.0/modifyheaders.go (1)

28-41: Logger field is added but never used.

The logger field is injected into ModifyHeadersPolicy but there are no logging statements in this policy. For consistency with other policies (e.g., json-schema-guardrail, cors), consider adding debug logs for:

  • Policy initialization in GetPolicy
  • Header modifications in OnRequest/OnResponse

If logging is intentionally omitted for this policy, consider documenting why or removing the unused field.

Example addition for GetPolicy
 func GetPolicy(
 	metadata policy.PolicyMetadata,
 	params map[string]interface{},
 	logger *slog.Logger,
 ) (policy.Policy, error) {
+	logger.Debug("GetPolicy called")
 	p := &ModifyHeadersPolicy{
 		logger: logger,
 	}
 	return p, nil
 }
gateway/policies/cors/v0.1.0/cors.go (1)

151-152: Minor: Request-scoped logger created redundantly in helper methods.

Both handlePreflight and handleNonPreflight create their own request-scoped logger, even though they're called from OnRequest which already has one. Consider passing the logger as a parameter to avoid creating redundant logger instances.

This is a minor optimization and the current approach keeps helper methods self-contained.

gateway/policy-engine/internal/registry/registry.go (1)

23-23: Consider adding policy version to logger context for disambiguation.

Using only the policy name can make logs ambiguous when multiple versions are deployed simultaneously; adding version to the context keeps logs traceable.

Also applies to: 129-133

Comment on lines 98 to 105
func GetPolicy(
metadata policy.PolicyMetadata,
params map[string]interface{},
logger *slog.Logger,
) (policy.Policy, error) {
slog.Debug("JWT Auth Policy: GetPolicy called")
logger.Debug("GetPolicy called")
return ins, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check the overall structure of the file and the GetPolicy function
cat -n gateway/policies/jwt-auth/v0.1.0/jwtauth.go | head -120

Repository: wso2/api-platform

Length of output: 4404


🏁 Script executed:

# Track remaining global slog usage in this policy
rg -n '\bslog\.' gateway/policies/jwt-auth/v0.1.0/jwtauth.go

Repository: wso2/api-platform

Length of output: 10792


🏁 Script executed:

# Check the OnRequest function to see what logger it's using
rg -A 20 'func.*OnRequest' gateway/policies/jwt-auth/v0.1.0/jwtauth.go | head -40

Repository: wso2/api-platform

Length of output: 1239


🏁 Script executed:

# Check the policy struct definition to see if logger is stored
rg -B 5 -A 10 'type.*Policy struct' gateway/policies/jwt-auth/v0.1.0/jwtauth.go

Repository: wso2/api-platform

Length of output: 466


🏁 Script executed:

# Verify the logger is never used after GetPolicy returns
rg -n 'logger\.' gateway/policies/jwt-auth/v0.1.0/jwtauth.go

Repository: wso2/api-platform

Length of output: 98


Injected logger isn't propagated to request logs (objective gap).

GetPolicy accepts a logger but the policy continues to use global slog in OnRequest and helpers (90+ occurrences), so standardized/request-scoped logging is not achieved. The singleton ins also lacks a logger field, making it risky to store per-policy loggers without risk of cross-policy contamination.

Consider storing the logger on the policy instance and propagating it through OnRequest/OnResponse, replacing slog.* calls with the scoped logger. If ins must remain for shared JWKS cache, extract the cache into a separate struct to keep the logger instance-scoped.

🤖 Prompt for AI Agents
In `@gateway/policies/jwt-auth/v0.1.0/jwtauth.go` around lines 98 - 105, GetPolicy
currently ignores the provided logger and returns the singleton ins that uses
package-level slog calls in OnRequest/OnResponse and helpers; modify the policy
to carry a scoped logger by adding a logger field to the policy instance
returned by GetPolicy (e.g., set ins.logger = logger or better: construct a new
policy instance that embeds the existing JWKS cache but has its own logger),
replace all slog.* usages in OnRequest, OnResponse and helper functions to use
that instance logger, and if ins must remain shared for the JWKS cache, extract
the cache into a separate struct (e.g., jwksCache) so you can instantiate
per-policy wrappers with their own logger while reusing the shared cache.

Comment on lines 162 to +163
slog.Debug("MCP Auth Policy: Delegating authentication to JWT Auth Policy")
jwtPolicy, _ := jwtauth.GetPolicy(policy.PolicyMetadata{}, params)
jwtPolicy, _ := jwtauth.GetPolicy(policy.PolicyMetadata{}, params, slog.Default())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Logger not forwarded to delegated JWT auth policy.

slog.Default() is passed to jwtauth.GetPolicy instead of forwarding the logger that was provided to this policy. This breaks the standardized logging chain and loses any policy-specific context that would be attached to the logger.

Proposed fix

Store the logger in the struct (as suggested above), then:

-	jwtPolicy, _ := jwtauth.GetPolicy(policy.PolicyMetadata{}, params, slog.Default())
+	jwtPolicy, _ := jwtauth.GetPolicy(policy.PolicyMetadata{}, params, p.logger)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
slog.Debug("MCP Auth Policy: Delegating authentication to JWT Auth Policy")
jwtPolicy, _ := jwtauth.GetPolicy(policy.PolicyMetadata{}, params)
jwtPolicy, _ := jwtauth.GetPolicy(policy.PolicyMetadata{}, params, slog.Default())
slog.Debug("MCP Auth Policy: Delegating authentication to JWT Auth Policy")
jwtPolicy, _ := jwtauth.GetPolicy(policy.PolicyMetadata{}, params, p.logger)
🤖 Prompt for AI Agents
In `@gateway/policies/mcp-auth/v0.1.0/mcp-auth.go` around lines 162 - 163, The
policy forwards authentication to jwtauth.GetPolicy but passes slog.Default()
instead of the policy's provided logger, losing context; update the MCP auth
policy to store the incoming logger on the policy struct (e.g., add a logger
field on the struct used by GetPolicy creation) and pass that stored logger to
jwtauth.GetPolicy (replace slog.Default() with the struct logger reference),
ensuring the call that creates jwtPolicy (jwtPolicy, _ :=
jwtauth.GetPolicy(...)) uses the forwarded logger so delegated logs retain the
policy-specific context.

Comment on lines 77 to 83
func GetPolicy(
metadata policy.PolicyMetadata,
params map[string]any,
logger *slog.Logger,
) (policy.Policy, error) {
slog.Debug("MCP Authorization Policy: GetPolicy called")
logger.Debug("GetPolicy called")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "mcp-authz.go" | head -20

Repository: wso2/api-platform

Length of output: 109


🏁 Script executed:

rg -l "mcp-authz" --type go | head -20

Repository: wso2/api-platform

Length of output: 43


🏁 Script executed:

fd "mcp-authz\.go$"

Repository: wso2/api-platform

Length of output: 107


🏁 Script executed:

cat -n "gateway/policies/mcp-authz/v0.1.0/mcp-authz.go"

Repository: wso2/api-platform

Length of output: 23584


Store logger in McpAuthzPolicy and use it throughout the policy lifecycle.

The logger parameter in GetPolicy is never stored in the McpAuthzPolicy struct, so logging in OnRequest and all helper methods falls back to global slog.Debug() calls. This loses the request-scoped logging context passed at initialization. Store the logger on the struct and propagate it through all methods for consistent, context-aware logging.

🔧 Suggested fix (store logger in policy)
 type McpAuthzPolicy struct {
-	Rules []Rule
+	Rules  []Rule
+	logger *slog.Logger
 }
 
 func GetPolicy(
 	metadata policy.PolicyMetadata,
 	params map[string]any,
 	logger *slog.Logger,
 ) (policy.Policy, error) {
 	logger.Debug("GetPolicy called")
 
-	p := &McpAuthzPolicy{}
+	p := &McpAuthzPolicy{
+		logger: logger,
+	}
🤖 Prompt for AI Agents
In `@gateway/policies/mcp-authz/v0.1.0/mcp-authz.go` around lines 77 - 83,
GetPolicy receives a request-scoped logger but never stores it on the
McpAuthzPolicy, causing downstream methods to use global slog; modify GetPolicy
to set the provided logger on the McpAuthzPolicy instance and update the policy
struct to include a logger field (e.g., logger *slog.Logger), then replace calls
to slog.Debug()/global logger in OnRequest and all helper methods with
p.logger.Debug()/p.logger.<level> so they use the stored request-scoped logger
throughout the policy lifecycle.

go 1.25.1

require github.com/wso2/api-platform/sdk v0.3.1
require github.com/wso2/api-platform/sdk v0.3.2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

github.com/wso2/api-platform/sdk releases versions changelog

💡 Result:

I found the WSO2 API Platform repository and its Releases (which include release notes/changelogs) on GitHub:

Open the Releases page to see available versions, tags, and each release’s changelog/notes. References: [1], [2].


🏁 Script executed:

# Search for PR `#754` references in the codebase
rg "754" --type-list | head -20
cd gateway/policies/url-guardrail/v0.1.0 && cat go.mod

Repository: wso2/api-platform

Length of output: 582


🏁 Script executed:

# Search for logging-related changes in the policy module
rg "logging|log\." gateway/policies/url-guardrail/ -A 2 -B 2

Repository: wso2/api-platform

Length of output: 2968


🏁 Script executed:

# Check if there are other SDK version references in the repo
rg "wso2/api-platform/sdk" -A 1 -B 1

Repository: wso2/api-platform

Length of output: 31626


Resolve go.mod/go.sum version mismatch for SDK dependency.

The go.mod specifies github.com/wso2/api-platform/sdk v0.3.2, but go.sum contains a hash for v0.3.1. These versions must match. Run go get -u or explicitly update go.sum to align with the intended v0.3.2 version declared in go.mod.

🤖 Prompt for AI Agents
In `@gateway/policies/url-guardrail/v0.1.0/go.mod` at line 5, go.mod declares
dependency github.com/wso2/api-platform/sdk at v0.3.2 but go.sum still has
hashes for v0.3.1; update the module metadata by running a module fetch/repair
(for example run go get github.com/wso2/api-platform/sdk@v0.3.2 and then go mod
tidy) to regenerate go.sum so the sdk version in go.mod and go.sum match; verify
the updated entries for github.com/wso2/api-platform/sdk now reference v0.3.2.

go 1.25.1

require github.com/wso2/api-platform/sdk v0.3.1
require github.com/wso2/api-platform/sdk v0.3.2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if go.mod file exists and contains the new SDK version
if [ -f "gateway/system-policies/analytics/v0.1.0/go.mod" ]; then
  echo "=== go.mod content ==="
  cat -n "gateway/system-policies/analytics/v0.1.0/go.mod"
  echo ""
fi

# Check for corresponding go.sum file
if [ -f "gateway/system-policies/analytics/v0.1.0/go.sum" ]; then
  echo "=== go.sum entries for SDK v0.3.2 ==="
  rg -n "github.com/wso2/api-platform/sdk v0.3.2" "gateway/system-policies/analytics/v0.1.0/go.sum"
  echo ""
  echo "=== All SDK-related entries in go.sum ==="
  rg -n "github.com/wso2/api-platform/sdk" "gateway/system-policies/analytics/v0.1.0/go.sum"
else
  echo "go.sum file not found at gateway/system-policies/analytics/v0.1.0/go.sum"
fi

Repository: wso2/api-platform

Length of output: 505


Update go.sum with v0.3.2 checksums.

The go.mod specifies github.com/wso2/api-platform/sdk v0.3.2, but go.sum only contains checksums for v0.3.1. This mismatch will cause builds with -mod=readonly to fail. The v0.3.2 checksums must be added to go.sum.

🤖 Prompt for AI Agents
In `@gateway/system-policies/analytics/v0.1.0/go.mod` at line 5, The go.mod was
bumped to require github.com/wso2/api-platform/sdk v0.3.2 but go.sum still only
has v0.3.1 checksums; update go.sum by fetching the new module checksums (e.g.,
run go get github.com/wso2/api-platform/sdk@v0.3.2 or go mod tidy / go mod
download) so the v0.3.2 entries are added, then commit the updated go.sum
alongside the change to go.mod.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
gateway/policies/advanced-ratelimit/v0.1.0/ratelimit.go (1)

71-279: Initialize and persist the injected logger to avoid nil deref.

GetPolicy now accepts logger and uses it before the policy is created, but the returned RateLimitPolicy does not store it. That leaves p.logger nil and causes a panic in policy.WithRequestID(p.logger, ...) (and in logger.Warn/Debug if caller passes nil). Please default and persist the logger.

🛠️ Proposed fix
 func GetPolicy(
 	metadata policy.PolicyMetadata,
 	params map[string]interface{},
 	logger *slog.Logger,
 ) (policy.Policy, error) {
+	if logger == nil {
+		logger = slog.Default()
+	}
 	// Store route name for default key
 	routeName := metadata.RouteName
 	if routeName == "" {
 		routeName = "unknown-route"
 	}
@@
 	// Return configured policy instance
 	return &RateLimitPolicy{
+		logger:         logger,
 		quotas:         quotas,
 		routeName:      routeName,
 		apiId:          apiId,
 		apiName:        apiName,
 		apiVersion:     apiVersion,
gateway/policies/model-round-robin/v0.1.0/roundrobin.go (1)

230-261: Remove racing field from log statement.

p.currentIndex is read at line 258 without mutex protection, while it can be modified concurrently at line 303 (in selectNextAvailableModel) under the mutex. This creates a data race condition under concurrent requests. The simplest fix is to remove the index from the log.

-	log.Debug("Selected model", "model", selectedModel.Model, "index", p.currentIndex)
+	log.Debug("Selected model", "model", selectedModel.Model)
gateway/policies/model-weighted-round-robin/v0.1.0/weightedroundrobin.go (1)

260-291: Remove racy currentIndex from log statement.

p.currentIndex is incremented inside the mutex within selectNextAvailableWeightedModel(), but read outside the lock at line 288. With concurrent requests, this causes a data race. Remove the field from the debug log.

Fix
-	log.Debug("Selected model", "model", selectedModel.Model, "weight", selectedModel.Weight, "index", p.currentIndex)
+	log.Debug("Selected model", "model", selectedModel.Model, "weight", selectedModel.Weight)
🤖 Fix all issues with AI agents
In `@gateway/gateway-controller/pkg/xds/translator.go`:
- Around line 1849-1856: The ProcessingMode block in translator.go currently
uses extproc.ProcessingMode_FULL_DUPLEX_STREAMED for RequestBodyMode and
ResponseBodyMode which prevents ModeOverride messages; change those two to
extproc.ProcessingMode_BUFFERED (or SKIP for headers-only flows) so Envoy can
accept per-request ModeOverride from the policy engine, remove the hardcoded
FULL_DUPLEX_STREAMED constraint, and keep AllowModeOverride=true so
getModeOverride() can dynamically switch to BUFFERED or NONE as needed.

In `@gateway/policies/prompt-template/v0.1.0/prompttemplate.go`:
- Around line 23-26: GetPolicy can receive a nil logger which causes parseParams
(calls logger.Debug) and WithRequestID (calls logger.With) to panic; update
GetPolicy to guard the logger field by defaulting nil to slog.Default (or
equivalent) before constructing PromptTemplatePolicy so all uses in parseParams
and WithRequestID are safe—ensure PromptTemplatePolicy.logger is set to the
non-nil logger, then continue to set params via parseParams and use
WithRequestID without additional nil checks.

In `@gateway/policies/sentence-count-guardrail/v0.1.0/sentencecountguardrail.go`:
- Line 28: The struct's logger field may be nil which will cause panics when
calling p.logger.Debug and policy.WithRequestID(p.logger, ...) (which calls
.With()) — fix by normalizing the logger on construction/assignment: if the
provided logger is nil, replace it with a safe no-op or default slog.Logger
(e.g., slog.Default() or a new logger) before storing in the struct so all uses
(p.logger.Debug, policy.WithRequestID, any .With calls) are safe; update the
constructor/initializer that sets logger and any code paths that assign to the
logger field to perform this nil-check and substitution.

In `@gateway/policies/word-count-guardrail/v0.1.0/wordcountguardrail.go`:
- Around line 27-29: The policy currently stores a potentially nil *slog.Logger
which can cause a panic when later used (e.g., policy.WithRequestID(p.logger,
...)); update the code that constructs/initializes WordCountGuardrailPolicy (and
any places assigning to the logger field) to check for nil and set policy.logger
= slog.Default() when nil (or return an error if you prefer), so every use of
p.logger (including WithRequestID) is safe; apply the same nil-check/defaulting
pattern around assignments to WordCountGuardrailPolicy.logger in the
constructor/initializers referenced (the struct, its constructor/initializer
functions and any setter locations).
♻️ Duplicate comments (5)
gateway/policies/url-guardrail/v0.1.0/go.mod (1)

5-5: Ensure go.sum matches the updated SDK version.

If not already done, update go.sum so it aligns with github.com/wso2/api-platform/sdk v0.3.2.

#!/bin/bash
# Verify that go.sum entries match the required sdk version.
rg -n 'github.com/wso2/api-platform/sdk' -C2 go.sum
gateway/policies/mcp-authz/v0.1.0/mcp-authz.go (1)

73-95: Store the injected logger on the policy and use it for request-scoped logs.
GetPolicy receives a logger but it is not retained, so downstream logging still uses global slog. This defeats the standardized, request-scoped logging goal. Please store the logger on McpAuthzPolicy and route logs through it (ideally via policy.WithRequestID in request paths).

🔧 Minimal fix for logger retention
 type McpAuthzPolicy struct {
-	Rules []Rule
+	Rules  []Rule
+	logger *slog.Logger
 }

 func GetPolicy(
 	metadata policy.PolicyMetadata,
 	params map[string]any,
 	logger *slog.Logger,
 ) (policy.Policy, error) {
 	logger.Debug("GetPolicy called")

-	p := &McpAuthzPolicy{}
+	p := &McpAuthzPolicy{
+		logger: logger,
+	}
gateway/policies/jwt-auth/v0.1.0/jwtauth.go (1)

98-104: Injected logger still not used for request logs (standardized logging gap).

Line 103 logs once, but the policy returns the singleton ins that continues to use package-level slog throughout OnRequest and helpers, so request-scoped logging isn’t achieved. Consider storing a logger per policy instance (or a wrapper with shared cache) and replacing slog.* calls.

gateway/policies/mcp-auth/v0.1.0/mcp-auth.go (1)

51-57: Forward injected logger to JWT policy (and store it).

Line 163 still uses slog.Default(), so delegated logs lose the policy context; the logger parameter in GetPolicy is not preserved.

🛠️ Possible fix
-type McpAuthPolicy struct{}
+type McpAuthPolicy struct {
+	logger *slog.Logger
+}

-func GetPolicy(
-	metadata policy.PolicyMetadata,
-	params map[string]any,
-	logger *slog.Logger,
-) (policy.Policy, error) {
-	logger.Debug("GetPolicy called")
-	return ins, nil
-}
+func GetPolicy(
+	metadata policy.PolicyMetadata,
+	params map[string]any,
+	logger *slog.Logger,
+) (policy.Policy, error) {
+	if logger == nil {
+		logger = slog.Default()
+	}
+	logger.Debug("GetPolicy called")
+	return &McpAuthPolicy{logger: logger}, nil
+}
-	jwtPolicy, _ := jwtauth.GetPolicy(policy.PolicyMetadata{}, params, slog.Default())
+	jwtPolicy, _ := jwtauth.GetPolicy(policy.PolicyMetadata{}, params, p.logger)

Also applies to: 162-164

gateway/policies/aws-bedrock-guardrail/v0.1.0/awsbedrockguardrail.go (1)

31-35: Guard against nil logger to avoid panic.

If the factory passes nil, p.logger.Debug(...) (Line 138) and policy.WithRequestID(p.logger, ...) (Line 334) will panic. Defaulting to slog.Default() (or returning an error) avoids this.

🛠️ Suggested fix
func GetPolicy(
	metadata policy.PolicyMetadata,
	params map[string]interface{},
	logger *slog.Logger,
) (policy.Policy, error) {
+	if logger == nil {
+		logger = slog.Default()
+	}
	// Validate and extract static configuration from params
	if err := validateAWSConfigParams(params); err != nil {
		return nil, fmt.Errorf("invalid params: %w", err)
	}

	p := &AWSBedrockGuardrailPolicy{
		logger:           logger,
		region:           getStringParam(params, "region"),
		guardrailID:      getStringParam(params, "guardrailID"),
		guardrailVersion: getStringParam(params, "guardrailVersion"),
	}

Also applies to: 61-75, 138-138, 316-329, 333-441

🧹 Nitpick comments (3)
gateway/policies/pii-masking-regex/v0.1.0/piimaskingregex.go (2)

96-96: Consider precompiling regex patterns for better performance.

The pattern ^[A-Z_]+$ is compiled on every iteration of the validation loop. Since this is a constant pattern, precompile it as a package-level variable similar to textCleanRegexCompiled on line 22.

♻️ Suggested improvement

Add at package level:

var piiEntityNamePattern = regexp.MustCompile(`^[A-Z_]+$`)

Then use:

-			if !regexp.MustCompile(`^[A-Z_]+$`).MatchString(piiEntity) {
+			if !piiEntityNamePattern.MatchString(piiEntity) {

251-251: Precompile placeholder pattern at package level.

This pattern is compiled on every call to maskPIIFromContent. Since it's constant, move it to a package-level variable for efficiency.

♻️ Suggested improvement

Add at package level:

var placeholderPattern = regexp.MustCompile(`^\[[A-Z_]+_[0-9a-f]{4}\]$`)

Then remove the local declaration at line 251.

gateway/policies/mcp-auth/v0.1.0/go.mod (1)

5-5: Confirm SDK version alignment with the rest of the policy modules.
Most policies in this PR appear to be on v0.3.3; this one remains v0.3.2. Please verify this is intentional to avoid SDK interface drift across policy modules; otherwise, align to v0.3.3.

♻️ Possible alignment update
-require github.com/wso2/api-platform/sdk v0.3.2
+require github.com/wso2/api-platform/sdk v0.3.3

Comment on lines 1849 to 1856
ProcessingMode: &extproc.ProcessingMode{
RequestHeaderMode: requestHeaderMode,
RequestHeaderMode: extproc.ProcessingMode_SEND,
RequestBodyMode: extproc.ProcessingMode_FULL_DUPLEX_STREAMED,
RequestTrailerMode: extproc.ProcessingMode_SEND,
ResponseHeaderMode: extproc.ProcessingMode_SEND,
ResponseBodyMode: extproc.ProcessingMode_FULL_DUPLEX_STREAMED,
ResponseTrailerMode: extproc.ProcessingMode_SEND,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cd /tmp && find . -name "translator.go" -path "*/xds/*" 2>/dev/null | head -5

Repository: wso2/api-platform

Length of output: 178


🏁 Script executed:

# First, let's locate the file
git ls-files | grep -E "gateway.*translator\.go$"

Repository: wso2/api-platform

Length of output: 161


🏁 Script executed:

# Let's get the file structure to understand the context better
fd translator.go | grep -E "xds|gateway"

Repository: wso2/api-platform

Length of output: 161


🏁 Script executed:

# Let's examine the specific file and lines
if [ -f "gateway/gateway-controller/pkg/xds/translator.go" ]; then
  wc -l gateway/gateway-controller/pkg/xds/translator.go
fi

Repository: wso2/api-platform

Length of output: 114


🏁 Script executed:

# Read the relevant section around lines 1849-1856
if [ -f "gateway/gateway-controller/pkg/xds/translator.go" ]; then
  sed -n '1820,1880p' gateway/gateway-controller/pkg/xds/translator.go | cat -n
fi

Repository: wso2/api-platform

Length of output: 2913


🏁 Script executed:

# Check function definition to understand context
if [ -f "gateway/gateway-controller/pkg/xds/translator.go" ]; then
  sed -n '1800,1870p' gateway/gateway-controller/pkg/xds/translator.go | cat -n
fi

Repository: wso2/api-platform

Length of output: 3252


🏁 Script executed:

# Search for FULL_DUPLEX_STREAMED usage in the codebase
rg "FULL_DUPLEX_STREAMED" --type go

Repository: wso2/api-platform

Length of output: 296


🏁 Script executed:

# Search for ProcessingMode configuration patterns
rg "ProcessingMode" --type go -A 3 -B 1

Repository: wso2/api-platform

Length of output: 28131


🏁 Script executed:

# Check for any policy-related configuration or mode_override patterns
rg "mode_override|request_header_mode|body.*mode" --type go -i

Repository: wso2/api-platform

Length of output: 14250


🏁 Script executed:

# Check if AllowModeOverride is actually configured/used
rg "AllowModeOverride" --type go -B 2 -A 2

Repository: wso2/api-platform

Length of output: 1579


🏁 Script executed:

# Look for ModeOverride usage in the policy engine
rg "ModeOverride" --type go -B 3 -A 3

Repository: wso2/api-platform

Length of output: 5122


🏁 Script executed:

# Check the config structure to understand AllowModeOverride
rg "type.*PolicyEngine|type Config" --type go gateway/gateway-controller/pkg/config/ | head -20

Repository: wso2/api-platform

Length of output: 288


🏁 Script executed:

# Look for where the mode override is actually sent to Envoy
rg "extproc.*ProcessingMode|ModeOverride" --type go -A 5 | head -50

Repository: wso2/api-platform

Length of output: 4446


🌐 Web query:

Envoy ext_proc ProcessingMode FULL_DUPLEX_STREAMED vs BUFFERED modes documentation

💡 Result:

Short summary — main differences

  • BUFFERED: Envoy buffers the entire request/response body (up to configured limits) and sends it to the external processor as a single message. If the buffer limit is exceeded the body is truncated or an error is produced. Use when the processor needs the whole body at once (e.g., full-body inspection/mutation). [1][2]

  • FULL_DUPLEX_STREAMED: Body data is sent as a bidirectional stream of chunks (full‑duplex). Envoy and the external processor can concurrently stream body chunks and the processor may send streamed responses (including streamed immediate responses). This mode disables some per‑message semantics (different response rules, different timeout behavior) and is intended for low‑latency, streaming interactions where processor can act on chunks as they arrive. Mode overrides are not supported for FULL_DUPLEX_STREAMED. [1][3]

Notable operational details

  • BUFFERED requires correct handling of content-length for mutated bodies (Envoy may allow content-length only in certain buffered+send combinations; mismatches cause rejection). FULL_DUPLEX_STREAMED (and several other streamed modes) will commonly cause Envoy to remove Content-Length and use chunked transfer encoding. [2]
  • Message timeouts / response sequencing differ: when not FULL_DUPLEX_STREAMED, Envoy expects one ProcessingResponse per ProcessingRequest; FULL_DUPLEX_STREAMED follows its own streaming API and has different timeout/response rules. Observability mode and flow-control warnings also apply. [1][3]

Sources

  • Envoy ext_proc filter docs / ext_proc.proto (ProcessingMode and behavior). [1]
  • processing_mode.proto (BodySendMode enum descriptions — BUFFERED, FULL_DUPLEX_STREAMED, and content-length notes). [2]
  • external_processor.proto (ProtocolConfiguration and ProcessingResponse rules for FULL_DUPLEX_STREAMED). [3]

Change initial ProcessingMode to support dynamic mode overrides.

The hardcoded ProcessingMode_FULL_DUPLEX_STREAMED for body modes prevents Envoy from accepting ModeOverride messages, which breaks the policy-engine's dynamic optimization mechanism.

Per Envoy ext_proc documentation: mode overrides are not supported when initial body mode is FULL_DUPLEX_STREAMED. Since your config sets AllowModeOverride: true and the policy-engine implements intelligent getModeOverride() (returning BUFFERED only when policies need body access, NONE otherwise), the initial mode should be:

  • ProcessingMode_BUFFERED (or SKIP for headers-only) — allows per-request ModeOverride messages
  • Remove the hardcoded FULL_DUPLEX_STREAMED constraint

This way, header-only policies (authentication, rate-limiting) will skip body buffering, and only policies requiring body inspection will trigger buffering. With current config, all requests incur full-duplex streaming overhead regardless of actual policy requirements.

Code location
ProcessingMode: &extproc.ProcessingMode{
    RequestHeaderMode:   extproc.ProcessingMode_SEND,
    RequestBodyMode:     extproc.ProcessingMode_FULL_DUPLEX_STREAMED,  // ← change to BUFFERED
    RequestTrailerMode:  extproc.ProcessingMode_SEND,
    ResponseHeaderMode:  extproc.ProcessingMode_SEND,
    ResponseBodyMode:    extproc.ProcessingMode_FULL_DUPLEX_STREAMED,  // ← change to BUFFERED
    ResponseTrailerMode: extproc.ProcessingMode_SEND,
},
🤖 Prompt for AI Agents
In `@gateway/gateway-controller/pkg/xds/translator.go` around lines 1849 - 1856,
The ProcessingMode block in translator.go currently uses
extproc.ProcessingMode_FULL_DUPLEX_STREAMED for RequestBodyMode and
ResponseBodyMode which prevents ModeOverride messages; change those two to
extproc.ProcessingMode_BUFFERED (or SKIP for headers-only flows) so Envoy can
accept per-request ModeOverride from the policy engine, remove the hardcoded
FULL_DUPLEX_STREAMED constraint, and keep AllowModeOverride=true so
getModeOverride() can dynamically switch to BUFFERED or NONE as needed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@gateway/policies/advanced-ratelimit/v0.1.0/ratelimit.go`:
- Around line 72-76: The logger may be nil when GetPolicy is entered and
logger.Warn(...) is called before it's ensured; call policy.EnsureLogger(logger)
at the start of GetPolicy (immediately after the function params) to guarantee a
non-nil logger, then replace subsequent calls that re-run EnsureLogger (the
later EnsureLogger call near policy creation) by using the already-initialized
logger directly; update references to logger.Warn(...) and other logger uses to
assume the ensured logger.
- Around line 233-237: The logger is used before it's guaranteed non-nil in
GetPolicy (e.g., in the q.Limiter cached branch where logger.Debug is called and
later at the other debug calls); call EnsureLogger at the start of GetPolicy and
use the returned/ensured logger value for all subsequent logging instead of the
original logger variable (update usages around q.Limiter assignment and the
debug calls in the cached/else branches to reference the ensured logger).

In `@gateway/policies/model-weighted-round-robin/v0.1.0/go.mod`:
- Around line 3-5: The go.mod currently requires a non-existent tag
"github.com/wso2/api-platform/sdk v0.3.5"; update the require line in go.mod to
point to a published tag for that module (for example replace v0.3.5 with an
available release such as v0.3.4 or the repository's latest stable tag) so that
`require github.com/wso2/api-platform/sdk` references a valid version; run `go
mod tidy` afterwards to verify and update go.sum.

In `@gateway/policies/semantic-cache/v0.1.0/go.mod`:
- Around line 7-8: Remove the invalid duplicate YAML module entry in go.mod:
delete the line declaring "go.yaml.in/yaml/v2 v2.4.2" so only the canonical
"gopkg.in/yaml.v2 v2.4.0" remains; keep the prometheus entries and the
intentional SDK bump "github.com/wso2/api-platform/sdk v0.3.5" as-is.
♻️ Duplicate comments (5)
gateway/policies/mcp-authz/v0.1.0/mcp-authz.go (2)

80-95: Logger injection is correctly implemented in GetPolicy, but the storage issue remains.

The addition of the logger parameter and the use of policy.EnsureLogger(logger) correctly follows the SDK pattern. The logger is properly used for the two debug calls within GetPolicy.

However, as noted in the existing review comment, the logger is not stored in the McpAuthzPolicy struct, so all calls in OnRequest and helper methods (lines 218-271, 322-329, 338-341, 362-380, 424-441, 455-477, 510-570, 595-597) still use the global slog.Debug(). This defeats the purpose of the standardized logging initiative.


216-273: OnRequest and downstream methods use global slog instead of the policy logger.

Once the logger is stored in the McpAuthzPolicy struct as suggested in the existing review, all slog.Debug() calls in OnRequest and helper methods (checkAuthorization, findMatchingRules, checkClaims, checkScopes, generateResourcePath, generateWwwAuthenticateHeader, handleAuthFailure) should be updated to use p.logger.Debug() for consistent, context-aware logging.

Additionally, consider using policy.WithRequestID(logger, ctx.RequestID) at the start of OnRequest to include the request ID in all subsequent log entries, as other policies in this PR appear to do.

gateway/system-policies/analytics/v0.1.0/go.mod (1)

5-5: Ensure go.sum is updated with v0.3.5 checksums before merging.

The go.mod file requires SDK v0.3.5, but go.sum contains only v0.3.4 checksums. This mismatch will cause build failures. Run go mod tidy or go mod download to sync go.sum with the required v0.3.5 version, which exists as a valid release (tag sdk/v0.3.5).

gateway/policies/jwt-auth/v0.1.0/jwtauth.go (1)

98-104: Injected logger still isn’t propagated to policy/request logs.

Same issue as earlier: GetPolicy accepts a logger but the singleton policy continues to rely on global slog elsewhere, so standardized/request-scoped logging isn’t achieved.

gateway/policies/prompt-template/v0.1.0/prompttemplate.go (1)

44-49: Pass p.logger instead of raw logger to parseParams to avoid potential nil dereference.

At line 45, policy.EnsureLogger(logger) guarantees p.logger is non-nil. However, line 49 passes the original logger parameter (which may be nil) to parseParams, which then calls logger.Debug() at line 117 without a nil guard. This will panic if GetPolicy is called with a nil logger.

Proposed fix
 	p := &PromptTemplatePolicy{
 		logger: policy.EnsureLogger(logger),
 	}

 	// Parse parameters
-	policyParams, err := parseParams(params, logger)
+	policyParams, err := parseParams(params, p.logger)
🧹 Nitpick comments (4)
gateway/policies/prompt-decorator/v0.1.0/promptdecorator.go (2)

329-331: Consider passing the per-request logger instead of requestID to avoid redundant logger creation.

Both updateStringAtPath and updateArrayAtPath create new per-request loggers via policy.WithRequestID(p.logger, requestID), even though the caller (OnRequest) already has a per-request logger. Passing the logger directly would be more efficient and cleaner.

♻️ Suggested refactor
-func (p *PromptDecoratorPolicy) updateStringAtPath(payloadData map[string]interface{}, jsonPath string, value string, requestID string) policy.RequestAction {
-	log := policy.WithRequestID(p.logger, requestID)
+func (p *PromptDecoratorPolicy) updateStringAtPath(payloadData map[string]interface{}, jsonPath string, value string, log *slog.Logger) policy.RequestAction {

-func (p *PromptDecoratorPolicy) updateArrayAtPath(payloadData map[string]interface{}, jsonPath string, value []map[string]interface{}, requestID string) policy.RequestAction {
-	log := policy.WithRequestID(p.logger, requestID)
+func (p *PromptDecoratorPolicy) updateArrayAtPath(payloadData map[string]interface{}, jsonPath string, value []map[string]interface{}, log *slog.Logger) policy.RequestAction {

Then update the call sites in OnRequest to pass log directly.

Also applies to: 371-373


169-172: Early return for empty body returns a 500 error without logging.

When the request body is nil or empty, buildErrorResponse is called but the error is not logged. Consider adding a debug log for consistency with other error paths.

♻️ Suggested addition
 	// Check for empty or nil content before unmarshaling
 	if ctx.Body == nil || len(content) == 0 {
+		log.Debug("Empty request body")
 		return p.buildErrorResponse("Empty request body", nil)
 	}
gateway/policies/cors/v0.1.0/cors.go (1)

133-148: Consider passing the per-request logger to helper methods.

OnRequest creates a per-request logger at line 134, but handlePreflight (line 153) and handleNonPreflight (line 281) each create their own per-request logger. Similarly, OnResponse (line 267) creates one. Since these helpers are always called from methods that already have a per-request logger, consider passing it as a parameter to avoid redundant logger creation.

Also applies to: 151-153, 266-276, 279-281

gateway/policies/api-key-auth/v0.1.0/apikey.go (1)

205-227: Consider accepting per-request logger as parameter in helper methods.

Both handleAuthSuccess and handleAuthFailure create their own per-request loggers via policy.WithRequestID(p.logger, ctx.RequestID), but they are always called from OnRequest which already has a per-request logger. Passing the logger as a parameter would be cleaner.

Also applies to: 234-278

Comment on lines 72 to 76
func GetPolicy(
metadata policy.PolicyMetadata,
params map[string]interface{},
logger *slog.Logger,
) (policy.Policy, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Logger used before being ensured — potential nil pointer dereference.

At line 180, logger.Warn(...) is called, but policy.EnsureLogger(logger) isn't called until line 265 when the policy is created. If a nil logger is passed to GetPolicy, this will panic.

Move the EnsureLogger call to the beginning of the function.

🛠️ Suggested fix
 func GetPolicy(
 	metadata policy.PolicyMetadata,
 	params map[string]interface{},
 	logger *slog.Logger,
 ) (policy.Policy, error) {
+	logger = policy.EnsureLogger(logger)
+
 	// Store route name for default key
 	routeName := metadata.RouteName

Then at line 265, simply use logger directly:

 	return &RateLimitPolicy{
-		logger:         policy.EnsureLogger(logger),
+		logger:         logger,

Also applies to: 180-181

🤖 Prompt for AI Agents
In `@gateway/policies/advanced-ratelimit/v0.1.0/ratelimit.go` around lines 72 -
76, The logger may be nil when GetPolicy is entered and logger.Warn(...) is
called before it's ensured; call policy.EnsureLogger(logger) at the start of
GetPolicy (immediately after the function params) to guarantee a non-nil logger,
then replace subsequent calls that re-run EnsureLogger (the later EnsureLogger
call near policy creation) by using the already-initialized logger directly;
update references to logger.Warn(...) and other logger uses to assume the
ensured logger.

Comment on lines 233 to 237
q.Limiter = cached.(limiter.Limiter)
slog.Debug("Reusing cached memory limiter",
logger.Debug("Reusing cached memory limiter",
"route", routeName, "apiName", apiName,
"quota", q.Name, "cacheKey", cacheKey[:16])
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same nil logger issue affects these log calls.

Lines 234 and 256 also use logger directly before EnsureLogger is called. These will also panic if a nil logger is passed. Fixing the issue at the start of GetPolicy (as suggested above) will address all these cases.

Also applies to: 255-259

🤖 Prompt for AI Agents
In `@gateway/policies/advanced-ratelimit/v0.1.0/ratelimit.go` around lines 233 -
237, The logger is used before it's guaranteed non-nil in GetPolicy (e.g., in
the q.Limiter cached branch where logger.Debug is called and later at the other
debug calls); call EnsureLogger at the start of GetPolicy and use the
returned/ensured logger value for all subsequent logging instead of the original
logger variable (update usages around q.Limiter assignment and the debug calls
in the cached/else branches to reference the ensured logger).

Comment on lines +3 to +5
go 1.25.1

require github.com/wso2/api-platform/sdk v0.3.1
require github.com/wso2/api-platform/sdk v0.3.5
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Go 1.25.1 release

💡 Result:

Go 1.25.1 — released September 3, 2025. It’s a minor/security patch for 1.25 that includes security fixes to net/http and bug fixes to the go command, net, os, os/exec, and testing/synctest packages (see Go 1.25.1 milestone/release notes). [1][2]

If you need links to the full release notes, changelog, or downloads, tell me which (notes, security advisories/CVEs, or binaries/source). [1] Go release history. [2] SUSE changelog summarizing 1.25.1 fixes.


🌐 Web query:

github.com/wso2/api-platform/sdk v0.3.5 release tag

💡 Result:

I couldn't find a git tag/release named v0.3.5 for github.com/wso2/api-platform/sdk. The SDK lives under the wso2/api-platform monorepo (see pkg.go.dev showing the sdk path and noting no tagged version), and WSO2's GitHub org repositories list but no sdk v0.3.5 release. [1][2]

Sources:

  • pkg.go.dev listing for github.com/wso2/api-platform/sdk (shows no tagged version). [1]
  • WSO2 GitHub / repositories overview. [2]

Update SDK dependency to an available version.

Go 1.25.1 is valid. However, github.com/wso2/api-platform/sdk v0.3.5 does not exist as a published release tag. Update the require statement to use an available SDK version that exists in the wso2/api-platform repository.

🤖 Prompt for AI Agents
In `@gateway/policies/model-weighted-round-robin/v0.1.0/go.mod` around lines 3 -
5, The go.mod currently requires a non-existent tag
"github.com/wso2/api-platform/sdk v0.3.5"; update the require line in go.mod to
point to a published tag for that module (for example replace v0.3.5 with an
available release such as v0.3.4 or the repository's latest stable tag) so that
`require github.com/wso2/api-platform/sdk` references a valid version; run `go
mod tidy` afterwards to verify and update go.sum.

Comment on lines +7 to 8
github.com/wso2/api-platform/sdk v0.3.5
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n gateway/policies/semantic-cache/v0.1.0/go.mod

Repository: wso2/api-platform

Length of output: 7221


🌐 Web query:

github.com/wso2/api-platform/sdk v0.3.5 Go module release

💡 Result:

Do you want me to (A) confirm whether the Go module version v0.3.5 exists and provide links, or (B) show how to fetch/use that version (go get / go.mod example)?


🌐 Web query:

prometheus/client_golang v1.23.2 release Go module

💡 Result:

Yes — prometheus/client_golang v1.23.2 is released (tag: v1.23.2, 2025-09-05). It’s the module github.com/prometheus/client_golang and the release upgrades prometheus/common to v0.66.1 with no functional changes. [1][2]

Install via Go modules:

  • go get github.com/prometheus/client_golang@v1.23.2
    Or add to go.mod:
  • require github.com/prometheus/client_golang v1.23.2

Sources:
[1] GitHub release (v1.23.2).
[2] pkg.go.dev module page.


🏁 Script executed:

# Check other gateway policy go.mod files to see if they have replace directives
find gateway/policies -name "go.mod" -type f | head -5 | xargs -I {} sh -c 'echo "=== {} ===" && grep -E "^replace" {} || echo "No replace directives"'

Repository: wso2/api-platform

Length of output: 508


🌐 Web query:

github.com/wso2/api-platform SDK v0.3.5 release Go

💡 Result:

Do you mean the GitHub repository wso2/api-platform (releases) and whether an SDK v0.3.5 for Go exists — or do you want the download/link/notes for a specific Go SDK v0.3.5?


🌐 Web query:

prometheus/procfs v0.16.1 Go module release

💡 Result:

Yes — prometheus/procfs v0.16.1 is a released Go module. Release notes (changelog highlights: sync common files, Go toolchain update, several dependency bumps and bugfixes) are available. [1][2]

Sources:

  • Release summary (v0.16.1). [1]
  • Example downstream usages / go.sum entries showing module v0.16.1. [2]

References:
[1] NewReleases — prometheus/procfs v0.16.1 (changelog/links).
[2] grafana go.sum / other downstream manifests referencing github.com/prometheus/procfs v0.16.1.


🌐 Web query:

go.yaml.in/yaml/v2 v2.4.2 Go module

💡 Result:

Short answer: There is no official module path "go.yaml.in/yaml/v2" with a v2.4.2 release. The canonical v2 import path is gopkg.in/yaml.v2 (also hosted as gopkg.in/go-yaml/yaml.v2), and the latest v2 tag shown by gopkg.in is v2.4.0 (not v2.4.2). [1][2]

Sources:

  • gopkg.in YAML v2 documentation (shows v2 → v2.4.0). [1]
  • pkg.go.dev listing for the YAML v2 package (import path gopkg.in/yaml.v2 / docs). [2]

🏁 Script executed:

# Check if semantic-cache had replace directives in a previous commit or if this is expected
git log --oneline -n 10 -- gateway/policies/semantic-cache/v0.1.0/go.mod

Repository: wso2/api-platform

Length of output: 104


🏁 Script executed:

# Check if there are other go.mod files with the same invalid yaml import
grep -r "go.yaml.in/yaml/v2" gateway/policies/ --include="go.mod"

Repository: wso2/api-platform

Length of output: 145


🏁 Script executed:

# Check the git history for when this invalid dependency was introduced
git log -p --all -- "*go.mod" | grep -B5 -A5 "go.yaml.in/yaml/v2" | head -50

Repository: wso2/api-platform

Length of output: 43


🏁 Script executed:

# Check if wso2/api-platform SDK v0.3.5 exists by looking at the SDK directory
ls -la sdk/ 2>/dev/null && cat sdk/go.mod | grep "^module"

Repository: wso2/api-platform

Length of output: 448


Fix invalid YAML dependency and remove duplicate.

The entry at line 108 (go.yaml.in/yaml/v2 v2.4.2) is invalid—this module path and version do not exist in the public Go module registry. The canonical YAML v2 path is gopkg.in/yaml.v2 (correctly declared at line 123 with version v2.4.0). Remove the invalid go.yaml.in/yaml/v2 entry.

The prometheus dependencies (client_golang v1.23.2, common v0.66.1, procfs v0.16.1) and SDK v0.3.5 upgrade are all valid and available. The SDK bump is intentional based on the repository's policy version update.

🤖 Prompt for AI Agents
In `@gateway/policies/semantic-cache/v0.1.0/go.mod` around lines 7 - 8, Remove the
invalid duplicate YAML module entry in go.mod: delete the line declaring
"go.yaml.in/yaml/v2 v2.4.2" so only the canonical "gopkg.in/yaml.v2 v2.4.0"
remains; keep the prometheus entries and the intentional SDK bump
"github.com/wso2/api-platform/sdk v0.3.5" as-is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Standardized Logging for Policy Developers

1 participant