Skip to content

Bash completion script breaks on special characters in user input (unsafe eval of COMP_WORDS) #440

Description

@billlevine

Problem

The bash completion script generated by --bpaf-complete-style-bash produces shell parse errors when the user's partial input contains unclosed quotes, backticks, or other shell-special characters.

For example, with a program that takes a -c flag accepting a string argument:

$ myprogram -c "bas<TAB>
bash: eval: line 5: unexpected EOF while looking for matching '"'

The same issue affects zsh:

% myprogram -c "<TAB>
(eval):1: unmatched "

Root Cause

The generated completion function in complete_run.rs (dump_bash_completer) constructs a command string via interpolation and passes it to eval:

_bpaf_dynamic_completion()
{
    line="$1 --bpaf-complete-rev=8 ${COMP_WORDS[@]:1}"
    if [[ ${COMP_WORDS[-1]} == "" ]]; then
        line="${line} \"\""
    fi
    source <( eval ${line})
}

The ${COMP_WORDS[@]:1} expansion is interpolated into a flat string assigned to line, then eval ${line} re-parses that string. Any shell metacharacters in user input (unclosed ", ', `, $(), etc.) are interpreted by eval rather than being passed as literal arguments.

Context: Why eval Was Introduced

I understand that eval was added intentionally in a03ca9f to fix #315. The previous version used direct array invocation:

source <( "$1" --bpaf-complete-rev=8 "${COMP_WORDS[@]:1}" )

This broke when the program was invoked via a path containing ~ or environment variables (e.g. ~/src/myapp/target/debug/myprogram), because COMP_WORDS[0] stores the literal ~/src/... and process substitution <( ... ) doesn't perform tilde expansion.

So the eval correctly fixes the tilde/env-var expansion issue from #315 but introduces this metacharacter problem.

Possible Approach

A fix would need to preserve the tilde/env-var expansion for the program name while keeping user arguments safe. One approach: eval only the program path, then use array-based passing for everything else:

_bpaf_dynamic_completion()
{
    local _prog
    eval _prog="$1"
    local -a _args=("$_prog" "--bpaf-complete-rev=8" "${COMP_WORDS[@]:1}")
    if [[ ${COMP_WORDS[-1]} == "" ]]; then
        _args+=("")
    fi
    source <( "${_args[@]}" )
}

This expands ~ and $VARS in the program path (preserving the #315 fix) while passing the remaining COMP_WORDS as discrete array elements, so special characters in user input are never re-interpreted.

I haven't tested this against all the edge cases you'd know better than me, so treat this as a starting point rather than a proven fix.

Versions

Observed on bpaf 0.9.x (the dump_bash_completer function in src/complete_run.rs).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions