Skip to content

feat(estate-safety-kit): canonical vendored source for cross-repo safety helpers - #276

Merged
mdheller merged 2 commits into
mainfrom
feat/estate-safety-kit
Aug 4, 2026
Merged

feat(estate-safety-kit): canonical vendored source for cross-repo safety helpers#276
mdheller merged 2 commits into
mainfrom
feat/estate-safety-kit

Conversation

@mdheller

@mdheller mdheller commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

What this is

This session (2026-08-03) independently fixed the same three defect shapes in
three separate repos, with no code sharing between the fixes:

  1. isSafeHttp — URL scheme allowlist against click-XSS. Written once in
    socioprophet-web/app-vue/src/services/url-safe.ts
    (socioprophet#477),
    then copy-pasted (not imported) into
    socioprophet-web/client-vue/src/utils/urlSafe.ts
    (socioprophet#550)
    because the two Vue packages don't share a workspace.
  2. mintId — CSPRNG id minting, replacing hash-of-own-inputs id schemes
    that collide same-millisecond under load. Written in
    apps/health-twin/src/ids.ts
    (prophet-platform#1070);
    the same PATTERN (not the code) had to be independently re-derived for
    bootProofRecord in socioprophet's server contracts
    (socioprophet#484).
  3. Bounded-int / bounded-payload validation — the exhaust-guard int-cap
    pattern from compute-gateway's engine.py
    (prophet-platform#1067
    #1071
    #1118),
    never generalized even to the sibling app nugget-extractor in the same
    repo.

That's real technical debt: the same bug fixed multiple times instead of
once. This PR adds estate-safety-kit/ as the canonical, single source for
all three, plus a vendoring contract so future consumers copy from one place
instead of re-deriving the fix again.

Why vendoring, not a package registry

The estate already has a proven pattern for cross-repo shared, trust-sensitive
content: SHA-pinned vendoring with a provenance record — not a package
registry. See prophet-platform/tools/revendor_engine.py +
tools/assert_vendored_engine_marker.py (the hellgraph engine tarball), and
apps/compute-gateway/src/compute_gateway/schemas/PROVENANCE.md (vendored
zero-trust kernel schemas). A private npm/PyPI registry for three files would
be a new supply-chain surface, a publish pipeline, and a versioning burden
this estate has explicitly avoided elsewhere. estate-safety-kit/PROVENANCE.md
in this PR is the full contract: consumers copy the file verbatim, record a
PROVENANCE.txt with the source commit SHA, and run
tools/check_vendored_safety_kit.py (byte-identity proof against the pinned
commit — fetched from raw.githubusercontent.com or a local --source-root
checkout) instead of trusting a comment that says "kept in sync."

What's in it

  • js/urlSafe.ts + js/urlSafe.test.ts — merged from both existing copies
    (they'd converged to identical logic already; kept the more complete
    comment). 53 tests, node --test (zero dependencies), all passing.
  • js/mintId.ts + js/mintId.test.ts — generalizes health-twin's ids.ts:
    default 128-bit width, with a bytes parameter so a caller with a
    stricter ratchet (health-twin's own 64-hex-everywhere invariant) can ask
    for 256 bits from the same helper instead of forking it. 12 tests, all
    passing.
  • py/bounded_int.py + py/test_bounded_int.py — generalizes
    _bad_nonneg_int/_MAX_NONNEG_INT from compute-gateway's exhaust guard,
    plus a bounded-string and aggregate-size-backstop sibling for the rest of
    the same defect class. 20 tests, pytest, all passing.
  • tools/check_vendored_safety_kit.py — the checker consumers run in CI.

Status

New shared-infrastructure pattern — held for human review of the vendoring
convention, not just the code.
None of the three defects this closes are
newly discovered; every site listed above already has its own independent
fix merged. This PR's only effect once its consumer PR lands is that two
already-fixed files become vendored copies of one source instead of two
hand-maintained ones.

A consumer PR in SocioProphet/socioprophet re-points app-vue's and
client-vue's isSafeHttp to vendor from this kit — it references this PR
by number since it can't pin a merge commit that doesn't exist yet. Not
blocking merge order on that PR; noting the dependency explicitly.

Not requesting admin-merge on this one — asking for a human look at the
vendoring shape itself before anything estate-wide starts depending on it.


Update: the two consumer PRs in SocioProphet/socioprophet are open:

  • socioprophet#551
    app-vue/src/services/url-safe.ts re-pointed to a vendored copy.
  • socioprophet#550
    client-vue/src/utils/urlSafe.ts re-pointed to a vendored copy (on the
    branch that originally introduced it).

Both carry a provisional PROVENANCE.txt pin at this PR's branch head
(78f5cf6) rather than a merge commit, since this PR isn't merged yet —
each notes it needs re-pinning to the actual main commit once this lands.
Both packages' full existing test suites pass unchanged against the
vendored copy (app-vue 65/65, client-vue 739/739).

…ety helpers

Three security-relevant helpers were each written correctly once, then written
AGAIN independently elsewhere in the estate with no code sharing, because there
was nowhere shared to put the fix:

- isSafeHttp (URL scheme allowlist / click-XSS guard): socioprophet#477
  (app-vue), copy-pasted into socioprophet#550 (client-vue) rather than shared,
  since the two Vue packages don't share a workspace.
- mintId (CSPRNG id minting, replacing hash-of-own-inputs schemes that collide
  same-millisecond): prophet-platform#1070 (health-twin ids.ts + consult.ts),
  the same pattern independently re-derived for socioprophet#484
  (bootProofRecord).
- bounded_int (non-negative-int-typed fields are not size-bounded when ints
  are arbitrary precision): prophet-platform#1067/#1071/#1118
  (compute-gateway's exhaust guard), never generalized even to the sibling app
  nugget-extractor in the same repo.

This follows the estate's existing vendoring convention (SHA-pinned tarball +
PROVENANCE, as in prophet-platform's revendor_engine.py /
assert_vendored_engine_marker.py, and the schemas/PROVENANCE.md pattern in
apps/compute-gateway) rather than standing up a new npm/PyPI registry surface
for three files. See estate-safety-kit/PROVENANCE.md for the full contract:
consumers copy the file verbatim, record a PROVENANCE.txt with the source
commit SHA, and run tools/check_vendored_safety_kit.py to prove byte-identity
rather than trust a comment.

65 JS tests (node:test, zero dependencies) + 20 Python tests (pytest), all
passing locally. Checker script smoke-tested against both a matching and a
deliberately-drifted copy.

New shared-infrastructure pattern — held for human review of the vendoring
convention, not just the code.
… vendoring

The canonical file docstrings said "See ../PROVENANCE.md", a path that is only
valid inside this repo — once a consumer vendors the file verbatim, that
relative path is wrong wherever it lands. Point at the repo-qualified location
instead so the comment stays correct in every vendored copy.

Also corrected the bounded_int.py citation: PR #1104 (found while drafting)
is prophet-platform's GitHub Actions SHA-pinning PR, unrelated to the exhaust
guard — the actual chain is #1067 (introduced) -> #1071 (open door found) ->
#1118 (this fix). Caught before anyone reviewed it; not relying on an
unverified citation in a PR body.
mdheller pushed a commit to SocioProphet/socioprophet that referenced this pull request Aug 3, 2026
…test glob

This file was added a few commits ago on this branch as a copy-paste of
app-vue's url-safe.ts (same defect class, same fix) because the two Vue
packages don't share a workspace. That duplication is exactly what
SourceOS-Linux/sourceos-spec#276 exists to close: repoints this file to be
a byte-identical vendored copy of estate-safety-kit/js/urlSafe.ts, with
PROVENANCE.txt recording the source commit. app-vue's copy is being
converted the same way on a separate PR (SocioProphet/socioprophet's
chore/vendor-estate-safety-kit branch), since it already existed on master.

No logic change — the vendored file is behavior-identical to what was
already here (comments only differ).

Also: vite.config.ts's vitest `include` was `src/__tests__/**/*.test.ts`
only, so `npm test` never actually ran src/utils/urlSafe.test.ts, added
earlier on this branch — the 12 tests pinning the allow/deny boundary
existed but were never executed. Added a second glob scoped to
src/utils/**/*.test.ts to fix that (deliberately not widened to
src/**/*.test.ts — several other pre-existing test files elsewhere in this
package are also out-of-glob with unverified pass/fail state; that is a
separate, broader cleanup, flagged out-of-band rather than folded in here).

Full suite after both changes: 124 files / 739 tests pass, including the
12 in urlSafe.test.ts.
@mdheller
mdheller merged commit 297c4e3 into main Aug 4, 2026
7 checks passed
@mdheller
mdheller deleted the feat/estate-safety-kit branch August 4, 2026 00:04
mdheller pushed a commit to SocioProphet/socioprophet that referenced this pull request Aug 4, 2026
…test glob

This file was added a few commits ago on this branch as a copy-paste of
app-vue's url-safe.ts (same defect class, same fix) because the two Vue
packages don't share a workspace. That duplication is exactly what
SourceOS-Linux/sourceos-spec#276 exists to close: repoints this file to be
a byte-identical vendored copy of estate-safety-kit/js/urlSafe.ts, with
PROVENANCE.txt recording the source commit. app-vue's copy is being
converted the same way on a separate PR (SocioProphet/socioprophet's
chore/vendor-estate-safety-kit branch), since it already existed on master.

No logic change — the vendored file is behavior-identical to what was
already here (comments only differ).

Also: vite.config.ts's vitest `include` was `src/__tests__/**/*.test.ts`
only, so `npm test` never actually ran src/utils/urlSafe.test.ts, added
earlier on this branch — the 12 tests pinning the allow/deny boundary
existed but were never executed. Added a second glob scoped to
src/utils/**/*.test.ts to fix that (deliberately not widened to
src/**/*.test.ts — several other pre-existing test files elsewhere in this
package are also out-of-glob with unverified pass/fail state; that is a
separate, broader cleanup, flagged out-of-band rather than folded in here).

Full suite after both changes: 124 files / 739 tests pass, including the
12 in urlSafe.test.ts.
mdheller added a commit to SocioProphet/socioprophet that referenced this pull request Aug 4, 2026
* chore(app-vue): vendor url-safe.ts from estate-safety-kit, not a hand-maintained copy

isSafeHttp was independently written here (#477) and then copy-pasted (not
imported) into client-vue's utils/urlSafe.ts (#550) because the two Vue
packages don't share a workspace — the exact kind of duplication
SourceOS-Linux/sourceos-spec#276 exists to close. This repoints app-vue's
copy to be a byte-identical vendored copy of
estate-safety-kit/js/urlSafe.ts, with PROVENANCE.txt recording the source
commit. client-vue's copy is being converted the same way on #550's branch,
since that file doesn't exist on master yet.

No behavior change: the vendored file is logic-identical to what was here
(comments only differ — the canonical version documents both consumers and
the vendoring contract instead of just this one). Confirmed with the
existing test suite unchanged: 65/65 app-vue tests pass, including all 52
in url-safe.test.ts covering the allow/deny scheme boundary.

* chore(app-vue): re-pin PROVENANCE.txt to the real sourceos-spec merge commit

sourceos-spec#276 merged (squash) as 297c4e38 — the branch-head SHA this
PROVENANCE.txt provisionally pinned no longer exists as a live ref.
Verified byte-identical against the new pin before re-pinning (sha256
9493df8b... both sides), so this closes the provisional-pin TODO the
file itself flagged rather than leaving it to rot.

---------

Co-authored-by: Michael Heller <charles.peterson@socioprophet.ai>
mdheller added a commit to SocioProphet/socioprophet that referenced this pull request Aug 4, 2026
)

* fix(competitive-intel): scheme-allowlist BoardTable evidence links (XSS defense-in-depth)

Adversarial review of #545 flagged: BoardTable.vue bound `:href` from
`cell(...).evidence!.href` (competitiveBoardsApi JSON) with no scheme
allowlist. `rel="noopener noreferrer"` is present but doesn't help — the
JS in a `javascript:` href runs before a tab exists.

Today's source is estate-internal (dashboard-bff/catalog-gateway JSON or
bundled fixtures with hardcoded https://github.com/SocioProphet URLs), so
this is defense-in-depth rather than an active exploit path. Same class
of fix as #477 (Search.vue) — ported the helper to client-vue as
`utils/urlSafe.ts` since app-vue's `services/url-safe.ts` isn't shared
across the two packages.

Unsafe evidence hrefs now render as plain text (label only, no link)
instead of a live anchor. 10 test cases pin the allow/deny boundary.

* chore(client-vue): vendor urlSafe.ts from estate-safety-kit; fix its test glob

This file was added a few commits ago on this branch as a copy-paste of
app-vue's url-safe.ts (same defect class, same fix) because the two Vue
packages don't share a workspace. That duplication is exactly what
SourceOS-Linux/sourceos-spec#276 exists to close: repoints this file to be
a byte-identical vendored copy of estate-safety-kit/js/urlSafe.ts, with
PROVENANCE.txt recording the source commit. app-vue's copy is being
converted the same way on a separate PR (SocioProphet/socioprophet's
chore/vendor-estate-safety-kit branch), since it already existed on master.

No logic change — the vendored file is behavior-identical to what was
already here (comments only differ).

Also: vite.config.ts's vitest `include` was `src/__tests__/**/*.test.ts`
only, so `npm test` never actually ran src/utils/urlSafe.test.ts, added
earlier on this branch — the 12 tests pinning the allow/deny boundary
existed but were never executed. Added a second glob scoped to
src/utils/**/*.test.ts to fix that (deliberately not widened to
src/**/*.test.ts — several other pre-existing test files elsewhere in this
package are also out-of-glob with unverified pass/fail state; that is a
separate, broader cleanup, flagged out-of-band rather than folded in here).

Full suite after both changes: 124 files / 739 tests pass, including the
12 in urlSafe.test.ts.

* fix(client-vue): widen vitest include — 6 real test files were never run

The estate-safety-kit vendoring commit narrowly widened test.include to
`['src/__tests__/**/*.test.ts', 'src/utils/**/*.test.ts']` to pick up
urlSafe.test.ts specifically, and explicitly punted a broader fix: 6
other pre-existing, real (non-stub) test files sat outside BOTH globs —
src/config/mesh.test.ts, src/features/contract-surfaces/contractSurfaces
.test.ts, and 4 files under src/runtime-adapters/ (knowledgeGraphClient,
ontologyClient, rescuedPlatformFeatures, routeRuntimeFeatures). `npm
test`/CI never executed them. Same declared-but-unenforced shape as
everything else this session has been closing — a test file existing is
not the same as a test file running.

Widened `include` to `src/**/*.test.ts`, replacing both narrower globs.

Verified rather than assumed clean:
- Full suite: 130 test files / 783 tests, all pass.
- The 6 previously-orphaned files run in isolation: 6/6 files, 44/44
  tests, all pass — nothing was actually stale or broken, they were
  just never executed. No test-content fixes were needed.
- `vue-tsc --noEmit`: clean.

The noisy ECONNREFUSED/AbortError console output during the full run is
expected — live-adapter fallback tests intentionally hit an unreachable
localhost:3000 to exercise the degraded path; vitest's own pass/fail
accounting (783/783) is unaffected by it.

* chore(client-vue): re-pin PROVENANCE.txt to the real sourceos-spec merge commit

Same fix as #551 — sourceos-spec#276 merged (squash) as
297c4e38, the provisional branch-head pin is now dead. Verified
byte-identical before re-pinning.

---------

Co-authored-by: Michael Heller <charles.peterson@socioprophet.ai>
mdheller added a commit to SocioProphet/prophet-platform that referenced this pull request Aug 4, 2026
…e fast-follow (#1336)

ids.ts's mint-not-derive pattern (fixed here in #1070) was independently
re-derived a third time for bootProofRecord in socioprophet's server
contracts (#484) — no code shared either time. That duplication
is what SourceOS-Linux/sourceos-spec#276 exists to close: a canonical
estate-safety-kit/js/mintId.ts generalizing this exact pattern, with a
`bytes` parameter so a caller can request this file's own stricter 256-bit
width instead of forking the helper.

Not converting ids.ts to a vendored copy in this change: the kit's default
width is 128 bits, and a blind file-swap would silently narrow every id
this service mints from 256 to 128 bits unless every call site (server.ts,
consult.ts, invariants.ts) is updated in the same change to request 32
bytes explicitly. That's a real, reviewable refactor, not a comment.
Tracked as prophet-platform#1335.

23/23 health-twin tests still pass — comment-only change.
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