{"id":43451740,"url":"https://github.com/keywaysh/keyway-backend","last_synced_at":"2026-02-08T00:01:27.053Z","repository":{"id":335934036,"uuid":"1102560558","full_name":"keywaysh/keyway-backend","owner":"keywaysh","description":"Fastify API for Keyway — sync .env files with your team using GitHub permissions","archived":false,"fork":false,"pushed_at":"2026-02-03T00:33:13.000Z","size":1249,"stargazers_count":2,"open_issues_count":6,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-03T13:47:02.850Z","etag":null,"topics":["developer-tools","devtools","dotenv","encryption","environment-variables","secrets-management"],"latest_commit_sha":null,"homepage":"https://keyway.sh","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/keywaysh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","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":"2025-11-23T17:19:40.000Z","updated_at":"2026-02-03T00:03:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/keywaysh/keyway-backend","commit_stats":null,"previous_names":["keywaysh/keyway-backend"],"tags_count":123,"template":false,"template_full_name":null,"purl":"pkg:github/keywaysh/keyway-backend","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keywaysh%2Fkeyway-backend","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keywaysh%2Fkeyway-backend/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keywaysh%2Fkeyway-backend/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keywaysh%2Fkeyway-backend/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/keywaysh","download_url":"https://codeload.github.com/keywaysh/keyway-backend/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keywaysh%2Fkeyway-backend/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29213447,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-07T23:58:20.073Z","status":"ssl_error","status_checked_at":"2026-02-07T23:58:07.729Z","response_time":63,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["developer-tools","devtools","dotenv","encryption","environment-variables","secrets-management"],"created_at":"2026-02-03T02:10:23.397Z","updated_at":"2026-02-08T00:01:27.031Z","avatar_url":"https://github.com/keywaysh.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Keyway API\n\n[![CI](https://github.com/keywaysh/keyway-backend/actions/workflows/ci.yml/badge.svg)](https://github.com/keywaysh/keyway-backend/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue.svg)](https://www.typescriptlang.org/)\n[![Node.js](https://img.shields.io/badge/Node.js-22_LTS-green.svg)](https://nodejs.org/)\n[![Keyway Secrets](https://www.keyway.sh/badge.svg?repo=keywaysh/keyway-backend)](https://www.keyway.sh/vaults/keywaysh/keyway-backend)\n\n**The API behind [Keyway](https://keyway.sh)** — GitHub-native secrets management.\n\n## Architecture\n\n```\n┌─────────┐       ┌─────────────┐       ┌──────────────┐\n│   CLI   │──────▶│  Keyway API │◀─────▶│   Postgres   │\n└─────────┘       └──────┬──────┘       └──────────────┘\n                         │\n            ┌────────────┼────────────┐\n            ▼                         ▼\n   ┌─────────────────┐      ┌─────────────────┐\n   │   GitHub API    │      │  keyway-crypto  │\n   │  (permissions)  │      │  (Go + gRPC)    │\n   └─────────────────┘      └─────────────────┘\n```\n\n- **Keyway API** — Fastify 5, TypeScript, handles auth and vault operations\n- **keyway-crypto** — Go microservice, AES-256-GCM encryption, holds the encryption key\n- **GitHub API** — Verifies repo access (collaborator check)\n- **Postgres** — Stores encrypted secrets, user data, audit logs\n\n## Features\n\n- **Encrypted at rest** — AES-256-GCM with random IV per secret, key isolated in Go service\n- **GitHub auth** — OAuth Device Flow or fine-grained PAT, no new credentials to manage\n- **Permission-based** — If you have repo access, you get secret access. That's it.\n- **CI-ready** — GitHub Action for injecting secrets into workflows\n\n## Project Structure\n\n```\nkeyway-backend/\n├── src/\n│   ├── db/              # Database schema and migrations\n│   │   ├── schema.ts    # Drizzle schema (users, vaults, secrets)\n│   │   ├── index.ts     # Database connection\n│   │   └── migrate.ts   # Migration runner\n│   ├── routes/          # API endpoints\n│   │   ├── auth.ts      # GitHub OAuth callback\n│   │   └── vaults.ts    # Vault operations (init, push, pull)\n│   ├── utils/           # Utilities\n│   │   ├── encryption.ts # AES-256-GCM encryption\n│   │   ├── github.ts     # GitHub API client\n│   │   └── analytics.ts  # PostHog integration\n│   ├── types/           # TypeScript types and Zod schemas\n│   │   └── index.ts\n│   └── index.ts         # Fastify server entry point\n├── drizzle/             # Generated migrations\n├── Dockerfile           # Production Docker image\n├── railway.json         # Railway configuration\n└── package.json\n```\n\n## Prerequisites\n\n- Node.js 22+\n- PostgreSQL (Neon recommended)\n- GitHub App\n- [keyway-crypto](../keyway-crypto) service running\n\n## Setup\n\n### 1. Install Dependencies\n\n```bash\npnpm install\n```\n\n### 2. Configure Environment Variables\n\nCreate `.env` from the example:\n\n```bash\ncp .env.example .env\n```\n\nEdit `.env` with the required variables:\n\n```env\n# Server\nPORT=3000\nNODE_ENV=development\n\n# Database\nDATABASE_URL=postgresql://user:password@host/database\n\n# Crypto Service (keyway-crypto gRPC address)\nCRYPTO_SERVICE_URL=localhost:50051\n\n# JWT Secret (min 32 chars)\nJWT_SECRET=your-32-character-minimum-secret-here\n\n# GitHub App (not OAuth App)\nGITHUB_APP_ID=123456\nGITHUB_APP_CLIENT_ID=Iv1.xxxxxxxxxxxxxxxx\nGITHUB_APP_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\nGITHUB_APP_PRIVATE_KEY=\u003cbase64-encoded .pem file\u003e\nGITHUB_APP_WEBHOOK_SECRET=\u003coptional in dev, required in prod\u003e\nGITHUB_APP_NAME=keyway\n\n# PostHog (optional)\nPOSTHOG_API_KEY=\nPOSTHOG_HOST=https://app.posthog.com\n```\n\n### 3. Create GitHub App\n\n1. Go to [GitHub Settings → Developer settings → GitHub Apps → New GitHub App](https://github.com/settings/apps/new)\n2. Configure:\n   - **GitHub App name**: `keyway` (or your app name)\n   - **Homepage URL**: `https://keyway.sh`\n   - **Callback URL**: `http://localhost:3000/v1/auth/callback`\n   - **Enable Device Flow**: ✓\n   - **Webhook URL**: `http://localhost:3000/v1/webhooks/github` (or disable in dev)\n3. Permissions:\n   - **Repository → Metadata**: Read-only\n   - **Repository → Administration**: Read-only\n   - **Account → Email addresses**: Read-only\n4. Save the App ID, Client ID, Client Secret, and generate a Private Key\n\n### 4. Start the Crypto Service\n\nThe backend requires the `keyway-crypto` gRPC service for encryption. See [keyway-crypto](../keyway-crypto) for setup.\n\n```bash\n# In keyway-crypto directory\nENCRYPTION_KEY=$(openssl rand -hex 32) go run .\n```\n\n### 5. Run Database Migrations\n\n```bash\n# Generate migrations from schema\npnpm run db:generate\n\n# Run migrations\npnpm run db:migrate\n```\n\n### 6. Start the Server\n\n```bash\n# Development mode (with auto-reload)\npnpm run dev\n\n# Production mode\npnpm run build\npnpm start\n```\n\nThe API will be available at `http://localhost:3000`.\n\n## API Endpoints\n\n### Health Check\n\n```bash\nGET /health\n```\n\n**Response:**\n```json\n{\n  \"status\": \"healthy\",\n  \"timestamp\": \"2024-01-01T00:00:00.000Z\",\n  \"environment\": \"production\",\n  \"database\": \"connected\",\n  \"crypto\": \"connected\",\n  \"cryptoVersion\": \"1.0.0\"\n}\n```\n\nReturns 503 if database is disconnected. Crypto service status is included but doesn't affect overall health status.\n\n### Authentication\n\n#### OAuth Device Flow\n\n**POST /auth/device/start** - Start device authorization\n\nRequest:\n```json\n{\n  \"repository\": \"owner/repo\"  // optional, suggested repo from CLI\n}\n```\n\nResponse:\n```json\n{\n  \"deviceCode\": \"abc123...\",\n  \"userCode\": \"ABCD-1234\",\n  \"verificationUri\": \"https://your-api.com/auth/device/verify\",\n  \"verificationUriComplete\": \"https://your-api.com/auth/device/verify?user_code=ABCD-1234\",\n  \"expiresIn\": 900,\n  \"interval\": 5\n}\n```\n\n**POST /auth/device/poll** - Poll for authorization status\n\nRequest:\n```json\n{\n  \"deviceCode\": \"abc123...\"\n}\n```\n\nResponse (approved):\n```json\n{\n  \"status\": \"approved\",\n  \"keywayToken\": \"eyJhbGc...\",\n  \"githubLogin\": \"johndoe\",\n  \"expiresAt\": \"2025-02-23T...\"\n}\n```\n\n#### Fine-grained PAT\n\n**POST /auth/token/validate** - Validate Personal Access Token\n\nHeaders:\n```\nAuthorization: Bearer github_pat_...\n```\n\nResponse:\n```json\n{\n  \"username\": \"johndoe\",\n  \"githubId\": 12345\n}\n```\n\n### Vaults\n\n#### `POST /vaults/init`\n\nInitialize a new vault for a repository.\n\n**Request:**\n```json\n{\n  \"repoFullName\": \"owner/repo\",\n  \"accessToken\": \"gho_...\"\n}\n```\n\n**Response:**\n```json\n{\n  \"vaultId\": \"uuid\",\n  \"repoFullName\": \"owner/repo\",\n  \"message\": \"Vault initialized successfully\"\n}\n```\n\n#### `POST /vaults/:repo/:env/push`\n\nPush secrets to a vault environment.\n\n**Request:**\n```json\n{\n  \"content\": \"API_KEY=abc123\\nDB_URL=postgres://...\",\n  \"accessToken\": \"gho_...\"\n}\n```\n\n**Response:**\n```json\n{\n  \"success\": true,\n  \"message\": \"Secrets pushed successfully\"\n}\n```\n\n#### `GET /vaults/:repo/:env/pull?accessToken=...`\n\nPull secrets from a vault environment.\n\n**Response:**\n```json\n{\n  \"content\": \"API_KEY=abc123\\nDB_URL=postgres://...\"\n}\n```\n\n### Environments\n\nEach vault has a list of allowed environments. Secrets can only be pushed to environments that exist in the vault.\n\n#### `GET /v1/vaults/:owner/:repo/environments`\n\nGet the list of environments for a vault.\n\n**Response:**\n```json\n{\n  \"data\": {\n    \"environments\": [\"local\", \"dev\", \"staging\", \"production\"]\n  }\n}\n```\n\n#### `POST /v1/vaults/:owner/:repo/environments` (Admin only)\n\nCreate a new environment.\n\n**Request:**\n```json\n{\n  \"name\": \"preview\"\n}\n```\n\n**Response:**\n```json\n{\n  \"data\": {\n    \"environment\": \"preview\",\n    \"environments\": [\"local\", \"dev\", \"staging\", \"production\", \"preview\"]\n  }\n}\n```\n\n**Validation:**\n- Name must be 2-30 characters\n- Lowercase letters, numbers, dashes, and underscores only\n- Must start with a letter\n\n#### `PATCH /v1/vaults/:owner/:repo/environments/:name` (Admin only)\n\nRename an environment. All secrets in that environment are updated.\n\n**Request:**\n```json\n{\n  \"newName\": \"development\"\n}\n```\n\n**Response:**\n```json\n{\n  \"data\": {\n    \"oldName\": \"dev\",\n    \"newName\": \"development\",\n    \"environments\": [\"local\", \"development\", \"staging\", \"production\"]\n  }\n}\n```\n\n#### `DELETE /v1/vaults/:owner/:repo/environments/:name` (Admin only)\n\nDelete an environment and all its secrets.\n\n**Response:**\n```json\n{\n  \"data\": {\n    \"deleted\": \"preview\",\n    \"environments\": [\"local\", \"dev\", \"staging\", \"production\"]\n  }\n}\n```\n\n**Note:** Cannot delete the last environment in a vault.\n\n## Development\n\n```bash\n# Install dependencies\npnpm install\n\n# Run in development mode (auto-reload)\npnpm dev\n\n# Build for production\npnpm build\n\n# Type check\npnpm run type-check\n\n# Generate database migrations\npnpm run db:generate\n\n# Run database migrations\npnpm run db:migrate\n```\n\n## Deployment\n\n### Railway\n\n**See [DEPLOYMENT.md](./DEPLOYMENT.md) for detailed deployment guide.**\n\nQuick steps:\n\n1. Create GitHub OAuth App\n2. Push code to GitHub\n3. Create new project on Railway.app\n4. Add PostgreSQL database\n5. Configure environment variables (see below)\n6. Railway auto-deploys on push to main\n\n**Always run before pushing:**\n```bash\npnpm run validate  # Type check + build + env validation\n```\n\nRailway will automatically:\n- Run migrations (`pnpm run db:migrate`)\n- Build the app (`pnpm build`)\n- Start the server (`node dist/index.js`)\n- Health check on `/health`\n- Rollback if deployment fails\n\n## Security\n\n### Encryption\n\n- **Service**: Dedicated `keyway-crypto` gRPC microservice\n- **Algorithm**: AES-256-GCM (authenticated encryption)\n- **Key**: 32-byte symmetric key stored only in crypto service\n- **IV**: Random 12-byte initialization vector per encryption\n- **Auth Tag**: 16-byte authentication tag for integrity\n- **Isolation**: Encryption key never leaves the crypto service\n\n### Access Control\n\n- **Authentication**: OAuth Device Flow or Fine-grained PAT\n- **Authorization**: GitHub repository collaborator/admin check via API\n- **Tokens**:\n  - OAuth tokens (30-day Keyway JWT + GitHub access token)\n  - Fine-grained PATs (user-controlled scope and expiration)\n- **Privacy**: Only metadata access, never reads repository code\n\n### Logging\n\n- **No secret values** are ever logged\n- All content is sanitized before logging (`sanitizeForLogging`)\n- Only metadata (line count, character count) is logged\n\n### Analytics Safety\n\n**NEVER tracked:**\n- Secret names or values\n- Environment variable content\n- Access tokens\n- Encryption keys\n\n**Only tracked:**\n- Repository names (public info)\n- Environment names (e.g., \"production\")\n- Command usage (init, push, pull)\n- Error messages (sanitized)\n\nSee [POSTHOG_CHECKLIST.md](./POSTHOG_CHECKLIST.md) for details.\n\n## Database Schema\n\n### Users Table\n\n```typescript\n{\n  id: uuid (PK)\n  githubId: number (unique)\n  username: string\n  email: string?\n  avatarUrl: string?\n  accessToken: string\n  createdAt: timestamp\n  updatedAt: timestamp\n}\n```\n\n### Vaults Table\n\n```typescript\n{\n  id: uuid (PK)\n  repoFullName: string (unique)\n  environments: string[] (default: ['local', 'dev', 'staging', 'production'])\n  ownerId: uuid (FK → users)\n  createdAt: timestamp\n  updatedAt: timestamp\n}\n```\n\n### Secrets Table\n\n```typescript\n{\n  id: uuid (PK)\n  vaultId: uuid (FK → vaults, cascade delete)\n  environment: string\n  encryptedContent: string\n  iv: string\n  authTag: string\n  createdAt: timestamp\n  updatedAt: timestamp\n}\n```\n\n## Troubleshooting\n\n### \"DATABASE_URL is not defined\"\n\nMake sure you've created a `.env` file with your database connection string.\n\n### \"Crypto service unavailable\"\n\nMake sure the `keyway-crypto` gRPC service is running:\n```bash\ncd ../keyway-crypto\nENCRYPTION_KEY=$(openssl rand -hex 32) go run .\n```\n\nAnd that `CRYPTO_SERVICE_URL` points to it (default: `localhost:50051`).\n\n### Migration errors\n\nIf migrations fail, check:\n1. Database is accessible\n2. DATABASE_URL is correct\n3. Database user has CREATE TABLE permissions\n\n### Port already in use\n\nChange the port in `.env`:\n```env\nPORT=3001\n```\n\n## License\n\nMIT\n\n## Support\n\n- **Docs**: https://docs.keyway.sh\n- **CLI**: https://github.com/keywaysh/cli\n- **MCP Server**: https://github.com/keywaysh/keyway-mcp\n- **Status**: https://status.keyway.sh\n- **Issues**: [GitHub Issues](https://github.com/keywaysh/keyway-backend/issues)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeywaysh%2Fkeyway-backend","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkeywaysh%2Fkeyway-backend","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeywaysh%2Fkeyway-backend/lists"}