Skip to content

feat: support custom HTTP headers via SIGNOZ_CUSTOM_HEADERS env var#65

Open
issmirnov wants to merge 4 commits intoSigNoz:mainfrom
Smirnov-Labs:feat/custom-headers
Open

feat: support custom HTTP headers via SIGNOZ_CUSTOM_HEADERS env var#65
issmirnov wants to merge 4 commits intoSigNoz:mainfrom
Smirnov-Labs:feat/custom-headers

Conversation

@issmirnov
Copy link
Copy Markdown

Summary

  • Adds SIGNOZ_CUSTOM_HEADERS environment variable for injecting custom HTTP headers into all outbound API requests
  • Format: Key1:Value1,Key2:Value2 (parsed at startup into a map[string]string)
  • Headers are sent alongside existing Content-Type and SIGNOZ-API-KEY on every request
  • Centralizes header setting via a new setHeaders() helper method on the client

Motivation

When SigNoz is deployed behind a reverse proxy that requires additional authentication (e.g. Cloudflare Access with service tokens), the MCP server currently has no way to pass the required headers. This blocks MCP clients from reaching the SigNoz API entirely.

This change enables configuring arbitrary headers like CF-Access-Client-Id and CF-Access-Client-Secret without modifying application code.

Example usage

{
  "env": {
    "SIGNOZ_URL": "https://signoz.example.com",
    "SIGNOZ_API_KEY": "your-key",
    "SIGNOZ_CUSTOM_HEADERS": "CF-Access-Client-Id:abc123.access,CF-Access-Client-Secret:secret456"
  }
}

Test plan

  • Verified against SigNoz instance behind Cloudflare Access (acceptance environment)
  • Confirmed all MCP tools work (list_services, list_dashboards, create_dashboard, update_dashboard, search_traces, etc.)
  • Verified backward compatibility: omitting SIGNOZ_CUSTOM_HEADERS results in empty map, no behavioral change
  • Unit tests for header parsing in config
  • Unit tests for setHeaders() applying custom headers

🤖 Generated with Claude Code

@issmirnov
Copy link
Copy Markdown
Author

Hi team,

This has been tested and works with our signoz behind Cloudflare access. Let me know if you have any feedback.

@pradeepitm12 pradeepitm12 added the safe-to-test Add this label to run CI. label Mar 16, 2026
@akshaysw
Copy link
Copy Markdown
Collaborator

@issmirnov can you please update your branch with latest changes from main and resolve merge conflicts.

issmirnov and others added 3 commits March 26, 2026 18:15
When SigNoz is behind a reverse proxy that requires additional
authentication headers (e.g. Cloudflare Access service tokens),
there is currently no way to pass them through the MCP server.

This adds a SIGNOZ_CUSTOM_HEADERS environment variable that accepts
key-value pairs in the format "Key1:Value1,Key2:Value2". These
headers are injected into every outbound HTTP request alongside
the existing Content-Type and SIGNOZ-API-KEY headers.

Changes:
- config: parse SIGNOZ_CUSTOM_HEADERS into map[string]string
- client: add NewClientWithHeaders() constructor and setHeaders()
  helper that centralizes header setting across all API methods
- handler: thread custom headers through to client cache

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add custom headers to the environment variables table and a new
section with examples for Cloudflare Access and custom bearer token
authentication through reverse proxies.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@issmirnov issmirnov force-pushed the feat/custom-headers branch from 97c7674 to ad41981 Compare March 27, 2026 00:19
- config_test.go: 6 test cases covering empty, single, multiple,
  whitespace-trimmed, colon-in-value, and malformed header pairs
- client_test.go: 3 test cases verifying custom headers are sent
  on requests, and that nil/empty header maps are handled safely
- Add doc comments to NewClientWithHeaders, setHeaders, and
  CustomHeaders field

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@issmirnov issmirnov force-pushed the feat/custom-headers branch from ad41981 to fe8a924 Compare March 27, 2026 00:25
@issmirnov
Copy link
Copy Markdown
Author

issmirnov commented Mar 27, 2026

Hi @akshaysw I have cleaned up the merge conflicts and sent up the latest changes. Doing a quick personal code review to make sure this is tidy and ready for you. Local CI/CD passes.

AI will send a followup note here with the initial code review, as it found some pre-existing issues that are out of scope for this change.

@issmirnov
Copy link
Copy Markdown
Author

Automated Code Review — Claude (on behalf of Codex gpt-5.3-codex)

This review was generated by running the PR diff through Codex (gpt-5.3-codex, reasoning effort: high) via Claude Code. Sharing for maintainer awareness — no action required.


High Severity

1. OAuth flow does not use SIGNOZ_CUSTOM_HEADERS

validateSigNozCredentials in internal/oauth/handlers.go creates a plain NewClient without custom headers. Users whose SigNoz instance is behind a proxy requiring custom auth headers (the exact use-case this PR targets) will fail the OAuth login flow even though their headers are correctly configured.

Relevant locations: internal/oauth/handlers.go:267, internal/client/client.go:87–98

2. Custom headers can be exfiltrated to attacker-controlled URLs in HTTP/multi-tenant mode

In HTTP transport mode, the SigNoz URL is supplied by the caller via X-SigNoz-URL. Since custom headers (e.g. CF-Access-Client-Id, CF-Access-Client-Secret) are attached to every outbound request unconditionally, a malicious caller can point the URL at an arbitrary host and harvest those secrets.

Relevant locations: internal/client/client.go:171, internal/mcp-server/server.go:245


Medium Severity

3. Custom headers can silently override authHeaderName and tenant credentials

Auth headers are set first, then custom headers are applied with req.Header.Set — which overwrites any existing key. If SIGNOZ_CUSTOM_HEADERS contains SIGNOZ-API-KEY, Authorization, or Content-Type, it will override the tenant's credentials.

Relevant location: internal/client/client.go:169–171

4. No startup validation of header format

Malformed entries (empty keys, invalid header names) are silently skipped at config load time rather than rejected. This can lead to hard-to-debug partial config application at runtime.

Relevant location: internal/config/config.go:69–72


Low Severity

5. LRU cache key does not include authHeaderName (pre-existing, not introduced by this PR)

Cache key is (apiKey, signozURL) only. If the same pair is ever used with different auth header names, a stale client could be returned. Worth noting alongside the authHeaderName design.

Relevant location: internal/handler/tools/handler.go:73


Test Coverage Gaps

  • No test for OAuth authorize flow succeeding when custom headers are required
  • No test for reserved header override prevention (SIGNOZ-API-KEY, Authorization, Content-Type)
  • No test for invalid header key/value rejection during config validation

Generated by Claude Code using Codex gpt-5.3-codex. Maintainers please use your own judgement on these findings.

@issmirnov
Copy link
Copy Markdown
Author

Hi @akshaysw , let me know if there is interest in this branch. I see it's been a few weeks, and we have some new merge conflicts. Once you are ready to review I can do a final fix and update.

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

Labels

safe-to-test Add this label to run CI.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants