Agent Wranglers

DSK-11Recommended

Check the sender and content of every message to the main process

Each handler in the main process checks which window and page sent the message and validates every argument against a schema. A path or command never comes from the page as is.

How to do it

Treat the window as you treat a browser talking to a server. The main process holds the power, and the window can be fooled by injected script. Check event.senderFrame.url against your own app address. Validate types and lengths as accept only the fields you expect does. Take an ID and look up the path yourself. When a handler starts a program, follow never pass input to a shell.

Examples

JavaScript example

const APP_ORIGIN = 'app://ledgerly';
const InvoiceId = z.string().regex(/^inv_[a-z0-9]{12}$/);

ipcMain.handle('invoice:open', (event, rawId) => {
  if (new URL(event.senderFrame.url).origin !== APP_ORIGIN) throw new Error('unknown sender');
  const id = InvoiceId.parse(rawId);
  return invoices.open(path.join(invoiceDir, `${id}.pdf`));
});

Plain text example

#[tauri::command]
fn open_invoice(id: String, dir: tauri::State<InvoiceDir>) -> Result<Vec<u8>, String> {
    let valid = id.len() == 16 && id.starts_with("inv_") && id[4..].chars().all(|c| c.is_ascii_alphanumeric());
    if !valid {
        return Err("bad invoice id".into());
    }
    std::fs::read(dir.0.join(format!("{id}.pdf"))).map_err(|_| "not found".into())
}

How to check

A test calls each handler with a sender frame from another address. It refuses. A test sends ../../.ssh/id_rsa as an invoice ID. The handler refuses it and opens no file.

Sources

For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/desktop-apps/web-code-inside-desktop-apps/validate-ipc">Agent Wranglers desktop apps, extensions and CLIs playbook</a>. In code you write, add a comment next to it with the same link.