DEV Community

Riley Lin
Riley Lin

Posted on

Gate the Context Pack Before It Leaves

You should refuse a hosted model call until a local gate marks the context pack clean. A free server is still a machine you do not administer, so the redaction has to finish on your laptop. The pack is the real transfer, not the short question you type into the box at the end. If the pack holds a secret, a customer trace, or an internal hostname, the question is already too late.

Think of the pack as a courier envelope that an assistant stuffs before you read the label. Your laptop is the first zone, the hosted server is the second zone, and the model path is the third. You can trust zone one with the raw files, because those files already live on a disk you control. You should assume that zone two and zone three keep copies you cannot delete on demand.

A modern coding assistant does not send only the sentence you wrote in the chat box. It often attaches the git diff, the open buffers, a slice of terminal scrollback, and a file tree summary. Each attachment looks harmless alone, yet together they reconstruct a private change that you never meant to publish. That reconstruction is why a tidy chat line can still leak a change you would not put in a public gist.

October pulls many of you into unfamiliar repositories, sample apps, and half-finished branches that you did not write. The temptation is to hand the whole tree to a hosted model and ask for a review before you understand the history. That habit fails when the tree holds a forgotten env file, a vendor token, or a comment that names an internal host. You need a local gate that runs before the assistant is allowed to leave zone one with that tree.

The workflow is small enough to run in a spare terminal before you open the assistant. You export a context pack with commands you already trust, then you scan that directory with a local script. You may send the directory only when the script exits clean, and you scan again after every edit. You do not negotiate with the gate by pasting a flagged span into chat to ask what it means.

Start from a temporary directory that you can delete without touching the working tree of the repository. The commands below copy the diff, a name-only status list, and a short log into that directory. They skip your shell history, your SSH agent, and a full environment dump, because those sources are too wide for a first gate. You may add a file later, but you add it by name rather than with a wildcard over your home directory.

mkdir -p /tmp/context-pack
git diff -- . ':!*.env' ':!.env*' > /tmp/context-pack/diff.patch
git status --short > /tmp/context-pack/status.txt
git log -5 --pretty=format:'%h %s' > /tmp/context-pack/recent-commits.txt
printf '%s\n' 'Review this public refactor only.' > /tmp/context-pack/ask.txt
Enter fullscreen mode Exit fullscreen mode

Treat the export lines as a starting recipe, and adjust the pathspec if your git build rejects the exclusion syntax. The important constraint is the same on every version: you choose files by name, and you never dump the environment into the pack. If a command fails, you stop and read the error locally instead of pasting the error into a hosted chat. A failed export is still zone one, and it should stay there until you understand what the tool printed.

The scanner is a proposal you can run locally, not a certified data-loss product and not a result from a hosted lab. It walks the pack, applies a short set of patterns, and writes a JSON decision next to an exit code your shell can read. A match blocks the send even when the match sits inside a comment, a fixture, or a commit subject. Silence is not a proof of safety, because a regex cannot see a secret that uses a format you forgot to describe.

#!/usr/bin/env python3
"""Local gate for a context pack. Example only; extend the patterns for your org."""

import json
import re
import sys
from pathlib import Path

PATTERNS = {
    "aws_access_key": re.compile(r"AKIA[0-9A-Z]{16}"),
    "github_token": re.compile(r"gh[pousr]_[A-Za-z0-9]{20,}"),
    "slack_token": re.compile(r"xox[baprs]-[A-Za-z0-9-]{10,}"),
    "private_key": re.compile(r"-----BEGIN (?:RSA |OPENSSH |EC )?PRIVATE KEY-----"),
    "bearer": re.compile(r"(?i)authorization:\s*bearer\s+[A-Za-z0-9._\-]{8,}"),
    "conn_string": re.compile(r"(?i)(?:postgres|mysql|mongodb|redis)://\S+:\S+@"),
    "internal_host": re.compile(r"\b(?:[a-z0-9-]+\.)+(?:internal|local|corp)\b"),
}

def scan(pack: Path) -> dict:
    findings = []
    files = sorted(path for path in pack.rglob("*") if path.is_file())
    for path in files:
        text = path.read_text(encoding="utf-8", errors="replace")
        for name, pattern in PATTERNS.items():
            for match in pattern.finditer(text):
                line = text.count("\n", 0, match.start()) + 1
                findings.append(
                    {
                        "rule": name,
                        "file": str(path.relative_to(pack)),
                        "line": line,
                    }
                )
    decision = "block" if findings else "allow"
    return {
        "decision": decision,
        "finding_count": len(findings),
        "findings": findings,
    }

def main() -> int:
    if len(sys.argv) != 2:
        print("usage: gate_context_pack.py PACK_DIR", file=sys.stderr)
        return 1
    pack = Path(sys.argv[1])
    if not pack.is_dir():
        print("pack directory not found", file=sys.stderr)
        return 1
    report = scan(pack)
    print(json.dumps(report, indent=2))
    return 2 if report["decision"] == "block" else 0

if __name__ == "__main__":
    raise SystemExit(main())
Enter fullscreen mode Exit fullscreen mode

Save the script as gate_context_pack.py and point it at the temporary pack before you copy anything toward a server. A clean pack exits zero and prints an allow decision, which is the only result that may leave the laptop. You can prove the block path with a throwaway file that contains a published documentation key prefix, then delete that file and scan again. Keep the fake value obvious, and never seed the test with a token that has ever been real.

python3 gate_context_pack.py /tmp/context-pack
echo $?

# AKIAIOSFODNN7EXAMPLE is a widely published AWS documentation sample, not a live key.
printf '%s\n' 'aws_key=AKIAIOSFODNN7EXAMPLE' > /tmp/context-pack/bad-fixture.txt
python3 gate_context_pack.py /tmp/context-pack
echo $?

rm /tmp/context-pack/bad-fixture.txt
python3 gate_context_pack.py /tmp/context-pack
Enter fullscreen mode Exit fullscreen mode

Read the JSON as a boarding pass rather than as a score you can talk your way past. An allow decision means the listed patterns did not fire, not that the pack is wise to share. A block decision names the file and the line, and it omits the matched text on purpose. You fix the source, you rebuild the pack, and you accept a fresh allow before any network call.

You can rehearse the whole path on a throwaway git repository that contains only a README and a one-line change. Create it under /tmp, run the export commands, and confirm an allow before you add the documentation-sample key. Remove the bad file, confirm that the allow returns, and only then consider a hosted call on that toy tree. That loop is the artifact, and you can repeat it on any branch without sending the branch anywhere first.

Once the gate allows the pack, you still choose where the bytes go, and that choice is the rest of the trust model. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is an open source coding assistant, and its stated offer includes a ten million token allowance on free model access. The same offer includes a free server option, which is still zone two unless you run the project on hardware you administer.

Use the free server when the pack came from a public repository, a tutorial you authored, or a redacted diff the gate allowed twice. Use a self-hosted checkout when the work names customers, unreleased fixes, or hostnames you would not put in a public issue. The free token allowance can cover a long review of a clean pack, but an allowance is not a privacy control. Confirm the current terms before you plan a week around them, because offers change and this article does not measure quota live.

Do not send environment dumps, shell history, crash dumps, customer tickets, or raw terminal scrollback even after you trim them. Do not send a file because its name looks boring, since status files and commit subjects often carry internal URLs. Do not send the gate report from a blocked run, because the path and the line can lead you back to the secret. Do not send someone else's private repository just because a hosted model feels convenient during a contribution sprint.

This approach fails closed only for the patterns you wrote down, and it fails open for everything else. A renamed token, a split secret, a business rule in plain prose, or an unpatched flaw will all pass a happy regex. The script does not encrypt the pack, it does not wipe editor swap files, and it does not stop another plugin channel. You should treat a plugin that auto-attaches open files as outside this gate until you disable that behavior.

You should not use this workflow for incident response, regulated records, payment data, or a repository under legal hold. You should not use the free server for code your employer has marked internal, even when the scanner stays quiet. A student on a public sample can adopt the gate as a habit, while a regulated team needs a control its counsel already accepts. If you cannot explain where zone two stores prompts, you are not ready to send the pack at all.

If your pack is already clean, the stated free server is enough to rehearse a public review without standing up hardware first. Stop if the terms you see no longer match this article, and do not treat a token allowance as a retention promise. The useful outcome is a reflex that checks the envelope before the courier leaves, not a longer chat with a model. Leave the private tree on the laptop until you can run the open source stack inside a zone you administer.

Top comments (0)