Skip to content

quote remote job paths in posix ssh command builders - #70091

Merged
potiuk merged 1 commit into
apache:mainfrom
Samin061:posix-path-quoting
Jul 31, 2026
Merged

quote remote job paths in posix ssh command builders#70091
potiuk merged 1 commit into
apache:mainfrom
Samin061:posix-path-quoting

Conversation

@Samin061

Copy link
Copy Markdown
Contributor

The POSIX builders in remote_job.py interpolate RemoteJobPaths values into single-quoted shell words without escaping, so a single quote anywhere in remote_base_dir closes the quote and the rest of the value is parsed as commands that run on the remote host as the SSH connection user. remote_base_dir is a template field, but _validate_base_dir runs in __init__ against the un-rendered Jinja literal and never looks for shell metacharacters, so a value like {{ dag_run.conf['dir'] }} reaches the shell unchecked and someone who can only trigger a Dag run with config gets a shell on the target host. Quoting with shlex.quote at each site matches what the Windows builders already do via ps_escape.

All six POSIX builders are affected. The _validate_job_dir prefix check on cleanup does not help, since a base dir that starts with /tmp/airflow-ssh-jobs/ and then contains a quote still passes it:

base_dir = "/tmp/airflow-ssh-jobs/x'; touch /tmp/pwned; :'"
build_posix_cleanup_command(paths.job_dir)
# rm -rf '/tmp/airflow-ssh-jobs/x'; touch /tmp/pwned; :'/job_123'

Added a parametrized regression that runs each builder's output through sh and asserts the injected marker is never created; it fails on all six before this change.


Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude Code (Opus 4.8) following the guidelines

@potiuk potiuk added the ready for maintainer review Set after triaging when all criteria pass. label Jul 20, 2026

@potiuk potiuk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — this is a proper hardening fix and the test is the best part of it.

The bug is real: job_dir='{paths.job_dir}' closes and reopens on any single quote in the path, so a remote_base_dir containing ' stops being data and becomes shell syntax. shlex.quote() is the correct tool, applied consistently across all six POSIX builders.

The test actually proves it. Rather than asserting on generated string content, TestPosixPathQuoting builds a real payload — /tmp/airflow-ssh-jobs/x'; touch {marker}; :'executes the generated command through sh -c, and asserts the marker file was never created, parametrised across all six builders. That's an executable proof of non-injection rather than a proxy for one, and it would catch a future builder that forgets to quote. Updating cat '/tmp/pid' -> cat /tmp/pid in the existing assertion is right too, since shlex.quote leaves simple paths bare.

Two notes, neither blocking:

The Windows builders are untouchedbuild_windows_log_tail_command, build_windows_file_size_command, build_windows_completion_check_command and build_windows_cleanup_command still interpolate directly. That matches your title's scoping, and shlex.quote would be actively wrong there (it emits POSIX quoting, not cmd.exe/PowerShell), so this isn't something to bolt on here. Worth a follow-up issue so the asymmetry is recorded rather than forgotten.

Framing: under Airflow's security model the Dag author is a trusted party who can already execute arbitrary code, so remote_base_dir isn't an untrusted input boundary — this is a robustness/correctness fix, not a vulnerability. Worth saying so in the description so nobody routes it through the security process.


Drafted-by: Claude Code (Opus 5); reviewed by @potiuk before posting

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:providers provider:ssh ready for maintainer review Set after triaging when all criteria pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants