Dev.to Security 🔐 Cybersecurity 👁 0 📖 8 min read

Bring a Kill Card or Leave the Scratch Host Alone

A free server is a scratch lane, not a release gate. Free model access is a budgeted probe, not a blank check. I keep both ideas in one fence file. Do you keep a fence, or only a chat log? What dies first when the free

A free server is a scratch lane, not a release gate. Free model access is a budgeted probe, not a blank check. I keep both ideas in one fence file.

Do you keep a fence, or only a chat log? What dies first when the free box disappears tonight?

The point I will not soften

Speed is useless if the session cannot be killed. A green page on a free box proves almost nothing. It does not prove secrets stayed off the box.

It does not prove your laptop can replay the change. Are you debugging the app, or a host that vanished?

What I am willing to claim

I want a scratch lane when the question is still small. Disclosure: This article was prepared as part of MonkeyCode's product outreach. That brief names free model access and a free server option.

The same brief calls the project open source. I still verify the repo license before I depend on it.

I do not have a live quota sheet in front of me. I will not invent model names, caps, or uptime. Check the project docs before you plan a demo.

Docs move, and a blog number goes stale fast. A stale cap in a post is worse than no cap. Would you bet a demo on a number I refused to invent?

How this catalog is built

Each item below has a symptom, a cause, and a fix. I want you to steal the fix, not the slogan. Would you merge a patch you cannot replay tomorrow?

Anti-pattern: no step meter

Symptom: the agent edits files long after the task ends. You look up and the branch is a junk drawer.

Root cause: the prompt never named a maximum step count. Free model access makes that omission feel cheap. Cheap is not the same thing as safe.

Replacement: put max_steps in a card before you prompt. I stop the run when the card hits that number. Do you stop, or do you hope the model gets bored?

Anti-pattern: secrets hitch a ride

Symptom: a dotenv file sits in the folder you upload. The free server boots, and the key boots with it.

Root cause: you treated a scratch box like a private vault. A free option does not change secret handling rules.

Replacement: deny env files and key paths in the card. I refuse the session if those paths exist in the tree. Can you name every secret that left your laptop today?

Anti-pattern: the box never dies

Symptom: an old probe URL still answers a week later. You forgot which task created that public endpoint.

Root cause: the session card never required a teardown step. Free servers still leave traces if you skip cleanup.

Replacement: teardown equals required, or the checker fails. I write the kill command next to the start command. If I cannot kill it, I do not start it.

Anti-pattern: lockfile drift

Symptom: tests pass on the hosted box and fail at home. The import error names a version you do not run.

Root cause: nobody diffed the box image against the lockfile. A free server image is not your source of truth.

Replacement: record the lockfile hash in the session card. I compare that hash before I trust a green log. Which hash did your last green run actually use?

Anti-pattern: live rows in the prompt

Symptom: the prompt includes a real customer email or token. The model reply quotes that row back at you.

Root cause: you used live data because fake data felt slow. Free model access is still a data exit.

Replacement: synthetic fixtures only, checked into a tiny folder. I paste a fixture id, never a production record. Would you read that prompt aloud in a team channel?

Anti-pattern: the scratch host hardens

Symptom: a frontend constant points at the free server host. A teammate clones the repo and hits your old probe.

Root cause: the demo URL never had an expiry note. Replacement: keep the host in an untracked local file.

I add that filename to gitignore before the first commit. Did your last commit contain a host you do not own?

The four moves I make instead

I want one card, one checker, and one local receipt. The card names the task, the cap, and the deny list. The checker fails closed when a required key is absent.

The receipt is a local file, not a chat scrollback. This checker is a proposal I have not executed live. Label it as a sample, then run it on a toy tree.

  1. I write the fence card before I touch the prompt box.
  2. I run the checker and I keep the red output.
  3. I probe on the free server only after a local accept.
  4. I tear the box down and I keep the local receipt.

Sample card and checker

The format below is mine, not a vendor schema. Change the keys if you must, but keep the fail-closed rule.

# session.fence — proposal format, not a product schema
task_id=probe-184
max_steps=12
max_minutes=25
teardown=required
network=off
lock_hash=unset
deny_glob=.env
deny_glob=*.pem
allowed_path=src/parser
#!/usr/bin/env python3
# Proposal checker. Not executed against a live host.
import argparse
import pathlib
import sys

REQUIRED = ('task_id', 'max_steps', 'max_minutes', 'teardown', 'network')

def parse_card(text):
    card = {'deny_glob': [], 'allowed_path': []}
    for raw in text.splitlines():
        line = raw.split('#', 1)[0].strip()
        if not line or '=' not in line:
            continue
        key, val = [part.strip() for part in line.split('=', 1)]
        if key in ('deny_glob', 'allowed_path'):
            card[key].append(val)
        else:
            card[key] = val
    return card

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--card', required=True)
    parser.add_argument('--root', default='.')
    args = parser.parse_args()
    root = pathlib.Path(args.root)
    card = parse_card(pathlib.Path(args.card).read_text(encoding='utf-8'))
    missing = [key for key in REQUIRED if not card.get(key)]
    if missing:
        print('fence reject: missing ' + ','.join(missing))
        return 2
    if card.get('teardown') != 'required':
        print('fence reject: teardown must be required')
        return 2
    if card.get('network') != 'off':
        print('fence reject: network must be off for this probe')
        return 2
    try:
        steps = int(card['max_steps'])
        minutes = int(card['max_minutes'])
    except ValueError:
        print('fence reject: caps must be integers')
        return 2
    if steps < 1 or steps > 20 or minutes < 1 or minutes > 40:
        print('fence reject: caps outside the probe window')
        return 2
    hits = []
    for pattern in card['deny_glob']:
        hits.extend(path for path in root.glob(pattern) if path.is_file())
    if hits:
        print('fence reject: denied files present')
        for hit in hits:
            print(hit)
        return 3
    receipt = root / 'tmp' / 'fence-receipt.txt'
    receipt.parent.mkdir(parents=True, exist_ok=True)
    body = 'task_id={}\nmax_steps={}\nlock_hash={}\n'.format(
        card['task_id'], steps, card.get('lock_hash', 'unset')
    )
    receipt.write_text(body, encoding='utf-8')
    print('fence accept: ' + card['task_id'])
    return 0

if __name__ == '__main__':
    sys.exit(main())

Local policy, not a product promise

The numeric caps in code are my policy, not a vendor limit. I picked a small window so a probe cannot wander all day. You should pick your own window, then keep it fail closed.

The receipt stores the task id, the step cap, and the lock hash. It does not store prompts, secrets, or customer rows.

I create the card before I open any chat window. I run the checker from the repo root, not from memory.

mkdir -p scripts tmp
printf '%s\n' 'tmp/' 'session.local.host' >> .gitignore
python3 scripts/fence_check.py --card session.fence --root .
sha256sum package-lock.json | awk '{print $1}'

I store the receipt under tmp, and I commit nothing there. I delete the scratch host before I close the laptop.

A reject on missing teardown is a success, not a bug. A reject on a pem file is the whole point. Do not weaken the card to get a green line.

A table I use before I upload

Situation Free server probe Laptop only
Toy bug, synthetic input, teardown written Allow Also fine
Dotenv, pem, or a customer row nearby Deny Required
You need the run tomorrow, or an audit trail Deny Required
Lockfile hash on the host is unknown Deny Required
Step cap and kill command already written Allow Also fine

I allow a free server only in the probe column. I deny it for secrets, production data, and long jobs. If a row says deny, I stay on my laptop.

Why would you rent risk for a task you can run locally?

Toy test plan

Seed a toy tree with a fake dotenv file. Expect exit code 3, and expect the path printed.

Delete the teardown line and expect exit code 2. Set network to on and expect a hard reject.

Use a clean tree and expect a receipt under tmp. Confirm that receipt path is listed in gitignore.

I treat a failed check as a blocked session, not a warning. I do not continue in chat while the checker is red. Have you ever talked a red gate into silence?

Limits I will not hide

A glob deny list is not a full secret scanner. The checker cannot see steps inside a remote agent. It cannot prove the free server stayed up.

It cannot prove a vendor quota will remain tomorrow. Hash notes do not replace a real lockfile install. Dotfile matches depend on the Python you actually run.

This card does not replace diff review or test ownership. I assume a free server can vanish without notice. Keep a laptop copy, or you do not have a copy.

Who should skip this lane

Skip it if you need an audit letter for a customer. Skip it if the data is regulated or personal. Skip it if the only copy of the work lives remotely.

Skip it if you cannot delete the box the same day. A laptop-only repro is slower, and that is fine. I would rather be slow than stuck with a mystery host.

One next step

Read current MonkeyCode docs before you trust a free lane. Then run a fence card before the first prompt. I would rather you bounce off the checker than leak a key.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.