Skip to main content
The m87 security model is built around three core principles: outbound-only connectivity, zero trust architecture, and cryptographic authentication.

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:
Device (behind NAT/firewall)
  └─[outbound only]──> m87 Server <──[authenticated]── CLI User
Benefits:
  • 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
Even though connections are outbound-only, m87 still provides full bidirectional communication for shells, file transfers, and port forwarding.

Network requirements

For devices:
  • Outbound TCP/UDP on port 443 to m87 server
  • No inbound ports required
  • No special firewall rules needed
For CLI users:
  • 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:
  1. User runs m87 login
  2. CLI initiates OAuth2 device authorization
  3. User visits verification URL in browser
  4. User enters device code and authenticates
  5. CLI receives OAuth2 token (access + refresh)
  6. Token stored locally in ~/.config/m87/credentials.json
Implementation details:
pub struct OAuth2Token {
    pub access_token: String,
    pub refresh_token: Option<String>,
    pub expires_at: u64,  // Unix timestamp
}
Tokens are:
  • Stored with 0o600 permissions (read/write for owner only)
  • Automatically refreshed when expired
  • Contain standard OAuth2 scopes: openid, offline_access, email, profile
Security features:
  • 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
For CI/CD or automation, you can use API keys via the M87_API_KEY environment variable instead of interactive OAuth.

Device authentication

Devices use an approval-based registration flow with API key credentials: Registration flow:
  1. Runtime runs m87 runtime run --email admin@example.com
  2. 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)
  3. Server creates pending request with unique request_id
  4. Admin approves via CLI: m87 devices approve <request-id>
  5. Server generates API key for device
  6. Runtime polls server, receives API key
  7. API key stored in ~/.config/m87/credentials.json
Device credentials:
pub struct APIKey {
    api_key: String,
}
Device API keys provide persistent access. Treat them as sensitive credentials. If a device is compromised, immediately reject/remove it via m87 devices reject <device-id>.

Access control model

m87 uses a scope-based access control system: Scopes:
  • user:<email>: Personal devices owned by a user
  • org:<org-id>: Organization-wide devices
Access rules:
  • Users can access devices they own (owner_scope matches their user scope)
  • Users can access devices shared with them (allowed_scopes includes their scope)
  • Organization admins can access all org devices
Implementation:
pub trait AccessControlled {
    fn owner_scope_field() -> &'static str;
    fn allowed_scopes_field() -> Option<&'static str>;
    
    fn access_filter(scopes: &Vec<String>) -> Document {
        doc! {
            "$or": [
                { Self::owner_scope_field(): { "$in": scopes } },
                { field: { "$in": scopes } }
            ]
        }
    }
}
Every API request validates that the authenticated user’s scopes permit access to the requested device.
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
Device ↔ Server:
  • QUIC with TLS 1.3 (via Quinn/Rustls)
  • Certificate validation enforced
  • Perfect forward secrecy
  • 0-RTT resumption disabled (security over latency)
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
QUIC uses TLS 1.3 internally, providing the same cryptographic guarantees as HTTPS.

Certificate handling

By default, m87 validates server certificates against system root CAs:
# Production use (validates certificates)
m87 login
m87 runtime run
For self-hosted development environments, you can disable validation:
# Development only - trust invalid certificates
export M87_TRUST_INVALID_CERT=true
m87 login
Only use M87_TRUST_INVALID_CERT=true in controlled development environments. Production deployments should always use valid TLS certificates.

Credential storage

Local credential file

All credentials stored in ~/.config/m87/credentials.json: Structure:
{
  "credentials": {
    "OAuth2Token": {
      "access_token": "eyJ...",
      "refresh_token": "eyJ...",
      "expires_at": 1709500000
    }
  },
  "device_credentials": {
    "api_key": "m87_..."
  }
}
Security measures:
  • 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:
# Device API key
export M87_API_KEY=m87_your_api_key_here
m87 runtime run  # Uses key from environment

# Owner reference (for device registration)
export OWNER_REFERENCE=admin@example.com
m87 runtime run  # Registers under this owner
When using environment variables in CI/CD, ensure they’re stored in secure secret management systems (GitHub Secrets, Vault, etc.), not committed to repositories.

Server-side security

JWT validation

The m87 server validates all API requests using JWT tokens: Validation steps:
  1. Extract JWT from Authorization: Bearer header
  2. Verify signature using JWKS from OAuth provider
  3. Validate claims (issuer, audience, expiration)
  4. Extract user scopes from token
  5. Apply access control rules
JWT claims structure:
pub struct Claims {
    pub sub: String,      // User ID
    pub email: String,    // User email
    pub exp: u64,         // Expiration timestamp
    pub iss: String,      // Issuer URL
    pub aud: Vec<String>, // Audience
    // Custom claims for scopes/roles
}

Connection state management

The server maintains secure tunnel state:
pub struct RelayState {
    tunnels: Arc<RwLock<HashMap<String, Connection>>>,
    lost: Arc<RwLock<HashMap<String, ()>>>,
}
Security features:
  • 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:
# View who accessed your device
m87 <device> audit --details
Audit logs capture:
  • User identity and timestamp
  • Actions performed (shell, exec, file access)
  • Duration of sessions
  • Source IP addresses
pub struct AuditLog {
    pub device_id: String,
    pub user_id: String,
    pub action: AuditAction,
    pub timestamp: DateTime<Utc>,
    pub source_ip: Option<String>,
    pub session_duration: Option<Duration>,
}

pub enum AuditAction {
    Shell,
    Exec { command: String },
    FileAccess { path: String, operation: FileOp },
    PortForward { ports: Vec<u16> },
}

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:
  1. Do not open a public GitHub issue
  2. Email security@make87.com with details
  3. Include steps to reproduce if possible
  4. Allow time for patching before public disclosure
We take security seriously and will respond promptly to verified reports.