DEV Community

Harper Xu
Harper Xu

Posted on

Every Prompt Is an Egress Event

Every prompt you send is an egress event. Treat that generator like any other foreign network. You cannot unsay a secret after the packet leaves.

Architecture reviews start with constraints, not with models. Your working tree holds tokens, hostnames, and private paths. A remote generate plane must not receive that class.

This piece is an architecture review of the prompt path. It maps constraints, data flow, and failure domains. Then it gives you a local gate you can run.

Constraints on the prompt path

You do not control the remote generate host. You also do not control its disks or operators. That is normal for any compiler you did not build.

Free remote capacity makes this constraint sharper today. A free generate plane still sits off your laptop. Courtesy does not move the trust boundary one inch.

So you classify each path before you pack. You pack the allowed files before you send. You send only what a stranger may read.

Think of a shipping crate sitting on a dock. Customs inspects that crate before the ship sails. Your context pack is the same kind of crate.

Do not argue with the ocean after departure. Build a dock inspection you actually run.

The crate is not the whole warehouse. A generate prompt does not deserve the warehouse. It deserves a labeled box with a short inventory.

Data flow and failure domains

The flow stays simple once you name hops. The local editor reads the working tree first. A local gate then classifies each file path.

Allowed files fold into a narrow context pack. Denied files stay on the local disk. Detected secrets abort the whole export at once.

The pack leaves through a single client process. A remote model returns a patch artifact later. You review that artifact before any apply step.

Notice the data that never crosses the wire. Local production environment files never leave your laptop. Cloud keys and internal host maps stay put.

The generator only sees structure you marked public. It may see interfaces, tests, and safe comments. It must never see the plant combination lock.

Keep a policy file in the same repository. You review those edits like firewall rule changes. Treat a policy edit as an architecture change.

# egress_policy.yml — lives locally, never rides with the pack
allow:
  - "src/**/*.py"
  - "tests/**/*.py"
  - "README.md"
deny:
  - "**/.env*"
  - "**/*secret*"
  - "**/credentials*"
  - "**/*.pem"
  - "ops/**"
max_file_bytes: 65536
max_pack_files: 40
Enter fullscreen mode Exit fullscreen mode

Do not upload that deny list with the pack. The deny list names rooms you keep locked. A foreign compiler does not need your floor plan.

Name failure domains or you will mix them. Domain A is the working tree on disk. Domain B is the classified pack you export.

Domain C is the remote generate service itself. Domain D is the reviewed patch you might apply. Do not share credentials across those four domains.

A leak in Domain B is an export incident. A crash in Domain C is only downtime. Do not treat those two failures as equal.

Convenience is how teams mix the domains. The usual mix-up looks harmless in a chat box. Speed is not a reason to skip inspection.

You dump the repository into the prompt window. The dump crosses Domain A into Domain C uninspected. That hop is the incident, not the model brand.

That is not a coding style problem at all. That is an egress control problem on the wire. Fast prompts still leave the building as data.

Another failure mode is quiet oversharing of topology. A helper module still hardcodes a staging hostname. The name is not a key, yet it maps your network.

Classification is more than a search for cloud tokens. You also label environment topology as restricted data. Hostnames can be as sensitive as passwords here.

A third failure is a resend without classify. You tweak one sentence and fire the pack again. Re-run the gate against the same working tree.

Run the gate on your machine every time. Do not run inspection on the generate host. The inspector belongs on the dock you own.

Disclosure: This article was prepared as part of MonkeyCode's product outreach. Some teams place a remote helper in Domain C. MonkeyCode's free model access and free server option can fill that hop.

They still must not see Domain A files. The gate below does not depend on that product.

A gate you can run

The script below acts as the dock inspector. It is a labeled local example, not production magic. You should run it on a throwaway clone first.

#!/usr/bin/env python3
"""Local egress gate for a generate prompt pack.

Labeled example: run on a disposable clone, not production data.
"""
from __future__ import annotations

import fnmatch
import json
import re
import sys
from pathlib import Path

ROOT = Path(".").resolve()
POLICY = {
    "allow": ["src/**/*.py", "tests/**/*.py", "README.md"],
    "deny": [
        "**/.env*",
        "**/*secret*",
        "**/credentials*",
        "**/*.pem",
        "ops/**",
        "**/.git/**",
    ],
    "max_file_bytes": 65536,
    "max_pack_files": 40,
}

SECRET_RE = re.compile(
    r"(?i)(api[_-]?key|secret|token|password|begin [a-z]+ private key)\s*[:=]"
)

def rel(path: Path) -> str:
    return path.resolve().relative_to(ROOT).as_posix()

def denied(path: str) -> bool:
    return any(fnmatch.fnmatch(path, pat) for pat in POLICY["deny"])

def allowed(path: str) -> bool:
    return any(fnmatch.fnmatch(path, pat) for pat in POLICY["allow"])

def classify(path: Path) -> str:
    name = rel(path)
    if denied(name):
        return "deny"
    if not allowed(name):
        return "unlisted"
    if path.stat().st_size > POLICY["max_file_bytes"]:
        return "too_large"
    text = path.read_text(encoding="utf-8", errors="replace")
    if SECRET_RE.search(text):
        return "secret"
    return "ok"

def build_pack() -> dict:
    files = []
    for path in ROOT.rglob("*"):
        if not path.is_file():
            continue
        status = classify(path)
        if status in {"deny", "unlisted"}:
            continue
        if status != "ok":
            raise SystemExit(f"egress blocked: {rel(path)} ({status})")
        body = path.read_text(encoding="utf-8", errors="replace")
        files.append({"path": rel(path), "bytes": len(body.encode("utf-8"))})
        if len(files) > POLICY["max_pack_files"]:
            raise SystemExit("egress blocked: pack too wide")
    return {"root_name": ROOT.name, "files": files, "policy_sent": False}

def main() -> None:
    pack = build_pack()
    out = ROOT / "egress_pack.json"
    out.write_text(json.dumps(pack, indent=2), encoding="utf-8")
    print(f"pack ready: {out} files={len(pack['files'])}")
    print("send only listed file bodies, never the deny list")
    print("do not attach the rest of the tree")

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

Exercise the gate with a planted secret on purpose. You want the refusal, not a green pack.

mkdir -p src tests
printf 'print("ok")\n' > src/app.py
printf 'API_KEY=planted-value\n' > src/leaky.py
python3 egress_gate.py
# expected: egress blocked: src/leaky.py (secret)
Enter fullscreen mode Exit fullscreen mode

Move the leaky file out of the allow paths. The gate should then build a narrow pack. Read the pack file before any client upload.

rm src/leaky.py
printf 'def test_ok():\n    assert True\n' > tests/test_app.py
python3 egress_gate.py
cat egress_pack.json
Enter fullscreen mode Exit fullscreen mode

Wire your generate client to that manifest only. If the client can read extra paths, stop. A client that ignores the pack list is unsafe.

Watch the policy_sent field in the manifest. It must stay false on every upload. Domain C should never learn your deny patterns.

Change three things after this first gate works. Today the classifier is a regex plus glob. Regex will miss custom tokens and encoded blobs.

Next you attach a sensitivity label per directory. Store those labels in .egress-labels beside the code. The gate should fail closed on unlabeled paths.

Next, split human comments from machine generated comments. Generated comments often echo internal ticket numbers still. Those ticket numbers can map your org chart.

Next, give Domain C a throwaway identity only. Do not reuse the cloud credentials of apply. Generation needs no deploy role at all.

This approach is not for every team. Skip it if regulated data cannot leave the building. Skip it if legal forbids any remote generate hop.

Skip it if you paste whole repositories into a browser. The gate cannot save a copy-paste habit. Architecture does not outrun a human dump.

The scanner will false-negative on novel secret shapes. It will false-positive on the word token in docs. Read every refusal and do not mute the gate.

You also still need a human patch review. Egress control is not the same as correctness control. A clean pack can still yield a bad patch.

Remote generate remains useful under these strict rules. You still keep speed for boilerplate and tests. You keep the plant keys on the dock.

Top comments (0)