microsoft / microsoft/AL

HttpHeaders added via HttpClient.DefaultRequestHeaders() lose SecretText protection (ContainsSecret = false) after Send(), when read back via HttpRequestMessage.GetHeaders()

Open
#8,329 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

accepted
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.