When a project's installation instructions start with npx some-tool init, what's your first instinct?
If you've been in software for more than five minutes, it's skepticism. And for good reason: the npm ecosystem has suffered through supply-chain incidents, typo-squatting attacks, and deeply nested dependency trees where a single compromised sub-dependency in node_modules can exfiltrate API tokens or compromise your environment.
Some statusline setups put the package manager straight into the refresh loop:
"statusLine": { "type": "command", "command": "npx -y untrusted-statusline@latest" } Or worse: piped shell scripts (curl -sL ... | bash).
In Google's Antigravity CLI (agy), the statusLine command runs on every refresh, many times an hour while you work. With an unpinned package or a download in that command, whatever version the registry or the server hands out next runs on your machine, with access to your files, your environment variables and your SSH keys.
When I built agy-powerline, a statusline for Google's Antigravity CLI (agy), I knew users would run npx agy-powerline init or install it as an agent plugin. That tool needs to inspect the current environment, read git states, and configure Antigravity's settings.json.
Asking users to run an untrusted script with write access to their dev setup is a non-starter. So from commit one, the project was designed under a strict set of non-negotiable security constraints. Here is the threat model, the architectural rules we enforced, and how we test them in CI.
1. The hard rule: dependencies: {}
In package.json, runtime dependencies stay empty:
{ "dependencies": {} } CI explicitly fails if any runtime dependency is added. Everything agy-powerline does—JSON parsing, formatting, Git inspection, terminal color detection, process launching, and the interactive TUI—is built with Node's standard library and raw ANSI escape codes.
Dev tools (eslint, prettier, fast-check) exist in devDependencies, but they are pinned to exact versions and are never included in the published npm package or the runtime bundle.
Zero dependencies means:
- Zero transitive dependency drift: What runs on your machine today runs tomorrow without an upstream maintainer slipping in analytics or an adware SDK.
- Fast cold starts: Nothing to resolve or unpack in
node_modules. Node opens the single bundled script directly. - Auditable: The whole runtime is about 7,500 lines of plain JavaScript, with nothing pulled in from elsewhere. Anyone can read all of it before running it.
2. Never trust the current working directory
A subtle attack vector for CLI tools that take configuration from the local workspace: a cloned repository containing a malicious .statusline.json could execute arbitrary shell commands when rendered.
To prevent this, agy-powerline never reads config from the current directory. Configuration is resolved exclusively from AGY_POWERLINE_HOME or your home directory (~/.agy-powerline/). A cloned repo cannot inject or override statusline settings.
Furthermore, the statusline includes an exec widget that allows running custom shell commands. To protect users:
-
execwidgets only run when the config sits in the install folder (~/.agy-powerlineorAGY_POWERLINE_HOME), whichisTrustedInstallDir()checks. - CLI previews (such as running
configureor previewing presets) never execute user commands—they display a static[cmd]placeholder instead. - If an imported config contains new shell commands,
importdemands explicit confirmation or--allow-exec.
3. Terminal injection: untrusted text is hostile
A statusline displays text it didn't write: branch names, folder names, subagent roles, conversation titles and the output of shell commands. If one of them holds something like \x1b[2J\x1b[;H..., printing it raw to your terminal can trigger terminal escape sequences, disguise shell prompts, or execute arbitrary terminal emulator actions (like OSC 8 hyperlink attacks or OSC 52 clipboard hijacking).
Every string originating outside our own code passes through stripControl and sanitizeDeep:
function stripControl(text) { return ( String(text ?? "") .replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?/g, "") .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "") .replace(/\x1b[@-_]?/g, "") .replace(/[\x00-\x1f\x7f-\x9f]/g, "") // Line/paragraph separators split the bar into extra rows in some // terminals, and bidi overrides can make a branch name read as another. // Zero-width joiners and plain RTL marks stay: emoji and RTL text need them. .replace(/[
--]/g, "") ); } In order: OSC strings (hyperlinks, clipboard and title changes), CSI sequences (colors, cursor moves, screen clears), any other escape, then C0 and C1 control characters, then the Unicode characters that can split a line or reverse the reading order.
Branch names, session payloads, and command outputs are sanitized before calculating column widths and before outputting to stdout.
4. Never lose or corrupt user settings
Running init touches Google Antigravity's settings.json. Touching another application's config file requires extreme care:
- Strict JSON parsing: If
settings.jsoncontains comments, syntax errors, or trailing commas thatJSON.parsecannot safely round-trip,initrefuses to modify it and prints instructions on what to add manually. - Permanent first backup: Before touching anything, the installer creates
settings.json.agy-powerline.original.bak. This file is never overwritten on subsequent runs or upgrades. You can always roll back to your pre-install state. - Symlink preservation: Many developers manage dotfiles with
stow,chezmoi, oryadm. Naive file writes replace symlinks with regular files, severing the link to the dotfile repo.agy-powerlineresolves the symlink target and writes safely to the destination file. - Targeted keys only: The installer touches only the
"statusLine"key, leaving every other setting intact. - Uninstall bounds:
uninstall --deleteonly removes files listed in a hardcoded manifest (INSTALLED_FILES). It never runs recursive deletes on user-derived directory paths.
5. Signed build provenance and offline archives
Publishing to npm shouldn't rely on long-lived personal API tokens stored on someone's laptop.
- npm Trusted Publishing: Releases are published exclusively from GitHub Actions using OpenID Connect (OIDC) tokens with zero stored secrets.
- npm provenance: Every version on npm carries a provenance statement that links it to the GitHub workflow run and commit that built it.
- Signed release files: Since 1.4.0, each GitHub release also attaches the npm tarball with its signed build attestation, so you can check a download yourself:
gh attestation verify agy-powerline-1.4.0.tgz -R wassim-azi/agy-powerline - Where npm is blocked: Each release since 1.4.0 also attaches plugin archives (
.zipand.tar.gz) foragy plugin install, with the same signed attestation.
6. Property-based fuzzing and static analysis
How do you guarantee a zero-dependency statusline never crashes your terminal session?
- Property-based tests with
fast-check: with a fixed seed and 300 runs per property, random and malformed payloads (wrong types, nulls, huge numbers, odd Unicode) go through the renderer. It must never throw, print no control characters besides its own colors, keep every row within widths from 40 to 240 columns, and never run anexeccommand in a preview. Random input piped into the runtime script must end in exit code 0, without a stack trace. - CodeQL & zizmor: CodeQL runs static code analysis on every push and pull request, and once a week. GitHub Actions workflows are audited with
zizmor(detecting credential leaks, runner compromises, or script injection) andactionlint. - OpenSSF Scorecard: The project continuously tracks OpenSSF security best practices.
The takeaway: a local file, not a download
Instead of fetching code on every refresh, agy-powerline joins its CommonJS runtime into a single local script at ~/.agy-powerline/statusline.js. Antigravity's settings.json points directly to your machine's local node binary and that local file.
On refresh:
- Zero network requests.
- Zero dynamic dependency resolutions.
- Zero execution of remote code.
Building CLI tools with zero runtime dependencies takes more upfront effort than pulling in five convenience packages from npm. But the payoff is immense: sub-second startup speed, zero supply-chain anxiety, and software you can run on your production workstation with confidence.
To inspect the build and install safely:
npx agy-powerline init 🤖 One-shot install via your Antigravity agent:
Hey agy, please install agy-powerline by running `npx agy-powerline init --yes`, then tell me to restart agy.
The code is MIT on GitHub: wassim-azi/agy-powerline. Read SECURITY.md for the full security policy, threat model, and how to report a vulnerability.


