Repository navigation
v0.5.5
security
- Add
MaxFingerprintBodySize(default 64 KiB) toSharkIdempotencyOptions— prevent OOM via attacker-controlledContent-Lengthheader in idempotency middleware fingerprinting. Fixed chunked-transfer bypass where allTransfer-Encoding: chunkedrequests hashed to the same fingerprint (now hashes body incrementally with-1sentinel for unknown length); setter rejects<= 0 - Replace
Thread.Sleeppolling withawait Task.Delayin graceful shutdown drain — prevent ApplicationStopping thread block (SHARK-SEC-003). Initial fix used.GetAwaiter().GetResult()on the drain task which still blocked the callback; corrected to true fire-and-forget soApplicationStoppingreturns immediately. - Make
SagaExecutor.LockTtlconfigurable; addLockRenewalIntervalwith periodic lock extension — prevent split-brain sagas when step duration exceeds lock TTL (SHARK-SEC-004, cross-repo withSharkable.Cache.Redis).ISagaStore.RenewLockAsyncconverted from a required interface member to a default interface method (DIM) so existing third-party implementations keep compiling unchanged. - Replace unconditional
KeyDeleteinRedisSagaStore.ReleaseLockAsyncandRedisCronJobStore.ReleaseJobLockAsyncwith check-and-delete Lua script — prevent split-brain when LockTtl expires mid-work (SHARK-SEC-005, cross-repo withSharkable.Cache.Redis) - Add
ICronJobStore.RenewJobLockAsyncplusCronScheduler.CronLockTtl(default 10 min) and a background renewal task mirroringSagaExecutor— prevent split-brain cron jobs when job duration exceeds lock TTL (SHARK-SEC-005 follow-up, cross-repo withSharkable.Cache.Redis) - BREAKING: Add
[CrudAllow]attribute for explicit field allowlist on AutoCrud insertable/updateable — endpoints withCreate | Updateenabled and zero[CrudAllow]properties now throwInvalidOperationExceptionat startup. Existing entities must mark every writable field with[CrudAllow]. Prevents mass-assignment privilege escalation (SHARK-SEC-006) - BREAKING (SHARK-SEC-006 follow-up): Exclude the configured soft-delete column (
SqlSugarOptions.SoftDeleteFieldName, default"IsDeleted") from the[CrudAllow]allow-list even when explicitly marked — otherwise an attacker can revive soft-deleted rows by sending the field in a PUT body. Honors[SugarColumn(ColumnName = "...")]renames - BREAKING (SHARK-SEC-006 follow-up): AutoCrud
POST /andPUT /{id}now return the persisted row re-read from the database instead of the user-controlled request body — previous behavior silently hid server-side defaults (timestamps, identity-generated PK, server-set soft-delete state) from the client - Require API key on profiler endpoint
/_sharkable/profilerby default; return 404 if no API keys configured (SHARK-SEC-015). AddsSharkOption.ProfilerRequireApiKey(defaulttrue); also caps thetopslow-requests surface at 50 to bound data exposure - Require API key on cron admin endpoint
/_sharkable/jobs; redactLastErrorfield to its first 100 characters +...to prevent business-logic leakage (SHARK-SEC-016). AddsSharkOption.CronAdminRequireApiKey(defaulttrue); returns 404 if no API keys are configured - BREAKING: Make
CronScheduler.Registerasync — eliminate sync-over-async deadlock risk with distributed stores (SHARK-SEC-017).ICronScheduler.Registeris replaced byRegisterAsyncreturningTask;SharkOption.ConfigureCronJobscallback type changes fromAction<ICronScheduler>toFunc<ICronScheduler, Task>so the hosted service can await it without blocking on startup. Internalawait _store.LoadStateAsync(...)replaces.GetAwaiter().GetResult(). - Add
SharkOption.RequireAuthenticatedByDefaultopt-in flag — enforce auth on framework endpoints via fallback policy (SHARK-SEC-011, closes both H-4 and H-6) - Add
ETagOptions.MaxResponseSize(default 10 MiB) + counting stream + incremental hashing — prevent OOM via huge response bodies (SHARK-SEC-012) - Add
IMemoryCacheSizeLimit (100k) + periodic eviction sweep toMemoryRateLimitStore— prevent slow-loris DoS via unique path explosion (SHARK-SEC-013) - Add
IMemoryCacheSizeLimit (10k) + entry.Size tracking toMemoryIdempotencyStore— prevent TB-DoS via unique idempotency keys (SHARK-SEC-014) - Add JWT algorithm allowlist +
RequireSignedTokens+RequireExpirationTime+ reduceClockSkewto 30s — prevent algorithm confusion attacks (SHARK-SEC-007) - Use
CryptographicOperations.FixedTimeEqualsfor API key comparison — prevent timing oracle (SHARK-SEC-008) - Gate
ScalarJwtToken/ScalarApiKeyValuetoIHostEnvironment.IsDevelopment()— prevent token leakage to public/scalar/v1UI (SHARK-SEC-009) - Implement
AuditTrailMiddlewareheader redaction perRedactHeaderslist — credential-bearing headers (Authorization,X-Api-Key,Cookieby default) have their values replaced with***in audit log output. Header names preserved so reviewers see which credentials were presented (SHARK-SEC-010) - Redact RedisHealthCheck description — never expose topology or exception messages on public
/healthz(SHARK-SEC-018) - Validate connection string at AddSharkableRedis — null-check + default
abortConnect=false+ optional TLS enforcement (SHARK-SEC-019) - Replace
JsonSerializer.Serialize<T>with source-generatedJsonSerializerContextinRedisIdempotencyStore— Cache.Redis is now AOT-compatible (SHARK-SEC-020 follow-up, cross-repo withSharkable.Cache.Redis) - Remove auto-registration of
RedisHealthCheckasIHealthCheckinAddSharkableRedis—UseSharkableRedisHealthCheck()is the only way to wire it (SHARK-SEC-021 follow-up, cross-repo withSharkable.Cache.Redis) - Connection string: only override
abortConnect=falsewhen the key is absent — respect an explicitabortConnect=true(SHARK-SEC-019 follow-up, cross-repo withSharkable.Cache.Redis) - Replace empty catch in RedisIdempotencyStore deserialization with typed exception handling + tombstone record — prevent silent double-execution on corruption (SHARK-SEC-020)
- Add
UseSharkableRedisHealthCheck()extension — explicit opt-in to wire health check into/healthz(SHARK-SEC-021) - Set TTL on RedisSagaStore progress records via
RedisStoreOptions.SagaProgressTtl(default 7d) — prevent unbounded memory growth (SHARK-SEC-022) - Add
AutoCrudSqlSugar.AutoCrudRequireAuthorizationopt-in flag — auto-attach.RequireAuthorization()to generated CRUD endpoints. Defaultfalsefor backward compat; production deployments MUST enable. (SHARK-SEC-023, cross-repo withSharkable.AutoCrud.SqlSugar) - Defense-in-depth: also exclude
SafeSoftDeleteFieldfrom[CrudAllow]allow-list by case-insensitive name match — protects against entities whose C# property name matches the configured soft-delete column but lacks[SugarColumn]rename (SHARK-SEC-024, cross-repo withSharkable.AutoCrud.SqlSugar) - Add
AutoCrudSqlSugar.MaxPageNumber(default 1M) + overflow check on(page - 1) * pageSize— prevent pagination DoS viapage=int.MaxValueproducing a negative OFFSET (SHARK-SEC-025, cross-repo withSharkable.AutoCrud.SqlSugar) - Redact
SqlSugarHealthCheckdescription — never exposedbTypeorex.Messageon public/healthz; full diagnostic detail logged atLogWarningfor operators only (SHARK-SEC-026, cross-repo withSharkable.AutoCrud.SqlSugar) - Escape SQL
LIKEwildcards + cap filter value length (200) + capINarray size (100) in AutoCrud search — prevent LIKE wildcard DoS and large-IN clause DoS (SHARK-SEC-027, cross-repo withSharkable.AutoCrud.SqlSugar) - Fix ETag
CountingResponseBody.FlushAsyncduplicate body write on over-cap responses (SHARK-SEC-012 follow-up) - Fix
MemoryRateLimitStore.IncrementAsyncnon-atomic increment allowing concurrent bypass (SHARK-SEC-013 follow-up) - Replace
AuditTrailMiddleware.CaptureHeadersJsonSerializer.Serializewith hand-rolled formatter — AOT-compatible (SHARK-SEC-010 follow-up) - Make JWT audience validation mandatory when
ConfigureJwtis called — reject empty audience list at config time (SHARK-SEC-007 follow-up) - Strip assembly-qualified type name from
ExceptionHandlerOptions.GetErrorMessagedev output — prevent fingerprinting ofSharkable.X.Y, Version=1.0.0.0over the wire (SHARK-SEC-M003) - Redact
JwtHealthCheckdescription on/healthz— replace authority URL +ex.Messageechoes with generic descriptions; full URL + exception now surface viaILogger.LogWarningfor operators only (SHARK-SEC-M004) - Pin
CultureInfo.InvariantCultureforstring.FormatinHttpContext.Localize(key, args)— prevent culture-dependent format-string growth attacks via malicious translations (SHARK-SEC-M005) - Implement
IDisposableonAdaptiveLimitMonitor+ wrapAdjust()in try/catch — prevent timer-callback exception tearing the process down + close theProcesshandle leak (SHARK-SEC-M010) - Cap
CronExpression.GetNextiteration count at 2.2 M — bound CPU on non-matching patterns (e.g.0 0 30 2 *) that previously looped 2.1 M times per hosted-service tick (SHARK-SEC-M011) - Link
SharkCronHostedServicehost stoppingToken to a per-jobCancellationTokenSource— prevent orphaned long-running cron jobs survivingapp.StopAsync()and tripping k8s termination grace periods (SHARK-SEC-M012) - Collapse
CronSchedulerthree correlated dictionaries (_jobs/_states/_expressions) into oneDictionary<string, JobEntry>under a single lock — eliminate TOCTOU betweenRegisterandGetDueJobsAsync(SHARK-SEC-M013) - Bound
/healthzaggregateHealthCheckService.CheckHealthAsyncwith a 10 sCancellationTokenSource— prevent a single hung check from keeping the endpoint open and tripping k8s probes (SHARK-SEC-M015) JwtHealthChecknow acceptsIHttpClientFactoryvia DI — reuse a pooledHttpMessageHandlerinstead of opening a new socket per probe (SHARK-SEC-M016)- Default rate-limit partition key includes the authenticated
User.Identity.Namewhen present, falling back toRemoteIpAddress— prevent shared-NAT / proxy-IP DoS or IP-rotation bypass (SHARK-SEC-M017) - Invoke
JwtConfigureBEFORE applying frameworkTokenValidationParametersdefaults — user callbacks that mutate properties in place (the documented contract) now compose correctly; framework safety (algorithm allowlist, 30 sClockSkew,RequireSignedTokens,RequireExpirationTime) is re-applied on the final instance regardless of user replacement (SHARK-SEC-M019) - Default
AuditTrailOptions.ForwardCorrelationIdtofalse+ validate inbound correlation id against[A-Za-z0-9._-]{1,128}— prevent log injection viaX-Correlation-Id: foo\n[CRITICAL] admin login OK(SHARK-SEC-M020) - Include the authenticated user identity (
User.Identity.Name/subclaim) inSharkIdempotencyMiddlewarefingerprint — prevent cross-user replay when two authenticated users share the sameIdempotency-Key+ body. Tests updated for the new signature (SHARK-SEC-M021) - Replace unbounded
MemoryStreamfor the idempotency response body with aCountingResponseBodywrapper that throwsResponseSizeExceededExceptionatMaxResponseSize— peak allocation is now bounded by the cap instead of by the attacker-controlled response size (SHARK-SEC-M008) - Expand
ConfigurationValidatorto validate header names (CorrelationIdHeader,HeaderPrefix,ReplayedHeaderName,Idempotency-Key,ApiKeyHeaderName) against[A-Za-z0-9._-]+— block CRLF-injection via appsettings.json. Try-compile regex patterns + validate RateLimiter / ETag / AuditTrail / Idempotency numeric/timespan ranges at startup (SHARK-SEC-M001, M002, L017) - Cap
AuditTrailMiddlewarequery-string length at 4 KiB before the redact pass — prevent multi-MB?x=strings bloating every audit log line (SHARK-SEC-M009) - Default
JsonHelperHttpResponseExtension.WriteJsonAsynctoWriteIndented=false— compact JSON is ~2x smaller on the wire (SHARK-SEC-L001) - Enforce
SharkIdempotencyOptions.MinKeyLength = 16(IETF draft) onIdempotency-Key— prevent attackers from pre-burning the short-key space (a,b,aa, ...) and filling the in-memory store (SHARK-SEC-L002) ApiKeyFilternow injectsIOptionsMonitor<SharkOption>and re-readsApiKeyson every invocation — hot-reload via configuration change now takes effect immediately (SHARK-SEC-L003)- Add
[SharkOpenApiIgnore]property attribute + schema transformer — properties carrying the marker are stripped from every generated OpenAPI schema, preventingPassword/RefreshToken/ApiSecretfrom appearing in/openapi/v1.json(SHARK-SEC-L009) - Parse
ETagMiddlewareIf-None-Matchper RFC 9110 §13.1.2 — split comma-separated candidates, honor weakW/"..."prefix, and accept*wildcard. The previousTrim('"')on the whole header only worked for a single strong candidate (SHARK-SEC-L011) - Narrow the empty
catch {}inEndPointExtensionAutoCrud marker lookup toTypeLoadException/TargetInvocationException/MissingMethodException— anything else (OOM, MemberAccess, …) propagates so a real startup failure stays visible. Logs viaILogger.LogDebug(SHARK-SEC-L019, L022) SharkBackgroundService.LastError = ex.ToString()instead ofex.Message— preserve inner exception stack so operators see the root cause (SHARK-SEC-L024)RedactingLoggernow redacts structurally by walking the{OriginalFormat}template and substituting{Key}placeholders — substring-search over-redaction (password="p4ss"+ unrelated "the password is p4ss" both rewritten) and under-redaction (JSON-escaped values) fixed (SHARK-SEC-M006)- Cache compiled default-pattern
Regex(GroupNameSuffixRegex,VersionFormatRegex) as static fields withRegexOptions.Compiled+ 100 ms timeout — no re-parse per call (SHARK-SEC-L012) - Add
TenantOptions.AllowedHosts+ check inTenantResolver.FromHost— when set, mismatched inboundHostheaders cannot be used to spoof the tenant (SHARK-SEC-L007) AuditLogBufferconsumer logs a debug line on shutdown — distinguish graceful cancellation from a misbehaving loop (SHARK-SEC-L020)- Pin
Microsoft.OpenApito 2.7.5 — suppress NU1903 (CVE-2026-49451, circular schema stack overflow)
feat
- Add
UnifiedResult<T>AOT preservation — Source Generator auto-emitstypeof(UnifiedResult<T>)for all endpoint return types
Full Changelog: v0.5.4...v0.5.5