Skip to content

feat(monitor): config-hazard advisory — default user disabled + AOF silent data loss (valkey#3983) - #337

Merged
jamby77 merged 4 commits into
masterfrom
feature/config-hazard-detector
Jul 27, 2026
Merged

feat(monitor): config-hazard advisory — default user disabled + AOF silent data loss (valkey#3983)#337
jamby77 merged 4 commits into
masterfrom
feature/config-hazard-detector

Conversation

@jamby77

@jamby77 jamby77 commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a config-hazard advisory for valkey-io/valkey#3983 (maintainer-confirmed bug): when the default ACL user is disabled while AOF is enabled — and default lacks the unrestricted +@all ~* &* grant — MULTI/EXEC and function-replicated writes are silently discarded on AOF reload. No error, the data is just gone.

We can't fix the server behaviour, but the dangerous configuration is fully detectable client-side and the fix is one config line. This PR detects it and surfaces a standing WARNING everywhere an operator or agent would look. First of the five upstream-issue detector cards added to the board on 2026-07-20.

Changes

  • config-hazard.ts — pure evaluator: fires only in the {AOF on} × {default off, no workaround} cell; handles both RESP2 pair-array and RESP3 record ACL GETUSER shapes; version-gated below 6.0 (pre-ACL); ACL GETUSER denied (NOPERM) → a distinct unverified finding rather than a silent false negative.
  • ConfigHazardService — probes CONFIG GET appendonly → (only if AOF is on) ACL GETUSER default, with a 60s per-connection TTL cache so health polling doesn't hammer the server. Degrades quietly when the config can't be read or the connection is gone.
  • GET /health/detailed now returns configHazards (shared type extended); optional injection so the service degrades to the previous payload when unwired. Deliberately not the capture-session preflight path — a data-loss hazard needs standing visibility, not visibility only when starting a MONITOR capture.
  • MCP get_health includes configHazards in the instance health summary, so agents investigating an instance see the advisory (McpModule now imports HealthModule).
  • Dashboard banner (ConfigHazardBanner) — yellow warning with the exact remediation (user default off +@all ~* &*, or re-enable the user); muted "could not be verified" variant for the NOPERM case; hidden when clean; polls /health/detailed at 30s (server cache absorbs it).

Verification

  • Evaluator: full matrix {AOF on/off} × {default on / off / off+workaround} + partial-workaround (keys restricted) + flag-form grants (allkeys/allchannels/allcommands) + RESP2/RESP3 + NOPERM + version gate — 10 tests.
  • Service: cache hit/expiry/per-connection, AOF-off short-circuit (no ACL probe), CONFIG-read failure, unregistered connection — 8 tests.
  • Health + MCP integration: 7 tests. Web banner states: 4 tests.
  • tsc --noEmit clean on api and web; full api unit suite green except the known pre-existing license.service.spec.ts failure on master.

Notes for review

  • Three clusters of pre-existing lint errors in touched files were deliberately left for a follow-up chore, to keep this diff clean and avoid colliding with the open MCP stack (feat(mcp): AI observability MCP tools #330feat(mcp): vector search MCP tools #332): the CLOUD_MODE token wiring in mcp.module.ts (any + require), Dashboard.tsx's legacy setState-in-effect, and the anomaly any generics in apps/web/src/api/metrics.ts.
  • Open question resolved as: fire on any AOF-enabled config (not only aof-use-rdb-preamble no) — the hazard applies to the AOF tail generally; happy to gate tighter if someone can demonstrate otherwise against a live build.

Checklist

  • Unit / integration tests added
  • Docs added / updated
  • Competitive analysis done / discussed (internal)
  • Blog post about it discussed (internal)

Note

Medium Risk
Adds read-only CONFIG/ACL probes on health paths with graceful degradation; advisory-only but touches health/MCP surfaces operators rely on.

Overview
Adds detection and surfacing of valkey#3983: AOF enabled with the default ACL user disabled (without the +@all ~* &* workaround) can silently lose MULTI/EXEC and function writes on AOF reload.

Backend: New pure evaluator (evaluateAclAofHazard) and ConfigHazardService that probes CONFIG GET appendonly and ACL GETUSER default, with 60s per-connection caching and no cache on failed probes. Optional configHazards on GET /health/detailed and MCP instance/:id/health; HealthModule exports the service and McpModule imports it.

Shared types: ConfigHazardFinding and optional configHazards on DetailedHealthResponse.

Web: ConfigHazardBanner on the dashboard (confirmed vs unverified styling), fed by getDetailedHealth polled every 30s.

Reviewed by Cursor Bugbot for commit 87ab2f5. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread apps/api/src/monitor/config-hazard.ts
Comment thread apps/api/src/monitor/config-hazard.service.ts Outdated
@jamby77
jamby77 requested a review from KIvanow July 24, 2026 07:17

@KIvanow KIvanow left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, after the last bugbot review is merged, it should be good to go

jamby77 added 2 commits July 27, 2026 14:11
…y#3983)

- Pure evaluator + ConfigHazardService with per-connection TTL cache
- Surface configHazards on /health/detailed and MCP instance health
- Dashboard warning banner incl. distinct could-not-verify state
- Full hazard-matrix, NOPERM, version-gate and caching tests
…not clean

Only a positively verified safe config returns no finding — same contract
as the NOPERM path (never a silent false negative)
@jamby77
jamby77 force-pushed the feature/config-hazard-detector branch from 8776b89 to 9e3029e Compare July 27, 2026 11:15
Comment thread apps/api/src/mcp/__tests__/mcp.controller.health.spec.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7956703. Configure here.

user.channels.split(/\s+/).includes('&*') || user.flags.includes('allchannels');

return allCommands && allKeys && allChannels;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Workaround ignores later command denials

Medium Severity

hasUnrestrictedGrant treats +@all as full command access even when the effective rules later deny transaction/function commands (for example -@transaction, -exec, or -multi). That still leaves the AOF reload data-loss path open, but the evaluator returns clean. Nearby grantsMonitor in acl-checker.ts correctly lets explicit denials win over +@all.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7956703. Configure here.

- probe() returns null on hard failure (missing connection, CONFIG GET
  error) instead of an empty finding list
- getHazards() only caches real probe results, so transient startup or
  disconnect failures no longer suppress the advisory for a full TTL
@jamby77
jamby77 merged commit 1f93abc into master Jul 27, 2026
3 checks passed
@jamby77
jamby77 deleted the feature/config-hazard-detector branch July 27, 2026 12:34
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 27, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants