lucifer-gate
v0.5.3
Published
AI agent command firewall with Telegram-based human approval
Maintainers
Readme
Lucifer Gate
AI agent command firewall with Telegram-based human approval.
Lucifer sits between your AI agent and the shell. It authenticates callers via API keys, matches commands against a policy file, and gates unknown commands through Telegram for human approval. Approved commands build up a permission library over time. Think "sudo via Telegram."
Quick start (2 minutes, no Telegram needed)
# Generate config files + API key
npx lucifer-gate --init .
# Start in dev mode (auto-approves everything, no Telegram)
LUCIFER_TELEGRAM_TOKEN=skip npx lucifer-gate --config ./config/lucifer.json --auto-approve
# In another terminal, test it:
curl -X POST http://localhost:3001/api/v1/execute \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_KEY_FROM_INIT" \
-d '{"command":"echo hello"}'How it works
- Caller sends
POST /api/v1/executewith API key + command - Lucifer authenticates the key and checks IP allowlist
- Command is matched against
config/command-rules.json:always_approve-> execute immediatelyalways_deny-> reject (403)manual_approve-> check SQLite for cached approval, or send to Telegram
- If Telegram approval needed, human sees the command with risk warnings and taps a button
- Approved commands execute and results are returned to the caller
Config files
Generated by --init, hand-editable:
config/lucifer.json - Server settings (port, timeouts, limits, log file path)
config/api-keys.json - API key definitions (hashed, with optional IP allowlists)
config/command-rules.json - Command policy:
{
"rules": [
{ "prefix": "echo ", "action": "always_approve" },
{ "prefix": "git pull", "action": "manual_approve" },
{ "prefix": "rm ", "action": "always_deny" }
],
"defaultAction": "always_deny"
}Rules matched top-to-bottom, first match wins.
Production setup (with Telegram)
- Create a Telegram bot via @BotFather and get the token
- Send any message to your bot from the chat you want to use for approvals
- Run the pairing command:
LUCIFER_TELEGRAM_TOKEN=your_bot_token npx lucifer-gate pair --config ./config/lucifer.jsonThe pairing wizard will:
- Validate your bot token
- List all chats that have messaged the bot (most recent first)
- Let you pick which chat to use for approvals
- Send a 6-digit verification code to that chat
- Ask you to enter the code to prove you can read the chat
- Save the chat ID to
config/lucifer.json
- Start the server (no
LUCIFER_TELEGRAM_CHAT_IDenv var needed — it's in the config):
LUCIFER_TELEGRAM_TOKEN=your_bot_token npx lucifer-gate --config ./config/lucifer.jsonManual alternative: You can still set
LUCIFER_TELEGRAM_CHAT_IDas an environment variable instead of usingpair. The env var takes precedence over the config file value.
When a manual_approve command arrives, you'll see an inline keyboard in Telegram:
Command Request from key-name (192.168.1.5)
git push origin main
[Exact 2h] [Exact 8h] [Exact forever]
["git push" 2h] ["git push" 8h]
[Deny]API
POST /api/v1/execute
Execute a command. The endpoint is synchronous: it blocks until the command reaches a terminal state (approved + executed, denied, or approval timed out) and returns the full result in a single response.
curl -X POST http://localhost:3001/api/v1/execute \
-H "Content-Type: application/json" \
-H "x-api-key: luc_yourkey" \
-d '{"command":"git status"}'Success response:
{ "requestId": "uuid", "status": "completed", "exitCode": 0, "stdout": "...", "stderr": "", "durationMs": 42 }Status values observable on success/failure paths: completed, failed,
denied, timed_out.
Error responses
All errors return:
{ "code": "ERROR_CODE", "message": "Human readable", "retryable": true }Codes: MISSING_API_KEY, INVALID_API_KEY, IP_NOT_ALLOWED, RATE_LIMITED,
COMMAND_DENIED, COMMAND_TOO_LONG, INVALID_CWD, DENIED,
DUPLICATE_IN_FLIGHT (an identical command from this API key is already
awaiting approval — retry after it settles), APPROVAL_TIMEOUT,
APPROVAL_ERROR.
CLI
lucifer-gate --init [dir] Generate starter config + API key
lucifer-gate pair [--config <path>] Pair a Telegram chat for approvals
lucifer-gate --config <path> Start server with config
lucifer-gate --auto-approve Dev mode (no Telegram)
lucifer-gate log [--limit N] Query audit log
lucifer-gate stats Show approval statisticsDocker
docker build -t lucifer-gate .
docker run -p 3001:3001 \
-e LUCIFER_TELEGRAM_TOKEN=your_token \
-e LUCIFER_TELEGRAM_CHAT_ID=your_chat_id \
-v ./config:/app/config \
-v ./data:/app/data \
lucifer-gateLogging
Lucifer logs to both console and file by default.
Console output is human-readable (colorized, formatted) when pino-pretty is available, and falls back to structured JSON otherwise. pino-pretty is included when you clone the repo and run npm install, but is not bundled with the published npm package — so npx lucifer-gate uses JSON console output. This is expected and fully functional.
File output is always structured JSON (one object per line), written to data/lucifer.log by default. Each line is a complete JSON object, making it easy to search, filter, and feed into log aggregators.
File logging is enabled via config/lucifer.json:
{
"logFile": "lucifer.log",
...
}The logFile path is relative to dataDir (default: data/lucifer.log). Remove the key to disable file logging. The --init command generates the config with file logging enabled.
Log levels (controlled via LOG_LEVEL env var):
debug— verbose output (default in development)info— normal operations (default in production, whenNODE_ENV=production)warn— potential issueserror— failures only
Example:
LOG_LEVEL=info LUCIFER_TELEGRAM_TOKEN=your_token npx lucifer-gate --config ./config/lucifer.jsonEnvironment variables
| Variable | Required | Description |
|---|---|---|
| LUCIFER_TELEGRAM_TOKEN | Yes (prod) | Telegram bot token from @BotFather |
| LUCIFER_TELEGRAM_CHAT_ID | No | Telegram chat ID (use pair command instead, or set manually) |
| LUCIFER_ADMIN_SECRET | No | Bearer token for admin endpoints |
| PORT | No | Server port (default: 3001) |
| LOG_LEVEL | No | Log level: debug, info, warn, error (default: debug / info in production) |
| NODE_ENV | No | Set to production for production defaults (info log level, no pretty-printing) |
Architecture
server/src/domains/
command-gateway/ Core domain
types/ CommandRequest, Approval, types
config/ Gateway configuration
repository/ SQLite stores, JSON config readers
service/ Auth, rules, risk analysis, execution, approvals
api/ Execute routes + web approval UI routes
platform-api/ Health endpoint + server runtime wiringDependency flow: Types -> Config -> Repository -> Service -> Runtime -> UI/API
Stack
- Express 5 + TypeScript
- SQLite (better-sqlite3) for approvals + audit
- Telegraf for Telegram bot
- Optional server-delivered web approval UI with SSE updates
- Pino for structured logging (pino-pretty for human-readable console output in dev)
- Vitest for testing
