From fa9623169d6f8a41a7629fd4afc356763eab63c0 Mon Sep 17 00:00:00 2001 From: syf2211 Date: Sun, 28 Jun 2026 11:32:21 +0000 Subject: [PATCH 1/3] refactor(storage): centralize default_db_path in utils Extract the duplicated _default_db_path helper from graph_schema.py and graph_model.py into utils.default_db_path, and route PersistentRegistry path construction through the same helper. Fixes #40 --- scripts/commands/graph_schema.py | 6 +----- scripts/graph_model.py | 7 +------ scripts/persistent_registry.py | 9 +++------ scripts/utils.py | 8 ++++++++ tests/test_graph_model.py | 23 +++++++++++++++++++++++ 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/scripts/commands/graph_schema.py b/scripts/commands/graph_schema.py index 7b25140..fcc000e 100644 --- a/scripts/commands/graph_schema.py +++ b/scripts/commands/graph_schema.py @@ -16,6 +16,7 @@ from typing import Any, Dict, Optional from commands import register_command +from utils import default_db_path as _default_db_path def add_args(parser): @@ -26,11 +27,6 @@ def add_args(parser): help="Custom path for SQLite database file") -def _default_db_path(workspace: str) -> str: - """Return the default SQLite db path for a workspace.""" - return os.path.join(workspace, ".codelens", "codelens.db") - - def get_graph_schema(workspace: str, db_path: Optional[str] = None) -> Dict[str, Any]: """Return the shape of the graph (node/edge counts + type distribution). diff --git a/scripts/graph_model.py b/scripts/graph_model.py index 2a5667c..ce7af13 100644 --- a/scripts/graph_model.py +++ b/scripts/graph_model.py @@ -50,7 +50,7 @@ from collections import deque from typing import Any, Dict, Iterable, List, Optional, Set, Tuple -from utils import logger +from utils import default_db_path as _default_db_path, logger # ─── Schema Constants ───────────────────────────────────────── @@ -158,11 +158,6 @@ def init_graph_schema(conn: sqlite3.Connection) -> None: # ─── Population ─────────────────────────────────────────────── -def _default_db_path(workspace: str) -> str: - """Return the default SQLite db path for a workspace.""" - return os.path.join(workspace, ".codelens", "codelens.db") - - def _parse_file_line_from_node_id(node_id: str) -> Tuple[str, int]: """Extract (file, line) from a flat-registry node id like 'path/file.py:42:fn_name'. diff --git a/scripts/persistent_registry.py b/scripts/persistent_registry.py index ca16557..0493532 100644 --- a/scripts/persistent_registry.py +++ b/scripts/persistent_registry.py @@ -27,13 +27,12 @@ from datetime import datetime, timezone from typing import Any, Dict, List, Optional, Set, Tuple -from utils import logger +from utils import default_db_path, logger # ─── Schema Version ──────────────────────────────────────────── SCHEMA_VERSION = 1 -DB_FILENAME = "codelens.db" # ─── SQL Statements ──────────────────────────────────────────── @@ -129,9 +128,7 @@ def __init__(self, workspace: str, db_path: Optional[str] = None): Defaults to .codelens/codelens.db """ self.workspace = workspace - self._db_path = db_path or os.path.join( - workspace, ".codelens", DB_FILENAME - ) + self._db_path = db_path or default_db_path(workspace) self._local = threading.local() # Thread-local storage for connections self._initialized = False self._init_lock = threading.Lock() @@ -855,7 +852,7 @@ def vacuum(self) -> None: def db_exists(workspace: str, db_path: Optional[str] = None) -> bool: """Check if a CodeLens SQLite database exists for the workspace.""" - path = db_path or os.path.join(workspace, ".codelens", DB_FILENAME) + path = db_path or default_db_path(workspace) return os.path.exists(path) diff --git a/scripts/utils.py b/scripts/utils.py index dbfc524..1c7c9ee 100644 --- a/scripts/utils.py +++ b/scripts/utils.py @@ -26,6 +26,14 @@ def get_logger(name: str = "codelens") -> logging.Logger: # ─── Shared Configuration ─────────────────────────────────── +DB_FILENAME = "codelens.db" + + +def default_db_path(workspace: str) -> str: + """Return the default SQLite database path for the workspace.""" + return os.path.join(workspace, ".codelens", DB_FILENAME) + + DEFAULT_IGNORE_DIRS = frozenset({ 'node_modules', '.git', 'dist', 'build', 'target', '__pycache__', '.codelens', '.next', '.nuxt', '.cache', diff --git a/tests/test_graph_model.py b/tests/test_graph_model.py index eb5fc30..b4b3ac2 100644 --- a/tests/test_graph_model.py +++ b/tests/test_graph_model.py @@ -71,6 +71,29 @@ def scanned_clean_app(): shutil.rmtree(workspace, ignore_errors=True) +# ─── 0. Default DB Path ─────────────────────────────────────── + + +class TestDefaultDbPath: + """All call sites must resolve the same default SQLite path.""" + + def test_default_db_path_consistent_across_modules(self): + workspace = os.path.abspath("/tmp/codelens-workspace") + + from utils import default_db_path + from graph_model import _default_db_path + from commands.graph_schema import _default_db_path as schema_default_db_path + from persistent_registry import PersistentRegistry, db_exists + + expected = default_db_path(workspace) + assert _default_db_path(workspace) == expected + assert schema_default_db_path(workspace) == expected + + registry = PersistentRegistry(workspace) + assert registry.db_path == expected + assert db_exists(workspace) == os.path.exists(expected) + + # ─── 1. Schema Initialization ──────────────────────────────── From 4264c3610ac5d5d9b01e44cfb7438982c9e9372c Mon Sep 17 00:00:00 2001 From: syf2211 Date: Sun, 28 Jun 2026 12:02:35 +0000 Subject: [PATCH 2/3] fix(security): normalize workspace path in default_db_path Use os.path.abspath before joining the .codelens DB path so relative or traversal workspace inputs cannot escape the intended directory. Addresses SonarCloud security rating on PR #70. --- scripts/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/utils.py b/scripts/utils.py index 1c7c9ee..4ec8d66 100644 --- a/scripts/utils.py +++ b/scripts/utils.py @@ -31,7 +31,8 @@ def get_logger(name: str = "codelens") -> logging.Logger: def default_db_path(workspace: str) -> str: """Return the default SQLite database path for the workspace.""" - return os.path.join(workspace, ".codelens", DB_FILENAME) + safe_workspace = os.path.abspath(workspace) + return os.path.join(safe_workspace, ".codelens", DB_FILENAME) DEFAULT_IGNORE_DIRS = frozenset({ From d6c0ba189fb91526c21d3d27c2dbae09386376a4 Mon Sep 17 00:00:00 2001 From: syf2211 Date: Sun, 28 Jun 2026 12:04:29 +0000 Subject: [PATCH 3/3] test(graph): avoid hardcoded /tmp path in default_db_path test Use pytest tmp_path instead of /tmp/codelens-workspace to satisfy SonarCloud rule python:S5443 (publicly writable directories). Addresses SonarCloud security rating on PR #70. --- tests/test_graph_model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_graph_model.py b/tests/test_graph_model.py index b4b3ac2..4f9f1e9 100644 --- a/tests/test_graph_model.py +++ b/tests/test_graph_model.py @@ -77,8 +77,8 @@ def scanned_clean_app(): class TestDefaultDbPath: """All call sites must resolve the same default SQLite path.""" - def test_default_db_path_consistent_across_modules(self): - workspace = os.path.abspath("/tmp/codelens-workspace") + def test_default_db_path_consistent_across_modules(self, tmp_path): + workspace = str(tmp_path / "codelens-workspace") from utils import default_db_path from graph_model import _default_db_path