@mushi-mushi/web 0.5.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +4 -0
- package/README.md +169 -5
- package/SECURITY.md +74 -0
- package/dist/index.cjs +1123 -152
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -7
- package/dist/index.d.ts +79 -7
- package/dist/index.js +1124 -154
- package/dist/index.js.map +1 -1
- package/dist/test-utils.cjs +17 -0
- package/dist/test-utils.cjs.map +1 -1
- package/dist/test-utils.d.cts +21 -2
- package/dist/test-utils.d.ts +21 -2
- package/dist/test-utils.js +15 -1
- package/dist/test-utils.js.map +1 -1
- package/package.json +4 -4
package/CONTRIBUTING.md
CHANGED
|
@@ -33,6 +33,10 @@ pnpm lint # ESLint
|
|
|
33
33
|
pnpm format # Prettier
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
Ad-hoc screenshots captured during UI reviews can live temporarily at the repo
|
|
37
|
+
root, but root-level `*.png` files are intentionally ignored. Canonical
|
|
38
|
+
screenshots that should be versioned belong under `docs/screenshots/`.
|
|
39
|
+
|
|
36
40
|
### Working on a single package
|
|
37
41
|
|
|
38
42
|
```bash
|
package/README.md
CHANGED
|
@@ -17,8 +17,18 @@ Browser SDK for Mushi Mushi — embeddable bug reporting widget with Shadow DOM
|
|
|
17
17
|
- On-device pre-filter (blocks spam before server submission)
|
|
18
18
|
- Client-side rate limiting (token bucket self-throttle)
|
|
19
19
|
- Light/dark theme with auto-detection (`prefers-color-scheme`)
|
|
20
|
+
- **Trigger modes** (0.6+) — `auto` / `edge-tab` / `attach` (bring-your-own-button) / `manual` / `hidden`, plus `smartHide`, `hideOnSelector`, `hideOnRoutes`, configurable `inset` and `respectSafeArea`
|
|
21
|
+
- **Runtime trigger APIs** — `Mushi.show()`, `Mushi.hide()`, `Mushi.attachTo(selector)`, `Mushi.setTrigger(mode)`, `Mushi.openWith(category)`
|
|
22
|
+
- **Widget anchor** (0.9+) — `widget.anchor` accepts raw CSS (including `var()` and `env()`) so the launcher honours your app shell's tab bars, docks, mini-players, and cookie banners without Shadow-DOM patching
|
|
23
|
+
- **Presets** (0.9+) — `preset: 'production-calm' | 'beta-loud' | 'internal-debug' | 'manual-only'` flips a coherent bundle of widget / capture / proactive defaults so prod apps stay quiet and internal builds stay loud
|
|
20
24
|
- **Proactive triggers** — rage click, long task, API cascade failure detection
|
|
21
25
|
- **Report fatigue prevention** — session limits, cooldowns, permanent suppression
|
|
26
|
+
- **Privacy controls** (0.9.1+) — `privacy.maskSelectors`, `privacy.blockSelectors`, `privacy.allowUserRemoveScreenshot` for selector-level screenshot redaction and a one-tap "Remove screenshot" button in the panel
|
|
27
|
+
- **Repro timeline** (0.10+) — auto-captures route changes, clicks, and SDK lifecycle into a normalised `MushiReport.timeline`; pair with `Mushi.setScreen({ name, route, feature })` for screen-level grouping in the admin
|
|
28
|
+
- **Two-way replies** (0.11+) — the panel ships a "Your reports" view that polls comments authored by the dev team and lets the reporter reply, all signed with HMAC against the public API key (no auth user required)
|
|
29
|
+
- **SDK identity & freshness** (0.8+) — every report ships `sdkPackage` + `sdkVersion`; the widget polls `/v1/sdk/latest-version` and surfaces an outdated banner (configurable via `widget.outdatedBanner`)
|
|
30
|
+
- **Self-noise filters** (0.7.1+) — internal Mushi requests are tagged with `X-Mushi-Internal` and excluded from network capture + `apiCascade`; configurable `capture.ignoreUrls` and `proactive.apiCascade.ignoreUrls` for host-app endpoints you also don't want counted
|
|
31
|
+
- **`Mushi.diagnose()`** (0.7.1+) — one-call CSP / runtime-config / capture / widget health check (also runs without an init for pre-install smoke tests)
|
|
22
32
|
- Keyboard-first: `Esc` to close, `⌘/Ctrl + Enter` to submit, focus-trapped panel
|
|
23
33
|
- Honours `prefers-reduced-motion` (animations collapse to instant)
|
|
24
34
|
|
|
@@ -84,6 +94,45 @@ Mushi.init({
|
|
|
84
94
|
});
|
|
85
95
|
```
|
|
86
96
|
|
|
97
|
+
### Bring your own launcher (`trigger: 'attach'`)
|
|
98
|
+
|
|
99
|
+
For mature production apps, prefer hosting the launcher inside your own help
|
|
100
|
+
menu, settings page, or beta banner. Mushi will not inject any UI of its own.
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const mushi = Mushi.init({
|
|
104
|
+
projectId: 'proj_xxx',
|
|
105
|
+
apiKey: 'mushi_xxx',
|
|
106
|
+
widget: {
|
|
107
|
+
trigger: 'attach',
|
|
108
|
+
attachToSelector: '[data-mushi-feedback]',
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
mushi.attachTo('#support-menu-feedback');
|
|
113
|
+
mushi.hide();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Smart-hide (`trigger: 'auto'` with viewport awareness)
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
Mushi.init({
|
|
120
|
+
projectId: 'proj_xxx',
|
|
121
|
+
apiKey: 'mushi_xxx',
|
|
122
|
+
widget: {
|
|
123
|
+
trigger: 'auto',
|
|
124
|
+
smartHide: { onMobile: 'edge-tab', onScroll: 'shrink', onIdleMs: 900 },
|
|
125
|
+
inset: { bottom: 96, right: 20 },
|
|
126
|
+
hideOnSelector: '[data-fullscreen-player]',
|
|
127
|
+
hideOnRoutes: ['/checkout/payment'],
|
|
128
|
+
respectSafeArea: true,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
See [Trigger modes](https://docs.mushimushi.dev/concepts/trigger-modes) for the
|
|
134
|
+
full posture matrix (`auto` / `edge-tab` / `attach` / `manual` / `hidden`).
|
|
135
|
+
|
|
87
136
|
### With Proactive Triggers
|
|
88
137
|
|
|
89
138
|
Proactive triggers are wired into `Mushi.init()` automatically when `config.proactive` is provided. The SDK opens the widget when a trigger fires, gated by fatigue prevention:
|
|
@@ -122,6 +171,118 @@ setupProactiveTriggers({
|
|
|
122
171
|
});
|
|
123
172
|
```
|
|
124
173
|
|
|
174
|
+
### Self-noise filters and CSP diagnostics
|
|
175
|
+
|
|
176
|
+
Out of the box the SDK tags every request it makes with `X-Mushi-Internal` and skips
|
|
177
|
+
those URLs in `capture.network` + `proactive.apiCascade`, so an unconfigured CSP
|
|
178
|
+
or a flaky local Supabase stack can no longer make Mushi report on Mushi:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
Mushi.init({
|
|
182
|
+
projectId: 'proj_xxx',
|
|
183
|
+
apiKey: 'mushi_xxx',
|
|
184
|
+
// 'auto' (default) skips the runtime-config fetch on localhost endpoints —
|
|
185
|
+
// pass `true` to force it everywhere, `false` to disable entirely.
|
|
186
|
+
runtimeConfig: 'auto',
|
|
187
|
+
capture: {
|
|
188
|
+
network: true,
|
|
189
|
+
ignoreUrls: [/\/api\/internal\//, 'https://posthog.example.com'],
|
|
190
|
+
},
|
|
191
|
+
proactive: {
|
|
192
|
+
apiCascade: {
|
|
193
|
+
enabled: true,
|
|
194
|
+
ignoreUrls: ['https://feature-flags.example.com'],
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const health = await Mushi.diagnose();
|
|
200
|
+
// → { apiEndpointReachable, cspAllowsEndpoint, widgetMounted, shadowDomAvailable,
|
|
201
|
+
// dialogSupported, runtimeConfigLoaded, captureScreenshotAvailable,
|
|
202
|
+
// captureNetworkIntercepting, sdkVersion }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`Mushi.diagnose()` works **before** `Mushi.init()` too — call it from a debug
|
|
206
|
+
console or installer wizard to surface CSP / endpoint problems with zero risk
|
|
207
|
+
of accidentally booting the widget.
|
|
208
|
+
|
|
209
|
+
### Presets and widget anchor
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
Mushi.init({
|
|
213
|
+
projectId: 'proj_xxx',
|
|
214
|
+
apiKey: 'mushi_xxx',
|
|
215
|
+
// production-calm = manual trigger, screenshot only on report, no proactive prompts
|
|
216
|
+
// beta-loud = proactive triggers + console + network always on
|
|
217
|
+
// internal-debug = above + verbose debug + always-on screenshot
|
|
218
|
+
// manual-only = trigger only, every proactive surface off
|
|
219
|
+
preset: 'production-calm',
|
|
220
|
+
widget: {
|
|
221
|
+
// Raw CSS strings (including `var()` and `env()`) win over `position` /
|
|
222
|
+
// `inset` so the launcher tracks your app shell's tab bars or mini-player.
|
|
223
|
+
anchor: {
|
|
224
|
+
bottom: 'calc(var(--app-dock-h, 0px) + env(safe-area-inset-bottom))',
|
|
225
|
+
right: 'calc(0.75rem + env(safe-area-inset-right))',
|
|
226
|
+
},
|
|
227
|
+
brandFooter: true,
|
|
228
|
+
outdatedBanner: 'auto',
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Privacy and screenshot redaction
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
Mushi.init({
|
|
237
|
+
projectId: 'proj_xxx',
|
|
238
|
+
apiKey: 'mushi_xxx',
|
|
239
|
+
privacy: {
|
|
240
|
+
maskSelectors: ['[data-private]', 'input', '.thai-answer-draft'],
|
|
241
|
+
blockSelectors: ['[data-payment]', '[data-auth-token]'],
|
|
242
|
+
allowUserRemoveScreenshot: true,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`maskSelectors` paints a solid block over matching elements before serialisation;
|
|
248
|
+
`blockSelectors` removes them entirely. `allowUserRemoveScreenshot` adds a
|
|
249
|
+
"Remove screenshot" affordance next to the attachment chip in the panel, so the
|
|
250
|
+
reporter can yank a screenshot they didn't realise contained sensitive data.
|
|
251
|
+
|
|
252
|
+
### Repro timeline and `setScreen()`
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
const mushi = Mushi.init({ /* ... */ });
|
|
256
|
+
mushi.setScreen({ name: 'Chat', route: '/chat', feature: 'roleplay' });
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The SDK auto-records `route` (initial + `pushState` / `popstate` / `hashchange`),
|
|
260
|
+
`click` (with selector + text snippet), and `screen` events into a 120-entry
|
|
261
|
+
ring buffer. Submissions ship the trail as `MushiReport.timeline` and the admin
|
|
262
|
+
console renders it as a chronological "what happened before the report" card on
|
|
263
|
+
`/reports/:id`.
|
|
264
|
+
|
|
265
|
+
### Two-way replies (Your reports)
|
|
266
|
+
|
|
267
|
+
The widget mounts a "Your reports" tab that lists this reporter's history,
|
|
268
|
+
unread admin replies (with a count badge on the trigger), and a reply input.
|
|
269
|
+
Calls are signed with an HMAC over `projectId.timestamp.sha256(reporterToken)`
|
|
270
|
+
using the public API key as the secret, so no Supabase auth is required.
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
Mushi.init({
|
|
274
|
+
projectId: 'proj_xxx',
|
|
275
|
+
apiKey: 'mushi_xxx',
|
|
276
|
+
widget: { brandFooter: true, outdatedBanner: 'auto' },
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Endpoints (Edge Function): `GET /v1/reporter/reports`,
|
|
281
|
+
`GET /v1/reporter/reports/:id/comments`, `POST /v1/reporter/reports/:id/reply`.
|
|
282
|
+
The DB-side `report_comments_fanout_to_reporter` trigger creates a
|
|
283
|
+
`reporter_notifications` row whenever a `visible_to_reporter` admin comment
|
|
284
|
+
lands, so the unread count stays in sync without polling.
|
|
285
|
+
|
|
125
286
|
## Test utilities (`./test-utils`)
|
|
126
287
|
|
|
127
288
|
Deterministic Playwright / jsdom helpers, published as a separate
|
|
@@ -131,11 +292,14 @@ entry-point so production bundles pay nothing for them:
|
|
|
131
292
|
import { triggerBug, openReport, waitForQueueDrain } from '@mushi-mushi/web/test-utils';
|
|
132
293
|
```
|
|
133
294
|
|
|
134
|
-
| Export
|
|
135
|
-
|
|
136
|
-
| `triggerBug(opts?)`
|
|
137
|
-
| `openReport(cat?)`
|
|
138
|
-
| `
|
|
295
|
+
| Export | Purpose |
|
|
296
|
+
|------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
|
|
297
|
+
| `triggerBug(opts?)` | Submit a report bypassing the widget. Returns the server-assigned id. |
|
|
298
|
+
| `openReport(cat?)` | Open the widget programmatically without submitting. |
|
|
299
|
+
| `openMushiWidget(cat?)` | Alias for `openReport` — Playwright-friendly name for the dogfood contract suite. |
|
|
300
|
+
| `waitForQueueDrain` | Resolve once the offline queue is empty (number remaining at timeout). |
|
|
301
|
+
| `expectMushiReady(opts?)` | Resolve with a `MushiDiagnosticsResult` once the SDK is initialised and reachable. Fails if `apiEndpointReachable === false`. |
|
|
302
|
+
| `expectNoMushiSelfCascade()` | Run an action and assert no internal Mushi request fired the `api_cascade` proactive trigger. Catches CSP / runtime-config self-noise. |
|
|
139
303
|
|
|
140
304
|
Every helper no-ops when `Mushi.getInstance()` returns `null`, so
|
|
141
305
|
conditional-wiring tests (e.g. cloud vs local targets) don't need to
|
package/SECURITY.md
CHANGED
|
@@ -48,3 +48,77 @@ We will acknowledge receipt within 48 hours and aim to release a patch within 7
|
|
|
48
48
|
- **Rotate API keys** regularly via the admin console
|
|
49
49
|
- **Enable SSO** for team projects (Enterprise tier)
|
|
50
50
|
- **Review audit logs** periodically for suspicious activity
|
|
51
|
+
|
|
52
|
+
## Supply-chain hardening (how this package is protected)
|
|
53
|
+
|
|
54
|
+
Mushi Mushi is built and published with the controls below. Consumers can
|
|
55
|
+
verify each control independently — the goal is to make tampering both
|
|
56
|
+
difficult and detectable.
|
|
57
|
+
|
|
58
|
+
### Publish-time controls
|
|
59
|
+
|
|
60
|
+
| Control | What it does | How to verify |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| **npm Trusted Publisher (OIDC)** | Every release is published from `.github/workflows/release.yml` on `master` using a short-lived OIDC token. Long-lived `NPM_TOKEN` is not used for publishing. | `npm view @mushi-mushi/<pkg> --json` shows `"trustedPublisher"` populated for recent versions. |
|
|
63
|
+
| **npm provenance attestations** | Every published tarball ships a [Sigstore provenance attestation](https://docs.npmjs.com/generating-provenance-statements) cryptographically linking the tarball to the exact GitHub Actions run that built it. | `npm audit signatures` (run inside any project that depends on `@mushi-mushi/*`) reports `verified registry signatures` and `verified attestations`. The npm web UI shows a "Built and signed on GitHub Actions" badge on each version. |
|
|
64
|
+
| **Pre-publish workspace-protocol guard** | Aborts the publish if `workspace:*` ranges leaked into the tarball (the bug class behind the v0.1.0 incident). | `scripts/check-workspace-protocol.mjs` runs before `changeset publish` in `pnpm release`. |
|
|
65
|
+
| **Post-publish tarball verification** | Re-downloads each just-published tarball and asserts it doesn't contain `workspace:*`. | See the "Verify published tarballs do not contain workspace:*" step in `release.yml`. |
|
|
66
|
+
| **Post-publish `npm audit signatures`** | Re-installs each published version and validates registry signatures + provenance against npm's transparency log. | See the "Audit signatures of installed dependencies" step in `release.yml`. |
|
|
67
|
+
|
|
68
|
+
### Build-time controls
|
|
69
|
+
|
|
70
|
+
| Control | What it does |
|
|
71
|
+
|---|---|
|
|
72
|
+
| **All third-party GitHub Actions pinned to commit SHAs** | Every `uses:` in every workflow under `.github/workflows/` is pinned to a 40-character commit SHA with a version comment. Floating tags (`@v4`, `@main`) are mutable and were the entry point for the [tj-actions/changed-files compromise (CVE-2025-30066)](https://github.com/step-security/harden-runner#detected-attacks). |
|
|
73
|
+
| **Harden-Runner egress audit on every job** | [step-security/harden-runner](https://github.com/step-security/harden-runner) records every outbound network call, file write, and process spawn on every CI runner. Detects exfiltration attempts in real time — caught the tj-actions, NX, Shai-Hulud, and Axios attacks for other projects. |
|
|
74
|
+
| **OpenSSF Scorecard** | Weekly + on-push score of the repo's security posture (Pinned-Dependencies, Token-Permissions, Branch-Protection, Code-Review, Dangerous-Workflow, Maintained, SAST, Security-Policy, Signed-Releases, Vulnerabilities). Public results at [scorecard.dev](https://scorecard.dev/viewer/?uri=github.com/kensaurus/mushi-mushi). |
|
|
75
|
+
| **Server-side secret scan (Gitleaks)** | Every PR and every push to `master` runs Gitleaks across the diff / full tree. Belt-and-suspenders to the local pre-commit hook (`scripts/check-no-secrets.mjs`) which can be bypassed with `--no-verify`. |
|
|
76
|
+
| **Local pre-commit secret scanner** | `scripts/check-no-secrets.mjs` runs as a git hook installed by `pnpm install`, blocking commits that look like AWS / Stripe / GitHub / Anthropic / OpenAI / Slack / Supabase keys. |
|
|
77
|
+
| **CodeQL `security-extended`** | Semantic analysis of every TypeScript / JavaScript change finds injection sinks, taint flows, prototype pollution, etc. Runs on every PR, push, and weekly cron. |
|
|
78
|
+
| **Dependency review on PRs** | `actions/dependency-review-action` blocks the PR if it adds or upgrades a dep with a high-severity advisory. |
|
|
79
|
+
| **`pnpm audit --prod --audit-level=high`** | Weekly cron + every push to `master` fails on any high/critical advisory in production deps. |
|
|
80
|
+
|
|
81
|
+
### Install-time controls (protect the project's own dependency graph)
|
|
82
|
+
|
|
83
|
+
| Control | What it does |
|
|
84
|
+
|---|---|
|
|
85
|
+
| **`min-release-age` (npm) / `minimumReleaseAge` (pnpm)** | Refuses to resolve any dep version published less than 7 days ago. The Axios 1.14.1 / 0.30.4 compromise (Mar 2026) was detected and removed within ~5 hours; Shai-Hulud (Sep 2025) within <12 hours — a 7-day cooldown blocks every publicly-disclosed 2025–2026 npm supply-chain attack outright. |
|
|
86
|
+
| **`strictDepBuilds: true`** | Fails the install if any transitive dep tries to run a `postinstall` hook the workspace hasn't pre-approved (`onlyBuiltDependencies` allow-list). |
|
|
87
|
+
| **`blockExoticSubdeps: true`** | Refuses to resolve transitive deps from git URLs, tarball URLs, or filesystem paths — anything that didn't go through the npm registry's signing pipeline. |
|
|
88
|
+
| **Dependabot with cooldown** | Routine dep upgrades wait 7 days; security advisories bypass the cooldown automatically. |
|
|
89
|
+
| **`pnpm audit signatures`-style verification** | The release pipeline re-runs `npm audit signatures` against each published version after the publish, with `--audit-level=high`. |
|
|
90
|
+
|
|
91
|
+
### Verifying a Mushi Mushi tarball before installing
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# 1. Check provenance attestation matches the public GitHub Actions run
|
|
95
|
+
npm view @mushi-mushi/core --json | jq '.signatures, .dist'
|
|
96
|
+
|
|
97
|
+
# 2. Inside your own project after install
|
|
98
|
+
npm audit signatures
|
|
99
|
+
|
|
100
|
+
# Expected: every @mushi-mushi/* package reports
|
|
101
|
+
# "verified registry signature"
|
|
102
|
+
# "verified attestation"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
If `npm audit signatures` reports any `@mushi-mushi/*` package as unsigned
|
|
106
|
+
or with an invalid attestation, **stop the install and email
|
|
107
|
+
kensaurus@gmail.com immediately** — that's the symptom of either a
|
|
108
|
+
registry compromise or a tampered tarball, and we want to know within
|
|
109
|
+
hours, not days.
|
|
110
|
+
|
|
111
|
+
### What this hardening does NOT cover
|
|
112
|
+
|
|
113
|
+
- **Self-hosted deployments.** Once the package is on your machine, the
|
|
114
|
+
security of your `node_modules`, build pipeline, and runtime is your
|
|
115
|
+
responsibility. The hardening above protects the path from source to
|
|
116
|
+
registry; it cannot protect a tarball after it has been downloaded.
|
|
117
|
+
- **Compromise of `kensaurus@gmail.com`.** A trusted-publisher rule still
|
|
118
|
+
lets the legitimate maintainer publish from any branch they push. If
|
|
119
|
+
you find yourself with admin access to this repo, treat
|
|
120
|
+
`.github/workflows/release.yml` as a tier-0 secret.
|
|
121
|
+
- **First-party bugs.** Provenance proves *who* built the tarball and
|
|
122
|
+
*when*; it does not prove the code is bug-free. CodeQL + tests cover
|
|
123
|
+
that surface, but no automation catches everything — please continue
|
|
124
|
+
to report issues to the address above.
|