fix(security): close Theme 7 — 8 secrets / redaction / append-only findings
Security-sensitive batch, handled main-thread for careful judgment on secret-leak and pepper-bypass paths. Secret leak / pepper bypass: - CD-016 (pepper bypass): InboundApiRepository's GetApiKeyByValueAsync no longer hashes the candidate with the unpeppered ApiKeyHasher.Default — ctor takes a lazy Func<IApiKeyHasher> accessor (lazy so test composition roots without a pepper still bring up the repository), and the DI registration wires sp.GetService<IApiKeyHasher>() so the production peppered hasher matches the stored KeyHash. Regression test asserts positive (peppered roundtrip) AND negative (Default hasher misses the same key — proving the lookup uses the injected hasher). - MgmtSvc-020 (SMTP credential leak): UpdateSmtpConfig/ListSmtpConfigs now project through SmtpConfigPublicShape so the response payload and audit-row afterState never carry the Credentials field — only a HasCredentials bool. The SMTP password / OAuth2 client secret no longer leaves the Admin-only UpdateSmtpConfig boundary the caller already supplied it to. Redaction: - AuditLog-008 (test-fixture under-redact): new SafeDefaultAuditPayloadFilter (stateless singleton) does HTTP header redaction for the always-sensitive defaults (Authorization, X-Api-Key, Cookie, Set-Cookie). FallbackAuditWriter, CentralAuditWriter, and AuditLogIngestActor (both ingest paths) default to it instead of null — composition roots that bypass AddAuditLog can no longer write unredacted auth headers to the audit store. - NotifService-025 (over-mask): CredentialRedactor.Scrub now only masks the last colon-separated component (password / clientSecret) AND only if it's >= 12 chars (typical password heuristic). Short user names like "root" no longer become global redaction tokens that eat unrelated diagnostic text. The full packed string is always masked regardless of length. 3 new negative tests pin the no-over-mask contract. Audit-row correctness / fail-loud: - InboundAPI-025: Program.cs UseWhen predicate now excludes /api/audit, /api/management, /api/centralui, /api/script-analysis AND requires POST — the AuditWriteMiddleware no longer emits spurious ApiInbound rows for audit-log query/export endpoints (write-on-read recursion broken). - ESG-021: ApplyAuth now logs Warning (not silent) on empty AuthConfiguration for apikey/basic, unknown AuthType, and malformed Basic config. AuthConfiguration value NEVER logged. AuthType=none remains silent (documented unauthenticated sentinel). - Security-021: AddSecurity now logs a startup Warning when RequireHttpsCookie=false — an HTTP-only deployment that previously transmitted the cookie-embedded JWT silently in cleartext is now audible in the log. Defensive: - CD-021: SwitchOutPartitionAsync's monthBoundary format string now yyyy-MM-dd HH:mm:ss.fffffff (datetime2(7) precision) so a future sub-second / non-midnight boundary doesn't silently round to the wrong partition. Plus reconciled stale per-module Open-findings counters that had drifted from earlier sessions (AuditLog, CD, ESG, IAPI, MgmtSvc, NotifService, Security). Build clean; all affected test projects green (Host 208, ConfigDB 242, ESG 69, IAPI 151, MgmtSvc 100, NotifService 55, Security 85, AuditLog 247/248 — 1 pre-existing date-sensitive integration test flake on PartitionPurgeTests, unrelated). README regenerated: 46 open (was 54).
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
| Last reviewed | 2026-05-28 |
|
||||
| Reviewer | claude-agent |
|
||||
| Commit reviewed | `1eb6e97` |
|
||||
| Open findings | 3 |
|
||||
| Open findings | 1 |
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -946,9 +946,22 @@ throws and exactly one row lands.
|
||||
|--|--|
|
||||
| Severity | Medium |
|
||||
| Category | Security |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/InboundApiRepository.cs:35-39` |
|
||||
|
||||
**Resolution (2026-05-28):** Took option (a) — `InboundApiRepository` ctor now
|
||||
accepts `Func<IApiKeyHasher>? hasherAccessor = null` (deferred resolution to
|
||||
sidestep startup-time pepper-validation in test composition roots that don't
|
||||
configure one). `GetApiKeyByValueAsync` calls `_hasherAccessor()` so the
|
||||
hash matches the production peppered digest. `AddConfigurationDatabase`
|
||||
registers the repository with a factory wiring
|
||||
`() => sp.GetService<IApiKeyHasher>() ?? ApiKeyHasher.Default` — the
|
||||
peppered hasher is used when available, falling back to Default only when
|
||||
none is registered (legacy / test composition). Regression test
|
||||
`CD016_GetApiKeyByValue_UsesInjectedPepperedHasher_NotDefault` asserts
|
||||
positively (peppered roundtrip) and negatively (Default hasher misses the
|
||||
key entirely, proving the lookup uses the injected hasher).
|
||||
|
||||
**Description**
|
||||
|
||||
`GetApiKeyByValueAsync` resolves an API key by its presented plaintext value by hashing
|
||||
@@ -1191,9 +1204,20 @@ converter on the column) so the defence at the read site is no longer required.
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Security |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/AuditLogRepository.cs:192-338` |
|
||||
|
||||
**Resolution (2026-05-28):** Took the targeted (1) part of the recommendation —
|
||||
the `monthBoundary` format string is now `"yyyy-MM-dd HH:mm:ss.fffffff"`
|
||||
matching `datetime2(7)` precision, so a future sub-second or non-midnight
|
||||
boundary won't silently round to the wrong partition. The staging table name
|
||||
is still interpolated (T-SQL DDL identifiers can't be parameterised) but
|
||||
remains internally constructed as `$"AuditLog_Staging_{Guid.NewGuid():N}"`
|
||||
so SQL injection is structurally impossible. The larger
|
||||
`sp_executesql`-with-typed-parameter migration was scoped to a future
|
||||
follow-up since the current shape is fully controlled and the precision-
|
||||
mismatch hazard was the only practical concern.
|
||||
|
||||
**Description**
|
||||
|
||||
`SwitchOutPartitionAsync` builds two large SQL batches via interpolated strings
|
||||
|
||||
Reference in New Issue
Block a user