Course outline

C# / Advanced: Lifetimes, Concurrency, and Runtime Design / Lesson 18

Security and Runtime Release Boundaries

Validate hostile input and release assumptions while keeping secrets, reflection, resource bounds, and deployment responsibilities explicit.

About 5 min readContent reviewed
On this page

Lesson goals

  • Use allowlists and culture-explicit protocol comparison.
  • Keep secret handling and cryptographic primitives within their contracts.
  • Record runtime, dependency, trimming, and deployment assumptions.

Type safety is one layer of input safety

C# type checking prevents many invalid operations, but a well-typed string can still contain an unauthorized action, huge payload, path traversal, or secret. Define the trust boundary and validate syntax, size, domain, and authorization separately. Authentication identifies a principal; authorization decides which action that principal may perform. A parser alone does neither.

Prefer allowlists for a small command vocabulary and avoid feeding raw text into process execution, dynamic code compilation, or unrestricted reflection. An action name should select a predeclared operation with typed arguments, not become executable syntax. This course’s demonstrations classify synthetic data and launch no external commands.

Use ordinal rules for protocol tokens

The first program uses an ordinal case-sensitive set for two exact operation tokens. Preview is rejected while preview is accepted. The case policy is intentional and independent of user culture. If a protocol instead permits case-insensitive tokens, choose OrdinalIgnoreCase deliberately and document normalization rather than using an ambient culture by accident.

An allowlist does not prove that an authenticated caller is authorized to use the allowed action. Apply authorization at the owning boundary and keep typed argument validation. Do not silently convert every unknown action into a default operation that may have side effects. Test near matches and unexpected Unicode as well as the known tokens.

csharp Exact synthetic action classification
using System;
using System.Collections.Generic;

var allowed = new HashSet<string>(StringComparer.Ordinal) { "preview", "status" };
Console.WriteLine($"Preview={allowed.Contains("Preview")}");
Console.WriteLine($"preview={allowed.Contains("preview")}");
Expected output
Preview=False
preview=True

The ordinal policy distinguishes uppercase Preview from the exact allowed lowercase token.

Use a purpose-built primitive within its scope

CryptographicOperations.FixedTimeEquals is designed for comparing byte sequences without content-dependent early exit under its documented conditions. The second program uses tiny synthetic nonsecret values to demonstrate equality and inequality. This does not implement password storage, authentication, signing, or key management. Length handling and the surrounding protocol remain separate concerns.

Never invent a password hash by repeatedly calling a fast general-purpose hash. Use an appropriate reviewed password-storage scheme through a trusted framework when that is the application requirement. Keep keys and secrets out of source control, logs, exceptions, profiles, and generated artifacts. This lesson performs no real credential operations.

csharp Compare synthetic equal-length byte sequences
using System;
using System.Security.Cryptography;

byte[] expected = { 1, 2, 3, 4 };
byte[] same = { 1, 2, 3, 4 };
byte[] different = { 1, 2, 3, 5 };
Console.WriteLine($"same={CryptographicOperations.FixedTimeEquals(expected, same)}");
Console.WriteLine($"different={CryptographicOperations.FixedTimeEquals(expected, different)}");
Expected output
same=True
different=False

The library primitive reports equality and inequality without creating an authentication system.

Avoid unbounded or authority-bearing deserialization

Deserializing data is not permission to instantiate arbitrary runtime types or execute callbacks chosen by the input. Use a bounded documented schema, validate required fields and domain values, and configure depth and size limits. Treat polymorphic type selection as a reviewed allowlist, not unrestricted type-name resolution. Prefer supported serializers and keep compatibility policy explicit.

Reflection and dynamic loading also interact with trimming and native ahead-of-time deployment. Code that works in a normal JIT console build may require metadata annotations or redesign under those deployment modes. Do not suppress trimming or AOT warnings without an argument about every needed type and member.

A release artifact has a runtime and support contract

Record the exact SDK selection, target framework, language version, dependencies, runtime identifier, and publish mode. Framework-dependent deployment needs a compatible supported runtime; self-contained deployment includes runtime components whose servicing becomes part of the artifact update process. Neither form is automatically patched merely because a developer machine has a newer SDK.

Review official support policy and supported operating systems at release time. Validate configuration before acquiring resources, keep startup failure clean, and shut down owned tasks before disposing dependencies. Security fixes and performance changes must preserve public contracts. Building or reviewing an artifact does not itself publish it or authorize deployment.

Common errors and fixes

A token check changes with machine culture.

Why it happens
Protocol identity uses a linguistic or ambient comparison.
How to fix it
Choose an explicit ordinal policy for protocol tokens.
Prevent it next time
Test casing and unexpected Unicode.

A self-contained release stays on an old runtime patch.

Why it happens
Runtime servicing was assumed to come from the host automatically.
How to fix it
Rebuild and release the artifact under the supported patch policy.
Prevent it next time
Record runtime components in release metadata.

AOT or trimming warnings are suppressed without tests.

Why it happens
Dynamic metadata requirements are unproven.
How to fix it
Annotate or redesign the relevant reflection and validate the actual publish mode.
Prevent it next time
Keep deployment-specific verification explicit.

Check your understanding

1. Why does accepting only an allowed action name not establish permission to execute it for every caller?

Show worked solution

The allowlist validates the vocabulary, not the caller’s rights. Authentication and authorization need their own trusted boundary and policy, and arguments still need type, size, and domain checks. Keep operation dispatch typed and avoid turning allowed text into a shell expression. Tests should distinguish unknown action, known-but-forbidden action, and authorized valid action.

2. What must a team update when a security patch is released for the runtime bundled into a self-contained application?

Show worked solution

Update the selected supported runtime/SDK inputs, rebuild the application artifact, run relevant correctness and publish-mode checks, and release through the team’s authorized process. Merely installing a runtime on a host does not replace runtime files bundled inside the existing artifact. Record the new components and retain rollback/compatibility information appropriate to the application.

Key takeaways

  • Type safety complements input validation and authorization rather than replacing them.
  • Use trusted primitives and keep secrets out of diagnostics and artifacts.
  • Runtime support, servicing, trimming, and publish mode are explicit release inputs.

Official references