Outbound-only access
One of m87’s defining features is that devices never accept inbound connections.How it works
Devices initiate and maintain a persistent outbound QUIC connection to the m87 server:- No inbound ports to open on device networks
- Works through NATs, firewalls, and restrictive networks
- Reduces attack surface significantly
- Eliminates need for VPNs or dynamic DNS
Network requirements
For devices:- Outbound TCP/UDP on port 443 to m87 server
- No inbound ports required
- No special firewall rules needed
- HTTPS access to m87 server API (port 8085 or 443)
- Outbound connection for tunnel relay
The outbound-only design means you can deploy m87 on devices in cellular networks, behind carrier-grade NAT, or in locked-down enterprise environments.
Authentication and authorization
User authentication
m87 uses OAuth2 device flow for CLI users, integrating with standard identity providers: Flow:- User runs
m87 login - CLI initiates OAuth2 device authorization
- User visits verification URL in browser
- User enters device code and authenticates
- CLI receives OAuth2 token (access + refresh)
- Token stored locally in
~/.config/m87/credentials.json
OAuth2 token structure
OAuth2 token structure
- Stored with
0o600permissions (read/write for owner only) - Automatically refreshed when expired
- Contain standard OAuth2 scopes:
openid,offline_access,email,profile
- No password handling in m87 (delegated to OAuth provider)
- Automatic token refresh using refresh tokens
- Tokens expire (typically 1 hour for access tokens)
- Supports Auth0 and standard OIDC providers
Device authentication
Devices use an approval-based registration flow with API key credentials: Registration flow:- Runtime runs
m87 runtime run --email admin@example.com - Runtime sends registration request to server with:
- Device system information (hostname, platform, architecture)
- Requested owner scope (user email or org ID)
- Unique device ID (generated locally)
- Server creates pending request with unique
request_id - Admin approves via CLI:
m87 devices approve <request-id> - Server generates API key for device
- Runtime polls server, receives API key
- API key stored in
~/.config/m87/credentials.json
Access control model
m87 uses a scope-based access control system: Scopes:user:<email>: Personal devices owned by a userorg:<org-id>: Organization-wide devices
- Users can access devices they own (
owner_scopematches their user scope) - Users can access devices shared with them (
allowed_scopesincludes their scope) - Organization admins can access all org devices
This scope-based model allows for flexible multi-tenancy while maintaining strong isolation between users and organizations.
Transport security
Encryption in transit
All communication uses industry-standard encryption: CLI ↔ Server:- HTTPS with TLS 1.2+ (via Rustls)
- Certificate validation enforced
- No legacy cipher suites
- QUIC with TLS 1.3 (via Quinn/Rustls)
- Certificate validation enforced
- Perfect forward secrecy
- 0-RTT resumption disabled (security over latency)
Why QUIC?
Why QUIC?
QUIC provides several security advantages over traditional TLS/TCP:
- Built-in encryption: All packets encrypted by default
- Connection migration: Handles network changes without re-authentication
- Reduced handshake: Faster establishment with equivalent security
- Multiplexing: Multiple streams without head-of-line blocking
Certificate handling
By default, m87 validates server certificates against system root CAs:Credential storage
Local credential file
All credentials stored in~/.config/m87/credentials.json:
Structure:
- File permissions set to
0o600(owner read/write only) - Located in user config directory (not world-readable)
- Separate CLI and device credentials
- Parent directories created with restrictive permissions
Environment variable support
For automation and CI/CD:Server-side security
JWT validation
The m87 server validates all API requests using JWT tokens: Validation steps:- Extract JWT from
Authorization: Bearerheader - Verify signature using JWKS from OAuth provider
- Validate claims (issuer, audience, expiration)
- Extract user scopes from token
- Apply access control rules
Connection state management
The server maintains secure tunnel state:- Connection replacement: New connection closes old one atomically
- Stale connection protection: Tracks connection IDs to prevent race conditions
- Lost device detection: Marks devices as unavailable on disconnect
- Memory safety: Rust’s ownership model prevents use-after-free bugs
The server uses
Arc<RwLock<>> for thread-safe shared state, ensuring consistent tunnel management even under high concurrency.Audit and logging
m87 provides comprehensive audit trails:- User identity and timestamp
- Actions performed (shell, exec, file access)
- Duration of sessions
- Source IP addresses
Audit log data model
Audit log data model
Security best practices
For device operators
Approve devices carefully
Review device information before approving registration requests. Verify hostname and system details match expected devices.
Use organization scopes
Register devices under organization IDs rather than personal emails for team access and better lifecycle management.
Monitor audit logs
Regularly review device audit logs for unexpected access patterns or unauthorized actions.
Rotate credentials
Periodically remove and re-register devices to rotate API keys, especially after team member departures.
For CLI users
Protect credentials file
Never commit
~/.config/m87/credentials.json to version control. Add to .gitignore if working in repo.Use short-lived sessions
Don’t leave shell sessions open indefinitely. Exit when done to minimize exposure window.
Verify device identity
Check device hostname and system info before running sensitive commands to ensure you’re on the intended device.
Log out on shared machines
Run
m87 logout on shared or public machines to clear stored credentials.For self-hosted deployments
Use valid TLS certificates
Always use certificates from trusted CAs (Let’s Encrypt, etc.) for production servers.
Restrict MongoDB access
Bind MongoDB to localhost or use network policies to prevent external access.
Set FORWARD_SECRET
Configure a strong random secret for signing tunnel tokens in
docker-compose.yml.Configure ADMIN_EMAILS
Set admin email list to control who can approve devices and manage the platform.
Security disclosure
If you discover a security vulnerability in m87:- Do not open a public GitHub issue
- Email security@make87.com with details
- Include steps to reproduce if possible
- Allow time for patching before public disclosure