jobbsok/tests/test_public_surface.py

203 lines
8.3 KiB
Python

"""What this repository publishes, and what it must never publish (plan Step 14).
The remote is PUBLIC. That single fact decides both halves of this file.
The first half is presentation: a README first screen that says what the plugin
is, an install block that covers both surfaces it actually runs on -- Claude
Code and Claude Cowork, which install it in completely different ways -- and a
`SECURITY.md` that states the trust model instead of implying it.
The second half is the one that matters more, and it is deliberately wider than
the two files this step writes. The scan reads **every tracked file**, because
the leak this repository is exposed to does not live in the README. It lives in
`docs/cowork-probe.md`, which is committed here and is appended to by six later
steps with measured facts from the operator's own machine and browser (risk
H1). A two-file scan would never look at it. The first run of this scan found
a real job-ad identifier there, published, which is the whole argument for
scanning wide compressed into one finding.
Three deliberate scopings, stated rather than smuggled, because a rule that
cannot be stated is a rule that will be quietly widened later:
1. **An absolute home path means a literal one.** `/Users/<name>/` and
`/home/<name>/` are refused; `$HOME/Library/...` is not, because that is the
correct way to write the same path and `scripts/cowork_probe_check.sh` has
to name a macOS directory to read a log out of it.
2. **Infrastructure hosts are allowed by name.** The Forgejo host this
repository lives on and the badge service in the README are addresses the
published repository cannot function without. Every other host in a URL that
points at a resource must be under `.example` (RFC 2606).
3. **`tests/test_fixture_hygiene.py` is exempt from the URL rule**, and only
from that one. It seeds a synthetic job-board URL as a canary to prove its
own scan can find, and a scanner cannot scan another scanner's canary
without one of the two being wrong.
The scan reports its denominator on every run, and proves it can find before it
reports that it found nothing. "Found nothing" is a measurement; without a
denominator and a positive control it is indistinguishable from a scan whose
patterns were wrong.
Style note: this file follows tests/test_fixture_hygiene.py.
"""
import os
import re
import subprocess
import warnings
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
README = os.path.join(REPO, "README.md")
SECURITY = os.path.join(REPO, "SECURITY.md")
class OffentligFlateSkannet(UserWarning):
"""Carries the denominator into the run's output, where a reader sees it."""
#: A literal home directory with a user name in it. Written as a pattern rather
#: than a literal so this file does not match itself.
HJEMMESTI = re.compile(r"/(?:Users|home)/[A-Za-z0-9._-]+/")
#: A URL pointing at a resource -- with a path, so a bare domain named in prose
#: is not a finding. Both spellings the corpus uses are covered.
ADRESSE = re.compile(r"(?:https?://|\bwww\.)([A-Za-z0-9.-]+\.[A-Za-z]{2,})(/[^\s`'\")\]]*)")
#: Hosts the published repository cannot function without.
INFRASTRUKTUR = ("git.fromaitochitta.com", "img.shields.io")
#: Exempt from the URL rule only, and for the reason in this module's docstring.
URL_UNNTAK = ("tests/test_fixture_hygiene.py",)
#: The invariants that are live at M1. M3 and M6 extend the list as their
#: surfaces land; these four are true from the first commit.
IKKE_FORHANDLINGSBART = (
"read-only",
"credential",
"local",
"guard",
)
def sporede_filer():
ut = subprocess.run(
["git", "-C", REPO, "ls-files"], capture_output=True, text=True
)
assert ut.returncode == 0, ut.stderr
return [p for p in ut.stdout.split("\n") if p]
def skann(rot, filer):
"""Return (findings, files read, bytes read) for ``filer`` under ``rot``."""
funn = []
lest = 0
bytes_lest = 0
for relativ in filer:
full = os.path.join(rot, relativ)
if not os.path.isfile(full):
continue
try:
with open(full, "r", encoding="utf-8") as handle:
tekst = handle.read()
except UnicodeDecodeError:
continue
lest += 1
bytes_lest += len(tekst.encode("utf-8"))
for treff in HJEMMESTI.finditer(tekst):
funn.append((relativ, "absolutt hjemmesti", treff.group(0)))
if relativ in URL_UNNTAK:
continue
for vert, _sti in ADRESSE.findall(tekst):
if vert in INFRASTRUKTUR:
continue
if vert.endswith(".example"):
continue
funn.append((relativ, "adresse utenfor .example", vert))
return funn, lest, bytes_lest
def test_the_readme_install_block_covers_both_surfaces():
with open(README, "r", encoding="utf-8") as handle:
tekst = handle.read()
assert "## Install" in tekst, "the README has no install section"
installer = tekst.split("## Install", 1)[1]
installer = installer.split("\n## ", 1)[0]
assert "Claude Code" in installer, (
"the install block does not name Claude Code, the development surface"
)
assert "Cowork" in installer, (
"the install block does not name Cowork, which installs by uploading an "
"archive and not by a marketplace command -- a reader following the "
"Claude Code lines there gets nowhere"
)
assert "scripts/package_plugin.sh" in installer, (
"the Cowork route needs the packaging command; never a recursive zip "
"of the repository root"
)
assert "scripts/bootstrap.sh" in installer, (
"without the bootstrap there is no environment for the host MCP server "
"to run on in an installed copy"
)
# The honesty clause: the guard is installed from a self-hosted host a
# third party may not reach. A public install block that fails at its first
# step for everyone but its author has to say so.
assert "git.fromaitochitta.com" in tekst
assert "## Non-goals" in tekst, "the README states no non-goals"
def test_security_md_states_the_trust_model():
assert os.path.isfile(SECURITY), "SECURITY.md does not exist"
with open(SECURITY, "r", encoding="utf-8") as handle:
tekst = handle.read()
lav = tekst.lower()
for begrep in IKKE_FORHANDLINGSBART:
assert begrep in lav, (
"SECURITY.md never mentions %r; it is one of the four invariants "
"that are live at M1" % begrep
)
# The trust boundary is the write, not the read. Saying only "we use a
# guard" leaves the reader guessing where it sits.
assert "guard_ingest.py" in tekst
assert "M3" in tekst or "M6" in tekst, (
"SECURITY.md must say which surfaces are not live yet, or it reads as "
"a description of a finished system"
)
def test_no_tracked_file_carries_a_home_path_or_a_real_address(tmp_path):
# Prove the scan can find before believing that it found nothing.
#
# The canary is assembled from parts rather than written out, and that is
# not fussiness: this file is tracked, so the scan reads it too. A literal
# canary here would be a finding in the scanner's own source -- which is
# exactly what the M1 gate caught the first time this file was committed.
# Building it at runtime keeps the scanner subject to its own rule instead
# of exempting itself from it.
kanari = tmp_path / "kanari.md"
kanari.write_text(
"Se /%s/enperson/hemmelig/mappe/ og https://ekte-%s.no/annonse/12345\n"
% ("Users", "jobbportal"),
encoding="utf-8",
)
kanarifunn, kanarilest, _ = skann(str(tmp_path), ["kanari.md"])
assert kanarilest == 1
assert len(kanarifunn) == 2, (
"the scan cannot find what it exists to find; it reported %r"
% (kanarifunn,)
)
filer = sporede_filer()
funn, lest, bytes_lest = skann(REPO, filer)
warnings.warn(
"public surface scan: %d tracked files, %d bytes read; %d finding(s)"
% (lest, bytes_lest, len(funn)),
OffentligFlateSkannet,
)
assert lest > 0, "the scan read no files; git ls-files returned %d paths" % len(filer)
assert funn == [], (
"tracked files carry paths or addresses that must not be published:\n%s"
% "\n".join(" %s: %s -- %s" % rad for rad in funn)
)