Summary
The authentication token is stored in plaintext at ~/.quikdb-frame/auth.json. While file permissions are 0600 (owner only), the token is fully readable as plain JSON and will appear in unencrypted disk backups.
Current behavior
internal/deploy/auth.go:132-137
func SaveAuth(auth *AuthConfig) error {
dir := filepath.Join(homeDir(), configDir)
os.MkdirAll(dir, 0700)
data, _ := json.MarshalIndent(auth, "", " ") // plaintext, human-readable
return os.WriteFile(filepath.Join(dir, configFile), data, 0600)
}
Contents of ~/.quikdb-frame/auth.json:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expiresAt": "2027-01-01T00:00:00Z"
}
Fix
Use the OS-native credential store via the zalando/go-keyring package:
import "github.com/zalando/go-keyring"
const service = "quikdb-frame"
func SaveAuth(auth *AuthConfig) error {
return keyring.Set(service, "token", auth.Token)
}
func LoadAuth() (*AuthConfig, error) {
token, err := keyring.Get(service, "token")
if err != nil { return nil, err }
return &AuthConfig{Token: token}, nil
}
This uses Keychain on macOS, Secret Service on Linux, and Credential Manager on Windows.
Severity
LOW — mitigated by file permissions, but unencrypted backups remain a risk.
Summary
The authentication token is stored in plaintext at
~/.quikdb-frame/auth.json. While file permissions are0600(owner only), the token is fully readable as plain JSON and will appear in unencrypted disk backups.Current behavior
internal/deploy/auth.go:132-137Contents of
~/.quikdb-frame/auth.json:{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expiresAt": "2027-01-01T00:00:00Z" }Fix
Use the OS-native credential store via the
zalando/go-keyringpackage:This uses Keychain on macOS, Secret Service on Linux, and Credential Manager on Windows.
Severity
LOW — mitigated by file permissions, but unencrypted backups remain a risk.