Janee API Security

by rsdouglas

Not rated
GitHub

About

MCP server that sits between AI agents and APIs. Agents request access, Janee makes the call with the real credentials, agents never see the secrets.

Details

Author
rsdouglas
Categories
Other, Security, API, Infrastructure

Setup

Install Janee API Security in your MCP client (Claude Desktop, Cursor, Windsurf, and others).

Repository: https://github.com/rsdouglas/janee

Follow the installation instructions in the repository README, then restart your MCP client.

Secrets management for AI agents via MCP

Your AI agents need API access to be useful. But they shouldn't have your raw API keys. Janee sits between your agents and your APIs β€” injecting credentials, enforcing policies, and logging everything.

AI agents need API access to be useful. The current approach is to give them your keys and hope they behave.

- πŸ”“ Agents have full access to Stripe, Gmail, databases
- πŸ“Š No audit trail of what was accessed or why
- 🚫 No kill switch when things go wrong
- πŸ’‰ One prompt injection away from disaster

Janee is anMCPserver that manages API secrets for AI agents:
- Store your API keysβ€” encrypted locally in~/.janee/
- Runjanee serveβ€” starts MCP server
- Agent requests accessβ€” viaexecuteMCP tool
- Janee injects the real keyβ€” agent never sees it
- Everything is loggedβ€” full audit trail

Your keys stay on your machine. Agents never see them. You stay in control.

services: stripe: baseUrl: https://api.stripe.com auth: { type: bearer, key: sk_live_xxx } github: baseUrl: https://api.github.com auth: { type: bearer, key: ghp_xxx } openai: baseUrl: https://api.openai.com auth: { type: bearer, key: sk-xxx }

Nowevery agentthat connects to Janee can use them:

- Claude Desktopβ€” access your APIs
- Cursorβ€” access your APIs
- OpenClawβ€” access your APIs
- Any MCP clientβ€” access your APIs

No more copying keys between tools. No more "which agent has which API configured?" Add a new agent? It already has access to everything. Revoke a key? Update it once in Janee.

One config. Every agent. Full audit trail.

This creates~/.janee/config.yamlwith example services.

Option 1: Interactive (recommended for first-time users)

Janee will guide you through adding a service:

Service name: stripe Base URL: https://api.stripe.com Auth type: bearer API key: sk_live_xxx βœ“ Added service "stripe" Create a capability for this service? (Y/n): y Capability name (default: stripe): TTL (e.g., 1h, 30m): 1h Auto-approve? (Y/n): y βœ“ Added capability "stripe" Done! Run 'janee serve' to start.

Using an AI agent?SeeNon-interactive Setupfor flags that skip prompts, or theagent-specific guidesbelow.

services: stripe: baseUrl: https://api.stripe.com auth: type: bearer key: sk_live_xxx capabilities: stripe: service: stripe ttl: 1h autoApprove: true

Some tools need credentials as environment variables, not HTTP headers. Exec mode handles this:

janee add twitter --exec \ --key "tvly-xxx" \ --allow-commands "bird,tweet-cli" \ --env-map "TWITTER_API_KEY={{credential}}"

Now agents can run CLI tools through Janee without ever seeing the API key:

// Agent calls janee_exec tool janee_exec({ capability: "twitter", command: ["bird", "post", "Hello world!"], cwd: "/home/agent/project", // optional working directory reason: "User asked to post a tweet" })

Janee spawns the process withTWITTER_API_KEYinjected, runs the command, and returns stdout/stderr. The credential never enters the agent's context.

- --execβ€” configure as exec-mode (CLI wrapper instead of HTTP proxy)
- --allow-commandsβ€” whitelist of allowed executables (security)
- --env-mapβ€” map credentials to environment variables
- --work-dirβ€” working directory for the subprocess
- --timeoutβ€” max execution time (default: 30s)

When using exec mode with GitHub credentials, Janee automatically handles git authentication. No extra configuration needed β€”git push,git pull, andgit clonejust work:

capabilities: - name: git-ops service: github mode: exec allowCommands: [git] env: GH_TOKEN: "{{credential}}"
// Agent can push code without ever seeing the token janee_exec({ capability: "git-ops", command: ["git", "push", "origin", "main"], cwd: "/workspace/my-repo" })

Janee detectsgitcommands withGH_TOKEN/GITHUB_TOKENin the environment and creates a temporary askpass script for HTTPS authentication. The script is cleaned up automatically after the command completes.

Add GitHub App auth (for autonomous agents)

Static tokens are risky for long-running agents. GitHub App auth generates short-lived installation tokens on demand β€” no long-lived PATs required.

Option 1: Use create-gh-app (recommended)

npx @true-and-useful/create-gh-app create my-agent --owner @me # Opens browser β†’ creates app β†’ saves credentials locally # Install the app on your repos # https://github.com/apps/my-agent/installations/new # Register with Janee in one command npx @true-and-useful/create-gh-app janee-add my-agent

Done. Your agent now gets short-lived GitHub tokens through Janee's MCP proxy.

janee add github-app \ --auth-type github-app \ --app-id 123456 \ --pem-file /path/to/private-key.pem \ --installation-id 789
services: github: baseUrl: https://api.github.com auth: type: github-app appId: "123456" pemFile: /path/to/private-key.pem installationId: "789"

How it works:When an agent requests access, Janee signs a JWT with the app's private key, exchanges it for a 1-hour installation token via GitHub's API, and caches the token until expiry. The agent never sees the private key β€” only the short-lived token reaches the API.

Agents that support MCP (Claude Desktop, Cursor, OpenClaw) can now call theexecutetool to make API requests through Janee:

// Agent calls the execute tool execute({ capability: "stripe", method: "GET", path: "/v1/balance", reason: "User asked for account balance" })

Janee decrypts the key, makes the request, logs everything, and returns the response.

- OpenClawβ€” Native plugin (@true-and-useful/janee-openclaw)

- Containerized agents?SeeContainer setup guide

If you're usingOpenClaw, install the plugin for native tool support:

npm install -g @true-and-useful/janee janee init # Edit ~/.janee/config.yaml with your services # Install the OpenClaw plugin openclaw plugins install @true-and-useful/janee-openclaw
{ agents: { list: [{ id: "main", tools: { allow: ["janee"] } }] } }

- janee_list_servicesβ€” Discover available APIs
- janee_executeβ€” Make API requests through Janee

The plugin spawnsjanee serveautomatically. All requests are logged to~/.janee/logs/.

Agents discover what's available, then call APIs through Janee. Same audit trail, same protection.

server: host: localhost services: stripe: baseUrl: https://api.stripe.com auth: type: bearer key: sk_live_xxx # encrypted at rest github: baseUrl: https://api.github.com auth: type: bearer key: ghp_xxx capabilities: stripe: service: stripe ttl: 1h autoApprove: true stripe_sensitive: service: stripe ttl: 5m requiresReason: true

Services= Real APIs with real keys
Capabilities= What agents can request, with policies

Janee computes OAuth 1.0a signatures (HMAC-SHA1) server-side, so your 4 Twitter secrets stay encrypted at rest and never enter the agent's context:

services: twitter: baseUrl: https://api.x.com auth: type: oauth1a-twitter consumerKey: xxx # encrypted at rest consumerSecret: xxx # encrypted at rest accessToken: xxx # encrypted at rest accessTokenSecret: xxx # encrypted at rest capabilities: twitter: service: twitter ttl: 1h autoApprove: true

Janee computes AWS Signature V4 (HMAC-SHA256) per-request, keeping your access keys encrypted at rest. Non-secret fields (region,awsService) stay in plain config:

services: aws-ses: baseUrl: https://email.us-east-1.amazonaws.com auth: type: aws-sigv4 accessKeyId: AKIA... # encrypted at rest secretAccessKey: xxx # encrypted at rest region: us-east-1 awsService: ses capabilities: aws-ses: service: aws-ses ttl: 1h autoApprove: true

Built-in templates for common AWS services:

janee add aws-ses # Amazon SES janee add aws-s3 # Amazon S3

Control which agents can use which capabilities:

server: host: localhost defaultAccess: restricted # capabilities require explicit allowlist capabilities: stripe: service: stripe ttl: 1h allowedAgents: ["agent-a", "agent-b"] # only these agents can use it github: service: github ttl: 1h # no allowedAgents + defaultAccess: restricted β†’ no agent can use this

- defaultAccess: restrictedβ€” capabilities without anallowedAgentslist are hidden from all agents
- defaultAccess: open(default) β€” capabilities without anallowedAgentslist are available to all agents
- allowedAgentsβ€” per-capability list of agent names (matched againstclientInfo.namefrom the MCP initialize handshake)

Credentials created by agents at runtime default toagent-onlyaccess β€” only the creating agent can use them unless it explicitly grants access via themanage_credentialtool.

services: twitter: auth: type: bearer key: tvly-xxx capabilities: twitter: service: twitter mode: exec allowCommands: ["bird", "tweet-cli"] envMap: TWITTER_API_KEY: "{{credential}}" ttl: 1h autoApprove: true

Exec-mode capabilities usejanee_execinstead ofexecute. The credential is injected as an environment variable β€” the agent sees only stdout/stderr.

- isolated minimal environment (no full host env inheritance)
- temporaryHOMEper command
- timeout kills the process group

When agents run inside Docker containers,janee_execon a remote host cannot access the container filesystem. The Runner/Authority architecture solves this:

- Authorityruns on the host: holds credentials, enforces policy, proxies API requests
- Runnerruns inside each container: serves MCP to the agent, forwards non-exec calls to the Authority, runsjanee_execlocally

# Host: start Authority (MCP + exec authorization on one port) janee serve -t http -p 3100 --host 0.0.0.0 --runner-key "$JANEE_RUNNER_KEY" # Container: start Runner (agent talks to this) janee serve -t http -p 3200 --host 127.0.0.1 \ --authority http://host.docker.internal:3100 --runner-key "$JANEE_RUNNER_KEY"

The agent only needsJANEE_URL=http://localhost:3200.

You can also run the Authority as a standalone process:

janee authority --runner-key "$JANEE_RUNNER_KEY" --host 127.0.0.1 --port 9120

See theRunner/Authority guidefor the full architecture, exec authorization flow, Docker Compose example, and troubleshooting.

Control exactly what requests each capability can make usingrules:

capabilities: stripe_readonly: service: stripe ttl: 1h rules: allow: - GET  deny: - POST  - PUT  - DELETE  stripe_billing: service: stripe ttl: 15m requiresReason: true rules: allow: - GET  - POST /v1/refunds/ - POST /v1/invoices/ deny: - POST /v1/charges/ # Can't charge cards - DELETE 

- denypatterns are checked firstβ€” explicit deny always wins
- Thenallowpatterns are checkedβ€” must match to proceed
- No rules defined→ allow all (backward compatible)
- Rules defined but no match→ denied by default

- GET β†’ any GET request
- POST /v1/charges/β†’ POST to /v1/charges/ and subpaths
-
/v1/customers→ any method to /v1/customers
- DELETE /v1/customers/β†’ DELETE any customer

This makes security real:Even if an agent lies about its "reason", it can only access the endpoints the policy allows. Enforcement happens server-side.

janee init # Set up ~/.janee/ with example config janee add # Add a service (interactive) janee add stripe -u https://api.stripe.com -k sk_xxx # Add with args janee remove <service> # Remove a service janee remove <service> --yes # Remove without confirmation janee list # List configured services janee list --json # Output as JSON (for integrations) janee search [query] # Search service directory janee search stripe --json # Search with JSON output janee cap list # List capabilities janee cap list --json # List capabilities as JSON janee cap add <name> --service <service> # Add capability janee cap edit <name> # Edit capability janee cap remove <name> # Remove capability janee serve # Start MCP server (stdio, default) janee serve --transport http --port 9100 # Start with HTTP transport (for containers) janee serve --authority https://janee.example.com --runner-key $JANEE_RUNNER_KEY # Runner mode janee authority --runner-key $JANEE_RUNNER_KEY # Start authority API janee logs # View audit log janee logs -f # Tail audit log janee logs --json # Output as JSON janee sessions # List active sessions janee sessions --json # Output as JSON janee revoke <id> # Kill a session

AI agents can't respond to interactive prompts. Use---from-envflags to read credentials from environment variables β€” this keeps secrets out of the agent's context window:

# Bearer auth (Stripe, OpenAI, etc.) janee add stripe -u https://api.stripe.com --auth-type bearer --key-from-env STRIPE_KEY # HMAC auth (Bybit) janee add bybit --auth-type hmac-bybit --key-from-env BYBIT_KEY --secret-from-env BYBIT_SECRET # HMAC auth with passphrase (OKX) janee add okx --auth-type hmac-okx --key-from-env OKX_KEY --secret-from-env OKX_SECRET --passphrase-from-env OKX_PASS # GitHub App auth (short-lived tokens) janee add github --auth-type github-app --app-id-from-env GH_APP_ID --pem-from-env GH_PEM --installation-id-from-env GH_INSTALL_ID # Twitter/X OAuth 1.0a (per-request signing) janee add twitter --consumer-key $TWITTER_CONSUMER_KEY --consumer-secret $TWITTER_CONSUMER_SECRET \ --access-token $TWITTER_ACCESS_TOKEN --access-token-secret $TWITTER_ACCESS_TOKEN_SECRET # AWS SigV4 (SES, S3, etc.) janee add aws-ses --access-key-id $AWS_ACCESS_KEY_ID --secret-access-key $AWS_SECRET_ACCESS_KEY \ --region us-east-1 --aws-service ses

When all required credentials are provided via flags, Janee:

- Never opens readline (no hanging on stdin)
- Auto-creates a capability with sensible defaults (1h TTL, auto-approve)

You can also edit~/.janee/config.yamldirectly if you prefer.

No reviews yet β€” be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.