Files
vulncheck/alembic/versions/044_asset_grace_window_six_weeks.py
T
vulncheck 0845fbe5db fix(wazuh): give offline hosts a six-week grace window before their CVEs vanish
The asset sync fetched active agents only, and the sync-driven reconcile
inactivates every agent id the sync does not report. Since INACTIVE assets are
hidden from every CVE view, a host that was merely switched off lost all its
findings at the next sync — while Wazuh itself kept listing the agent and its
vulnerabilities. Holiday, sick leave or a spare laptop in a drawer looked
exactly like a decommissioned machine.

Root cause confirmed: not the 30-day soft-inactive window (which never got a
chance to apply here), but the connection-state filter feeding seen_ids.

- sync fetches ALL agents, paginated; disconnected ones stay in seen_ids
- last_seen now carries Wazuh's lastKeepAlive instead of "when we synced",
  so there is a real stamp to measure against
- per-agent status comes from status_for_last_seen(): ACTIVE inside the
  window, INACTIVE outside — replaces the binary connected/disconnected test
- reconcile_asset_lifecycle uses max(last_scan, last_seen): a host that keeps
  checking in but cannot be scanned while offline no longer ages out early
- window default 30 -> 42 days, one setting for both paths; migration 044
  rewrites only rows still holding the old default
- agents registered but never connected are skipped instead of imported

tests/test_asset_grace_window.py covers the window edges, UTC keepalive
parsing, the newest-stamp reconcile, and guards the sync against a
status filter being reintroduced.
2026-08-17 11:07:30 +02:00

37 lines
1.0 KiB
Python

"""Widen the asset inactivity window from 30 to 42 days (six weeks)
Revision ID: 044
Revises: 043
Create Date: 2026-08-17 10:00:00.000000
The window now governs the Wazuh sync too: a disconnected agent keeps its
findings visible until the window closes, instead of losing them at the next
sync. Four weeks is too tight for a three-week absence plus a weekend, so the
default moves to six.
Only rows still holding the old default '30' are rewritten — an operator who
deliberately set another value keeps it. Assets whose stored settings row does
not exist at all pick up the new code default (asset_lifecycle._DEFAULT_DAYS).
"""
from alembic import op
revision = "044"
down_revision = "043"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.execute(
"UPDATE settings SET value = '42' "
"WHERE key = 'asset_inactive_after_days' AND trim(value) = '30'"
)
def downgrade() -> None:
op.execute(
"UPDATE settings SET value = '30' "
"WHERE key = 'asset_inactive_after_days' AND trim(value) = '42'"
)