← back to Cli Printing Press
fix(cli): exempt CLI-root vendor spec files from PII audit scope (#1121)
6536cfffc1a796604286df2c8fc67d8ff113e1ae · 2026-05-11 16:42:26 -0700 · Trevin Chow
* fix(cli): exempt CLI-root vendor spec files from PII audit scope
Vendor-published OpenAPI specs ship `example:` blocks with synthetic
emails, phones, and addresses (Stripe `jenny@example.com`, Zendesk
`roge@example.org`, GitHub user schemas). The PII audit treated these
as customer PII, so promote/publish hard-failed on every
OpenAPI-sourced CLI: Zendesk hits 229 findings against `spec.yaml`
alone, and the polish ruleset forbids the bulk-accept ledger shape
that would clear them.
Exempt depth-1 `spec.yaml`, `spec.yml`, and `spec.json` (the embedded
vendor source the operator passed to `--spec`, mirroring
`findArchivedSpec()`'s candidate set). Nested copies under
`.manuscripts/` or `testdata/` stay in scope so browser-sniff captures
keep flagging real customer values.
Closes #1117
* test(cli): cover depth-2 spec.yaml outside high-risk dirs
Extends TestFindPII_NestedSpecYamlStillScans with output/spec.yaml so
the depth-2 scan-via-*.yaml-glob path is explicit, not just the
depth>=2 scan-via-highRiskDirGlobs path. Pins behavior against a
future regression that might broaden the depth-1 exemption to all
paths. Greptile review feedback on #1121.
Files touched
M internal/artifacts/pii.goM internal/artifacts/pii_test.goM skills/printing-press-polish/references/pii-polish.md
Diff
commit 6536cfffc1a796604286df2c8fc67d8ff113e1ae
Author: Trevin Chow <trevin@trevinchow.com>
Date: Mon May 11 16:42:26 2026 -0700
fix(cli): exempt CLI-root vendor spec files from PII audit scope (#1121)
* fix(cli): exempt CLI-root vendor spec files from PII audit scope
Vendor-published OpenAPI specs ship `example:` blocks with synthetic
emails, phones, and addresses (Stripe `jenny@example.com`, Zendesk
`roge@example.org`, GitHub user schemas). The PII audit treated these
as customer PII, so promote/publish hard-failed on every
OpenAPI-sourced CLI: Zendesk hits 229 findings against `spec.yaml`
alone, and the polish ruleset forbids the bulk-accept ledger shape
that would clear them.
Exempt depth-1 `spec.yaml`, `spec.yml`, and `spec.json` (the embedded
vendor source the operator passed to `--spec`, mirroring
`findArchivedSpec()`'s candidate set). Nested copies under
`.manuscripts/` or `testdata/` stay in scope so browser-sniff captures
keep flagging real customer values.
Closes #1117
* test(cli): cover depth-2 spec.yaml outside high-risk dirs
Extends TestFindPII_NestedSpecYamlStillScans with output/spec.yaml so
the depth-2 scan-via-*.yaml-glob path is explicit, not just the
depth>=2 scan-via-highRiskDirGlobs path. Pins behavior against a
future regression that might broaden the depth-1 exemption to all
paths. Greptile review feedback on #1121.
---
internal/artifacts/pii.go | 17 ++++++++
internal/artifacts/pii_test.go | 50 ++++++++++++++++++++++
.../printing-press-polish/references/pii-polish.md | 2 +
3 files changed, 69 insertions(+)
diff --git a/internal/artifacts/pii.go b/internal/artifacts/pii.go
index 5ec73586..97189c27 100644
--- a/internal/artifacts/pii.go
+++ b/internal/artifacts/pii.go
@@ -163,6 +163,20 @@ var excludedFiles = map[string]bool{
ToolsPolishLedgerFilename: true,
}
+// rootVendorSpecFiles are the CLI-root basenames the generator embeds
+// as vendor source — the OpenAPI/internal spec the operator passed to
+// `--spec`. Vendor-published `example:` values (emails, phones,
+// addresses) are documentation, not customer PII, so a Stripe/Zendesk/
+// GitHub spec doesn't false-fail every promote. Exemption is depth-1
+// only; a spec.yaml nested under .manuscripts/ or testdata/ is captured
+// content and stays in scope. Mirrors findArchivedSpec()'s candidate
+// set in internal/pipeline/climanifest.go.
+var rootVendorSpecFiles = map[string]bool{
+ "spec.json": true,
+ "spec.yaml": true,
+ "spec.yml": true,
+}
+
// skippedDirs are subtree names the walker never descends into at the
// top level. Scoping to depth-1 is deliberate — `.git` and friends as
// direct children of the cli-dir are infrastructure; the same names
@@ -241,6 +255,9 @@ func isHighRiskFile(relSlash string) bool {
if excludedFiles[base] {
return false
}
+ if !strings.Contains(relSlash, "/") && rootVendorSpecFiles[base] {
+ return false
+ }
parts := strings.Split(relSlash, "/")
for _, dir := range highRiskDirGlobs {
if slices.Contains(parts, dir) {
diff --git a/internal/artifacts/pii_test.go b/internal/artifacts/pii_test.go
index e623e9c5..794453d0 100644
--- a/internal/artifacts/pii_test.go
+++ b/internal/artifacts/pii_test.go
@@ -188,6 +188,56 @@ func TestFindPII_ExcludedFiles(t *testing.T) {
assert.Contains(t, files, "data.json")
}
+// CLI-root vendor spec files (the source the operator passed to --spec)
+// are exempt because vendor-published `example:` blocks are not customer
+// PII. The exemption is depth-1 only; a spec.yaml nested under
+// .manuscripts/ is captured content and stays in scope.
+func TestFindPII_RootVendorSpecExempt(t *testing.T) {
+ root := t.TempDir()
+ pii := `"email": "jenny@example.com"`
+ write(t, filepath.Join(root, "spec.yaml"), pii)
+ write(t, filepath.Join(root, "spec.yml"), pii)
+ write(t, filepath.Join(root, "spec.json"), pii)
+ // Sibling yaml at the root must still scan — only the literal
+ // vendor-spec basenames are exempt.
+ write(t, filepath.Join(root, "config.yaml"), pii)
+
+ findings, err := FindPII(root)
+ require.NoError(t, err)
+
+ files := uniqueFiles(findings)
+ assert.NotContains(t, files, "spec.yaml")
+ assert.NotContains(t, files, "spec.yml")
+ assert.NotContains(t, files, "spec.json")
+ assert.Contains(t, files, "config.yaml")
+}
+
+// Negative: nested spec.yaml files are captured content, not vendor
+// source. They stay in scope so browser-sniff captures keep flagging.
+// Two scope re-entry paths are exercised:
+// - high-risk dirs (.manuscripts/, testdata/) match via highRiskDirGlobs
+// - arbitrary subdirs (output/) match via the *.yaml entry in
+// highRiskFileGlobs; pinned here as a regression guard against a
+// future tweak that broadens the exemption from depth-1 to all paths
+func TestFindPII_NestedSpecYamlStillScans(t *testing.T) {
+ root := t.TempDir()
+ pii := `"email": "captured@victim.com"`
+ require.NoError(t, os.MkdirAll(filepath.Join(root, ".manuscripts", "run1", "research"), 0755))
+ require.NoError(t, os.MkdirAll(filepath.Join(root, "testdata"), 0755))
+ require.NoError(t, os.MkdirAll(filepath.Join(root, "output"), 0755))
+ write(t, filepath.Join(root, ".manuscripts", "run1", "research", "spec.yaml"), pii)
+ write(t, filepath.Join(root, "testdata", "spec.yaml"), pii)
+ write(t, filepath.Join(root, "output", "spec.yaml"), pii)
+
+ findings, err := FindPII(root)
+ require.NoError(t, err)
+
+ files := uniqueFiles(findings)
+ assert.Contains(t, files, ".manuscripts/run1/research/spec.yaml")
+ assert.Contains(t, files, "testdata/spec.yaml")
+ assert.Contains(t, files, "output/spec.yaml")
+}
+
func TestFindPII_BinaryFileSkip(t *testing.T) {
root := t.TempDir()
// Planted PII in a "json" file with embedded nulls (mimics binary)
diff --git a/skills/printing-press-polish/references/pii-polish.md b/skills/printing-press-polish/references/pii-polish.md
index 0ec8e9ae..66f6a96e 100644
--- a/skills/printing-press-polish/references/pii-polish.md
+++ b/skills/printing-press-polish/references/pii-polish.md
@@ -70,6 +70,8 @@ When a downstream consumer (parser, schema validator) needs a valid-shape value,
**Manuscripts (`.manuscripts/<runID>/`).** Highest-risk source — captured browser-sniff content is by construction where customer values entered. Acceptance for manuscript findings requires the explicit `evidence_context`-cited justification; "captured data" alone is not sufficient. When in doubt, replace the value in the manuscript file directly (it's local working state) and use a hand-authored fixture with synthetic values for any downstream tests.
+**Vendor spec files at the CLI root (`spec.yaml`, `spec.yml`, `spec.json`).** Exempt from the audit by design — these are the OpenAPI/internal source the operator passed to `--spec`, and vendor `example:` blocks (Stripe `jenny@example.com`, GitHub user-schema example phones) are documentation, not customer PII. The exemption is depth-1 only; the same basename nested under `.manuscripts/` or `testdata/` is captured content and stays in scope.
+
## Forbidden accept patterns
The binary enforces these via gate failures, not just convention:
← 5f2bde7f fix(cli): suppress max_pages_cap_hit warning under --latest-
·
back to Cli Printing Press
·
chore(main): release 4.4.0 (#1073) d4c8bfbf →