conductor-oss/conductor · error · IllegalStateException

File-storage URL signing is not enabled

Error message

File-storage URL signing is not enabled

What it means

Thrown by FileStorageUrlSigner.sign() when the signing feature flag is off. The signer bean is constructed regardless, but sign() is gated behind SigningProperties.isEnabled() because HMAC-signed content URLs are opt-in. The default value of conductor.file-storage.conductor.signing.enabled is false, so any code path that calls sign() without the operator turning signing on hits this IllegalStateException at runtime.

Source

Thrown at core/src/main/java/org/conductoross/conductor/core/storage/FileStorageUrlSigner.java:58

        properties.validate();
        this.keysById =
                properties.getKeys().stream()
                        .collect(
                                Collectors.toUnmodifiableMap(
                                        ConductorFileStorageProperties.Key::getId,
                                        Function.identity()));
    }

    /** Signs a content request using the first configured key, the active key during rotation. */
    public SignedUrl sign(
            Operation operation,
            String workflowId,
            String fileId,
            long expirationEpochSeconds,
            String uploadId,
            Integer partNumber) {
        if (!properties.isEnabled()) {
            throw new IllegalStateException("File-storage URL signing is not enabled");
        }

        ConductorFileStorageProperties.Key signingKey = properties.getKeys().get(0);
        return new SignedUrl(
                signingKey.getId(),
                Base64.getUrlEncoder()
                        .withoutPadding()
                        .encodeToString(
                                hmac(
                                        canonicalize(
                                                operation,
                                                workflowId,
                                                fileId,
                                                expirationEpochSeconds,
                                                uploadId,
                                                partNumber),
                                        signingKey)));
    }

View on GitHub (pinned to cf7c3e4a8a)

Solutions

  1. Set conductor.file-storage.conductor.signing.enabled=true in application.yml (or the active profile) and supply at least one entry in conductor.file-storage.conductor.signing.keys with non-blank id and secret.
  2. Verify the YAML node path and indentation: the prefix is conductor.file-storage.conductor.signing.* (note the inner conductor segment), not conductor.file-storage.signing.*.
  3. If sign() is called from your own code, gate the call on the bound SigningProperties.isEnabled() (or properties.getSigning().isEnabled()) so the path is skipped when signing is intentionally disabled.
  4. Rebuild/restart the Spring context so @ConfigurationProperties rebinds; confirm no profile-specific file is shadowing the enabled flag with a later-loaded false.
  5. Add an integration test that asserts the signer bean is in the expected enabled state for the production profile to catch regressions.

Example fix

// before (application.yml)
conductor:
  file-storage:
    conductor:
      signing:
        keys:
          - id: k1
            secret: ${SIGNING_SECRET}

// after
conductor:
  file-storage:
    conductor:
      signing:
        enabled: true
        keys:
          - id: k1
            secret: ${SIGNING_SECRET}
Defensive patterns

Strategy: validation

Validate before calling

// Call this before FileStorageUrlSigner.sign(...)
import org.conductoross.conductor.core.storage.ConductorFileStorageProperties;

public boolean canSignUrls(ConductorFileStorageProperties.SigningProperties signing) {
    return signing != null
        && signing.isEnabled()
        && signing.getKeys() != null
        && !signing.getKeys().isEmpty();
}

// usage
if (!canSignUrls(properties.getSigning())) {
    // return an unsigned URL, or a 503 / feature-disabled response
} else {
    SignedUrl signed = signer.sign(op, workflowId, fileId, exp, uploadId, partNumber);
}

Try / catch

// Only when you cannot pre-validate (e.g. signer handed to you opaquely)
try {
    SignedUrl signed = signer.sign(op, workflowId, fileId, exp, uploadId, partNumber);
} catch (IllegalStateException e) {
    if ("File-storage URL signing is not enabled".equals(e.getMessage())) {
        // feature is off: degrade gracefully (unsigned URL or 503), do NOT retry
        log.warn("File-storage URL signing disabled; returning unsigned URL");
    } else {
        throw e; // a different IllegalStateException (e.g. error 481) — rethrow
    }
}

Prevention

When it happens

Trigger: Any invocation of FileStorageUrlSigner.sign(operation, workflowId, fileId, expirationEpochSeconds, uploadId, partNumber) while ConductorFileStorageProperties.SigningProperties.isEnabled() returns false. This happens when the HTTP transfer layer is wired to issue signed URLs but the conductor.file-storage.conductor.signing.enabled YAML property was left at its default (false) or explicitly set to false.

Common situations: Operator switched the file-storage backend to the Conductor-managed one and added signing.keys but forgot the enabled: true sibling; a Spring profile override (e.g. application-dev.yml) reset signing.enabled to false; YAML indentation placed keys under the wrong node so the enabled flag never bound; CI/test environment loads defaults and a test calls sign() directly without a test fixture flipping the flag.

Related errors


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