# Show HN: Laptop is the last place your secrets are still in plaintext

> Source: <https://github.com/jitpass/jit>
> Published: 2026-08-16 06:55:04+00:00

**Just-in-time credentials for your dev machine.**

** Documentation** ·

[Quickstart](/jitpass/jit/blob/main/docs/getting-started/quickstart.md)·

[Supported tools](/jitpass/jit/blob/main/docs/tools.md)·

[Command reference](/jitpass/jit/blob/main/docs/reference/commands/jit.md)·

[Security](/jitpass/jit/blob/main/docs/security/architecture.md)

Status:macOS-only (Apple Silicon), and still in development.

Your secrets live in plaintext all over your machine: `.env`

files,
`~/.aws/credentials`

, `~/.zshrc`

exports, `.npmrc`

tokens, MCP configs. Anything
running as you can read them. A bad `curl | sh`

, a sketchy `npm install`

, or one
of the AI agents now running in your editor with your full permissions.

`jit`

moves each secret into a local encrypted vault gated by Touch ID, and
rewrites the files so your tools keep working. On disk there's now a decoy. The
real value only appears, in memory, for the specific process that asked for it,
after a biometric prompt. The result: you unlock once, `jit`

asks before handing
a credential to a tool (or an agent), and there's a decoy on disk the rest of
the time.

| launched by Code | launched by claude |
|---|---|

```
brew install jitpass/tap/jitpass
```

Or without Homebrew:

```
curl -sL https://dl.jitpass.com/jitpass/jit/releases/latest/download/jitpass_darwin_arm64.tar.gz | tar -xz jit
sudo mv jit /usr/local/bin/
```

Apple Silicon only — on an Intel Mac, build from source with
`go install github.com/jitpass/jit/cmd/jit@latest`

.

Pick one route. If you installed from the tarball before and are switching to
Homebrew, remove the old copy after the `brew install`

(`sudo rm /usr/local/bin/jit`

); otherwise two jits sit on PATH upgrading separately, and
`jit doctor`

will flag it.

Releases are signed with a Developer ID and notarized by Apple, so both paths
run without a Gatekeeper prompt: Homebrew quarantines its downloads and
Gatekeeper clears them against the notarization ticket, while `curl`

(and `go install`

) set no quarantine flag at all. To check what you got rather than
take our word for it, run `jit doctor`

— its `jit`

line reports
`signed CZC6BH93GJ`

, using the same check `jit upgrade`

runs before it will
install anything.

**Upgrading:** `brew upgrade jitpass`

, or `jit upgrade`

— a verified
self-update (Developer-ID signature and checksum both checked before the
swap, restarts the service). Either way your vault is untouched.

Homebrew installs shell completion with the binary, so `jit <TAB>`

completes
subcommands, flags, vault paths, and wrappable tool names out of the box.
Installed from the tarball or from source, add it yourself:

```
echo 'source <(jit completion zsh)' >> ~/.zshrc && exec zsh
```

Either way, `jit doctor`

tells you if completion isn't reaching your shell.

```
jit scan                            # read-only. changes no file it scans, prints no real value.
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
jit run -- npm run dev              # run your tool; real values injected into that process only
```

`jit scan`

with no path sweeps your whole home directory, so give it a moment on
a large one. To go straight at one place, point it at a path: `jit scan ~/.aws`

.

Day to day it's mostly `jit run -- <cmd>`

. For CLIs that carry their own login
token (`gh`

, `glab`

, `stripe`

, and more) you `jit wrap gh`

once and then keep
typing `gh`

as normal forever.

Not sure whether something needs `jit wrap`

, `jit migrate`

, or nothing? You
don't have to know. `jit scan`

splits everything it finds into what jit will
protect (one command - the wraps included) and what only you can fix, and
bare `jit migrate`

runs that whole plan:

``` bash
$ jit scan
  YOUR SECRETS: 7 — 0 protected by jit (0%)
  ▱▱▱▱▱▱▱▱▱▱  to 100%: one command +71% · 2 secrets only you can fix +29%

  jit will protect these — 5 secrets in 4 files, 0% → 71%
      → jit migrate
        ~/.zshrc            STRIPE_API_KEY, DB_PASSWORD
        ~/.config/gh/hosts.yml  GitHub CLI token · wraps gh
        ...

  only you can protect these — 2 secrets, 71% → 100%

    [rotate, then delete every copy]
    ! A production database password in 2 files
      → rotate it now, then delete every copy
```

(`jit scan --full`

still gives the classic per-category inventory with
severities, including the **Wrappable CLI Tokens** section.)

Migrate the credential once, then keep using the tool the way you always have.

```
# AWS (and Terraform, and every AWS SDK)
jit migrate ~/.aws/credentials       # keys move to the vault; no plaintext file left
aws s3 ls                            # resolves from the vault on demand. no prefix, no flag.
terraform apply                      # same creds, same command

# GCP application-default credentials (a machine-wide credential)
jit migrate ~/.config/gcloud/application_default_credentials.json
terraform apply                      # google provider reads ADC; works after a Touch ID prompt

# Docker / docker-compose
jit migrate ~/.docker/config.json    # registry logins move to the vault
jit run -- docker compose up         # jit injects them for this run
docker login ghcr.io                 # still works; the helper stores to the vault

# Shell exports that used to sit in ~/.zshrc
jit migrate ~/.zshrc                 # leaves a one-line hook; new shells just have the vars
./deploy.sh                          # scripts that read those vars work unchanged

# Tokens you once typed at the prompt, now sitting in your shell history
jit migrate ~/.zsh_history           # each one moves to the vault; your commands stay, the secrets don't
jit guard history                    # and stop the next one being recorded at all (zsh)
                                     # (bare `jit migrate` offers this too, in the plan it asks you to confirm)

# A CLI that carries its own token (gh, stripe, glab)
jit wrap gh                          # one time
gh pr list                           # token injected per call, forever
```

The first time each tool reaches for a real credential, `jit`

asks once and
remembers your answer until the vault locks. See [Two Touch ID moments](#two-touch-id-moments-not-one)
for how that sits on top of the vault unlock, what `--trust`

does, and how to
turn the per-tool prompts off.

Why do some tools need no setup while others take a `jit run`

? One rule: **can
the tool ask jit for the secret itself?** AWS (via `credential_process`

), your
shell at login, and docker's registry logins (via a credential helper) all can,
so you type nothing extra. Tools that only read a file at runtime (docker
compose, plain SDKs) can't ask, so `jit run`

hands them the value.

The machine-global credential files (GCP ADC, `sops`

, `npm`

, `netrc`

) work the
same everyday way: run your tool and approve the per-process prompt. Add `jit run --with <name>`

only when you want it explicit: for scripts and CI where there's
no prompt to answer, or when you want a hard gate a project's own config can
never reach. ** Supported tools** lists exactly what to type
for every tool, and how each is delivered.

`jit`

asks for your fingerprint at two different moments, doing two different jobs:

**Unlocking your vault.** The first time you use`jit`

after it locks, one Touch ID opens the vault for the whole session (5 minutes of activity, then it re-locks; and never longer than 8 hours, however busy you are). You unlock once, not once per command.**Handing a credential to a tool.** On top of that, the first time a given tool reaches for a real credential,`jit`

asks before handing it over and names what's asking. This is what stops a program you didn't run from quietly using your keys while the vault is open.

``` php
$ aws s3 ls
  Touch ID  ->  unlock your vault              # gate 1: opens the vault for 5 min
  Touch ID  ->  aws wants your aws credential   # gate 2: this tool, this credential
  ...your buckets...

$ aws s3 cp ./file s3://bucket/   # same tool, same session: no prompt

$ terraform apply
  Touch ID  ->  terraform wants your aws credential   # a different tool: it asks on its own
```

Gate 2 is what keeps an unlocked vault from being a free-for-all: even after
you've used `aws`

yourself, a sketchy `npm install`

reaching for those same keys
still triggers a prompt naming it, so you can say no.

Don't want the second gate? Turn it off; the vault lock stays (turning it off itself takes a Touch ID, since it reopens the window it closes):

```
jit service consent off   # tools resolve silently while the vault is unlocked
jit service consent on    # ask per tool again (the default)
```

Kicking off something that needs several credentials at once? `jit run --trust -- terraform apply`

approves that whole run's tools in one gesture. Full details:
[per-process consent](/jitpass/jit/blob/main/docs/service/consent.md).

Both gates assume a human is there to answer. An AI agent working overnight, a
long build, a scheduled job: the screen locks, the session drops, and the run
stalls on a prompt nobody will see. A **process grant** moves your decision
earlier instead of removing it - one Touch ID, given while you're still there,
that names exactly what you're signing:

``` php
$ jit grant --process claude --profile jamf --for 8h
  Touch ID  ->  let claude under iTerm2 use 2 secrets (jamf) unattended for 8h
✓ granted g-7f3a2c81   claude -> jamf   until 17:42
  └ covers claude under iTerm2: 1 running now, any started before 17:42
```

For the next 8 hours, every `claude`

under **the terminal you typed that in**
(and what it launches) gets those secrets with no prompts - through screen lock
and all, including sessions you start later: a new tab, the next `claude`

, a
script that fires at 3am. It's your terminal being named, not a name being
trusted: a program calling itself `claude`

somewhere else on the machine
doesn't descend from that tree and inherits nothing. The grant ends at its
deadline, when you quit that terminal, or the moment you type `jit grant revoke`

(which needs no fingerprint - taking access away is always free). Want
one exact process instead, gone when it exits? `--pid`

. Every serve lands in
the audit trail as its own event, so the morning after you can read exactly
what your agent touched while you slept. Full details:
[process grants](/jitpass/jit/blob/main/docs/service/grants.md).

Every jit command and every unlock lands in a durable log you read back with
`jit audit`

, newest first, one `key=value`

line per event, so it greps like a
real service log. Command arguments are masked, so the log proves a command ran
without ever storing the secret it carried.

``` bash
$ jit audit --since 1h
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
```

The middle line is the story jit exists to tell: `aws/default`

was read by `aws s3 ls`

, launched by `claude`

. The last is a prompt you declined: a `node postinstall.js`

under `npm`

reaching for those same keys, refused. jit also logs
what the service turned away at its socket (a process the kernel says isn't
yours, probing the agent) as `kind=error`

.

Narrow it with flags instead of grep: `--kind`

, `--status ok|failed|denied`

,
`--since`

/`--until`

(an age like `2h`

/`3d`

or a date), `--parent claude`

,
`--secret aws`

, `--user`

, `--grep <regexp>`

. Add `--follow`

(`-f`

) to stream new
events live like `tail -f`

, or `--format json`

for a machine-parseable dump. Both
halves are durable files beside the vault, so it answers for last week as readily
as the last hour.

`.env`

files, shell exports, AWS and Terraform, kubeconfig, Docker registry
logins, GCP ADC, `.npmrc`

/ `.netrc`

tokens, MCP server configs, bare token
files, credentials recorded in your shell history, wrappable CLIs (`gh`

,
`stripe`

, `vercel`

, …), and SSO CLIs that mint credentials at login
(`clisso`

). In every case the file keeps working and the real value comes
from the vault on demand.

The full catalog, grouped by exactly what to type for each tool, is
** Supported tools**: it tracks the code as tools are added or
removed. Anything not listed can still be wrapped with

[.](/jitpass/jit/blob/main/docs/wrap/custom-tools.md)

`jit wrap add`

`jit`

never destroys a credential. Migrate **moves** the value into the vault and
leaves a **working hook** where it was (a decoy `.env`

, an `eval "$(jit export)"`

line in your shell config, `credential_process = jit …`

in `~/.aws/config`

, or a
`PATH`

shim), so your tools keep resolving it on demand. The credential still
exists, just encrypted instead of sitting in plaintext.

And every change is reversible. Before touching a file, jit backs it up encrypted
into the vault, so `jit migrate undo`

puts it back byte-for-byte:

```
jit migrate ~/code/myapp        # applied the fix, one Touch ID
# changed your mind, or something broke?
jit migrate undo ~/code/myapp   # every touched file restored, byte-for-byte
```

The docs live under ** docs/**, organized by task:

: setup, migrating, living with the fix, step by step[Quickstart](/jitpass/jit/blob/main/docs/getting-started/quickstart.md): the vault, the service, mounts, and shims in one page[How it works](/jitpass/jit/blob/main/docs/getting-started/how-it-works.md): developer and security questions, answered bluntly[FAQ](/jitpass/jit/blob/main/docs/faq.md): what the per-tool prompts do, and how to tune or turn them off[Per-process consent](/jitpass/jit/blob/main/docs/service/consent.md): pre-approve a running tool to work unattended for a bounded, revocable, audited window[Process grants](/jitpass/jit/blob/main/docs/service/grants.md): read back every command, unlock, and refusal, filterable and followable[Audit trail](/jitpass/jit/blob/main/docs/reference/commands/jit_audit.md): every command and flag, generated from the CLI[Command reference](/jitpass/jit/blob/main/docs/reference/commands/jit.md): the threat model and the honest limits[Security architecture](/jitpass/jit/blob/main/docs/security/architecture.md): build/test setup; sign-off via DCO ([CONTRIBUTING.md](/jitpass/jit/blob/main/CONTRIBUTING.md)`git commit -s`

), no CLA

[PolyForm Perimeter License 1.0.0](/jitpass/jit/blob/main/LICENSE) - free for personal and internal
company use only.
