Skip to content

fix(competitive-intel): scheme-allowlist BoardTable evidence links - #550

Merged
mdheller merged 4 commits into
masterfrom
fix/boardtable-scheme-allowlist
Aug 4, 2026
Merged

fix(competitive-intel): scheme-allowlist BoardTable evidence links#550
mdheller merged 4 commits into
masterfrom
fix/boardtable-scheme-allowlist

Conversation

@mdheller

@mdheller mdheller commented Aug 3, 2026

Copy link
Copy Markdown
Member

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.


Update (2026-08-03): utils/urlSafe.ts here has been re-pointed to a
byte-identical vendored copy of the canonical helper in
SourceOS-Linux/sourceos-spec#276
(estate-safety-kit/js/urlSafe.ts), which also folds in app-vue's
services/url-safe.ts — the two had converged to identical logic despite
being maintained separately. utils/PROVENANCE.txt records the source
commit; tools/check_vendored_safety_kit.py in that repo can verify the
copy is still byte-identical. Dependency ordering: sourceos-spec#276 is
open, not merged — this PR's PROVENANCE.txt pin is provisional (points at
the PR branch head, which GitHub still serves) and should be updated to the
eventual merge commit once #276 lands. Not blocking this PR's merge on
that; noting the dependency explicitly per the vendoring contract in #276.

Also fixed in the same commit: vite.config.ts's vitest include never
matched src/utils/**, so npm test was silently never running
urlSafe.test.ts's 12 cases. Full suite after both changes: 124 files /
739 tests pass.

See also SocioProphet/socioprophet chore/vendor-estate-safety-kit
(app-vue's equivalent conversion).

Michael Heller added 3 commits August 3, 2026 20:07
…SS 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.
…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.
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.
@mdheller
mdheller force-pushed the fix/boardtable-scheme-allowlist branch from bb148f0 to 701396c Compare August 4, 2026 00:08
…rge 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.
@mdheller
mdheller enabled auto-merge (squash) August 4, 2026 00:32
mdheller added a commit 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
mdheller merged commit 36ca9fd into master Aug 4, 2026
14 checks passed
@mdheller
mdheller deleted the fix/boardtable-scheme-allowlist branch August 4, 2026 00:35
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