{"record":{"id":"7b9da8cffe2c01ce","repo":"Eugeny/tabby","slug":"config-sync-host-must-use-https-got-host","errorCode":null,"errorMessage":"Config sync host must use HTTPS (got: ${host})","messagePattern":"Config sync host must use HTTPS \\(got: (.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tabby-settings/src/services/configSync.service.ts","lineNumber":174,"sourceCode":"        await this.platform.saveConfig(yaml.dump(data))\n        await this.config.load()\n        await this.config.save()\n    }\n\n    private async request (method: 'GET'|'POST'|'PATCH'|'DELETE', url: string, { data }: { data?: any } = {}) {\n        if (this.config.store.configSync.host.endsWith('/')) {\n            this.config.store.configSync.host = this.config.store.configSync.host.slice(0, -1)\n        }\n        const host: string = this.config.store.configSync.host\n        // Refuse to sync configuration over a plaintext channel. The remote\n        // payload is parsed as YAML and merged into the local config (including\n        // profiles whose `command`/`env` are later executed by the terminal),\n        // so a network attacker able to MITM cleartext HTTP could achieve\n        // arbitrary command execution on the next sync. Require HTTPS.\n        if (!/^https:\\/\\//i.test(host)) {\n            const message = `Config sync host must use HTTPS (got: ${host})`\n            this.logger.error(message)\n            throw new Error(message)\n        }\n        url = host + url\n        this.logger.debug(`${method} ${url}`, data)\n        try {\n            const response = await fetch(url, {\n                method,\n                headers: {\n                    Authorization: `Bearer ${this.config.store.configSync.token}`,\n                    ...data !== undefined ? { 'Content-Type': 'application/json' } : {},\n                },\n                body: data !== undefined ? JSON.stringify(data) : undefined,\n            })\n            if (!response.ok) {\n                throw new Error(`${method} ${url} failed: ${response.status} ${response.statusText}`)\n            }\n            this.logger.debug(response)\n            // ponytail: DELETE returns empty 204, parse only if there's a body\n            const text = await response.text()","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/Eugeny/tabby/blob/14e2d60b9b6dee84a53c37f05eefeb803787de04/tabby-settings/src/services/configSync.service.ts#L156-L192","documentation":"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.","triggerScenarios":"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`.","commonSituations":"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.","solutions":["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).","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.","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.","Validate the host at config-save time and show a UI error before sync is ever attempted."],"exampleFix":"// before\nif (!/^https:\\/\\//i.test(host)) throw new Error(`Config sync host must use HTTPS (got: ${host})`)\n\n// config validation at save time\nif (store.configSync?.enabled && !/^https:\\/\\//i.test(store.configSync.host)) {\n    notifications.error('Config sync host must be an https:// URL')\n    return false\n}","handlingStrategy":"validation","validationCode":"function isValidHttpsHost (host: string): boolean {\n    return /^https:\\/\\//i.test(host)\n}\n\nconst host = config.store.configSync.host\nif (config.store.configSync.enabled && !isValidHttpsHost(host)) {\n    throw new Error('Config sync requires an https:// host')\n}","typeGuard":"function isHttpsUrl (u: string): boolean { return /^https:\\/\\//i.test(u) }","tryCatchPattern":null,"preventionTips":["Always configure configSync.host with https:// (use a TLS-terminating proxy if needed).","Validate the host at config-save time so the user is warned before any sync attempt.","Never disable the HTTPS check - synced config is executed and plaintext is unsafe.","For local dev, front the server with mkcert/Caddy/ngrok to obtain an https URL."],"tags":["security","config-sync","https","network","configuration","mitm"],"backgroundTag":null,"analyzedSha":"14e2d60b9b6dee84a53c37f05eefeb803787de04","analyzedAt":"2026-08-12T11:46:48.773Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}