https://github.com/ashref-dev/one-time-secret
Secure, self-hosted secret sharing with zero-knowledge encryption
https://github.com/ashref-dev/one-time-secret
Last synced: about 2 months ago
JSON representation
Secure, self-hosted secret sharing with zero-knowledge encryption
- Host: GitHub
- URL: https://github.com/ashref-dev/one-time-secret
- Owner: Ashref-dev
- License: mit
- Created: 2026-02-05T00:31:00.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2026-03-19T07:00:02.000Z (3 months ago)
- Last Synced: 2026-03-20T00:17:34.477Z (3 months ago)
- Language: Go
- Size: 749 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Security: SECURITY.md
Awesome Lists containing this project
README
# π One-Time Secret
**Secure, self-hosted secret sharing with zero-knowledge encryption**
[](https://github.com/Ashref-dev/one-time-secret/actions)
[](https://opensource.org/licenses/MIT)
[](https://golang.org)
[](https://www.docker.com)
[Demo](#demo) β’ [Features](#features) β’ [Quick Start](#quick-start) β’ [Security](#security) β’ [API](#api)
---
## π Overview
One-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.
### π¨ Beautiful Design
Built with a minimalist, muted purple aesthetic that works in both light and dark modes.

---
## β¨ Features
### π Security First
- **Client-Side Encryption** - AES-256-GCM encryption in the browser
- **Zero-Knowledge** - Server never sees plaintext or keys
- **One-Time Access** - Secrets are destroyed immediately after viewing
- **Automatic Expiration** - Configurable TTL (5 min - 24 hours)
- **Optional Passphrase** - Additional layer with PBKDF2 key derivation
### π Production Ready
- **Atomic Operations** - Row-level database locking prevents race conditions
- **Rate Limiting** - Configurable per-IP read, write, and agent request limits
- **Structured Logging** - JSON logs with slog
- **Health Checks** - Kubernetes-ready endpoints
- **Docker Compose** - One-command deployment
- **Agent-Friendly API** - Plaintext/file uploads plus a served `/agents.txt` guide
### π οΈ Tech Stack
- **Backend:** Go (Chi router, pgx PostgreSQL driver)
- **Frontend:** React + TypeScript + Vite
- **Crypto:** WebCrypto API (AES-256-GCM)
- **Database:** PostgreSQL (latest)
- **Proxy:** Caddy (automatic HTTPS ready)
---
## π Quick Start
### Prerequisites
- Docker & Docker Compose
- ~2GB RAM available
### 1. Clone & Configure
```bash
git clone https://github.com/Ashref-dev/one-time-secret.git
cd ots
# Copy and edit configuration
cp .env.example .env
# Edit DB_PASSWORD to something secure
nano .env
```
### 2. Deploy
```bash
# Start all services
docker-compose up -d
# Check logs
docker-compose logs -f
# Verify health
curl http://localhost:3069/health
```
The application will be available at `http://localhost:3069`
### 3. Configure HTTPS (Production)
Edit `caddy/Caddyfile`:
```
yourdomain.com {
tls your-email@example.com
# ... rest of config
}
```
Restart: `docker-compose restart caddy`
---
## ποΈ Architecture
```
βββββββββββββββ ββββββββββββββββ βββββββββββββββ
β Browser ββββββΆβ Caddy ββββββΆβ Backend β
β (Encrypt) β β (Proxy) β β (Go) β
βββββββββββββββ ββββββββββββββββ ββββββββ¬βββββββ
β β
β 1. Encrypt secret β
β with AES-256-GCM β
β β
β 2. Send ciphertext only β
ββββββββββββββββββββββββββββββββββββββββββΆβ
β β
β 3. Store encrypted data β
βββββββββββββββββββββββββββββββββββββββββββ
β (key stays in URL fragment) β
β β
β 4. Share link β
ββββββββββββββββββββββββββββββββββββββββββΆβ
β β
β 5. Request secret β
ββββββββββββββββββββββββββββββββββββββββββΆβ
β β
β 6. Delete & return ciphertext β
βββββββββββββββββββββββββββββββββββββββββββ
β (atomic operation) β
β β
β 7. Decrypt in browser β
β with key from URL β
Key Features:
- Encryption key never sent to server
- Atomic consume prevents double retrieval
- Row-level locking prevents race conditions
- No plaintext ever touches the server
```
---
## π Security
### Encryption
- **Algorithm:** AES-256-GCM (authenticated encryption)
- **Key Generation:** Cryptographically secure random (Crypto.getRandomValues)
- **Key Derivation:** PBKDF2 with 100,000 iterations (for passphrase mode)
- **IV:** 12-byte random nonce per encryption
### Data Handling
| Aspect | Implementation |
|--------|----------------|
| Storage | Ciphertext only (no keys, no plaintext) |
| Transport | HTTPS required, HSTS enforced |
| Access | Single-use, immediate deletion |
| Expiration | Automatic cleanup after TTL |
| Logging | No secret content logged |
### Headers
```
Content-Security-Policy: default-src 'self'...
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=31536000
```
See [SECURITY.md](SECURITY.md) for detailed security information.
---
## π API Documentation
### Agent Convenience API
```http
POST /api/agent/secrets
```
Use 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.
- Accepts `application/json`, `text/plain`, and `multipart/form-data`
- Defaults to 1 day expiration
- Always creates a one-time secret
- Supports an optional passphrase
**Response:**
```json
{
"id": "abc123...",
"url": "https://ots.ashref.tn/s/abc123#fragment-key",
"expires_at": "2026-03-20T12:00:00Z",
"expires_in": 86400,
"passphrase_required": false
}
```
**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.
### Create Secret
```http
POST /api/secrets
Content-Type: application/json
{
"ciphertext": "base64_aes_gcm_ciphertext",
"iv": "base64_12_byte_iv",
"salt": "base64_salt_if_passphrase_used",
"expires_in": 3600,
"burn_after_read": true
}
```
**Response:**
```json
{
"id": "abc123..."
}
```
### Retrieve Secret (Atomic Consume)
```http
GET /api/secrets/{id}
```
**Response:**
```json
{
"ciphertext": "base64_aes_gcm_ciphertext",
"iv": "base64_12_byte_iv",
"salt": "base64_salt_if_used"
}
```
**Note:** Secret is deleted immediately upon retrieval.
### Burn Secret
```http
DELETE /api/secrets/{id}
```
**Response:** `204 No Content`
---
## βοΈ Configuration
### Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `DB_PASSWORD` | - | **Required:** PostgreSQL password |
| `DB_USER` | `ots_user` | PostgreSQL username |
| `DB_NAME` | `ots_db` | PostgreSQL database |
| `MAX_SECRET_SIZE` | `32768` | Max secret size in bytes (32KB) |
| `DEFAULT_TTL` | `3600` | Default TTL in seconds (1 hour) |
| `AGENT_DEFAULT_TTL` | `86400` | Default TTL for the agent convenience endpoint |
| `RATE_LIMIT_REQUESTS` | `30` | Legacy shared rate limit fallback for older configs |
| `RATE_LIMIT_WINDOW` | `60` | Legacy shared rate limit fallback window |
| `RATE_LIMIT_WRITE_REQUESTS` | `30` | Create/burn requests per write window per IP |
| `RATE_LIMIT_WRITE_WINDOW` | `60` | Write rate limit window in seconds |
| `RATE_LIMIT_READ_REQUESTS` | `180` | Read requests per read window per IP |
| `RATE_LIMIT_READ_WINDOW` | `60` | Read rate limit window in seconds |
| `RATE_LIMIT_AGENT_REQUESTS` | `10` | Agent convenience uploads per agent window per IP |
| `RATE_LIMIT_AGENT_WINDOW` | `60` | Agent rate limit window in seconds |
| `PUBLIC_BASE_URL` | - | Optional public origin for generated agent share URLs |
| `LOG_LEVEL` | `info` | Log level (debug/info/warn/error) |
| `ENV` | `production` | Environment mode |
### Docker Compose
```yaml
services:
backend:
environment:
- DATABASE_URL=postgres://ots_user:${DB_PASSWORD}@postgres:5432/ots_db?sslmode=disable
- MAX_SECRET_SIZE=32768
- AGENT_DEFAULT_TTL=86400
- RATE_LIMIT_WRITE_REQUESTS=30
- RATE_LIMIT_READ_REQUESTS=180
- RATE_LIMIT_AGENT_REQUESTS=10
- PUBLIC_BASE_URL=https://ots.ashref.tn
deploy:
resources:
limits:
memory: 256M
reservations:
memory: 128M
```
---
## π§ͺ Development
### Backend
```bash
cd backend
# Install dependencies
go mod download
# Run tests
go test -v ./...
# Run with hot reload (requires air)
air
# Or manually
go run cmd/server/main.go
```
### Frontend
```bash
cd frontend
# Install dependencies
npm install
# Start development server
npm run dev
# Build for production
npm run build
```
### Database Migrations
Migrations run automatically on startup. To run manually:
```bash
docker-compose exec postgres psql -U ots_user -d ots_db -f /docker-entrypoint-initdb.d/000001_init_schema.up.sql
```
---
## π’ Deployment
### Docker Swarm
```bash
# Initialize swarm
docker swarm init
# Deploy stack
docker stack deploy -c docker-compose.yml ots
# Check status
docker stack ps ots
docker service logs ots_backend
```
### Kubernetes
Coming soon. See [k8s/](./k8s/) directory for manifests.
### VPS / Cloud
1. Provision server (1 CPU, 1GB RAM minimum)
2. Install Docker & Docker Compose
3. Clone repository
4. Configure `.env`
5. Run `docker-compose up -d`
6. Configure DNS to point to server
7. Edit Caddyfile with your domain
8. Restart: `docker-compose restart caddy`
---
## π Monitoring
### Health Endpoints
- `GET /health` - Basic health check
- Backend logs structured JSON to stdout
### Log Format
```json
{
"time": "2026-02-04T12:00:00Z",
"level": "INFO",
"msg": "secret created",
"secret_id": "abc123",
"size": 1024,
"ip": "192.168.1.1"
}
```
### Metrics
Export Prometheus metrics (coming soon).
---
## π€ Contributing
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
Please read [SECURITY.md](SECURITY.md) before reporting security issues.
---
## π License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
---
## π Acknowledgments
- [Chi Router](https://github.com/go-chi/chi) - Lightweight Go router
- [pgx](https://github.com/jackc/pgx) - PostgreSQL driver for Go
- [WebCrypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) - Browser cryptography
- [Caddy](https://caddyserver.com/) - Modern web server
---
## π‘ Alternatives
- [One-Time Secret](https://onetimesecret.com/) - Original service (hosted)
- [Password Pusher](https://github.com/pglombardo/PasswordPusher) - Similar self-hosted option
- [Vaultwarden Send](https://github.com/dani-garcia/vaultwarden) - Bitwarden feature
---
**[β¬ Back to Top](#-one-time-secret)**
Built with β€οΈ for privacy-conscious users