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).

Solutions

  1. Pass the exact workflowId that owns the file (the one used in createFile).
  2. For family-wide read/download use getDownloadUrl/downloadContent (family-accessible), not the write endpoints.
  3. 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

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


AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14). Data as JSON: /api/errors/4b6b1a141cb49661. Report an issue: GitHub.

Appendix: 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)