[object Object]

← 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

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 →