Skip to content

Make browser automation work on real ATS forms (upload, custom selects, stable refs) - #113

Merged
ralyodio merged 1 commit into
mainfrom
worktree-mcp-upload
Sep 24, 2026
Merged

ralyodio merged 1 commit into
mainfrom
worktree-mcp-upload

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Found by driving tron automate (MCP) through real job applications on Greenhouse and Ashby. With this branch the same MCP session filled and submitted Greenhouse forms end to end: resume upload, react-select dropdowns, location autocomplete, checkbox groups, and the emailed security-code resubmit. Before it, none of those forms could be completed.

Fixes

Kink Fix
No way to attach a file browser_upload (MCP), tron upload <ref> <file...>, Page.upload, via DOM.setFileInputFiles; snapshots list file inputs as role file even when visually hidden
browser_select only handled native <select> Custom comboboxes too: trusted CDP mouse click + Input.insertText, then click the matching [role=option]; options and comboboxes are now collectable
browser_press sent untrusted KeyboardEvents Input.dispatchKeyEvent; adds tron press
Every snapshot renumbered refs, so after a dropdown opened the next fill hit the wrong field Stable per-element refs; new elements get new numbers; a vanished element's ref is never reused (STALE_REF)
scrollIntoView under scroll-behavior: smooth measured mid-animation, so clicks landed off-screen behavior: 'instant'
extract text used textContent, so script source used up the 20k cap and "Thanks for applying" was never seen on Ashby Rendered text (innerText, script/style-stripped fallback)
Required fields invisible to agents; fill on a file input threw InvalidStateError required in snapshots ([required] in text); a clear "use upload" error

Verification

  • pnpm -r test exit 0 (browser-core 98, sdk 51 + 1 skipped, new tests for each fix); pnpm -r typecheck and lint clean; sh -n on install.sh and the extracted tron CLI heredoc.
  • Live: Greenhouse applications (Arize, Cresta ×2, You.com) submitted through the worktree build of tron automate over stdio MCP, including the security-code resubmit.

Not in this PR

  • On Ubuntu 24.04 without libatk*/libatspi/libXcomposite/libXdamage/libXRes, the bundled engine cannot start. The installer only warns about AppArmor, not the missing libs. I worked around it with a user-space lib dir + TRONBROWSER_BROWSER wrapper; the installer could detect this.
  • tron upload needs the install.sh deploy (CLI dispatch) plus a tagged release (the automate runtime) to reach users.

🤖 Generated with Claude Code

…s, stable refs)

Dogfooding `tron automate` on Greenhouse and Ashby job applications hit
seven problems; each one broke form filling:

- No way to set a file input. Add browser_upload (MCP), `tron upload`
  (CLI) and Page.upload, via DOM.setFileInputFiles. Snapshots now list
  file inputs as role "file" even when an Attach button hides them.
- browser_select only handled native <select>. It now also drives
  custom comboboxes (react-select, ARIA listboxes) with trusted CDP
  mouse and text input, then clicks the matching [role=option].
  [role=option]/[role=combobox] are collectable in snapshots.
- browser_press dispatched untrusted KeyboardEvents that widgets ignore.
  It now uses Input.dispatchKeyEvent. Adds `tron press`.
- Every snapshot renumbered all refs, and every mutating tool takes one,
  so opening a dropdown shifted later refs and the next fill hit the
  wrong field. Refs are now stable per element; new elements get new
  numbers and a vanished element's ref is never reused.
- scrollIntoView under scroll-behavior:smooth measured mid-animation, so
  trusted clicks landed off-screen. Scroll instantly.
- extract text used body.textContent: script source filled the 20k cap
  before visible words, hiding "Thanks for applying" on Ashby. Use
  rendered text.
- Snapshots carry `required` (attribute or aria-required), since ATS
  forms mark it outside the label; fill on a file input now says to
  upload instead of throwing InvalidStateError.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

describe('text extraction', () => {
it('returns visible words, not <script>/<style> source that would eat the 20k cap', () => {
document.body.innerHTML = `<script>${'var bundle = 1;'.repeat(3000)}</script><style>.x{color:red}</style><main><h1>Thanks for applying</h1></main>`;
@github-actions

Copy link
Copy Markdown
Contributor

ThreatCrush Security Scan

52 finding(s)

HIGH/CRITICAL: 3 | MEDIUM: 28 | LOW: 21

Severity Rule Location
HIGH py-ssrf-outbound-request apps/desktop/launcher/tron-tor-helper:303
HIGH py-ssrf-outbound-request apps/desktop/launcher/tron-tor-helper:437
HIGH sh-remote-script-execution apps/web/public/install.sh:757
MEDIUM js-open-redirect apps/desktop/extensions/ai-sidebar/install-helper.js:156
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/media.js:34
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/media.js:57
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/newtab.js:237
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/newtab.js:266
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/newtab.js:336
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/options.js:305
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/sidepanel.js:78
MEDIUM js-unescaped-html-sink apps/desktop/extensions/ai-sidebar/sidepanel.js:166
MEDIUM js-unescaped-html-sink apps/extensions/public/store.js:77
MEDIUM js-unescaped-html-sink apps/extensions/public/store.js:227
MEDIUM js-unescaped-html-sink apps/extensions/public/store.js:569
MEDIUM js-unescaped-html-sink apps/web/public/app.js:29
MEDIUM js-unescaped-html-sink apps/web/public/dns.js:54
MEDIUM sh-remote-script-execution apps/web/public/install.sh:164
MEDIUM sh-remote-script-execution apps/web/public/install.sh:169
MEDIUM sh-remote-script-execution apps/web/public/install.sh:282
MEDIUM sh-remote-script-execution apps/web/public/install.sh:300
MEDIUM sh-remote-script-execution apps/web/public/install.sh:813
MEDIUM sh-remote-script-execution apps/web/public/install.sh:864
MEDIUM js-unescaped-html-sink apps/web/public/settings.js:96
MEDIUM js-unescaped-html-sink apps/web/public/settings.js:168
MEDIUM js-dynamic-code-execution packages/sdk/src/mcp/tools.ts:125
MEDIUM js-dynamic-code-execution packages/sdk/src/page.ts:68
MEDIUM js-dynamic-code-execution packages/sdk/src/page.ts:73
MEDIUM sql-template-interpolation services/api/src/store/db.ts:116
MEDIUM js-dynamic-code-execution services/api/src/store/scanner.ts:44
MEDIUM sh-predictable-temp-path start.sh:25
LOW js-dynamic-code-execution apps/desktop/extensions/ai-sidebar/pit-proxy.test.js:8
LOW js-dynamic-code-execution packages/agent-runtime/src/analyze/form-script.test.ts:6
LOW js-unescaped-html-sink packages/agent-runtime/src/analyze/form-script.test.ts:26
LOW js-unescaped-html-sink packages/agent-runtime/src/analyze/form-script.test.ts:49
LOW js-dynamic-code-execution packages/browser-core/src/automation/extract-script.test.ts:6
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:16
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:45
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:57
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:80
LOW js-unescaped-html-sink packages/browser-core/src/automation/extract-script.test.ts:99
LOW js-dynamic-code-execution packages/browser-core/src/automation/snapshot-script.test.ts:11
LOW js-unescaped-html-sink packages/browser-core/src/automation/snapshot-script.test.ts:62
LOW js-unescaped-html-sink packages/browser-core/src/automation/snapshot-script.test.ts:101
LOW js-unescaped-html-sink packages/provenance/src/scan.test.ts:47
LOW js-unescaped-html-sink packages/provenance/src/scan.test.ts:170
LOW js-unescaped-html-sink packages/provenance/src/scan.test.ts:216
LOW js-dynamic-code-execution packages/sdk/src/mcp/automate.test.ts:261
LOW secret-generic-api-key packages/storage/src/config.ts:51
LOW secret-generic-credential packages/storage/src/config.ts:51

…and 2 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio marked this pull request as ready for review September 24, 2026 07:05
@ralyodio
ralyodio merged commit c8fb9a3 into main Sep 24, 2026
7 checks passed
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.

2 participants