zscripts-token-savers/tests/Sanitization.Tests.ps1
Kelly Michels fcbad44e0e
feat(zdeploy): add the static deploy kind, and a test that keeps this repo sanitized (#55)
Two things, both prompted by the same incident.

STATIC KIND
Plain static sites - no build, no container of their own; a shared web
container serves them off disk, so shipping the files IS the deploy.
Directories are staged to a sibling and swapped in with mv rather than
copied in place, because a large media file uploaded in place is served
half-written to anyone who requests it mid-copy. The swap is a rename,
so the switch is atomic.

Skips $JunkDirNames + .github + deploy.skipDirs, matching the docker
kind rather than inventing a third convention.

SANITIZATION TEST
This repo is public and must stay standalone, but the toolkit is
developed in a private checkout and copied here. Twice in three days a
wholesale copy landed carrying real project names, internal hostnames,
host disk figures, and references to scripts that exist only in the
private copy. Both times a human reading the diff caught it - the
control that fails exactly when a diff is 280 lines of good work with
three bad words buried in it.

So it is a test now. Seven rules: private project names, private product
domains, private-only script names, local drive paths, the operator home
path, the real key filename, and any IPv4 outside RFC 5737 documentation
space and the private ranges.

Two anti-vacuity guards, because a denylist that silently matches
nothing is worse than no denylist: one asserts the file scan is
non-empty, and one plants a known violation and requires the pattern to
find it.

The IP rule bounds on [\d.] rather than \d deliberately - this toolkit's
own 5-segment version (v1.0.0.0.14) contains "0.0.0.14", which a plain
digit boundary reads as an address.

Verified against the real unsanitized copy that slipped through: 3 of
the 7 rules fire, naming file and line.

Tests: 231/231 (222 + 9 new). CHECKSUMS.txt refreshed.
2026-08-21 21:43:40 -05:00

118 lines
6.2 KiB
PowerShell

# Evomedia.net Token Savers — https://github.com/evomedia-net/evo.zscripts
# Created by Kelly Michels · dev@evomedia.net
# Licensed under the MIT License. See LICENSE.
# Sanitization.Tests.ps1 — this repo is public and must stay standalone.
#
# WHY THIS EXISTS
# ---------------
# The toolkit is developed in a private checkout and copied here. Twice in three
# days a wholesale copy landed carrying environment-specific detail: real
# project names, internal hostnames, host disk figures, and references to
# scripts that exist only in the private copy. Each time it was caught by a
# human reading the diff, which is exactly the control that fails when a diff is
# 280 lines of good work with three bad words buried in it.
#
# So it is a test. A copy that reintroduces private detail turns the suite red
# at the moment it happens rather than at review.
#
# WHAT IT CANNOT DO
# -----------------
# This is a denylist, so it proves the absence of KNOWN patterns, not the
# absence of secrets. It is a regression net for a specific recurring mistake -
# not a substitute for reading what you publish. Add a pattern whenever a new
# private identifier appears; the cost of a stale entry is zero.
BeforeAll {
$script:RepoRoot = Split-Path -Parent $PSScriptRoot
# Scanned: everything a reader of the published repo can see. Skipped:
# .git (history is out of scope here), releases/ (published zips are
# immutable by policy - rewriting one would break its checksum), and this
# file, which necessarily contains every pattern it looks for.
$script:Scanned = @(
Get-ChildItem -LiteralPath $script:RepoRoot -Recurse -File |
Where-Object {
$_.Extension -in @('.ps1', '.cmd', '.md', '.txt', '.json', '.bats', '.sh') -and
$_.FullName -notlike '*\.git\*' -and
$_.FullName -notlike '*\releases\*' -and
$_.Name -ne 'Sanitization.Tests.ps1'
}
)
# name -> what it is, so a failure explains itself
# pattern -> regex, case-insensitive
# Kept narrow on purpose: "evomedia.net" alone is legitimate here (the
# attribution header and the repo URL), so only the drive path and specific
# internal hosts are matched.
$script:Denied = @(
@{ Name = 'private project name'; Pattern = '\b(EvoCivilCode|EvoPlatform|DocketMail|SmartPlant\w*|ProvenSheet|evoehs|evoproven|evoaicc|evolocate|evoplatform)\b' }
@{ Name = 'private product domain'; Pattern = '\b(smartplantehs\.com|provensheet\.com|evoehs\.com|civilcode\.evomedia\.net|dashboard\.evomedia\.net|webmail\.evomedia\.net|mail-admin\.evomedia\.net|docketmail\.evomedia\.net|cardiff\.evomedia\.net|platform\.evomedia\.net|ai\.evomedia\.net|git\.evomedia\.net|analytics\.evomedia\.net)\b' }
@{ Name = 'private-only script'; Pattern = '\b(register_civilcode|register_docketmail|sp_seed_demo_prod|zpublish_stats|zcoverage|zmerge|zpull|zresume|swag_set_owner|provision_demo|apply_platform_config_fixes)\b' }
@{ Name = 'local drive path'; Pattern = '[A-Za-z]:\\\\?evomedia\.net' }
@{ Name = 'operator home path'; Pattern = '/home/ubuntu/' }
@{ Name = 'real pem key name'; Pattern = 'evomedia-prod\.pem' }
# RFC 5737 reserves 203.0.113.0/24 for documentation - that one is the
# correct placeholder and must stay allowed, as are loopback and the
# private ranges. Anything else that looks like a public IPv4 literal is
# suspect.
#
# The boundaries are [\d.] rather than \d on purpose: this toolkit's own
# 5-segment version (v1.0.0.0.14) contains "0.0.0.14", which a plain
# digit boundary happily reads as an address. Refusing a match that
# touches another dot rules out every version string without weakening
# detection of a real address, which is always delimited by whitespace
# or quotes.
@{ Name = 'non-documentation IP'; Pattern = '(?<![\d.])(?!203\.0\.113\.)(?!127\.0\.0\.1)(?!0\.0\.0\.0)(?!255\.)(?!10\.)(?!192\.168\.)(?!172\.(1[6-9]|2\d|3[01])\.)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?![\d.])' }
)
function Get-Hits {
param([string]$Pattern)
$hits = @()
foreach ($f in $script:Scanned) {
$m = Select-String -LiteralPath $f.FullName -Pattern $Pattern -AllMatches -ErrorAction SilentlyContinue
foreach ($line in $m) {
$rel = $line.Path.Substring($script:RepoRoot.Length).TrimStart('\')
$hits += "$rel`:$($line.LineNumber): $($line.Line.Trim())"
}
}
return $hits
}
}
Describe "public repo carries no private detail" {
It "finds files to scan at all" {
# Guards the guard: a bad filter here would make every test below pass
# vacuously, which is worse than no test.
$script:Scanned.Count | Should -BeGreaterThan 30
}
It "contains no <Name>" -ForEach @(
@{ Name = 'private project name' }
@{ Name = 'private product domain' }
@{ Name = 'private-only script' }
@{ Name = 'local drive path' }
@{ Name = 'operator home path' }
@{ Name = 'real pem key name' }
@{ Name = 'non-documentation IP' }
) {
$rule = $script:Denied | Where-Object { $_.Name -eq $Name }
$hits = Get-Hits -Pattern $rule.Pattern
$hits | Should -BeNullOrEmpty -Because "these look like private detail copied in from the internal toolkit:`n$($hits -join "`n")"
}
It "still detects a planted violation" {
# Mutation check. Without this the suite passes just as happily when the
# patterns are broken as when the repo is clean - the failure mode that
# makes a denylist worthless.
$probe = Join-Path ([IO.Path]::GetTempPath()) ("sanitize-probe-" + [guid]::NewGuid().ToString("N") + ".ps1")
try {
Set-Content -LiteralPath $probe -Value '# deploy target /home/ubuntu/stack/example' -Encoding UTF8
$found = Select-String -LiteralPath $probe -Pattern ($script:Denied | Where-Object { $_.Name -eq 'operator home path' }).Pattern
$found | Should -Not -BeNullOrEmpty
}
finally { Remove-Item -LiteralPath $probe -Force -ErrorAction SilentlyContinue }
}
}