Security

August 1, 2026 · View on GitHub

Reporting a vulnerability

Please report privately, not in a public issue.

Use Security → Report a vulnerability on https://github.com/moul/gnomputer. If private reporting is unavailable to you, open an issue that says only that you have a security report and asks for a private channel — no details, no proof of concept.

Please include what you can: what an attacker gains, the steps to reproduce, and the commit or deployed build you saw it on. There is no bounty; this is a personal project.

What Gnomputer is

A static, client-side PWA. There is no Gnomputer server, no account system, and no database that belongs to us. The app runs in your browser and talks directly to public Gno RPC endpoints and tx-indexers. Everything it stores — layout, theme, scripts, trails, favorites — is in your own browser's IndexedDB and localStorage.

That shapes the threat model: the interesting attacks are on what the app does with untrusted data it fetches, and on what it can be tricked into asking your wallet to sign.

Security properties this codebase tries to hold

These are enforced by code and covered by tests. A change that breaks one is a security regression, not a style question.

Keys never reach Gnomputer. Signing happens in your wallet extension. The app builds a message and hands it over; it never sees a private key, a seed phrase, or an unlocked account.

One signing path. apps/web/src/shell/transaction-intent.ts is the only place in the app that calls the wallet's DoContract. Anything that wants to submit a transaction goes through it, which is what makes the next two properties checkable rather than aspirational.

Nothing is signed without a review step. Chain, account, realm, function, arguments, and any attached funds are shown before the wallet is called.

Chain mismatches refuse to sign. If the wallet's chain ID and the active network's chain ID disagree, signing is blocked and the error names both. A network whose chain ID is unknown — which is what a custom endpoint gets when discovery fails — is never signable, because an unknown chain can't be proven to match.

Realm-supplied URLs are filtered. Realm output is chain data authored by whoever deployed the realm, and it becomes links in the UI. Only http:, https:, and mailto: survive; javascript: and data: are rejected at both the parse and the render site, including control-character obfuscation (java�script:) and embedded credentials. See packages/lenses/src/safe-url.ts.

Responses are validated at the adapter boundary, and every RPC and indexer request carries a deadline (15s by default) so a hostile or hung endpoint can't pin the UI open indefinitely.

Known limits — please don't report these as vulnerabilities

A custom network endpoint is trusted with what it serves. If you point Gnomputer at a malicious RPC URL, it will render what that server returns. The URL filtering above limits the damage, but a hostile endpoint can still lie to you about chain state. Improving how custom endpoints are presented and trusted is tracked in the audit backlog.

Realm content is untrusted by construction. Rendered realm output can say anything. Treat it the way you'd treat any web page you didn't write.

Public endpoints see your queries. Browsing a realm tells that RPC endpoint's operator you looked at it. This is inherent to reading a public chain over someone else's node.

Local data is not encrypted. Scripts and history sit in IndexedDB in the clear. Anyone with access to your browser profile can read them.

Third-party embeds. Some views embed external pages (Gnockpit, gnoweb) in an iframe, and Resources fetches from api.github.com and raw.githubusercontent.com. Those services' own security applies.