@re-cinq/bowman-ui is a presentational React component library. Its attack
surface is the customer's browser, not a server: the components render
model-authored markdown into a customer-facing chat, hold no credential, and
fetch nothing. This policy describes how to report a vulnerability, what to
expect after you do, and which defects are and are not bowman-ui's.
Do not open a public GitHub issue for a vulnerability. A public report tells an attacker before it tells a maintainer.
Report privately to security@re-cinq.com (the org-wide intake settled in
KU-24; the same address handles @re-cinq/hal-engine, so a reporter never has
to guess which package owns the bug). If you have a minimal reproduction, a
model-authored markdown string that renders an unexpected anchor, image, or
script is worth more than a prose description.
We acknowledge a report within 48 hours. If you have evidence of active
exploitation, put ACTIVE EXPLOITATION in the subject line; those we aim to
acknowledge within 24 hours and triage ahead of the queue.
We coordinate disclosure: we ask that you give us a reasonable window to ship a fix before publishing details, and we will credit you in the release notes unless you ask us not to.
A fix ships as a new npm version of @re-cinq/bowman-ui and reaches an OLT
customer only when support-agent bumps the dependency and redeploys (see
107-support-agent-security-advisory-route); this package publishes no runtime
of its own.
| Version | Supported |
|---|---|
| 0.x | Yes |
The package is pre-1.0 and has no maintenance branch: the latest 0.x published
to npm is the supported line, and a fix lands there rather than as a backport.
The markdown rendering path is the primary attack surface. ChatMessage renders
model-authored content through react-markdown 10 with remark-gfm 4, so the
reports this package will actually receive are XSS, sanitizer bypass, and
markdown-pipeline dependency advisories. A report is measured against the
existing controls, which are the security baseline a bypass has to beat:
- The scheme allowlist in
src/markdown/urlPolicy.ts.defaultMarkdownPolicyis a frozen policy allowing only thehttps,mailto, andtelschemes, with no relative URLs (protocol-relative//hostand authority-lesshttps:/api/logoutincluded) and no images.createUrlTransformreplaces react-markdown's default URL filter. The value arrives from micromark already percent-encoded — an entity-encoded tab such asjava	script:becomesjava%09script:before this function sees it — and the comparison never decodes it, so the encoded-tab trick never matches the allowlist. A rejected URL renders its link text in a<span>, never an anchor. - The
rel/targetpolicy. Every rendered anchor carriesrel="noopener noreferrer", and_blanklinks open in a new tab with a visually-hidden "opens in a new tab" notice (src/markdown/components.tsx). - The one injection point for consumer strings is a component's
labelsprop. Labels are the only consumer-controlled string surface that reaches the DOM; alabelsvalue that escapes text rendering is in scope.
In practice, a valid report demonstrates a model-authored markdown string (or a
labels value) that produces an anchor, image, script, or navigation the policy
above is meant to refuse.
- Authorization and data-boundary defects belong to the consuming application.
A
bowman-uicomponent renders exactly theChatEntry[]it is handed and reports exactly what the user did; it holds no credential, makes no access decision, and fetches nothing. Data reaching the wrong customer is an authorization defect in the consuming application that assembled the data — not abowman-uivulnerability. - Hardening the markdown renderer itself beyond the documented policy. The policy above is the control; proposals to change it are feature requests, not vulnerabilities, unless they demonstrate a bypass of the current behavior.
- Findings in a consuming application's own code, infrastructure, or deployment, which this library does not control.