Skip to content

Production log redaction + retention + cost + log sink #273

Description

@mforce

Deployment-readiness gap (#244), RECOMMENDED — flagged by 2 of 3 reviewers.

Problem

Production logging has no redaction, retention, or cost policy, and no aggregation sink:

No repository policy names forbidden fields or retention limits.

Fix

Sample production-shaped logs before launch; redact credentials, tokens, cookies, connection strings, emails, phone/address, and sensitive free text at ingestion; choose a log sink with defined retention, access control, a sampling/volume budget, and deletion handling.

Verify

Grep a captured production-shaped log sample for the forbidden-field set → zero hits; confirm retention + access on the chosen sink.

Part of the #244 deployment-readiness audit; tracked in epic #15.

Security-event amendment — 2026-07-31

The audit found that sink selection and redaction are only half of the detection control: several security-relevant outcomes are not emitted as stable structured events. This issue now also owns safe event emission. Any host-specific alert configuration should be addressed separately in a private deploy/infra tracker.

Acceptance additions

  • Emit stable structured events for failed login, account lockout, refresh-token replay/reuse detection, refresh revocation failure where relevant, and auth rate-limit rejection.
  • Events carry the minimum useful correlation fields without passwords, tokens, cookies, raw connection strings, or an identity-existence oracle.
  • Client address and user identifiers follow the documented PII/redaction and retention policy.
  • Integration tests prove each event fires once with the expected event id and safe field set.
  • The deployment backend can alert on brute-force, replay, and abnormal rejection rates using those event ids.
  • A production-shaped captured-log scan still produces zero forbidden-field hits.

Activity

  1. changed the title [-]Deploy: production log redaction + retention + cost + log sink[/-] [+]Production log redaction + retention + cost + log sink[/+] on Jul 30, 2026
  2. mforce commented on Aug 3, 2026

    @mforce
    OwnerAuthor

    Escalation from #404 (PR #405) — 2026-08-02

    Two of the exposures listed above stop being latent when #405 merges. Recording it here so this issue is scoped against what is actually shipping, not against the pre-#404 behaviour.

    #404 switches the Production console sink from a human outputTemplate to CompactJsonFormatter. The template rendered exactly six things — Timestamp, Level, TraceId, SpanId, Message, Exception — and silently dropped every other property on the event. Compact JSON serializes all of them. Nothing about what the app attaches changed; what reaches the collector did.

    Concretely, for the item above about /client-errors writing caller-controlled content into the log pipeline: ClientErrorEndpoints.cs attaches Stack and ComponentStack (up to 8K chars each), AppVersion and ClientTraceId via BeginScope. Those are anonymous, caller-supplied, CRLF-sanitized but not content-redacted, and as of #405 they are retained by whatever collects stdout. Browser stacks routinely carry URLs with query strings, which can carry tokens.

    The startup-logging item (real email addresses) is worth re-checking on the same basis — any property that was invisible only because the template didn't name it is now emitted.

    One thing that gets better: the log-forging risk behind the control-character stripping is specific to a plain-text sink. A JSON writer escapes control characters, so no property value can break out of its string and forge a record. That risk now applies to Development's template, not Production. The stripping stays unconditional regardless — the sink format is a configuration choice the emitting code cannot see.

    This was raised by two independent reviewers on #405 (codex CLI and pi) and the decision there was to ship the format change and escalate here rather than pull redaction into a config PR — redaction is this issue's acceptance criterion, and defining the forbidden-field set is the substance of it.

    Suggested acceptance addition: the production-shaped captured-log scan should be run against a sample containing a real /client-errors report, since that is the highest-volume anonymous free-text path into the sink.

  3. mforce commented on Aug 3, 2026

    @mforce
    OwnerAuthor

    Correction to my 2026-08-02 escalation above. That comment read as though redaction were unstarted work. It is not: PR #349 (feat/273-log-redaction-security-events, open as a draft and updated today) already implements it, and its body states the /client-errors Stack/ComponentStack leak was confirmed and fixed end-to-end via SensitiveDataRedactionEnricher, wired once so it covers every sink.

    So the exposure #404/#405 makes live is bounded by #349 landing, not open-ended. The escalation still stands as a sequencing note — whichever of the two merges second should be re-verified against the other — but the fix exists.

    The two PRs barely overlap: the only shared file is tests/Cluckwork.Api.IntegrationTests/ClientErrorReportTests.cs, where #405 only rewrites a comment that its own change made false. Worth noting for whoever rebases second: #349 touches CluckworkTelemetryServiceCollectionExtensions.cs to insert the redaction pipeline, while #405 changes only appsettings*.json and the formatter selection, so they compose rather than conflict — an enricher redacts before the formatter serializes.

    One consequence worth checking when they meet: #405 makes compact JSON emit every property, where the old template rendered six and dropped the rest. #349's enricher is therefore doing strictly more work post-#405 than the tests on its own branch exercise — any property that was invisible only because the template did not name it is now both emitted and in scope for redaction. Its LogRedactionTests should be re-run against the JSON formatter, not only the template, before the second of the two merges.

  4. mforce commented on Aug 8, 2026

    @mforce
    OwnerAuthor

    Closing: the in-repo scope shipped in PR #349 (redaction enricher + stable security events + per-event integration tests, all mutation-checked) with the forbidden-field scan pinned by LogRedactionTests against the #404 compact-JSON production shape, and the policy documented in docs/security/log-redaction-policy.md. The residue this issue names — concrete sink choice, retention window, and alert rules on the stable event ids — is deploy-side by the host-agnostic boundary and the 2026-07-31 amendment's own note, and belongs in the private deploy tracker.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions