conductor-oss/conductor · error · AccessForbiddenException

Workflow cannot access file: {}

Error message

Workflow cannot access file: {}

What it means

Thrown by getFamilyAccessibleFile when the caller's workflowId is not part of the workflow family that owns the file. Downloads and metadata reads are visible to the full workflow family (parent + sub-workflows), but a workflow outside that family is denied. Raised as AccessForbiddenException (HTTP 403).

Source

Thrown at core/src/main/java/org/conductoross/conductor/core/storage/FileStorageServiceImpl.java:246

        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;
    }

    private FileModel getFileModelOrThrow(String fileId) {
        FileModel model = fileMetadataDAO.getFileMetadata(fileId);
        if (model == null) {
            throw new NotFoundException("File not found: " + fileId);
        }
        return model;
    }
}

View on GitHub (pinned to cf7c3e4a8a)

Solutions

  1. Use the workflowId of the file owner or a workflow that is a parent/child of the owner.
  2. If a legitimate family relationship is missing, verify WorkflowFamilyResolver registers the parent/sub-workflow link.
  3. Route the read through the owning workflow if family access is not intended.

Example fix

// before - unrelated workflow id
fileStorageService.getDownloadUrl(unrelatedWfId, fileId); // 403

// after - use a workflow in the owning family
fileStorageService.getDownloadUrl(ownerOrParentWfId, fileId);
Defensive patterns

Strategy: validation

Validate before calling

Set<String> family = workflowFamilyResolver.getFamily(wfId);
FileHandle meta = fileStorageService.getFileMetadata(wfId, fileId);
boolean accessible = family.contains(meta.getWorkflowId());

Try / catch

try {
    fileStorageService.getDownloadUrl(wfId, fileId);
} catch (AccessForbiddenException e) {
    // caller not in file's workflow family
}

Prevention

When it happens

Trigger: Calling getDownloadUrl/downloadContent/getFileMetadata with a workflowId whose resolved family (via WorkflowFamilyResolver) does not contain the file's owning workflowId. E.g. an unrelated workflow trying to read another workflow's file.

Common situations: Caller passing the wrong workflowId; sibling (non-parent/child) workflow attempting access; the family resolver does not recognize the relationship (e.g. dynamic fork not tracked); cross-tenant access.

Related errors


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