browser¶
A safe entry point for opening URLs in the user's default browser or mail client — one validation gate in front of the OS handler, as a light, framework-free library.
browser constrains every URL-opening path through a single validation point,
so a URL built from user- or config-influenced data can never hand a dangerous
scheme or a malformed string to the operating system. It is the same helper
behind go-tool-base's
URL-opening, extracted so any project can adopt the discipline without
pulling in the framework.
The single-gate idea¶
Opening a URL means handing a string to open (macOS), xdg-open (Linux), or
rundll32 (Windows). If that string is attacker- or config-influenced, the OS
handler is a dangerous place to send it unchecked. The whole point of this
module is that there is exactly one function — OpenURL —
that any code path may use to reach the OS, and it validates before it opens.
Scatter direct calls to the OS opener across a codebase and each one is a hole;
route them all through one gate and you have one place to reason about.
The three defences¶
Every URL passes three checks before the OS ever sees it. Validation is fail-fast, in this order:
- Scheme allowlist. Only
https,http, andmailtoreach the OS.file://,javascript:,data:, and custom protocol handlers are rejected withErrDisallowedScheme. The comparison is case-insensitive per RFC 3986, and the list is not configurable at runtime —AllowedSchemes()returns a defensive copy. - Length bound. URLs longer than
MaxURLLength(8192 bytes — chosen to sit below every supported platform's command-line length limit) are rejected withErrInvalidURL. - Control-character rejection. Any ASCII control character (
0x00–0x1F, including NUL, and0x7F) rejects the URL withErrInvalidURLbefore a platform handler can receive it. An empty or unparseable URL fails the same way.
Framework-free & injectable¶
The module graph is deliberately tiny: only
cli/browser (the cross-platform OS opener)
and cockroachdb/errors are in the
graph, and a depfootprint_test.go guard fails the build if anything else
enters it. No config framework, no TUI, no go-tool-base.
The actual OS open is an injectable seam — an Opener func(rawURL string) error
supplied through WithOpener. Tests inject a fake so nothing ever launches a
real browser (see Test without launching a browser).
Quick start¶
package main
import (
"context"
"log"
"gitlab.com/phpboyscout/go/browser"
)
func main() {
if err := browser.OpenURL(context.Background(), "https://example.com/docs"); err != nil {
log.Fatal(err)
}
}
OpenURL returns ErrDisallowedScheme for a scheme outside the allowlist,
ErrInvalidURL for an empty, oversize, control-character-bearing, or
unparseable URL, the context's error if the context was cancelled before the
open, or a wrapped error from the OS opener itself. Match the sentinels with
errors.Is.
Install¶
Where to go next¶
The documentation follows the Diátaxis framework:
- Getting started — a learning-oriented walkthrough: open a valid URL, then watch a dangerous scheme get rejected and handle the error.
- How-to guides — task-oriented recipes:
- Explanation — understanding-oriented background:
- Reference — the full API, with runnable examples, lives on pkg.go.dev.
Using go-tool-base? The framework routes all of its URL-opening through this helper. That wiring lives in go-tool-base; this module is standalone and framework-agnostic.