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

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:
2026-05-10 23:53:17 -04:00
parent 0f2506d5a4
commit ca41486c45
2 changed files with 14 additions and 5 deletions
+8 -2
View File
@@ -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)