diff --git a/cross_repo_test.go b/cross_repo_test.go index 6f8b3fd..473ccd5 100644 --- a/cross_repo_test.go +++ b/cross_repo_test.go @@ -9,8 +9,8 @@ func TestExtractRepoFromDir(t *testing.T) { dir string want string }{ - {"/Users/andreas/dev/brick-now/openspec", "brick-now"}, - {"/Users/andreas/dev/tengil/openspec/changes/foo", "tengil"}, + {"/home/user/dev/brick-now/openspec", "brick-now"}, + {"/home/user/dev/tengil/openspec/changes/foo", "tengil"}, {"/home/user/project/openspec", "project"}, {"/openspec", ""}, // edge case: no parent {"openspec", "openspec"}, // bare openspec — no parent, returns full dir diff --git a/openspec/changes/archive/2026-07-25-launch-readiness/proposal.md b/openspec/changes/archive/2026-07-25-launch-readiness/proposal.md index 0c183a7..b132931 100644 --- a/openspec/changes/archive/2026-07-25-launch-readiness/proposal.md +++ b/openspec/changes/archive/2026-07-25-launch-readiness/proposal.md @@ -69,7 +69,7 @@ owner-only decisions. - Grep for `/Users/`, `/home/`, `192.168.`, `10.0.`, personal emails — no matches in tracked files. - History spot checks (`git log --all -S`) for `ghp_`, `github_pat_`, - `sk-ant`, `/Users/andreas`, `192.168.` — no matches. + `sk-ant`, `/Users/`, `192.168.` — no matches. - Commit author metadata contains the owner's work and personal email addresses. This is ordinary git metadata, visible on any public repo, and not a secret; noted only so the owner can consciously accept it. diff --git a/openspec/changes/archive/2026-09-17-agent-topology-command/proposal.md b/openspec/changes/archive/2026-09-17-agent-topology-command/proposal.md index e11fca2..eb0c07e 100644 --- a/openspec/changes/archive/2026-09-17-agent-topology-command/proposal.md +++ b/openspec/changes/archive/2026-09-17-agent-topology-command/proposal.md @@ -44,13 +44,13 @@ JSON shape, one entry per change: "slug": "add-field-forms", "title": "Auto-render field forms", "stage": "active", - "store": "/Users/andreas/exopen/portal", + "store": "/home/user/projects/portal", "repo": "ExopenGitHub/portal", "issue": {"provider": "github:ExopenGitHub/portal", "id": "4231", "url": "https://github.com/ExopenGitHub/portal/issues/4231", "state": "open"}, "branch": "feat/4231-add-field-forms", - "worktree": "/Users/andreas/exopen/portal-worktrees/add-field-forms", + "worktree": "/home/user/projects/portal-worktrees/add-field-forms", "epic": "https://github.com/ExopenGitHub/planning/issues/88", "linked": ["add-field-forms-api"] } diff --git a/private_data_test.go b/private_data_test.go new file mode 100644 index 0000000..fe86044 --- /dev/null +++ b/private_data_test.go @@ -0,0 +1,96 @@ +package specsync + +// This repository is PUBLIC. A check that only runs when someone remembers to +// run it will drift — and did. A scan on 2026-07-25 found no home paths and was +// recorded as done; six days later cross_repo_test.go added real absolute paths +// as test fixtures, and nothing re-checked for two months. +// +// So the check runs in `go test ./...`, which ci.yml already invokes. It +// cannot be skipped by forgetting a CI step, and it fails locally too. + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + "testing" +) + +var ( + // /Users/ — a real macOS home directory. Placeholders + // like /Users/ are deliberately excluded by requiring lowercase + // letters, so documentation can still describe the shape of a path. + homePath = regexp.MustCompile(`/Users/[a-z][a-z0-9._-]*`) + + // RFC1918 address literals. A dotted quad only; prose mentioning + // "192.168." as a thing to scan for is not a match. + privateIP = regexp.MustCompile( + `\b(?:10\.[0-9]{1,3}|192\.168|172\.(?:1[6-9]|2[0-9]|3[01]))\.[0-9]{1,3}\.[0-9]{1,3}\b`) + + // Credential prefixes. Belt and braces: the check that matters most is + // the one nobody should ever need. + credential = regexp.MustCompile( + `\b(?:ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|` + + `ABSK[A-Z0-9]{10,}|AKIA[0-9A-Z]{16}|sk-ant-[A-Za-z0-9_-]{20,})\b`) +) + +// skippedNever: this test is meaningless if the repository stops being public, +// but it is also not harmful then, so it runs unconditionally and says so. +func TestNoPrivateDataInPublicRepo(t *testing.T) { + root := "." + if _, err := os.Stat("go.mod"); err != nil { + root = "." // running from a subpackage; paths still resolve upward + } + + var offenders []string + err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { + if err != nil { + return nil // unreadable path: not a privacy failure + } + name := d.Name() + if d.IsDir() { + // Skip VCS, build and dependency trees. They are not published + // as repository content and would drown the signal. + switch name { + case ".git", "node_modules", "site", "dist", "vendor": + return filepath.SkipDir + } + return nil + } + switch filepath.Ext(name) { + case ".go", ".md", ".yml", ".yaml", ".json", ".sh", ".txt", ".toml": + default: + return nil + } + data, err := os.ReadFile(path) + if err != nil { + return nil + } + for i, line := range strings.Split(string(data), "\n") { + for _, re := range []struct { + what string + re *regexp.Regexp + }{ + {"home path", homePath}, + {"private IP", privateIP}, + {"credential", credential}, + } { + if re.re.FindString(line) != "" { + offenders = append(offenders, fmt.Sprintf("%s:%d: %s: %s", + path, i+1, re.what, strings.TrimSpace(line))) + } + } + } + return nil + }) + if err != nil { + t.Fatalf("walk: %v", err) + } + + if len(offenders) > 0 { + t.Errorf("this repository is PUBLIC but contains private data "+ + "(%d occurrence(s)). Redact before pushing:\n %s", + len(offenders), strings.Join(offenders, "\n ")) + } +}