Skip to main content
Glama
Nesarf
by Nesarf

Aragami

release

A portable static posture auditor for Tor Browser and Firefox.

One pure-Node head (lib/core.mjs), two interfaces (MCP stdio server + CLI), sharing the same single TOOLS[].execute -- no second implementation.

Aragami -- the uninvited deity, a wrathful god. It does not fight for you; it only points out, one item at a time, the traces already left on your machine. The name is about "being visible": an auditor does not create safety, it merely leaves residue nowhere to hide.


1. What it is, and what it is not

This section has to come first. This tool is designed to say "no".

What it does

Layer

What is audited

Build layer

Tor Browser: version, channel, Firefox base, bundled Tor version. Firefox: application version, and the version the profile was last used with

Transport layer

Tor: bridge configuration, pluggable transport inventory, uTLS fingerprint mimicry, domain fronting, built-in vs. custom bridges, deprecated PTs, Quick Start timing. Firefox: proxy mode, DNS over HTTPS, content blocking, fingerprint resistance, cookie policy

Content/authorization layer

Tor: onion service client authorization, whether .onion service private keys exist. Firefox: saved-login database, key material, client certificate store

Metadata layer

Tor: the guard set in state, descriptor cache, PT state, profile first-use time, session checkpoints, browsing history. Firefox: history and cookies, per-site permissions, form history, site storage, telemetry client ID, account linkage, extensions

Endpoint layer

Where the install or profile lives, user/temp directory residue, how many profiles exist, persistent vs. amnesic

Read-only, entirely. It does not start Tor, does not touch configuration, does not write any file. The only network action is the version check (which can be turned off).

What it does not do

Every tool, on every path -- error paths included -- carries this notice verbatim:

This tool performs a static audit only: it reads files, never launches the application
under audit, and never touches the network (except the version lookup).
It does NOT address: traffic correlation and timing analysis, endpoint compromise
(malware / device seizure), or anything that happens after decryption.
It protects [configuration posture and static residue]. It is NOT an anti-tracking tool.
A clean audit is NOT proof of safety. Treating it as one is a misuse.

The rest of this section is this README explaining that notice, not repeating it.

  • Traffic correlation and timing analysis: an adversary does not need to read the content, only to line up the entry and exit timings. Content encryption helps the metadata layer by roughly zero.

  • Endpoint compromise: malware, keyloggers, a seized device. Cryptography cannot reach this layer.

  • What happens after decryption: the recipient read it, kept a copy, said something, met someone -- the most common point of failure in public cases.

  • People: no technical solution exists. This is the one layer aragami_layer_assess will not recommend anything for, and the one aragami_audit never reports a finding under.

A clean audit is not a licence to relax. To address the metadata layer, the direction that works is a mixnet (Nym) or a metadata-hiding messenger (SimpleX / Cwtch) -- not more configuration piled onto Tor. That advice is this document's, not the tool's.

The emblem

Aragami -- the bare name, no extension, at the repository root and inside every package -- is a sealed statement of the work the project takes its name from. It is a short ASCII envelope holding an AES-256-GCM seal over a few lines of text about that work: title, artist, album, duration, format, and two digests.

The single-file executable has no files beside it to read, so the seal is also inlined into the bundle at build time. The file is the copy a person can find and read the shape of; the inline is what the executable reports from. Both paths are exercised -- the suite passes an inlined value to the loader, and the bundle is run from a directory with no seal in it before it is shipped.

The key is derived from the work's own content digest, so the seal opens only where the work itself is present. That is the whole of it, and it is worth being exact about what that does and does not mean:

  • It is not a secret, a credential, or a security control, and nothing here depends on it for safety. Anyone holding the source work can derive the key; the repository alone cannot.

  • What GCM adds is tamper-evidence: an altered seal fails to open rather than opening to something else. The suite asserts exactly that, along with a wrong source, an altered nonce, an altered tag, and a swapped payload.

  • No audio is carried in the repository or in any package, and the project distributes none. The default target is Tor Browser and the emblem belongs to a piece of music; a project about what you leave behind should not be the thing that leaves a copy.

aragami_env reports it as emblem, in structure only -- present, sealed, format, cipher, kdf, opens_with. It never reports contents, because it holds no key. Sealing is done by packaging/emblem.mjs, which reads the work, derives the text from the work's own metadata rather than from the machine or the moment, and writes the envelope:

node packaging/emblem.mjs seal <source-work>    # writes Aragami
node packaging/emblem.mjs open <source-work>    # prints the statement
node packaging/emblem.mjs verify                # structure only, no key needed

Related MCP server: disk-clean-mcp

2. Why a tool, not a document

Wrap an analysis in a plugin and the plugin only returns an essay -- but a reader can already read an essay, which is zero gain.

So this carries only the two executable pieces:

  1. Posture audit -- static, reproducible, with concrete artifacts (which file, how large, how long ago it was written)

  2. Layer arbitration -- aragami_layer_assess encodes "which layer's problem calls for which layer's countermeasure" as a program. Its core capability is recognizing layer mismatch and raising an alarm: real-time round trips and metadata protection are in hard conflict; pure content sensitivity should not be wrapped in high-risk tradecraft.

Point 2 matters especially: an advisor that only dispenses advice manufactures false confidence. This tool's does_not_cover field is permanent, not an optional decoration.


3. Installation

Dependencies

npm install          # only @modelcontextprotocol/sdk

Node >= 18 (fetch and WebCrypto are required). Zero other runtime dependencies -- the proxy tunnel is implemented from the stdlib.

Prebuilt packages

Every artifact is produced per target, so a download can carry a default. ARAGAMI_TARGET selects that default; an explicit --target argument always overrides it, which is what keeps a target-specific build from becoming a target-locked one.

Form

Runtime it needs

Verified by

Portable .zip + .cmd launchers

Node.js >= 18 on the host

Extracted, all six launchers run from a foreign working directory

.msi (WiX, perMachine)

Node.js >= 18 on the host

msiexec /i real install, registry entry, launcher run from a path containing spaces and parentheses, msiexec /x clean removal

Single-file .exe (Node SEA)

nothing

Run with Node removed from PATH; CLI and --mcp both answer

winget manifests

(installs the MSI, so Node)

winget validate 3/3, recorded SHA matches the MSI it was generated from

npm package

Node.js >= 18

npm pack, global install into a scratch prefix, both commands run

Linux .tar.gz + install.sh

Node.js >= 18 on the host

./install.sh executed for real, six wrappers installed, target pinning and --uninstall checked

.deb

nodejs (>= 18)

dpkg-deb accepted it, then dpkg -i and run

.rpm

nodejs >= 18

rpm -i and run; Conflicts refuses a second variant correctly

AppImage

nothing

Run on a distribution with no node and no libnode; CLI and --mcp both answer

Flatpak

nothing

flatpak install and run; the bundled interpreter reports its version while command -v node finds nothing

Snap (classic)

nothing

snap install --classic and run; same check as the Flatpak

Four forms carry their own runtime and the rest use the host's, and the difference is worth knowing before picking one. Carrying a runtime costs size: the Linux tarball is ~170 KB, the MSI ~188 KB, and the self-contained ones are 16 MB (Flatpak) to 40 MB (AppImage) to 90 MB (the single-file executable). A runtime that works anywhere cannot share libraries with the machine that built it. That is not a theoretical distinction: the AppImage used to copy the node that happened to be on PATH, which on a distribution that ships node as a shared library produced an artifact that only ran on machines with that library. packaging/linux/fetch-node.sh fetches the official self-contained build, pinned to a version and checked against its published SHA256, and both the AppImage and the snap use it.

confinement for the snap is classic, which asks the user to opt in with --classic. The alternative was measured rather than assumed: under strict confinement the home interface excludes hidden files, so ~/.mozilla is unreachable and the Firefox target cannot work at all. A strict snap would install cleanly and then be unable to do the one thing it exists for.

Build them yourself with the scripts in packaging/ -- each one stages a bundle and then wraps it, so no form re-implements the tool. -Target all means the neutral package that auto-detects at runtime, in every one of them; the pinned -tor and -firefox variants are separate calls.

pwsh packaging/pack-portable.ps1       -Target firefox
pwsh packaging/pack-msi.ps1            -Target tor
pwsh packaging/pack-sea.ps1            -Target firefox
pwsh packaging/linux/build-tarball.ps1 -Target firefox
pwsh packaging/linux/build-deb.ps1     -Target firefox

MCP setup

Aragami speaks MCP over stdio, so any MCP-capable client can run it: point the client at mcp/index.mjs and it will discover the four tools.

{
  "mcpServers": {
    "Aragami": {
      "command": "node",
      "args": ["/path/to/Aragami/mcp/index.mjs"]
    }
  }
}

If the client registers servers through a CLI, the equivalent is:

<client> mcp add Aragami -- node /path/to/Aragami/mcp/index.mjs

Installing from npm also exports the server as aragami-mcp, so the command can simply be aragami-mcp.

CLI

node cli/index.mjs                       # usage and tool list
node cli/index.mjs aragami_env --human
node cli/index.mjs aragami_audit --human
node cli/index.mjs aragami_audit --target firefox --human
node cli/index.mjs aragami_audit --layer metadata --min_severity warn --human
node cli/index.mjs aragami_version --human
node cli/index.mjs aragami_layer_assess --metadata_sensitive true --realtime true --human

Output is JSON by default (easy for a program to consume); --human switches to a human-readable layout.


4. Tool reference

Every tool takes an optional target (auto | tor | firefox), where auto prefers Tor Browser and falls back to Firefox. ARAGAMI_TARGET sets the same default from the environment, which is how the per-target packages pin one without forking the code; an explicit argument always wins over both.

Note that two different layer taxonomies appear in this document and they are not the same thing. The audit tags each finding with one of six layers -- build, transport, crypto (shown as "Content / authorization"), metadata, endpoint, social -- and aragami_audit's layer parameter accepts the first five, because nothing is ever reported under social: there is no technical solution to it, so there is nothing to find. Section 5 describes the separate four-layer model that aragami_layer_assess reasons over.

aragami_env

Probes the runtime environment and discovers every supported target: Tor Browser installs under targets.tor, Firefox profiles under targets.firefox. Run this first to get a path for the other tools.

Parameter

Description

install

Optional: a Tor Browser install root to use directly instead of auto-discovery

The response also carries emblem, the structure of the seal described in The emblem. It reports shape and never contents: this process holds no key.

aragami_audit

The full static audit.

Parameter

Description

target

auto | tor | firefox

install

Tor Browser install root; when given explicitly, only that one location is audited, otherwise auto-discovery

profile

Firefox profile directory; defaults to the profile the application opens, else the first found

layer

all | build | transport | crypto | metadata | endpoint

min_severity

ok | info | warn | critical; default ok (everything shown, including positive findings)

Returns findings grouped by layer, each carrying layer / severity / id / title / detail / evidence.

aragami_version

Checks the version in use against the vendor's own published metadata, and reports how far behind it is. Both targets are supported:

Target

Local version read from

Compared against

tor

tbb_version.json in the install

the Tor Project release endpoint (aus1.torproject.org), per channel

firefox

application.ini in the program directory, else the profile's compatibility.ini

product-details.mozilla.org/1.0/firefox_versions.json

A local version can honestly be absent -- a machine may have neither installed -- in which case installed and local_source are both null rather than one of them being invented.

Parameter

Description

target

auto | tor | firefox

install

Tor Browser install root, or a Firefox program directory; auto-discovered when omitted

online

Whether to check online; default true. false reports the local version only

proxy

Proxy URL, e.g. http://127.0.0.1:8080 or socks5://127.0.0.1:1080

It routes through the local proxy automatically: first the HTTPS_PROXY / ALL_PROXY environment variables, then the Windows system proxy (WinINET ProxyServer). Override with proxy, or pass "none" to force a direct connection.

Why this is needed: Node's built-in fetch does not read the system proxy, so on a network that requires one it goes straight to UND_ERR_CONNECT_TIMEOUT. This implements HTTP CONNECT and SOCKS5 tunnelling from the stdlib.

aragami_layer_assess

Layer arbitration. Takes scenario characteristics, returns a recommended channel + steps + warnings + a non-coverage list.

Parameter

Description

content_sensitive

Does the content itself need protection (what happens if it is read)

metadata_sensitive

Does the fact of communicating need protection (what happens if who-talks-to-whom is known)

realtime

Is a realtime / near-realtime round trip required

counterpart_capability

email-only | can-install-tools | signal-capable | tor-capable. The upper bound of the counterpart's tooling -- this usually caps the whole design

must_leave_no_third_party_copy

Must no third party retain a copy

endpoint_shared_or_seizable

Can the endpoint be accessed by others or seized

Key behaviors:

  • metadata_sensitive + realtime -> reports a hard conflict (real time couples the two ends' clocks tightly, which is perfect material for timing correlation)

  • content_sensitive and not metadata-sensitive -> discourages over-tradecraft (high-risk tradecraft is itself the most conspicuous signature)

  • counterpart_capability: email-only -> points out that the ceiling on the approach is pressed very low

  • every assessment carries does_not_cover


5. The four-layer model

Content layer   Can anyone else read the content itself?
                -> End-to-end encryption. The best-solved layer; not the bottleneck.

Metadata layer  Can anyone else learn who talks to whom, when, and how often?
                -> mixnet / metadata-hiding messenger / asynchronous mailbox / onion service.
                -> [Content encryption helps this layer by roughly zero] -- where the vast majority of approaches go wrong.

Endpoint layer  What did this machine in your hands leave behind? What if it is taken?
                -> Environment isolation / full-disk encryption. Cryptography cannot reach it.

Social layer    What will people say, keep, or be asked?
                -> No technical solution.

What aragami_audit examines is the static residue of the metadata layer and the endpoint layer (the remaining layers get posture-compliance checks only).


6. Measured baseline

Results from a run on this machine (Windows, Tor Browser installed on a non-system drive, network requiring a proxy). A snapshot rather than a specification: the version numbers and the guard count are what this install had, and the cache sizes in particular grow every time the browser is used, so they will not match a later run. The point of the section is the shape of the output and which layers speak up, not the arithmetic.

Build layer          Tor Browser 15.0.21 / Firefox 140.15.0 / Tor 0.4.9.11
                     version check -> outdated: 15.0.21 -> 15.0.22
Transport layer      Snowflake bridges x2 (built-in type), uTLS mimicry hellorandomizedalpn,
                     domain fronting fronts=, lyrebird ships 8 PTs, Conjure
Authorization layer  ClientOnionAuthDir configured (empty directory = feature ready, not enabled)
Metadata layer       state 11.9 KB: 22 guards (sets default + bridges), 266 circuit build timings,
                     descriptor cache 35.5 MB + 8.4 MB, profile first use 2025-11-21,
                     places.sqlite 5.0 MB (browsing history present -- worth a manual check)
Endpoint layer       Drive E, no residue in user or temp directories, persistent install

7. Verification

npm test              # all traversers, exit code 0
npm run test:cov      # coverage report (--gap prints the uncovered lines in detail)
npm run test:net      # network-layer protocol behavior only
npm run test:matrix   # contract traversal only
npm run test:emblem   # the seal only; needs no source work

Traverser

What it asserts

Scale

test/selftest.mjs

Semantic correctness (Tor target): should this branch report at all, and at what severity

165 items

test/matrix.mjs

Contract invariance + robustness: however it is called, the output contract does not break

277 calls / 557 contract checks / 0 violations

test/net.mjs

Protocol behavior: local mock proxy / SOCKS5 / a real TLS handshake over the tunnel / echo / HTTP servers

54 items

test/firefox.mjs

Semantic correctness (Firefox target): profile discovery, retention findings, and the credential-exposure line

142 items

test/emblem.mjs

The seal: opening with the source work, and refusing a wrong source, an altered payload, tag or nonce, or a swapped container. Built on a synthetic stream, because the claim under test is that a machine without the work cannot open it

94 items

test/coverage.mjs

Evidence of the traversal: which lines were never executed

1608/1619 = 99.3% on Windows; the Linux continuous integration runner is lower and is the figure the gate is set against

Coverage is a property of the environment as much as of the code, and both numbers are given because quoting only the higher one would be misleading -- but the two environments differ by platform and by proxy, which is why each row says which. The tunnel's success path, writing a request after the TLS handshake and reading the response back, used to be entered only where a proxy was reachable, so the runner never ran it and the gate had to be set to that floor rather than to the code. test/certs/ now lets the suite drive a real handshake over the tunnel, so that path is covered wherever the suite runs and the gate is back to 97.

What remains uncovered is detectProxy and only detectProxy. With a proxy variable set it returns inside the loop, so line 92's platform test needs a machine with no proxy variable at all, and line 93, readWininetProxy, needs Windows. Windows therefore reports 190/190 -- its proxy arrives through WinINET, not the environment -- while Linux reports 189/190 with no proxy variable and 188/190 with one. None of that is a behavioural difference between platforms: the same code runs, and only the fixture that reaches it differs.

selftest, matrix, firefox and emblem use synthetic fixtures (test/fixtures.mjs: good / degraded / minimal / self-inconsistent / boundary, for both targets; the emblem suite assembles its own FLAC stream from known field values), so they do not depend on whether Tor Browser or Firefox is actually installed on this machine and reproduce on any machine. net uses local mock servers and depends on neither the public internet nor a real proxy. The emblem suite additionally depends on no source work being present, which is the condition it is testing.

The matrix contract includes: serializable, boundary_notice permanent, no safety assertions may appear, finding fields complete, ordered by descending severity, tally consistent with reality, layer and severity filtering exact, does_not_cover non-empty, and 16 sets of malformed input must not throw.

Coverage is measured on Windows and Linux, because one platform is not enough. Windows alone reports 11 uncovered lines and Linux alone reports 40, but the two sets barely overlap: each platform covers the branches the other cannot reach -- the Windows drive-letter scan and WinINET proxy read are unreachable on Linux, and the /usr/lib/firefox candidates are unreachable on Windows. Only 5 lines are never executed anywhere, and they are named in PROOFREADING.md rather than papered over. The first Linux run also failed three suites, all of them test-portability defects that CI would have hit on its first run; those are recorded there too, together with every defect found during traversal and proofreading.


8. Design discipline (read this before changing anything)

  1. Read-only. Does not start Tor, does not change configuration, does not write files. The only networking is the version check, and it must be switchable off.

  2. boundary_notice is permanent. Every return of every tool carries it -- error paths included. Do not make it optional.

  3. The auditor makes no unfounded assertions. For example: guard entries in Tor 0.4.x are written Guard in=default rsa_id=..., and in the old style EntryGuard .... Recognizing only one format produces a false "no guards" false positive -- this trap was hit in practice. When unsure, emit info and say plainly "please confirm manually".

  4. Positive findings must be shown. Default min_severity: ok. Hiding ok makes the report look one-sided and creates needless alarm.

  5. No second implementation. The MCP server and the CLI share the same single TOOLS[].execute.

  6. aragami_layer_assess never claims safety. It must be able to say loudly "I do not solve this".

  7. Line coverage is not branch coverage. When a ternary branch on a line is never taken, that line still counts as covered -- a tautological ternary (x ? "a" : "a") is invisible to coverage and can only be caught by human proofreading. Coverage is necessary evidence, not sufficient evidence.

  8. Tests must not pass for the wrong reason. During one proofreading round, a blackhole-timeout test "passed" because the port was wrong, so it never actually reached the TLS timeout branch under test -- coverage is what dragged it into the light. Assertions must target the exact behavior, not "if there is an error, count it as a pass".

  9. Fixtures are shared. test/fixtures.mjs is the single source of truth. Inlining a copy means two sources of truth that drift apart on their own.

  10. Filter parameters must be echoed back. An unknown value falls back to the default, and the filter that actually took effect goes into applied_filter -- otherwise a caller who mistypes just gets an empty result and no idea why.


9. Next steps

  • Interlock with a delivery-preparation workflow: before handing off a sensitive package, verify OnionShare availability and onion client authorization state

  • Release pipeline (GitHub Release / npm) -- note that npmjs.com is hard to reach from mainland China, so the Git channel is the primary channel

License

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Enables read-only binary diff and fingerprinting of files without writing to disk, providing structured diffs and per-section SHA-256 hashes.
    3
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables LLMs and AI agents to perform defensive security posture assessments, privilege escalation surface audits, and post-quantum cryptography readiness checks through read-only diagnostic tools.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables read-only inspection of Git repository status and diff summaries, along with validation of OpenTofu and Ansible configurations, through a strictly limited, non-mutating toolset.
    MIT