DEV Community

Harper Xu
Harper Xu

Posted on

Never Unpack a Patch Onto the Tree

You should never let a remote generator write your tree. The working copy is a privileged control plane. You treat generation as cargo that arrives sealed.

This is an architecture review, not a product pitch. You will walk constraints, data flow, and failure domains. Then you will see what to change next.

The public argument about AI coding skill misses layout. Fluent generated text does not earn write access. A smooth patch can still poison a branch.

Planes you refuse to merge

You keep three planes apart on purpose. Local context packing stays on your own laptop. Remote generation stays on a separate draft host.

Local apply stays behind a human command. The generator never sees .git or .env. It never mounts your repo as a disk.

Think of a loading dock behind a locked warehouse. Trucks only drop crates at that dock. Those trucks never walk the stocked aisles.

Your working tree is that aisle space. A free remote model can still draft diffs. A free server can still host that draft.

Neither fact grants the server a git remote. Write access is not some courtesy upgrade. It is a failure domain you refuse to share.

Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode currently offers free model access for drafts. It also offers a free server option you can use.

You still run the git write path locally. Swap the vendor and the planes still hold. Remove every brand name and the rule survives.

Data flow, crate by crate

Start from a working tree you already trust. You pick files by allowlist, never by panic. You copy those files into a staging pack.

The packer strips secrets before any network hop. It drops lines that match key patterns. It drops files whose names look like credentials.

You send only that pack to the generator. The request is source, not a login shell. The response is files, not a merge commit.

Those returned files land in a quarantine directory. That directory sits outside every git worktree. git status in your repo must ignore that folder.

You inspect the crate before you open it. Path names must stay inside an allowlist. A throwaway clone must accept the dry run.

Only then do you copy a hunk into the tree. You still type the commit message yourself. You still run the test command yourself.

Here is a packer you can run locally. Treat this script as a labeled proposal. Review it before you point it at secrets.

#!/usr/bin/env python3
"""Local context packer. Proposal only. Review before use."""
from pathlib import Path
import io
import re
import tarfile

ALLOW = {".py", ".ts", ".go", ".rs", ".md"}
DENY_NAMES = {".env", "id_rsa", "credentials.json"}
SECRET = re.compile(r"(api[_-]?key|secret|BEGIN PRIVATE)", re.I)

def pack(root: Path, out: Path) -> None:
    root = root.resolve()
    buf = io.BytesIO()
    with tarfile.open(fileobj=buf, mode="w:gz") as tar:
        for path in root.rglob("*"):
            if not path.is_file():
                continue
            if path.suffix not in ALLOW:
                continue
            if path.name in DENY_NAMES:
                continue
            rel = path.relative_to(root)
            if any(part == ".git" for part in rel.parts):
                continue
            text = path.read_text(encoding="utf-8", errors="replace")
            if SECRET.search(text):
                continue
            data = text.encode("utf-8")
            info = tarfile.TarInfo(rel.as_posix())
            info.size = len(data)
            tar.addfile(info, io.BytesIO(data))
    out.write_bytes(buf.getvalue())

if __name__ == "__main__":
    pack(Path("."), Path("/tmp/context-pack.tgz"))
    print("wrote /tmp/context-pack.tgz")
Enter fullscreen mode Exit fullscreen mode

You run it from a clean clone, not production. You list the tarball before any upload. Surprise paths mean you stop the hop.

python3 pack_context.py
tar -tzf /tmp/context-pack.tgz
Enter fullscreen mode Exit fullscreen mode

The generator returns a second archive of files. You never extract that archive onto the repo. You extract it under /tmp/quarantine for that run.

RUN_ID=$(date +%s)
mkdir -p "/tmp/quarantine/$RUN_ID"
tar -tzf /tmp/generated.tgz
tar -xzf /tmp/generated.tgz -C "/tmp/quarantine/$RUN_ID"
Enter fullscreen mode Exit fullscreen mode

Record the parent hash beside the run identifier. You will need that hash when crates come back. A missing hash means you discard the crate.

mkdir -p "/tmp/quarantine/$RUN_ID"
sha256sum /tmp/context-pack.tgz > "/tmp/quarantine/$RUN_ID.parent-hash"
cat "/tmp/quarantine/$RUN_ID.parent-hash"
Enter fullscreen mode Exit fullscreen mode

Then a local checker decides if the crate may move. This checker reviews paths, not the model's tone. Model tone is not a security control here.

#!/usr/bin/env python3
"""Quarantine a generated tree, then dry-run it on a clone."""
from pathlib import Path
import shutil
import subprocess
import sys

ALLOWED_PREFIXES = ("src/", "tests/", "docs/")
FORBIDDEN_PARTS = {".git", ".ssh", ".github", "node_modules"}

def normalize(rel: Path) -> str:
    return rel.as_posix().lstrip("./")

def path_allowed(rel: Path) -> bool:
    text = normalize(rel)
    if rel.is_absolute() or ".." in rel.parts:
        return False
    if any(part in FORBIDDEN_PARTS for part in rel.parts):
        return False
    return any(text.startswith(prefix) for prefix in ALLOWED_PREFIXES)

def check_quarantine(qdir: Path) -> list[str]:
    rejected: list[str] = []
    files = [p for p in qdir.rglob("*") if p.is_file()]
    if not files:
        return ["<empty>"]
    for path in files:
        rel = path.relative_to(qdir)
        if not path_allowed(rel):
            rejected.append(normalize(rel))
    return rejected

def dry_run(qdir: Path, repo: Path) -> int:
    work = Path("/tmp/apply-check")
    if work.exists():
        shutil.rmtree(work)
    subprocess.run(
        ["git", "clone", "--local", str(repo), str(work)],
        check=True,
    )
    for path in qdir.rglob("*"):
        if not path.is_file():
            continue
        rel = path.relative_to(qdir)
        dest = work / rel
        dest.parent.mkdir(parents=True, exist_ok=True)
        dest.write_bytes(path.read_bytes())
    diff = subprocess.run(
        ["git", "-C", str(work), "diff", "--stat"],
        text=True,
        capture_output=True,
        check=True,
    )
    print(diff.stdout or "no diff")
    conflict = subprocess.run(
        ["git", "-C", str(work), "diff", "--check"]
    )
    return conflict.returncode

def main() -> int:
    qdir = Path(sys.argv[1]).resolve()
    repo = Path(sys.argv[2]).resolve()
    rejected = check_quarantine(qdir)
    if rejected:
        print("rejected paths:")
        print("\n".join(rejected))
        return 1
    return dry_run(qdir, repo)

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

Run the checker against the repo you actually trust. A nonzero exit means the crate is trash. You delete it before anyone gets curious.

python3 quarantine_check.py "/tmp/quarantine/$RUN_ID" /path/to/repo
Enter fullscreen mode Exit fullscreen mode

You should confirm parent segments cannot sneak into names. Python will show .. inside the path parts. Your checker must treat that as a hard reject.

python3 - <<'PY'
from pathlib import Path
rel = Path("src/../.git/hooks/pre-commit")
print(rel.parts)
print(".." in rel.parts)
PY
Enter fullscreen mode Exit fullscreen mode

If the checker exits nonzero, you delete the crate. You do not just peek with a recursive copy. Peeking is how path traversal becomes a commit.

Failure domains on this layout

The first failure domain is the outbound prompt. A useful file can still hide a token. Your packer is a filter, not a proof.

If the filter misses, the server learns the secret. You rotate that secret as if it leaked. You do not argue with the model about intent.

The second failure domain is the inbound crate. A patch can rewrite workflows you never asked for. It can plant a path that walks toward .git.

Quarantine exists because file names often lie. That src/../.git/hooks/pre-commit path is not a source file. Your checker must reject parent segments every time.

The third failure domain is availability of the draft host. The free server can stall, rate-limit, or vanish. Your ship date cannot live on that host.

That is why apply stays local and boring. You can still edit by hand when drafts die. Remote generation is an optimization, not a runtime.

The fourth failure domain is false confidence after a dry run. git diff --check does not run your tests. A clean apply can still ship a logic bomb.

You keep tests on the same machine that commits. The remote host never receives your test credentials. It never receives the production config files either.

A useful analogy here is airport cargo screening. The plane does not taxi into your warehouse. Bags move through a separate hall first.

You are not being precious about tools. You are drawing a trust boundary on disk. Disk is the place where branches actually change.

What you should change next

Today the allowlist is a tuple of prefixes. That is too coarse for a large monorepo. Next you should map each task to a path budget.

A path budget is a short signed list of globs. The generator may touch only those globs. Anything else is a rejected crate, full stop.

You should also hash every file that leaves. Store the hash beside the run identifier. When the crate returns, you match the conversation.

Today the dry run still clones with --local. That is fast, and it is also leaky. A local clone can still see your remotes.

Next you should clone from a bare mirror instead. The mirror has no working secrets and no hooks. Repo hooks are another quiet write path.

You should refuse generated workflow files by default. CI YAML is a remote code execution surface. Treat it like a new production binary.

None of this requires a smarter model. It requires a smaller blast radius on disk. Radius is an architecture choice you already own.

Who should not use this layout

Do not use this layout for incident hotfixes. A quarantine bus adds minutes you may not have. Type the fix and skip the draft host.

Do not use it if you cannot list allowlists. A wide open src/ in a huge repo is weak. Split the tree before you invite a generator.

Do not auto-apply crates from a cron job. The whole point is a human on the write path. Automation here recreates the problem you just drew.

Do not send customer data in the pack. Free servers are still not your compliance boundary. If the file is sensitive, it stays offline.

Limits you should not paper over

The scripts above are proposals, not audited tools. They will miss encodings, symlinks, and sparse checkouts. They will miss secrets that look like prose.

They also do not measure model quality at all. This review is about failure domains, not tokens. Token counts would belong in a different article.

You still need a human who can read the diff. Architecture cannot replace that last pair of eyes. It can only keep the eyes off a burning branch.

You keep the generator on the loading dock. You keep the warehouse keys in your pocket.

Top comments (0)