{"id":50069619,"url":"https://github.com/davideuler/cortex-auth","last_synced_at":"2026-05-22T02:18:45.060Z","repository":{"id":353255865,"uuid":"1218239305","full_name":"davideuler/cortex-auth","owner":"davideuler","description":"Cortext Auth: Manage secrets, keys, configurations for AI Agent, let AGENT be autonomous.","archived":false,"fork":false,"pushed_at":"2026-04-23T05:05:31.000Z","size":101,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-23T06:23:59.534Z","etag":null,"topics":["ai","ai-agents","autonomous-agents","environment-variables","secrets-management","security"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davideuler.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-22T17:13:35.000Z","updated_at":"2026-04-23T05:05:19.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/davideuler/cortex-auth","commit_stats":null,"previous_names":["davideuler/cortex-auth"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/davideuler/cortex-auth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davideuler%2Fcortex-auth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davideuler%2Fcortex-auth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davideuler%2Fcortex-auth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davideuler%2Fcortex-auth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davideuler","download_url":"https://codeload.github.com/davideuler/cortex-auth/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davideuler%2Fcortex-auth/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33325234,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-21T12:23:38.849Z","status":"online","status_checked_at":"2026-05-22T02:00:06.671Z","response_time":265,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","ai-agents","autonomous-agents","environment-variables","secrets-management","security"],"created_at":"2026-05-22T02:18:44.339Z","updated_at":"2026-05-22T02:18:45.052Z","avatar_url":"https://github.com/davideuler.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CortexAuth — Agent-Centric Secrets \u0026 Configuration Service\n\n[中文文档](README.zh-CN.md)\n\nA lightweight, Rust-based secrets vault designed for AI agents and automated pipelines. Store API keys and configuration securely, discover which secrets your project needs, and inject them at runtime — without ever hardcoding secrets in source code.\n\n## Architecture\n\n```\n                      ┌──────────────────────────────────────┐\n                      │           cortex-server              │\n         admin API    │  · KEK in mlock'd memory (operator)  │\n  Admin ─────────────►│  · per-row DEKs wrapped by KEK       │\n  (curl / API)        │  · authenticates agents  (JWT)       │\n                      │  · issues project tokens             │\n                      └───────────────┬──────────────────────┘\n                                      │  ② project_token\n                                      │  ③ env vars\n                                      │\n  ┌───────────────────────────────────┼────────────────────────┐\n  │                Agent              │                        │\n  │         (autonomous pipeline)     │                        │\n  │                                   ▼                        │\n  │  ① cortex-cli daemon login ┌────────────────────┐         │\n  │  ──────────────────────────►   cortex-daemon    │         │\n  │                            │  · holds project   │         │\n  │  ④ cortex-cli run ─socket─►│    tokens          │         │\n  │                            │  · attests binary  │         │\n  │                            │  · spawns child    │         │\n  └────────────────────────────┴───────┬────────────┘─────────┘\n                                      │\n                                 exec() with env vars injected\n                                      │\n                                      ▼\n                            ┌─────────────────────┐\n                            │   Project Process   │\n                            │  python main.py     │\n                            │  node app.js  …     │\n                            │                     │\n                            │  OPENAI_API_KEY=...  │\n                            │  DB_PASSWORD=...    │\n                            │  AUTH_TOKEN=...     │\n                            └─────────────────────┘\n```\n\n**Flow:**\n1. **Admin** pre-loads project secrets into `cortex-server` via the admin API\n2. **Operator** runs `cortex-cli daemon login` once on each agent host (OAuth 2.0\n   device-grant) and starts `cortex-daemon`. The daemon keeps the agent's Ed25519\n   private key in mlock'd memory and registers an ephemeral attestation public\n   key at `/daemon/attest` (binary SHA-256 must be on the allowlist).\n3. **Daemon** signs `/agent/discover` with the agent's Ed25519 key on demand;\n   the first request for a new `(agent_id, project)` pair waits in `pending_grants`\n   until an admin approves it on the dashboard, after which auto-approval runs\n   for 30 days as long as the requested env-key set is a subset.\n4. **Agent** calls `cortex-cli run --project \u003cname\u003e --url \u003cserver\u003e -- \u003ccmd\u003e`,\n   which sends one line of JSON to `~/.cortex/agent.sock`. The daemon fetches\n   secrets, spawns the child with them injected, and returns the exit code —\n   neither the project token nor the secret values traverse the socket.\n\n## Agent Key Management Principles\n\n- **Agents never touch secret values** — secrets flow directly from `cortex-server` into the process environment via `exec()`; agent code never reads or stores them\n- **No human intervention per task** — agents autonomously obtain and inject secrets across any number of projects and tasks without requiring manual input for each run (except first-time project secrets access approval)\n- **Fully autonomous secret injection** — unattended agent pipelines retrieve all required credentials on demand at runtime; no operator in the loop\n- **Secrets never written to disk** — API keys, database credentials, tokens, and passwords exist only in process memory as environment variables; nothing is persisted to files\n- **Daemon mode is the only path** — `cortex-cli run` no longer accepts\n  `--token`/`--agent-id`/`--priv-key-file`. `cortex-daemon` (#16) holds the\n  agent's Ed25519 private key + project tokens in mlock'd memory, exposes\n  `~/.cortex/agent.sock` (mode 0600, SO_PEERCRED-gated), and proves its\n  identity to the server with an ephemeral Ed25519 attestation key (#17).\n  Peers can request `run` operations but cannot extract raw secret material.\n\n## Installation\n\n### Homebrew (macOS Apple Silicon)\n\n```bash\nbrew tap davideuler/cortex-auth\nbrew install cortex-auth\n```\n\n\u003e **Note:** The Homebrew tap provides pre-built binaries for **Apple Silicon (M1/M2/M3) only**.\n\u003e macOS Intel users should [build from source](#build-from-source).\n\n### Pre-built binaries (Linux / macOS Apple Silicon)\n\nDownload from the [GitHub Releases](https://github.com/davideuler/cortex-auth/releases) page.\n\n```bash\nVERSION=v0.1.2\n\n# Detect platform\ncase \"$(uname -s)-$(uname -m)\" in\n  Darwin-arm64)  TARGET=aarch64-apple-darwin ;;\n  Linux-x86_64)  TARGET=x86_64-unknown-linux-musl ;;\n  Linux-aarch64) TARGET=aarch64-unknown-linux-musl ;;\n  *) echo \"No pre-built binary for this platform — see Build from source below\"; exit 1 ;;\nesac\n\nARCHIVE=\"cortex-auth-${VERSION}-${TARGET}\"\ncurl -fLO \"https://github.com/davideuler/cortex-auth/releases/download/${VERSION}/${ARCHIVE}.tar.gz\"\ntar xzf \"${ARCHIVE}.tar.gz\"\nsudo mv \"${ARCHIVE}/cortex-server\" \"${ARCHIVE}/cortex-cli\" /usr/local/bin/\nrm -rf \"${ARCHIVE}\" \"${ARCHIVE}.tar.gz\"\n```\n\n| Platform | Pre-built binary |\n|----------|-----------------|\n| macOS Apple Silicon (M1/M2/M3) | `cortex-auth-v0.1.2-aarch64-apple-darwin.tar.gz` |\n| macOS Intel | — build from source |\n| Linux x86_64 | `cortex-auth-v0.1.2-x86_64-unknown-linux-musl.tar.gz` |\n| Linux ARM64 | `cortex-auth-v0.1.2-aarch64-unknown-linux-musl.tar.gz` |\n\n### Build from source\n\nRequires [Rust](https://rustup.rs) (stable).\n\n```bash\ngit clone https://github.com/davideuler/cortex-auth.git\ncd cortex-auth\ncargo build --release\n# Binaries at: target/release/cortex-server  target/release/cortex-cli\n```\n\n## Quick Start\n\n```bash\n# Generate the admin token (the KEK is derived from an operator passphrase you type at startup)\nADMIN_TOKEN=$(openssl rand -hex 16)\n\n# Start the server. It boots SEALED and prompts for the KEK operator password\n# on stdin. After the password unwraps the on-disk sentinel the server\n# transitions to UNSEALED and starts listening on :3000.\nDATABASE_URL=sqlite://cortex-auth.db \\\nADMIN_TOKEN=$ADMIN_TOKEN \\\ncortex-server\n# [cortex-server SEALED] Enter KEK operator password: ********\n\n# (Headless deployments — supply the password via env var instead of stdin.)\n# CORTEX_KEK_PASSWORD='strong-passphrase' cortex-server\n\n# In another terminal — add a secret\ncurl -X POST http://localhost:3000/admin/secrets \\\n  -H \"Content-Type: application/json\" \\\n  -H \"X-Admin-Token: $ADMIN_TOKEN\" \\\n  -d '{\"key_path\":\"openai_api_key\",\"secret_type\":\"KEY_VALUE\",\"value\":\"sk-your-key\"}'\n\n# Discover project secrets (authenticate with agent_id + Ed25519 signature).\n# `cortex-cli sign-proof` reads ~/.cortex/agent-\u003cid\u003e.key created by\n# `cortex-cli gen-key` and prints {\"ts\",\"nonce\",\"auth_proof\"}.\n# One-time on the agent host: register the daemon via OAuth 2.0 device-grant.\ncortex-cli daemon login --url http://localhost:3000\n# Visit the printed dashboard URL and approve the user_code.\n\n# Start the daemon. It generates an ephemeral attestation key, registers\n# its binary SHA-256 with /daemon/attest, and listens on\n# ~/.cortex/agent.sock with SO_PEERCRED + PR_SET_DUMPABLE + mlockall.\ncortex-daemon \u0026\n\n# Launch your app — no token, no agent_id, no priv-key-file. The daemon\n# discovers, rotates, and injects everything transparently. The first\n# discover for a new (agent_id, project) pair is gated by an admin\n# approval flow (see \"Pending Grants\" in docs/USAGE.md).\ncortex-cli run \\\n  --project my-app --url http://localhost:3000 \\\n  -- python3 main.py\n```\n\n## Components\n\n| Component | Description |\n|-----------|-------------|\n| `cortex-server` | HTTP API server (axum + SQLite). Stores secrets encrypted with AES-256-GCM. |\n| `cortex-cli` | CLI launcher that fetches secrets and `exec()`s your process with them injected as env vars. |\n\n## Agent Skills Integration\n\nThe `cortex-skills/` directory contains a ready-to-use skill following the\n[Agent Skills open standard](https://developers.openai.com/codex/skills) — the same\n`SKILL.md` format works across all major agent frameworks. Once installed, your agent\nautonomously authenticates with Cortex and injects secrets without any human prompting.\n\n| Agent | Skills directory | Docs |\n|-------|-----------------|------|\n| [Claude Code](https://code.claude.com/docs/en/skills) | `~/.claude/skills/` (global) · `.claude/skills/` (project) | [Extend Claude with skills](https://code.claude.com/docs/en/skills) |\n| [Codex CLI](https://developers.openai.com/codex/skills) | `~/.codex/skills/` (global) · `.agents/skills/` (project) | [Agent Skills – Codex](https://developers.openai.com/codex/skills) |\n| [OpenCode](https://opencode.ai/docs/skills/) | `~/.opencode/skills/` (global) · `.opencode/skills/` (project) | [Agent Skills · OpenCode](https://opencode.ai/docs/skills/) |\n| [OpenClaw](https://docs.openclaw.ai/tools/skills) | `~/.openclaw/skills/` (global) · `skills/` (workspace) | [Skills – OpenClaw](https://docs.openclaw.ai/tools/skills) |\n| [Hermes Agent](https://hermes-agent.nousresearch.com/docs/user-guide/features/skills) | `~/.hermes/skills/` (local) · `~/.agents/skills/` (shared) | [Skills System · Hermes](https://hermes-agent.nousresearch.com/docs/user-guide/features/skills) |\n\n```bash\n# 1. Clone (or use your existing copy of) cortex-auth\ngit clone https://github.com/davideuler/cortex-auth.git /tmp/cortex-auth\n\n# 2. Install the skill for your agent — pick one:\n\n# Claude Code (global)\ncp -r /tmp/cortex-auth/cortex-skills ~/.claude/skills/cortex-secrets\n\n# Codex CLI (global)\ncp -r /tmp/cortex-auth/cortex-skills ~/.codex/skills/cortex-secrets\n\n# OpenCode (global)\ncp -r /tmp/cortex-auth/cortex-skills ~/.opencode/skills/cortex-secrets\n\n# OpenClaw (global)\ncp -r /tmp/cortex-auth/cortex-skills ~/.openclaw/skills/cortex-secrets\n\n# Hermes Agent (local)\ncp -r /tmp/cortex-auth/cortex-skills ~/.hermes/skills/cortex-secrets\n```\n\nTo keep the skill in sync with future cortex-auth updates, use symlinks instead of copying:\n```bash\nln -sf /tmp/cortex-auth/cortex-skills ~/.claude/skills/cortex-secrets\n```\n\nFor project-scoped installation (committed alongside your code), copy into the\nagent-specific project directory (e.g. `.claude/skills/cortex-secrets/`).\n\n## Documentation\n\n- [Design \u0026 Architecture](docs/DESIGN.md) — System design, security model, data flow\n- [Usage Guide](docs/USAGE.md) — Admin API examples, cortex-cli usage, production setup\n- [Open Questions](docs/UNCERTAINTIES.md) — Items needing stakeholder decisions\n- [Roadmap](docs/NEXT_STEPS.md) — Security hardening, features, optimizations\n\n## Development\n\n```bash\n# Run all tests\ncargo test --workspace\n\n# Check for lint issues\ncargo clippy --workspace -- -D warnings\n\n# Build release binaries\ncargo build --release\n```\n\n## Security Model\n\n### Envelope Encryption with Operator-Held KEK\n\nCortexAuth uses a two-tier key hierarchy. The **KEK** (Key Encryption Key) lives only in the\noperator's head and in process memory; the DB never stores it. Every secret is encrypted with\nits own random **DEK** (Data Encryption Key); the DEK is then wrapped with the KEK and stored\nbeside the ciphertext. The plaintext DEK is zeroized as soon as the row is written.\n\n#### Server boot — SEALED → UNSEALED\n\n```\n1. cortex-server starts in SEALED state — no listener yet\n2. Operator types the KEK password (stdin) or supplies CORTEX_KEK_PASSWORD\n3. Server derives KEK = Argon2id(password, salt_from_DB) and mlocks it\n4. Server reads the sentinel ciphertext, decrypts it with KEK, compares to\n   the known plaintext → proves the password matches the prior KEK\n5. Server transitions to UNSEALED and binds :3000\n```\n\nA wrong password fails the sentinel check and the process exits without ever opening the listener.\nOn first boot the sentinel is generated and stored automatically.\n\n#### Write path (admin adds a secret)\n\n```\nplaintext = \"sk-abc123...\"\n\nstep 1  DEK         = random_bytes(32)\nstep 2  ciphertext  = AES-256-GCM(DEK, nonce_d, plaintext)\nstep 3  wrapped_DEK = AES-256-GCM(KEK, nonce_k, DEK)\nstep 4  INSERT INTO secrets(ciphertext, wrapped_DEK, kek_version, ...)\nstep 5  zeroize(DEK, plaintext)\n```\n\n#### Read path (agent fetches a secret)\n\n```\nstep 1  SELECT ciphertext, wrapped_DEK\nstep 2  DEK       = AES-256-GCM-Decrypt(KEK, nonce_k, wrapped_DEK)\nstep 3  plaintext = AES-256-GCM-Decrypt(DEK, nonce_d, ciphertext)\nstep 4  return plaintext; zeroize intermediate DEK copies\n```\n\nCompromise of the DB alone does not leak any secret — the wrapped DEKs are useless without the KEK,\nwhich is only ever in the running server's memory.\n\n### Namespaces\n\nNamespaces partition secrets, agents, projects, and configs. An agent registered in namespace\n`prod` only sees secrets in `prod`; the same agent ID in `staging` sees a different set. Manage\nnamespaces from the dashboard (`Namespaces` tab) or the admin API:\n\n```bash\ncurl -X POST http://localhost:3000/admin/namespaces \\\n  -H \"X-Admin-Token: $ADMIN_TOKEN\" -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"staging\",\"description\":\"Pre-prod environment\"}'\n\n# Tag a secret/agent at create-time:\ncurl -X POST http://localhost:3000/admin/secrets \\\n  -H \"X-Admin-Token: $ADMIN_TOKEN\" -H \"Content-Type: application/json\" \\\n  -d '{\"key_path\":\"openai_api_key\",\"secret_type\":\"KEY_VALUE\",\"value\":\"sk-...\",\"namespace\":\"staging\"}'\n```\n\nThe `default` namespace is created automatically and cannot be deleted. A namespace that still\nowns secrets/agents/projects refuses deletion.\n\n### Scoped project tokens\n\nEach `project_token` carries an explicit **scope** — the set of `key_path`s the\ncaller is allowed to read. The scope is computed from the `.env` file the\nagent submits to `/agent/discover` and frozen on the `projects` row at issue\ntime. `/project/secrets` filters its response to that frozen scope, so a leaked\ntoken can only ever read the secrets it was originally minted for. The default\nTTL is **14 days** (1209600 seconds); admins can revoke the token early via\n`POST /admin/projects/\u003cname\u003e/revoke`.\n\n### Honey tokens\n\nMark a secret as a decoy at create time:\n\n```bash\ncurl -X POST http://localhost:3000/admin/secrets \\\n  -H \"X-Admin-Token: $ADMIN_TOKEN\" -H \"Content-Type: application/json\" \\\n  -d '{\"key_path\":\"legacy_aws_root_key\",\"secret_type\":\"KEY_VALUE\",\n       \"value\":\"AKIA-FAKE-DO-NOT-USE\",\"is_honey_token\":true}'\n```\n\nA honey-token is never returned to a legitimate caller. Any read attempt is a\n100% attack signal: the calling project's token is **revoked immediately**, an\n`alarm`-status row is written to the audit log, the response is a generic\n401, and an outbound notification is dispatched to every enabled channel\n(see below).\n\n### Outbound notifications (#12 / #15)\n\nHoney-token alarms and Shamir recovery boots fan out to every enabled\n**notification channel** managed under the dashboard's `Notifications` tab\n(or `POST /admin/notification-channels`):\n\n| Channel | Transport | Config |\n|---------|-----------|--------|\n| Slack | incoming webhook | `{\"webhook_url\":\"https://hooks.slack.com/...\"}` |\n| Discord | incoming webhook | `{\"webhook_url\":\"https://discord.com/api/webhooks/...\"}` |\n| Telegram | Bot API | `{\"bot_token\":\"...\",\"chat_id\":\"...\"}` |\n| Email | `himalaya-cli` (when on PATH) | `{\"to\":\"oncall@example.com\",\"account\":\"...\"}` |\n\nChannel configs are envelope-encrypted under the KEK, so a DB-only compromise\nleaks nothing about who gets paged.\n\n### Ed25519 agent identity (#13)\n\nEvery agent authenticates with an **Ed25519 public key**. The agent generates\nthe keypair locally with `cortex-cli gen-key`, uploads only the public key,\nand proves identity at `/agent/discover` by signing\n`ts | nonce | agent_id | /agent/discover`:\n\n```bash\n# 1. Generate a keypair (private key persisted at ~/.cortex/agent-\u003cid\u003e.key, mode 0600)\ncortex-cli gen-key --agent-id my-agent\n# stdout: \u003cbase64url public key\u003e\n\n# 2. Register the agent with that public key\ncurl -X POST http://localhost:3000/admin/agents \\\n  -H \"Content-Type: application/json\" -H \"X-Admin-Token: $ADMIN_TOKEN\" \\\n  -d '{\"agent_id\":\"my-agent\",\"agent_pub\":\"\u003cbase64url-pubkey\u003e\"}'\n\n# 3. Sign an auth_proof at run time\ncortex-cli sign-proof --agent-id my-agent --priv-key-file ~/.cortex/agent-my-agent.key\n# stdout: {\"ts\":1714248000,\"nonce\":\"...\",\"auth_proof\":\"\u003cbase64url-sig\u003e\"}\n```\n\nReplay protection: the request `ts` must be within ±5 minutes of the server\nclock. The private key never leaves the agent's machine — a DB compromise\nexposes only public keys.\n\n### Ed25519-signed project tokens (#14)\n\n`POST /agent/discover` now accepts `signed_token: true`, which mints an\nEdDSA-signed JWT project token alongside the legacy random one:\n\n```jsonc\n{\n  \"iss\": \"cortex-auth\", \"sub\": \"\u003cproject_name\u003e\", \"aud\": \"cortex-cli\",\n  \"iat\": 1714248000, \"exp\": 1715457600,\n  \"jti\": \"\u003cuuid\u003e\", \"scope\": [\"openai_api_key\", ...],\n  \"namespace\": \"default\", \"project_id\": \"\u003cproject_name\u003e\"\n}\n```\n\nVerifiers fetch the public key from `GET /.well-known/jwks.json` (keyed by\n`kid` so old tokens stay verifiable across rotations). Revocation is\nenforced via the `revoked_token_jti` table.\n\n### Shamir m-of-n unseal recovery (#15)\n\nIf the operator password is lost, the running KEK can be split into n shares\nwith threshold m and reconstructed at boot. Generate shares once from the\ndashboard's `Shamir Recovery` tab (or `POST /admin/shamir/generate`) and\ndistribute them to operators — the server does not retain a copy.\n\n```bash\n# Recovery boot: prompts for `threshold` shares interactively on stdin.\nCORTEX_RECOVERY_MODE=1 \\\nCORTEX_RECOVERY_THRESHOLD=3 \\\nADMIN_TOKEN=$ADMIN_TOKEN \\\ncortex-server\n# [cortex-server SEALED (RECOVERY MODE)] — awaiting Shamir shares on stdin\n#   share 1 of 3: ********\n#   share 2 of 3: ********\n#   share 3 of 3: ********\n```\n\nA successful recovery boot writes an `alarm`-status row to the audit log\n(`action=\"recovery_boot\"`) and dispatches notifications to every enabled\nchannel.\n\n### Device authorization \u0026 cortex-daemon (#16)\n\nA long-running `cortex-daemon` process can hold the session for an agent so\nunattended pipelines never see raw secret material. Login uses the OAuth 2.0\nDevice Authorization Grant (RFC 8628):\n\n```bash\n# 1. Start the daemon (listens on ~/.cortex/agent.sock)\ncortex-daemon \u0026\n\n# 2. Trigger device login — prints a user_code\ncortex-cli daemon login --url http://localhost:3000\n# [cortex-cli] visit http://localhost:3000/device and approve user_code: ABCD-1234\n\n# 3. An admin approves the user_code via the dashboard's `Devices` tab\n#    (or POST /admin/web/device/approve), binding it to an agent_id.\n\n# 4. The daemon now holds an Ed25519-signed access token for the agent.\ncortex-cli daemon status\n```\n\nThe daemon scaffolding implements the basic socket protocol (`status` /\n`run`); full attestation and the `inject_template` / `ssh_proxy` socket\nverbs are tracked in [docs/UNCERTAINTIES.md](docs/UNCERTAINTIES.md) #17.\n\n### Tamper-evident audit log\n\nEvery audit row is HMAC-SHA256 chained to the previous row using a key derived\nfrom the KEK (HKDF-style, fixed domain separator). Each row stores\n`prev_hash || entry_mac`; the running tail MAC lives in `audit_mac_state`.\nAny deletion, re-order, or rewrite of an audit row breaks the chain and is\ndetectable by replaying entries.\n\nRows also record optional caller metadata (`caller_pid`,\n`caller_binary_sha256`, `caller_argv_hash`, `caller_cwd`, `caller_git_commit`,\n`source_ip`, `hostname`, `os`) populated from `X-Cortex-Caller-*` request\nheaders. Missing fields stay NULL — the chain MAC covers them either way.\n\nAudit logs are auto-deleted after 60 days.\n\n### Other guarantees\n\n- AES-256-GCM with a unique nonce per write for both DEK→body and KEK→DEK steps\n- Project tokens hashed with SHA-256 (the raw token is never stored). EdDSA-signed JWT tokens are also supported (#14) when the caller passes `signed_token: true` to `/agent/discover`; the server's public key is published at `GET /.well-known/jwks.json`.\n- Admin operations protected by static `ADMIN_TOKEN` (#18 multi-user RBAC tracked as deferred)\n- `/agent/discover` authenticates agents either via Ed25519 signature (#13) when an `agent_pub` is registered, or via legacy HMAC-SHA256 JWT — no separate session token issued\n- Project access via one-time-issued `project_token` (must be saved — cannot be recovered) or via the EdDSA-signed JWT alternative\n- `cortex-cli` uses `exec()` — secrets never visible to a parent process\n- `cortex-daemon` (#16) holds the session over a Unix socket so unattended pipelines never see raw secret material\n- KEK rotation: `POST /admin/rotate-key {\"new_kek_password\": \"...\"}` re-wraps every DEK with the new KEK and bumps `kek_version`. Body ciphertexts are untouched.\n- KEK recovery (#15): `CORTEX_RECOVERY_MODE=1` boots from m Shamir shares typed on stdin instead of the operator password\n- TLS terminated in-process when `TLS_CERT_FILE` + `TLS_KEY_FILE` are set (rustls)\n- Outbound notifications (#12 / #15): honey-token alarms and recovery boots fan out to Slack / Discord / Telegram / email channels managed in the dashboard\n\n### Roadmap toward the full design\n\nThe current build covers envelope encryption, namespaces, scoped tokens,\nhoney tokens, tamper-evident audit logs, **outbound notifications**, **Ed25519\nagent identity**, **Ed25519-signed project tokens** with a JWKS endpoint,\n**Shamir m-of-n unseal recovery**, and the **OAuth 2.0 device-authorization\nflow** with a basic `cortex-daemon`. The remaining gap from\n[UPDATED_DESIGN.md](UPDATED_DESIGN.md) is daemon attestation (#17) plus the\nmulti-user RBAC for admins (#18) and the verify-audit CLI (#11) — see\n[docs/UNCERTAINTIES.md](docs/UNCERTAINTIES.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavideuler%2Fcortex-auth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavideuler%2Fcortex-auth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavideuler%2Fcortex-auth/lists"}