{"record":{"id":"40ec939e83475e79","repo":"apache/seatunnel","slug":"invalid-jobid-for-trace-file-path-jobid","errorCode":null,"errorMessage":"Invalid jobId for trace file path: ${jobId}","messagePattern":"Invalid jobId for trace file path: (.+?)","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":54,"sourceCode":"import java.util.regex.Pattern;\n\n/** Writes OTLP JSON lines into per-job trace files and rotates them based on count or size. */\n@Slf4j\npublic class TraceFileWriter implements Closeable {\n    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(\"HH-mm-ss\");\n    private static final Pattern JOB_ID_PATTERN = Pattern.compile(\"[a-zA-Z0-9_-]+\");\n\n    private final String jobId;\n    private final String date;\n    private final Path filePath;\n    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);","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-engine/seatunnel-engine-server/src/main/java/org/apache/seatunnel/engine/server/event/TraceFileWriter.java#L36-L72","documentation":"TraceFileWriter writes job event trace files under {baseDir}/traces/{jobId}/{date}/. To prevent path injection, the jobId must match a strict JOB_ID_PATTERN (UUID-like) before it is used in the path; a null or malformed jobId throws this IllegalArgumentException at construction time.","triggerScenarios":"Constructing TraceFileWriter with jobId=null; passing a jobId containing slashes, '..', or other characters outside the expected pattern; wiring event job status events with an unset job id.","commonSituations":"Custom event listeners built before jobId assignment; manually passing user-controlled job identifiers; misconfigured job event listeners forwarding placeholder ids.","solutions":["Ensure the job has a valid engine-assigned jobId (UUID format) before creating the writer","Validate/sanitize the jobId string with a UUID/regex check before constructing TraceFileWriter","Fix event listener wiring so trace writers are only created after job initialization","Never pass raw user input as jobId; generate or look up the engine id"],"exampleFix":"// before\nnew TraceFileWriter(baseDir, request.getJobId(), date); // may be arbitrary\n// after\nString jobId = request.getJobId();\nif (jobId != null && jobId.matches(\"[a-fA-F0-9-]{32,36}\")) {\n    new TraceFileWriter(baseDir, jobId, date);\n}","handlingStrategy":"validation","validationCode":"if (jobId == null || !jobId.matches(\"[0-9a-fA-F-]{32,36}\")) { throw new IllegalArgumentException(\"jobId must be engine-assigned UUID\"); }","typeGuard":"boolean isValidJobId(String id) { return id != null && id.matches(\"[0-9a-fA-F-]{32,36}\"); }","tryCatchPattern":"try { writer = new TraceFileWriter(baseDir, jobId, date); } catch (IllegalArgumentException e) { if (e.getMessage().startsWith(\"Invalid jobId\")) { log.warn(\"skipping trace writer for invalid jobId\"); } else { throw e; } }","preventionTips":["Only use engine-generated jobIds","Validate identifiers before constructing writers","Don't pass user-controlled strings as jobIds"],"tags":["zeta-engine","event-tracing","validation","path-safety"],"backgroundTag":"invalid-identifier-format","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"}