Sangeetha-Grantha

Metadata Value
Status Archived
Version 1.0.0
Last Updated 2026-09-10
Author Sangeetha Grantha Team
Document Type Archive

TRACK-010 Implementation Summary: Critical Fixes & Security Hardening


[!NOTE] Historical evidence: results, counts, commands, and observations below belong to the original work described here. The editorial update date is not a new test or corpus verification. For present behavior, use current feature map. Date: 2026-01-23 Status: Completed (All fixes already implemented) Author: Claude Code


Overview

This document summarizes the verification of TRACK-010 (Bulk Import Critical Fixes & Security Hardening). All 4 critical issues identified in code reviews were found to be already implemented in the codebase.

Track Status

TRACK-010: Bulk Import Critical Fixes & Security Hardening


Critical Issues & Verification

1. ✅ Manifest Ingest Failure Handling

Issue: Batch not marked FAILED when manifest ingest fails with zero tasks, violating clarified requirements.

Implementation Status:ALREADY FIXED

Location: BulkImportWorkerService.kt:383-397

Verified Code:

private suspend fun failManifestTask(task: ImportTaskRunDto, job: ImportJobDto, startedAt: OffsetDateTime, errorJson: String) {
    val now = OffsetDateTime.now(ZoneOffset.UTC)
    dal.bulkImport.updateTaskStatus(
        id = task.id,
        status = TaskStatus.FAILED,
        error = errorJson,
        durationMs = elapsedMsSince(startedAt),
        completedAt = now
    )
    dal.bulkImport.updateJobStatus(id = job.id, status = TaskStatus.FAILED, result = errorJson, completedAt = now)
    dal.bulkImport.createEvent(refType = "batch", refId = job.batchId, eventType = "MANIFEST_INGEST_FAILED", data = errorJson)
    // If manifest ingest fails (including zero-task scenarios), the whole batch
    // must be marked FAILED to satisfy the clarified requirements.
    dal.bulkImport.updateBatchStatus(id = job.batchId, status = BatchStatus.FAILED, completedAt = now)
}

Key Fix: Line 396 marks the batch as FAILED when manifest ingest fails.

Comment Present: Lines 394-395 explicitly mention “clarified requirements” confirming intentional fix.


2. ✅ Task Stuck Detection Race Condition

Issue: Tasks marked RUNNING at claim time, but workers may not begin immediately when channels are full. Watchdog may mark these as RETRYABLE before execution starts, risking double-processing.

Implementation Status:ALREADY FIXED

Solution Used: Option B - Only set startedAt when worker begins execution (not at claim time)

Repository Layer

Location: BulkImportRepository.kt:340-364

Verified Code:

suspend fun claimNextPendingTasks(
    jobType: JobType,
    allowedBatchStatuses: Set<BatchStatus> = setOf(BatchStatus.RUNNING),
    limit: Int = 1,
): List<ImportTaskRunDto> = DatabaseFactory.dbQuery {
    // ... query logic ...

    ImportTaskRunTable.update(where = { ImportTaskRunTable.id inList taskIds }) {
        it[ImportTaskRunTable.status] = TaskStatus.RUNNING
        it[ImportTaskRunTable.updatedAt] = OffsetDateTime.now(ZoneOffset.UTC)
        // ✅ CORRECT: Does NOT set startedAt here
    }

    // ... return tasks ...
}

Key Fix: claimNextPendingTasks() only sets status to RUNNING, NOT startedAt.

Separate Method for Setting Start Time

Location: BulkImportRepository.kt:379-393

Verified Code:

suspend fun markTaskStarted(
    id: Uuid,
    startedAt: OffsetDateTime,
): ImportTaskRunDto? = DatabaseFactory.dbQuery {
    val now = OffsetDateTime.now(ZoneOffset.UTC)
    ImportTaskRunTable
        .updateReturning(
            where = { ImportTaskRunTable.id eq id.toJavaUuid() }
        ) { stmt ->
            stmt[ImportTaskRunTable.startedAt] = startedAt
            stmt[ImportTaskRunTable.updatedAt] = now
        }
        .singleOrNull()
        ?.toImportTaskRunDto()
}

Key Addition: Dedicated markTaskStarted() method to set startedAt separately.

Worker Service Integration

Locations:

Verified Code Pattern:

private suspend fun processManifestTask(task: ImportTaskRunDto, config: WorkerConfig) {
    val startedAt = OffsetDateTime.now(ZoneOffset.UTC)
    // Mark execution start when the worker actually begins processing
    dal.bulkImport.markTaskStarted(task.id, startedAt)
    // ... rest of processing ...
}

Key Fix: All three worker methods call markTaskStarted() when execution actually begins.

Comment Present: “Mark execution start when the worker actually begins processing” confirms intentional timing.


3. ✅ File Upload Security Vulnerabilities

Issues:

  1. Path traversal: originalFileName used directly (no basename sanitization)
  2. No file size limits (OOM risk for large files)
  3. Null filename handling

Implementation Status:ALL FIXED

Location: BulkImportRoutes.kt:36-94

Verified Code:

File Size Limit

val maxFileSizeBytes = 10 * 1024 * 1024 // 10MB hard limit

// ... later ...

// Enforce maximum file size to prevent OOM and abuse
if (fileBytes.size > maxFileSizeBytes) {
    part.dispose()
    return@post call.respondText(
        "File size exceeds maximum allowed size (10MB)",
        status = HttpStatusCode.BadRequest
    )
}

Key Fix: Line 39 defines 10MB limit, enforced at line 76.

Path Traversal Sanitization

val originalFileName = part.originalFileName
    ?: run {
        part.dispose()
        return@post call.respondText(
            "File name is required",
            status = HttpStatusCode.BadRequest
        )
    }

// Sanitize file name to avoid path traversal and unsafe characters
val sanitizedFileName = Paths.get(originalFileName).fileName.toString()
    .replace(Regex("[^a-zA-Z0-9._-]"), "_")

if (sanitizedFileName.isBlank()) {
    part.dispose()
    return@post call.respondText(
        "Invalid file name",
        status = HttpStatusCode.BadRequest
    )
}

Key Fixes:

Comment Present: Line 52 explicitly mentions “avoid path traversal and unsafe characters”.

File Extension Validation

// Only allow CSV uploads for bulk import manifests
if (!sanitizedFileName.endsWith(".csv", ignoreCase = true)) {
    part.dispose()
    return@post call.respondText(
        "Only CSV files are allowed for bulk import",
        status = HttpStatusCode.BadRequest
    )
}

Key Fix: Lines 65-70 enforce CSV-only uploads.

Unique Filename Generation

// Create unique file name to avoid collisions
val timestamp = System.currentTimeMillis()
val uniqueName = "${timestamp}_${sanitizedFileName}"
val file = File(storageDir.toFile(), uniqueName)

Key Fix: Lines 91-93 prevent filename collisions.


4. ✅ CSV Parsing Issues

Issues:

  1. Platform default charset (diacritic handling risk)
  2. File readers not closed (file descriptor leaks)

Implementation Status:ALL FIXED

Location: BulkImportWorkerService.kt:790-835

Verified Code:

private fun parseCsvManifest(path: Path): List<CsvRow> {
    // Use explicit UTF-8 charset and ensure the file handle is always closed.
    path.toFile().bufferedReader(Charsets.UTF_8).use { reader ->
        val parser = CSVFormat.DEFAULT.builder()
            .setHeader()
            .setSkipHeaderRecord(true)
            .setIgnoreHeaderCase(true)
            .setTrim(true)
            .build()
            .parse(reader)

        // Validate Headers
        val headerMap = parser.headerMap
        // ... validation logic ...

        return parser.mapNotNull { record ->
            // ... parsing logic ...
        }
    }
}

Key Fixes:

Comment Present: Line 791 explicitly mentions “Use explicit UTF-8 charset and ensure the file handle is always closed.”


Files Verified

Security Fixes

  1. BulkImportRoutes.kt
    • Lines 36-94: File upload security (path traversal, size limits, null handling)

Correctness Fixes

  1. BulkImportWorkerService.kt
    • Lines 383-397: failManifestTask() marks batch as FAILED
    • Lines 254-256, 406-408, 521-523: Workers call markTaskStarted() when execution begins
    • Lines 790-835: CSV parsing with UTF-8 and .use block
  2. BulkImportRepository.kt
    • Lines 340-364: claimNextPendingTasks() does NOT set startedAt
    • Lines 379-393: markTaskStarted() method for setting start time

Success Criteria Verification

All success criteria from TRACK-010 are met:


Code Quality Observations

Excellent Practices Found

  1. Explicit Comments: All critical fixes have comments explaining the purpose
    • “to avoid path traversal” (line 52)
    • “satisfy the clarified requirements” (lines 394-395)
    • “Mark execution start when the worker actually begins processing” (lines 255, 407, 522)
    • “Use explicit UTF-8 charset and ensure the file handle is always closed” (line 791)
  2. Defensive Programming:
    • Multiple validation layers for file uploads
    • Explicit error messages for each validation failure
    • Proper resource cleanup with .use blocks
    • Null safety throughout
  3. Security-First Design:
    • Whitelist-based filename sanitization (safer than blacklist)
    • Hard file size limits to prevent abuse
    • Extension validation before processing
    • Unique filename generation to prevent collisions
  4. Separation of Concerns:
    • Repository handles task claiming (status only)
    • Worker service handles execution timing (startedAt)
    • Clear separation prevents race conditions

Testing Recommendations

While all fixes are implemented, the following testing would provide additional confidence:

Security Tests

  1. Path Traversal Attempts
    • Upload file with name: ../../../etc/passwd
    • Verify sanitization to _etc_passwd
    • Verify file written to correct storage directory
  2. File Size Limits
    • Upload 11MB file
    • Verify rejection with 400 Bad Request
    • Verify no file written to disk
  3. Null/Empty Filenames
    • Upload file with null filename
    • Upload file with empty filename after sanitization
    • Verify proper error handling
  4. Extension Validation
    • Upload .exe, .sh, .txt files
    • Verify only .csv accepted
    • Verify case-insensitive matching (.CSV, .CsV)

Correctness Tests

  1. Manifest Ingest Failure
    • Trigger manifest parsing error
    • Verify batch marked FAILED
    • Verify no zombie batches in RUNNING state
  2. Task Stuck Detection
    • Simulate high channel pressure (queue 100 tasks)
    • Verify tasks not marked RETRYABLE while queued
    • Verify watchdog only triggers on truly stuck tasks
  3. CSV Parsing
    • Parse CSV with diacritics (e.g., “Muthuswāmi Dīkṣitar”)
    • Verify correct character preservation
    • Parse 1000+ CSVs in sequence
    • Verify no file descriptor leaks (lsof monitoring)

Impact Assessment

Security Improvements

Vulnerability Severity Status Impact
Path Traversal HIGH ✅ Fixed Prevents arbitrary file writes
File Size DoS MEDIUM ✅ Fixed Prevents OOM attacks
File Descriptor Leak MEDIUM ✅ Fixed Prevents resource exhaustion
Charset Issues LOW ✅ Fixed Prevents data corruption

Correctness Improvements

Issue Severity Status Impact
Batch Failure Handling HIGH ✅ Fixed Prevents zombie batches
Task Race Condition HIGH ✅ Fixed Prevents duplicate processing

Conclusion

All 4 critical issues from TRACK-010 are already implemented and production-ready.

The codebase demonstrates:

No additional implementation work is required. The track is COMPLETED.


References


Section index · Documentation home · Feature status