Severity 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 Vendor (https://github.com/tursodatabase/turso-cli).
CVSS VectorVendor: https://github.com/tursodatabase/turso-cli
Lifecycle Timeline
3DescriptionCVE.org
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. A proof-of-concept demonstrating the 0o644 mode is included in the advisory; no active exploitation is listed in CISA KEV.
Technical ContextAI
turso-cli is a Go-based command-line tool for the Turso hosted SQLite platform, using the Viper configuration library (v1.21.0, pinned in go.mod) for settings persistence. Viper initializes its internal configPermissions field to os.FileMode(0o644) at viper.go:198 and passes this mode directly to os.OpenFile at viper.go:1688 when writing config files. The turso-cli auth flow captures the platform JWT in the OAuth callback (internal/cmd/auth.go:205-214), writes it via viper.Set('token', jwt) through the settings layer (internal/settings/settings.go:124-127), and persists it with viper.WriteConfig() (internal/settings/settings.go:96-101) - without ever calling viper.SetConfigPermissions(0o600) to override the insecure default. A code audit confirmed zero occurrences of Chmod, 0o600, or 0600 on the auth-config write path under internal/. CWE-276 (Incorrect Default Permissions) is the primary root cause, with CWE-732 (Incorrect Permission Assignment for Critical Resource) also applicable given the credential nature of the file. The affected package is pkg:go/github.com/tursodatabase/turso-cli at versions <= 1.0.25.
RemediationAI
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. Critically, viper.SetConfigPermissions applies only on file creation - existing settings.json files with 0o644 mode are not automatically tightened by upgrading alone. After upgrading, run chmod 600 ~/.config/turso/settings.json && chmod 700 ~/.config/turso on Linux, or chmod 600 "$HOME/Library/Application Support/turso/settings.json" && chmod 700 "$HOME/Library/Application Support/turso" on macOS. This manual step must also be repeated after any turso auth login that recreates the file on an unpatched installation. As defense in depth, the vendor advisory recommends also adding os.Chmod(configFile, 0o600) after TryToPersistChanges to self-heal pre-existing files with overly broad modes, following the pattern used by the PlanetScale CLI.
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
Path traversal in JFrog Artifactory (CWE-22) enables an authenticated low-privilege user to write data outside the inten
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 Gogs through 0.14.2 allows authenticated users (and unauthenticated attackers on default-config
Remote code execution in Flowise before 3.1.2 allows any authenticated user (or API caller with chatflow view/update per
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
Same weakness CWE-276 – Incorrect Default Permissions
View allSame technique Privilege Escalation
View allVendor StatusVendor
SUSE
Severity: Moderate| Product | Status |
|---|---|
| SUSE Linux Enterprise Server 16.1 | Affected |
| SUSE Linux Enterprise Server for SAP applications 16.1 | Affected |
| SUSE Linux Enterprise Module for Package Hub 15 SP5 | Affected |
| SUSE Linux Enterprise Module for Package Hub 15 SP6 | Affected |
| openSUSE Leap 15.5 | Affected |
| openSUSE Leap 15.6 | Affected |
Share
External POC / Exploit Code
Leaving vuln.today
EUVD-2026-56787
GHSA-57f6-pvx8-hwj6