DEV Community

Zijian Xiao
Zijian Xiao

Posted on AI-assisted

Uploading verified public images in a remote browser without file:// access

When browser automation runs through a browser extension on another machine, a server path such as /srv/assets/logo.png is not a path the browser can read. Treating it as a file:// URL also requires a broad extension permission that is unnecessary for a normal website upload.

I ran into this while preparing public screenshots for TeamCerts, a training-record and certificate-expiry tracker I’m building. The safe approach was to keep the reviewed public asset on the server, verify it there, and send only its bytes to the already-authorized page.

The pattern

  1. Restrict files to one reviewed cache directory.
  2. Require a PNG signature, a size limit, and a filename containing the expected SHA-256.
  3. Pin the exact HTTPS page URL before touching the DOM.
  4. Require exactly one intended file input.
  5. Decode the bytes in the page, hash them again, create a File, and assign a DataTransfer.files list.
  6. Dispatch input and change, then read the site’s own preview back. Never treat assignment alone as a successful upload or publication.
async function attachVerifiedPng({ expectedUrl, selector, base64, name, size, sha256 }) {
  if (location.href !== expectedUrl) throw new Error('page context changed');

  const inputs = [...document.querySelectorAll(selector)];
  if (inputs.length !== 1) throw new Error('file input is not unique');

  const input = inputs[0];
  if (!(input instanceof HTMLInputElement) || input.type !== 'file' || input.disabled) {
    throw new Error('not an enabled file input');
  }

  const raw = atob(base64);
  const bytes = Uint8Array.from(raw, ch => ch.charCodeAt(0));
  const digest = [...new Uint8Array(await crypto.subtle.digest('SHA-256', bytes))]
    .map(x => x.toString(16).padStart(2, '0')).join('');

  if (bytes.length !== size || digest !== sha256) throw new Error('digest mismatch');

  const transfer = new DataTransfer();
  transfer.items.add(new File([bytes], name, { type: 'image/png', lastModified: 0 }));
  input.files = transfer.files;
  input.dispatchEvent(new Event('input', { bubbles: true }));
  input.dispatchEvent(new Event('change', { bubbles: true }));
}
Enter fullscreen mode Exit fullscreen mode

Important boundaries

This does not bypass authentication, CAPTCHA, browser permissions, or the website’s submit button. Some sites reject synthetic events; in that case the correct result is to stop and ask for normal human input. The page URL must be read immediately before the operation, and a changed tab must fail closed.

After assigning the file, verify the preview’s count, filename, size, and—when possible—content hash. Final publishing should remain a separate, recorded action with duplicate checks and a public readback.

This article was prepared with AI assistance and reviewed against the implementation behavior described above. Project context: https://teamcerts.com

Top comments (0)