[E00-S04-T03] Secrets automatically redact from logs #184

Closed
opened 2026-08-27 00:08:43 +00:00 by kpcto · 14 comments
Owner

Parent story: [E00-S04] Configuration service (#61)

Intent

Ensure secrets automatically redact from logs.

Acceptance criteria

  • secrets automatically redact from logs
  • log output contains no secret values

Explicitly out of scope

  • field-specific startup error (E00-S04-T02)
  • process.env access rule (E00-S04-T04)
  • .env.example placeholders (E00-S04-T05)

Test plan

  • log configuration and confirm secret values are redacted

Rollback note

  • revert the redaction changes

Owning stream

platform

Risk quadrant

agent-full

> Parent story: [E00-S04] Configuration service (#61) ## Intent Ensure secrets automatically redact from logs. ## Acceptance criteria - secrets automatically redact from logs - log output contains no secret values ## Explicitly out of scope - field-specific startup error (E00-S04-T02) - process.env access rule (E00-S04-T04) - .env.example placeholders (E00-S04-T05) ## Test plan - log configuration and confirm secret values are redacted ## Rollback note - revert the redaction changes ### Owning stream platform ### Risk quadrant agent-full
kpcto added this to the Sprint 0 milestone 2026-08-27 00:08:43 +00:00
kpcto added the
status
ready
kind
task
labels 2026-08-27 00:08:43 +00:00
bot-dispatcher added
status
proposed
and removed
status
ready
kind
task
labels 2026-08-27 00:08:45 +00:00
Member

Auto-reverted by dispatcher: DoR lint: required section "Intent" is empty; required section "Acceptance criteria" is empty; required section "Explicitly out of scope" is empty; required section "Test plan" is empty; required section "Rollback note" is empty; acceptance criteria: no bullet assertions found

status/ready may only be applied by a human maintainer.

> Auto-reverted by dispatcher: DoR lint: required section "Intent" is empty; required section "Acceptance criteria" is empty; required section "Explicitly out of scope" is empty; required section "Test plan" is empty; required section "Rollback note" is empty; acceptance criteria: no bullet assertions found `status/ready` may only be applied by a human maintainer.
kpcto added the
kind
task
label 2026-08-27 21:25:06 +00:00
kpcto added
status
ready
and removed
status
proposed
labels 2026-08-30 03:34:49 +00:00
bot-dispatcher added
loop
1
status
in-progress
and removed
status
ready
labels 2026-08-30 03:34:58 +00:00
Member
agent: dispatcher
phase: complete
plan_summary: Dispatch #184: [E00-S04-T03] Secrets automatically redact from logs (platform, security-gated)
```yaml agent: dispatcher phase: complete plan_summary: Dispatch #184: [E00-S04-T03] Secrets automatically redact from logs (platform, security-gated) ```
bot-dispatcher added the needs/security-reviewneeds/security-review labels 2026-08-30 03:34:58 +00:00
Member
agent: implementer
phase: start
issue: 184
objective: "[E00-S04-T03] Secrets automatically redact from logs"
plan: inspect config package + server logging, add redaction layer, lock with tests, open PR
branch: feature/184
```yaml agent: implementer phase: start issue: 184 objective: "[E00-S04-T03] Secrets automatically redact from logs" plan: inspect config package + server logging, add redaction layer, lock with tests, open PR branch: feature/184 ```
Member
agent: security
phase: start
issue: "184"
pr: 402
head_branch: feature/184
head_sha: 25248461e0e983e16fb6213d1726c9757aebd9d7
objective: security review of PR #402 (feature/184) — secrets automatically redact from logs
plan:
  - run secrets scan (gitleaks) over the PR tree
  - run dependency scan (osv-scanner) over the PR tree
  - trace authz / input boundaries for all new code paths in the diff
  - post final verdict YAML comment on this issue
status: in-progress
verdict: pending
```yaml agent: security phase: start issue: "184" pr: 402 head_branch: feature/184 head_sha: 25248461e0e983e16fb6213d1726c9757aebd9d7 objective: security review of PR #402 (feature/184) — secrets automatically redact from logs plan: - run secrets scan (gitleaks) over the PR tree - run dependency scan (osv-scanner) over the PR tree - trace authz / input boundaries for all new code paths in the diff - post final verdict YAML comment on this issue status: in-progress verdict: pending ```
Member
agent: security
issue: 184
pr: 402
head_branch: feature/184
phase: started
scope: security review of PR #402 (secrets automatically redact from logs)
checks_planned:
  - gitleaks (secrets in diff/tree)
  - osv-scanner (dependencies)
  - manual diff review (authz trace, input boundaries, redaction correctness/bypass)
status: in-progress
```yaml agent: security issue: 184 pr: 402 head_branch: feature/184 phase: started scope: security review of PR #402 (secrets automatically redact from logs) checks_planned: - gitleaks (secrets in diff/tree) - osv-scanner (dependencies) - manual diff review (authz trace, input boundaries, redaction correctness/bypass) status: in-progress
Member
agent: security
issue: 184
pr: 402
head_branch: feature/184
head_sha: ae900589c3834a3d3f3e7406562b4b4480975254
phase: complete
verdict: approve
blocking_findings: 0
summary: >-
  Fail-closed secret redaction done right: packages/config/src/redact.ts masks the
  databaseUrl password in place (postgres://user:[REDACTED]@host), replaces an
  unparseable databaseUrl wholesale rather than logging it raw, and redactText
  scrubs every log line of the config's secret values via exact-match split/join
  (no regex, no ReDoS surface). The server's redacting logger covers ALL output —
  every former console.log/console.error call site now routes through it, the
  resolved configuration is logged with sessionSecret as [REDACTED], and the boot
  probes assert secret absence across stdout AND stderr. No new routes, handlers,
  endpoints, or outbound requests; no secrets committed; the CI change is additive
  and matches the security-reviewed #396/#397 precedent.
scanners:
  gitleaks:
    command: gitleaks detect --source . --no-git --redact
    result: clean
    evidence: 'exit 0, "no leaks found" (~502 KB scanned); test fixtures are synthetic placeholders only (redact-me-0123..., redact-user:redact-password)'
  osv-scanner:
    command: osv-scanner --recursive .
    result: clean
    evidence: 'exit 0, "No issues found" (25 packages); the only dependency change is the @types/node 24.13.3 devDep, exact-pinned, already resolved in the lockfile (no new package entries)'
  semgrep:
    result: skipped
    evidence: 'not installed in the worker image (command -v semgrep fails) — gap covered by the manual authz/input trace per the scanner playbook'
  ci:
    run: 'http://gitea:3000/Fabrika/PersonalBlog/actions/runs/101'
    status: queued at review time (head ae90058)
authz_trace:
  new_routes: none
  note: >-
    The diff adds no route/handler/endpoint; the only HTTP surface remains the
    pre-existing public GET /health. The resolved-configuration log goes to stdout
    only, after assertValidConfig, through redactConfig. No default-allow path and
    no bypass entry point introduced; no new outbound request (SSRF n/a).
    new URL(databaseUrl) parses operator-supplied env config, not request input.
input_boundaries:
  injection: 'none — no SQL/command/template/header construction in the diff; scrubbing uses split/join exact match, no regex (ReDoS n/a)'
  deserialization: 'none — log args are serialized via JSON.stringify wrapped in try/catch (circular-safe), never deserialized'
findings:
  - id: F1
    severity: low
    blocking: false
    location: 'packages/config/src/redact.ts:100-107'
    what: >-
      redactText scrubs only the WHATWG-decoded databaseUrl password; a
      percent-encoded password embedded verbatim in free text (e.g. an error
      message quoting DATABASE_URL) would evade the exact-match scrub.
    exploit_path: >-
      No current path: the app never logs the raw connection string (the config
      log masks the password in place; MigrationFailedError diagnostics embed
      only name/message/pg error code, not the URL) and the failure is caught
      and routed through the redacting logger.
    fix: >-
      Also collect encodeURIComponent(password) (and the full raw URL string)
      in secretValuesOf when scrubbing free text.
  - id: F2
    severity: info
    blocking: false
    location: 'apps/server/src/index.ts:143-152'
    what: >-
      Node's default fatal-error handler writes uncaught exceptions straight to
      stderr, bypassing the redacting logger.
    exploit_path: >-
      No current path: validation errors name fields, not values, and migration
      failures are caught and logged via logger.error.
    fix: >-
      Later story: route uncaughtException/unhandledRejection through
      createLogger.
  - id: F3
    severity: info
    blocking: false
    location: 'packages/config/src/redact.ts:43'
    what: >-
      The scrub list is closed over SECRET_FIELD_NAMES (currently only
      sessionSecret) plus the databaseUrl password; a future secret outside the
      config schema would not be redacted.
    exploit_path: >-
      None today: the schema is closed (additionalProperties false) and these
      are its only secret-bearing fields.
    fix: >-
      Extend SECRET_FIELD_NAMES when a new secret config field lands — it is
      schema-derived, one line, and gated by this suite's mutation probes.
  - id: F4
    severity: info
    blocking: false
    location: '.gitea/workflows/ci.yml:208-231'
    what: >-
      New merge-gating CI job (config-log-redaction) — pipeline change flagged
      per the review-checklist tripwire for the human merge-gate decision.
    exploit_path: >-
      None: additive job, pinned actions (checkout@v4, setup-node@v4), frozen
      lockfile, no secrets exposed, matches the #396/#397 precedent.
    fix: >-
      No security change needed; human sign-off on the new gate per checklist.
verified:
  - 'No bare console.log/console.error remains in apps/ or packages/ TS sources (grep: only a doc-comment mention); the suite mutation-probes its reintroduction'
  - 'All four former console call sites now route through the redacting logger (log and error)'
  - 'redactDatabaseUrl masks the password in place and replaces an unparseable databaseUrl wholesale (fail-closed — the schema allows any non-empty string, so this path is load-bearing)'
  - 'redactConfig operates on the validated config (assertValidConfig returns it) and skips only empty values when scrubbing'
  - 'Boot probes assert the secret value is absent from stdout AND stderr combined, and that the masked URL appears'
  - 'Docs accurately describe the redacted log line; pnpm-lock.yaml change is the devDep only'
review_comment_start: 'http://gitea:3000/Fabrika/PersonalBlog/issues/184#issuecomment-3261'
```yaml agent: security issue: 184 pr: 402 head_branch: feature/184 head_sha: ae900589c3834a3d3f3e7406562b4b4480975254 phase: complete verdict: approve blocking_findings: 0 summary: >- Fail-closed secret redaction done right: packages/config/src/redact.ts masks the databaseUrl password in place (postgres://user:[REDACTED]@host), replaces an unparseable databaseUrl wholesale rather than logging it raw, and redactText scrubs every log line of the config's secret values via exact-match split/join (no regex, no ReDoS surface). The server's redacting logger covers ALL output — every former console.log/console.error call site now routes through it, the resolved configuration is logged with sessionSecret as [REDACTED], and the boot probes assert secret absence across stdout AND stderr. No new routes, handlers, endpoints, or outbound requests; no secrets committed; the CI change is additive and matches the security-reviewed #396/#397 precedent. scanners: gitleaks: command: gitleaks detect --source . --no-git --redact result: clean evidence: 'exit 0, "no leaks found" (~502 KB scanned); test fixtures are synthetic placeholders only (redact-me-0123..., redact-user:redact-password)' osv-scanner: command: osv-scanner --recursive . result: clean evidence: 'exit 0, "No issues found" (25 packages); the only dependency change is the @types/node 24.13.3 devDep, exact-pinned, already resolved in the lockfile (no new package entries)' semgrep: result: skipped evidence: 'not installed in the worker image (command -v semgrep fails) — gap covered by the manual authz/input trace per the scanner playbook' ci: run: 'http://gitea:3000/Fabrika/PersonalBlog/actions/runs/101' status: queued at review time (head ae90058) authz_trace: new_routes: none note: >- The diff adds no route/handler/endpoint; the only HTTP surface remains the pre-existing public GET /health. The resolved-configuration log goes to stdout only, after assertValidConfig, through redactConfig. No default-allow path and no bypass entry point introduced; no new outbound request (SSRF n/a). new URL(databaseUrl) parses operator-supplied env config, not request input. input_boundaries: injection: 'none — no SQL/command/template/header construction in the diff; scrubbing uses split/join exact match, no regex (ReDoS n/a)' deserialization: 'none — log args are serialized via JSON.stringify wrapped in try/catch (circular-safe), never deserialized' findings: - id: F1 severity: low blocking: false location: 'packages/config/src/redact.ts:100-107' what: >- redactText scrubs only the WHATWG-decoded databaseUrl password; a percent-encoded password embedded verbatim in free text (e.g. an error message quoting DATABASE_URL) would evade the exact-match scrub. exploit_path: >- No current path: the app never logs the raw connection string (the config log masks the password in place; MigrationFailedError diagnostics embed only name/message/pg error code, not the URL) and the failure is caught and routed through the redacting logger. fix: >- Also collect encodeURIComponent(password) (and the full raw URL string) in secretValuesOf when scrubbing free text. - id: F2 severity: info blocking: false location: 'apps/server/src/index.ts:143-152' what: >- Node's default fatal-error handler writes uncaught exceptions straight to stderr, bypassing the redacting logger. exploit_path: >- No current path: validation errors name fields, not values, and migration failures are caught and logged via logger.error. fix: >- Later story: route uncaughtException/unhandledRejection through createLogger. - id: F3 severity: info blocking: false location: 'packages/config/src/redact.ts:43' what: >- The scrub list is closed over SECRET_FIELD_NAMES (currently only sessionSecret) plus the databaseUrl password; a future secret outside the config schema would not be redacted. exploit_path: >- None today: the schema is closed (additionalProperties false) and these are its only secret-bearing fields. fix: >- Extend SECRET_FIELD_NAMES when a new secret config field lands — it is schema-derived, one line, and gated by this suite's mutation probes. - id: F4 severity: info blocking: false location: '.gitea/workflows/ci.yml:208-231' what: >- New merge-gating CI job (config-log-redaction) — pipeline change flagged per the review-checklist tripwire for the human merge-gate decision. exploit_path: >- None: additive job, pinned actions (checkout@v4, setup-node@v4), frozen lockfile, no secrets exposed, matches the #396/#397 precedent. fix: >- No security change needed; human sign-off on the new gate per checklist. verified: - 'No bare console.log/console.error remains in apps/ or packages/ TS sources (grep: only a doc-comment mention); the suite mutation-probes its reintroduction' - 'All four former console call sites now route through the redacting logger (log and error)' - 'redactDatabaseUrl masks the password in place and replaces an unparseable databaseUrl wholesale (fail-closed — the schema allows any non-empty string, so this path is load-bearing)' - 'redactConfig operates on the validated config (assertValidConfig returns it) and skips only empty values when scrubbing' - 'Boot probes assert the secret value is absent from stdout AND stderr combined, and that the masked URL appears' - 'Docs accurately describe the redacted log line; pnpm-lock.yaml change is the devDep only' review_comment_start: 'http://gitea:3000/Fabrika/PersonalBlog/issues/184#issuecomment-3261'
bot-dispatcher removed the needs/security-reviewneeds/security-review labels 2026-08-30 03:57:50 +00:00
Member
agent: reviewer
verdict: approve
```yaml agent: reviewer verdict: approve ```
bot-dispatcher added
status
review
and removed
status
in-progress
loop
1
kind
task
labels 2026-08-30 03:58:56 +00:00
Member
agent: security
phase: complete
issue: "184"
pr: 402
head_branch: feature/184
head_sha: 25248461e0e983e16fb6213d1726c9757aebd9d7
scope: security review of PR #402 — secrets automatically redact from logs
verdict: approve
blockers: 0
scanners:
  gitleaks: "pass — exit 0, 'no leaks found' (full-tree scan, ~502 KB, --redact)"
  osv-scanner: "pass — exit 0, 'No issues found' (25 packages from pnpm-lock.yaml)"
  semgrep: "skipped — not installed in the worker image; gap covered by the manual authz/input trace below"
checks:
  authz: "pass — no new routes/handlers/endpoints; handleRequest (health/404) untouched (diff-verified); no default-allow or bypass paths introduced"
  injection: "pass — no SQL/command/template/header sinks; no deserialization of untrusted data; no SSRF (node:url used to parse operator config for redaction only)"
  secrets: "pass — no secret literals in the diff, tests (fake values: redact-me-…/redact-password), or the 3 commit messages; gitleaks clean"
  redaction_correctness: "pass — empirically verified with the exact committed logic: secret field replaced by name; databaseUrl password masked in place (plain AND percent-encoded form — non-special URL schemes keep userinfo encoded, so the scrub value matches raw log text); unparseable URL replaced wholesale in redactConfig AND in the free-text scrub; non-secret fields pass through"
  error_paths: "pass — startup validation errors name fields only (Ajv messages, no verbose values); MigrationFailedError.message carries no credential and the logger serializes errors by message only (cause/diagnostic never printed)"
  test_honesty: "pass — tests assert behavior, not implementation detail: 9 mutation probes prove the static assertions non-vacuous; 2 server boot probes assert the ABSENCE of the session secret, the database password and the raw connection string across stdout+stderr; boot-probe skips are env-gated (build artifacts) with an explicit hint and the CI job builds first, so they run for real in the gating job"
  criterion_test_mapping: "pass — both acceptance criteria map to tests that fail without the change (static + mutation + compiled-boundary probe + server boot probes), gated on every PR by the new CI job"
findings:
  - id: 1
    severity: should
    file: "apps/server/src/index.ts:135"
    what: "the redaction guarantee covers only logger-mediated writes; Node runtime-level output (uncaught exceptions, unhandled rejections, process warnings) prints to stderr directly and bypasses redactText"
    exploit_path: "a future unhandled rejection whose text embeds a credential (e.g. a connection string) would reach stderr unredacted — no current code path does this (migration errors are caught; pg errors carry host:port, not credentials), so both acceptance criteria hold today"
    fix: "register process.on('uncaughtException'/'unhandledRejection') handlers that route through logger.error when the Fastify shell lands (or now, as defense-in-depth)"
  - id: 2
    severity: nit
    file: "packages/config/src/redact.ts:122"
    what: "encodeURIComponent(url.username) double-encodes an already-percent-encoded username (WHATWG keeps userinfo encoded for non-special schemes), e.g. user%40x is logged as user%2540x"
    exploit_path: "none — usernames are non-secret and the password slot is always [REDACTED]; cosmetic only"
    fix: "use url.username verbatim (it is already URL-encoded for non-special schemes)"
flags:
  - "pipeline-tripwire: diff touches .gitea/workflows/ci.yml — flagged per review-checklist §pipeline-tripwire. Resolution evidence: the change is strictly additive (new config-log-redaction job; no existing job modified/removed, no secrets: context, no pull_request_target, no untrusted ${{ }} interpolation into run:) and it ADDS a merge gate. The identical pattern was merged twice by the human maintainer (PR #396 config-schema job, PR #397 config-startup-error job — both merged_by kpcto, the owner of this issue; #397's merge commit is this PR's base), so the required human decision exists on record; the merge of #402 itself remains a human action."
notes:
  - "SECRET_FIELD_NAMES is a manual list (currently ['sessionSecret']) pinned by test; when a future schema field becomes secret, it must be added there — the pin makes drift visible."
  - "redactText scrubbing is substring-based, so over-redaction (a secret value that is a substring of unrelated logged text) is possible by design — the fail-safe direction."
  - "scope: all 10 changed files map to the E00-S04-T03 brief (redact.ts, boundary re-exports, server redacting logger, CI job, docs, @types/node devDep + tsconfig types, lockfile, tests); no unrelated changes."
  - "out-of-scope items respected: process.env access rule (T04) and .env.example (T05) untouched."
```yaml agent: security phase: complete issue: "184" pr: 402 head_branch: feature/184 head_sha: 25248461e0e983e16fb6213d1726c9757aebd9d7 scope: security review of PR #402 — secrets automatically redact from logs verdict: approve blockers: 0 scanners: gitleaks: "pass — exit 0, 'no leaks found' (full-tree scan, ~502 KB, --redact)" osv-scanner: "pass — exit 0, 'No issues found' (25 packages from pnpm-lock.yaml)" semgrep: "skipped — not installed in the worker image; gap covered by the manual authz/input trace below" checks: authz: "pass — no new routes/handlers/endpoints; handleRequest (health/404) untouched (diff-verified); no default-allow or bypass paths introduced" injection: "pass — no SQL/command/template/header sinks; no deserialization of untrusted data; no SSRF (node:url used to parse operator config for redaction only)" secrets: "pass — no secret literals in the diff, tests (fake values: redact-me-…/redact-password), or the 3 commit messages; gitleaks clean" redaction_correctness: "pass — empirically verified with the exact committed logic: secret field replaced by name; databaseUrl password masked in place (plain AND percent-encoded form — non-special URL schemes keep userinfo encoded, so the scrub value matches raw log text); unparseable URL replaced wholesale in redactConfig AND in the free-text scrub; non-secret fields pass through" error_paths: "pass — startup validation errors name fields only (Ajv messages, no verbose values); MigrationFailedError.message carries no credential and the logger serializes errors by message only (cause/diagnostic never printed)" test_honesty: "pass — tests assert behavior, not implementation detail: 9 mutation probes prove the static assertions non-vacuous; 2 server boot probes assert the ABSENCE of the session secret, the database password and the raw connection string across stdout+stderr; boot-probe skips are env-gated (build artifacts) with an explicit hint and the CI job builds first, so they run for real in the gating job" criterion_test_mapping: "pass — both acceptance criteria map to tests that fail without the change (static + mutation + compiled-boundary probe + server boot probes), gated on every PR by the new CI job" findings: - id: 1 severity: should file: "apps/server/src/index.ts:135" what: "the redaction guarantee covers only logger-mediated writes; Node runtime-level output (uncaught exceptions, unhandled rejections, process warnings) prints to stderr directly and bypasses redactText" exploit_path: "a future unhandled rejection whose text embeds a credential (e.g. a connection string) would reach stderr unredacted — no current code path does this (migration errors are caught; pg errors carry host:port, not credentials), so both acceptance criteria hold today" fix: "register process.on('uncaughtException'/'unhandledRejection') handlers that route through logger.error when the Fastify shell lands (or now, as defense-in-depth)" - id: 2 severity: nit file: "packages/config/src/redact.ts:122" what: "encodeURIComponent(url.username) double-encodes an already-percent-encoded username (WHATWG keeps userinfo encoded for non-special schemes), e.g. user%40x is logged as user%2540x" exploit_path: "none — usernames are non-secret and the password slot is always [REDACTED]; cosmetic only" fix: "use url.username verbatim (it is already URL-encoded for non-special schemes)" flags: - "pipeline-tripwire: diff touches .gitea/workflows/ci.yml — flagged per review-checklist §pipeline-tripwire. Resolution evidence: the change is strictly additive (new config-log-redaction job; no existing job modified/removed, no secrets: context, no pull_request_target, no untrusted ${{ }} interpolation into run:) and it ADDS a merge gate. The identical pattern was merged twice by the human maintainer (PR #396 config-schema job, PR #397 config-startup-error job — both merged_by kpcto, the owner of this issue; #397's merge commit is this PR's base), so the required human decision exists on record; the merge of #402 itself remains a human action." notes: - "SECRET_FIELD_NAMES is a manual list (currently ['sessionSecret']) pinned by test; when a future schema field becomes secret, it must be added there — the pin makes drift visible." - "redactText scrubbing is substring-based, so over-redaction (a secret value that is a substring of unrelated logged text) is possible by design — the fail-safe direction." - "scope: all 10 changed files map to the E00-S04-T03 brief (redact.ts, boundary re-exports, server redacting logger, CI job, docs, @types/node devDep + tsconfig types, lockfile, tests); no unrelated changes." - "out-of-scope items respected: process.env access rule (T04) and .env.example (T05) untouched." ```
Member

Security approved; dispatching reviewer.

> Security approved; dispatching reviewer.
kpcto added
kind
task
status
done
and removed
status
review
labels 2026-08-30 04:00:49 +00:00
kpcto closed this issue 2026-08-30 04:00:54 +00:00
Member
agent: reviewer
verdict: approve
```yaml agent: reviewer verdict: approve ```
bot-dispatcher added
status
review
and removed
status
done
kind
task
labels 2026-08-30 04:00:58 +00:00
Member

Security approved; dispatching reviewer.

> Security approved; dispatching reviewer.
Member
agent: implementer
phase: complete
issue: 184
pr: 402
state: merged
summary: |
  [E00-S04-T03] Secrets automatically redact from logs — implemented and merged.
  - packages/config gains the redaction layer (redactConfig + redactText, REDACTED placeholder,
    schema-derived SECRET_FIELD_NAMES); the server logs its resolved configuration at startup
    with secret values replaced by [REDACTED] and all log output goes through a redacting logger
    (no bare console.log/console.error).
  - New tests/config-log-redaction.test.mjs (16 tests): static assertions + mutation probes +
    compiled-boundary probe + server boot probes executing the issue's test plan
    ("log configuration and confirm secret values are redacted").
  - New additive CI job config-log-redaction (E00-S04-T03); full run green on Node 24.
ci: green (run 101, head ae90058, all 11 jobs success)
acceptance: secrets redact from logs; log output contains no secret values (covered by criterion-to-test table in PR #402 description)
```yaml agent: implementer phase: complete issue: 184 pr: 402 state: merged summary: | [E00-S04-T03] Secrets automatically redact from logs — implemented and merged. - packages/config gains the redaction layer (redactConfig + redactText, REDACTED placeholder, schema-derived SECRET_FIELD_NAMES); the server logs its resolved configuration at startup with secret values replaced by [REDACTED] and all log output goes through a redacting logger (no bare console.log/console.error). - New tests/config-log-redaction.test.mjs (16 tests): static assertions + mutation probes + compiled-boundary probe + server boot probes executing the issue's test plan ("log configuration and confirm secret values are redacted"). - New additive CI job config-log-redaction (E00-S04-T03); full run green on Node 24. ci: green (run 101, head ae90058, all 11 jobs success) acceptance: secrets redact from logs; log output contains no secret values (covered by criterion-to-test table in PR #402 description) ```
Member
agent: tester
phase: start
issue: 184
pr: 402
head_branch: feature/184
head_sha: ae900589c3834a3d3f3e7406562b4b4480975254
objective: independently probe PR #402 — secrets automatically redact from logs
plan:
  - read the PR diff, acceptance criteria and the implementer's suite (read-only)
  - independently exercise the redaction layer (redactConfig/redactText) with fresh edge-case inputs not taken from the implementer's suite
  - boot the committed server and assert stdout+stderr contain no secret values
  - confirm the config-log-redaction CI job gates the criterion and collect CI links
  - post an evidence comment with CI links
constraint: never edit the implementer's tests
status: in-progress
```yaml agent: tester phase: start issue: 184 pr: 402 head_branch: feature/184 head_sha: ae900589c3834a3d3f3e7406562b4b4480975254 objective: independently probe PR #402 — secrets automatically redact from logs plan: - read the PR diff, acceptance criteria and the implementer's suite (read-only) - independently exercise the redaction layer (redactConfig/redactText) with fresh edge-case inputs not taken from the implementer's suite - boot the committed server and assert stdout+stderr contain no secret values - confirm the config-log-redaction CI job gates the criterion and collect CI links - post an evidence comment with CI links constraint: never edit the implementer's tests status: in-progress ```
Member
agent: tester
phase: complete
issue: 184
pr: 402
head_branch: feature/184
head_sha: ae900589c3834a3d3f3e7406562b4b4480975254
verdict: pass
blockers: 0
constraint_respected: "never edited the implementer's tests (suite run read-only as a cross-check only)"
method: |
  Independent probing, not a re-run of the implementer's assertions. I wrote two
  fresh probe scripts OUTSIDE the repo tests/ tree:
  1. Redaction-layer probe — imports the committed packages/config/src/redact.ts
     directly (Node type stripping) and drives redactConfig/redactText with
     distinct edge-case inputs (percent-encoded password, multiple occurrences,
     unparseable URL, passwordless URL, non-secret passthrough).
  2. Server-boot probe — boots the committed apps/server/src/index.ts with
     distinct secrets and asserts the acceptance criteria at the process level
     across stdout+stderr (after a fresh frozen-lockfile install and package build).
results:
  redaction_layer:
    checks: 16
    passed: 16
    failed: 0
    highlights: |
      sessionSecret replaced by name -> [REDACTED]; databaseUrl password masked in
      place (postgres://tester-user:[REDACTED]@host), including a percent-encoded
      password; unparseable databaseUrl replaced wholesale; passwordless URL left
      unchanged; redactText scrubs single + multiple occurrences; combined output
      contains no session secret, no raw DB password, no raw connection string.
  server_boot:
    checks: 10
    passed: 10
    failed: 0
    highlights: |
      no-DB boot -> GET /health 200; resolved config logged with
      "sessionSecret":"[REDACTED]"; stdout+stderr contain no secret value.
      dead-port DATABASE_URL boot -> GET /health 503 (not ready); resolved-config
      log masks the password in place; migration failure logged via the redacting
      logger; stdout+stderr contain no DB password, no raw connection string, no
      session secret.
  cross_check:
    implementer_suite: "node --test tests/config-log-redaction.test.mjs -> 16 pass / 0 fail / 0 skip (fresh build; boot probes ran for real)"
acceptance:
  - "secrets automatically redact from logs: PASS"
  - "log output contains no secret values: PASS"
ci:
  run: "http://gitea:3000/Fabrika/PersonalBlog/actions/runs/101"
  redaction_job: "http://gitea:3000/Fabrika/PersonalBlog/actions/runs/101/jobs/241"
  redaction_job_status: "success — 'Secret redaction from logs (E00-S04-T03)', all 5 steps success"
  run_summary: "11/11 jobs success on head ae90058 (Node 24)"
```yaml agent: tester phase: complete issue: 184 pr: 402 head_branch: feature/184 head_sha: ae900589c3834a3d3f3e7406562b4b4480975254 verdict: pass blockers: 0 constraint_respected: "never edited the implementer's tests (suite run read-only as a cross-check only)" method: | Independent probing, not a re-run of the implementer's assertions. I wrote two fresh probe scripts OUTSIDE the repo tests/ tree: 1. Redaction-layer probe — imports the committed packages/config/src/redact.ts directly (Node type stripping) and drives redactConfig/redactText with distinct edge-case inputs (percent-encoded password, multiple occurrences, unparseable URL, passwordless URL, non-secret passthrough). 2. Server-boot probe — boots the committed apps/server/src/index.ts with distinct secrets and asserts the acceptance criteria at the process level across stdout+stderr (after a fresh frozen-lockfile install and package build). results: redaction_layer: checks: 16 passed: 16 failed: 0 highlights: | sessionSecret replaced by name -> [REDACTED]; databaseUrl password masked in place (postgres://tester-user:[REDACTED]@host), including a percent-encoded password; unparseable databaseUrl replaced wholesale; passwordless URL left unchanged; redactText scrubs single + multiple occurrences; combined output contains no session secret, no raw DB password, no raw connection string. server_boot: checks: 10 passed: 10 failed: 0 highlights: | no-DB boot -> GET /health 200; resolved config logged with "sessionSecret":"[REDACTED]"; stdout+stderr contain no secret value. dead-port DATABASE_URL boot -> GET /health 503 (not ready); resolved-config log masks the password in place; migration failure logged via the redacting logger; stdout+stderr contain no DB password, no raw connection string, no session secret. cross_check: implementer_suite: "node --test tests/config-log-redaction.test.mjs -> 16 pass / 0 fail / 0 skip (fresh build; boot probes ran for real)" acceptance: - "secrets automatically redact from logs: PASS" - "log output contains no secret values: PASS" ci: run: "http://gitea:3000/Fabrika/PersonalBlog/actions/runs/101" redaction_job: "http://gitea:3000/Fabrika/PersonalBlog/actions/runs/101/jobs/241" redaction_job_status: "success — 'Secret redaction from logs (E00-S04-T03)', all 5 steps success" run_summary: "11/11 jobs success on head ae90058 (Node 24)" ```
Sign in to join this conversation.