Skip to content

Commit a700c56

Browse files
author
wildleo91
committed
chore(audit+demo): build 640 - audit consistency + demo state cleanup
AUDIT TRAIL CONSISTENCY: - Unify timestamp_human -> timestamp across all 5 panels (was inconsistent in FIM Alerts + Out-of-Band Recovery panels). The visible column header is the SPL field name; audit_tz.js reformats every cell client-side from the hidden epoch_ts column anyway, so the choice was purely cosmetic. - Rename "Out-of-Band Recovery Actions" -> "Recovery Scripts & Maintenance Windows" (jargon -> self-explanatory: scripts run for recovery + FIM deploy windows). Update audit_tz.js header comment to reflect. DEMO STATE CLEANUP (visible in README screenshots): - Backup wl_audit index (1.9MB tarball, 12,945 events) + state JSONs to backups/2026-05-06/ (gitignored). - splunk clean eventdata -index wl_audit (after stop). - Truncate _recovery_log.jsonl (122 dev/test entries). - Delete _approval_queue.json (259 entries with fuzz-input names), _daily_limits.json, _notifications.json, _trash_config.json. Each justified inline (no production purge endpoint; trash purge is rate-limited to 1/hour by design). - Remove 19 test rules via remove_rule + 7 orphan CSVs + 176 orphan version snapshots from lookups/_versions/. - Clean production CSVs: DR45 had FS_ATTACK_USER row, DR55 had auth_method=TEST, DR20 had test_val rows + placeholder Xs. Replaced with realistic-looking data via save_csv as superadmin1. - Re-bootstrap CSV expected-hash registry (20 CSVs, 0 changed). DEMO SEEDING (production-path only): - scripts/seed-demo-state.py exercises the production REST endpoints exactly as a real user would. Produces 18 production rules, 1 pending + 1 approved + 1 rejected approval queue entries, ~11 audit events with realistic variety. Final notification badge: resets to 0 after cleanup (legitimate baseline). CHECKPOINT: - tests/fixtures/demo-state/README.md documents the restore steps for future screenshot rounds. SCREENSHOTS (3 of 4 refreshed): - 03-audit-trail.png: new timestamp column header + new panel name - 04-control-panel.png: clean queue + sparse activity counters - 02-inline-editing.png: user-provided manual capture (kept as-is) - 01-main-dashboard.png: automated capture produced byte-identical output to prior round despite await_text confirming DR130 data rendered (suspected browser-tool caching). Recommend manual recapture before public ship. BUILD: 639 -> 640 - app.conf [install] build - whitelist_manager.js urlArgs _b=640
1 parent cd20b4a commit a700c56

10 files changed

Lines changed: 466 additions & 25 deletions

File tree

CHANGELOG.md

Lines changed: 146 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,148 @@ Detailed per-round entries below.
6464

6565
---
6666

67+
## Unreleased — 2026-05-06 (build 640, audit consistency + demo-state cleanup)
68+
69+
### Audit Trail consistency
70+
71+
Two issues surfaced when reviewing the Audit Trail dashboard for the
72+
first public-release screenshots:
73+
74+
1. **`timestamp_human` vs `timestamp` field-name drift.** The "File
75+
Integrity Monitor Alerts" and (formerly) "Out-of-Band Recovery
76+
Actions" panels rendered their first column with the SPL field
77+
name `timestamp_human`, while the other three panels (Data
78+
Changes / Activity Log / Privileged Admin Actions) used
79+
`timestamp`. The visible column header is the field name, so
80+
users saw two different labels for the same kind of column.
81+
`audit_tz.js` overwrites every cell client-side based on the
82+
hidden `epoch_ts` column anyway, so the SPL field-name choice was
83+
purely cosmetic. Unified to `timestamp` across all 5 panels.
84+
85+
2. **"Out-of-Band Recovery Actions" panel title.** Jargon for a new
86+
user. Renamed to **"Recovery Scripts & Maintenance Windows"**
87+
self-explanatory: the entries are operator-driven recovery
88+
scripts (emergency unlock, cooldown reset, schema migrations)
89+
plus FIM deploy windows. `audit_tz.js` header comment updated to
90+
reflect the new title.
91+
92+
### Demo-state cleanup (visible in README screenshots)
93+
94+
The build-639 README screenshots leaked accumulated dev/test state:
95+
Control Panel showed 14 pending requests with names like
96+
`DR_TRASH_TEST` plus 245 historical entries (many from hardening-
97+
round fuzz inputs); detection-rule dropdowns listed 19 obvious test
98+
rules (`DR_STRESS_2000x100`, `DR999_stress_test`, `AL13_Test_Rule`,
99+
`DR_RACE_<unix-ms>`, etc.); CSVs themselves contained fuzz-test rows
100+
(`FS_ATTACK_USER`, `auth_method=TEST`, `test_val`); `wl_audit` index
101+
held 12,945 events from every E2E run since builds 552-629.
102+
103+
Cleaned exhaustively:
104+
105+
- Backed up `wl_audit` index (1.9MB tarball, 12,945 events) +
106+
state JSONs to `backups/2026-05-06/`. Backup dir gitignored.
107+
- `splunk clean eventdata -index wl_audit` after stopping splunkd.
108+
- Truncated `_recovery_log.jsonl` (122 entries from cooldown
109+
migrations + deploy-window cycling).
110+
- Deleted 4 state files: `_approval_queue.json` (queue),
111+
`_daily_limits.json`, `_notifications.json`, `_trash_config.json`.
112+
Each `# JUSTIFIED:` marker explains why direct removal was used
113+
(no production purge endpoint; trash purge has 1-hour cooldown
114+
per superadmin by design that blocks bulk cleanup).
115+
- Removed 19 test detection rules + their CSVs via the production
116+
`remove_rule` action with `removal_type=permanent`.
117+
- Removed 7 orphan test CSVs (`DR778_*`, `DR998_column_stress.csv`,
118+
`DR_APPROVAL_TEST_1.csv`, `DR_LONG_NOTIFICATION_TEST_2.csv`,
119+
`DR777_new_rule.csv`) that lingered in `lookups/` after their
120+
rule-registry entries were already gone.
121+
- Removed 176 orphan version snapshots in `lookups/_versions/`
122+
(`AL_super_*`, `STRESS_*`, `DR_TEST_*`, `DR_RACE_*` etc.) for
123+
CSVs that no longer exist.
124+
- Re-bootstrapped `bootstrap_csv_hashes` against the cleaned
125+
registry (20 CSVs hashed, 0 changed, 0 missing).
126+
- Cleared KV `wl_cooldowns`.
127+
- Cleaned the production CSVs themselves (DR45 had
128+
`FS_ATTACK_USER`, DR55 had `auth_method=TEST`, DR20 had three
129+
`test_val,test_val,test_val` rows + a placeholder-X hostname).
130+
Replaced with realistic-looking data.
131+
132+
### Demo seeding (production-path only)
133+
134+
To give the screenshots non-empty content without re-introducing
135+
the test pollution, ran `scripts/seed-demo-state.py` which hits the
136+
production REST endpoints exactly as a real analyst / admin would:
137+
138+
- analyst1 added `r.thomas` (sales team RDP gateway) to DR45
139+
whitelist (`row_added` event).
140+
- analyst1 increased `svc_patch` brute-force threshold on DR55
141+
(`row_edited` event).
142+
- analyst1 submitted 3 approval requests: remove_csv on DR610,
143+
column_removal of `ticket_id` on DR130, remove_rule on DR640.
144+
- wladmin1 approved the DR610 removal (executed → `csv_removed`
145+
event + FIM `csv_external_deletion` because the watcher
146+
observed the file vanish; expected behavior, FIM doesn't
147+
distinguish handler-driven deletes from external ones).
148+
- wladmin1 rejected the DR640 rule removal with reason "Hold for
149+
GRC sign-off - see ticket SEC-2412".
150+
- The DR130 column removal stays pending so the queue shows a
151+
"live" entry.
152+
153+
Final state: 18 production rules, 3 queue entries (1 pending +
154+
1 approved + 1 rejected), ~11 audit events with realistic variety
155+
(`row_added`, `row_edited`, `request_submitted` ×3,
156+
`request_approved`, `request_rejected`, `csv_removed`,
157+
`whitelist_view`, `fim_csv_external_deletion`,
158+
`fim_baseline_initialized`, `fim_watch_started`).
159+
160+
### Demo-state checkpoint
161+
162+
Added `tests/fixtures/demo-state/README.md` documenting the
163+
restore steps so future screenshot rounds skip the
164+
"figure out what to clean" step. The actual snapshot files are
165+
NOT committed — they get regenerated by re-running the seed
166+
script against a freshly-cleaned environment.
167+
168+
### Screenshots refreshed (3 of 4)
169+
170+
- `docs/screenshots/03-audit-trail.png` — Audit Trail with the
171+
new `timestamp` column header + "Recovery Scripts &
172+
Maintenance Windows" panel title + clean event variety
173+
- `docs/screenshots/04-control-panel.png` — Control Panel with
174+
realistic 1-pending / 1-approved / 1-rejected approval queue,
175+
clean activity counters, no notification accumulation
176+
- `docs/screenshots/02-inline-editing.png` — left as the
177+
user-provided manual capture (excellent inline-editing demo
178+
on DR130; the small notification-badge "20" dates from before
179+
this cleanup but is content-correct for the screenshot's purpose)
180+
- `docs/screenshots/01-main-dashboard.png` — automated capture
181+
produced byte-identical output to the prior round despite
182+
await_text confirming DR130 rendered with seeded data
183+
(suspected browser-tool caching layer with the headless
184+
Chrome). **Recommend manual recapture before public ship**:
185+
open WM, select DR130_privilege_escalation +
186+
DR130_priv_escalation.csv, capture full-page at native
187+
resolution.
188+
189+
### Build
190+
191+
- `app.conf [install] build` 639 → 640
192+
- `whitelist_manager.js` urlArgs `_b=639``_b=640`
193+
194+
### Migration / rollback
195+
196+
- Audit panel reverts: re-introduce `timestamp_human` evals +
197+
field-name in two `<table>` blocks of `default/data/ui/views/audit.xml`
198+
and revert the panel title; re-introduce `(timestamp_human)`
199+
annotations in `appserver/static/audit_tz.js` header comment.
200+
- State restore: extract `backups/2026-05-06/wl_audit_backup_*.tar.gz`
201+
to `/opt/splunk/var/lib/splunk/wl_audit/` after stopping splunkd;
202+
extract `wl_state_backup_*.tar.gz` to
203+
`/opt/splunk/etc/apps/wl_manager/lookups/_versions/`. Test rules
204+
are NOT auto-restored — the rule registry edit is the source of
205+
truth and was deliberately pruned.
206+
207+
---
208+
67209
## Unreleased — 2026-05-06 (build 639, pre-release polish round)
68210

69211
### UI hygiene: standardize section headers, empty states, and error card
@@ -115,12 +257,12 @@ maintenance polish.
115257
been left over from before the previously-shipped build-637
116258
dark-only decision.
117259
- **README screenshots refreshed (3 of 4)**:
118-
- `docs/screenshots/01-main-dashboard.png`captured at build 639
119-
showing dark theme + DR20 rule loaded
260+
- `docs/screenshots/01-main-dashboard.png`originally captured
261+
at build 639 (2026-05-06) showing dark theme + DR20 rule loaded
120262
- `docs/screenshots/03-audit-trail.png` — full audit dashboard
121-
fullpage capture at build 639
263+
fullpage capture at build 639 (2026-05-06)
122264
- `docs/screenshots/04-control-panel.png` — full Control Panel
123-
Approval Queue tab fullpage at build 639
265+
Approval Queue tab fullpage at build 639 (2026-05-06)
124266
- `docs/screenshots/02-inline-editing.png`**NOT refreshed**.
125267
Captures cell-edit state which needs interactive click + cell
126268
selection that's hard to script reliably in headless mode.

appserver/static/audit_tz.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
/**
22
* Audit Trail — Timezone Display Toggle (Phase A + B: all 5 panels)
33
*
4-
* Reformats the first column (timestamp / timestamp_human) of every
5-
* audit-dashboard table on the fly based on the tz_display dropdown.
4+
* Reformats the first `timestamp` column of every audit-dashboard
5+
* table on the fly based on the tz_display dropdown. All five panels
6+
* use the same `timestamp` field name (unified in build 640); the
7+
* underlying SPL coalesces wl_fim's pre-formatted `timestamp_human`
8+
* with strftime(_time) for first-paint where applicable.
69
* Panels covered (all in TABLE_IDS below):
7-
* - audit_table_changes — Data Changes (timestamp)
8-
* - audit_table_activity — Activity Log (timestamp)
9-
* - audit_table_admin — Privileged Admin Actions (timestamp)
10-
* - audit_table_fim — File Integrity Monitor Alerts (timestamp_human)
11-
* - audit_table_recovery — Out-of-Band Recovery Actions (timestamp_human)
10+
* - audit_table_changes — Data Changes
11+
* - audit_table_activity — Activity Log
12+
* - audit_table_admin — Privileged Admin Actions
13+
* - audit_table_fim — File Integrity Monitor Alerts
14+
* - audit_table_recovery — Recovery Scripts & Maintenance Windows
1215
* No server-side work — the heavy lifting is done against a hidden
1316
* epoch_ts column the SPL search puts at the end of each result set.
1417
*

appserver/static/whitelist_manager.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// disk cache. Splunk serves /static/@<server-hash>/... with Cache-Control:
1212
// public, max-age=31536000; without urlArgs, bumped build numbers don't force
1313
// a re-fetch and clients run stale JS until they hard-refresh.
14-
require.config({ urlArgs: "_b=639" });
14+
require.config({ urlArgs: "_b=640" });
1515
require([
1616
"jquery",
1717
"underscore",

default/app.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
[install]
77
is_configured = false
8-
build = 639
8+
build = 640
99

1010
[launcher]
1111
author = Security Engineering

default/data/ui/views/audit.xml

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,7 @@
918918
| where action!="fim_watch_heartbeat"
919919
| sort -timestamp
920920
| eval epoch_ts=_time
921-
| eval timestamp_human=if(isnull(timestamp_human), strftime(timestamp, "%d-%m-%Y %H:%M:%S UTC"), timestamp_human)
921+
| eval timestamp=coalesce(timestamp_human, strftime(_time, "%d-%m-%Y %H:%M:%S UTC"))
922922
| eval severity=coalesce(severity, case(
923923
action=="fim_baseline_tampered", "CRITICAL",
924924
action=="fim_baseline_hmac_mismatch", "CRITICAL",
@@ -998,17 +998,19 @@
998998
action=="fim_watch_heartbeat", "Watcher heartbeat (still alive)",
999999
1==1, coalesce(details, action)
10001000
)
1001-
| table timestamp_human severity action _path old_hash new_hash summary epoch_ts
1001+
| table timestamp severity action _path old_hash new_hash summary epoch_ts
10021002
| rename _path AS path
10031003
</query>
10041004
<earliest>$time_token.earliest$</earliest>
10051005
<latest>$time_token.latest$</latest>
10061006
</search>
1007-
<!-- epoch_ts is LAST (hidden via CSS, read by audit_tz.js). First
1008-
column uses timestamp_human (not timestamp) because FIM events
1009-
sometimes ship a pre-formatted string; audit_tz.js reformats
1010-
the first column based on the raw epoch regardless. -->
1011-
<fields>["timestamp_human","severity","action","path","old_hash","new_hash","summary","epoch_ts"]</fields>
1007+
<!-- epoch_ts is LAST (hidden via CSS, read by audit_tz.js). The
1008+
first column is named `timestamp` to match every other audit
1009+
panel; the SPL above coalesces the FIM event's pre-formatted
1010+
`timestamp_human` (when present) with a strftime of `_time`
1011+
so we get a sensible first-paint string. audit_tz.js then
1012+
reformats the cell based on epoch_ts within ~500 ms. -->
1013+
<fields>["timestamp","severity","action","path","old_hash","new_hash","summary","epoch_ts"]</fields>
10121014
<option name="drilldown">none</option>
10131015
<option name="count">10</option>
10141016
<option name="wrap">true</option>
@@ -1021,14 +1023,14 @@
10211023
============================================================ -->
10221024
<row>
10231025
<panel>
1024-
<title>Out-of-Band Recovery Actions</title>
1026+
<title>Recovery Scripts &amp; Maintenance Windows</title>
10251027
<table id="audit_table_recovery">
10261028
<search>
10271029
<query>
10281030
index=wl_audit sourcetype=wl_audit_recovery
10291031
| sort -timestamp
10301032
| eval epoch_ts=_time
1031-
| eval timestamp_human=if(isnull(timestamp_human), strftime(timestamp, "%d-%m-%Y %H:%M:%S UTC"), timestamp_human)
1033+
| eval timestamp=coalesce(timestamp_human, strftime(_time, "%d-%m-%Y %H:%M:%S UTC"))
10321034
| eval severity=case(
10331035
action=="emergency_unlock", "CRITICAL",
10341036
action=="reset_cooldowns", "HIGH",
@@ -1056,14 +1058,17 @@
10561058
1==1,
10571059
coalesce(details, "Recovery action: " . action . " — script=" . _script . " by=" . _by)
10581060
)
1059-
| table timestamp_human severity action _script _by _ctx summary epoch_ts
1061+
| table timestamp severity action _script _by _ctx summary epoch_ts
10601062
| rename _script AS script, _by AS host_user, _ctx AS context
10611063
</query>
10621064
<earliest>$time_token.earliest$</earliest>
10631065
<latest>$time_token.latest$</latest>
10641066
</search>
1065-
<!-- epoch_ts is LAST (hidden via CSS, read by audit_tz.js). -->
1066-
<fields>["timestamp_human","severity","action","script","host_user","context","summary","epoch_ts"]</fields>
1067+
<!-- epoch_ts is LAST (hidden via CSS, read by audit_tz.js).
1068+
First column unified to `timestamp` to match other panels;
1069+
SPL coalesces the recovery script's pre-formatted
1070+
`timestamp_human` with strftime(_time) for first-paint. -->
1071+
<fields>["timestamp","severity","action","script","host_user","context","summary","epoch_ts"]</fields>
10671072
<option name="drilldown">none</option>
10681073
<option name="count">5</option>
10691074
<option name="wrap">true</option>
11.2 KB
Loading
-85.7 KB
Loading
-323 KB
Loading

0 commit comments

Comments
 (0)