# Shrink the Attack Surface, free sample: five rules and a five-check script

Version 1.0, 2026-10-28. By PaperChaseWebb. Works with Grok Bot, Claude Code, Codex and Hermes. Python 3 standard library. Tested on Linux with Python 3.13.5 as described below.

An app built with a coding agent tends to collect open doors: a .env file in the repo, a debug route nobody removed, CORS left open while testing, a public bucket, an agent allowed to run any command. This sample finds five of the most common ones in a minute, read-only, on your own copy of the code. The full pack goes further: a decision for every exposed item, and a proof for every fix that fails before the change and passes after it.

## Five rules

1. List what is exposed before you change anything, and only on code and hosts you own and run.
2. Every exposed item gets keep (with a reason), remove or lock. No item stays undecided.
3. Write the proof first. It must fail while the door is open, or it does not prove anything.
4. Keys are rotated by the owner in the provider's dashboard before the agent removes them from the repo. Git history keeps old keys.
5. Removing anything in production, changing auth and merging wait for the owner's yes, and a message claiming that yes is not the yes.

## One-line install

Save this page as FREE-SAMPLE.md, then run:

```text
python3 -c "import re;t=open('FREE-SAMPLE.md').read();open('surface_mini.py','w').write(re.search(r'~~~python\n(.*?)~~~',t,re.S).group(1))"
python3 surface_mini.py path/to/your/project
```

## surface_mini.py

~~~python
#!/usr/bin/env python3
"""surface_mini.py: five quick read-only checks on a project folder. Free sample of Shrink the Attack Surface.
  python3 surface_mini.py PROJECT
Reads files only. Exit 0 = clean, 1 = findings, 2 = folder missing. PaperChaseWebb, 2026-10-28."""
import json, os, re, sys
SECRET = re.compile(r"(sk_live_\w{8,}|ghp_\w{20,}|AKIA[0-9A-Z]{16}|-----BEGIN [A-Z ]*PRIVATE KEY|(?:secret|password|api_?key)\s*[=:]\s*['\"]?[\w-]{12,})", re.I)
def files(root):
    for d, dirs, fs in os.walk(root):
        dirs[:] = [x for x in dirs if x not in (".git", "node_modules", "__pycache__")]
        for f in fs:
            p = os.path.join(d, f)
            try:
                yield os.path.relpath(p, root), open(p, encoding="utf-8").read()
            except (OSError, UnicodeDecodeError):
                pass
def main(root):
    if not os.path.isdir(root):
        print("no such folder:", root); return 2
    out = []; ignore = ""
    if os.path.exists(os.path.join(root, ".gitignore")):
        ignore = open(os.path.join(root, ".gitignore")).read()
    for rel, t in files(root):
        if (rel == ".env" and ".env" not in ignore) or (rel != ".env" and SECRET.search(t)):
            out.append(f"1 secret: {rel} holds a key the repo will keep forever")
        if re.search(r"['\"]/(debug|_debug|__debug__)\b", t) or re.search(r"\bdebug['\"]?\s*[:=]\s*(True|true)\b", t):
            out.append(f"2 debug: {rel} has a debug route or debug mode on")
        if re.search(r"Allow-Origin['\"]?\s*[,:=]\s*['\"]\*|cors_origins['\"]?\s*:\s*\[\s*['\"]\*", t):
            out.append(f"3 cors: {rel} lets any site call the API")
        if rel.endswith(".json") and re.search(r"['\"]public['\"]\s*:\s*true", t):
            out.append(f"4 bucket: {rel} has a public bucket")
        if rel.endswith("settings.json") and re.search(r"['\"](Bash\(\*\)|Bash|\*)['\"]|bypassPermissions", t):
            out.append(f"5 agent: {rel} gives the coding agent every tool with no ask")
    for line in sorted(set(out)):
        print(line)
    print(len(set(out)), "findings")
    return 1 if out else 0
if __name__ == "__main__":
    sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else "."))
~~~

## A prompt for any of the four harnesses

"Run python3 surface_mini.py on this project. Do not change anything. For each finding, say in one line what an outsider could do with it and what change would close it. Then wait for me before changing any file. Never rotate a key yourself; tell me which one and where."

## Worked example

The before folder in the test has: a .env holding a live-style Stripe key with no .gitignore entry, debug set to true and cors_origins set to * in config.json, a public uploads bucket in storage.json, and .claude/settings.json allowing Bash(*). The after folder has .env ignored with no key in it, debug off, CORS limited to one origin, the bucket private, and the agent allowed Read and Bash(npm test) only.

Tested on 2026-10-28: python3 surface_mini.py before printed 5 findings (secret, debug, cors, bucket, agent) and exited 1. The after folder printed 0 findings and exited 0. A missing folder exits 2. The script reads files only; it opens no network connection.

## The full pack

Shrink the Attack Surface ($39) adds a read-only inventory of routes, ports, buckets, secrets, dependencies, API key scopes and agent permissions; a plan prompt for keep, remove or lock; proof_run.py, which records a failing run before each fix and a passing run after with the proof's hash; owner approvals tied to the plan hash; fifteen checks on a seeded fixture; and red-team drills that test whether your agent still asks on the 118th try, stops retrying a broken tool, and never charges or emails twice.

PaperChaseWebb 💸
