Files
cameleer/docs/superpowers/specs/2026-04-14-sensitive-keys-unification-design.md
hsiegeln f22203a14a
Some checks failed
CI / build (push) Successful in 7m38s
CI / docker (push) Successful in 43s
CI / deploy (push) Failing after 5s
chore: rename cameleer3 to cameleer
Rename Java packages from com.cameleer3 to com.cameleer, module
directories from cameleer3-* to cameleer-*, and all references
throughout workflows, Dockerfiles, docs, and pom.xml.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:28:26 +02:00

5.6 KiB

Sensitive Keys Unification + SSE Support + Pattern Matching

Date: 2026-04-14 Status: Approved

Problem

The agent has two separate configuration fields for masking sensitive data in captured payloads:

  • sensitiveHeaders (default: Authorization,Cookie,Set-Cookie,X-API-Key,X-Auth-Token,Proxy-Authorization)
  • sensitiveProperties (default: empty)

Issues:

  1. Unnecessary split — a key like secretToken should be masked whether it appears as a Camel header or exchange property. Two lists means double configuration and easy omissions.
  2. Case-sensitive matchingsensitive.contains(key) is case-sensitive. HTTP headers are case-insensitive by spec, and Camel normalizes inconsistently. A header arriving as authorization (lowercase) bypasses masking. This is a bug.
  3. No pattern support — only exact key names match. No way to mask X-Internal-* or anything containing password.
  4. No SSE support — sensitive keys can only be set via system properties at startup. The server cannot push updated masking rules at runtime.

Design

1. SensitiveKeyMatcher (new class)

Immutable matcher that splits configured keys into two tiers at construction time:

  • Exact keys (no * or ?) — stored in TreeSet(String.CASE_INSENSITIVE_ORDER) for O(log n) case-insensitive lookup.
  • Glob patterns (contain * or ?) — compiled to java.util.regex.Pattern with CASE_INSENSITIVE flag. Compiled once, reused.

Match algorithm:

matches(key):
  if exactKeys.contains(key) -> true       // fast path, no iteration
  for each compiled pattern  -> match      // only reached on exact miss
  false

Glob-to-regex conversion:

Glob Regex Use case
* .* Wildcard
? . Single char
Other regex metacharacters Escaped Safety

Pattern wrapped with ^...$ and compiled with Pattern.CASE_INSENSITIVE.

Examples:

Key config Matches Does not match
Authorization Authorization, authorization, AUTHORIZATION MyAuthorization
X-Internal-* X-Internal-Token, x-internal-secret X-Public-Token
*password* db-password-hash, PASSWORD, userPassword passwd
*-Secret API-Secret, app-secret SecretKey

Thread safety: Immutable after construction. The AtomicReference swap in CameleerAgentConfig handles thread-safe replacement when SSE pushes new keys.

Location: cameleer-core/src/main/java/com/cameleer/core/collector/SensitiveKeyMatcher.java

2. ApplicationConfig change

Add to ApplicationConfig in cameleer-common:

private List<String> sensitiveKeys;
// + getter/setter

Sent by server in config-update SSE payloads. Supports both exact names and glob patterns. @JsonInclude(NON_NULL) means omitting the field leaves the agent's current config unchanged (same semantics as other nullable fields).

3. CameleerAgentConfig change

Replace:

private Set<String> sensitiveHeaders;
private Set<String> sensitiveProperties;

With:

private volatile SensitiveKeyMatcher sensitiveKeyMatcher;
  • System property: cameleer.agent.payload.sensitivekeys (comma-separated, supports globs)
  • Default: Authorization,Cookie,Set-Cookie,X-API-Key,X-Auth-Token,Proxy-Authorization
  • Old properties removed: sensitiveheaders and sensitiveproperties are deleted, no backward compatibility.
  • Getter: getSensitiveKeyMatcher() returns the current matcher.
  • Handled in both applyServerConfig() (startup) and applyServerConfigWithDiff() (runtime SSE).

4. PayloadCapture change

Both captureHeaders() and captureProperties() use the same matcher:

SensitiveKeyMatcher matcher = config.getSensitiveKeyMatcher();
// ...
if (matcher.matches(key)) {
    result.put(key, "***MASKED***");
}

5. No new SSE command

This piggybacks on the existing config-update command. The server includes sensitiveKeys in the ApplicationConfig payload — same pattern as tracedProcessors, compressSuccess, routeRecording, etc.

Server Team Contract

The server needs to:

  1. Store sensitiveKeys: List<String> on the application config document.
  2. Include the field in config-update SSE payloads when set.
  3. Accept both exact key names and glob patterns (*, ?) in the list.
  4. Omit the field (or send null) to leave the agent's current keys unchanged.
  5. Send an empty list [] to clear all masking (agent will mask nothing).

Example config-update payload fragment:

{
  "sensitiveKeys": [
    "Authorization",
    "Cookie",
    "Set-Cookie",
    "X-API-Key",
    "*password*",
    "*secret*",
    "X-Internal-*"
  ],
  "version": 42,
  ...
}

Agent ACK will include sensitiveKeys in the change summary if the list differs from current.

Files Changed

File Change
cameleer-core/.../collector/SensitiveKeyMatcher.java New — immutable glob matcher
cameleer-core/.../CameleerAgentConfig.java Replace two fields with one SensitiveKeyMatcher, add to applyServerConfig/applyServerConfigWithDiff
cameleer-core/.../collector/PayloadCapture.java Use getSensitiveKeyMatcher().matches() for both headers and properties
cameleer-common/.../model/ApplicationConfig.java Add sensitiveKeys field
cameleer-agent/.../collector/PayloadCaptureTest.java Update mocks, add pattern tests
cameleer-agent/.../collector/SensitiveKeyMatcherTest.java New — unit tests for matcher
cameleer-agent/.../perf/* Update mock stubs