{"id":49394761,"url":"https://github.com/ashref-dev/one-time-secret","last_synced_at":"2026-04-28T15:03:17.200Z","repository":{"id":337519572,"uuid":"1150161354","full_name":"Ashref-dev/one-time-secret","owner":"Ashref-dev","description":"Secure, self-hosted secret sharing with zero-knowledge encryption","archived":false,"fork":false,"pushed_at":"2026-03-19T07:00:02.000Z","size":767,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-20T00:17:34.477Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","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/Ashref-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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":"2026-02-05T00:31:00.000Z","updated_at":"2026-03-19T07:00:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Ashref-dev/one-time-secret","commit_stats":null,"previous_names":["ashref-dev/one-time-secret"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Ashref-dev/one-time-secret","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ashref-dev%2Fone-time-secret","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ashref-dev%2Fone-time-secret/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ashref-dev%2Fone-time-secret/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ashref-dev%2Fone-time-secret/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Ashref-dev","download_url":"https://codeload.github.com/Ashref-dev/one-time-secret/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ashref-dev%2Fone-time-secret/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32385943,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-28T14:34:11.604Z","status":"ssl_error","status_checked_at":"2026-04-28T14:32:37.009Z","response_time":56,"last_error":"SSL_read: 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":[],"created_at":"2026-04-28T15:02:56.748Z","updated_at":"2026-04-28T15:03:17.187Z","avatar_url":"https://github.com/Ashref-dev.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# 🔐 One-Time Secret\n\n**Secure, self-hosted secret sharing with zero-knowledge encryption**\n\n[![CI/CD](https://github.com/Ashref-dev/one-time-secret/actions/workflows/ci.yml/badge.svg)](https://github.com/Ashref-dev/one-time-secret/actions)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Go Version](https://img.shields.io/badge/go-1.26.0-blue.svg)](https://golang.org)\n[![Docker](https://img.shields.io/badge/docker-ready-blue.svg)](https://www.docker.com)\n\n[Demo](#demo) • [Features](#features) • [Quick Start](#quick-start) • [Security](#security) • [API](#api)\n\n\u003c/div\u003e\n\n---\n\n## 📖 Overview\n\nOne-Time Secret is a **privacy-first**, **self-hosted** platform for securely sharing sensitive information. Unlike cloud-based alternatives, your secrets are **encrypted in the browser** before reaching the server—ensuring true zero-knowledge security.\n\n### 🎨 Beautiful Design\n\nBuilt with a minimalist, muted purple aesthetic that works in both light and dark modes.\n\n![Screenshot](docs/screenshot.png)\n\n---\n\n## ✨ Features\n\n### 🔒 Security First\n- **Client-Side Encryption** - AES-256-GCM encryption in the browser\n- **Zero-Knowledge** - Server never sees plaintext or keys\n- **One-Time Access** - Secrets are destroyed immediately after viewing\n- **Automatic Expiration** - Configurable TTL (5 min - 24 hours)\n- **Optional Passphrase** - Additional layer with PBKDF2 key derivation\n\n### 🚀 Production Ready\n- **Atomic Operations** - Row-level database locking prevents race conditions\n- **Rate Limiting** - Configurable per-IP read, write, and agent request limits\n- **Structured Logging** - JSON logs with slog\n- **Health Checks** - Kubernetes-ready endpoints\n- **Docker Compose** - One-command deployment\n- **Agent-Friendly API** - Plaintext/file uploads plus a served `/agents.txt` guide\n\n### 🛠️ Tech Stack\n- **Backend:** Go (Chi router, pgx PostgreSQL driver)\n- **Frontend:** React + TypeScript + Vite\n- **Crypto:** WebCrypto API (AES-256-GCM)\n- **Database:** PostgreSQL (latest)\n- **Proxy:** Caddy (automatic HTTPS ready)\n\n---\n\n## 🚀 Quick Start\n\n### Prerequisites\n\n- Docker \u0026 Docker Compose\n- ~2GB RAM available\n\n### 1. Clone \u0026 Configure\n\n```bash\ngit clone https://github.com/Ashref-dev/one-time-secret.git\ncd ots\n\n# Copy and edit configuration\ncp .env.example .env\n# Edit DB_PASSWORD to something secure\nnano .env\n```\n\n### 2. Deploy\n\n```bash\n# Start all services\ndocker-compose up -d\n\n# Check logs\ndocker-compose logs -f\n\n# Verify health\ncurl http://localhost:3069/health\n```\n\nThe application will be available at `http://localhost:3069`\n\n### 3. Configure HTTPS (Production)\n\nEdit `caddy/Caddyfile`:\n\n```\nyourdomain.com {\n    tls your-email@example.com\n    \n    # ... rest of config\n}\n```\n\nRestart: `docker-compose restart caddy`\n\n---\n\n## 🏗️ Architecture\n\n```\n┌─────────────┐     ┌──────────────┐     ┌─────────────┐\n│   Browser   │────▶│    Caddy     │────▶│   Backend   │\n│  (Encrypt)  │     │   (Proxy)    │     │    (Go)     │\n└─────────────┘     └──────────────┘     └──────┬──────┘\n       │                                        │\n       │ 1. Encrypt secret                      │\n       │    with AES-256-GCM                    │\n       │                                        │\n       │ 2. Send ciphertext only                │\n       │────────────────────────────────────────▶│\n       │                                        │\n       │ 3. Store encrypted data                │\n       │◀────────────────────────────────────────│\n       │    (key stays in URL fragment)         │\n       │                                        │\n       │ 4. Share link                          │\n       │────────────────────────────────────────▶│\n       │                                        │\n       │ 5. Request secret                      │\n       │────────────────────────────────────────▶│\n       │                                        │\n       │ 6. Delete \u0026 return ciphertext          │\n       │◀────────────────────────────────────────│\n       │    (atomic operation)                  │\n       │                                        │\n       │ 7. Decrypt in browser                  │\n       │    with key from URL                   │\n\nKey Features:\n- Encryption key never sent to server\n- Atomic consume prevents double retrieval\n- Row-level locking prevents race conditions\n- No plaintext ever touches the server\n```\n\n---\n\n## 🔐 Security\n\n### Encryption\n\n- **Algorithm:** AES-256-GCM (authenticated encryption)\n- **Key Generation:** Cryptographically secure random (Crypto.getRandomValues)\n- **Key Derivation:** PBKDF2 with 100,000 iterations (for passphrase mode)\n- **IV:** 12-byte random nonce per encryption\n\n### Data Handling\n\n| Aspect | Implementation |\n|--------|----------------|\n| Storage | Ciphertext only (no keys, no plaintext) |\n| Transport | HTTPS required, HSTS enforced |\n| Access | Single-use, immediate deletion |\n| Expiration | Automatic cleanup after TTL |\n| Logging | No secret content logged |\n\n### Headers\n\n```\nContent-Security-Policy: default-src 'self'...\nX-Frame-Options: DENY\nX-Content-Type-Options: nosniff\nReferrer-Policy: no-referrer\nStrict-Transport-Security: max-age=31536000\n```\n\nSee [SECURITY.md](SECURITY.md) for detailed security information.\n\n---\n\n## 📚 API Documentation\n\n### Agent Convenience API\n\n```http\nPOST /api/agent/secrets\n```\n\nUse this endpoint for AI agents, CLI tools, and automation that need to submit plaintext or UTF-8 text files and receive a ready-to-share secret URL.\n\n- Accepts `application/json`, `text/plain`, and `multipart/form-data`\n- Defaults to 1 day expiration\n- Always creates a one-time secret\n- Supports an optional passphrase\n\n**Response:**\n```json\n{\n  \"id\": \"abc123...\",\n  \"url\": \"https://ots.ashref.tn/s/abc123#fragment-key\",\n  \"expires_at\": \"2026-03-20T12:00:00Z\",\n  \"expires_in\": 86400,\n  \"passphrase_required\": false\n}\n```\n\n**Important:** this convenience endpoint encrypts plaintext on the server during the request. If you need strict zero-knowledge uploads, use the encrypted API below and keep encryption client-side.\n\n### Create Secret\n\n```http\nPOST /api/secrets\nContent-Type: application/json\n\n{\n  \"ciphertext\": \"base64_aes_gcm_ciphertext\",\n  \"iv\": \"base64_12_byte_iv\",\n  \"salt\": \"base64_salt_if_passphrase_used\",\n  \"expires_in\": 3600,\n  \"burn_after_read\": true\n}\n```\n\n**Response:**\n```json\n{\n  \"id\": \"abc123...\"\n}\n```\n\n### Retrieve Secret (Atomic Consume)\n\n```http\nGET /api/secrets/{id}\n```\n\n**Response:**\n```json\n{\n  \"ciphertext\": \"base64_aes_gcm_ciphertext\",\n  \"iv\": \"base64_12_byte_iv\",\n  \"salt\": \"base64_salt_if_used\"\n}\n```\n\n**Note:** Secret is deleted immediately upon retrieval.\n\n### Burn Secret\n\n```http\nDELETE /api/secrets/{id}\n```\n\n**Response:** `204 No Content`\n\n---\n\n## ⚙️ Configuration\n\n### Environment Variables\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `DB_PASSWORD` | - | **Required:** PostgreSQL password |\n| `DB_USER` | `ots_user` | PostgreSQL username |\n| `DB_NAME` | `ots_db` | PostgreSQL database |\n| `MAX_SECRET_SIZE` | `32768` | Max secret size in bytes (32KB) |\n| `DEFAULT_TTL` | `3600` | Default TTL in seconds (1 hour) |\n| `AGENT_DEFAULT_TTL` | `86400` | Default TTL for the agent convenience endpoint |\n| `RATE_LIMIT_REQUESTS` | `30` | Legacy shared rate limit fallback for older configs |\n| `RATE_LIMIT_WINDOW` | `60` | Legacy shared rate limit fallback window |\n| `RATE_LIMIT_WRITE_REQUESTS` | `30` | Create/burn requests per write window per IP |\n| `RATE_LIMIT_WRITE_WINDOW` | `60` | Write rate limit window in seconds |\n| `RATE_LIMIT_READ_REQUESTS` | `180` | Read requests per read window per IP |\n| `RATE_LIMIT_READ_WINDOW` | `60` | Read rate limit window in seconds |\n| `RATE_LIMIT_AGENT_REQUESTS` | `10` | Agent convenience uploads per agent window per IP |\n| `RATE_LIMIT_AGENT_WINDOW` | `60` | Agent rate limit window in seconds |\n| `PUBLIC_BASE_URL` | - | Optional public origin for generated agent share URLs |\n| `LOG_LEVEL` | `info` | Log level (debug/info/warn/error) |\n| `ENV` | `production` | Environment mode |\n\n### Docker Compose\n\n```yaml\nservices:\n  backend:\n    environment:\n      - DATABASE_URL=postgres://ots_user:${DB_PASSWORD}@postgres:5432/ots_db?sslmode=disable\n      - MAX_SECRET_SIZE=32768\n      - AGENT_DEFAULT_TTL=86400\n      - RATE_LIMIT_WRITE_REQUESTS=30\n      - RATE_LIMIT_READ_REQUESTS=180\n      - RATE_LIMIT_AGENT_REQUESTS=10\n      - PUBLIC_BASE_URL=https://ots.ashref.tn\n    deploy:\n      resources:\n        limits:\n          memory: 256M\n        reservations:\n          memory: 128M\n```\n\n---\n\n## 🧪 Development\n\n### Backend\n\n```bash\ncd backend\n\n# Install dependencies\ngo mod download\n\n# Run tests\ngo test -v ./...\n\n# Run with hot reload (requires air)\nair\n\n# Or manually\ngo run cmd/server/main.go\n```\n\n### Frontend\n\n```bash\ncd frontend\n\n# Install dependencies\nnpm install\n\n# Start development server\nnpm run dev\n\n# Build for production\nnpm run build\n```\n\n### Database Migrations\n\nMigrations run automatically on startup. To run manually:\n\n```bash\ndocker-compose exec postgres psql -U ots_user -d ots_db -f /docker-entrypoint-initdb.d/000001_init_schema.up.sql\n```\n\n---\n\n## 🚢 Deployment\n\n### Docker Swarm\n\n```bash\n# Initialize swarm\ndocker swarm init\n\n# Deploy stack\ndocker stack deploy -c docker-compose.yml ots\n\n# Check status\ndocker stack ps ots\ndocker service logs ots_backend\n```\n\n### Kubernetes\n\nComing soon. See [k8s/](./k8s/) directory for manifests.\n\n### VPS / Cloud\n\n1. Provision server (1 CPU, 1GB RAM minimum)\n2. Install Docker \u0026 Docker Compose\n3. Clone repository\n4. Configure `.env`\n5. Run `docker-compose up -d`\n6. Configure DNS to point to server\n7. Edit Caddyfile with your domain\n8. Restart: `docker-compose restart caddy`\n\n---\n\n## 📊 Monitoring\n\n### Health Endpoints\n\n- `GET /health` - Basic health check\n- Backend logs structured JSON to stdout\n\n### Log Format\n\n```json\n{\n  \"time\": \"2026-02-04T12:00:00Z\",\n  \"level\": \"INFO\",\n  \"msg\": \"secret created\",\n  \"secret_id\": \"abc123\",\n  \"size\": 1024,\n  \"ip\": \"192.168.1.1\"\n}\n```\n\n### Metrics\n\nExport Prometheus metrics (coming soon).\n\n---\n\n## 🤝 Contributing\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\nPlease read [SECURITY.md](SECURITY.md) before reporting security issues.\n\n---\n\n## 📄 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n---\n\n## 🙏 Acknowledgments\n\n- [Chi Router](https://github.com/go-chi/chi) - Lightweight Go router\n- [pgx](https://github.com/jackc/pgx) - PostgreSQL driver for Go\n- [WebCrypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) - Browser cryptography\n- [Caddy](https://caddyserver.com/) - Modern web server\n\n---\n\n## 💡 Alternatives\n\n- [One-Time Secret](https://onetimesecret.com/) - Original service (hosted)\n- [Password Pusher](https://github.com/pglombardo/PasswordPusher) - Similar self-hosted option\n- [Vaultwarden Send](https://github.com/dani-garcia/vaultwarden) - Bitwarden feature\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n**[⬆ Back to Top](#-one-time-secret)**\n\nBuilt with ❤️ for privacy-conscious users\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fashref-dev%2Fone-time-secret","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fashref-dev%2Fone-time-secret","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fashref-dev%2Fone-time-secret/lists"}