
Security News
Re-Enabled GitHub Actions Expose Thousands of Repositories to Mini Shai-Hulud
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.
CLI for Keychains.dev — register machines, manage permissions, and make proxied API calls
CLI for Keychains.dev — secure credential delegation for AI agents.
npm install -g keychains
# Just run it — no setup needed
keychains curl https://api.lifx.com/v1/lights/all \
-X GET \
-H "Authorization: Bearer {{LIFX_PERSONAL_ACCESS_TOKEN}}"
That's it. On first run, Keychains automatically:
Click the URL, sign in, connect your credentials, and re-run the command. After that, every call just works.
You never put real tokens in the command. Use template variables like {{OAUTH2_ACCESS_TOKEN}} or {{LIFX_PERSONAL_ACCESS_TOKEN}} and Keychains swaps them for real credentials at proxy time.
keychains curl <url>
│
├── Machine not registered? → auto-register using hostname
├── No permission? → auto-create wildcard (active immediately)
├── Make proxied API call
│
└── 403: credentials missing
└── "Authorize at: https://keychains.dev/add-permissions?machineId=...&scopes=..."
└── User clicks → signs in → machine linked → credentials connected
└── Re-run → works!
Every command supports three output formats:
| Flag | Format | Use case |
|---|---|---|
--human | Colored table (default) | Reading in terminal |
--json | Raw JSON | Piping to jq, scripts, CI |
--env | KEY=VALUE | Appending to .env files |
# Create a permission and append directly to .env
keychains permissions create --name "Dev" --env >> .env
# List permissions as JSON
keychains permissions ls --json
# Get your identity as env vars
keychains whoami --env >> .env
Use {{$varname}} patterns in your URL, headers, or body to inject values from piped JSON input. This is different from Keychains credential templates ({{OAUTH2_ACCESS_TOKEN}}) which are resolved server-side.
# Pipe JSON into curl — variables get substituted before the request
echo '{"user": "alice", "email": "alice@example.com"}' \
| keychains curl https://api.com/users/{{$user}} \
-X POST \
-d '{"user": "{{$user}}", "email": "{{$email}}"}'
| Syntax | Resolves to |
|---|---|
{{$user}} | params.user |
{{$user.name}} | params.user.name |
{{$items[0].id}} | params.items[0].id |
{{$items[0]['key']}} | params.items[0]["key"] |
{{$items[0]["key"]}} | params.items[0]["key"] |
{{$a.b[2].c['d']}} | params.a.b[2].c["d"] |
{{$...}} patterns in the URL, headers, and body{{$path}} is replaced with the resolved value from the piped JSON{{$...}} patterns) is saved to historyparams# Complex nested data
echo '{"users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}' \
| keychains curl https://api.com/users/{{$users[0].id}} \
-X PATCH \
-d '{"name": "{{$users[0].name}}"}'
# Credential templates (server-side) can mix with input variables (client-side)
echo '{"repo": "my-org/my-repo"}' \
| keychains curl https://api.github.com/repos/{{$repo}}/issues \
-X POST \
-H "Authorization: Bearer {{OAUTH2_ACCESS_TOKEN}}" \
-d '{"title": "Bug in {{$repo}}"}'
keychains machine — Machine IdentityManage your machine's cryptographic identity (Ed25519 SSH keypair).
Most users don't need to register manually — machines are auto-registered on first use. Use these commands for explicit registration or management.
keychains machine register --name "my-laptop" # Register with a custom name
keychains machine register --name "ci" --server https://custom.keychains.dev
keychains machine status # Show machine status & permissions
keychains machine ls # List all machines on your account
keychains machine update --name "new-name" # Rename this machine
keychains machine rotate-keys # Rotate SSH keypair
keychains machine rm <machineId> # Remove a machine from your account
keychains machine rm <machineId> -f # Remove without confirmation
keychains permissions — Permission RequestsCreate, list, revoke, and suspend permissions. Use -a to see all permissions across your account (like git branch -a).
# Create a wildcard permission (default)
keychains permissions create --name "Dev machine"
keychains permissions create --name "Dev" --env >> .env # Append to .env
# Create a scoped permission
keychains permissions create --name "GitHub read-only" \
--scopes "github::repo:read,github::read:org"
# List permissions
keychains permissions ls # This machine only
keychains permissions ls -a # All permissions on your account
keychains permissions ls --json # JSON output
keychains permissions ls --status active # Filter by status
# Revoke / suspend
keychains permissions rm <id> # Revoke a permission
keychains permissions rm <id> -f # Skip confirmation
keychains permissions suspend <id> # Suspend (requires re-auth)
Aliases: keychains perm create, keychains perm ls, etc.
keychains delegates — Delegate IdentitiesGive remote systems (sandboxed agents, CI runners) limited access without sharing your machine keys.
# Create a wildcard delegate from a permission
keychains delegates add <permissionId> --wildcard --ttl 3600
# Create a scoped delegate
keychains delegates add <permissionId> --scopes "github::repo"
# List delegate-enabled permissions
keychains delegates ls
keychains delegates ls -a # All on account
keychains delegates ls <permissionId> # For a specific permission
# Revoke a delegate
keychains delegates rm <delegateId>
keychains delegates rm <delegateId> -f # Skip confirmation
# Get delegate credentials as env vars
keychains delegates add <permissionId> --wildcard --env >> .env
Aliases: keychains delegate add, keychains delegate ls, etc.
keychains connections — OAuth & API Key ConnectionsList and manage your connected services (OAuth providers and API keys). Interactive and non-interactive modes.
# List connections
keychains connections ls # Active connections
keychains connections ls --status all # All connections
keychains connections ls --provider github # Filter by provider
keychains connections ls --json # JSON output
keychains connections ls -i # Interactive mode
# Remove a connection
keychains connections rm github # By provider name
keychains connections rm <connectionId> # By connection ID
keychains connections rm -i # Interactive mode (select from list)
keychains connections rm github -f # Skip confirmation
Aliases: keychains conn ls, keychains conn rm, etc.
keychains curl — Proxied API CallsMake API calls through the Keychains proxy. Credentials are injected automatically.
# Basic GET request
keychains curl https://api.github.com/user \
-H "Authorization: Bearer {{OAUTH2_ACCESS_TOKEN}}"
# POST with body
keychains curl https://slack.com/api/chat.postMessage \
-X POST \
-H "Authorization: Bearer {{SLACK_BOT_TOKEN}}" \
-H "Content-Type: application/json" \
-d '{"channel": "#general", "text": "Hello from keychains!"}'
# Use a specific permission
keychains curl https://api.stripe.com/v1/customers?limit=5 \
--permission pr_abc123 \
-H "Authorization: Bearer {{STRIPE_SECRET_KEY}}"
Permission resolution order:
--permission <id> flagKEYCHAINS_PERMISSION_ID in .env (cwd)KEYCHAINS_PERMISSION_ID environment variableEvery curl command is automatically saved to local history.
keychains whoami — Identity Checkkeychains whoami # Human-readable
keychains whoami --json # JSON output
keychains whoami --env >> .env # Append to .env
keychains history — Curl Command HistoryBrowse, search, and manage your past keychains curl commands. History is stored locally in ~/.keychains/history.json (default: 100 entries).
# List recent history
keychains history
# Search history
keychains history "github"
keychains history "stripe" --limit 50
# Interactive browse (select entries to export/re-run)
keychains history -i
keychains history -i "gmail"
# Save a history entry as a template
keychains history save hist_abc123 --name "List GitHub repos"
# Clear history
keychains history clear
keychains history clear -f # Skip confirmation
keychains templates — Request TemplatesSave frequently-used requests as named templates for quick re-use and code export.
# List templates
keychains templates ls
keychains templates ls "github" # Search templates
keychains templates ls --json # JSON output
# Show template details
keychains templates show "my-template"
keychains templates show tpl_abc123
# Delete a template
keychains templates rm tpl_abc123
keychains templates rm "my-template" -f # By name, skip confirmation
Aliases: keychains tpl ls, keychains tpl show, etc.
keychains export — Code GenerationExport any history entry or template as runnable code in your language of choice.
When the command contains input variables ({{$...}}), the generated code is a function that accepts a params dict. Use --name to set the function name.
# Export a standalone script (no input variables)
keychains export hist_abc123 --nodejs > index.js
keychains export hist_abc123 --ts > index.ts
keychains export hist_abc123 --python > script.py
keychains export hist_abc123 --go > main.go
keychains export hist_abc123 --curl > request.sh
# Export a function (when input variables are present)
keychains export hist_abc123 --ts --name fetchUser > fetchUser.ts
keychains export tpl_abc123 --python --name fetch_user > fetch_user.py
keychains export hist_abc123 --go --name FetchUser > fetch_user.go
# Export from template (by ID or name)
keychains export tpl_abc123 --nodejs > index.js
keychains export "my-template" --python > script.py
# Use --lang flag
keychains export hist_abc123 --lang typescript > index.ts
Generated function example (TypeScript):
// keychains export hist_xxx --ts --name fetchUser > fetchUser.ts
interface InputParams {
user: any;
email: any;
}
export async function fetchUser(params: InputParams): Promise<{ status: number; data: any }> {
const url = `https://api.com/users/${params.user}`;
const headers: Record<string, string> = {};
headers["Content-Type"] = "application/json";
const body = `{"user": "${params.user}", "email": "${params.email}"}`;
const response = await fetch(url, { method: "POST", headers, body });
const text = await response.text();
let data: any;
try { data = JSON.parse(text); } catch { data = text; }
return { status: response.status, data };
}
Supported languages:
| Flag | Language | File |
|---|---|---|
--nodejs / --node / --js / --javascript | Node.js (CommonJS) | .js |
--typescript / --ts | TypeScript (fetch) | .ts |
--python / --py | Python (requests) | .py |
--go / --golang | Go (net/http) | .go |
--curl | curl / bash function | .sh |
You don't pass real secrets. Use {{VARIABLE_NAME}} placeholders in your headers. Keychains resolves them at the proxy level — the actual token never touches your machine.
| Template | Resolves to |
|---|---|
{{OAUTH2_ACCESS_TOKEN}} | OAuth 2.0 access token (auto-refreshed) |
{{OAUTH2_REFRESH_TOKEN}} | OAuth 2.0 refresh token |
{{OAUTH1_TOKEN}} | OAuth 1.0 token |
{{OAUTH1_TOKEN_SECRET}} | OAuth 1.0 token secret |
Named API keys stored in the Keychains dashboard:
| Template | Example service |
|---|---|
{{STRIPE_SECRET_KEY}} | Stripe |
{{OPENAI_API_KEY}} | OpenAI |
{{SENDGRID_API_KEY}} | SendGrid |
{{LIFX_PERSONAL_ACCESS_TOKEN}} | LIFX |
# Toggle LIFX lights
keychains curl https://api.lifx.com/v1/lights/all/toggle \
-X POST \
-H "Authorization: Bearer {{LIFX_PERSONAL_ACCESS_TOKEN}}"
# List Stripe customers
keychains curl https://api.stripe.com/v1/customers?limit=5 \
-H "Authorization: Bearer {{STRIPE_SECRET_KEY}}"
# Send a Slack message
keychains curl https://slack.com/api/chat.postMessage \
-X POST \
-H "Authorization: Bearer {{SLACK_BOT_TOKEN}}" \
-H "Content-Type: application/json" \
-d '{"channel": "#general", "text": "Hello from keychains!"}'
# Create a GitHub issue
keychains curl https://api.github.com/repos/octocat/hello-world/issues \
-X POST \
-H "Authorization: Bearer {{OAUTH2_ACCESS_TOKEN}}" \
-H "Content-Type: application/json" \
-d '{"title": "Found a bug", "body": "Something is broken"}'
# 1. Register machine
keychains machine register --name "dev-laptop"
# 2. Create a scoped permission for a project
keychains permissions create --name "My Project" \
--scopes "github::repo,slack::chat:write" \
--env >> .env
# 3. Make API calls (permission auto-detected from .env)
keychains curl https://api.github.com/user \
-H "Authorization: Bearer {{OAUTH2_ACCESS_TOKEN}}"
# 4. Use input variables with piped JSON
echo '{"owner":"octocat","repo":"hello-world"}' \
| keychains curl https://api.github.com/repos/{{$owner}}/{{$repo}}/issues \
-X POST \
-H "Authorization: Bearer {{OAUTH2_ACCESS_TOKEN}}" \
-d '{"title":"Bug in {{$repo}}"}'
# 5. Export the command as a TypeScript function
keychains export hist_abc123 --ts --name createIssue > createIssue.ts
# 6. Give a CI runner limited access
keychains delegates add pr_abc123 --wildcard --ttl 3600 --env >> ci.env
# 7. Check your connections
keychains connections ls
keychains connections ls --json | jq '.[] | .provider'
.env for ProjectsDrop a .env in your project root:
KEYCHAINS_PERMISSION_ID=pr_abc123
Every keychains curl in that directory will use it automatically.
Or create a permission directly into .env:
keychains permissions create --name "My Project" --env >> .env
All local data is stored in ~/.keychains/:
| File | Contents |
|---|---|
credentials.json | Machine ID, server URL, key paths |
history.json | Curl command history (default limit: 100) |
templates.json | Saved request templates |
config.json | CLI configuration (history limit, etc.) |
id_ed25519 | Ed25519 private key |
id_ed25519.pub | Ed25519 public key |
| Command | Description |
|---|---|
| Machine | |
machine register --name <n> | Register this machine |
machine status | Show machine status |
machine ls | List machines on account |
machine update --name <n> | Update machine name |
machine rotate-keys | Rotate SSH keypair |
machine rm <id> | Remove a machine |
| Permissions | |
permissions create --name <n> | Create permission (wildcard default) |
permissions ls | List machine permissions |
permissions ls -a | List all account permissions |
permissions rm <id> | Revoke a permission |
permissions suspend <id> | Suspend a permission |
| Delegates | |
delegates add <permId> | Create a delegate identity |
delegates ls | List delegate-enabled permissions |
delegates rm <id> | Revoke a delegate |
| Connections | |
connections ls | List connections |
connections rm <provider> | Remove a connection |
| Curl & History | |
curl <url> | Proxied API call |
history [query] | Browse curl history |
history save <id> --name <n> | Save history as template |
history clear | Clear history |
| Templates | |
templates ls [query] | List templates |
templates show <id> | Show template details |
templates rm <id> | Delete a template |
| Export | |
export <id> --nodejs | Export as Node.js |
export <id> --ts | Export as TypeScript |
export <id> --python | Export as Python |
export <id> --go | Export as Go |
export <id> --curl | Export as curl |
| Identity | |
whoami | Show current identity |
npm run build
node dist/index.js --help
FAQs
CLI for Keychains.dev — register machines, manage permissions, and make proxied API calls
The npm package keychains receives a total of 0 weekly downloads. As such, keychains popularity was classified as not popular.
We found that keychains demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Two compromised GitHub Actions were re-enabled with malicious tags intact, exposing thousands of downstream repositories to Mini Shai-Hulud.

Research
/Security News
A malicious Firefox extension fetches its payload after installation to evade detection, steal Google session cookies, and automate account takeover.

Research
/Security News
The compromise affects MemTensor's MemOS, an open source memory framework for large language models (LLMs) and AI agents. Both npm package @memtensor/memos-cloud-openclaw-plugin and the PyPI package MemoryOS are compromised. They drop cross-platform Go binaries that exfiltrate developer secrets.