Your API keys are in plain text right now: jit saves them and leaves decoys in their place

Open a terminal and run cat ~/.aws/credentials. If something came out, you just read a live production token from your own disk without authentication, without a prompt, and without a single line of log. Now do the same with the .env from the project you touched last week, with ~/.npmrc, with ~/.netrc, with the config of the MCP server that your agent loads on startup. Most of us know it and agree in silence to live with it, because the alternative — a secrets manager that every tool in the chain has to learn to speak — always turned out more expensive than what the risk hurt.

jit is a bet that the alternative can be free. Its pitch, verbatim from jitpass.com, is twelve words: “jit locks the real values away and leaves decoys in your files.” Your tools keep reading the same files in the same paths. What they receive depends on whether the process that asked was authorized, and the real value exists only in the memory of that process, after Touch ID.

It arrived at Show HN on August 16, 2026 with a title that is the complete argument: “Laptop is the last place your secrets are still in plaintext.” Fifty-four points, seventy-nine comments — a thread that discussed more than it applauded, and we’ll get to that.

The mechanism, before the commands

There are two pieces and it’s worth understanding them separately, because the second is why this doesn’t break your workflow.

The vault and the decoys. jit migrate reads the real credential from the file, encrypts it in a local vault whose master key lives in your login keychain, and writes a decoy where the real value was. Anything that reads that file without going through jit — a compromised postinstall script, a trojanized IDE extension, an infostealer sweeping known paths, an agent with prompt injection — receives the decoy. It gets a value that looks like a credential, has the shape of a credential, and is worth nothing.

The injection. This is the part that decides whether a tool like this is usable. jit uses three mechanisms depending on what the destination tool can do:

  1. Replacement via execve. jit run -- npm run dev puts the real value in the environment and then replaces itself in memory with your command. The secret lives in that process and nowhere else.
  2. Native credential protocols. AWS’s credential_process, Docker and Git’s credential helpers, kubectl’s exec plugins, Terraform’s credential helpers. These are hooks that the tools already publish exactly for this — jit doesn’t wrap or intercept AWS, it registers as what the AWS SDK was already designed to call.
  3. Named-pipe mounts. For tools that only know how to read a file, jit puts a mkfifo(2) mount in mode 0600 where the file was. The decrypted value passes through memory; nothing is written to disk. A migrated .env is a live mount, not a flat file.

Point 2 is the design decision that matters. Every previous attempt at this problem asked your tooling to change. jit uses the emergency exits that were already there.

Installation

Mac with Apple Silicon, via Homebrew:

brew install jitpass/tap/jitpass

Use Homebrew and not the tarball. Releases are signed with Developer ID and notarized by Apple; Homebrew puts the download in quarantine and Gatekeeper verifies the notarization before anything executes. The README does document a path with curl | tar, and a commentator on HN pointed out — rightly — that it looks odd next to the project’s own warning about curl | sh. The author agreed and said he’d reorder the docs so Homebrew comes first.

On an Intel Mac there’s no bottle; you compile from source:

go install github.com/jitpass/jit/cmd/jit@latest

Then confirm what you actually installed:

jit doctor

jit doctor reports the signature status, among other things. In a tool that’s about to hold all your machine’s credentials, run it.

Step one: jit scan

This is the five-minute version of the article, and it changes nothing:

jit scan                            # read-only. changes no file it scans, prints no real value.

It walks through the places where secrets actually accumulate: .env files and shell exports, ~/.aws, Terraform configs, kubeconfig, GCP’s Application Default Credentials, Docker registry auth, .npmrc and .netrc, MCP server configs, tokens from CLIs like gh, stripe, vercel and company, and your shell history. It prints findings without printing complete values.

That last category is what surprises. ~/.zsh_history is where a secret will go to live forever after a hasty export STRIPE_KEY=sk_live_... on a Tuesday in 2023. It doesn’t rotate, it doesn’t get scanned, it doesn’t get thought about, and it’s in plaintext.

Run this before deciding anything else. The output is the argument.

Step two: the vault and migration

jit vault init                      # make the vault (master key in your login keychain)
jit migrate --dry-run               # preview the whole machine-wide fix plan
jit migrate                         # apply it: shows plan, asks [y/N], one Touch ID
jit migrate ~/code/myapp            # or fix just one project

Run --dry-run first and read it. Then narrow your first real migration to a single project instead of the whole machine — jit migrate ~/code/myapp. You want to see a full day of work with your own tooling going through the FIFO mounts and credential helpers before you hand over ~/.aws.

Undo is real, and it’s why a first scoped attempt has little risk:

jit migrate undo ~/code/myapp   # every touched file restored, byte-for-byte

Before modifying anything, jit encrypts a backup of the original inside the vault. Byte-for-byte restoration, not a reconstruction with the best intentions.

Step three: run your tools

jit run -- npm run dev              # real values injected into that process only

For everything that speaks a native credential protocol — aws, docker, git, kubectl, terraform — you don’t need the wrapper after migrating. The helper gets registered; the tool calls it; Touch ID happens; the credential arrives.

Two stages of authentication. The first use after the session locks asks for Touch ID and opens the vault for five minutes of activity, up to eight continuous hours. After that, the first time a given tool asks for a given credential, Touch ID asks again and names the process that’s asking. The same tool later, no prompt.

The second stage is what a lot of people will turn off by the second day, and it’s also what makes the work interesting: it’s what turns “something on my machine read my AWS key” into “node postinstall.js, parent npm, asked for aws/default and I said no”. If it feels like too much:

jit service consent off             # keeps stage 1, drops stage 2
jit run --trust -- <command>        # approve all of a command's tools in one gesture

The part that’s actually about agents

If you run Claude Code, Codex, Gemini CLI, cline or Copilot with broad permissions, you have a process on your machine that reads files, executes commands, and can be directed by text it found in a repository. jit treats that as a first-class case and not as an afterthought.

Migrated MCP server configs store vault paths instead of keys. Agent CLIs come wrapped, so the Touch ID prompt names the agent that’s asking. Unauthorized reads get the decoy and land in the log with a placeholder value. And you can audit a particular agent:

jit audit --parent claude

For unattended runs, a grant instead of forty prompts:

jit grant --process claude --profile jamf --for 8h

A single Touch ID, while you’re sitting there. The grant is inherited only by descendants of the terminal that issued it, expires in the timeframe, gets revoked with jit grant revoke, and every access under that permission gets logged.

The audit log is the underestimated feature

time=2026-07-24 10:15:04 level=info kind=cmd status=ok dur=312ms cmd="jit migrate ~/.aws/credentials" user=meni parent=claude
time=2026-07-24 10:16:22 level=info kind=use op="read a secret" cmd="aws s3 ls" parent=claude secrets=aws/default
time=2026-07-24 10:31:09 level=warn kind=unlock status=denied method=touchid-or-passcode cmd="node postinstall.js" parent=npm secrets=aws/default

Read that third line again. That’s a package’s postinstall script reaching for your AWS credentials and getting told no, with a timestamp. Today you don’t have any equivalent of that line, because today there’s nothing to deny: the file just can be read.

jit audit --since 1h --parent claude --status denied
jit audit --follow                  # stream live
jit audit --format json             # machine-parseable

What it doesn’t do, stated plainly

The strongest objection on HN came from user hypfer: “If someone or something is executing code on your machine, you have already lost.” The author never answered it head-on, and I don’t think it can be refuted as stated — only narrowed.

The project narrows it with honesty in its own README. jit doesn’t protect a compromised account, and doesn’t protect a secret once it enters a process’s memory: the process is the boundary, period. And on identity, the README says something most security tools would have omitted:

“Caller identity explains and audits, it never decides. Process names are forgeable, and a fast-closing FIFO reader can evade identification entirely.”

So the honest claim is narrower than the marketing and still worth something: this doesn’t prevent code execution on your Mac, it eliminates the free loot. The dominant case in the real world isn’t a targeted attacker who already owns your machine and scales with patience — it’s an infostealer or a malicious dependency doing a quick sweep of known paths and exfiltrating what it finds. That attack gets decoys, and lands in the log.

The state of the project, worth keeping in mind

The Show HN post reads like a launch. It’s not a 1.0.

The repo was created on July 13, 2026. Today it’s at v0.98.0, released August 17 — about ninety-eight releases in five weeks, several a day. The status line in the README itself leaves no room for doubt: “macOS-only (Apple Silicon), and still in development.” Linux is “soon”, according to the author on HN.

And the v0.98.0 release notes are the most useful thing I read this week, because they document a failure instead of a feature. A brew upgrade on a real Mac left jit’s background service dead for 71 minutes. jit service restart reported success and did nothing. Root cause: replacing the binary made the daemon exit cleanly, by design, and then wait for launchd to restart it, which never happened; launchctl kickstart, the only mechanism that worked reliably, had been removed on July 20 and nobody noticed for four weeks.

That release fixes it: now restarts require a startup instead of registering and waiting, upgrades confirm the service responds at the expected build, and the status report distinguishes “accepted but never started” from “stopped with exit code” from “discarded entirely”, instead of reporting a generic crash.

Take that as a data point on where this is on the maturity curve, not as a reason to skip it. A daemon in the path between your tools and your credentials has a failure mode that a password manager doesn’t: when it crashes, things don’t fail loudly and obviously, they fail confusingly. Which is an argument for running jit scan today, migrating a project this week, and giving it a month before it holds ~/.aws.

The license, with precision

PolyForm Perimeter License 1.0.0 — free only for personal and internal company use. The author on HN: “Yes, but it’s completely free for internal Personal and Company use.”

It’s worth being precise, because the repo takes care to never say otherwise: this is free, and not open source. PolyForm Perimeter is not an OSI-approved license — it’s a source-available license built to prevent use that competes with the licensor. For your team using it internally on their laptops, that distinction changes nothing. For anyone thinking about building on it or distributing it within a product, it changes everything. Read the license before you assume.


None of this is a new idea. Vaults, biometrics, credential helpers and audit logs already existed. What jit did was notice that the boring, universal, embarrassing part of the problem — the laptop — is the part nobody had made a five-minute fix for, and then wire that fix to hooks that AWS, Docker, Git, kubectl and Terraform already published and almost nobody uses.

Start with jit scan. It changes nothing, prints nothing secret, and will tell you in about a second how bad the situation is on your own machine.

How many secrets do you think you have in plaintext on your machine right now? Run jit scan and tell me if the number surprised you.

https://news.ycombinator.com/item?id=49317546