7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
107 lines
4.4 KiB
Python
107 lines
4.4 KiB
Python
"""Regression tests for DB-file permission hardening (PR #3135).
|
|
|
|
``DatabaseManager.create_user_database`` tightens the on-disk database file to
|
|
owner-only (0o600). The encrypted (SQLCipher) branch chmods right after
|
|
creating the file; the unencrypted fallback — which writes PLAINTEXT data and
|
|
is therefore the higher-risk path — chmods after ``initialize_database``
|
|
materializes the file. This test pins the unencrypted behavior, mirroring the
|
|
salt-file permission test in ``test_encryption_constants.py``.
|
|
"""
|
|
|
|
import os
|
|
import stat
|
|
from unittest.mock import patch
|
|
|
|
from tests.test_utils import add_src_to_path
|
|
|
|
add_src_to_path()
|
|
|
|
|
|
class TestDatabaseFilePermissions:
|
|
def test_unencrypted_db_file_has_restrictive_permissions(
|
|
self, tmp_path, monkeypatch
|
|
):
|
|
"""The unencrypted-fallback DB file must be created 0o600.
|
|
|
|
It holds plaintext user data, so leaving it at umask-default perms
|
|
(commonly 0o644) would expose it to other local accounts.
|
|
"""
|
|
from local_deep_research.database.encrypted_db import DatabaseManager
|
|
|
|
monkeypatch.setenv("LDR_BOOTSTRAP_ALLOW_UNENCRYPTED", "true")
|
|
|
|
with patch(
|
|
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
|
|
) as mock_get_sqlcipher:
|
|
mock_get_sqlcipher.side_effect = ImportError(
|
|
"No module named 'sqlcipher3'"
|
|
)
|
|
with patch(
|
|
"local_deep_research.database.encrypted_db.get_data_directory",
|
|
return_value=tmp_path,
|
|
):
|
|
manager = DatabaseManager()
|
|
assert manager.has_encryption is False, (
|
|
"Test requires the unencrypted fallback path"
|
|
)
|
|
|
|
engine = manager.create_user_database(
|
|
"permuser", "test-password-123"
|
|
)
|
|
try:
|
|
db_path = manager._get_user_db_path("permuser")
|
|
assert db_path.exists(), "DB file was not created"
|
|
|
|
mode = stat.S_IMODE(os.stat(db_path).st_mode)
|
|
assert mode == 0o600, (
|
|
f"Unencrypted DB file should be 0o600, got {oct(mode)}"
|
|
)
|
|
|
|
# The per-user DB directory is owner-only (0o700), so the
|
|
# plaintext WAL/SHM sidecars and any temp files SQLite may
|
|
# create alongside the DB are not sibling-readable either.
|
|
dir_mode = stat.S_IMODE(os.stat(manager.data_dir).st_mode)
|
|
assert dir_mode == 0o700, (
|
|
f"DB directory should be 0o700, got {oct(dir_mode)}"
|
|
)
|
|
finally:
|
|
engine.dispose()
|
|
|
|
def test_db_creation_survives_chmod_failure(self, tmp_path, monkeypatch):
|
|
"""Permission hardening must never break DB creation.
|
|
|
|
On filesystems that reject POSIX chmod (some Docker bind mounts,
|
|
network/FUSE volumes — e.g. Docker Desktop on macOS/Windows),
|
|
os.chmod can raise OSError. The chmods are best-effort, so
|
|
create_user_database must still succeed and return a usable engine.
|
|
"""
|
|
from sqlalchemy import text
|
|
|
|
from local_deep_research.database.encrypted_db import DatabaseManager
|
|
|
|
monkeypatch.setenv("LDR_BOOTSTRAP_ALLOW_UNENCRYPTED", "true")
|
|
|
|
with patch(
|
|
"local_deep_research.database.encrypted_db.get_sqlcipher_module",
|
|
side_effect=ImportError("No module named 'sqlcipher3'"),
|
|
):
|
|
with patch(
|
|
"local_deep_research.database.encrypted_db.get_data_directory",
|
|
return_value=tmp_path,
|
|
):
|
|
# Simulate a volume that rejects chmod for every call.
|
|
with patch(
|
|
"local_deep_research.database.encrypted_db.os.chmod",
|
|
side_effect=OSError("Operation not permitted"),
|
|
):
|
|
manager = DatabaseManager()
|
|
engine = manager.create_user_database(
|
|
"chmoduser", "test-password-123"
|
|
)
|
|
try:
|
|
# DB must be created and queryable despite chmod failing.
|
|
with engine.connect() as conn:
|
|
assert conn.execute(text("SELECT 1")).scalar() == 1
|
|
finally:
|
|
engine.dispose()
|