> ## Documentation Index
> Fetch the complete documentation index at: https://docs.make87.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Security model

> How m87 maintains security with outbound-only connections and zero trust architecture

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:

```text theme={null}
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

<Warning>
  Even though connections are outbound-only, m87 still provides full bidirectional communication for shells, file transfers, and port forwarding.
</Warning>

### 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

<Info>
  The outbound-only design means you can deploy m87 on devices in cellular networks, behind carrier-grade NAT, or in locked-down enterprise environments.
</Info>

## 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:**

<Accordion title="OAuth2 token structure">
  ```rust theme={null}
  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`
</Accordion>

**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

<Tip>
  For CI/CD or automation, you can use API keys via the `M87_API_KEY` environment variable instead of interactive OAuth.
</Tip>

### 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:**

```rust theme={null}
pub struct APIKey {
    api_key: String,
}
```

<Warning>
  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>`.
</Warning>

### 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:**

```rust theme={null}
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.

<Info>
  This scope-based model allows for flexible multi-tenancy while maintaining strong isolation between users and organizations.
</Info>

## 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)

<Accordion title="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

  QUIC uses TLS 1.3 internally, providing the same cryptographic guarantees as HTTPS.
</Accordion>

### Certificate handling

By default, m87 validates server certificates against system root CAs:

```bash theme={null}
# Production use (validates certificates)
m87 login
m87 runtime run
```

For self-hosted development environments, you can disable validation:

```bash theme={null}
# Development only - trust invalid certificates
export M87_TRUST_INVALID_CERT=true
m87 login
```

<Warning>
  Only use `M87_TRUST_INVALID_CERT=true` in controlled development environments. Production deployments should always use valid TLS certificates.
</Warning>

## Credential storage

### Local credential file

All credentials stored in `~/.config/m87/credentials.json`:

**Structure:**

```json theme={null}
{
  "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:

```bash theme={null}
# 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
```

<Tip>
  When using environment variables in CI/CD, ensure they're stored in secure secret management systems (GitHub Secrets, Vault, etc.), not committed to repositories.
</Tip>

## 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:**

```rust theme={null}
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:

```rust theme={null}
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

<Info>
  The server uses `Arc<RwLock<>>` for thread-safe shared state, ensuring consistent tunnel management even under high concurrency.
</Info>

## Audit and logging

m87 provides comprehensive audit trails:

```bash theme={null}
# 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

<Accordion title="Audit log data model">
  ```rust theme={null}
  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> },
  }
  ```
</Accordion>

## Security best practices

### For device operators

<CardGroup cols={2}>
  <Card title="Approve devices carefully">
    Review device information before approving registration requests. Verify hostname and system details match expected devices.
  </Card>

  <Card title="Use organization scopes">
    Register devices under organization IDs rather than personal emails for team access and better lifecycle management.
  </Card>

  <Card title="Monitor audit logs">
    Regularly review device audit logs for unexpected access patterns or unauthorized actions.
  </Card>

  <Card title="Rotate credentials">
    Periodically remove and re-register devices to rotate API keys, especially after team member departures.
  </Card>
</CardGroup>

### For CLI users

<CardGroup cols={2}>
  <Card title="Protect credentials file">
    Never commit `~/.config/m87/credentials.json` to version control. Add to `.gitignore` if working in repo.
  </Card>

  <Card title="Use short-lived sessions">
    Don't leave shell sessions open indefinitely. Exit when done to minimize exposure window.
  </Card>

  <Card title="Verify device identity">
    Check device hostname and system info before running sensitive commands to ensure you're on the intended device.
  </Card>

  <Card title="Log out on shared machines">
    Run `m87 logout` on shared or public machines to clear stored credentials.
  </Card>
</CardGroup>

### For self-hosted deployments

<CardGroup cols={2}>
  <Card title="Use valid TLS certificates">
    Always use certificates from trusted CAs (Let's Encrypt, etc.) for production servers.
  </Card>

  <Card title="Restrict MongoDB access">
    Bind MongoDB to localhost or use network policies to prevent external access.
  </Card>

  <Card title="Set FORWARD_SECRET">
    Configure a strong random secret for signing tunnel tokens in `docker-compose.yml`.
  </Card>

  <Card title="Configure ADMIN_EMAILS">
    Set admin email list to control who can approve devices and manage the platform.
  </Card>
</CardGroup>

## Security disclosure

If you discover a security vulnerability in m87:

1. **Do not** open a public GitHub issue
2. Email [security@make87.com](mailto: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.
