Skip to content

fix: accessible GitHub auth dialog + argv-form exec at three sites - #288

Open
NovakPAai wants to merge 1 commit into
mainfrom
claude/novak-backlog-modal-exec
Open

fix: accessible GitHub auth dialog + argv-form exec at three sites#288
NovakPAai wants to merge 1 commit into
mainfrom
claude/novak-backlog-modal-exec

Conversation

@NovakPAai

Copy link
Copy Markdown
Collaborator

Summary

Clears the last two items from the UX/server audit backlog.

GitHub device-code modal (leaderboard)

  • Was a bare div — no dialog semantics at all. Now role="dialog" + aria-modal + aria-labelledby, focus moved inside on open, Tab trapped within it, Escape closes, and focus returns to whatever opened it (guarded against that node having been replaced by render()).
  • The status line is aria-live so "Code expired" / "Connection error" is announced rather than silently repainted.
  • The poll loop had a bare catch {}: every network failure was swallowed and the user watched "Waiting for authorization..." until the tries ran out, with nothing ever indicating a problem. It now counts consecutive failures (3 → surface and stop), reports a server-sent error field, and honours slow_down per RFC 8628 §3.5 — matching pollRepoScopeOnce() in app.js, which already did all of this.
  • Cancel is a real handler instead of an inline this.parentElement.parentElement DOM walk.

Shell-string exec → argv form

The codebase already documents argv-form execFileSync as the injection-safe pattern (see the tar call in migrate.js). Three sites didn't follow it:

  • data.js findQwenSessionByPid interpolated a pid into an lsof … shell string — the sibling lsof call in getActiveSessions already used execFileSync, so this was the outlier. stderr is now suppressed via stdio rather than a 2>/dev/null that required a shell.
  • migrate.js built find "$path" … | wc -l from a home path; now argv form with the count done in JS (which also drops the pipe).
  • Removed the adjacent du -sb … || du -sk … call entirely. Its result (totalSize) was computed and never printed — and it was wrong besides: BSD du has no -b, so on macOS it always fell through to -sk and added kilobytes to a byte total. Dead code hiding a unit bug behind a shell ||.

Today's inputs at all three sites are non-attacker-controlled (a pid parsed from ps, a fixed home-relative path list), so this is pattern-consistency hardening rather than a live vulnerability fix.

Test plan

  • node --test "test/**/*.test.js" — 267 passed, 1 skipped (win32-only); 10 new contract tests
  • Modal driven in a real browser: role=dialog/aria-modal/aria-labelledby correct, focus lands on "Open GitHub", Tab cycles link→cancel→link without escaping, Escape closes, focus returns to the opener
  • lsof argv vs shell form confirmed to find the same session id on a process deliberately holding a matching .qwen/.../*.jsonl open (the raw outputs differ only in ephemeral socket fds, which churn between any two invocations)
  • find argv form matches the old | wc -l count on a tree with quotes, $ and backticks in the path
  • codbash export end-to-end: reports 934 files, archive contains exactly 934

Note on merge order

Touches one line that #286 also touches (rel="noopener noreferrer" on the modal's link). The new markup here already includes that attribute, so if #286 lands first the conflict resolves to identical content.

Follow-up worth doing once this queue drains: _installModalFocusTrap in app.js dispatches Escape through a hardcoded if-chain of overlay ids, which is why this dialog handles its own keys instead of reusing it. Generalizing that to a close-callback would let all four dialogs share one implementation.

🤖 Generated with Claude Code

Clears the last two items from the UX/server audit backlog.

GitHub device-code modal (leaderboard):
- Was a bare div with no dialog semantics. Now role="dialog" +
  aria-modal + aria-labelledby, focus moved inside on open, Tab trapped
  within it, Escape closes, and focus returns to whatever opened it
  (guarded against the node having been replaced by render()).
- The status line is aria-live so "Code expired" / "Connection error" is
  announced rather than silently repainted.
- The poll loop had a bare `catch {}`: every network failure was
  swallowed and the user watched "Waiting for authorization..." until
  the tries ran out, with nothing ever indicating a problem. It now
  counts consecutive failures (3 → surface and stop), reports a
  server-sent `error` field, and honours `slow_down` per RFC 8628 §3.5
  — matching pollRepoScopeOnce() in app.js, which already did all this.
- Cancel is a real handler instead of an inline
  this.parentElement.parentElement DOM walk.

Shell-string exec → argv form:
- data.js findQwenSessionByPid interpolated a pid into an `lsof …` shell
  string; the sibling lsof call in getActiveSessions already used
  execFileSync, so this was the outlier. stderr now suppressed via stdio
  rather than a `2>/dev/null` that required a shell.
- migrate.js built `find "$path" … | wc -l` from a home path; now argv
  form with the count done in JS (which also drops the pipe).
- Removed the adjacent `du -sb … || du -sk …` call entirely: its result
  (totalSize) was computed and never printed. It was also wrong — BSD du
  has no -b, so on macOS it always fell through to -sk and added
  KILOBYTES to a byte total. Dead code hiding a unit bug behind a shell
  `||`.

Verified: modal semantics/Tab-trap/Escape/focus-return driven in a real
browser; lsof argv vs shell forms confirmed to find the same session on
a process holding a matching file open; find argv form matches the old
pipe count on paths containing quotes, $ and backticks; `codbash export`
end-to-end reports 934 files against 934 actually in the archive.
@NovakPAai
NovakPAai requested a review from vakovalskii August 3, 2026 04:37
vakovalskii added a commit that referenced this pull request Aug 3, 2026
Conflict in leaderboard.js: #288 replaces the inline GitHub device-code modal
markup with _lbBuildAuthModal() (focus trap + Escape + focus return). Took #288
for that hunk only — resolving the whole file to either side would have dropped
the other PR's work; #286's rel="noopener noreferrer" on the remaining three
target="_blank" links is kept (its test asserts all four).
vakovalskii added a commit that referenced this pull request Aug 3, 2026
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant