conductor-oss/conductor · error · AccessForbiddenException
Workflow does not own file: {}
Error message
Workflow does not own file: {} What it means
Thrown by getOwnedFile when the supplied workflowId is null/blank or does not equal the file record's owning workflowId. Upload-state mutations (confirmUpload, uploadContent, getUploadUrl, multipart ops) may only be performed by the workflow that created the file. Raised as AccessForbiddenException (HTTP 403).
Source
Thrown at core/src/main/java/org/conductoross/conductor/core/storage/FileStorageServiceImpl.java:232
response.setFileHandleId(FileIdToFileHandleIdConverter.toFileHandleId(fileId));
response.setUploadStatus(FileUploadStatus.UPLOADED);
response.setContentHash(info.getContentHash());
return response;
}
@Override
public void abortMultipartUpload(String workflowId, String fileId, String uploadId) {
FileModel model = getOwnedFile(workflowId, fileId);
fileStorage.abortMultipartUpload(model.getStoragePath(), uploadId);
}
/** Upload state may only be mutated by the workflow that created the file record. */
private @NonNull FileModel getOwnedFile(String workflowId, String fileId) {
FileModel model = getFileModelOrThrow(fileId);
if (workflowId == null
|| workflowId.isBlank()
|| !workflowId.equals(model.getWorkflowId())) {
throw new AccessForbiddenException("Workflow does not own file: " + fileId);
}
return model;
}
/** Downloads and metadata are visible to the owning workflow's full workflow family. */
private @NonNull FileModel getFamilyAccessibleFile(String workflowId, String fileId) {
FileModel model = getFileModelOrThrow(fileId);
if (model.getWorkflowId() == null || model.getWorkflowId().isBlank()) {
throw new AccessForbiddenException("File has no workflowId: " + fileId);
}
Set<String> family = workflowFamilyResolver.getFamily(workflowId);
if (!family.contains(model.getWorkflowId())) {
throw new AccessForbiddenException("Workflow cannot access file: " + fileId);
}
return model;
}
View on GitHub (pinned to cf7c3e4a8a)
Solutions
- Pass the exact workflowId that owns the file (the one used in createFile).
- For family-wide read/download use getDownloadUrl/downloadContent (family-accessible), not the write endpoints.
- If a child workflow must write, have the owner create the file and pass the handle down with its workflowId.
Example fix
// before - using the parent workflowId for a file owned by the child fileStorageService.confirmUpload(parentWfId, fileId); // throws 403 // after - use the owning workflowId fileStorageService.confirmUpload(ownerWorkflowId, fileId);
Defensive patterns
Strategy: validation
Validate before calling
FileHandle meta = fileStorageService.getFileMetadata(wfId, fileId); boolean isOwner = wfId != null && !wfId.isBlank() && wfId.equals(meta.getWorkflowId());
Try / catch
try {
fileStorageService.confirmUpload(ownerWfId, fileId);
} catch (AccessForbiddenException e) {
// not the owner; cannot mutate
} Prevention
- Pass the exact owning workflowId for write operations
- Use family-accessible read endpoints for parent/child access
When it happens
Trigger: Calling confirmUpload/getUploadUrl/uploadContent/multipart endpoints with a workflowId different from the file's owner; passing a blank workflowId; cross-workflow access to another workflow's file for a write operation.
Common situations: Sub-workflow attempting a write on a parent-owned file; client passing a stale or wrong workflow id; cross-tenant access attempt; misrouting a request to the wrong file.
Related errors
- File has no workflowId: {}
- Workflow cannot access file: {}
- conductor.file-storage.conductor.signing.keys is required wh
- Each conductor.file-storage.conductor.signing.keys entry req
- conductor.file-storage.conductor.signing.keys contains dupli
AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14).
Data as JSON: /api/errors/4b6b1a141cb49661.
Report an issue: GitHub.