Security
Security Practices
For information about how Actipro develops, maintains, and secures its products, please see our company-wide Security Practices Policy.
That page outlines our secure development processes, vulnerability handling procedures, support period commitments, and other practices that help ensure the safety and reliability of our software.
Vulnerability Reporting and Security Advisories
Security reporting for the Actipro WinForms controls is managed through our public GitHub repository.
You can find the product-specific instructions for reporting vulnerabilities and any published security advisories in the Actipro WinForms controls repository's Security page, which serves as the central location for coordinated vulnerability disclosure for both the commercial Actipro WinForms controls and any related open-source content.
EU Cyber Resilience Act (CRA) Compliance
Actipro WinForms controls are developed and maintained in accordance with the requirements of the EU Cyber Resilience Act (CRA). We follow a secure development lifecycle, provide a defined support period, publish vulnerability reporting instructions, and maintain the technical documentation and evidence required for CE marking under Module A (Internal Control).
Dynamic Code and Trusted Type Validation
Some product features may attempt to dynamically create and use .NET types from a string type/assembly name. In accordance with our secure-by-default design, types specified by string type/assembly names will be considered untrusted by default.
All type loading in Actipro logic, like Type.GetType and Activator.CreateInstance calls, is performed in the TrustedCodeService class, which will not load untrusted types.
Note
Types that are already accessible via a Type class instance are considered trusted, since application code external to Actipro logic has already loaded them into the trusted application domain.
Preemptively Trusting a Type
The TrustedCodeService class has these methods for preemptively marking a string type/assembly name as trusted:
- AddTrustedAssembly - Marks that all types within a specified
Assemblyare to be trusted. This is a preferred option when there are multiple types within an assembly that are trusted. - AddTrustedType - Marks that a specific
Typeshould be trusted.
Dynamically Trusting a Type
The TypeResolutionRequested event is raised whenever a type load for a string type/assembly name is requested. Its TypeResolutionEventArgs specify the assembly and type names. The IsTrusted property is initialized based on if the type is considered trusted, either from one of the trust methods described above or from a previous TypeResolutionRequested event handler invocation where the type was flagged as trusted.
Marking a Type As Trusted
This code snippet demonstrates handling of the event to mark that a type should be trusted and subsequently loaded with a Type.GetType(typeName) call:
using ActiproSoftware.Security;
...
// Application startup logic
TrustedCodeService.TypeResolutionRequested += OnTrustedCodeServiceTypeResolutionRequested;
...
private void OnTrustedCodeServiceTypeResolutionRequested(object? sender, TypeResolutionEventArgs e) {
if (!e.IsTrusted) {
// Flag a specific class as trusted
if ((e.TypeName == "MyCompany.TrustedClass") && (e.AssemblyName == "MyApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"))
e.IsTrusted = true;
}
}
Tip
Although this is NOT recommended, an application may reproduce the legacy behavior from before TrustedCodeService existed by handling the TypeResolutionRequested event and always setting e.IsTrusted = true, effectively bypassing all type and assembly trust checks.
Manual Type Loading
In some cases, it may be preferable to manually load and provide an appropriate Type instance, which can be done as an alternative to setting the TypeResolutionEventArgs.IsTrusted property to true. When the ResolvedType property is set to a non-null instance, that Type will be directly returned by the Resolve event and no Type.GetType(typeName) call will be made in Actipro logic.
This code snippet shows the concept of manually loading a resolved Type with a user-defined ManuallyLoadType method and returning the result:
using ActiproSoftware.Security;
...
// Application startup logic
TrustedCodeService.TypeResolutionRequested += OnTrustedCodeServiceTypeResolutionRequested;
...
private void OnTrustedCodeServiceTypeResolutionRequested(object? sender, TypeResolutionEventArgs e) {
if (!e.IsTrusted)
e.ResolvedType = ManuallyLoadType(e.AssemblyName, e.TypeName);
}
Assembly Name Matching Strategy
The AssemblyNameMatchingStrategy property can be set to an AssemblyNameMatchingStrategy enumeration value that determines how TrustedCodeService matches the assembly portion of a string type name with trusted assemblies. Enumeration values include:
- FullName - The full assembly name (name, version, culture, and public key token) must match. This is the default and most secure.
- NameAndPublicKey - Only the name and public key token are matched. This allows the version to be ignored.
- NameOnly - Only the name is matched, which is insecure and possibly allows assembly spoofing. This is not recommended for production usage.
Avoiding NameOnly Usage
As an example, assuming a string type name "MyCompany.MyClass, MyApp" is being resolved. Since no matches can be made on version or public key token, only a setting of NameOnly would trust the type.
Instead of using the NameOnly option, it is better to create a TypeResolutionRequested event handler and add logic in there. The TrustLevel property will indicate the level of trust TrustedCodeService assigned to the type based on its configuration.
Consider if this application startup code was added to an application named MyApp:
using ActiproSoftware.Security;
...
// Application startup logic
TrustedCodeService.AddTrustedAssembly(Assembly.GetExecutingAssembly());
TrustedCodeService.TypeResolutionRequested += OnTrustedCodeServiceTypeResolutionRequested;
...
private void OnTrustedCodeServiceTypeResolutionRequested(object? sender, TypeResolutionEventArgs e) {
// Some type names in deserialization scenarios may only include the pure assembly name
// (no version or public key token) for context, so trust those types that are defined in this assembly
e.IsTrusted |= (e.TrustLevel == StringTypeNameTrustLevel.ConditionallyTrusted) && (e.AssemblyName == Assembly.GetExecutingAssembly().GetName().Name);
}
Again, using the example string type name, but with the default AssemblyNameMatchingStrategy, the e.TrustLevel would be ConditionallyTrusted since the full assembly name couldn't match, but the pure assembly name did match a trusted assembly. The event handler above verifies conditional trust and checks that the assembly name is the currently executing assembly (MyApp). In that case, the type is marked as trusted.