Address security privacy review findings

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 9c2eebc4-dcd5-4b85-8113-90772d818900
This commit is contained in:
Jesper Schulz-Wedde 2026-07-14 11:08:45 +02:00
parent ec8f891954
commit d8f372453c
8 changed files with 14 additions and 14 deletions

View file

@ -5,7 +5,7 @@ codeunit 50209 "Privacy Sample GetLastError Bad"
AttachmentFailedErr: Label 'Attachment failed: %1', Comment = '%1 = underlying error';
begin
if not TryAddAttachment() then
Error(StrSubstNo(AttachmentFailedErr, GetLastErrorText(true)));
Error(StrSubstNo(AttachmentFailedErr, GetLastErrorText()));
end;
procedure AddAttachmentWithConcatenation()
@ -13,7 +13,7 @@ codeunit 50209 "Privacy Sample GetLastError Bad"
AttachmentFailedErr: Label 'Attachment failed: ';
begin
if not TryAddAttachment() then
Error(AttachmentFailedErr + GetLastErrorText(true));
Error(AttachmentFailedErr + GetLastErrorText());
end;
[TryFunction]

View file

@ -5,7 +5,7 @@ codeunit 50208 "Privacy Sample GetLastError Good"
AttachmentFailedErr: Label 'Failed to add the attachment: %1', Comment = '%1 = underlying error shown to the user';
begin
if not TryAddAttachment() then
Error(AttachmentFailedErr, GetLastErrorText(true));
Error(AttachmentFailedErr, GetLastErrorText());
end;
[TryFunction]

View file

@ -11,16 +11,16 @@ application-area: [all]
## Description
`GetLastErrorText()` can contain customer content such as field values, record keys, and file names. When it is passed as a substitution value to an `Error` whose first argument is a `Label` or `TextConst`, the label supplies the Error method trace telemetry message. If `StrSubstNo` or concatenation makes `GetLastErrorText()` part of the first argument, the actual dynamic string is not emitted as that telemetry message; telemetry uses generic guidance instead.
Parameterless `GetLastErrorText()` can contain customer content such as field values, record keys, and file names. The Boolean overload names its parameter `ExcludeCustomerContent`; passing `true` requests scrubbed text and is not the customer-content scenario covered here. When unsanitized error text is passed as a substitution value to an `Error` whose first argument is a `Label` or `TextConst`, the label supplies the Error method trace telemetry message.
## Best Practice
Use a generic label when the user does not need the underlying detail. If showing the detail is appropriate, put `%1` in a label and pass `GetLastErrorText()` as a separate argument. This preserves a useful static telemetry message while keeping the dynamic value out of the telemetry message field.
Use a generic label when the user does not need the underlying detail. If showing unsanitized detail is appropriate, put `%1` in a label and pass parameterless `GetLastErrorText()` as a separate argument. This preserves a useful static telemetry message while keeping the dynamic value out of the telemetry message field.
See sample: `getlasterrortext-customer-content-in-errors.good.al`.
## Anti Pattern
`Error(StrSubstNo(AttachmentFailedErr, GetLastErrorText(true)))` or `Error(AttachmentPrefixErr + GetLastErrorText(true))`. Both lose the static first argument and trigger AA0231; neither causes the composed text to be logged verbatim as the Error telemetry message.
`Error(StrSubstNo(AttachmentFailedErr, GetLastErrorText()))` or `Error(AttachmentPrefixErr + GetLastErrorText())`. Both lose the static first argument and trigger AA0231; neither causes the composed text to be logged verbatim as the Error telemetry message.
See sample: `getlasterrortext-customer-content-in-errors.bad.al`.

View file

@ -2,7 +2,7 @@ codeunit 50212 "Sec Sample SecretSubst Bad"
{
procedure BuildAuthHeader(Token: Text): Text
begin
exit(StrSubstNo('Bearer %1', Token));
exit(StrSubstNo('Token %1', Token));
end;
procedure BuildSecretUri(ApiKey: Text): Text
@ -12,6 +12,6 @@ codeunit 50212 "Sec Sample SecretSubst Bad"
procedure BuildBrokenAuthHeader(Token: SecretText): SecretText
begin
exit(SecretStrSubstNo('Bearer', Token));
exit(SecretStrSubstNo('Token', Token));
end;
}

View file

@ -2,7 +2,7 @@ codeunit 50211 "Sec Sample SecretSubst Good"
{
procedure BuildAuthHeader(Token: SecretText): SecretText
begin
exit(SecretStrSubstNo('Bearer %1', Token));
exit(SecretStrSubstNo('Token %1', Token));
end;
procedure BuildSecretUri(ApiKey: SecretText): SecretText

View file

@ -11,7 +11,7 @@ application-area: [all]
## Description
`SecretStrSubstNo` is the secret-preserving counterpart of `StrSubstNo`. It inserts `SecretText` arguments into `%1`, `%2`, and similar placeholders and returns `SecretText` without materializing the result as plain text. It is the right tool for values such as a `Bearer %1` authorization header or a URI with an API key placeholder.
`SecretStrSubstNo` is the secret-preserving counterpart of `StrSubstNo`. It inserts `SecretText` arguments into `%1`, `%2`, and similar placeholders and returns `SecretText` without materializing the result as plain text. It is the right tool for values such as a `Token %1` authorization header or a URI with an API key placeholder.
## Best Practice

View file

@ -10,14 +10,14 @@ codeunit 50210 "Sec Sample SecretHttp Bad"
HttpClient.Get(RequestUri, Response);
end;
procedure CallApiWithBearer(BearerToken: Text)
procedure CallApiWithAccessToken(AccessToken: Text)
var
HttpClient: HttpClient;
Response: HttpResponseMessage;
Headers: HttpHeaders;
begin
Headers := HttpClient.DefaultRequestHeaders();
Headers.Add('Authorization', StrSubstNo('Bearer %1', BearerToken));
Headers.Add('Authorization', StrSubstNo('Token %1', AccessToken));
HttpClient.Get('https://api.example.com/data', Response);
end;
}

View file

@ -13,7 +13,7 @@ codeunit 50209 "Sec Sample SecretHttp Good"
HttpClient.Send(Request, Response);
end;
procedure CallApiWithBearer(BearerToken: SecretText)
procedure CallApiWithAccessToken(AccessToken: SecretText)
var
HttpClient: HttpClient;
Request: HttpRequestMessage;
@ -25,7 +25,7 @@ codeunit 50209 "Sec Sample SecretHttp Good"
Request.Method := 'GET';
Request.SetRequestUri('https://api.example.com/data');
Request.GetHeaders(Headers);
AuthHeader := SecretStrSubstNo('Bearer %1', BearerToken);
AuthHeader := SecretStrSubstNo('Token %1', AccessToken);
Headers.Add('Authorization', AuthHeader);
if not Headers.ContainsSecret('Authorization') then
Error(AuthorizationHeaderMissingErr);