axis-asv
v0.9.0
Published
An agent secrets vault (ASV) for AI agents — credentials stay server-side, agents never see raw keys
Downloads
173
Maintainers
Readme
Axis
Your AI agent can read your .env file. Axis stops that.
Axis is the identity and credential layer for AI agents — an MCP-native broker where agents get scoped, time-limited permission to act, without ever seeing a raw API key. Think AWS IAM roles, but for LLM tool calls.
Agent (Claude Code / Cursor / Devin)
|
| execute_action({ service: "openai", action: "responses.create", ... })
v
Axis MCP Server ---- policy check ---- audit log
|
| injects stored API key server-side
v
OpenAI API ---- response ----> back to agent
The API key is never returned to the agent.Why Axis
AI agents are non-human identities. Every method of delivering credentials to them is broken:
| Method | Problem |
|--------|---------|
| Environment variables | echo $OPENAI_API_KEY works in any shell tool |
| .env files | cat .env readable by any file-access tool |
| System prompt injection | Key is in agent context; any log captures it |
| Manual proxies | HashiCorp Vault overhead for a side project |
Axis is the missing primitive: time-limited, action-scoped credential access without the agent ever seeing the key.
How Axis fits the NHI landscape
Enterprise NHI platforms (Oasis, CyberArk, Strata) govern identity at organizational scale — SSO, cross-cloud policy, compliance dashboards. Axis operates at the developer layer: local-first, single-machine, zero-config credential brokering for individual developers and small teams. They're complementary, not competing. Axis is the tool you install today; enterprise NHI is what your security team evaluates next quarter.
Supported Services
| Service | Actions |
|---------|---------|
| OpenAI | responses.create |
| Anthropic | messages.create |
| GitHub | repos.get, issues.create, pulls.create, contents.read |
| Stripe | paymentIntents.create, customers.list |
| Slack | chat.postMessage, conversations.list |
| SendGrid | mail.send |
| Notion | pages.create, databases.query |
| Linear | issues.create |
| Twilio | messages.create |
| AWS S3 | s3.getObject, s3.putObject |
| GCP Cloud Storage | storage.getObject, storage.listObjects |
Quick Start
1. Install
npm install -g axis-asv2. Run the setup wizard
axis setupThe wizard walks you through:
- Creating config directories
- Setting your master password (stored in OS keychain)
- Encrypting your first API key
- Generating your MCP config
Total time: ~60 seconds.
3. Add MCP config to your editor
Copy the config the wizard outputs into your MCP host config file:
- Claude Code:
~/.claude.jsonor.mcp.json(project-level) - Cursor:
~/.cursor/mcp.json
4. Verify
axis doctorPrerequisites
- Node.js 20+
1. Initialize
axis init2. Store your first credential
axis add openaiYou'll be prompted for:
- Your master password — used to encrypt the key locally
- Your OpenAI API key (
sk-...)
3. Store master password in OS keychain
axis keychain setThis stores your master password in the OS keychain (macOS Keychain, Linux Secret Service, Windows Credential Manager) so Axis can start without a plaintext password in config.
4. Configure your MCP host
Claude Code — Add to ~/.claude.json (global) or .mcp.json (project-level):
{
"mcpServers": {
"axis": {
"command": "axis",
"args": ["mcp"],
"env": {
"AXIS_IDENTITY": "local-dev"
}
}
}
}Axis reads the master password from your OS keychain automatically. If you prefer to use an environment variable instead, add "AXIS_MASTER_PASSWORD": "your-password" to the env block above.
Cursor — Add to ~/.cursor/mcp.json or your project's .cursor/mcp.json:
{
"mcpServers": {
"axis": {
"command": "axis",
"args": ["mcp"],
"env": {
"AXIS_IDENTITY": "local-dev"
}
}
}
}5. Verify
axis doctorHow Your Agent Uses Axis
Axis exposes one MCP tool: execute_action. Your agent calls it like this:
{
"service": "openai",
"action": "responses.create",
"justification": "Summarising the uploaded document for the user",
"params": {
"model": "gpt-4o",
"input": "Summarise the following in 3 sentences: ..."
}
}Axis checks the policy, proxies the call, and returns the API response. The agent never sees the key.
Response (allowed)
{
"ok": true,
"result": { "output": [{ "content": "..." }] },
"request_id": "550e8400-..."
}Response (denied)
{
"denied": true,
"reason": "Policy denied: identity=\"local-dev\" service=\"stripe\" action=\"paymentIntents.create\"",
"request_id": "550e8400-..."
}Security Model
Local-first encryption
All secrets are encrypted on your machine with AES-256-GCM. Your master password never leaves your machine.
Your machine: plaintext secret + master password -> AES-256-GCM -> ciphertext
Keystore: stores ciphertext, salt, IV only — never the plaintext or password
MCP Server: decrypts locally -> proxies call -> returns responseDeny-by-default policy
Every request is denied unless an explicit rule allows it. Edit config/policy.yaml:
policies:
- identity: local-dev
allow:
- service: openai
actions:
- responses.create
# Wildcard example
- identity: ci-runner
allow:
- service: github
actions:
- "*"The identity comes from AXIS_IDENTITY in your MCP config. Policy files hot-reload on save.
Rate limiting
Limit how many requests an identity can make per minute. If the limit is exceeded, further requests are denied until the window resets.
policies:
- identity: ci-runner
rateLimit:
requestsPerMinute: 30
allow:
- service: openai
actions:
- responses.createTTL (time-to-live grants)
Restrict how frequently a credential can be used. After a successful call, further requests for the same service/action are denied until the TTL expires. Useful for expensive or destructive operations.
policies:
- identity: ci-runner
allow:
- service: stripe
actions:
- paymentIntents.create
ttl: 300 # credential access expires after 5 minutesAudit log
Every request — allowed or denied — is logged locally:
{
"timestamp": "2025-01-15T10:30:00.000Z",
"request_id": "550e8400-...",
"identity": "local-dev",
"service": "openai",
"action": "responses.create",
"decision": "allow",
"justification": "Summarising document for user",
"latency_ms": 342
}Secrets are never logged. View logs with axis logs.
Encryption
| Property | Value | |----------|-------| | Algorithm | AES-256-GCM | | Key derivation | PBKDF2-SHA-512, 210,000 iterations | | Salt | 32 bytes, unique per entry | | IV | 12 bytes, unique per entry | | Auth tag | 128 bits |
CLI Reference
Credentials
axis add <service> # Store an encrypted credential
axis list # List stored services (no secrets shown)
axis revoke <service> # Delete a stored credential
axis rotate <service> # Re-encrypt a credential under a new master passwordOperations
axis mcp # Start the MCP server
axis logs # View audit log (--tail to watch, --last N for count)
axis doctor # Health check: config, policy, crypto, keystore
axis init # Create config dirs and default policy.yamlKeychain
axis keychain set # Store master password in OS keychain
axis keychain delete # Remove from keychain
axis keychain status # Check if master password is in keychainLinux: Install libsecret first:
# Ubuntu/Debian
sudo apt install libsecret-1-dev
# Fedora
sudo dnf install libsecret-develDashboard
Axis includes a local web dashboard for monitoring and inspecting your vault.
axis dashboardOpens http://localhost:3847 in your browser. The dashboard shows:
- Health status — same checks as
axis doctor - Audit log — searchable, filterable, auto-refreshing
- Stored services — credential inventory (no secrets shown)
- Policy rules — current deny/allow configuration
The dashboard runs locally and binds to 127.0.0.1 only — it is never exposed to the network.
CI/CD Integration
Axis works in CI environments. Use --stdin to pipe secrets non-interactively:
echo "$OPENAI_API_KEY" | axis add openai --stdinSet AXIS_MASTER_PASSWORD as a CI secret. See docs/ci-guide.md for full GitHub Actions examples.
Threat Model
What Axis protects against
| Threat | How |
|--------|-----|
| Agent receives raw credential | Never in tool responses — proxy injects key server-side |
| Credential leaked in logs | Audit log records metadata only, never secrets |
| Credential stored in plaintext | AES-256-GCM at rest, unique salt+IV per entry |
| Unauthorized service access | Deny-by-default policy, identity-scoped rules |
| Wrong action on a service | Policy rules scoped to service + action pairs |
What Axis does not protect against
| Threat | Notes |
|--------|-------|
| Compromised local machine | An attacker with OS-level access + your master password can decrypt. Use full-disk encryption (FileVault, BitLocker, LUKS). |
| Malicious MCP host | The MCP host receives the API response. Axis assumes the host is trusted. |
| Memory scraping | Decrypted secrets pass through process memory briefly during proxying. |
| Policy misconfiguration | Broad wildcards ("*") grant wide access. Review policy rules. |
Development
npm run build # compile TypeScript -> dist/
npm run build:watch # watch mode
npm test # run test suite (no build needed)
npm run lint # type-check without emittingAdding a new service proxy
- Create
src/proxy/<service>.ts— seesrc/proxy/github.tsas the template - Export
validate<Action>Params(),sanitizeParams(), andproxy<Service>Action() - Register in the dispatch table in
src/proxy/openai.ts - Add tests in
src/tests/index.ts
License
MIT — axisproxy.com
