Eugeny/tabby · error · Error

Config sync host must use HTTPS (got: ${host})

Error message

Config sync host must use HTTPS (got: ${host})

What it means

Thrown by `ConfigSyncService`'s HTTP helper when the configured sync host does not begin with `https://` (case-insensitive). This is a deliberate security control: the synced payload is YAML merged into local config, including profiles whose `command`/`env` are later executed by the terminal, so a MITM on plaintext HTTP could achieve arbitrary command execution. The check refuses to send over an insecure channel.

Source

Thrown at tabby-settings/src/services/configSync.service.ts:174

        await this.platform.saveConfig(yaml.dump(data))
        await this.config.load()
        await this.config.save()
    }

    private async request (method: 'GET'|'POST'|'PATCH'|'DELETE', url: string, { data }: { data?: any } = {}) {
        if (this.config.store.configSync.host.endsWith('/')) {
            this.config.store.configSync.host = this.config.store.configSync.host.slice(0, -1)
        }
        const host: string = this.config.store.configSync.host
        // Refuse to sync configuration over a plaintext channel. The remote
        // payload is parsed as YAML and merged into the local config (including
        // profiles whose `command`/`env` are later executed by the terminal),
        // so a network attacker able to MITM cleartext HTTP could achieve
        // arbitrary command execution on the next sync. Require HTTPS.
        if (!/^https:\/\//i.test(host)) {
            const message = `Config sync host must use HTTPS (got: ${host})`
            this.logger.error(message)
            throw new Error(message)
        }
        url = host + url
        this.logger.debug(`${method} ${url}`, data)
        try {
            const response = await fetch(url, {
                method,
                headers: {
                    Authorization: `Bearer ${this.config.store.configSync.token}`,
                    ...data !== undefined ? { 'Content-Type': 'application/json' } : {},
                },
                body: data !== undefined ? JSON.stringify(data) : undefined,
            })
            if (!response.ok) {
                throw new Error(`${method} ${url} failed: ${response.status} ${response.statusText}`)
            }
            this.logger.debug(response)
            // ponytail: DELETE returns empty 204, parse only if there's a body
            const text = await response.text()

View on GitHub (pinned to 14e2d60b9b)

Solutions

  1. Configure the sync host with HTTPS: set `configSync.host` to a `https://...` URL (enable TLS on the server or use a reverse proxy with a valid cert).
  2. For local development, put a TLS-terminating proxy (e.g. Caddy, mkcert, ngrok) in front of the dev server and point the host at its https URL.
  3. Do NOT weaken the check by allowing http - the guard exists because synced config is executed; if you truly must use plaintext on an isolated network, accept that you are bypassing a security control.
  4. Validate the host at config-save time and show a UI error before sync is ever attempted.

Example fix

// before
if (!/^https:\/\//i.test(host)) throw new Error(`Config sync host must use HTTPS (got: ${host})`)

// config validation at save time
if (store.configSync?.enabled && !/^https:\/\//i.test(store.configSync.host)) {
    notifications.error('Config sync host must be an https:// URL')
    return false
}
Defensive patterns

Strategy: validation

Validate before calling

function isValidHttpsHost (host: string): boolean {
    return /^https:\/\//i.test(host)
}

const host = config.store.configSync.host
if (config.store.configSync.enabled && !isValidHttpsHost(host)) {
    throw new Error('Config sync requires an https:// host')
}

Type guard

function isHttpsUrl (u: string): boolean { return /^https:\/\//i.test(u) }

Prevention

When it happens

Trigger: Setting `configSync.host` to an `http://` URL (or any non-https scheme) and triggering any sync operation (push/pull/auto-sync). The regex `/^https:\/\//i` fails and the request is aborted before `fetch`.

Common situations: User pastes a self-hosted sync URL without TLS; using `http://` for a localhost dev server; misconfigured reverse proxy that terminates TLS but the configured host still says http; copy-paste from a tool that strips the scheme.

Related errors


AI-assisted analysis of Eugeny/tabby@14e2d60b9b (2026-08-12). Data as JSON: /api/errors/7b9da8cffe2c01ce. Report an issue: GitHub.