security+a11y: job ownership check, aria-live chips, aria-hidden topo
Lint / Python (flake8) (push) Failing after 45s
Lint / JS (eslint) (push) Successful in 8s
Security / Python Security (bandit) (push) Successful in 1m5s
Test / Python Tests (pytest) (push) Successful in 49s
Lint / Notify on failure (push) Successful in 3s
Lint / Deploy (push) Has been skipped
Lint / Python (flake8) (push) Failing after 45s
Lint / JS (eslint) (push) Successful in 8s
Security / Python Security (bandit) (push) Successful in 1m5s
Test / Python Tests (pytest) (push) Successful in 49s
Lint / Notify on failure (push) Successful in 3s
Lint / Deploy (push) Has been skipped
security: - Fix bare open(sentinel, 'w').close() file descriptor leak; use context manager instead - Store requesting username in _diag_jobs at creation time; return 403 from api_diagnose_poll if the polling user does not match the job owner accessibility: - Add aria-live="polite" aria-atomic="true" to .status-chips container so screen readers announce critical/warning count changes on refresh - Add aria-controls="events-table-wrap" to critical and warning stat cards so assistive tech knows these buttons control the events table - Add aria-hidden sync to topology setCollapsed() — hidden topology content is now removed from the accessibility tree when collapsed, preventing keyboard focus from entering invisible elements Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -436,8 +436,12 @@ def api_diagnose_start():
|
||||
return jsonify({'error': 'Resolved interface name contains invalid characters'}), 400
|
||||
|
||||
job_id = str(uuid.uuid4())
|
||||
requesting_user = _get_user()['username']
|
||||
with _diag_lock:
|
||||
_diag_jobs[job_id] = {'status': 'running', 'result': None, 'created_at': time.time()}
|
||||
_diag_jobs[job_id] = {
|
||||
'status': 'running', 'result': None,
|
||||
'created_at': time.time(), 'user': requesting_user,
|
||||
}
|
||||
|
||||
def _run():
|
||||
try:
|
||||
@@ -467,6 +471,8 @@ def api_diagnose_poll(job_id: str):
|
||||
job = _diag_jobs.get(job_id)
|
||||
if not job:
|
||||
return jsonify({'error': 'Job not found'}), 404
|
||||
if job.get('user') != _get_user()['username']:
|
||||
return jsonify({'error': 'Forbidden'}), 403
|
||||
return jsonify({'status': job['status'], 'result': job.get('result')})
|
||||
|
||||
|
||||
@@ -524,7 +530,7 @@ def api_avatar():
|
||||
return '', 404
|
||||
|
||||
if not avatar_data or len(avatar_data) < 100:
|
||||
open(sentinel, 'w').close()
|
||||
with open(sentinel, 'w'): pass
|
||||
return '', 404
|
||||
|
||||
# Validate JPEG magic bytes (FF D8 FF)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
<!-- ── Status bar ──────────────────────────────────────────────────── -->
|
||||
<div class="status-bar">
|
||||
<div class="status-chips">
|
||||
<div class="status-chips" id="status-chips" aria-live="polite" aria-atomic="true">
|
||||
{% if not daemon_ok %}
|
||||
<span class="chip chip-critical">⚠ MONITOR OFFLINE</span>
|
||||
{% endif %}
|
||||
@@ -30,7 +30,8 @@
|
||||
<div class="lt-stats-grid">
|
||||
<div class="lt-stat-card{% if summary.critical %} lt-stat-card--alert{% endif %}"
|
||||
id="stat-critical" role="button" tabindex="0"
|
||||
data-stat-filter="critical" aria-label="{{ summary.critical or 0 }} critical alerts">
|
||||
data-stat-filter="critical" aria-label="{{ summary.critical or 0 }} critical alerts"
|
||||
aria-controls="events-table-wrap">
|
||||
<span class="lt-stat-icon lt-text-red" aria-hidden="true">●</span>
|
||||
<div class="lt-stat-info">
|
||||
<span class="lt-stat-value lt-text-red" id="stat-critical-val">{{ summary.critical or 0 }}</span>
|
||||
@@ -39,7 +40,8 @@
|
||||
</div>
|
||||
<div class="lt-stat-card"
|
||||
id="stat-warning" role="button" tabindex="0"
|
||||
data-stat-filter="warning" aria-label="{{ summary.warning or 0 }} warning alerts">
|
||||
data-stat-filter="warning" aria-label="{{ summary.warning or 0 }} warning alerts"
|
||||
aria-controls="events-table-wrap">
|
||||
<span class="lt-stat-icon lt-text-amber" aria-hidden="true">●</span>
|
||||
<div class="lt-stat-info">
|
||||
<span class="lt-stat-value lt-text-amber" id="stat-warning-val">{{ summary.warning or 0 }}</span>
|
||||
@@ -484,6 +486,7 @@
|
||||
|
||||
function setCollapsed(v) {
|
||||
wrap.classList.toggle('is-collapsed', v);
|
||||
wrap.setAttribute('aria-hidden', v ? 'true' : 'false');
|
||||
btn.setAttribute('aria-expanded', v ? 'false' : 'true');
|
||||
btn.textContent = v ? '▾ Expand' : '▴ Collapse';
|
||||
try { localStorage.setItem(LS_KEY, v ? '1' : '0'); } catch(_) {}
|
||||
|
||||
Reference in New Issue
Block a user