Conversation
Recognize shell syntax in app run commands so variables, pipelines, and redirects execute as users expect without changing ordinary argv semantics. Decline shell expressions on old clusters whose entrypoint exec path cannot preserve a script argument safely.
|
Warning Review paused — included plan limit reachedKeep your review moving with free on-demand reviews.
On-demand reviews are free for the next 16 days.
Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing. Promotion and pricing detailsOn-demand reviews are free for the next 16 days. After that, they cost $0.25 per reviewed file. Review limit detailsOr wait 9 minutes for your next included review. Limit details: You’ve used all 6 included reviews currently available. Your 40 included PR review attempts over the past 7 days set your current allowance at 6 reviews per hour. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (5)
Comment |
Only treat a whole command string or standalone operator tokens as shell syntax. Characters embedded in interpreter arguments, SQL, URLs, and patterns stay literal; commit regenerated app run docs so CI's generation check stays clean.
phinze
left a comment
There was a problem hiding this comment.
We figured this might have been handled by the durable-runs rework of app run, but it wasn't. miren app run -- 'echo $HOME | wc -c' still exits 127 on main, because shellQuote deliberately mutes the server's sh -c. The single-string form is a clean fix. The one thing I'd change before merging is the inline note about the multi-argument operator branch.
One design question, not blocking: since the server already runs every override through sh -c, would it be cleaner for CreateRun to take a "this is shell source" flag and skip shellQuote, instead of sending a client-built /bin/sh -c that gets quoted and wrapped in a second shell? That would also make app runs list show the command as typed.
--p+🤖
| parts := make([]string, len(args)) | ||
| for i, arg := range args { | ||
| switch arg { | ||
| case "|", "||", "&&", ";", "&", ">", ">>", "<", "<<", "2>", "2>>": |
There was a problem hiding this comment.
🤖 This branch only ever sees a standalone operator when the user escaped it on purpose. Unescaped, miren app run -- echo hi | wc has the local shell eat the | before we run. And people escape operators because they want the literal character. Both of these work on main and break here:
find . -name '*.log' -exec rm {} \; -> find: missing argument to `-exec'
expr 3 '>' 2 -> prints nothing, creates a file named "2"
Dropping ; fixes find, but expr, test/[, and awk programs take >, <, |, and & as literal arguments too. I'd drop the multi-argument branch altogether and let the single-string form carry shell syntax. The docs already steer people there, and it's the only form you can type without hand-escaping operators.
There was a problem hiding this comment.
I agree, and I should have followed my own reasoning further. In my last review I called grep -F '|' rare. But you're right that the local shell consumes any unescaped operator before runCommand sees it. So every lone |, >, ; or & that reaches line 117 was quoted or escaped on purpose, and that points to literal intent, not shell structure. I checked the code: expr 3 '>' 2 goes to the > case, gets joined as 'expr' '3' > '2', and redirects into a file named 2. That's a silent write, not just a failed command, which makes it worse than the find case. The same goes for test/[ and awk programs. Dropping ; alone wouldn't fix any of this. I'd also remove the whole multi-argument branch and keep only the len(args) == 1 shell-source path. A nice side effect is that legacyRunCommandCheck then only rejects the single-string form, so older clusters lose less.
There was a problem hiding this comment.
I checked the current head: the multi-argument branch has been removed as you suggested. find … -exec … ; and expr 3 '>' 2 now pass through as argv, and there are unit and blackbox tests for both. Removing it also let legacyRunCommandCheck go, so older clusters are back to how main behaves. I'll leave this one for you to close.
There was a problem hiding this comment.
🤖 Agreed. Operators that reach the CLI were quoted or escaped locally, so treating them as shell syntax was unsafe. The merged change only interprets a single command string as shell source; the affected blackbox test passed.
An operator reaching the CLI as a separate argument was escaped or quoted by the caller and must remain data. Restrict automatic shell execution to a single command string and preserve the legacy fallback unchanged.
There was a problem hiding this comment.
🍪 biscuit: ✅ ready to merge — auto-review, non-blocking
I'm happy for this to merge. Both caveats from my last review are gone, and the fixes are the ones phinze and I asked for.
The multi-argument operator branch has been removed. runCommand (app_run.go:96-102) now switches to /bin/sh -c only for a single argument that contains shell syntax or whitespace. Anything with more than one argument keeps its argv unchanged. The comment above it gives the right reason: once the local shell has removed the quoting, a standalone operator is data the user escaped on purpose. TestRunCommand has rows for find … -exec rm {} ;, expr 3 > 2, grep -F '|' and echo hi > /tmp/result, and each one asserts that argv is preserved. TestRunPreservesArgumentBoundaries in the blackbox suite checks expr 3 > 2 end to end, so the silent-redirect-into-a-file-named-2 case now has a test at the level where it would actually cause harm.
The older-cluster regression is gone too. legacyRunCommandCheck has been removed, and appRunLegacy gets the raw opts.Args again. That means miren app run -- 'bin/rails console' on a pre-runs cluster behaves exactly as it does on main.
I also traced the wrapped form through the server. resolveCommand/shellQuote turn it into '/bin/sh' '-c' '<src>', and appspec puts the config entrypoint in front of that. So on CNB apps it runs as launcher /bin/sh -c <src> and still gets the buildpack environment.
I have one small inline note: a new blackbox assertion depends on an env var alias that is deprecated and scheduled for removal. It doesn't block the merge, but it's a one-word fix.
I'm resolving my open thread. I'm leaving phinze's thread for them to close, since they opened it.
🍪 full review note · reviewed at f9e365b · comment /biscuit review to run biscuit again.
| t.Fatalf("quoted comparison was interpreted as redirection; want 1, got:\n%s", r.Stdout) | ||
| } | ||
|
|
||
| r = m.MustRun("app", "run", "-a", name, "--", "echo $MIREN_APP") |
There was a problem hiding this comment.
MIREN_APP is the deprecated alias that api/app/runtimeenv.go keeps only for a deprecation window. The comment there says the aliases "will be removed in a future release". Once that happens, this assertion fails because $MIREN_APP expands to nothing, and that has nothing to do with shell handling. Please use echo $MIREN_RUNTIME_APP instead, since that's the canonical name appspec injects. The test checks the same thing and doesn't depend on the alias.
|
Responding to the biscuit review and human review: the merged change preserves every multi-argument invocation as argv and retains the legacy fallback unchanged, rather than rejecting single-string commands. I kept the existing run protocol instead of adding a shell-source flag, which would widen this compatibility change. |
Why
miren app runtreated shell expressions supplied as a single command string as literal argv. Operators need expansion, pipelines, and redirection in one-off runs, without changing commands that pass arguments to interpreters or tools.Assumptions and compatibility
miren app run -- 'echo $HOME | wc -c'on current clusters.find -exec ... \;orexpr 3 '>' 2).Verification
make lint— 0 issuesgo test -p 1 ./cli/commands -count=1— passedgo test -tags blackbox -timeout 15m -v -count=1 -p 1 -run '^TestRunPreservesArgumentBoundaries$' ./blackbox— passed against the local dev cluster (literal operator argv, pipeline, expansion, redirect, exit code)make generate-check— passedLinear: https://linear.app/miren/issue/MIR-871