Files
agent-skills/skills/remote-shell-quoting-safety/SKILL.md
T
iWP Claudy 39a637410c Initial skill library: 9 skills for delegate briefs
WordPress plugin rebrand/conventions/remote-CLI patterns, Gitea release
workflow, bastille jail provisioning, remote shell quoting safety, server
fleet map, delegate brief writing, and verification discipline -- all
derived from real incidents this session, plus two skills adapted (MIT
license, attributed) from obra/superpowers and andrej-karpathy-skills.
2026-08-02 20:08:29 +02:00

3.2 KiB

name, description
name description
remote-shell-quoting-safety Use when a command or file content needs to pass through multiple nested shell layers (e.g. local shell -> ssh -> a jail/container exec wrapper -> su -> the target interpreter). Avoids silent corruption of $, quotes, and special characters.

Remote Shell Quoting Safety

The problem

Content containing $, quotes, or other shell-special characters gets progressively mangled the more shell layers it passes through (local shell → ssh → a container/jail exec wrapper like bastille cmdsu -c → the actual target interpreter). Each layer's own quoting rules interact, and by 3+ layers deep it becomes extremely hard to reason about correctly — and failures are often silent: no error, just corrupted content (e.g. $_SERVER['KEY'] silently becoming ['KEY'], $ and the variable name simply eaten).

Confirmed real incident: a wp-config.php snippet containing $_SERVER['HTTP_X_FORWARDED_PROTO'] passed through ssh → bastille cmd → su -m www -c '...' came out the other side as bare ["HTTP_X_FORWARDED_PROTO"] — no error at the point of corruption, the resulting fatal PHP error only surfaced two steps later at a completely different command, making the actual cause much harder to trace back.

The fix: base64, not nested quoting

Don't try to escape correctly through N layers. Instead:

  1. Write the target file's complete final content locally (a real file, not a shell variable).
  2. Base64-encode it locally: base64 -w0 localfile > /tmp/payload.b64 (or pipe directly).
  3. cat/pipe the base64 text through the SSH/exec layers (base64 output is alphanumeric-only — nothing for any shell layer to misinterpret, regardless of how many layers it passes through).
  4. Decode back to the real file on the far side, in ONE final step: base64 -d payload.b64 > /real/target/path.
B64=$(base64 -w0 local_file.php)
echo "$B64" | ssh user@host "cat > /tmp/upload.b64"
ssh user@host "base64 -d /tmp/upload.b64 > /real/target/path && rm /tmp/upload.b64"

For a jail/container layer on top of SSH (e.g. bastille cmd <jail>), land the base64 payload on the host filesystem first (which the jail can usually see via a shared mount or a copy step), then decode from inside the jail in one command — don't try to thread the base64 string itself through the jail-exec wrapper's own quoting too; one decode step per real filesystem boundary crossed is enough.

When to reach for this

  • Any file content containing $, backticks, mixed quotes, or PHP/shell code being pushed through 2+ nested shell contexts.
  • Whenever a "the file exists but does something subtly wrong" bug appears after a multi-hop deployment step, and the content involves special characters — check for quoting corruption before looking elsewhere.

What NOT to do

Don't respond to a quoting failure by adding more layers of escaping (\\\$, '"'"', etc.) — this usually makes the problem harder to reason about, not easier, and is exactly the kind of fragile fix that looks like it works until the next slightly-different payload breaks it again. Switch to the base64 pattern instead of debugging escape depth.