Skip to content

Commit a2a619b

Browse files
jpicklykclaude
andcommitted
fix: add foreign key validation to prevent database schema exposure
- Add pre-validation for featureId in CreateTaskTool to check feature exists - Add pre-validation for projectId in CreateFeatureTool to check project exists - Replace raw SQL foreign key constraint errors with proper RESOURCE_NOT_FOUND responses - Update CreateTaskToolTest to mock feature repository validation - Resolves security vulnerability where invalid foreign keys exposed database internals 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 9b55cfd commit a2a619b

File tree

3 files changed

+46
-0
lines changed

3 files changed

+46
-0
lines changed

src/main/kotlin/io/github/jpicklyk/mcptask/application/tools/feature/CreateFeatureTool.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,20 @@ class CreateFeatureTool : BaseToolDefinition() {
315315
if (it.isEmpty()) null else UUID.fromString(it)
316316
}
317317

318+
// Validate that referenced project exists before attempting to create feature
319+
if (projectId != null) {
320+
when (val projectResult = context.repositoryProvider.projectRepository().getById(projectId)) {
321+
is Result.Error -> {
322+
return errorResponse(
323+
message = "Project not found",
324+
code = ErrorCodes.RESOURCE_NOT_FOUND,
325+
details = "No project exists with ID $projectId"
326+
)
327+
}
328+
is Result.Success -> { /* Project exists, continue */ }
329+
}
330+
}
331+
318332
// Convert string parameters to appropriate types
319333
val status = parseStatus(statusStr)
320334
val priority = parsePriority(priorityStr)

src/main/kotlin/io/github/jpicklyk/mcptask/application/tools/task/CreateTaskTool.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,33 @@ class CreateTaskTool : BaseToolDefinition() {
324324
if (it.isEmpty()) null else UUID.fromString(it)
325325
}
326326

327+
// Validate that referenced entities exist before attempting to create task
328+
if (projectId != null) {
329+
when (val projectResult = context.repositoryProvider.projectRepository().getById(projectId)) {
330+
is Result.Error -> {
331+
return errorResponse(
332+
message = "Project not found",
333+
code = ErrorCodes.RESOURCE_NOT_FOUND,
334+
details = "No project exists with ID $projectId"
335+
)
336+
}
337+
is Result.Success -> { /* Project exists, continue */ }
338+
}
339+
}
340+
341+
if (featureId != null) {
342+
when (val featureResult = context.repositoryProvider.featureRepository().getById(featureId)) {
343+
is Result.Error -> {
344+
return errorResponse(
345+
message = "Feature not found",
346+
code = ErrorCodes.RESOURCE_NOT_FOUND,
347+
details = "No feature exists with ID $featureId"
348+
)
349+
}
350+
is Result.Success -> { /* Feature exists, continue */ }
351+
}
352+
}
353+
327354
// Parse tags
328355
val tags = optionalString(params, "tags")?.let {
329356
if (it.isEmpty()) emptyList()

src/test/kotlin/io/github/jpicklyk/mcptask/application/tools/task/CreateTaskToolTest.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,11 @@ class CreateTaskToolTest {
300300
mockTaskRepository.create(any())
301301
} returns Result.Success(taskWithFeature)
302302

303+
// Setup feature repository to return success for the feature ID validation
304+
coEvery {
305+
mockRepositoryProvider.featureRepository().getById(featureId)
306+
} returns Result.Success(mockk())
307+
303308
val result = tool.execute(params, context)
304309

305310
// Check that the result is a success response

0 commit comments

Comments
 (0)