turso-cli CVE-2026-48790
MEDIUMSeverity by source
AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Local file read requires an existing local account (AV:L, PR:L); 0o644 default needs no special conditions (AC:L); credential theft yields full confidentiality loss with no integrity or availability impact on the local system.
Primary rating from GitHub Advisory.
CVSS VectorGitHub Advisory
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Lifecycle Timeline
3DescriptionGitHub Advisory
Summary
turso-cli persists the user's Turso platform JWT to settings.json using Viper's default configPermissions of 0o644, leaving the credential file world-readable on standard Linux and macOS systems. Any other local UID on the host can read the file and recover the platform JWT, which grants full Turso platform access scoped to the user's organizations.
Impact
The token in settings.json grants the holder full Turso platform access - create or destroy databases, rotate credentials, exfiltrate data, change billing settings - for any organization the user belongs to.
Because the file is world-readable, the credential is reachable by:
- Cron jobs or daemons running as a different system user on the same host
- Sandboxed CI runners with a mounted home directory
- Containers with a bind-mounted host home
- Co-tenants on a shared multi-user developer or jumpbox host
The file path resolves through configdir.LocalConfig("turso"):
- macOS:
~/Library/Application Support/turso/settings.json - Linux:
~/.config/turso/settings.json(or$XDG_CONFIG_HOME/turso/settings.json)
It contains the platform JWT in plaintext JSON alongside organization and username fields.
Comparable CLIs (gh, aws, docker, gcloud, plus close peers planetscale, neon, upstash) write credential files at 0o600 explicitly, so this is a deviation from the cross-vendor baseline rather than a deliberate trade-off.
Details
The OAuth callback handler stores the platform JWT via the settings layer:
// internal/cmd/auth.go:205-214
jwt, err := callbackServer.Result()
...
settings.SetToken(jwt)SetToken writes through Viper:
// internal/settings/settings.go:124-127
func (s *Settings) SetToken(token string) {
viper.Set("token", token)
s.changed = true
}Persistence runs through viper.WriteConfig:
// internal/settings/settings.go:96-101
func TryToPersistChanges() error {
if err := viper.WriteConfig(); err != nil {
return fmt.Errorf("failed to persist turso settings file: %w", err)
}
return nil
}Viper v1.21.0 (pinned in turso-cli go.mod) initializes configPermissions to os.FileMode(0o644) at viper.go:198 and passes that mode straight to os.OpenFile at viper.go:1688. Without a call to viper.SetConfigPermissions(0o600), the resulting settings.json is created at 0o644.
A grep over the auth-config write path under internal/ returns zero hits for Chmod, 0o600, or 0600, confirming there is no follow-up tightening of the file mode anywhere on the persistence path.
Proof of concept
Minimal reproducer using the same Viper version turso-cli pins (github.com/spf13/viper v1.21.0):
package main
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/viper"
)
func main() {
dir, _ := os.MkdirTemp("", "viperpoc-*")
defer os.RemoveAll(dir)
viper.SetConfigName("settings")
viper.SetConfigType("json")
viper.AddConfigPath(dir)
viper.Set("token", "FAKE_TURSO_JWT_xxxxxxxxxxxxxxxxxxxx")
viper.Set("organization", "exampleorg")
viper.SafeWriteConfig()
st, _ := os.Stat(filepath.Join(dir, "settings.json"))
fmt.Printf("mode: %o\n", st.Mode()&0o777)
}$ go run main.go mode: 644
The same SafeWriteConfig / WriteConfig calls turso-cli uses produce the same 0o644 mode in a real turso auth login flow.
Remediation
One-line fix at the existing Viper configuration site in internal/settings/settings.go (around lines 48-50):
viper.SetConfigName("settings")
viper.SetConfigType("json")
viper.AddConfigPath(configPath)
viper.SetConfigPermissions(0o600) // restrict settings.json to owner onlyDefense in depth:
- Add
os.Chmod(configFile, 0o600)afterTryToPersistChanges, or on read (as PlanetScale does ininternal/config/config.go- theyStatthe token file and self-heal ifMode() &^ 0o600is nonzero).viper.SetConfigPermissionsapplies only on file creation, so an existing wider-mode file is not tightened otherwise. - Add
os.Chmod(configPath, 0o700)afterconfigdir.MakePath(configPath)(line 43) to close the equivalent gap on the enclosing directory, which is otherwise created under the default umask.
Patch: https://github.com/tursodatabase/turso-cli/commit/ffb914849216ef5a86353b3fa6cee66f33af3b66
Workarounds
Until upgraded, users can tighten the existing files manually:
# Linux
chmod 600 ~/.config/turso/settings.json
chmod 700 ~/.config/turso
# macOS
chmod 600 "$HOME/Library/Application Support/turso/settings.json"
chmod 700 "$HOME/Library/Application Support/turso"This must be repeated after any operation that recreates the file (e.g. turso auth login) until the patched version is installed.
Resources
- Patch commit: https://github.com/tursodatabase/turso-cli/commit/ffb914849216ef5a86353b3fa6cee66f33af3b66
- Viper
configPermissionsdefault: https://github.com/spf13/viper/blob/v1.21.0/viper.go#L198 - Viper write path: https://github.com/spf13/viper/blob/v1.21.0/viper.go#L1688
- CWE-276: https://cwe.mitre.org/data/definitions/276.html
- CWE-732: https://cwe.mitre.org/data/definitions/732.html
AnalysisAI
Credential exposure in turso-cli versions 1.0.25 and earlier allows any local user on the same host to read the Turso platform JWT stored world-readable at mode 0o644 in settings.json, granting full access to all Turso organizations the victim belongs to. The root cause is turso-cli's failure to override Viper's insecure default configPermissions before writing credentials - a deviation from the explicit 0o600 baseline established by comparable CLIs including gh, aws, docker, and gcloud. …
Unlock full vulnerability intelligence
- Risk assessment & exploitation conditions
- Attack chain visualization
- Remediation with exact patch versions
- Threat intelligence from 22 sources
- Personal watchlist & email alerts
Free forever · No credit card required
Attack ChainAIDerived
Hypothetical attack flow derived from CVE metadata
Vulnerability AssessmentAI
| Exploitation | The attacker must hold any valid local UID on the same host as the turso-cli user and must be able to read the victim's configuration directory - satisfied by default on Linux and macOS where home directories are typically mode 0o755. … Additional conditions and limiting factors are described in the full assessment. |
| Risk Assessment | The NVD CVSS 3.1 score of 5.5 with vector AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N accurately represents the local-access prerequisite and high-confidentiality impact. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in. |
| Exploit Scenario | A co-tenant on a shared Linux developer host or a CI job running on a runner with a mounted home directory executes cat ~/.config/turso/settings.json and trivially recovers the plaintext JWT. The attacker then presents the token as a Bearer credential against the Turso platform API to enumerate, query, or delete all databases across any organization the victim belongs to, or rotates credentials to lock the legitimate user out. … |
| Remediation | Upgrade turso-cli to version 1.0.26, which includes the vendor-released patch at https://github.com/tursodatabase/turso-cli/commit/ffb914849216ef5a86353b3fa6cee66f33af3b66, adding viper.SetConfigPermissions(0o600) to the Viper initialization block in internal/settings/settings.go. … Detailed patch versions, workarounds, and compensating controls in full report. |
Threat intelligence, references, and detailed analysis are available after sign-in.
An issue was discovered in Appsmith before 1.52. Rated critical severity (CVSS 9.8), this vulnerability is remotely expl
runc through version 1.0-rc6 (used in Docker before 18.09.2) contains a container escape vulnerability that allows attac
Netmaker makes networks with WireGuard. Rated high severity (CVSS 7.5), this vulnerability is remotely exploitable, no a
Unauthenticated remote code execution in Marimo ≤0.20.4 allows attackers to execute arbitrary system commands via the `/
The News & Blog Designer Pack - WordPress Blog Plugin - (Blog Post Grid, Blog Post Slider, Blog Post Carousel, Blog Post
Docker 1.3.2 allows remote attackers to execute arbitrary code with root privileges via a crafted (1) image or (2) build
Remote code execution in NocoBase Workflow Script Node (npm @nocobase/plugin-workflow-javascript) allows authenticated l
Docker Desktop Community Edition before 2.1.0.1 allows local users to gain privileges by placing a Trojan horse docker-c
Vasion Print (formerly PrinterLogic) Virtual Appliance Host prior to version 25.2.169 and Application prior to version 2
An issue in Plone Docker Official Image 5.2.13 (5221) open-source software that could allow for remote code execution du
Tandoor Recipes is an application for managing recipes, planning meals, and building shopping lists. Rated critical seve
Unauthenticated remote code execution in 9router (npm package) versions 0.4.30 through 0.4.36 allows network-adjacent at
Same weakness CWE-276 – Incorrect Default Permissions
View allSame technique Privilege Escalation
View allShare
External POC / Exploit Code
Leaving vuln.today
GHSA-57f6-pvx8-hwj6