{"record":{"id":"3c8f86186dee6ffa","repo":"apache/seatunnel","slug":"resolved-trace-path-escapes-basedir-tracedir","errorCode":null,"errorMessage":"Resolved trace path escapes baseDir: ${traceDir}","messagePattern":"Resolved trace path escapes baseDir: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/event/TraceFileWriter.java","lineNumber":65,"sourceCode":"    private final BufferedWriter writer;\n    private final AtomicLong eventCount;\n    private final AtomicLong fileSize;\n    private final AtomicBoolean closed = new AtomicBoolean(false);\n\n    public TraceFileWriter(String baseDir, String jobId, String date) throws IOException {\n        if (jobId == null || !JOB_ID_PATTERN.matcher(jobId).matches()) {\n            throw new IllegalArgumentException(\"Invalid jobId for trace file path: \" + jobId);\n        }\n        this.jobId = jobId;\n        this.date = date;\n        this.eventCount = new AtomicLong(0);\n        this.fileSize = new AtomicLong(0);\n\n        // Create directory: {baseDir}/traces/{jobId}/{date}/\n        Path basePath = Paths.get(baseDir).toAbsolutePath().normalize();\n        Path traceDir = basePath.resolve(Paths.get(\"traces\", jobId, date)).normalize();\n        if (!traceDir.startsWith(basePath)) {\n            throw new IllegalArgumentException(\"Resolved trace path escapes baseDir: \" + traceDir);\n        }\n        Files.createDirectories(traceDir);\n\n        // Generate file name: traces-{HH-mm-ss}-{uuid}.jsonl\n        String timestamp = LocalDateTime.now().format(TIME_FORMATTER);\n        String shortUuid = UUID.randomUUID().toString().substring(0, 8);\n        String fileName = String.format(\"traces-%s-%s.jsonl\", timestamp, shortUuid);\n\n        this.filePath = traceDir.resolve(fileName);\n\n        // Create file with BufferedWriter\n        this.writer =\n                Files.newBufferedWriter(\n                        filePath,\n                        StandardCharsets.UTF_8,\n                        StandardOpenOption.CREATE_NEW,\n                        StandardOpenOption.WRITE);\n","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/event/TraceFileWriter.java#L47-L83","documentation":"After resolving the trace directory as {baseDir}/traces/{jobId}/{date} and normalizing it, TraceFileWriter verifies the resulting path still starts with the normalized baseDir. If a crafted jobId or date (e.g. containing '..') resolves outside baseDir, this IllegalArgumentException is thrown — a path-traversal defense.","triggerScenarios":"Constructing TraceFileWriter with a jobId or date containing path-traversal sequences (../) that survive normalization; baseDir itself misconfigured to a path where resolve escapes containment.","commonSituations":"External input (jobId/date from request or config) used directly in the writer; symlinked or oddly-shaped baseDir setups; custom event reporters passing untrusted identifiers.","solutions":["Validate jobId and date against strict patterns before constructing the writer (jobId must pass JOB_ID_PATTERN; date must be a plain date string)","Use canonical, engine-generated identifiers only","Check baseDir configuration points to an intended, existing directory","If legit paths are rejected, verify no symlinks/normalization surprises between baseDir and the trace directory"],"exampleFix":"// before\nnew TraceFileWriter(baseDir, userInputId, userInputDate); // untrusted\n// after\nif (!jobId.matches(UUID_REGEX) && !date.matches(\"\\\\d{4}-\\\\d{2}-\\\\d{2}\")) {\n    throw new IllegalArgumentException(\"untrusted trace path components\");\n}\nnew TraceFileWriter(baseDir, jobId, date);","handlingStrategy":"validation","validationCode":"Path base = Paths.get(baseDir).toAbsolutePath().normalize();\nPath resolved = base.resolve(Paths.get(\"traces\", jobId, date)).normalize();\nif (!resolved.startsWith(base)) throw new IllegalArgumentException(\"path escapes baseDir\");","typeGuard":"boolean isInsideBase(Path base, Path p) { return p.normalize().startsWith(base); }","tryCatchPattern":"try { writer = new TraceFileWriter(baseDir, jobId, date); } catch (IllegalArgumentException e) { if (e.getMessage().contains(\"escapes baseDir\")) { log.error(\"possible path traversal attempt\", e); } else { throw e; } }","preventionTips":["Sanitize jobId/date before path resolution","Use strict regex allow-lists for path components","Avoid symlinks under baseDir","Never interpolate untrusted input into trace paths"],"tags":["zeta-engine","event-tracing","path-traversal","security"],"backgroundTag":"path-traversal-blocked","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-14T21:17:11.552Z"}