Skip to content

feat(OMN-17320): gate denylisted customer identifiers out of this public repo - #3074

Merged
jonahgabriel merged 2 commits into
devfrom
jonah/omn-17320-exposed-identifier-enforcement-gate
Aug 31, 2026
Merged

jonahgabriel merged 2 commits into
devfrom
jonah/omn-17320-exposed-identifier-enforcement-gate

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Aug 31, 2026 •

Copy link
Copy Markdown
Collaborator

What and why

OMN-17288 scrubbed a live customer's tenant slug out of five files in this repo (#3062, 3f10ee5e) and established a synthetic-identifier convention in its place. Three hours later an unrelated lane reintroduced the same slug in omnimarket (#2239), and the rebase carried it onto #2241 — the PR whose own acceptance criterion was "zero grep hits" — with every enforced gate green. Nothing in either repo was looking.

The convention was documentation, and documentation lost a race in three hours. Operating Rule #5: detection that is not a gate gets ignored. This PR is the gate.

Ticket: OMN-17320
Companion PR: OmniNode-ai/omnimarket#2243 (byte-identical scanner + denylist)

Design decision: digests, not a plaintext pattern list

This repo is PUBLIC. Writing a forbidden customer identifier into a pattern file here would (1) create exactly the fresh, greppable, current-tree occurrence the gate exists to prevent, and (2) force that file to be exempt from its own rule — a special file holding the forbidden value, that nobody scans and that people copy from. That is the shape of the incident, not a fix for it.

Entries are salted SHA-256 plus a class label and owning ticket; the loader refuses any entry carrying a value/literal/plaintext field.

Stated plainly rather than glossed: the salt is committed, so this is obfuscation, not secrecy — a reader holding this repo can brute-force a short slug. That is not the property being bought. The OMN-17288 values are already public in git history and the operator ruled document-and-accept on that history; this PR does not try to undo it. What the format buys is forward safety: the next entry added here may be a live identifier that has not leaked, where a plaintext denylist would be an active disclosure.

Wiring — both surfaces, this PR (Rule #5)

  • pre-commit hook exposed-identifier-gate
  • CI job Exposed Identifier Gate (OMN-17320) in ci.yml, registered in scripts/ci/ci_summary_gate.py::STRICT_GATE_JOBS

That registration is half the mechanism, and is the part specific to this repo: dev requires exactly one context (CI Summary), so a workflow file alone is not enforcement here. The default-deny sweep already fails CI Summary when the job fails — but an unregistered job that is skipped or deleted yields SUCCESS. Without the registration, deleting this job would silently restore the unenforced state that produced the recurrence. The job is unconditional (if: always()), so a skip is anomalous and never a legitimate opt-out.

The CI job scans the full tree, not the diff: a denylisted identifier reaching dev by any route — including a rebase carrying it in, which is exactly what happened on omnimarket#2241 — must red the PR, not only one that edits the offending line.

Matching and exemption

Windowed inside each identifier token, so a literal is caught bare, embedded (tenant_<slug>_v2), and inside a path/URL segment. Findings print path:line:col + match length + entry id — never the value. Encoded forms are deliberately not decoded; OMN-17180 owns that class and claiming coverage here would be a false claim.

One escape hatch, per line, ticket + reason required:

# onex-allow-exposed-identifier OMN-XXXXX reason="<concrete reason>"

A bare annotation is rejected, matching every other onex-allow class. There is deliberately no file-level waiver and no self-exempt file — a whole-file waiver is how a forbidden value survives in a corner nobody reads. The gate is subject to its own rule.

dod_evidence

AC1 — the gate is not exempt from itself. git grep for either OMN-17288 literal over the full worktree returns 0 hits, including the scanner and denylist themselves (verified tracked + untracked).

AC2 — bare / embedded / path-segment, proven by committed tests over a synthetic denylist entry.

AC3 — non-vacuity against the REAL entries, proven out-of-tree. Committed tests cannot carry this proof without defeating AC1; that split is stated, not glossed. Method: pre-scrub content was extracted from git objects that already exist (3f10ee5e^, and omnimarket's eb356f07^/e0cb5235^) into a scratch tree outside both repos, and the shipped denylist was used as an oracle to identify the real literals inside it — so the proof was produced without hand-typing a literal anywhere. Result: all 5 entries confirmed live —
omn17288-tenant-slug, -slug-body, -tenant-uuid, -uuid-prefix matched real historical content directly; -uuid-hex was confirmed by transforming the recovered UUID.

scanner over reconstructed corpus:  exit 1, findings=15 (bare/embedded/path-segment, real values)
scanner over 10 real pre-scrub files: findings=18, every file flagged

This mattered: an earlier pass over only the slug-carrying files matched 2 of 5 entries — the three UUID entries were unproven until the UUID-carrying files were pulled in. A vacuous digest would have shipped silently otherwise.

AC4 — RED-first incident replay (OMN-15547 convention), tests/ci/test_incident_replay_omn17320.py + tests/incident_replays/registry.yaml. The artifact is docker/migrations/forward/_ledger/migration-supersessions.tsv exactly as it stood at 6527db3f (= 3f10ee5e^), immediately before the scrub — one of the five files the OMN-17288 census enumerated.

One redaction, recorded because it is load-bearing. The 11-byte slug on line 7 is replaced by an 11-byte stand-in. Every other byte of the 8455 is verbatim and the length is preserved, so every offset is unchanged and the replay still asserts the finding at the slug's real position (line 7, col 379). The pre-redaction sha256 is recorded in registry.yaml so anyone can re-fetch the git object and verify byte-for-byte what changed. The redaction is unavoidable rather than convenient: this repo is PUBLIC, and committing the real value as a fixture is precisely the disclosure the guard under test exists to prevent — an unredacted fixture would fail the very gate it proves. OMN-17288 set this same precedent in this same registry.

An accept-control in the same module (test_the_shipped_denylist_does_not_fire_on_this_artifact) requires the SHIPPED denylist to pass those same bytes, so a guard that simply rejects everything cannot satisfy the case.

AC5 — annotation with ticket+reason exempts; bare annotation does not. Committed tests.

AC6 — pre-commit hook + CI gate + STRICT_GATE_JOBS registration, all in this PR.

AC7 — scanner + denylist byte-identical with omnimarket, pinned by test_cross_repo_fingerprint_pin:

scanner=3bcae6e1d4c884ec9cc21d76ad75ef2a394ae5376b0f3fa8263f3ba607ce6826
denylist=0ca020357e5745885bd120c9e96ba95e069c34ea7fb1eb745b7f9895f635e8b6

Tests: 29 pass locally; the governed pre-push selector escalated to the full suite (reason=test_infrastructure) and it ran green on h101 over the remote leg — no override grant, no PREPUSH_*, no skip token.

Scope

Only omnimarket + omnibase_infra — the two repos the OMN-17288 occurrence map names as having had hits. Rolling to the other nine public repos is the G1-FULL shape OMN-16156 deferred post-beta, explicitly not in scope. Git-history rewrite is closed by operator ruling on OMN-17288.

Evidence-Ticket: OMN-17320
Evidence-Source: OCC#7850

…lic repo

OMN-17288 scrubbed a live tenant slug out of five files in this repo (#3062,
3f10ee5) and established a synthetic-identifier convention in its place. Three
hours later an unrelated lane reintroduced the same slug in omnimarket (#2239),
and the rebase carried it onto omnimarket#2241 -- the PR whose own acceptance
criterion was "zero grep hits" -- with every enforced gate green. Nothing in
either repo was looking. The convention was documentation, and documentation
lost a race in three hours. Operating Rule #5: detection that is not a gate
gets ignored.

Why digests and not a plaintext pattern list: this repo is PUBLIC. Writing a
forbidden customer identifier into a pattern file here would create exactly the
fresh, greppable, current-tree occurrence the class exists to prevent, and would
force that file to be exempt from its own rule -- a special file holding the
forbidden value, that nobody scans and that people copy from. That is the shape
of the incident, not a fix for it. Entries are salted SHA-256 plus a class label
and owning ticket; the loader refuses to load an entry carrying a value/literal/
plaintext field. The salt is committed, so this is obfuscation and not secrecy,
and that is stated in the file rather than implied: the OMN-17288 values are
already public in git history and the operator ruled document-and-accept on that
history. What the format buys is FORWARD safety -- the next entry may be a live
identifier that has NOT leaked, where a plaintext denylist would be an active
disclosure.

Matching windows inside each identifier token, so a literal is caught bare,
embedded (tenant_<slug>_v2), and inside a path or URL segment. Findings print
path:line:col plus match length and entry id, never the value. Encoded forms are
deliberately NOT decoded -- OMN-17180 owns that class, and claiming coverage
here would be a false claim.

One escape hatch, per line, ticket + reason required:
  # onex-allow-exposed-identifier OMN-XXXXX reason="<concrete reason>"
A bare annotation is rejected, matching every other onex-allow class. There is
deliberately NO file-level waiver and NO self-exempt file: a whole-file waiver is
how a forbidden value survives in a corner nobody reads. The gate is subject to
its own rule.

Wired in this same PR on both surfaces (Rule #5):
- pre-commit hook `exposed-identifier-gate`
- CI job `Exposed Identifier Gate (OMN-17320)` in ci.yml, registered in
  scripts/ci/ci_summary_gate.py::STRICT_GATE_JOBS. That registration is half the
  mechanism: dev requires exactly one context (CI Summary), and while the
  default-deny sweep already fails on a FAILING job, an unregistered job that is
  skipped or deleted yields SUCCESS -- so without it, removing this job would
  silently restore the unenforced state that produced the recurrence.

Evidence:
- Incident replay (OMN-15547 convention) over the real pre-scrub artifact,
  captured from git object 6527db3 (= 3f10ee5^). ONE same-length redaction of
  the slug, recorded in registry.yaml with the pre-redaction sha256 so the git
  object can be re-fetched and diffed; every other byte of the 8455 is verbatim
  and offsets are preserved, so the finding is asserted at the slug's real
  position (line 7, col 379). An accept-control in the same module requires the
  SHIPPED denylist to PASS those same bytes, so a reject-everything guard cannot
  satisfy the case.
- Non-vacuity of all five real entries proven OUT OF TREE (committed tests cannot
  carry this without defeating the gate's own rule): pre-scrub content extracted
  from git objects identifies the slug, slug-body, uuid and uuid-prefix entries
  by digest, and the uuid-hex entry is confirmed by transforming the recovered
  UUID. Scanner exit 1 with 15 findings across bare/embedded/path-segment forms.
- 29 tests pass; full-tree scan clean.

Ticket: OMN-17320
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Hostile Reviewer — DEGRADED (informational)

Blocking findings (critical): 0
Total findings: 0
Models succeeded: none

Note: All reviewer models failed or were unavailable. Degraded results are informational during the pilot phase (OMN-8468/OMN-8524) and do not block merge. Error: all review endpoints [192.168.86.201:8000 192.168.86.201:8000 ] unreachable — preflight short-circuit (no models available)


Gate semantics (pilot phase)

Verdict Meaning Blocks merge?
passed No critical findings No
blocked CRITICAL findings found Yes
degraded All models unavailable (infra) No (pilot)

Powered by omniintelligence.review_pairing.cli_review — multi-model adversarial review (OMN-8468/OMN-8524)

jonahgabriel pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Aug 31, 2026
…ibase_infra#3074 (#7850)

* evidence(OMN-17320): author OCC companion for OmniNode-ai/omnibase_infra#3074

OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head 982125bce57ad6cb273424de39ddf5647f9506d3.

* evidence(OMN-17320): self-bind OCC#7850 + rebind contract_sha256

---------

Co-authored-by: omnimarket-bot <bot@omninode.ai>
@jonahgabriel
jonahgabriel enabled auto-merge (squash) August 31, 2026 15:52
@jonahgabriel
jonahgabriel merged commit de00cee into dev Aug 31, 2026
185 of 191 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-17320-exposed-identifier-enforcement-gate branch August 31, 2026 16:02
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