Address review feedback on security knowledge promotion

- do-not-grant-rights-beyond-a-users-entitlement.md: drop the See sample
  reference to a .good.al file that does not exist
- Remove the 'Contributions welcome' boilerplate line from
  compose-permission-sets, prefer-oauth2, and protect-sensitive-data
- protect-sensitive-data-in-temporary-tables: remove the pointless
  DeleteAll on the locally scoped temp buffer in the good sample and
  reword Best Practice to note local buffers are cleaned up automatically
- Drop guard-bulk-operations-with-istemporary from the promotion; it
  stays in the community layer pending a decision on whether it is security

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Jesper Schulz-Wedde 2026-07-10 12:27:31 +02:00
parent 347984ac74
commit 2350e10966
8 changed files with 1 additions and 12 deletions

View file

@ -9,8 +9,6 @@ application-area: [all]
# Compose permission sets with IncludedPermissionSets
> Contributions welcome — open a PR to refine or extend this article.
## Description
The `IncludedPermissionSets` property lets one AL permission set reference another, composing rights out of smaller building blocks. Combined with `Assignable = false` on the building blocks, an extension can ship focused per-module units (a table-data cluster, an API-access cluster) and assemble role-shaped sets that include them. Adding an object updates one building block, and every role-shaped set that includes it inherits the change automatically — instead of drifting apart across duplicated definitions.

View file

@ -19,8 +19,6 @@ Entitlements are license-level caps on what a user can access, derived automatic
When designing a permission set that ships with an extension, consult the entitlement model for the target user population before finalizing the grants. Every object and tabledata right the set expects to grant should be reachable within the intended entitlement tier; if it is not, the set needs to be scoped to licenses that permit it, or the feature needs a different access path.
See sample: `do-not-grant-rights-beyond-a-users-entitlement.good.al`.
## Anti Pattern
Authoring permission sets in a sandbox with full-license context and shipping them without verifying which entitlement tier customer users actually hold. The sets look complete in test; on a real customer they silently lose rights at runtime and the symptom is "the feature does not work for some users" with no obvious authorization error.

View file

@ -1,10 +0,0 @@
codeunit 50100 "Order Buffer Helper"
{
procedure ResetStagingBuffer(var OrderBuffer: Record "Sales Header")
begin
// No IsTemporary check. A caller that accidentally passes the real
// Sales Header table wipes every sales header in the company with
// no prior warning.
OrderBuffer.DeleteAll();
end;
}

View file

@ -1,12 +0,0 @@
codeunit 50100 "Order Buffer Helper"
{
procedure ResetStagingBuffer(var OrderBuffer: Record "Sales Header")
begin
// The helper is designed for a temporary buffer only. Fail loudly
// if a caller accidentally passes the real table.
if not OrderBuffer.IsTemporary() then
Error('ResetStagingBuffer requires a temporary Sales Header; a persistent record was passed.');
OrderBuffer.DeleteAll();
end;
}

View file

@ -1,28 +0,0 @@
---
bc-version: [all]
domain: security
keywords: [istemporary, deleteall, modifyall, safeguard, precondition]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Guard bulk operations with IsTemporary
> Contributions welcome — open a PR to refine or extend this article.
## Description
An AL helper that accepts a `var Rec: Record X` parameter and performs a bulk operation (`DeleteAll`, `ModifyAll`, or an unfiltered loop that mutates every record) cannot tell from the signature alone whether the caller passed a temporary buffer or the real table. A misuse that passes the real table wipes or rewrites live data at production scale with no earlier warning. A single `IsTemporary` check at the procedure entry turns a silent-corruption risk into an early, actionable failure.
## Best Practice
Any helper designed to operate on a temporary record, and that performs `DeleteAll`, `ModifyAll`, or similar bulk writes on its parameter, should call `Rec.IsTemporary()` at the top and raise a descriptive error when the assumption is violated. The error message should name the parameter so the misuse is easy to locate.
See sample: `guard-bulk-operations-with-istemporary.good.al`.
## Anti Pattern
Trusting documentation or naming conventions alone to signal that a `var Rec` parameter is expected to be temporary. A future refactor or a copy-paste caller can pass the real table; the bulk operation then executes against production rows silently.
See sample: `guard-bulk-operations-with-istemporary.bad.al`.

View file

@ -9,8 +9,6 @@ application-area: [all]
# Prefer OAuth2 over API keys for external HTTP calls
> Contributions welcome — open a PR to refine or extend this article.
## Description
External HTTP integrations from AL can authenticate using OAuth 2.0 (client-credentials for service-to-service, authorization-code for user-delegated), API keys, basic authentication, or credentials in URLs. The mechanisms differ substantially in the blast radius of a leaked secret and in how cleanly tokens can be rotated. OAuth-issued tokens expire on their own schedule and rotate cleanly; API keys and basic-auth passwords typically have to be rotated manually and usually live unencrypted in a configuration table. When the partner supports OAuth, the difference is a material security improvement, not a stylistic preference.

View file

@ -19,9 +19,6 @@ codeunit 50100 "Customer Temp Processor"
until Customer.Next() = 0;
ProcessCustomerBuffer(TempCustomer);
// Explicit cleanup on the normal exit path.
TempCustomer.DeleteAll();
exit(true);
end;

View file

@ -9,15 +9,13 @@ application-area: [all]
# Protect sensitive data in temporary tables
> Contributions welcome — open a PR to refine or extend this article.
## Description
A temporary record copies data out of the source table into session memory. The platform does not automatically enforce the source table's permission model on the copy, and a value written to a temporary buffer can outlive the procedure that put it there if the buffer is a global or is passed upward. Code that places sensitive rows into a temporary table is therefore responsible for the checks and cleanup the source table would otherwise provide.
## Best Practice
Validate the caller's read permission on the source table before populating the temporary buffer. Keep the buffer's lifetime as short as the work requires, and delete its contents on every exit path — including error paths — so sensitive values do not linger. Prefer local temporary variables over globals for anything carrying sensitive data.
Validate the caller's read permission on the source table before populating the temporary buffer. Keep the buffer's lifetime as short as the work requires, and prefer local temporary variables over globals for anything carrying sensitive data — a local buffer's contents are discarded automatically when the procedure returns. When a buffer must be global or is passed back to callers, delete its contents on every exit path — including error paths — so sensitive values do not linger.
See sample: `protect-sensitive-data-in-temporary-tables.good.al`.