{"id":50613729,"url":"https://github.com/stepandel/key-proxy","last_synced_at":"2026-06-06T06:30:42.995Z","repository":{"id":351663631,"uuid":"1211957520","full_name":"stepandel/key-proxy","owner":"stepandel","description":"A macOS menu bar app that acts as a credential-injecting HTTPS proxy. It stores API keys in the macOS Keychain and injects them in-flight into outbound requests transparently, for every process on the machine","archived":false,"fork":false,"pushed_at":"2026-04-16T01:20:09.000Z","size":133,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-16T01:24:36.914Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Swift","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/stepandel.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-15T23:28:21.000Z","updated_at":"2026-04-16T01:20:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/stepandel/key-proxy","commit_stats":null,"previous_names":["stepandel/key-proxy"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/stepandel/key-proxy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stepandel%2Fkey-proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stepandel%2Fkey-proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stepandel%2Fkey-proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stepandel%2Fkey-proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stepandel","download_url":"https://codeload.github.com/stepandel/key-proxy/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stepandel%2Fkey-proxy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33972397,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-06T02:00:07.033Z","response_time":107,"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":[],"created_at":"2026-06-06T06:30:42.305Z","updated_at":"2026-06-06T06:30:42.978Z","avatar_url":"https://github.com/stepandel.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# KeyProxy\n\nCredential-injecting HTTPS proxy for macOS. Stores API keys in the Keychain and injects them into outbound requests to whitelisted domains — transparently, for every process on the machine.\n\n## Architecture\n\nTwo components:\n\n- **`keyproxyd`** — Rust daemon. Runs the HTTPS proxy (TCP listener + CONNECT routing + TLS interception via rustls + blind-tunnel fallback). State is fully runtime-injected over a Unix socket. Holds no persistent data.\n- **`KeyProxy.app`** — SwiftUI menu bar app. Owns the Keychain, the rule config file, CA trust prompts, and system proxy configuration. Spawns and talks to `keyproxyd` over a line-delimited JSON socket protocol. Fails closed: when the app quits, the daemon exits and the system proxy is cleared.\n\n```\n┌─────────────────────────────┐      spawns / IPC      ┌──────────────────────┐\n│  KeyProxy.app (SwiftUI)     │ ──────────────────────▶│  keyproxyd (Rust)    │\n│  · Keychain (API keys + CA) │   Unix socket 0600     │  · TCP :7777         │\n│  · config.json              │   line-delimited JSON  │  · CONNECT routing   │\n│  · networksetup control     │                        │  · TLS terminate     │\n│  · CA trust dialog          │                        │  · blind tunnel      │\n└─────────────────────────────┘                        └──────────────────────┘\n```\n\nSee the spec at the top of the conversation for the end-to-end problem/solution framing.\n\n## Requirements\n\n- macOS 14+ (Sonoma)\n- Rust stable (1.77+)\n- Swift 5.9+ (Xcode 15 CLT or Xcode 15+)\n\n## Build \u0026 run\n\n```bash\n./app/scripts/build-app.sh                 # release build\nCONFIG=debug ./app/scripts/build-app.sh    # debug build\n\nopen build/KeyProxy.app\n```\n\nThe build script:\n1. Runs `swift build` for the app.\n2. Runs `cargo build` for the daemon.\n3. Assembles `build/KeyProxy.app` with the daemon at `Contents/Resources/keyproxyd`.\n\n### First launch\n\n1. App generates a local CA (by asking the daemon) and stores the private key in your login Keychain.\n2. Open Settings → General → **Trust CA** — enter your admin password when prompted.\n3. Add a rule: domain, header name, credential. Credential is write-only from the UI (it goes straight to Keychain).\n4. Click **Start** in the menu bar popover. System HTTPS proxy is set to `127.0.0.1:7777`.\n\n### Verify\n\n```bash\n# succeeds — credentials injected by the proxy\ncurl -v https://api.anthropic.com/v1/messages\n\n# passes through untouched — blind tunnel, no interception\ncurl -v https://example.com\n```\n\n## File layout\n\n```\nkey-proxy/\n├── daemon/                         # Rust daemon\n│   ├── Cargo.toml\n│   └── src/\n│       ├── main.rs                 # entry + socket path\n│       ├── ipc.rs                  # JSON IPC protocol\n│       ├── state.rs                # runtime rule/CA state\n│       ├── stats.rs                # log entry shape\n│       └── proxy/\n│           ├── mod.rs              # listener + shutdown\n│           ├── connect.rs          # CONNECT routing\n│           ├── intercept.rs        # TLS terminate + header inject + forward\n│           ├── tunnel.rs           # copy_bidirectional\n│           └── cert.rs             # CA gen, per-domain leaf cert cache\n└── app/                            # SwiftUI app\n    ├── Package.swift\n    ├── Resources/Info.plist        # LSUIElement = menu bar app\n    ├── scripts/build-app.sh        # assembles .app from SwiftPM binary + daemon\n    └── Sources/KeyProxy/\n        ├── KeyProxyApp.swift       # @main, MenuBarExtra + Settings scene\n        ├── Models/Rule.swift\n        ├── Services/\n        │   ├── KeychainStore.swift     # SecItem wrapper (device-only access)\n        │   ├── ConfigStore.swift       # ~/Library/Application Support/KeyProxy/config.json\n        │   ├── NetworkProxy.swift      # networksetup + CA trust via osascript\n        │   ├── DaemonClient.swift      # NWConnection unix socket + IPC\n        │   └── ProxyController.swift   # top-level ObservableObject\n        └── Views/\n            ├── MenuBarView.swift\n            ├── SettingsView.swift\n            ├── RulesTab.swift\n            ├── GeneralTab.swift\n            ├── StatsTab.swift\n            └── AddRuleSheet.swift\n```\n\n## IPC protocol (daemon)\n\nLine-delimited JSON on `~/Library/Application Support/KeyProxy/daemon.sock` (mode 0600).\n\n**Client → daemon:**\n\n```json\n{\"id\": 1, \"cmd\": \"generate_ca\"}\n{\"id\": 2, \"cmd\": \"set_ca\", \"key_pem\": \"...\", \"cert_pem\": \"...\"}\n{\"id\": 3, \"cmd\": \"set_rules\", \"rules\": [{\"domain\": \"api.anthropic.com\", \"header_name\": \"x-api-key\", \"credential\": \"...\"}]}\n{\"id\": 4, \"cmd\": \"start\", \"port\": 7777}\n{\"id\": 5, \"cmd\": \"stop\"}\n{\"id\": 6, \"cmd\": \"subscribe_logs\"}\n{\"id\": 7, \"cmd\": \"ping\"}\n```\n\n**Daemon → client:**\n\n```json\n{\"type\": \"ok\", \"id\": 1}\n{\"type\": \"error\", \"id\": 2, \"message\": \"...\"}\n{\"type\": \"ca\", \"id\": 1, \"key_pem\": \"...\", \"cert_pem\": \"...\"}\n{\"type\": \"pong\", \"id\": 7}\n{\"type\": \"log\", \"timestamp\": \"2026-04-15T12:34:56Z\", \"domain\": \"api.anthropic.com\", \"status\": 200, \"latency_ms\": 12, \"intercepted\": true, \"error\": null}\n```\n\n## Security properties\n\n| Property | Mechanism |\n|---|---|\n| Keys never on disk as plaintext | macOS Keychain (`kSecAttrAccessibleWhenUnlockedThisDeviceOnly`) |\n| Daemon holds secrets in memory only | `keyproxyd` receives rules/CA at startup, never touches disk |\n| IPC socket not world-readable | Unix socket with 0600 permissions |\n| Non-whitelisted traffic untouched | Blind TCP tunnel — no TLS termination |\n| No persistent request logging | In-memory only; cleared on daemon exit |\n| Proxy fails closed | Daemon shuts down when Swift side disconnects; system proxy cleared on app quit |\n\n## Recovering from a stuck proxy\n\nIf something goes wrong and HTTPS on the whole machine stops working (dead daemon, force-quit, uninstalled app with proxy still set), run the daemon binary directly with `--unset`:\n\n```bash\n./build/KeyProxy.app/Contents/Resources/keyproxyd --unset\n# or if installed in /Applications:\n/Applications/KeyProxy.app/Contents/Resources/keyproxyd --unset\n```\n\nThis disables the system HTTPS proxy on every network interface and exits. Always safe to run. The app also does this automatically on launch if it detects a leftover proxy from a previous crash, and on quit via `applicationWillTerminate`.\n\n## Notes\n\n- The `.app` currently isn't code-signed. For a signed build, add a signing step after `cp` in `scripts/build-app.sh` (`codesign --sign \"Developer ID Application: …\" --deep build/KeyProxy.app`).\n- The menu bar uses an SF Symbol (`key` / `key.fill`). Replace with a custom template image by bundling a PNG and using `Image(nsImage:)` if you want your own icon.\n- Only HTTPS is supported (CONNECT tunnels). Plain HTTP isn't a goal — credentials shouldn't travel over cleartext anyway.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstepandel%2Fkey-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstepandel%2Fkey-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstepandel%2Fkey-proxy/lists"}