Skip to content

Project .ralphrc is executed as Bash before Ralph's trust and validation steps #346

Description

@glmgbj233

Summary

Ralph unconditionally executes a project-root .ralphrc with Bash source during normal startup. Because .ralphrc is a repository-controlled file, a later change to an existing Ralph project can add arbitrary shell commands that run with the invoking user's privileges when the user starts Ralph in that project. The same behavior occurs with --dry-run.

This is not an "open or clone a repository" issue: the user must have Ralph installed and explicitly run ralph (or ralph_loop.sh) from the project directory. The concern is that the normal project-level enablement/authorization is not bound to the current .ralphrc contents, and no separate trust or content-change confirmation is performed before the file is sourced.

Affected Revision

The behavior is present in e8533cc3f00900e6f3f4acf8c8761e1db4a26e47 (main, checked 2026-08-10). The latest upstream main checked for this report resolves to the same commit. No SECURITY.md or equivalent security policy was found in that revision.

Impact

An attacker who can introduce or modify .ralphrc in a project that the victim later processes with Ralph can execute arbitrary Bash as the victim before Claude CLI validation, sandbox initialization, model execution, or Claude tool permissions. The command can read or modify files available to the user, access user-scoped environment and network resources, and launch other local processes. The minimum trigger is one explicit Ralph startup from the affected project directory.

Reproduction

The following reproducer uses a temporary project, a marker file, /bin/true as the Claude command, and --dry-run. It does not invoke Claude, use an API key, make a network request, or modify the cloned repository.

  1. Obtain the source and record the loop script path:
git clone https://github.com/frankbria/ralph-claude-code.git
cd ralph-claude-code
RALPH_LOOP="$PWD/ralph_loop.sh"
  1. Create a minimal Ralph project in a separate temporary directory:
TEST_DIR="$(mktemp -d)"
mkdir -p "$TEST_DIR/.ralph" "$TEST_DIR/home"
printf '%s\n' '# test prompt' > "$TEST_DIR/.ralph/PROMPT.md"
printf '%s\n' '# test plan' > "$TEST_DIR/.ralph/fix_plan.md"
printf '%s\n' '# test agent instructions' > "$TEST_DIR/.ralph/AGENT.md"
  1. Add this repository-controlled .ralphrc:
printf '%s\n' "printf '%s\\n' RALPHRC_EXECUTED > .ralphrc-marker" "exit 0" > "$TEST_DIR/.ralphrc"

The resulting file contains:

printf '%s\n' RALPHRC_EXECUTED > .ralphrc-marker
exit 0
  1. Run Ralph from the temporary project directory:
(
    cd "$TEST_DIR"
    HOME="$TEST_DIR/home" \
    CLAUDE_CODE_CMD=/bin/true \
    CLAUDE_AUTO_UPDATE=false \
    bash "$RALPH_LOOP" --dry-run
)

Expected result: Ralph exits immediately because the sourced .ralphrc calls exit 0, and the following command succeeds:

test "$(cat "$TEST_DIR/.ralphrc-marker")" = RALPHRC_EXECUTED

The marker is created before Ralph can reach its normal project validation or dry-run loop. Removing the exit 0 line allows Ralph to continue into the normal loop; it is included only to make the reproducer terminate without a real Claude invocation.

Control

Repeat the fixture creation and run command without creating $TEST_DIR/.ralphrc. The marker must remain absent. On the current revision, Ralph proceeds past the loader and later exits with an integrity error reporting .ralphrc as missing. This control shows that the marker is produced by the project file being sourced.

Source Evidence

At ralph_loop.sh:291-298, load_ralphrc() checks only whether .ralphrc exists and then executes:

source "$RALPHRC_FILE"

main() calls load_ralphrc() at ralph_loop.sh:2496-2501. The call precedes Claude validation at ralph_loop.sh:2594-2608, the project prompt check at ralph_loop.sh:2614-2637, integrity validation at ralph_loop.sh:2639-2646, and sandbox startup. The dry-run branch is later in execute_claude_code() at ralph_loop.sh:1798-1806, so --dry-run does not prevent the source operation. The documented CLI behavior also states that the project-root .ralphrc is sourced before each loop in docs/CLI_OPTIONS.md:304-306.

Suggested Fix

Do not source repository-controlled configuration. Parse .ralphrc as a strict data format with an allowlisted key schema and reject shell syntax, or move executable settings to a user-owned configuration outside the repository. If project configuration must remain executable, require an explicit first-use confirmation that displays the resolved command-bearing values, bind approval to the canonical workspace and a digest of the final configuration, re-check that digest immediately before every sensitive launch, and require a new confirmation after changes. The approval should cover fields such as CLAUDE_CODE_CMD, RALPH_SHELL_INIT_FILE, sandbox settings, and any future launcher paths rather than only the project path.

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