Drop quotesu/ setup doc; make the demo take any sicd host as an argument
The quotesu setup notes exposed internal host details and aren't useful to readers, so remove the directory. The quoting-hell demo now requires the target host as an argument (any host running sicd) instead of defaulting to an internal name, and its labels say sic rather than the old rs. Example hostnames in the README/SKILL/main.go are now the neutral placeholder host1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
This commit is contained in:
+29
-27
@@ -3,18 +3,20 @@
|
||||
|
||||
Each scenario is run both ways:
|
||||
A) Plain SSH — shows the breakage
|
||||
B) sic tool — shows the fix
|
||||
B) sic — shows the fix
|
||||
|
||||
Usage:
|
||||
python3 demo-quoting-hell.py <host>
|
||||
Point it at any host that runs sicd (reachable over SSH with your key):
|
||||
|
||||
python3 quoting-hell.py <host>
|
||||
"""
|
||||
import subprocess
|
||||
import sys
|
||||
import shlex
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
HOST = sys.argv[1] if len(sys.argv) > 1 else "quotesu"
|
||||
if len(sys.argv) < 2:
|
||||
sys.exit("usage: python3 quoting-hell.py <host> # any host running sicd")
|
||||
|
||||
HOST = sys.argv[1]
|
||||
|
||||
SIC = os.path.expanduser("~/bin/sic")
|
||||
SSH = ["/usr/bin/ssh", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=5"]
|
||||
@@ -29,7 +31,7 @@ def ssh(cmd_str):
|
||||
|
||||
|
||||
def sic(*args):
|
||||
"""Run argv via sic tool (which frames netstrings for sicd)."""
|
||||
"""Run argv via sic (which frames netstrings for sicd)."""
|
||||
return subprocess.run(
|
||||
[SIC, HOST] + list(args),
|
||||
capture_output=True, text=True, timeout=10
|
||||
@@ -62,7 +64,7 @@ def result(label, r):
|
||||
# ── 1. Space in filename ──────────────────────────────────────────
|
||||
section("1. Space in filename — the classic")
|
||||
|
||||
sub("SSH: ssh host touch 'my file.txt'")
|
||||
sub(f"SSH: ssh {HOST} touch 'my file.txt'")
|
||||
# SSH concatenates args with spaces, remote shell re-splits
|
||||
r = ssh("touch 'my file.txt'")
|
||||
r2 = ssh("ls my file.txt 2>&1; echo ---; ls 'my file.txt' 2>&1")
|
||||
@@ -72,10 +74,10 @@ print(" → 'touch' sees TWO args: 'my' and 'file.txt' — two files created"
|
||||
# Clean up
|
||||
ssh("rm -f my file.txt 'my file.txt'")
|
||||
|
||||
sub("rs: rs quotesu touch 'my file.txt'")
|
||||
sub(f"sic: sic {HOST} touch 'my file.txt'")
|
||||
r = sic("touch", "my file.txt")
|
||||
r2 = sic("ls", "-la", "my file.txt")
|
||||
result("rs", r2)
|
||||
result("sic", r2)
|
||||
print(" → 'touch' sees ONE arg: 'my file.txt' — one file created")
|
||||
sic("rm", "-f", "my file.txt")
|
||||
|
||||
@@ -83,59 +85,59 @@ sic("rm", "-f", "my file.txt")
|
||||
# ── 2. Dollar sign / variable expansion ───────────────────────────
|
||||
section("2. Dollar sign — unintended variable expansion")
|
||||
|
||||
sub("SSH: ssh host 'echo \$HOME'")
|
||||
sub(f"SSH: ssh {HOST} 'echo \$HOME'")
|
||||
r = ssh("echo $HOME")
|
||||
result("SSH", r)
|
||||
print(" → Remote shell expands $HOME — leaks local values")
|
||||
|
||||
sub("SSH escaped: ssh host 'echo \\$HOME'")
|
||||
sub(f"SSH escaped: ssh {HOST} 'echo \\$HOME'")
|
||||
r = ssh("echo \\$HOME")
|
||||
result("SSH escaped", r)
|
||||
|
||||
sub("rs: rs quotesu echo '\$HOME'")
|
||||
sub(f"sic: sic {HOST} echo '\$HOME'")
|
||||
r = sic("echo", "$HOME")
|
||||
result("rs", r)
|
||||
result("sic", r)
|
||||
print(" → No shell involved — literal $HOME printed")
|
||||
|
||||
|
||||
# ── 3. Backticks / command substitution ──────────────────────────
|
||||
section("3. Backticks — unintended command execution")
|
||||
|
||||
sub("SSH: ssh host 'echo `hostname`'")
|
||||
sub(f"SSH: ssh {HOST} 'echo `hostname`'")
|
||||
r = ssh("echo `hostname`")
|
||||
result("SSH", r)
|
||||
print(" → Backticks execute on remote — prints quotesu")
|
||||
print(" → Backticks execute on remote — prints the remote hostname")
|
||||
|
||||
sub("rs: rs quotesu echo '`hostname`'")
|
||||
sub(f"sic: sic {HOST} echo '`hostname`'")
|
||||
r = sic("echo", "`hostname`")
|
||||
result("rs", r)
|
||||
result("sic", r)
|
||||
print(" → Literal backticks — no execution")
|
||||
|
||||
|
||||
# ── 4. Nested quotes ─────────────────────────────────────────────
|
||||
section("4. Nested quotes — escaping hell")
|
||||
|
||||
sub('SSH: ssh host "echo \\"hello world\\""')
|
||||
sub(f'SSH: ssh {HOST} "echo \\"hello world\\""')
|
||||
r = ssh('echo "hello world"')
|
||||
result("SSH", r)
|
||||
|
||||
sub('rs: rs quotesu echo "hello world"')
|
||||
sub(f'sic: sic {HOST} echo "hello world"')
|
||||
r = sic("echo", "hello world")
|
||||
result("rs", r)
|
||||
result("sic", r)
|
||||
print(" → No nested quoting needed — just pass the string")
|
||||
|
||||
|
||||
# ── 5. Newlines in arguments ──────────────────────────────────────
|
||||
section("5. Newlines in arguments")
|
||||
|
||||
sub("SSH: ssh host 'echo \"line1\\nline2\"'")
|
||||
sub(f"SSH: ssh {HOST} 'echo \"line1\\nline2\"'")
|
||||
r = ssh('echo "line1\nline2"')
|
||||
result("SSH", r)
|
||||
print(" → Newline in SSH command string is fragile")
|
||||
|
||||
sub("rs: rs quotesu echo 'line1\\nline2'")
|
||||
sub(f"sic: sic {HOST} echo 'line1\\nline2'")
|
||||
r = sic("echo", "line1\nline2")
|
||||
result("rs", r)
|
||||
result("sic", r)
|
||||
print(" → Newline is just a byte in a netstring field")
|
||||
|
||||
|
||||
@@ -149,7 +151,7 @@ r = ssh(script)
|
||||
result("SSH", r)
|
||||
print(" → Try to get all those quoting levels right...")
|
||||
|
||||
sub("rs: rs quotesu python3 -c ... with clean args")
|
||||
sub("sic: sic <host> python3 -c ... with clean args")
|
||||
r = sic(
|
||||
"python3", "-c",
|
||||
"import sys; [print(i, repr(a)) for i,a in enumerate(sys.argv)]",
|
||||
@@ -160,7 +162,7 @@ r = sic(
|
||||
"arg with `ls`",
|
||||
'nested "quotes"'
|
||||
)
|
||||
result("rs", r)
|
||||
result("sic", r)
|
||||
print(" → Each argument is a separate field — no escaping needed")
|
||||
|
||||
|
||||
@@ -169,7 +171,7 @@ section("SUMMARY")
|
||||
print("""
|
||||
The pattern in every case:
|
||||
SSH → flattens argv to one string, shell re-parses → QUOTING HELL
|
||||
rs → sends argv as framed fields, execvp directly → NO ESCAPING
|
||||
sic → sends argv as framed fields, execvp directly → NO ESCAPING
|
||||
|
||||
Netstrings (length:bytes,) are the key: the length tells the parser
|
||||
exactly where each field ends, so NO delimiter needs escaping.
|
||||
|
||||
Reference in New Issue
Block a user