This page covers what DKNet supports with security fixes, how to report a vulnerability, and the
security-relevant behaviour of the packages themselves. The repository root
SECURITY.md is the short version and links here.
DKNet has no long-term-support branches. Packages are published from main by
.github/workflows/dotnet-publish.yml, which derives each version from the commit history with
paulhatch/semantic-version in major.minor.patch form, so
there is no maintained matrix of older lines.
| Version | Supported |
|---|---|
| The latest version of a package on NuGet | ✅ Fixes land here |
| Any earlier version | ❌ Upgrade to the latest |
If upgrading is blocked by a breaking change, the Migration Guide documents the ones that have shipped.
Do not open a public issue for a security problem.
Use GitHub’s private vulnerability reporting for this repository: https://github.com/baoduy/DKNet/security/advisories/new. That channel is private to the maintainers until an advisory is published.
Please include the affected package and version, what an attacker can do, and a minimal reproduction. There is no published response-time commitment — DKNet is a volunteer-maintained open-source project.
Two scanners run over the repository, and their findings are triaged like any other defect:
.github/workflows/codeql.yml, C# analysis on push and pull request.qodana.yaml.src/Directory.Build.props sets TreatWarningsAsErrors and Nullable=enable solution-wide, so a nullability
mistake in a security-relevant path fails the build rather than shipping.
Each of these is a decision the packages make on your behalf. Read the ones you use.
DKNet.RandomCreator for anything secret. It wraps
System.Security.Cryptography.RandomNumberGenerator; System.Random is not cryptographically secure and its
output is predictable. See DKNet.RandomCreator.AddEncryptionServices() registers no cipher. It registers IShaHashing and IHmacHashing only. AES and
RSA are opt-in through AddAesGcmEncryption(base64Key) and AddRsaEncryption(privateKeyBase64), so the key is
something you supply and persist. An earlier release registered AES over a randomly generated key that was
never persisted — ciphertext produced under that registration cannot be recovered. Details:
Migration Guide.DKNet.EfCore.Encryption fails closed. It needs an IEncryptionKeyProvider registered through
AddEfCoreEncryption<TKeyProvider>(); without usable key material the model build fails rather than storing
plaintext. See DKNet.EfCore.Encryption.IAesEncryption (AES-CBC) has been removed, not just deprecated. It used a fixed IV embedded in the key, so
identical plaintexts always produced identical ciphertext — a real information leak, not a theoretical one. Use
IAesGcmEncryption, which authenticates the ciphertext and uses a fresh random nonce per call. See
DKNet.Svc.Encryption.AddDataOwnerProvider<TDbContext, TProvider>() requires IDataOwnerDbContext on the context. The
constraint exists because the older, unconstrained signature allowed a context the ownership filter could not
read — which silently disabled row isolation.AccessibleKeys denies access; it is never read as “see everything”. Unrestricted access is an
explicit opt-in via IsUnrestrictedAccess, which defaults to false.AddDataOwnerProvider adds it to a static model-builder list, so
every DbContext that calls UseAutoConfigModel() applies it. A second context holding IOwnedBy entities
must also implement IDataOwnerDbContext, or keep those entities out of its model.OwnedBy says which tenant a row belongs to, never who touched it.
Register an ICurrentUserProvider (see Audit trails below) when CreatedBy/UpdatedBy must answer the second
question; without one they are filled from the ownership key, which answers only the first.UseAutoConfigModel<TContext>() removes the filter. This matters most in tests: without it a
query returns rows it never would in production. See DKNet.EfCore.DataAuthorization.AuditPropertyPolicy.RedactSensitive,
properties whose name matches a built-in deny-list (password, secret, token, apikey, ssn,
creditcard, connectionstring, privatekey, …) and any SecureString property are captured as
"***REDACTED***" — the field still shows that it changed, never its value.[AuditLog] on a property forces plaintext capture. Do not put it on a secret. [SensitiveData] always
redacts and cannot be overridden by [AuditLog]; [IgnoreAuditLog] removes the property from the trail
entirely. See DKNet.EfCore.AuditLogs.DKNet.EfCore.Encryption is redacted in audit entries. [Encrypted] uses a
ColumnEncryptionConverter, a ValueConverter — so the change tracker holds the decrypted model value, and
without this redaction that plaintext would reach every registered IAuditLogPublisher. DKNet.EfCore.AuditLogs
treats [Encrypted] as unconditionally sensitive, the same as [SensitiveData]. See
DKNet.EfCore.Encryption.ICurrentUserProvider via
AddCurrentUserProvider<TDbContext, TProvider>() fills CreatedBy/UpdatedBy from
ICurrentUserProvider.GetCurrentUser() instead of from the tenant ownership key. Whatever that method returns
reaches every registered IAuditLogPublisher in full — the redaction rules above cover entity property
values, not the audit identity. An application subject to a personal-data rule (GDPR, PDPA) must therefore
return a stable, non-personal identifier such as the token subject id, not an email address or any other
directly identifying value.DKNet.EfCore.DataAuthorization fills CreatedBy/UpdatedBy from the ownership key, so the
trail silently records a tenant instead of a person. Check which one your reports assume.DKNet.Svc.BlobStorage.Local rejects path traversal. A resolved path that escapes the configured
RootFolder raises UnauthorizedAccessException rather than reading or writing outside it.BlobService.ValidateFile checks MaxFileNameLength,
IncludedExtensions, and MaxFileSizeInMb and throws FileLoadException on a violation; Azure, S3, and local
all call it at the top of their SaveAsync, so an extension allow-list applies uniformly. It is protected
virtual, so a custom adapter of your own must call it — the base class does not force it. See
DKNet.Svc.BlobStorage.Abstractions.DKNet.AspCore.Idempotency checks the header’s presence,
format, and length before it reaches a store.Authorization header
(only when IdempotencyOptions.ScopeHmacSecret is configured — otherwise that fallback is skipped), then the
client IP if IncludeClientIpInScope is set. Neither the secret nor the raw header is ever logged. A
KeyScopeResolver replaces the whole chain. See
DKNet.AspCore.Idempotency..RequiredIdempotentKey() always adds the filter, but the filter
cannot be constructed unless AddIdempotentKey/AddIdempotencyWith*Store registered an
IIdempotencyKeyStore — the route then fails on its first request. Between two named stores
AddIdempotentKey is first-registration-wins: a second call with different options is silently ignored, so a
stricter Expiration or a ScopeHmacSecret added later may never take effect. Cover the registration with a
start-up test.AddIdempotentKey() is a local-development default, not a deployment one. Its in-process
store reserves each key atomically, so it is not a concurrency hazard — but the keys live in one process’s
memory: they are lost on restart and not shared between instances. A multi-instance deployment left on it keeps
one ledger per instance and therefore loses cross-instance idempotency, letting the same key be processed once
per instance. That is an operational failure rather than an attack surface — the store holds only process-own
memory, opens no listener, takes no credential, and adds no configuration input. The mitigation is the startup
warning the app logs while that store is the one serving requests: treat it as a deployment defect and switch to
the SQL Server, PostgreSQL, or Redis store, which reserve the key atomically across instances.DKNet does not authenticate or authorise callers. It gives you row-level ownership filtering, column encryption, audit redaction, and idempotency; authentication, authorisation policies, transport security, and key rotation remain yours. Keep the values listed in Environment and secrets out of source control.