HttpHeaders added via HttpClient.DefaultRequestHeaders() lose SecretText protection (ContainsSecret = false) after Send(), when read back via HttpRequestMessage.GetHeaders()
Nobody has claimed this yet.
- Dominant language
- PowerShell
- Stars
- 881
- Forks
- 285
- Avg merge
- 3d 36m
- Merged PRs (30d)
- 1
Description
1. Describe the bug
When a secret value (SecretText) is added as a header via HttpClient.DefaultRequestHeaders().Add(), the header value loses its "secret" status after the request is sent with HttpClient.Send(). If the same request's headers are read back afterwards via HttpRequestMessage.GetHeaders(), HttpHeaders.ContainsSecret() returns false for that header, and GetValues() returns the plaintext value instead of requiring GetSecretValues().
This does not happen when the secret header is added directly to the request's own header collection via RequestMessage.GetHeaders(Headers); Headers.Add(...) ΓÇö in that case ContainsSecret correctly stays true after Send().
This is a plausible instance of CWE-532 (Insertion of Sensitive Information into Log File). Extensions that log HTTP request headers for diagnostics ΓÇö a common, otherwise-safe pattern using ContainsSecret() to decide whether to mask a value ΓÇö will inadvertently persist plaintext secrets (e.g. Bearer tokens, API keys, Basic Auth credentials) if those secrets were set via Client.DefaultRequestHeaders(). This affects any AL extension using this common pattern of shared default headers across multiple Send() calls combined with post-send header inspection, not just a single app.
2. To Reproduce
Case A (bug: secret is lost):
var
Client: HttpClient;
RequestMessage: HttpRequestMessage;
ResponseMessage: HttpResponseMessage;
TestHeaders: HttpHeaders;
Secret: SecretText;
Token: Text;
begin
Token := 'MySecretToken';
Secret := SecretStrSubstNo('%1', Token);
Client.DefaultRequestHeaders().Add('Authorization', Secret);
// ContainsSecret here = true
RequestMessage.SetRequestUri('https://example.com');
RequestMessage.Method('GET');
Client.Send(RequestMessage, ResponseMessage);
RequestMessage.GetHeaders(TestHeaders);
// BUG: TestHeaders.ContainsSecret('Authorization') = false
// GetValues() now returns the plaintext token
end;
Case B (works as expected)
var
Client: HttpClient;
RequestMessage: HttpRequestMessage;
ResponseMessage: HttpResponseMessage;
Headers: HttpHeaders;
TestHeaders: HttpHeaders;
Secret: SecretText;
Token: Text;
begin
Token := 'MySecretToken';
Secret := SecretStrSubstNo('%1', Token);
RequestMessage.GetHeaders(Headers);
Headers.Add('Authorization', Secret);
// ContainsSecret here = true
RequestMessage.SetRequestUri('https://example.com');
RequestMessage.Method('GET');
Client.Send(RequestMessage, ResponseMessage);
RequestMessage.GetHeaders(TestHeaders);
// OK: TestHeaders.ContainsSecret('Authorization') = true
end;
3. Expected behavior
ContainsSecret() should return true for the header in both cases after Send(), since HttpHeaders is documented as a reference type and no code explicitly downgrades the secret to plaintext.
4. Actual behavior
In Case A, the secret protection is silently lost after Send(). Any code reading headers back from the request afterwards (e.g. for logging/diagnostics) receives the plaintext value without any indication that it was originally a SecretText.
5. Versions:
- AL Language: 17.0.2273547
- Visual Studio Code: 1.134.0
- Business Central: 28.4
- Operating System:
- Windows
- Linux
- MacOS
Final Checklist
Please remember to do the following:
-
Search the issue repository to ensure you are reporting a new issue
-
Reproduce the issue after disabling all extensions except the AL Language extension
-
Simplify your code around the issue to better isolate the problem
Internal work item: AB#649126
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the minimal AL cases using HttpClient.DefaultRequestHeaders(), HttpRequestMessage.GetHeaders(), and Send(). Compare ContainsSecret() and value access before and after sending for default and request-specific headers. Done means secret status and protected access remain consistent for both cases after Send().
Written by the indexing model from the issue text.
Assessment
- Domain
- api, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100