Skip to content
authgrabber
Menu
Reference

Zig reference

Generated reference for the authgrabber binary.

authgrabber captures the browser’s authenticated state from the active profile and hands it to an automated browser (i.e., obscura). Concretely:

  • Session / SSO cookies (cookies.sqlite): the cookies that keep you signed in across apps (a single sign-on cookie plus per-app session cookies). Opaque cookies at or above 128 characters are flagged as auth candidates.
  • Stored OAuth tokens (webappsstore2.sqlite = localStorage): access, refresh and id tokens, classified by key name (access_token, refresh_token, id_token), by JWT shape (eyJ...), or by being a long opaque cookie. JWT payloads are decoded for the claims exp, iat, iss, sub, aud, scope, name, email, picture.
  • Fresh tokens on demand: the login command runs the active authorization-code flow (PKCE) to mint a new token instead of reusing what is already stored.
  • Saved logins (logins.json / key4.db): the password manager’s stored usernames and passwords, decrypted via the logins command using the browser’s NSS library.

It does not handle basic-auth credentials, HTTP auth, client secrets, or anything outside the profile’s SQLite stores.

Security: several commands decrypt real saved secrets, so keep --values output out of logs and shared files; prefer the default redacted form.

Usage: authgrabber <command> [args...]. Every command targets exactly one profile: the active one by default, or a specific one passed as [BROWSER:PROFILE] (a Firefox profile name matching Name=, an absolute path, a bare browser name selecting that browser’s default profile, or a <browser>:<profile> pair such as vivaldi:Default).

Command Description
authgrabber discover [BROWSER:PROFILE] print the resolved profile directory
authgrabber user-agent [BROWSER:PROFILE] print the user-agent the supported browser profile sends
authgrabber logins [--values] [BROWSER:PROFILE] list saved logins (decrypted via NSS)
authgrabber login <flags> active authorization-code flow (PKCE + localhost callback)
authgrabber cookies [--values] [--host <s>] [BROWSER:PROFILE] list cookies from the selected profile
authgrabber tokens [--json] [--values] [--host <s>] [BROWSER:PROFILE] list classified auth tokens
authgrabber export-session [--format json|obscura] [--values] [--out <file>] [--host <s>] [BROWSER:PROFILE] export session
authgrabber storage [--values] [--host <s>] [BROWSER:PROFILE] list localStorage per origin
authgrabber history [--values] [--json] [--limit N] [--host <s>] [BROWSER:PROFILE] list Firefox browsing history (redacted unless –values)
authgrabber query <DB> <SQL> run a SQL SELECT against a SQLite db

PROFILE may be a profile name or an absolute path.

The listing commands (cookies, storage, tokens) and export-session accept --host <substring>, which keeps only entries whose host or origin contains the substring, case-insensitively. Without it, every host in the profile is included.

Resolve and print the active profile directory.

Command
authgrabber discover
authgrabber discover vivaldi:Default
Output
/Users/you/Library/Application Support/Firefox/Profiles/xxxxxxxx.default-release
/Users/you/Library/Application Support/Vivaldi/User Data/Default

Supported browsers: firefox (default), zen, vivaldi, brave, edge, chromium, chrome.

List saved logins (username/password) from the password manager, decrypted via the browser’s own NSS library.

Command
authgrabber logins
authgrabber logins --values
Output
host.example.com username-len=12 password-len=14
host.example.com username=alice@example password=correct-horse

Default output is hostname username-len=<n> password-len=<n> (values redacted); --values prints the decrypted credentials.

List Firefox browsing history, newest visit first. Reads places.sqlite (moz_historyvisits joined to moz_places); the database is copied with its WAL sidecars before reading, so a running browser is not disturbed. Firefox only: Chromium history is not implemented.

Command
authgrabber history
authgrabber history --values --limit 20
authgrabber history --host example.com
Output
2026-09-15T21:43:27Z https://app.example.com <redacted, url-len=41, title-len=22>
2026-09-15T07:54:10Z https://app.example.com <redacted, url-len=68, title-len=40>

Default output is visited-at origin <redacted, url-len=<n>, title-len=<n>>: the origin is enough to recognise a site while the path, query and page title stay hidden. --values prints the full URL and title, --host <substring> filters case-insensitively, and --limit <n> caps the row count (newest first).

List session and SSO cookies from the cookie store.

Command
authgrabber cookies
authgrabber cookies --values
authgrabber cookies --host example.com
Output
session-token app.example.com 212
refresh-token login.example.com 212
persistent-auth example.com 1383

Opaque cookies at or above 128 characters are flagged as auth candidates. Chromium profiles (Vivaldi, Brave, Edge, Chrome, Chromium) are decrypted with the OS keychain via the os_crypt scheme.

List localStorage entries per origin (Firefox webappsstore2.sqlite, Chromium LevelDB log files).

Command
authgrabber storage
authgrabber storage --values
authgrabber storage --host example.com
Output
[https://example.com]
access_token = eyJhbGciOi...
refresh_token = eyJhbGciOi...

List classified auth tokens. Tokens are classified by key name (access_token, refresh_token, id_token), by JWT shape (eyJ...), or by being a long opaque cookie. JWT payloads are decoded for the claims exp, iat, iss, sub, aud, scope, name, email, picture.

Pass --host <substring> to keep only tokens from matching hosts or origins, and --json for a machine-readable listing.

Print the user-agent (UA) string the supported browser profile sends on the wire, for passing to obscura’s --user-agent (so the bot presents as the same browser that was authenticated).

Command
authgrabber user-agent
Output
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36 Vivaldi/8.2.4133.47
  • A general.useragent.override pref in the profile’s prefs.js, if set, is returned verbatim (authoritative).
  • Otherwise the default UA is reconstructed from the browser version recorded in the profile’s compatibility.ini / LastVersion plus the host OS.
  • For a Chromium profile (vivaldi:Default, …), the Chrome/ token uses the Chromium base read at runtime from the profile’s Preferences (extensions.last_chrome_version), so it tracks browser upgrades.
  • Example use: obscura fetch <url> --storage-dir <dir> --stealth --user-agent "$(authgrabber user-agent)".

Emit an obscura session bundle (cookies.json) for the automated browser. Pass --host <substring> to bundle only the hosts a task needs, instead of every credential in the profile.

Command
authgrabber export-session --format obscura --values --out ~/.config/authgrabber/session/cookies.json
authgrabber export-session --format obscura --values --host example.com --out ~/.config/authgrabber/session/cookies.json
Output
wrote 12 cookies to /Users/you/.config/authgrabber/session/cookies.json (mode 0600)

The session directory lives under the user’s private home location (~/.config/authgrabber/session), never /tmp.

Run the active OAuth authorization-code flow (PKCE + localhost callback) to mint a fresh token.

Command
authgrabber login --auth-url https://provider.example/authorize --client-id abc123 --token-url https://provider.example/token --scope openid
Output
opened the authorization URL in your default browser; waiting for the callback...
token response redacted; use --values to print it

The callback binds an OS-assigned ephemeral port (no fixed-port squatting), the state is compared in constant time, and the token exchange is a POST with grant_type=authorization_code plus the PKCE code_verifier.

Run a SQL SELECT against any SQLite database.

Command
authgrabber query <DB> <SQL>

Output is the result rows, columns separated by <TAB>; the database is read together with its -wal sidecar. Errors are reported clearly: OpenFailed, PrepareFailed, database is locked (Firefox running?); retry or close Firefox.

Build the binary with the Zig build system (0.16.0). The package manifest:

.{
.name = .authgrabber,
.version = "0.0.0",
.fingerprint = 0x94a8b077e1164c1d,
.minimum_zig_version = "0.16.0",
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}```
```sh
zig build
zig build test
zig build cross
just release
just release-cross

zig build produces zig-out/bin/authgrabber (a ~1.5 MB ReleaseSmall binary), zig build cross cross-compiles for x86_64 and arm64 on Windows, Linux and macOS, and just release runs the optimized native build.

  • 0 success.
  • Non-zero on errors (unrecognized command, missing args, or a command error).

The public API of every module, parsed from the source at generation time (including the /// doc comments). pub fn entries show their signature; pub const entries show the full declaration.

Where a candidate value was found.

pub const Store = enum { cookie, localStorage };

A classified token.

pub const Token = struct {
store: Store,
origin: []const u8,
name: []const u8,
value: []const u8 = "",
kind: []const u8,
expiry: ?i64 = null,
issued_at: ?i64 = null,
scope: ?[]const u8 = null,
audience: ?[]const u8 = null,
subject: ?[]const u8 = null,
issuer: ?[]const u8 = null,
name_claim: ?[]const u8 = null,
email: ?[]const u8 = null,
picture: ?[]const u8 = null,
};

Opaque session cookies at or above this length are treated as auth candidates even when their name is not a known token key and they are not a JWT.

pub const opaque_min_len: usize = 128;

Classify a candidate value from a cookie or localStorage key.

Returns a Token when the key matches a known token name, the value is a JWT, or (for cookies) the value is long enough to be an opaque session token; otherwise null (noise). All strings are owned by allocator.

pub fn classifyToken(
allocator: std.mem.Allocator,
store: Store,
origin: []const u8,
name: []const u8,
value: []const u8,
) !?Token

A single localStorage entry.

pub const Item = struct {
key: []const u8,
value: []const u8,
};

One origin’s extracted web storage.

pub const Origin = struct {
name: []const u8,
items: []Item,
};

Extract localStorage from a Firefox profile.

Enumerates storage/default/<origin>/ and reads each origin’s ls/data.sqlite (data table) into key/value pairs. The stored value BLOBs are plain UTF-8 text for localStorage. All returned strings are owned by allocator (pass an arena so you never free individually). Container-partitioned origins (name carries a ^userContextId= suffix) are returned as-is.

pub fn extractLocalStorage(
allocator: std.mem.Allocator,
io: std.Io,
profile_dir: []const u8,
) ![]Origin

One history visit, joined with the visited page.

pub const Entry = struct {
url: []const u8,
title: []const u8,
/// Visit time in microseconds since the Unix epoch (Firefox `visit_date`).
visited_at_us: i64,
/// Total number of recorded visits for the URL.
visit_count: i64,
};

Rows returned when the caller does not ask for a different limit.

pub const default_limit: usize = 100;

Extract browsing history from a Firefox (or Firefox-based, e.g. Zen) profile.

Reads places.sqlite: moz_historyvisits joined to moz_places, newest visit first, skipping hidden (internal) places. The live database is locked while the browser runs, so the main file and its -wal/-shm sidecars are copied to a temp dir and read from there – the same snapshot approach as cookies.extract. All returned strings are owned by allocator (pass an arena so you never free individually).

pub fn extract(
allocator: std.mem.Allocator,
io: std.Io,
env: *const std.process.Environ.Map,
profile_dir: []const u8,
host_filter: ?[]const u8,
limit: usize,
) ![]Entry

Run the history query against an already-readable places.sqlite.

The optional host_filter is applied in SQL, before limit, so a filtered listing always fills up to the requested row count. Split out from extract so the SQL and row parsing can be exercised against a fixture database instead of a live profile.

pub fn query(
allocator: std.mem.Allocator,
db_path: []const u8,
host_filter: ?[]const u8,
limit: usize,
) ![]Entry

Format a Firefox visit time (microseconds since the Unix epoch) as an ISO-8601 UTC timestamp. Returns "" for pre-epoch or unrepresentable values, so a corrupt row cannot poison the whole listing.

pub fn formatUtc(buf: []u8, visited_at_us: i64) []const u8

Reduce a URL to its origin (scheme://host[:port]), dropping path, query, fragment and any userinfo. Returns "" when there is no scheme://.

pub fn originOf(url: []const u8) []const u8

A row from moz_cookies.

pub const Cookie = struct {
host: []const u8,
name: []const u8,
value: []const u8,
path: []const u8,
expiry: i64,
is_secure: bool,
is_http_only: bool,
same_site: i32,
};

Extract cookies from a Firefox profile.

The live cookies.sqlite is locked while Firefox runs, so the main DB and its -wal/-shm sidecars are copied to a temp dir and read from there. All returned strings are owned by allocator (pass an arena).

pub fn extract(
allocator: std.mem.Allocator,
io: std.Io,
env: *const std.process.Environ.Map,
profile_dir: []const u8,
) ![]Cookie

Proof Key for Code Exchange (RFC 7636) values.

pub const Pkce = struct {
verifier: []const u8,
challenge: []const u8,
method: []const u8 = "S256",
};

Random code verifier: 32 bytes via the Io CSPRNG, base64url (no padding).

pub fn codeVerifier(allocator: std.mem.Allocator, io: std.Io) ![]const u8

S256 code challenge = base64url(sha256(verifier)).

pub fn codeChallenge(allocator: std.mem.Allocator, verifier: []const u8) ![]const u8

Random state token.

pub fn stateToken(allocator: std.mem.Allocator, io: std.Io) ![]const u8

Build the authorization URL (RFC 6749 section 4.1.1).

pub fn authorizeUrl(
allocator: std.mem.Allocator,
auth_url: []const u8,
client_id: []const u8,
redirect_uri: []const u8,
scope: ?[]const u8,
state: []const u8,
pkce: Pkce,
) ![]const u8

Result captured from the localhost redirect callback.

pub const CallbackResult = struct {
code: ?[]const u8 = null,
state: ?[]const u8 = null,
};

Exchange the authorization code for tokens (RFC 6749 section 4.1.3). Returns the raw response body (JSON). Caller owns it.

pub fn tokenExchange(
allocator: std.mem.Allocator,
io: std.Io,
token_url: []const u8,
client_id: []const u8,
code: []const u8,
verifier: []const u8,
redirect_uri: []const u8,
) ![]const u8

Extract an access token from a token-endpoint JSON body (best-effort).

pub fn accessToken(allocator: std.mem.Allocator, body: []const u8) ?[]const u8

Constant-time equality for byte strings (use for state/token comparisons).

pub fn secureEq(a: []const u8, b: []const u8) bool

Bind a localhost TCP listener. When port is 0 the OS assigns an ephemeral port; read it back with boundPort. Caller must deinit.

pub fn listen(io: std.Io, port: u16) !std.Io.net.Server

The actual port a bound server is listening on (resolved ephemeral).

pub fn boundPort(server: *const std.Io.net.Server) u16

Wait for one connection on an already-bound server, read the request, parse the query parameters, and respond. Returns the captured code/state.

pub fn acceptCallback(
allocator: std.mem.Allocator,
io: std.Io,
server: *std.Io.net.Server,
) !CallbackResult

A parsed result set: rows, each a slice of column strings.

pub const Table = [][]const []const u8;

Execute one or more SQL statements (no result rows), e.g. CREATE/INSERT.

pub fn exec(
allocator: std.mem.Allocator,
db_path: []const u8,
sql: []const u8,
) !void

Run a SQL SELECT against a SQLite database and return the parsed rows.

Uses the vendored SQLite library in-process, which reads the database file together with its -wal sidecar, so committed-but-uncheckpointed rows are visible and reads are safe while Firefox is running. All strings are owned by allocator (pass an arena so you never free individually).

pub fn query(
allocator: std.mem.Allocator,
db_path: []const u8,
sql: []const u8,
) !Table

Like query, but with an explicit lock wait budget in milliseconds. The 5s default keeps the single cookie/login DBs usable while the browser holds them; the localStorage scan uses the immutable mode so it never blocks on the running browser.

pub fn queryBusy(
allocator: std.mem.Allocator,
db_path: []const u8,
sql: []const u8,
busy_ms: c_int,
) !Table

Like query, but opens with SQLite’s immutable=1 URI flag: file locking is skipped entirely, so reads never block on the running browser holding per-origin localStorage databases (a snapshot read; in-flight writes may be missed for that origin, surfaced as a per-file error the caller skips).

pub fn queryImmutable(
allocator: std.mem.Allocator,
db_path: []const u8,
sql: []const u8,
) !Table

Like query, but reads every column as raw bytes via column_blob so binary BLOBs (e.g. encrypted cookie values) are not truncated at NUL.

pub fn queryBytes(
allocator: std.mem.Allocator,
db_path: []const u8,
sql: []const u8,
) !Table

Run a scalar query (e.g. SELECT count(*) FROM ...) and return the trimmed single value, or null if the result set is empty.

pub fn queryScalar(
allocator: std.mem.Allocator,
db_path: []const u8,
sql: []const u8,
) !?[]const u8
pub const Nss = struct {
handle: ?*anyopaque,
d_init: NssInit,
d_shutdown: NssShutdown,
d_slot: Pk11GetSlot,
d_checkpw: Pk11CheckPw,
d_sdr: Pk11SdrDecrypt,
d_free: SecItemFreeItem,
slot: ?*anyopaque = null,
};

Load libnss3 and initialize NSS on the given profile directory. Only implemented on macOS; other targets are a compile-time no-op.

pub fn open(allocator: std.mem.Allocator, io: std.Io, profile_dir: []const u8) !Nss
pub fn deinit(nss: *Nss) void

Decrypt one base64-encoded encrypted value (username or password). The caller owns the returned slice.

pub fn decrypt(nss: *Nss, allocator: std.mem.Allocator, b64: []const u8) ![]u8

Host operating system.

pub const Os = enum {
macos,
linux,
windows,
pub fn label(self: Os) []const u8 {
return switch (self) {
.macos => "macos",
.linux => "linux",
.windows => "windows",
};
}
};

A Firefox profile section parsed from profiles.ini.

pub const ProfileEntry = struct {
name: ?[]const u8 = null,
path: ?[]const u8 = null,
is_relative: bool = true,
is_default: bool = false,
};

A resolved profile directory.

pub const Profile = struct {
/// The profile's path as given in `profiles.ini` (possibly relative).
raw_path: []const u8,
/// Absolute path to the resolved profile directory.
dir: []const u8,
};

A supported browser backend. Selection stays inside the existing [PROFILE] argument as a browser:profile prefix (e.g. vivaldi:Default), so no CLI command or flag changes and Firefox defaults when the prefix is absent.

pub const Browser = enum {
firefox,
zen,
vivaldi,
brave,
edge,
chromium,
chrome,
pub fn fromName(name: []const u8) ?Browser {
const map = .{
.{ "firefox", Browser.firefox },
.{ "zen", Browser.zen },
.{ "vivaldi", Browser.vivaldi },
.{ "brave", Browser.brave },
.{ "edge", Browser.edge },
.{ "chromium", Browser.chromium },
.{ "chrome", Browser.chrome },
};
inline for (map) |m| {
if (std.ascii.eqlIgnoreCase(name, m[0])) return m[1];
}
return null;
}
/// Chromium data-dir name used under the browser's config root.
fn dataDir(self: Browser) []const []const u8 {
return switch (self) {
.firefox, .zen => &.{},
.vivaldi => &.{"Vivaldi"},
.brave => &.{ "BraveSoftware", "Brave-Browser" },
.edge => &.{"Microsoft Edge"},
.chromium => &.{"Chromium"},
.chrome => &.{ "Google", "Chrome" },
};
}
pub fn isChromium(self: Browser) bool {
return switch (self) {
.firefox, .zen => false,
else => true,
};
}
};

Detect the host OS.

Windows sets %OS% = Windows_NT in the environment, which we honour for hosts that inherit a Windows environment. Otherwise the compile-time target is used: each shipped cross-compiled binary targets exactly one OS (a Windows binary cannot run on Linux), so builtin.os.tag is correct for the running host. (builtin.cpu.arch is the field that must not be trusted, since the same binary is shipped for both archs.)

pub fn detectHost(env: *const std.process.Environ.Map) !Os

Absolute path of the directory that contains profiles.ini (Firefox-family).

pub fn profileRootDir(
allocator: std.mem.Allocator,
os: Os,
browser: Browser,
env: *const std.process.Environ.Map,
) ![]const u8

Absolute path of the Chromium-family browser’s data root, which contains the profile subdirs (Default, Profile 1, …).

pub fn chromiumRootDir(
allocator: std.mem.Allocator,
os: Os,
browser: Browser,
env: *const std.process.Environ.Map,
) ![]const u8

Resolve the active (or requested) profile directory.

override may be a profile name (matched against Name= for Firefox), an absolute path, or a browser:profile prefix to select another browser (e.g. vivaldi:Default). When absent, the Firefox active profile is chosen. The returned Profile.dir is an allocated absolute path (caller frees it).

pub fn discover(
allocator: std.mem.Allocator,
io: std.Io,
override: ?[]const u8,
env: *const std.process.Environ.Map,
) !Profile

Split a browser:profile override; returns the browser (firefox default).

pub fn browserFromOverride(override: ?[]const u8) Browser

Parse profiles.ini contents.

pub const Parsed = struct {
profiles: std.ArrayList(ProfileEntry) = .empty,
/// `Default=` paths from `[Install*]` sections (the profiles Firefox runs).
install_defaults: std.ArrayList([]const u8) = .empty,
};

Return the user-agent the active Firefox profile sends on the wire.

A general.useragent.override pref in prefs.js is authoritative and is returned verbatim. Otherwise the default UA is reconstructed from the Firefox version (compatibility.ini LastVersion) and the host OS. The caller owns the returned string.

pub fn userAgent(
allocator: std.mem.Allocator,
io: std.Io,
profile_dir: []const u8,
) ![]const u8

Raw (base64-decoded) os_crypt.encrypted_key bytes from Local State (or the profile’s Preferences for browsers that keep it there). Caller owns the result.

pub fn osEncryptedKey(
allocator: std.mem.Allocator,
io: std.Io,
profile_dir: []const u8,
) ![]u8

Return the actual AES key used for cookie/login encryption, per platform. The caller owns the returned bytes.

pub fn encryptionKey(
allocator: std.mem.Allocator,
io: std.Io,
browser: profile.Browser,
profile_dir: []const u8,
) ![]const u8

The user-agent a Chromium-family browser sends, reconstructed from its version (data root’s Last Version), the Chromium base (the profile’s Preferences, read at runtime so it tracks upgrades), and the host OS. Caller owns result.

pub fn userAgent(
allocator: std.mem.Allocator,
io: std.Io,
browser: profile.Browser,
profile_dir: []const u8,
) ![]const u8

A saved login (username/password) from a Chromium Login Data store.

pub const LoginInfo = struct {
origin: []const u8,
username: []const u8,
password: []const u8,
};

Read saved logins from a Chromium Login Data database, decrypting the password values with the os_crypt key. Caller owns the slice and strings.

pub fn extractLogins(
allocator: std.mem.Allocator,
io: std.Io,
env: *const std.process.Environ.Map,
browser: profile.Browser,
profile_dir: []const u8,
) ![]LoginInfo

A localStorage entry from a Chromium profile.

pub const LocalStorageEntry = struct {
origin: []const u8,
key: []const u8,
value: []const u8,
};

Snappy decompression ported line-by-line from golang/snappy decode_other.go. Caller owns the result.

pub fn snappyDecompress(allocator: std.mem.Allocator, src: []const u8) ![]u8

Parse a LevelDB SSTable (.ldb): footer -> index block -> data blocks, Snappy-decompressing each block, and append localStorage entries.

pub fn parseSSTable(
allocator: std.mem.Allocator,
out: *std.ArrayList(LocalStorageEntry),
data: []const u8,
) !void

Read localStorage entries from the LevelDB write-ahead logs AND the SSTables: Local Storage/leveldb/*.log (recent, uncompressed) and *.ldb (older, Snappy-compressed blocks). Caller owns the slice/strings.

pub fn extractLocalStorage(
allocator: std.mem.Allocator,
io: std.Io,
profile_dir: []const u8,
) ![]LocalStorageEntry

Decrypt one Chromium encrypted value (v10 or legacy) with the os_crypt key. The macOS keychain provider uses AES-128-CBC with the fixed 16-space IV; newer Chromium prepends a 32-byte SHA256(host) hash to cookie values, which is stripped here. Also tries the empty-key fallback and AES-GCM (other providers). The caller owns the result.

pub fn decryptCookieValue(allocator: std.mem.Allocator, key: []const u8, ev: []const u8, strip: usize) ![]u8

Extract cookies from a Chromium profile’s Cookies database, decrypting values with the os_crypt key. Returns the same cookies.Cookie shape as the Firefox extractor. Caller owns the slice and its strings.

pub fn extractCookies(
allocator: std.mem.Allocator,
io: std.Io,
env: *const std.process.Environ.Map,
browser: profile.Browser,
profile_dir: []const u8,
) ![]cookies.Cookie