@mushi-mushi/core 0.2.1 → 0.3.1
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/CODE_OF_CONDUCT.md +51 -0
- package/CONTRIBUTING.md +122 -0
- package/SECURITY.md +50 -0
- package/dist/index.cjs +171 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -4
- package/dist/index.d.ts +69 -4
- package/dist/index.js +171 -22
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AUTO-SYNCED from repo root by scripts/sync-community-files.mjs.
|
|
3
|
+
Do not edit here — edit the canonical file at the repository root and
|
|
4
|
+
re-run `node scripts/sync-community-files.mjs` (pre-commit hook does this
|
|
5
|
+
automatically).
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Contributor Covenant Code of Conduct
|
|
9
|
+
|
|
10
|
+
## Our Pledge
|
|
11
|
+
|
|
12
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
13
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
14
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
15
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
16
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
17
|
+
identity and orientation.
|
|
18
|
+
|
|
19
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
20
|
+
diverse, inclusive, and healthy community.
|
|
21
|
+
|
|
22
|
+
## Our Standards
|
|
23
|
+
|
|
24
|
+
Examples of behavior that contributes to a positive environment:
|
|
25
|
+
|
|
26
|
+
- Using welcoming and inclusive language
|
|
27
|
+
- Being respectful of differing viewpoints and experiences
|
|
28
|
+
- Gracefully accepting constructive criticism
|
|
29
|
+
- Focusing on what is best for the community
|
|
30
|
+
- Showing empathy towards other community members
|
|
31
|
+
|
|
32
|
+
Examples of unacceptable behavior:
|
|
33
|
+
|
|
34
|
+
- The use of sexualized language or imagery, and sexual attention or advances of any kind
|
|
35
|
+
- Trolling, insulting or derogatory comments, and personal or political attacks
|
|
36
|
+
- Public or private harassment
|
|
37
|
+
- Publishing others' private information without explicit permission
|
|
38
|
+
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
|
39
|
+
|
|
40
|
+
## Enforcement
|
|
41
|
+
|
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
43
|
+
reported to the project team at **security@mushimushi.dev**.
|
|
44
|
+
|
|
45
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
|
46
|
+
|
|
47
|
+
## Attribution
|
|
48
|
+
|
|
49
|
+
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/),
|
|
50
|
+
version 2.1, available at
|
|
51
|
+
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AUTO-SYNCED from repo root by scripts/sync-community-files.mjs.
|
|
3
|
+
Do not edit here — edit the canonical file at the repository root and
|
|
4
|
+
re-run `node scripts/sync-community-files.mjs` (pre-commit hook does this
|
|
5
|
+
automatically).
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Contributing to Mushi Mushi
|
|
9
|
+
|
|
10
|
+
Thanks for wanting to help. Here's everything you need to get started.
|
|
11
|
+
|
|
12
|
+
## Prerequisites
|
|
13
|
+
|
|
14
|
+
- **Node.js >= 22** (see `.node-version`)
|
|
15
|
+
- **pnpm >= 10** — install with `corepack enable`
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
git clone https://github.com/kensaurus/mushi-mushi.git
|
|
21
|
+
cd mushi-mushi
|
|
22
|
+
pnpm install
|
|
23
|
+
pnpm build
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Development
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm dev # Start all dev servers (admin on :6464)
|
|
30
|
+
pnpm test # Run Vitest across all packages
|
|
31
|
+
pnpm typecheck # TypeScript checks
|
|
32
|
+
pnpm lint # ESLint
|
|
33
|
+
pnpm format # Prettier
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Working on a single package
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
cd packages/core
|
|
40
|
+
pnpm dev # Watch mode
|
|
41
|
+
pnpm test # Tests for this package only
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Project Structure
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
packages/
|
|
48
|
+
core/ Types, API client, offline queue (MIT)
|
|
49
|
+
web/ Browser SDK — widget, capture (MIT)
|
|
50
|
+
react/ React bindings (MIT)
|
|
51
|
+
vue/ Vue 3 plugin (MIT)
|
|
52
|
+
svelte/ Svelte SDK (MIT)
|
|
53
|
+
angular/ Angular SDK (MIT)
|
|
54
|
+
react-native/ React Native SDK (MIT)
|
|
55
|
+
cli/ CLI tool (MIT)
|
|
56
|
+
mcp/ MCP server for coding agents (MIT)
|
|
57
|
+
server/ Supabase Edge Functions (BSL)
|
|
58
|
+
agents/ Agentic fix pipeline (BSL)
|
|
59
|
+
verify/ Fix verification (BSL)
|
|
60
|
+
apps/
|
|
61
|
+
admin/ Admin dashboard (React + Tailwind)
|
|
62
|
+
docs/ Documentation site (planned)
|
|
63
|
+
tooling/
|
|
64
|
+
eslint-config/ Shared ESLint flat config
|
|
65
|
+
tsconfig/ Shared TypeScript configs
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Making Changes
|
|
69
|
+
|
|
70
|
+
1. Create a feature branch from `master`
|
|
71
|
+
2. Make your changes
|
|
72
|
+
3. Add tests for new functionality
|
|
73
|
+
4. Run `pnpm typecheck && pnpm lint && pnpm test` to verify
|
|
74
|
+
5. Create a changeset if your change affects published packages:
|
|
75
|
+
```bash
|
|
76
|
+
pnpm changeset
|
|
77
|
+
```
|
|
78
|
+
6. Open a pull request
|
|
79
|
+
|
|
80
|
+
## Changesets
|
|
81
|
+
|
|
82
|
+
We use [Changesets](https://github.com/changesets/changesets) for versioning. If your PR modifies a published package (`core`, `web`, `react`, `vue`, `svelte`, `angular`, `react-native`, `cli`, `mcp`), add a changeset:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pnpm changeset
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Select the affected packages, the semver bump type, and write a summary. The changeset file gets committed with your PR.
|
|
89
|
+
|
|
90
|
+
## Code Style
|
|
91
|
+
|
|
92
|
+
- **TypeScript strict mode** — no `any` unless absolutely necessary
|
|
93
|
+
- **Prettier** formats everything — run `pnpm format` before committing
|
|
94
|
+
- **ESLint** catches bugs — `pnpm lint` must pass
|
|
95
|
+
- **No default exports** in library packages — use named exports
|
|
96
|
+
- **Dual ESM/CJS** builds via tsup for all SDK packages
|
|
97
|
+
|
|
98
|
+
## Commit Messages
|
|
99
|
+
|
|
100
|
+
Use conventional commits:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
feat(core): add batch report submission
|
|
104
|
+
fix(web): prevent widget from opening during screenshot
|
|
105
|
+
docs(react): update provider usage example
|
|
106
|
+
chore: bump dependencies
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Tests
|
|
110
|
+
|
|
111
|
+
- **Framework:** Vitest
|
|
112
|
+
- **Location:** Co-located with source (`src/foo.test.ts`)
|
|
113
|
+
- **Coverage:** Required for `core`, `web`, `react` — encouraged for all packages
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
- SDK packages are MIT — your contributions will be MIT-licensed
|
|
118
|
+
- Server/agents/verify are BSL 1.1 — contributions to those packages fall under BSL
|
|
119
|
+
|
|
120
|
+
## Questions?
|
|
121
|
+
|
|
122
|
+
Open an issue or start a discussion. We're happy to help.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AUTO-SYNCED from repo root by scripts/sync-community-files.mjs.
|
|
3
|
+
Do not edit here — edit the canonical file at the repository root and
|
|
4
|
+
re-run `node scripts/sync-community-files.mjs` (pre-commit hook does this
|
|
5
|
+
automatically).
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Security Policy
|
|
9
|
+
|
|
10
|
+
## Supported Versions
|
|
11
|
+
|
|
12
|
+
| Version | Supported |
|
|
13
|
+
|---------|-----------|
|
|
14
|
+
| 0.x | Yes |
|
|
15
|
+
|
|
16
|
+
## Reporting a Vulnerability
|
|
17
|
+
|
|
18
|
+
If you discover a security vulnerability, please report it responsibly.
|
|
19
|
+
|
|
20
|
+
**Do NOT open a public GitHub issue.**
|
|
21
|
+
|
|
22
|
+
Instead, email: **security@mushimushi.dev**
|
|
23
|
+
|
|
24
|
+
Include:
|
|
25
|
+
- Description of the vulnerability
|
|
26
|
+
- Steps to reproduce
|
|
27
|
+
- Impact assessment
|
|
28
|
+
- Suggested fix (if any)
|
|
29
|
+
|
|
30
|
+
We will acknowledge receipt within 48 hours and aim to release a patch within 7 days for critical issues.
|
|
31
|
+
|
|
32
|
+
## Scope
|
|
33
|
+
|
|
34
|
+
- All `@mushi-mushi/*` npm packages
|
|
35
|
+
- Supabase Edge Functions (server-side)
|
|
36
|
+
- Admin console application
|
|
37
|
+
- CLI tool
|
|
38
|
+
|
|
39
|
+
## Out of Scope
|
|
40
|
+
|
|
41
|
+
- Self-hosted deployments configured by the user
|
|
42
|
+
- Third-party integrations (Jira, Linear, PagerDuty)
|
|
43
|
+
- Vulnerabilities requiring physical access
|
|
44
|
+
|
|
45
|
+
## Security Best Practices for Users
|
|
46
|
+
|
|
47
|
+
- **Never commit your API keys** — use environment variables
|
|
48
|
+
- **Rotate API keys** regularly via the admin console
|
|
49
|
+
- **Enable SSO** for team projects (Enterprise tier)
|
|
50
|
+
- **Review audit logs** periodically for suspicious activity
|
package/dist/index.cjs
CHANGED
|
@@ -350,6 +350,98 @@ var noopLogger = {
|
|
|
350
350
|
}
|
|
351
351
|
};
|
|
352
352
|
|
|
353
|
+
// src/queue-crypto.ts
|
|
354
|
+
var KEY_DB = "mushi-mushi-keyring";
|
|
355
|
+
var KEY_STORE = "keys";
|
|
356
|
+
var KEY_RECORD_ID = "offline-queue/v1";
|
|
357
|
+
var cachedKey = null;
|
|
358
|
+
var cachedKeyPromise = null;
|
|
359
|
+
function hasWebCrypto() {
|
|
360
|
+
return typeof globalThis !== "undefined" && typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.subtle !== "undefined" && typeof indexedDB !== "undefined";
|
|
361
|
+
}
|
|
362
|
+
function openKeyDb() {
|
|
363
|
+
return new Promise((resolve, reject) => {
|
|
364
|
+
const req = indexedDB.open(KEY_DB, 1);
|
|
365
|
+
req.onupgradeneeded = () => {
|
|
366
|
+
const db = req.result;
|
|
367
|
+
if (!db.objectStoreNames.contains(KEY_STORE)) {
|
|
368
|
+
db.createObjectStore(KEY_STORE);
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
req.onsuccess = () => resolve(req.result);
|
|
372
|
+
req.onerror = () => reject(req.error);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
async function loadKey() {
|
|
376
|
+
const db = await openKeyDb();
|
|
377
|
+
return new Promise((resolve, reject) => {
|
|
378
|
+
const tx = db.transaction(KEY_STORE, "readonly");
|
|
379
|
+
const req = tx.objectStore(KEY_STORE).get(KEY_RECORD_ID);
|
|
380
|
+
req.onsuccess = () => resolve(req.result ?? null);
|
|
381
|
+
req.onerror = () => reject(req.error);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
async function storeKey(key) {
|
|
385
|
+
const db = await openKeyDb();
|
|
386
|
+
return new Promise((resolve, reject) => {
|
|
387
|
+
const tx = db.transaction(KEY_STORE, "readwrite");
|
|
388
|
+
tx.objectStore(KEY_STORE).put(key, KEY_RECORD_ID);
|
|
389
|
+
tx.oncomplete = () => resolve();
|
|
390
|
+
tx.onerror = () => reject(tx.error);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
async function getOfflineQueueKey() {
|
|
394
|
+
if (cachedKey) return cachedKey;
|
|
395
|
+
if (cachedKeyPromise) return cachedKeyPromise;
|
|
396
|
+
if (!hasWebCrypto()) {
|
|
397
|
+
throw new Error("Web Crypto + IndexedDB required for offline queue encryption");
|
|
398
|
+
}
|
|
399
|
+
cachedKeyPromise = (async () => {
|
|
400
|
+
const existing = await loadKey();
|
|
401
|
+
if (existing) {
|
|
402
|
+
cachedKey = existing;
|
|
403
|
+
return existing;
|
|
404
|
+
}
|
|
405
|
+
const key = await crypto.subtle.generateKey(
|
|
406
|
+
{ name: "AES-GCM", length: 256 },
|
|
407
|
+
false,
|
|
408
|
+
["encrypt", "decrypt"]
|
|
409
|
+
);
|
|
410
|
+
await storeKey(key);
|
|
411
|
+
cachedKey = key;
|
|
412
|
+
return key;
|
|
413
|
+
})();
|
|
414
|
+
return cachedKeyPromise;
|
|
415
|
+
}
|
|
416
|
+
function bytesToB64(bytes) {
|
|
417
|
+
let s = "";
|
|
418
|
+
for (const b of bytes) s += String.fromCharCode(b);
|
|
419
|
+
return btoa(s);
|
|
420
|
+
}
|
|
421
|
+
function b64ToBytes(s) {
|
|
422
|
+
const bin = atob(s);
|
|
423
|
+
const out = new Uint8Array(new ArrayBuffer(bin.length));
|
|
424
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
425
|
+
return out;
|
|
426
|
+
}
|
|
427
|
+
async function encryptJson(plain) {
|
|
428
|
+
const key = await getOfflineQueueKey();
|
|
429
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
430
|
+
const data = new TextEncoder().encode(JSON.stringify(plain));
|
|
431
|
+
const cipher = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, data));
|
|
432
|
+
return { _mme: 1, iv: bytesToB64(iv), ct: bytesToB64(cipher) };
|
|
433
|
+
}
|
|
434
|
+
function isEncryptedPayload(v) {
|
|
435
|
+
return !!v && typeof v === "object" && v._mme === 1 && typeof v.iv === "string" && typeof v.ct === "string";
|
|
436
|
+
}
|
|
437
|
+
async function decryptJson(payload) {
|
|
438
|
+
const key = await getOfflineQueueKey();
|
|
439
|
+
const iv = b64ToBytes(payload.iv);
|
|
440
|
+
const ct = b64ToBytes(payload.ct);
|
|
441
|
+
const plain = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ct);
|
|
442
|
+
return JSON.parse(new TextDecoder().decode(plain));
|
|
443
|
+
}
|
|
444
|
+
|
|
353
445
|
// src/queue.ts
|
|
354
446
|
var queueLog = createLogger({ scope: "mushi:queue", level: "warn" });
|
|
355
447
|
var DB_NAME = "mushi-mushi";
|
|
@@ -359,9 +451,36 @@ var LS_KEY = "mushi_offline_queue";
|
|
|
359
451
|
var BATCH_SIZE = 10;
|
|
360
452
|
var MAX_BACKOFF_MS = 6e4;
|
|
361
453
|
function createOfflineQueue(config = {}) {
|
|
362
|
-
const { enabled = true, maxQueueSize = 50, syncOnReconnect = true } = config;
|
|
454
|
+
const { enabled = true, maxQueueSize = 50, syncOnReconnect = true, encryptAtRest = true } = config;
|
|
363
455
|
let syncCleanup = null;
|
|
364
456
|
let backendType = null;
|
|
457
|
+
async function wrapForStorage(report) {
|
|
458
|
+
const queuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
459
|
+
if (!encryptAtRest) {
|
|
460
|
+
return { ...report, queuedAt };
|
|
461
|
+
}
|
|
462
|
+
try {
|
|
463
|
+
const payload = await encryptJson(report);
|
|
464
|
+
return { id: report.id, queuedAt, payload };
|
|
465
|
+
} catch (err) {
|
|
466
|
+
queueLog.warn("Offline queue: encryption failed, storing plaintext", { err: String(err) });
|
|
467
|
+
return { ...report, queuedAt };
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
async function unwrapForSend(row) {
|
|
471
|
+
if (isEncryptedRecord(row)) {
|
|
472
|
+
try {
|
|
473
|
+
return await decryptJson(row.payload);
|
|
474
|
+
} catch (err) {
|
|
475
|
+
queueLog.warn("Offline queue: decrypt failed, dropping row", { err: String(err), id: row.id });
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return row;
|
|
480
|
+
}
|
|
481
|
+
function isEncryptedRecord(row) {
|
|
482
|
+
return !!row.payload && isEncryptedPayload(row.payload);
|
|
483
|
+
}
|
|
365
484
|
function detectBackend() {
|
|
366
485
|
if (backendType) return backendType;
|
|
367
486
|
if (typeof indexedDB !== "undefined") {
|
|
@@ -391,9 +510,10 @@ function createOfflineQueue(config = {}) {
|
|
|
391
510
|
}
|
|
392
511
|
async function idbEnqueue(report) {
|
|
393
512
|
const db = await openDb();
|
|
513
|
+
const row = await wrapForStorage(report);
|
|
394
514
|
return new Promise((resolve, reject) => {
|
|
395
515
|
const tx = db.transaction(STORE_NAME, "readwrite");
|
|
396
|
-
tx.objectStore(STORE_NAME).put(
|
|
516
|
+
tx.objectStore(STORE_NAME).put(row);
|
|
397
517
|
tx.oncomplete = () => resolve();
|
|
398
518
|
tx.onerror = () => reject(tx.error);
|
|
399
519
|
});
|
|
@@ -442,20 +562,20 @@ function createOfflineQueue(config = {}) {
|
|
|
442
562
|
return [];
|
|
443
563
|
}
|
|
444
564
|
}
|
|
445
|
-
function lsWrite(
|
|
565
|
+
function lsWrite(rows) {
|
|
446
566
|
try {
|
|
447
|
-
localStorage.setItem(LS_KEY, JSON.stringify(
|
|
567
|
+
localStorage.setItem(LS_KEY, JSON.stringify(rows));
|
|
448
568
|
} catch {
|
|
449
569
|
}
|
|
450
570
|
}
|
|
451
|
-
function lsEnqueue(report) {
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
lsWrite(
|
|
571
|
+
async function lsEnqueue(report) {
|
|
572
|
+
const rows = lsRead();
|
|
573
|
+
rows.push(await wrapForStorage(report));
|
|
574
|
+
lsWrite(rows);
|
|
455
575
|
}
|
|
456
576
|
function lsDelete(id) {
|
|
457
|
-
const
|
|
458
|
-
lsWrite(
|
|
577
|
+
const rows = lsRead().filter((r) => r.id !== id);
|
|
578
|
+
lsWrite(rows);
|
|
459
579
|
}
|
|
460
580
|
async function enqueue(report) {
|
|
461
581
|
if (!enabled) return;
|
|
@@ -474,7 +594,7 @@ function createOfflineQueue(config = {}) {
|
|
|
474
594
|
}
|
|
475
595
|
}
|
|
476
596
|
if (backend === "localstorage" || backendType === "localstorage") {
|
|
477
|
-
lsEnqueue(report);
|
|
597
|
+
await lsEnqueue(report);
|
|
478
598
|
return;
|
|
479
599
|
}
|
|
480
600
|
}
|
|
@@ -486,29 +606,41 @@ function createOfflineQueue(config = {}) {
|
|
|
486
606
|
}
|
|
487
607
|
async function flush(client) {
|
|
488
608
|
if (!enabled) return { sent: 0, failed: 0 };
|
|
489
|
-
let
|
|
609
|
+
let rows;
|
|
490
610
|
const backend = detectBackend();
|
|
491
611
|
if (backend === "indexeddb") {
|
|
492
612
|
try {
|
|
493
|
-
|
|
613
|
+
rows = await idbGetAll();
|
|
494
614
|
} catch {
|
|
495
|
-
|
|
615
|
+
rows = lsRead();
|
|
496
616
|
}
|
|
497
617
|
} else {
|
|
498
|
-
|
|
618
|
+
rows = lsRead();
|
|
499
619
|
}
|
|
500
|
-
const batch =
|
|
620
|
+
const batch = rows.slice(0, BATCH_SIZE);
|
|
501
621
|
let sent = 0;
|
|
502
622
|
let failed = 0;
|
|
503
623
|
for (let i = 0; i < batch.length; i++) {
|
|
504
|
-
const
|
|
624
|
+
const row = batch[i];
|
|
625
|
+
const rowId = row.id;
|
|
626
|
+
const report = await unwrapForSend(row);
|
|
627
|
+
if (!report) {
|
|
628
|
+
try {
|
|
629
|
+
if (backend === "indexeddb") await idbDelete(rowId);
|
|
630
|
+
else lsDelete(rowId);
|
|
631
|
+
} catch {
|
|
632
|
+
lsDelete(rowId);
|
|
633
|
+
}
|
|
634
|
+
failed++;
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
505
637
|
const result = await client.submitReport(report);
|
|
506
638
|
if (result.ok) {
|
|
507
639
|
try {
|
|
508
|
-
if (backend === "indexeddb") await idbDelete(
|
|
509
|
-
else lsDelete(
|
|
640
|
+
if (backend === "indexeddb") await idbDelete(rowId);
|
|
641
|
+
else lsDelete(rowId);
|
|
510
642
|
} catch {
|
|
511
|
-
lsDelete(
|
|
643
|
+
lsDelete(rowId);
|
|
512
644
|
}
|
|
513
645
|
sent++;
|
|
514
646
|
} else {
|
|
@@ -742,16 +874,33 @@ function createRateLimiter(config = {}) {
|
|
|
742
874
|
var ORDERED_PATTERNS = [
|
|
743
875
|
{ key: "ssns", regex: /\b\d{3}-\d{2}-\d{4}\b/g, replacement: "[REDACTED_SSN]" },
|
|
744
876
|
{ key: "creditCards", regex: /\b(?:\d[ -]*){12,18}\d\b/g, replacement: "[REDACTED_CC]" },
|
|
877
|
+
// Vendor secret tokens — mirrors packages/server/.../pii-scrubber.ts exactly.
|
|
878
|
+
{ key: "secretTokens", regex: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g, replacement: "[REDACTED_AWS_KEY]" },
|
|
879
|
+
{ key: "secretTokens", regex: /(?:aws_secret_access_key|secret_access_key)["'\s:=]+[A-Za-z0-9/+=]{40}\b/gi, replacement: "aws_secret_access_key=[REDACTED_AWS_SECRET]" },
|
|
880
|
+
{ key: "secretTokens", regex: /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{24,}\b/g, replacement: "[REDACTED_STRIPE_KEY]" },
|
|
881
|
+
{ key: "secretTokens", regex: /\bpk_(?:live|test)_[A-Za-z0-9]{24,}\b/g, replacement: "[REDACTED_STRIPE_PK]" },
|
|
882
|
+
{ key: "secretTokens", regex: /\bxox[abpor]-[A-Za-z0-9-]{10,}\b/g, replacement: "[REDACTED_SLACK_TOKEN]" },
|
|
883
|
+
{ key: "secretTokens", regex: /\bghp_[A-Za-z0-9]{36}\b/g, replacement: "[REDACTED_GITHUB_PAT]" },
|
|
884
|
+
{ key: "secretTokens", regex: /\bgithub_pat_[A-Za-z0-9_]{80,}\b/g, replacement: "[REDACTED_GITHUB_PAT]" },
|
|
885
|
+
{ key: "secretTokens", regex: /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g, replacement: "[REDACTED_OPENAI_KEY]" },
|
|
886
|
+
{ key: "secretTokens", regex: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g, replacement: "[REDACTED_ANTHROPIC_KEY]" },
|
|
887
|
+
{ key: "secretTokens", regex: /\bAIza[0-9A-Za-z_-]{35}\b/g, replacement: "[REDACTED_GOOGLE_KEY]" },
|
|
888
|
+
{ key: "secretTokens", regex: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, replacement: "[REDACTED_JWT]" },
|
|
745
889
|
{ key: "emails", regex: /\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b/g, replacement: "[REDACTED_EMAIL]" },
|
|
746
890
|
{ key: "phones", regex: /(?:\+\d{1,3}[\s.-])?\(?\d{2,4}\)?[\s.-]\d{3,4}[\s.-]\d{3,4}\b/g, replacement: "[REDACTED_PHONE]" },
|
|
747
|
-
{ key: "ipAddresses", regex: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g, replacement: "[REDACTED_IP]" }
|
|
891
|
+
{ key: "ipAddresses", regex: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g, replacement: "[REDACTED_IP]" },
|
|
892
|
+
{ key: "ipv6", regex: /\b(?:[A-Fa-f0-9]{1,4}:){2,7}[A-Fa-f0-9]{0,4}\b/g, replacement: "[REDACTED_IPV6]" }
|
|
748
893
|
];
|
|
749
894
|
var DEFAULT_CONFIG = {
|
|
750
895
|
emails: true,
|
|
751
896
|
phones: true,
|
|
752
897
|
creditCards: true,
|
|
753
898
|
ssns: true,
|
|
754
|
-
ipAddresses: false
|
|
899
|
+
ipAddresses: false,
|
|
900
|
+
// Secret tokens default ON — if they leak into a bug report there's no
|
|
901
|
+
// good reason to ship them to our servers. Cheaper to scrub client-side.
|
|
902
|
+
secretTokens: true,
|
|
903
|
+
ipv6: false
|
|
755
904
|
};
|
|
756
905
|
function createPiiScrubber(config = {}) {
|
|
757
906
|
const merged = { ...DEFAULT_CONFIG, ...config };
|