Skip to content

Commit 8c2f42f

Browse files
Refactor process_repo into utils
1 parent b13e365 commit 8c2f42f

File tree

3 files changed

+30
-35
lines changed

3 files changed

+30
-35
lines changed

src/seclab_taskflows/mcp_servers/codeql_python/mcp_server.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@
2121
from sqlalchemy import create_engine
2222
from sqlalchemy.orm import Session
2323

24-
25-
26-
2724
from .codeql_sqlite_models import Base, Source
25+
from .utils import process_repo
2826

2927
MEMORY = Path(os.getenv('CODEQL_SQLITE_DIR', default='/app/my_data'))
3028
mcp = FastMCP("CodeQL-Python")
@@ -66,7 +64,9 @@ def _resolve_db_path(relative_db_path: str | Path):
6664
# not windows compatible and probably needs additional hardening
6765
relative_db_path = str(relative_db_path).strip().lstrip('/')
6866
relative_db_path = Path(relative_db_path)
69-
absolute_path = CODEQL_DBS_BASE_PATH / relative_db_path
67+
absolute_path = (CODEQL_DBS_BASE_PATH / relative_db_path).resolve()
68+
if not str(absolute_path).startswith(str(CODEQL_DBS_BASE_PATH.resolve())):
69+
raise RuntimeError(f"Error: Database path {absolute_path} is outside the base path {CODEQL_DBS_BASE_PATH}")
7070
if not absolute_path.is_dir():
7171
_debug_log(f"Database path not found: {absolute_path}")
7272
raise RuntimeError(f"Error: Database not found at {absolute_path}!")
@@ -148,11 +148,6 @@ def _run_query(query_name: str, database_path: str, language: str, template_valu
148148
except Exception as e:
149149
return f"The query {query_name} encountered an error: {e}"
150150

151-
def _get_file_contents(db: str | Path, uri: str):
152-
"""Retrieve file contents from a CodeQL database"""
153-
db = Path(db)
154-
return file_from_uri(uri, db)
155-
156151
backend = CodeqlSqliteBackend(MEMORY)
157152

158153
@mcp.tool()
@@ -161,7 +156,7 @@ def remote_sources(owner: str, repo: str,
161156
language: str = Field(description="The language used for the CodeQL database.")):
162157
"""List all remote sources and their locations in a CodeQL database, then store the results in a database."""
163158

164-
repo = f"{owner}/{repo}"
159+
repo = process_repo(owner, repo)
165160
results = _run_query('remote_sources', database_path, language, {})
166161

167162
# Check if results is an error (list of strings) or valid data (list of dicts)
@@ -190,7 +185,7 @@ def fetch_sources(owner: str, repo: str):
190185
"""
191186
Fetch all sources from the repo
192187
"""
193-
repo = f"{owner}/{repo}"
188+
repo = process_repo(owner, repo)
194189
return json.dumps(backend.get_sources(repo))
195190

196191
@mcp.tool()
@@ -202,15 +197,15 @@ def add_source_notes(owner: str, repo: str,
202197
"""
203198
Add new notes to an existing source. The notes will be appended to any existing notes.
204199
"""
205-
repo = f"{owner}/{repo}"
200+
repo = process_repo(owner, repo)
206201
return backend.store_new_source(repo = repo, source_location = source_location, line = line, type = "", notes = notes, update=True)
207202

208203
@mcp.tool()
209204
def clear_codeql_repo(owner: str, repo: str):
210205
"""
211206
Clear all data for a given repo from the database
212207
"""
213-
repo = f"{owner}/{repo}"
208+
repo = process_repo(owner, repo)
214209
with Session(backend.engine) as session:
215210
deleted_sources = session.query(Source).filter_by(repo=repo).delete()
216211
# deleted_apps = session.query(Application).filter_by(repo=repo).delete()

src/seclab_taskflows/mcp_servers/repo_context.py

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from pathlib import Path
2222

2323
from .repo_context_models import Application, EntryPoint, UserAction, WebEntryPoint, ApplicationIssue, AuditResult, Base
24+
from .utils import process_repo
2425

2526
MEMORY = Path(os.getenv('REPO_CONTEXT_DIR', default='/app/my_data'))
2627

@@ -90,9 +91,9 @@ def __init__(self, memcache_state_dir: str):
9091
else:
9192
db_dir = f'sqlite:///{self.memcache_state_dir}/repo_context.db'
9293
self.engine = create_engine(db_dir, echo=False)
93-
Base.metadata.create_all(self.engine, tables=[Application.__table__, EntryPoint.__table__, UserAction.__table__,
94+
Base.metadata.create_all(self.engine, tables=[Application.__table__, EntryPoint.__table__, UserAction.__table__,
9495
WebEntryPoint.__table__, ApplicationIssue.__table__, AuditResult.__table__])
95-
96+
9697
def store_new_application(self, repo, location, is_app, is_library, notes):
9798
with Session(self.engine) as session:
9899
existing = session.query(Application).filter_by(repo = repo, location = location).first()
@@ -107,7 +108,7 @@ def store_new_application(self, repo, location, is_app, is_library, notes):
107108
session.add(new_application)
108109
session.commit()
109110
return f"Updated or added application for {location} in {repo}."
110-
111+
111112
def store_new_component_issue(self, repo, component_id, issue_type, notes):
112113
with Session(self.engine) as session:
113114
existing = session.query(ApplicationIssue).filter_by(repo = repo, component_id = component_id, issue_type = issue_type).first()
@@ -155,7 +156,7 @@ def store_new_entry_point(self, repo, app_id, file, user_input, line, notes, upd
155156
session.add(new_entry_point)
156157
session.commit()
157158
return f"Updated or added entry point for {file} and {line} in {repo}."
158-
159+
159160
def store_new_web_entry_point(self, repo, entry_point_id, method, path, component, auth, middleware, roles_scopes, notes, update = False):
160161
with Session(self.engine) as session:
161162
existing = session.query(WebEntryPoint).filter_by(repo = repo, entry_point_id = entry_point_id).first()
@@ -177,7 +178,7 @@ def store_new_web_entry_point(self, repo, entry_point_id, method, path, componen
177178
if update:
178179
return f"No web entry point exists at repo {repo} with entry_point_id {entry_point_id}."
179180
new_web_entry_point = WebEntryPoint(
180-
repo = repo,
181+
repo = repo,
181182
entry_point_id = entry_point_id,
182183
method = method,
183184
path = path,
@@ -190,7 +191,7 @@ def store_new_web_entry_point(self, repo, entry_point_id, method, path, componen
190191
session.add(new_web_entry_point)
191192
session.commit()
192193
return f"Updated or added web entry point for entry_point_id {entry_point_id} in {repo}."
193-
194+
194195
def store_new_user_action(self, repo, app_id, file, line, notes, update = False):
195196
with Session(self.engine) as session:
196197
existing = session.query(UserAction).filter_by(repo = repo, file = file, line = line).first()
@@ -203,7 +204,7 @@ def store_new_user_action(self, repo, app_id, file, line, notes, update = False)
203204
session.add(new_user_action)
204205
session.commit()
205206
return f"Updated or added user action for {file} and {line} in {repo}."
206-
207+
207208
def get_app(self, repo, location):
208209
with Session(self.engine) as session:
209210
existing = session.query(Application).filter_by(repo = repo, location = location).first()
@@ -271,7 +272,7 @@ def get_web_entries_for_repo(self, repo):
271272
with Session(self.engine) as session:
272273
results = session.query(WebEntryPoint).filter_by(repo = repo).all()
273274
return [{
274-
'repo' : r.repo,
275+
'repo' : r.repo,
275276
'entry_point_id' : r.entry_point_id,
276277
'method' : r.method,
277278
'path' : r.path,
@@ -286,7 +287,7 @@ def get_web_entries(self, repo, component_id):
286287
with Session(self.engine) as session:
287288
results = session.query(WebEntryPoint).filter_by(repo = repo, component = component_id).all()
288289
return [{
289-
'repo' : r.repo,
290+
'repo' : r.repo,
290291
'entry_point_id' : r.entry_point_id,
291292
'method' : r.method,
292293
'path' : r.path,
@@ -313,7 +314,7 @@ def get_user_actions_for_repo(self, repo):
313314
).filter(UserAction.app_id == Application.id).all()
314315
uas = [user_action_to_dict(ua) for app, ua in results]
315316
return uas
316-
317+
317318
def clear_repo(self, repo):
318319
with Session(self.engine) as session:
319320
session.query(Application).filter_by(repo = repo).delete()
@@ -324,7 +325,7 @@ def clear_repo(self, repo):
324325
session.query(AuditResult).filter_by(repo = repo).delete()
325326
session.commit()
326327
return f"Cleared results for repo {repo}"
327-
328+
328329
def clear_repo_issues(self, repo):
329330
with Session(self.engine) as session:
330331
session.query(ApplicationIssue).filter_by(repo = repo).delete()
@@ -336,13 +337,10 @@ def clear_repo_issues(self, repo):
336337

337338
backend = RepoContextBackend(MEMORY)
338339

339-
def process_repo(owner, repo):
340-
return f"{owner}/{repo}".lower()
341-
342340
@mcp.tool()
343-
def store_new_component(owner: str, repo: str, location: str = Field(description="The directory of the component"),
344-
is_app: bool = Field(description="Is this an application", default=None),
345-
is_library: bool = Field(description="Is this a library", default=None),
341+
def store_new_component(owner: str, repo: str, location: str = Field(description="The directory of the component"),
342+
is_app: bool = Field(description="Is this an application", default=None),
343+
is_library: bool = Field(description="Is this a library", default=None),
346344
notes: str = Field(description="The notes taken for this component", default="")):
347345
"""
348346
Stores a new component in the database.
@@ -386,9 +384,9 @@ def store_new_component_issue(owner: str, repo: str, component_id: int,
386384
return backend.store_new_component_issue(repo, component_id, issue_type, notes)
387385

388386
@mcp.tool()
389-
def store_new_audit_result(owner: str, repo: str, component_id: int, issue_type: str, issue_id: int,
390-
has_non_security_error: bool = Field(description="Set to true if there are security issues or logic error but may not be exploitable"),
391-
has_vulnerability: bool = Field(description="Set to true if a security vulnerability is identified"),
387+
def store_new_audit_result(owner: str, repo: str, component_id: int, issue_type: str, issue_id: int,
388+
has_non_security_error: bool = Field(description="Set to true if there are security issues or logic error but may not be exploitable"),
389+
has_vulnerability: bool = Field(description="Set to true if a security vulnerability is identified"),
392390
notes: str = Field(description="The notes for the audit of this issue")):
393391
"""
394392
Stores the audit result for issue with issue_id.
@@ -397,7 +395,7 @@ def store_new_audit_result(owner: str, repo: str, component_id: int, issue_type:
397395
return backend.store_new_audit_result(repo, component_id, issue_type, issue_id, has_non_security_error, has_vulnerability, notes)
398396

399397
@mcp.tool()
400-
def store_new_web_entry_point(owner: str, repo: str,
398+
def store_new_web_entry_point(owner: str, repo: str,
401399
entry_point_id: int = Field(description="The ID of the entry point this web entry point refers to"),
402400
location: str = Field(description="The directory of the component where the web entry point belongs to"),
403401
method: str = Field(description="HTTP method (GET, POST, etc)", default=""),
@@ -432,7 +430,7 @@ def add_entry_point_notes(owner: str, repo: str,
432430
@mcp.tool()
433431
def store_new_user_action(owner: str, repo: str, location: str = Field(description="The directory of the component where the user action belonged to"),
434432
file: str = Field(description="The file that contains the user action"),
435-
line: int = Field(description="The file line that contains the user action"),
433+
line: int = Field(description="The file line that contains the user action"),
436434
notes: str = Field(description="New notes for this user action", default = "")):
437435
"""
438436
Stores a new user action in a component to the database.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
def process_repo(owner, repo):
2+
return f"{owner}/{repo}".lower()

0 commit comments

Comments
 (0)