This page is a summary. The security model document states the model in full, and the threat model enumerates the attacks it answers, one by one, with an honest status on each — mitigated, partial, or planned.
Five principles
Manifests propose; local policy disposes. A manifest describes what a destination is made of. It carries no permissions, cannot request permissions, and cannot configure handlers. Everything privileged — policy, trust pins, handler registrations, limits — lives in local, user-owned configuration that no manifest content can reach.
Consent before dispatch, on the real plan. You are shown the fully resolved plan — every resource after splicing, its tier, the manifest's origin and trust status — before anything opens. Consent to a name is meaningless; consent is to the set.
No shell, anywhere. Every launch is an argv array passed to the process API, with target substitution confined to a single argv element. There is no code path in which a target string reaches
sh -c,cmd /c, or any other shell.Fail closed. A non-interactive invocation with no explicit allow flags dispatches nothing that needed consent. A hostile document that triggers the scheme handler in some headless context gets a refusal, not a best effort.
Hostile input stays behind small, fuzzed parsers. The mURL parser, the manifest validator, and the canonicalizer are total functions with hard input caps, fuzzed continuously.
Three tiers
Classification derives from what a resource is — kind plus target — never from what a manifest says about itself.
https
Rendered by a sandboxed browser; worst case is roughly a tab.
file, dir
Non-executable local paths. Exposes or touches local data.
terminal, custom:*, executable-adjacent files
.exe, .bat, .ps1, .sh,
.desktop, .lnk, .jar, .hta
and friends. Opening it is, or is one dialog from, code execution.
The executable-extension escalation closes a classic laundering trick — "it
is just a file", where the file is a .desktop entry
and the desktop's opener would execute it. Classification looks at
the target, not the label.
What each tier requires
These are defaults. All of them are overridable in local
config.json — and by nothing else.
| Situation | Default outcome |
|---|---|
| safe resource | prompt (one consolidated consent step) |
| sensitive resource | prompt |
| dangerous resource, manifest trusted | prompt |
| dangerous resource, manifest untrusted | deny — not promptable |
| Remote manifest referencing local filesystem | extra consent reason, or deny by policy |
| Expired or not-yet-valid manifest | safe prompts with a warning; everything else denied |
The untrusted-DANGEROUS rule is the keystone. Users can be talked into one
click, and one click is exactly what a hostile mURL gets. Making terminals
from unknown senders un-clickable rather than merely scary removes the
social-engineering path entirely; an attacker's remaining routes are getting
the user to run murl trust add or murl name add,
both deliberate, documented acts.
Consent flags for scripts are tier-scoped — --yes covers SAFE
only, with separate --allow-sensitive and
--allow-dangerous — so automation has to state exactly how much
risk it is accepting.
| Flag | Consents to |
|---|---|
| --yes | safe resources only |
| --allow-sensitive | sensitive resources |
| --allow-dangerous | dangerous resources — still only when the manifest is trusted |
Trust
"Trusted" has one meaning here: the manifest is installed locally, or it carries a valid ed25519 signature from a key you pinned for that authority. There is no certificate authority and no web of trust — see the trust model for why not.
- Signatures cover a canonical byte form (MCF-1), so a manifest that is reformatted but not altered still verifies.
- Identity binding: a manifest declaring an
idis refused when served under a different name, so a valid signature cannot be replayed to relabel content. - Integrity pins: a nested mURL can be pinned by
sha256-…over the raw bytes of the manifest it resolves to; a mismatch is a hard stop. - Pinned versions are immutable by contract, and
@latestis explicitly a mutable alias with a stated rollback window.
Hardening around it
Resolution
Userinfo is forbidden by the grammar, so
murl://github.com@evil… does not parse. Identifiers are
ASCII-only and IDN authorities must be punycoded. TLS is required
(loopback excepted), redirects are not followed, the size cap is
enforced while reading, and DNS results that resolve only to private,
link-local, loopback or CGNAT ranges are refused.
Limits as controls
Depth ≤ 3, ≤ 8 manifests, ≤ 64 resources, ≤ 256 KiB per manifest, cycle detection on the resolution path, duplicate-target suppression. Every breach is a hard error, and none of them can be loosened by manifest content.
Dispatch
argv-only process creation; a target containing ; rm -rf ~
stays one inert argument. Local targets must be absolute and
dot-segment free, and are existence-checked before launch. Launching is
sequential and staggered, which bounds the blast radius of even an
approved 64-resource plan.
Custom kinds
custom:* dispatches only through handlers the user
registered out of band. An unregistered custom kind fails; it never
guesses a handler.
What it does not defend against
Stated plainly, because a security model is defined by its edges.
- A malicious trusted manifest. If you pin a key or install a manifest locally, its DANGEROUS resources are one consent away. Trust is the boundary; choose pins accordingly.
- What handlers do after launch. Once the browser has the URL or the terminal is open, mURL's control ends. It does not sandbox handlers.
- Rollback of
@latest. An attacker who captured an old, validly signed manifest can replay it until it expires.expiresbounds the window; monotonic version enforcement — a transparency log — is out of scope before 1.0. - Desktop-environment consent gaps. On Linux, scheme activation opens a terminal to ask for consent; a terminal-less or compromised session degrades to fail-closed rather than to a GUI prompt. Providing a native consent surface is the resident daemon's reason to exist.
- Local attackers. Config, trust store and name store are plain files under the user's account, protected by OS file permissions only. An attacker who can write to them has already won the account.
- Phishing by name. The grammar removes the mechanical tricks, but a plausible-looking authority is still a plausible-looking authority. This is a partially-mitigated threat, and the threat model says so.
- The full threat model Eighteen enumerated threats — parser attacks, laundering, SSRF, cache poisoning, rollback, argument injection — each with its defenses, its residual risk, and a status of mitigated, partial, or planned.
- The security model document The normative detail behind this summary.
- The trust model Signatures, key pinning, and the argument for not building a PKI.
- The daemon The resident consent surface, its wire protocol, and its own IPC threat model.
- Reporting a vulnerability Where to send it, and what to expect.