Back to Blog
high SEVERITY3 min read

SQLite Auth Database Plaintext Storage in InitAuthDB()

The `InitAuthDB()` function opened SQLite databases without restricting filesystem permissions, leaving bcrypt password hashes, session tokens, and user settings exposed to any local account with read access. The fix explicitly sets `os.Chmod(filepath, 0600)` immediately after database creation, ensuring only the owner can access authentication secrets.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published October 7, 2026•Reviewed October 7, 2026

Answer Summary

The `InitAuthDB()` function in internal authentication code is affected for all versions prior to the fix. An attacker with local filesystem access can read the SQLite database file directly and extract bcrypt password hashes, session tokens, and call metadata without application credentials. The fix adds explicit `os.Chmod(filepath, 0600)` permission enforcement immediately after `sql.Open()`. CWE-311.

Vulnerability at a Glance

cweCWE-311
fixExplicit os.Chmod(filepath, 0600) after database creation
riskLocal privilege escalation via credential theft
languageGo
root causeSQLite database created with default umask permissions, readable by other users
vulnerabilityMissing encryption at rest / insufficient file permissions

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code) — see PR for commit
Ecosystem Go
CVE / GHSA not assigned
CWE CWE-311: Missing Encryption of Sensitive Data

The Vulnerability Explained

When InitAuthDB(filepath string) creates a new SQLite database for authentication data, it relies on SQLite's default file creation behavior. On most systems, this means the file inherits permissions from the process umask—typically 0644, readable by any user on the host.

The vulnerable code opened the database without subsequent permission hardening:

func InitAuthDB(filepath string) error {
    var err error
    authDB, err = sql.Open("sqlite3", filepath)
    if err != nil {
        return err
    }

    // Set busy timeout for better concurrent access
    _, err = authDB.Exec("PRAGMA busy_timeout=5000;")

This left bcrypt password hashes, session tokens, user settings, and call metadata in a plaintext file accessible to any local account. An attacker with non-privileged filesystem access—through a compromised service account, container escape, or shared hosting environment—could simply cat the database file and extract credentials without ever interacting with the application API.

The attack is particularly severe because bcrypt hashes, while slow to crack, are still vulnerable to offline brute-force attacks. Session tokens grant immediate access without any cracking effort.

The Fix

The patch adds explicit permission enforcement immediately after database creation:

authDB, err = sql.Open("sqlite3", filepath)
if err != nil {
    return err
}

// The auth database holds bcrypt password hashes, session tokens, and user
// settings. Restrict it to the owner so other local accounts can't read
// those secrets straight off disk; sql.Open may create the file with a
// looser mode depending on umask, so enforce it explicitly here.
if err = os.Chmod(filepath, 0600); err != nil {
    return fmt.Errorf("failed to restrict auth database file permissions: %w", err)
}

The os.Chmod(filepath, 0600) call executes before any sensitive data is written, ensuring the file is owner-readable and owner-writable only. The error is wrapped with context to aid debugging if the permission change fails—critical for fail-secure behavior where a misconfigured filesystem should block database initialization rather than proceed with insecure permissions.

This change addresses the root cause: SQLite's sql.Open does not provide a cross-platform way to specify creation permissions, and Go's os.OpenFile flags cannot be passed through the database driver. Post-creation permission adjustment is the only reliable mechanism.

Key Takeaways

  • Never trust default file permissions for security-critical data: Database drivers and filesystems vary in behavior; explicitly set permissions after creation.
  • Bcrypt hashes require the same protection as plaintext passwords: Offline cracking makes hash exposure nearly as dangerous as credential theft.
  • Session tokens stored on disk need immediate access controls: Unlike password hashes, tokens provide instant authentication without computation.
  • Fail-secure on permission enforcement: Return errors and halt initialization if permissions cannot be restricted, rather than continuing with potentially insecure defaults.
  • Document security intent in code comments: The inline comment explains why 0600 is necessary, preserving this knowledge for future maintainers.

How Orbis AppSec Detected This

Source: The filepath parameter to InitAuthDB(), accepting arbitrary database locations from calling code.

Sink: sql.Open("sqlite3", filepath) creating a database file with default umask-derived permissions.

Missing control: No os.Chmod or equivalent permission restriction between file creation and data storage.

CWE: CWE-311: Missing Encryption of Sensitive Data

Fix: Added os.Chmod(filepath, 0600) immediately after sql.Open with wrapped error handling to enforce owner-only access.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This vulnerability demonstrates that encryption at rest does not require complex cryptography—sometimes proper filesystem permissions are the critical missing control. The InitAuthDB() fix shows how a single os.Chmod call can eliminate a local privilege escalation vector. For Go developers using SQLite for sensitive data, always verify and enforce permissions after database creation rather than assuming secure defaults.

Prevention and further reading

Frequently Asked Questions

Why does InitAuthDB need os.Chmod after sql.Open when SQLite already creates files?

SQLite respects the process umask, which often defaults to 022, creating files with 0644 permissions. The fix explicitly overrides this to 0600 to protect authentication secrets.

Does the PRAGMA busy_timeout change address the security issue?

No. The busy timeout improves concurrent access reliability but does not affect file permissions. Only the added os.Chmod call fixes CWE-311.

What specific data classes are protected by the 0600 permission change?

Bcrypt password hashes, user settings, session tokens, and call metadata stored in the authentication database.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #35

Related Articles

critical

Hybridauth Telegram OAuth Timing Attack in `authenticateCheckError()`

The Telegram OAuth provider in Hybridauth relied on `strcmp()` to validate HMAC-SHA256 signatures, exposing a timing side-channel that could allow attackers to forge authentication tokens character by character. The fix replaces this with PHP's timing-safe `hash_equals()` function, eliminating the information leak.

high

undici 7.29.0 TLS Bypass: BalancedPool Drops Connect Options

A critical flaw in undici's BalancedPool implementation silently discards TLS certificate validation settings when routing requests through certain connection paths. Attackers on the network could intercept HTTPS traffic without triggering validation errors. The fix preserves connect options throughout the connection lifecycle.

critical

Model Fetching Without Integrity Verification in ONNX Loading

A machine learning application was fetching ONNX model files and chunks over the network without verifying their integrity, creating an opening for model poisoning attacks. The fix adds cryptographic integrity verification at the point where downloaded chunks are reassembled and cached, ensuring models have not been modified in transit or at rest.

high

nanoid 3.3.11 Integer Overflow: Predictable ID Generation

An integer overflow in nanoid 3.3.11's internal randomness generation causes the library to fall back to predictable ID sequences, undermining the cryptographic guarantees of its supposedly unguessable identifiers. The fix upgrades the dependency tree to patched versions 3.3.12 or 5.1.11.

high

generateUUID() Ditches MD5 for SHA-256 to Fix CWE-328

The `generateUUID()` helper built request-identifying UUIDs by hashing an input string with MD5, a cryptographically broken algorithm susceptible to collisions. The fix swaps the hash function for SHA-256, reducing the chance that two different inputs produce the same generated identifier.

high

xcb_get_image_reply() NULL Deref on malloc() Failure

The XCB image-reply handler in the screen-streaming bridge allocated a buffer with `malloc()` and immediately wrote to it with `memset()` and `memcpy()` without checking for allocation failure. Under memory pressure, this produces a null-pointer dereference that crashes the process, turning a resource-exhaustion condition into an immediate denial of service. The fix adds a NULL check that frees the intermediate reply and returns early instead of writing to invalid memory.