Skip to content

chore: de-link parent organization from public tree and add an identity gate - #68

Closed
verlyn13 wants to merge 3 commits into
mainfrom
docs/delink-parent-org
Closed

chore: de-link parent organization from public tree and add an identity gate#68
verlyn13 wants to merge 3 commits into
mainfrom
docs/delink-parent-org

Conversation

@verlyn13

Copy link
Copy Markdown
Member

Stacked on #66merge #66 first, see Ordering below.

Why

system-config is public and now has outside readers. The identity registry gives the parent organization linkPolicy: no-link / publicAlias: "parent organization", and docs/git-identity.md forbids committing "registry.json or any value from it". registry.json itself is clean — never tracked, never added in any commit, gitignored twice. The values rule was the one being violated, and nothing enforced it.

1. OpenBao host identity → machine data

home/private_dot_ssh/private_conf.d/tng-openbao.conf.tmpl was the highest-value single artifact in the repo. Fourteen lines that published, together: the parent org's real name, a publicly routable IPv4 (verified non-RFC1918), User root, and the operator key filename. A no-link entity's secrets manager, by reachable address, with root login, in a repo carrying the operator's real identity on every commit.

Host identity now comes from [data.ssh.parent_org_openbao] in ~/.config/chezmoi/chezmoi.toml. Guarded with hasKey rather than a bare lookup, because chezmoi runs missingkey=error and a bare key breaks rendering on an unconfigured machine.

⚠️ Operator action required before the next chezmoi apply. Add the [data.ssh.parent_org_openbao] table, or the live ~/.ssh/conf.d/tng-openbao.conf loses its Host entry and the alias stops resolving. Example values in the template are RFC 5737 documentation addresses.

2. Name sweep — 142 → 42 occurrences

100 incidental occurrences removed across 28 files. Biggest single win: docs/google-admin-tooling.md 15 → 0, which was publishing a Google Workspace super-admin mailbox beside the exact gcloud auth login invocation that uses it.

Deliberately not changed, because changing the prose while the identifier stays would make the docs wrong:

  • SSH host alias github.com-nash-group, key filename id_ed25519_nash-group, 1P item path ssh/workstation/nash-group/github
  • iterm2/profiles/*.json and the four docs describing them — the filenames and "Bound Hosts" globs are matched at runtime
  • policies/host-capability-substrate/tiers.yaml — byte-pinned by a sibling repo

This reduces exposure; it does not de-link the organization. Two tracked filenames still carry the name (10-nash-guardian-l0.json, 11-nash-repos.json). Completing that means renaming live artifacts and a vault item — a separate operator-gated change.

3. The enforcement gap

identity_no_registry_values_committed greps git ls-files for a file named registry.json and runs check-ignore. Zero content inspection. It reported green throughout. ng-doctor is also never executed by CI — validate-repo.sh only shellchecks its template.

Two content scans added to the gate CI actually runs:

  • Parent-org name. Does not match bare nash-group, which only occurs inside the structural identifiers above.
  • Emails outside an allowlist. The allowlist is what makes this runnable rather than permanently red — it passes vendor, RFC-reserved and operator-owned domains, and fails personal and third-party addresses.

Email failure output is masked to localpart-initial. CI logs on a public repo are public; echoing a matched address would publish, durably and indexably, the exact value the step exists to keep out. The org-name scan prints unmasked, which is correct — that name is already public via the structural files.

Exclusions are structural, not cosmetic: iterm2/**, docs/iterm2-*.md, zz-iterm2.zsh, the byte-pinned tiers.yaml, tests/policies/** (negative fixtures whose values are the test input), and home/.chezmoidata.yaml (allowed_signers principals must byte-match the signing identity or verification stops).

Ordering

This gate fails on main until #66 lands. That is intended, not a regression. Verified: the six files the email scan flags are exactly the six #66 fixes — so the merged state passes.

Validation

  • shellcheck clean at the pinned 0.11.0
  • Gate fires correctly on the current tree and its output is masked
  • No structural file appears in the sweep diff (asserted explicitly in the commit)
  • chezmoi execute-template renders the OpenBao template safely with the key absent

Not in scope

History. The name reaches the root commit, so a full rewrite is 335/335 commits and destroys 250 verifiable signatures, and cleans none of the GitHub-side residue. Recommendation is accept-and-document; that decision is yours.

system-config is public and now has outside readers. The parent org carries
linkPolicy "no-link" / publicAlias "parent organization" in the identity
registry, but its real name appeared 142 times across 40 tracked files.

This sweep removes the 100 incidental occurrences -- doc prose, filesystem
paths, the org domain, and a Google Workspace super-admin mailbox published
beside the gcloud invocation that uses it. Highest-value single change:
docs/google-admin-tooling.md went from 15 occurrences to 0.

Substitutions:
  Organizations/the-nash-group -> Organizations/<parent-org>
  the-nash-group               -> <parent-org>
  Nash-Group                   -> parent-organization
  The Nash Group / Nash Group  -> the parent organization
  <admin>@thenash.group        -> <parent-org-admin>@<parent-org-domain>
  thenash.group                -> <parent-org-domain>

Deliberately NOT changed, and why:

- Structural identifiers other things resolve against: the SSH host alias
  github.com-nash-group, the key filename id_ed25519_nash-group, and the
  1Password item path ssh/workstation/nash-group/github. Renaming the prose
  while the identifier stays would make the docs wrong.
- iterm2/profiles/*.json and the four docs describing them. The profile
  filenames and Bound Hosts globs are matched at runtime; the docs must keep
  naming them accurately.
- policies/host-capability-substrate/tiers.yaml, byte-pinned by a sibling
  repo -- coordinated re-vendor only.

42 occurrences remain, all structural. This reduces exposure; it does not
de-link the organization, because two tracked FILENAMES still carry the name.
Completing that requires renaming live artifacts and a vault item, which is a
separate operator-gated change.

Validation: scripts/validate-repo.sh PASS. No structural file appears in the
diff (asserted explicitly).
This 14-line template was the highest-value single artifact in the public
repo. It published, together: the parent organization's real name, a
publicly routable IPv4 (verified non-RFC1918), User root, and the operator
key filename -- i.e. a no-link entity's secrets manager, by reachable
address, with root login, in a repo carrying the operator's real identity
on every commit.

Host identity now comes from ~/.config/chezmoi/chezmoi.toml under
[data.ssh.parent_org_openbao] (alias, host, user, identity_file). Machine
data already lives outside the repo, so no value is committed.

The block is guarded with `hasKey .ssh "parent_org_openbao"` rather than a
bare lookup, because chezmoi runs with missingkey=error and a bare key would
break rendering on any machine that has not been configured. With the key
absent the file renders as comments explaining how to restore it, so
`chezmoi apply --dry-run --force` stays clean.

OPERATOR ACTION REQUIRED: add the [data.ssh.parent_org_openbao] table before
the next `chezmoi apply`, or the live ~/.ssh/conf.d/tng-openbao.conf loses
its Host entry and the alias stops resolving. The example values in the
template are RFC 5737 documentation addresses, not the real ones.

The source filename still carries the "tng" abbreviation. Renaming it is
safe (the SSH config Include is a *.conf glob, and the alias now comes from
machine data) but renames outside scripts/ and docs/ are ask-first per
AGENTS.md, and chezmoi would orphan the old deployed file. Left for a
separate decision.
docs/git-identity.md presents identity_no_registry_values_committed as the
control for "never commit registry.json or any value from it". Only the file
half was ever implemented: that ng-doctor check greps git ls-files for a file
NAMED registry.json and runs check-ignore. It performs no content inspection,
and it reported green while 142 parent-org occurrences and 32 third-party
personal email addresses sat in the tree. ng-doctor is also never executed by
CI -- validate-repo.sh only shellchecks its template.

Add two content scans to the gate CI actually runs.

1. Parent-org real name. Deliberately does not match a bare "nash-group",
   which only occurs inside identifiers that other things resolve against
   (github.com-nash-group, id_ed25519_nash-group, ssh/workstation/nash-group).

2. Email addresses outside an allowlist. The allowlist is what makes this
   runnable rather than permanently red: it passes vendor, RFC-reserved and
   operator-owned domains and fails personal and third-party addresses.

Failure output for the email scan is MASKED to localpart-initial. CI logs on
a public repo are themselves public, so echoing a matched address would
publish, durably and indexably, the exact value the step exists to keep out.
The org-name scan prints matching lines unmasked, which is correct -- that
name is already public via the structural files below.

Exclusions are structural, each a value something resolves against at runtime:
iterm2/** profile filenames and Bound Hosts globs, docs/iterm2-*.md which
document them, zz-iterm2.zsh's $PWD test, the byte-pinned tiers.yaml,
tests/policies/** negative fixtures whose values ARE the test input, and
home/.chezmoidata.yaml allowed_signers principals which must byte-match the
signing identity or verification stops.

Also fixes three name occurrences the doc sweep missed on case and hyphen
variants -- two description fields in the SSH import manifests and one line in
secret-records.md. The structural identifiers on those same lines are
unchanged.

NOTE: this gate FAILS on main until the PII redaction lands, which is the
intended ordering, not a regression. shellcheck clean at the pinned 0.11.0.
@verlyn13
verlyn13 requested a review from a team as a code owner August 20, 2026 03:01
verlyn13 added a commit that referenced this pull request Aug 23, 2026
Closes the ownership half of the oldest open finding in
docs/ai-tool-config-ownership.md. That file is loaded into every Claude Code
session in every repo on this host and had no source of truth, so it was
invisible to chezmoi status, chezmoi diff, validate-repo.sh and ng-doctor. The
doc records fish surviving in it through every migration pass for exactly that
reason, and this is the fourth time its content has been corrected without the
ownership being assigned.

The source is home/dot_claude/CLAUDE.md and the adoption is a no-op deployment:
the source is byte-identical to the live file, chezmoi status reports no diff,
and chezmoi apply --dry-run --force exits 0. Nothing on the machine changed;
the file simply became visible.

dot_claude, deliberately NOT exact_dot_claude. The exact_ prefix would make
chezmoi delete everything under ~/.claude/ it does not manage, which is the
app's own settings.json, settings.local.json, agents/, commands/ and skills/.
.chezmoiignore's Claude guard block is rewritten to say so at the point someone
would be tempted to add the prefix.

Safe for a public repo, checked rather than assumed: the file has zero
non-allowlisted email addresses, zero parent-org name hits, zero identity
values and zero absolute /Users paths, so it passes the content gate arriving
in PR #68.

Scope note: visibility is not binding force. README.md's configuration-layers
table still lists this file as non-binding, because it is -- being managed means
drift is now detectable, not that the instructions are enforced.
@verlyn13

Copy link
Copy Markdown
Member Author

Superseded by the accepted successor series: #76 established value-blind public-source privacy enforcement, #77 removed whole-file historical privacy exemptions while preserving public-safe provenance, and #78 moved the remaining machine-specific SSH identity behind validated private machine data with fail-closed destination semantics. The successor changes were independently reviewed, validated, and merged; this older combined branch is no longer a merge candidate.

@verlyn13 verlyn13 closed this Aug 29, 2026
@verlyn13
verlyn13 deleted the docs/delink-parent-org branch August 30, 2026 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant