Why this is worth reading: you already paste model output into a terminal, and one bad command is all it takes to delete a directory, force-push over a branch, or pipe a remote script into sh. More prompting does not remove that risk. In this walkthrough, you will build a small pre-execution gatekeeper that catches obvious disasters locally, then asks a free model endpoint for a conservative second decision before you ever run the command.
The problem with model output in a shell
You usually review a generated command in your head, but that review is weakest exactly when the output looks plausible. A model can produce git push origin main --force with full confidence because it does not know your current branch has work you have not pushed. A static review catches patterns you can write down; a model review catches context that is hard to fit in a regex.
The goal is not to block every automated command. It is to insert one consistent control point between “generated text” and “executed shell.” The gatekeeper should be cheap enough to leave in place, open enough to skip when you are explicitly testing it, and safe enough to fail toward asking when the review service is unavailable.
What you will build
You will build a Bash script called ai-guard. It accepts a command and a working directory, runs a set of static dangerous patterns, and then sends the command to a model endpoint for a second pass. On timeout, it fails to ASK rather than silently allowing the command. You can run it locally as a wrapper, or place it on a small free server and call it from CI, cron, or an SSH forced command.
MonkeyCode’s open-source project is the free model access and free server option I will use for the review layer. Disclosure: This article was prepared as part of MonkeyCode's product outreach. The operator’s current materials list 30 million free tokens and a free server slot. Treat those as starting numbers and confirm them from the project dashboard before you depend on them. I will not assume any specific model name, guaranteed latency, or perpetual quota here.
Static rules catch the obviously destructive commands
Copy the following script to /usr/local/bin/ai-guard and make it executable:
#!/usr/bin/env bash
set -euo pipefail
ENDPOINT="${MONKEYCODE_ENDPOINT:?set MONKEYCODE_ENDPOINT}"
AUTH="${MONKEYCODE_AUTH:?set MONKEYCODE_AUTH}"
TIMEOUT_SECS="${GATE_TIMEOUT_SECS:-5}"
cmd="$1"
cwd="${2:-$PWD}"
dangerous_patterns=(
'rm\s+-rf\s+/'
'mkfs\.'
'dd\s+if=/dev/zero'
'git\s+push\s+.*--force'
'curl\s+.*\|\s*(ba)?sh'
'chmod\s+-R\s+777'
'sudo\s+reboot'
)
for pat in "${dangerous_patterns[@]}"; do
if echo "$cmd" | grep -Eq "$pat"; then
echo "DENY static: matched $pat"
exit 1
fi
done
payload=$(jq -n --arg c "$cmd" --arg d "$cwd" \
'{command:$c, cwd:$d, instruction:"Return exactly ALLOW, DENY, or ASK followed by one short reason."}')
response=$(curl -sS --max-time "$TIMEOUT_SECS" \
-H "Authorization: Bearer $AUTH" \
-H "Content-Type: application/json" \
-d "$payload" \
"$ENDPOINT" || true)
if [ -z "$response" ]; then
echo "ASK fallback: endpoint did not respond in ${TIMEOUT_SECS}s"
exit 2
fi
decision=$(echo "$response" | jq -r '.decision // "ASK"')
reason=$(echo "$response" | jq -r '.reason // "no reason"')
echo "${decision}: ${reason}"
case "$decision" in
ALLOW|allow) exit 0 ;;
DENY|deny) exit 1 ;;
*) exit 2 ;;
esac
This is a deliberately small artifact, not a security product. The static list is your first line of defense because it costs no tokens and never times out. The model pass is for cases that need more context, such as a destructive-looking command that might be valid in one directory but not another.
The free model endpoint does the second pass
You run the gatekeeper with:
./ai-guard "git push origin main --force" "$PWD"
The static matcher will deny that command before the endpoint is called. For a command that passes the static checks, the script constructs a single small JSON payload and sends it to MONKEYCODE_ENDPOINT. The prompt is intentionally narrow: return ALLOW, DENY, or ASK with one short reason. You want one decision, not a paragraph.
If the endpoint does not answer within five seconds, the script exits with status 2 and prints ASK fallback. That is the control you want when a free endpoint goes quiet. You can tune GATE_TIMEOUT_SECS down to two seconds for interactive use, but remember that lower timeouts cause more ASK fallbacks.
Run it on the free server
If all you need is a local wrapper, you can stop here. The more useful option is to put the script on a small server and call it from a pipeline. On the free server option, create a dedicated user and copy the script into its home directory.
A minimal systemd service can keep the environment variables in one place:
[Service]
Environment=MONKEYCODE_ENDPOINT=your-endpoint-url
Environment=MONKEYCODE_AUTH=your-token
Environment=GATE_TIMEOUT_SECS=5
ExecStart=/usr/local/bin/ai-guard
User=gatekeeper
Restart=on-failure
You would more likely invoke it through SSH or a small HTTP wrapper, but the unit keeps the token out of your shell history and makes the timeout policy explicit. Do not put the token in the script itself.
Test fixtures and failure modes
You need at least three fixtures before you trust this in a real workflow:
-
./ai-guard "git push origin main --force"should exit1from the static deny list. -
./ai-guard "python3 -m pytest tests/"should pass static and return a model decision without hanging. -
GATE_TIMEOUT_SECS=1 ./ai-guard "python3 -m pytest tests/"with the endpoint unreachable should exit2and printASK fallback.
Run the timeout fixture by pointing MONKEYCODE_ENDPOINT at a port nothing is listening on, or by temporarily unsetting the token. If the failure mode is not obvious, the gatekeeper is not ready.
Cost and boundaries
You pay token cost only for commands that survive the static list. If most of your generated commands are ordinary python, npm, or git status calls, the review pass is short. The operator-supplied 30 million free tokens are enough for a very large number of one-line reviews, but the exact consumption depends on the endpoint and prompt length. Measure one real review from the dashboard before you assume a daily budget.
This approach is not a substitute for sandboxing untrusted model output. You should still run risky work in a disposable environment when you can. The gatekeeper is a second decision, not a permission system with a strong identity.
Who should skip this
Do not use this as your only control if you are running model output as root, against production data, or in an environment where a five-second timeout breaks an automated pipeline. A static deny list also gives you false confidence if you never update it. If you need an audit trail, combine this with a separate execution log instead of relying on the gatekeeper output alone.
Try the script with three commands from your own recent work. Add the one dangerous pattern your stack actually needs before you wire it any further. What single command pattern would you add to the static deny list first?
Top comments (0)