v1.3.36.5 -- Capture: tab-nav escape hatch + DNS-01 selector fix¶
A bugfix on top of v1.3.36.4 closing two issues from the operator's third prod capture run:
- BUG A:
security-whitelist.pngfailed because the Whitelist tab's button text "Whitelist" tripped the read-only blocklist (which exists for the "Whitelist this IP" verb action, NOT for tab navigation). - BUG B:
host-form-dns-provider-dropdown.pngshowed the modal in default state (no DNS-01 selected, no picker) because the DNS-01 selector matched zero elements — the panel's<ChallengeRadio>renders<input>without avalueattribute.
argosVersion and frontend/package.json deliberately stay at 1.3.35.4 (tooling-only). scripts/capture/package.json bumps 1.3.36.4 → 1.3.36.5.
BUG A — Whitelist tab false positive¶
Root cause¶
safeClick's BLOCKED_TEXT_PATTERNS includes /^Whitelist\b/i for the "Whitelist this IP" verb action button. The Whitelist TAB has the same button text — the blocklist couldn't tell them apart and rejected the click:
[safeClick] BLOCKED: selector="button:has-text(\"Whitelist\")"
text="Whitelist" matches a state-changing action.
This is a false positive: tab clicks change view URL / local component state, not server-side state. They're inherently read-only.
Fix¶
New safeClickTab(page, selector, reason) escape hatch in lib/safe-page.js. Same shape as openModal:
- Requires a
reasonstring (audit log). - Skips the
BLOCKED_TEXT_PATTERNScheck. - Logs
[safeClickTab] override: ...to stdout for the audit transcript. - Misuse is obvious at the call site (
safeClickTabname vssafeClick).
Six tab-click call sites in capture.spec.js migrated:
| Test | Tab | Reason |
|---|---|---|
| 13 | Whitelist | switch to Whitelist tab |
| 14 | Activity | switch to Activity tab |
| 15 | Scenarios | switch to Scenarios tab |
| 16 | AppSec | switch to AppSec tab |
| 21 | Metrics (sub-tab) | switch to Metrics sub-tab |
| 22 | Deliveries | switch to Deliveries tab |
Only Whitelist was actively failing; the other 5 happened to use tab labels that didn't collide with blocked verbs. But this is fragile — if the panel later adds a "Disabled", "Reset", "Enable", etc. tab, the same regression would silently reappear. Migrating all 6 future-proofs.
BUG B — DNS-01 selector matched zero elements¶
Root cause¶
PHASE 0 source-code investigation of Hosts.tsx:
function ChallengeRadio({ value, challenge, label, hint, onChange }) {
return (
<label className="...">
<input
type="radio"
name="tls-challenge"
checked={value === challenge}
onChange={() => onChange(challenge)}
className="..."
/>
<span>
<span className="...">{label}</span> {/* "DNS-01" */}
<span className="...">{hint}</span>
</span>
</label>
);
}
The <input> has no value attribute. It only has type, name, checked, onChange, className. The differentiator between the three radios is checked={value === challenge} (controlled input) and the wrapping <label>'s text content ("DNS-01" / "HTTP-01" / "TLS-ALPN-01").
The v1.3.36.x selector 'input[type="radio"][value="dns"]' matched zero elements. The defensive if (await dnsRadio.count()) check made the failure silent: zero elements → no click → modal stayed in default state → capture showed the modal without DNS-01 selected and without the picker rendered. No error was thrown — operator only noticed by inspecting the captured PNG.
Fix¶
Click the <label> element by its visible text. Clicking a <label> that wraps an <input> fires the input's onChange via standard HTML semantics (label-for relationship by nesting):
try {
await safeClick(page, 'label:has-text("DNS-01")');
} catch { /* tolerate if DNS already selected or label missing */ }
await page.waitForTimeout(400); // picker render frame
safeClick (not openModal) is the right wrapper here — it's a form-state change inside an already-open modal, not a new modal-open. The <label>'s visible text contains "DNS-01" + the hint string; neither matches any blocklist pattern.
The 400 ms waitForTimeout covers the React render tick + a single-frame paint for the DNSProviderPicker (per Hosts.tsx:553-559, the picker renders synchronously when tls_challenge==='dns'; one of three shapes — multi-provider <select>, singleton "Using
Smoke phases 12 + 13¶
scripts/smoke/capture-automation.sh gains 11 new asserts across two phases:
12. safeClickTab helper + tab-click migrations:
- safeClickTab() helper defined
- safeClickTab() requires reason argument
- safeClickTab() exported from lib/safe-page.js
- capture.spec.js imports safeClickTab
- >= 6 tab-click call sites use safeClickTab
- Whitelist tab specifically wired through safeClickTab
(regression-guard for the v1.3.36.4 failure)
13. DNS-01 selector fix:
- Active code uses 'label:has-text("DNS-01")'
- Old broken input[value="dns"] selector removed from
active code (comment lines documenting the failure
mode are allowed)
- Synthetic verify against Hosts.tsx: ChallengeRadio
still renders <label> with {label} prop visible. If
the component is refactored away from this shape,
phase 13 fails loudly so the regression is caught at
smoke time, not at capture time.
The synthetic verify uses grep -A 30 to scan ChallengeRadio (the JSX is ~23 lines after the function declaration); this window was bumped from -A 20 mid-impl after the smoke flagged a false negative on the {label} line being just outside the window.
Smoke result post-fix¶
phase 1: run.sh refuses without .env... PASS
phase 2: .env is git check-ignore'd... PASS
phase 3: .env.example placeholders only... PASS
phase 4: safeClick synthetic test... PASS (13/13)
phase 5: working tree unchanged by smoke... PASS
phase 6: storageState wiring (v1.3.36.1)... PASS (5/5)
phase 7: banner output uses fs.readFileSync... PASS
phase 8: viewport 1440x1080 + shotFullScroll... PASS
phase 9: waitForSettled helper (timing fix)... PASS (5/5)
phase 10: openModal modal-visibility wait + TG selector... PASS (6/6)
phase 11: host-row triggers click button[aria-label=edit]... PASS (2/2)
phase 12: safeClickTab helper + tab-click migrations... PASS (7/7)
phase 13: DNS-01 selector fix... PASS (3/3)
scripts/check-no-personal-data.sh clean. mkdocs build --strict clean.
Files changed¶
scripts/capture/lib/safe-page.js— newsafeClickTabhelper with requiredreasonaudit string.scripts/capture/capture.spec.js— importsafeClickTab; migrate 6 tab clicks; replace broken DNS-01 radio click withlabel:has-text("DNS-01"); comments document both v1.3.36.4 failure modes.scripts/capture/package.json—1.3.36.4→1.3.36.5.scripts/smoke/capture-automation.sh— phases 12 + 13 added.docs/release-notes/v1.3.36.5.md— this file.CHANGELOG.md,mkdocs.yml.
NOT changed: argosVersion stays at 1.3.35.4, frontend/package.json version stays at 1.3.35.4. No Go code; no frontend code; no panel binary change.
Operator workflow post-fix¶
cd ~/argos-edge && git pull
scripts/capture/run.sh
# Verify post-fix:
# 1. security-whitelist.png shows the Whitelist tab content
# (table of whitelist entries), NOT a [safeClick BLOCKED]
# error.
# 2. security-activity.png + security-scenarios.png +
# appsec-status.png + appsec-metrics.png +
# notifications-deliveries.png — all unchanged from
# v1.3.36.4 functional behaviour, just routed through
# safeClickTab now.
# 3. host-form-dns-provider-dropdown.png shows the host
# edit modal with DNS-01 radio SELECTED and the
# DNSProviderPicker rendered below the radios (one of
# three shapes per provider count: select / singleton /
# amber-warning).
# 4. No regression on other surfaces.
Versioning¶
scripts/capture/package.json 1.3.36.4 → 1.3.36.5. Tag-without-rebuild precedent for tooling-only patches: v1.3.27.1, v1.3.34, v1.3.35.1, v1.3.35.5.