Skip to content

[Security] Remote installer script executed without integrity verification #389

Description

@fdelbrayelle

Summary

When uv is absent from the worker PATH, PythonDependenciesResolver downloads https://astral.sh/uv/install.sh at task runtime and immediately executes it with sh. No checksum or cryptographic signature is verified before execution. A compromised CDN, poisoned DNS, or MITM on an unprotected network path could substitute arbitrary shell code that runs with full Kestra worker OS permissions.

Severity

HIGH — exploitable via external supply chain compromise; no local attacker access required; execution scope is the full Kestra worker process.

Standards

OWASP A03:2025 (Software Supply Chain Failures), OWASP A08:2025 (Software and Data Integrity Failures), SLSA

Affected Code

File: plugin-script-python/src/main/java/io/kestra/plugin/scripts/python/internals/PythonDependenciesResolver.java (lines 292–306)

// Triggered when `uv` binary is not detected on the worker
script = workingDir.createFile("install-uv.sh");
try (InputStream in = URI.create("https://astral.sh/uv/install.sh").toURL().openStream()) {
    Files.copy(in, script, StandardCopyOption.REPLACE_EXISTING);
}
// No hash verification here ↓
execCommandAndGetStdOut(List.of("chmod", "+x", script.toString()));
execCommandAndGetStdOut(List.of("sh", script.toString()), builder -> {
    Map<String, String> env = builder.environment();
    env.clear();
    env.put("HOME", HOME_ENV);
    env.put("PATH", PATH_ENV);
    env.put("UV_INSTALL_DIR", workingDir.path().toString());
    env.put("UV_NO_MODIFY_PATH", "true");
    return builder;
});

Threat Scenario

An attacker who compromises the astral.sh CDN, intercepts DNS for astral.sh, or performs a MITM on a worker running in a network with TLS inspection can serve a modified install.sh. The script executes with the worker's OS identity — enabling exfiltration of secrets from the worker environment, lateral movement to services reachable from the worker, or silent corruption of task outputs.

Remediation

Preferred (eliminate the download path):

  • Pre-install uv in the Docker image used by the Python task runner. The download path is never triggered, removing the attack surface entirely.

If runtime download must be retained:

  • Pin the expected SHA-256 of the installer as a compile-time constant.
  • After download, verify: MessageDigest.getInstance("SHA-256") → compare hex → abort if mismatch.
  • Log the hash and installer URL to the task log for auditability.
  • Expose the expected hash as a configuration property so operators can update it without a code change.
// Fixed pattern
byte[] downloaded = Files.readAllBytes(script);
byte[] actual = MessageDigest.getInstance("SHA-256").digest(downloaded);
String actualHex = HexFormat.of().formatHex(actual);
if (!EXPECTED_UV_INSTALLER_SHA256.equals(actualHex)) {
    throw new KestraRuntimeException(
        "uv installer integrity check failed: expected " + EXPECTED_UV_INSTALLER_SHA256
        + " got " + actualHex);
}

Acceptance Criteria

Fix

  • Vulnerability remediated per Remediation section
  • No new instances of unverified remote script execution in the same module
  • Test added that validates the secure behavior (e.g. a mock CDN returning a tampered script causes the task to fail with a clear error)

Kestra Plugin Coding Standards

  • All new properties use Property<T> — no legacy @PluginProperty(dynamic = true) on new code
  • Secret/credential properties annotated with @PluginProperty(secret = true)
  • Logging via runContext.logger() only — no sensitive data in log statements
  • Build passes with ./gradlew build

References


Part of security audit EPIC: #388
View as Artifact

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/pluginPlugin-related issue or feature requestkind/securitySecurity-related issue

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions