@genn-inc/cluebase-frontend-sdk 0.0.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/README.md +222 -0
- package/dist/_generated/correlation-attribute-keys.d.ts +2 -0
- package/dist/_generated/correlation-attribute-keys.js +33 -0
- package/dist/_generated/identity-pii-policy.d.ts +38 -0
- package/dist/_generated/identity-pii-policy.js +103 -0
- package/dist/_generated/ingest-privacy-tables.d.ts +120 -0
- package/dist/_generated/ingest-privacy-tables.js +890 -0
- package/dist/_generated/otel-resource-keys.d.ts +21 -0
- package/dist/_generated/otel-resource-keys.js +72 -0
- package/dist/_generated/otel-span-conversion.d.ts +48 -0
- package/dist/_generated/otel-span-conversion.js +69 -0
- package/dist/_generated/parameter-snapshot-leaf.d.ts +11 -0
- package/dist/_generated/parameter-snapshot-leaf.js +37 -0
- package/dist/_generated/sdk-runtime-policy.d.ts +7 -0
- package/dist/_generated/sdk-runtime-policy.js +14 -0
- package/dist/_generated/usage-governor-tables.d.ts +15 -0
- package/dist/_generated/usage-governor-tables.js +44 -0
- package/dist/context/browser-storage.d.ts +15 -0
- package/dist/context/browser-storage.js +38 -0
- package/dist/context/environment.d.ts +37 -0
- package/dist/context/environment.js +229 -0
- package/dist/context/identity.d.ts +16 -0
- package/dist/context/identity.js +139 -0
- package/dist/context/organization.d.ts +16 -0
- package/dist/context/organization.js +60 -0
- package/dist/context/session.d.ts +28 -0
- package/dist/context/session.js +130 -0
- package/dist/context/tab.d.ts +22 -0
- package/dist/context/tab.js +117 -0
- package/dist/context/trace.d.ts +76 -0
- package/dist/context/trace.js +163 -0
- package/dist/exporter/batch.d.ts +94 -0
- package/dist/exporter/batch.js +485 -0
- package/dist/exporter/browser-outbox-records.d.ts +159 -0
- package/dist/exporter/browser-outbox-records.js +341 -0
- package/dist/exporter/browser-outbox.d.ts +112 -0
- package/dist/exporter/browser-outbox.js +876 -0
- package/dist/exporter/client.d.ts +92 -0
- package/dist/exporter/client.js +541 -0
- package/dist/exporter/delivery-response-contract.d.ts +103 -0
- package/dist/exporter/delivery-response-contract.js +147 -0
- package/dist/exporter/delivery-response.d.ts +37 -0
- package/dist/exporter/delivery-response.js +151 -0
- package/dist/exporter/diagnostics.d.ts +42 -0
- package/dist/exporter/diagnostics.js +148 -0
- package/dist/exporter/event-id.d.ts +10 -0
- package/dist/exporter/event-id.js +75 -0
- package/dist/exporter/frontend-resource.d.ts +20 -0
- package/dist/exporter/frontend-resource.js +52 -0
- package/dist/exporter/local-minimization.d.ts +29 -0
- package/dist/exporter/local-minimization.js +87 -0
- package/dist/exporter/observation-source-event-exporter.d.ts +69 -0
- package/dist/exporter/observation-source-event-exporter.js +154 -0
- package/dist/exporter/parameter-snapshot.d.ts +14 -0
- package/dist/exporter/parameter-snapshot.js +215 -0
- package/dist/exporter/path-template.d.ts +1 -0
- package/dist/exporter/path-template.js +67 -0
- package/dist/exporter/raw-event-to-source-signal.d.ts +12 -0
- package/dist/exporter/raw-event-to-source-signal.js +297 -0
- package/dist/exporter/retry.d.ts +8 -0
- package/dist/exporter/retry.js +8 -0
- package/dist/exporter/sanitize.d.ts +30 -0
- package/dist/exporter/sanitize.js +177 -0
- package/dist/exporter/schema-evidence.d.ts +20 -0
- package/dist/exporter/schema-evidence.js +232 -0
- package/dist/exporter/source-event.d.ts +99 -0
- package/dist/exporter/source-event.js +13 -0
- package/dist/exporter/span-to-source-signal.d.ts +8 -0
- package/dist/exporter/span-to-source-signal.js +174 -0
- package/dist/exporter/text-fingerprint.d.ts +9 -0
- package/dist/exporter/text-fingerprint.js +16 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +158 -0
- package/dist/instrumentation/action.d.ts +2 -0
- package/dist/instrumentation/action.js +171 -0
- package/dist/instrumentation/cluebase-authored-attributes.d.ts +4 -0
- package/dist/instrumentation/cluebase-authored-attributes.js +32 -0
- package/dist/instrumentation/common.d.ts +73 -0
- package/dist/instrumentation/common.js +90 -0
- package/dist/instrumentation/dom.d.ts +63 -0
- package/dist/instrumentation/dom.js +640 -0
- package/dist/instrumentation/drag.d.ts +2 -0
- package/dist/instrumentation/drag.js +82 -0
- package/dist/instrumentation/error.d.ts +2 -0
- package/dist/instrumentation/error.js +318 -0
- package/dist/instrumentation/form.d.ts +2 -0
- package/dist/instrumentation/form.js +132 -0
- package/dist/instrumentation/input.d.ts +2 -0
- package/dist/instrumentation/input.js +180 -0
- package/dist/instrumentation/location.d.ts +11 -0
- package/dist/instrumentation/location.js +69 -0
- package/dist/instrumentation/naming-evidence.d.ts +20 -0
- package/dist/instrumentation/naming-evidence.js +11 -0
- package/dist/instrumentation/navigation.d.ts +2 -0
- package/dist/instrumentation/navigation.js +144 -0
- package/dist/instrumentation/network-request.d.ts +103 -0
- package/dist/instrumentation/network-request.js +285 -0
- package/dist/instrumentation/network-span-attributes.d.ts +25 -0
- package/dist/instrumentation/network-span-attributes.js +153 -0
- package/dist/instrumentation/network.d.ts +16 -0
- package/dist/instrumentation/network.js +502 -0
- package/dist/instrumentation/page.d.ts +2 -0
- package/dist/instrumentation/page.js +56 -0
- package/dist/instrumentation/performance.d.ts +2 -0
- package/dist/instrumentation/performance.js +365 -0
- package/dist/integrations/next-router.d.ts +16 -0
- package/dist/integrations/next-router.js +18 -0
- package/dist/integrations/react-router.d.ts +7 -0
- package/dist/integrations/react-router.js +61 -0
- package/dist/internal/internal-log.d.ts +23 -0
- package/dist/internal/internal-log.js +47 -0
- package/dist/internal/metrics.d.ts +10 -0
- package/dist/internal/metrics.js +50 -0
- package/dist/provider/_version.d.ts +1 -0
- package/dist/provider/_version.js +3 -0
- package/dist/provider/config.d.ts +12 -0
- package/dist/provider/config.js +177 -0
- package/dist/provider/constants.d.ts +62 -0
- package/dist/provider/constants.js +118 -0
- package/dist/provider/contracts.d.ts +51 -0
- package/dist/provider/contracts.js +46 -0
- package/dist/provider/privacy-policy-store.d.ts +20 -0
- package/dist/provider/privacy-policy-store.js +56 -0
- package/dist/provider/sampling.d.ts +2 -0
- package/dist/provider/sampling.js +17 -0
- package/dist/provider/sdk-event-pipeline.d.ts +60 -0
- package/dist/provider/sdk-event-pipeline.js +478 -0
- package/dist/provider/sdk-input-normalization.d.ts +7 -0
- package/dist/provider/sdk-input-normalization.js +113 -0
- package/dist/provider/sdk-otel-pipeline.d.ts +14 -0
- package/dist/provider/sdk-otel-pipeline.js +148 -0
- package/dist/provider/sdk-runtime-surfaces.d.ts +49 -0
- package/dist/provider/sdk-runtime-surfaces.js +216 -0
- package/dist/provider/sdk.d.ts +2 -0
- package/dist/provider/sdk.js +614 -0
- package/dist/provider/types.d.ts +323 -0
- package/dist/provider/types.js +1 -0
- package/dist/provider/usage-governor.d.ts +7 -0
- package/dist/provider/usage-governor.js +116 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# @genn-inc/cluebase-frontend-sdk
|
|
2
|
+
|
|
3
|
+
Cluebase browser ingest SDK.
|
|
4
|
+
|
|
5
|
+
## Minimal integration
|
|
6
|
+
|
|
7
|
+
After `pnpm add @genn-inc/cluebase-frontend-sdk`, the intended Next.js integration is:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import cluebase from "@genn-inc/cluebase-frontend-sdk";
|
|
11
|
+
|
|
12
|
+
cluebase.init({
|
|
13
|
+
endpoint: process.env.NEXT_PUBLIC_CLUEBASE_API_BASE_URL!,
|
|
14
|
+
projectKey: process.env.NEXT_PUBLIC_CLUEBASE_PROJECT_KEY!,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
cluebase.identify(user.id, { name: user.name });
|
|
18
|
+
cluebase.group("organization", organization.id, { name: organization.name });
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For Vite, use `import.meta.env.VITE_CLUEBASE_API_BASE_URL` and
|
|
22
|
+
`import.meta.env.VITE_CLUEBASE_PROJECT_KEY`. For React apps that use the
|
|
23
|
+
Create React App env contract, use `process.env.REACT_APP_CLUEBASE_API_BASE_URL`
|
|
24
|
+
and `process.env.REACT_APP_CLUEBASE_PROJECT_KEY`. Other frontend frameworks should
|
|
25
|
+
use the public env names generated by Cluebase setup.
|
|
26
|
+
|
|
27
|
+
`projectKey` is the public ingest key. The API resolves authoritative
|
|
28
|
+
`tenantId`, `projectId`, and dev/prod `ProjectService` from that key. `endpoint`
|
|
29
|
+
is the Cluebase API base URL. The SDK appends `/api/v1/ingest/browser-tokens` and
|
|
30
|
+
the canonical browser observation batch path internally.
|
|
31
|
+
|
|
32
|
+
## Responsibility boundary
|
|
33
|
+
|
|
34
|
+
- SDK captures browser signals and applies local minimization where the signal is obviously high-risk.
|
|
35
|
+
- API ingest privacy gate is authoritative for final allow/deny/unmask policy.
|
|
36
|
+
- Worker-side sanitize is defense-in-depth only.
|
|
37
|
+
- SDK sends `projectKey`, `schemaVersion`, `sdkType`, `sdkVersion`, stable keys, and minimized capture payloads.
|
|
38
|
+
- SDK does **not** authoritatively stamp `tenant_id`, `project_id`, `environment_id`, or final raw-ingest request/response payloads.
|
|
39
|
+
|
|
40
|
+
The frontend SDK does **not** own final raw-ingest allowlist decisions.
|
|
41
|
+
|
|
42
|
+
## Default privacy/minimization behavior
|
|
43
|
+
|
|
44
|
+
The frontend SDK avoids shipping obvious high-risk plaintext and keeps the MVP
|
|
45
|
+
default event surface intentionally narrow.
|
|
46
|
+
|
|
47
|
+
- `input_change.value` -> local value metadata only
|
|
48
|
+
- `selection_text` -> metadata only
|
|
49
|
+
- raw selector/path -> never serialized; stable key only
|
|
50
|
+
|
|
51
|
+
Current MVP defaults:
|
|
52
|
+
|
|
53
|
+
- successful network emits one `request_finished`
|
|
54
|
+
- failed network emits one `request_failed`
|
|
55
|
+
- standard action capture emits `element_clicked`, `form_submitted`,
|
|
56
|
+
`input_committed`, `toggle_changed`, `selection_committed`,
|
|
57
|
+
`file_selected`, and `drag_drop_completed`
|
|
58
|
+
- normal batching flushes when the queued payload reaches the 48KB send
|
|
59
|
+
threshold, every 180 seconds by default, or when the page is
|
|
60
|
+
leaving
|
|
61
|
+
- event type does not change the delivery rule in MVP
|
|
62
|
+
|
|
63
|
+
Stable key precedence:
|
|
64
|
+
|
|
65
|
+
1. `data-testid`
|
|
66
|
+
2. `data-qa`
|
|
67
|
+
3. safe `name`
|
|
68
|
+
4. safe `aria-label`
|
|
69
|
+
5. structural fallback
|
|
70
|
+
|
|
71
|
+
Cluebase-specific DOM attributes are not part of the MVP stable-key contract.
|
|
72
|
+
Captured stable keys are best-effort evidence, not authoritative business
|
|
73
|
+
meaning.
|
|
74
|
+
|
|
75
|
+
## Sampling and cost guards
|
|
76
|
+
|
|
77
|
+
- `sampling.sessionSampleRate` controls whether a session emits normal capture
|
|
78
|
+
- oversized events degrade in stages before final drop:
|
|
79
|
+
- no raw payload body
|
|
80
|
+
- metadata/schema only
|
|
81
|
+
- shell only
|
|
82
|
+
- oversized batches stay below the configured payload hard max
|
|
83
|
+
|
|
84
|
+
## Flush reliability
|
|
85
|
+
|
|
86
|
+
The SDK flushes on:
|
|
87
|
+
|
|
88
|
+
- `visibilitychange` -> `hidden`
|
|
89
|
+
- `pagehide`
|
|
90
|
+
- `beforeunload`
|
|
91
|
+
|
|
92
|
+
The page-leave flush is best effort. The SDK persists unsent source events in
|
|
93
|
+
`localStorage` and removes them only after the ingest API accepts the batch.
|
|
94
|
+
Lifecycle session URLs strip query strings and hashes before they enter the raw
|
|
95
|
+
SDK event payload.
|
|
96
|
+
|
|
97
|
+
## Keepalive flush contract
|
|
98
|
+
|
|
99
|
+
- unload / hidden flush uses authenticated `fetch` with the `keepalive` hint
|
|
100
|
+
- unload flush only sends when a valid browser token is already cached
|
|
101
|
+
- events are not sent through `navigator.sendBeacon`
|
|
102
|
+
|
|
103
|
+
## Degraded event contract
|
|
104
|
+
|
|
105
|
+
Downstream ingest must accept these as normal input:
|
|
106
|
+
|
|
107
|
+
- no raw payload body
|
|
108
|
+
- metadata + schema only
|
|
109
|
+
- shell-only event
|
|
110
|
+
|
|
111
|
+
## Privacy and PII handling
|
|
112
|
+
|
|
113
|
+
The SDK applies a layered privacy model:
|
|
114
|
+
|
|
115
|
+
### 1. Hard-deny: PII / secrets are stripped before transport
|
|
116
|
+
|
|
117
|
+
The SDK strips a built-in set of property keys **before** the event leaves
|
|
118
|
+
the browser. These keys cannot be unmasked by the delivered privacy policy —
|
|
119
|
+
selecting `body.email` in the Cluebase privacy screen does not bypass the
|
|
120
|
+
hard-deny.
|
|
121
|
+
|
|
122
|
+
Hard-deny categories (case- and separator-insensitive — `userEmail`,
|
|
123
|
+
`user-email`, `USER_EMAIL`, `email_address` all match):
|
|
124
|
+
|
|
125
|
+
- Auth credentials: `authorization`, `cookie`, `set-cookie`, `password`,
|
|
126
|
+
`passwd`, `secret`, `token`, `access_token`, `refresh_token`, `session`,
|
|
127
|
+
`session_token`, `api_key`, `apikey`, `private_key`
|
|
128
|
+
- PII categories: `email`, `phone`, `credit_card`, `ssn`
|
|
129
|
+
|
|
130
|
+
This list is a strict superset of the server-side ingest hard-deny
|
|
131
|
+
(`@cluebase/shared` `INGEST_HARD_DENY_KEYS`), enforced by CI parity tests in
|
|
132
|
+
all three SDKs.
|
|
133
|
+
|
|
134
|
+
### 2. Default-masked: arbitrary values are masked unless allowlisted
|
|
135
|
+
|
|
136
|
+
Properties that pass the hard-deny gate are still **masked by default**.
|
|
137
|
+
The SDK emits a fingerprint or shape-only digest, not the raw value.
|
|
138
|
+
|
|
139
|
+
To capture a specific field in plaintext, two things must both be true:
|
|
140
|
+
|
|
141
|
+
1. The field path is selected in the Cluebase privacy screen. Cluebase delivers that
|
|
142
|
+
choice to the SDK with the ingest response, and the SDK applies it. This is
|
|
143
|
+
not an `init` option: until a policy arrives, and whenever the delivered
|
|
144
|
+
policy cannot be read, every value stays masked.
|
|
145
|
+
2. The same property key appears in the project's server-side
|
|
146
|
+
project service `observationAnalysisPropertyAllowlist`.
|
|
147
|
+
|
|
148
|
+
If condition 1 holds but condition 2 does not, the value is stored in
|
|
149
|
+
raw observation storage only — `facts_json.analysisProperties.projected` will
|
|
150
|
+
be empty and the field will not appear in Cluebase's analysis UI.
|
|
151
|
+
|
|
152
|
+
### 3. Operational consequence: configure server allowlist before launch
|
|
153
|
+
|
|
154
|
+
Customers who launch without setting `observationAnalysisPropertyAllowlist`
|
|
155
|
+
on their resolved project service will see custom event names land in
|
|
156
|
+
`observation_type` but **no projected properties** in dashboards. The
|
|
157
|
+
raw observation store keeps masked/raw-only evidence for debugging, while
|
|
158
|
+
analysis projection remains empty until the server allowlist includes the
|
|
159
|
+
field. Configure the server allowlist via the Cluebase admin UI or project service
|
|
160
|
+
allowlist API after taking a privacy review of which property names you want to
|
|
161
|
+
project for analytics.
|
|
162
|
+
|
|
163
|
+
## Architecture boundary
|
|
164
|
+
|
|
165
|
+
The frontend SDK is a browser wrapper around capture, privacy minimization,
|
|
166
|
+
correlation propagation, and transport. It sends source-signal envelopes such
|
|
167
|
+
as `sdk_source_signal_observed`; the Cluebase backend ingest core performs final
|
|
168
|
+
classification, unknown diagnostic handling, backend request correlation, and
|
|
169
|
+
raw-ingest normalization.
|
|
170
|
+
|
|
171
|
+
```text
|
|
172
|
+
browser capture / OTel network span
|
|
173
|
+
-> frontend SDK source-signal envelope
|
|
174
|
+
-> /api/v1/ingest/browser
|
|
175
|
+
-> apps/api/src/modules/ingest core
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Rules:
|
|
179
|
+
|
|
180
|
+
- Keep browser code limited to raw facts, correlation context, standard OTel
|
|
181
|
+
attributes, local minimization, and transport.
|
|
182
|
+
- Do not add frontend-only classification, source-hint strategy, semantic DOM
|
|
183
|
+
annotations, or custom fallback behavior.
|
|
184
|
+
- Add classifier behavior in the backend ingest core so frontend-originated
|
|
185
|
+
requests, Node.js, Python, and future SDK wrappers receive the same behavior.
|
|
186
|
+
- Preserve `interaction_id`, `request_span_id`, `request_id`, and `trace_id`
|
|
187
|
+
whenever they are available. User-action linkage is an ingest contract, not a
|
|
188
|
+
display-only field.
|
|
189
|
+
|
|
190
|
+
### Explicit Event Emit: `cluebase.track`
|
|
191
|
+
|
|
192
|
+
When DOM auto-capture cannot infer the operation (e.g. a custom
|
|
193
|
+
keyboard shortcut, an API-only background sync, or a multi-step UI
|
|
194
|
+
flow), the host app may emit an explicit semantic action:
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import cluebase from "@genn-inc/cluebase-frontend-sdk";
|
|
198
|
+
|
|
199
|
+
cluebase.track("report.create", { source: "shortcut", result: "success" });
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`cluebase.track` emits a `custom_emitted` event for product actions that are not
|
|
203
|
+
covered by DOM auto-capture. Keep properties privacy-safe and stable; the worker
|
|
204
|
+
derives higher-level semantics downstream. Event names must match
|
|
205
|
+
`^[A-Za-z0-9_.:-]{1,128}$`; invalid names are dropped with a public warning.
|
|
206
|
+
|
|
207
|
+
### Identity Lifecycle: Organization
|
|
208
|
+
|
|
209
|
+
The frontend SDK exposes the MVP identity lifecycle APIs that the worker uses to
|
|
210
|
+
preserve anonymous → identified user → organization evidence:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
cluebase.identify("user_42", { plan: "pro" });
|
|
214
|
+
cluebase.group("organization", "org_3", { name: "Acme Inc." });
|
|
215
|
+
cluebase.reset();
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
MVP does not expose Cluebase `account` or `workspace` as public group types. If the
|
|
219
|
+
customer product calls its company concept an account, workspace, tenant, or
|
|
220
|
+
team, pass that stable company id as `cluebase.group("organization", ...)`.
|
|
221
|
+
Blank or non-string `identify` user ids and `organization` group ids are
|
|
222
|
+
dropped with a public warning and do not update SDK identity state.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// 自動生成です。**手で編集しないでください。**
|
|
2
|
+
//
|
|
3
|
+
// 正本: packages/cluebase-backend-contract/src/delivery/correlation-attribute-keys.v1.json
|
|
4
|
+
// 出し直す: node scripts/generate-shared-contract-tables.mjs
|
|
5
|
+
//
|
|
6
|
+
// 正本を直したらこの file も一緒に commit してください。CI が
|
|
7
|
+
// `node scripts/generate-shared-contract-tables.mjs --check` で照合し、ずれていれば落とします。
|
|
8
|
+
/** 伏せる処理を通しても残す、相関のための属性名。 */
|
|
9
|
+
export const CLUEBASE_CORRELATION_ATTRIBUTE_KEYS = new Set([
|
|
10
|
+
"cluebase.actor.anonymous_id",
|
|
11
|
+
"cluebase.actor.user_id",
|
|
12
|
+
"cluebase.interaction.id",
|
|
13
|
+
"cluebase.interaction_id",
|
|
14
|
+
"cluebase.organization.id",
|
|
15
|
+
"cluebase.organization_id",
|
|
16
|
+
"cluebase.origin_frontend_interaction.id",
|
|
17
|
+
"cluebase.parent_request_span_id",
|
|
18
|
+
"cluebase.parent_span_id",
|
|
19
|
+
"cluebase.request.id",
|
|
20
|
+
"cluebase.request_id",
|
|
21
|
+
"cluebase.request_span_id",
|
|
22
|
+
"cluebase.session.id",
|
|
23
|
+
"cluebase.session_id",
|
|
24
|
+
"cluebase.span_id",
|
|
25
|
+
"cluebase.subject.organization_id",
|
|
26
|
+
"cluebase.tab.id",
|
|
27
|
+
"cluebase.tab_id",
|
|
28
|
+
"cluebase.trace_id",
|
|
29
|
+
"enduser.id",
|
|
30
|
+
"mcp.client.name",
|
|
31
|
+
"mcp.client.version",
|
|
32
|
+
"mcp.session.id",
|
|
33
|
+
]);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** 見る順序。先に当たった種類を返します。 */
|
|
2
|
+
export declare const PII_CATEGORY_ORDER: readonly ["email", "ssn", "credit_card", "phone"];
|
|
3
|
+
/** 電子メールの形。 */
|
|
4
|
+
export declare const PII_EMAIL_PATTERN = "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}";
|
|
5
|
+
/** 社会保障番号の形。 */
|
|
6
|
+
export declare const PII_SSN_PATTERN = "(?:^|\\D)\\d{3}-\\d{2}-\\d{4}(?:\\D|$)";
|
|
7
|
+
/** 電話とみなす桁数の範囲と、求める区切り。 */
|
|
8
|
+
export declare const PII_PHONE_MIN_DIGITS = 10;
|
|
9
|
+
export declare const PII_PHONE_MAX_DIGITS = 15;
|
|
10
|
+
export declare const PII_PHONE_SEPARATOR_PATTERN = "[+().\\-\\s]";
|
|
11
|
+
/** 支払い用の番号とみなす桁数の範囲。Luhn の検算を必ず通します。 */
|
|
12
|
+
export declare const PII_CREDIT_CARD_MIN_DIGITS = 13;
|
|
13
|
+
export declare const PII_CREDIT_CARD_MAX_DIGITS = 19;
|
|
14
|
+
/** 先に取り除く、汎用の一意識別子の形。 */
|
|
15
|
+
export declare const PII_UUID_PATTERN = "[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
|
|
16
|
+
/** 数字の連なりの候補を切り出す形。 */
|
|
17
|
+
export declare const PII_DIGIT_RUN_PATTERN = "\\+?\\d[0-9().\\-\\s]{5,}\\d";
|
|
18
|
+
/** 伏せた値の形。同じ入力なら配る部品 3 つとも同じ値になります。 */
|
|
19
|
+
export declare const PII_HASH_PREFIX = "cluebase_pii_hashed:";
|
|
20
|
+
export declare const PII_HASH_HEX_LENGTH = 32;
|
|
21
|
+
/** どの面の規則か(読み手が取り違えないため)。 */
|
|
22
|
+
export declare const PII_SURFACE: "text_contains_pii";
|
|
23
|
+
/**
|
|
24
|
+
* Luhn の検算。支払い用の番号らしさを、無作為な数字の並びと分けます。
|
|
25
|
+
*
|
|
26
|
+
* **手順もここから出します。** 桁数と綴りだけを正本化して手順を手書きで残すと、
|
|
27
|
+
* 同じ算法が 4 か所(受付側と配る部品 3 つ)に残ります。実際に残っていました。
|
|
28
|
+
*/
|
|
29
|
+
export declare const passesLuhn: (digits: string) => boolean;
|
|
30
|
+
/**
|
|
31
|
+
* この文字列が個人情報を**含む**か。埋もれた形も拾います。
|
|
32
|
+
*
|
|
33
|
+
* 任意の文字列(自由文を含む項目値)に対して問います。埋もれた形を拾わないと、
|
|
34
|
+
* 「contact alice@example.com for access」のような値が生の個人情報のまま
|
|
35
|
+
* 保管されます。値**そのもの**が識別子かを問いたい面は、もう一方の面
|
|
36
|
+
* (whole_value_is_pii)を使ってください。
|
|
37
|
+
*/
|
|
38
|
+
export declare const detectIdentityPII: (value: string) => string | null;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// 自動生成です。**手で編集しないでください。**
|
|
2
|
+
//
|
|
3
|
+
// 正本: packages/cluebase-shared/src/ingest/identity-pii-policy.v1.json
|
|
4
|
+
// 出し直す: node scripts/generate-shared-contract-tables.mjs
|
|
5
|
+
//
|
|
6
|
+
// 正本を直したらこの file も一緒に commit してください。CI が
|
|
7
|
+
// `node scripts/generate-shared-contract-tables.mjs --check` で照合し、ずれていれば落とします。
|
|
8
|
+
/** 見る順序。先に当たった種類を返します。 */
|
|
9
|
+
export const PII_CATEGORY_ORDER = [
|
|
10
|
+
"email",
|
|
11
|
+
"ssn",
|
|
12
|
+
"credit_card",
|
|
13
|
+
"phone",
|
|
14
|
+
];
|
|
15
|
+
/** 電子メールの形。 */
|
|
16
|
+
export const PII_EMAIL_PATTERN = "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}";
|
|
17
|
+
/** 社会保障番号の形。 */
|
|
18
|
+
export const PII_SSN_PATTERN = "(?:^|\\D)\\d{3}-\\d{2}-\\d{4}(?:\\D|$)";
|
|
19
|
+
/** 電話とみなす桁数の範囲と、求める区切り。 */
|
|
20
|
+
export const PII_PHONE_MIN_DIGITS = 10;
|
|
21
|
+
export const PII_PHONE_MAX_DIGITS = 15;
|
|
22
|
+
export const PII_PHONE_SEPARATOR_PATTERN = "[+().\\-\\s]";
|
|
23
|
+
/** 支払い用の番号とみなす桁数の範囲。Luhn の検算を必ず通します。 */
|
|
24
|
+
export const PII_CREDIT_CARD_MIN_DIGITS = 13;
|
|
25
|
+
export const PII_CREDIT_CARD_MAX_DIGITS = 19;
|
|
26
|
+
/** 先に取り除く、汎用の一意識別子の形。 */
|
|
27
|
+
export const PII_UUID_PATTERN = "[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
|
|
28
|
+
/** 数字の連なりの候補を切り出す形。 */
|
|
29
|
+
export const PII_DIGIT_RUN_PATTERN = "\\+?\\d[0-9().\\-\\s]{5,}\\d";
|
|
30
|
+
/** 伏せた値の形。同じ入力なら配る部品 3 つとも同じ値になります。 */
|
|
31
|
+
export const PII_HASH_PREFIX = "cluebase_pii_hashed:";
|
|
32
|
+
export const PII_HASH_HEX_LENGTH = 32;
|
|
33
|
+
/** どの面の規則か(読み手が取り違えないため)。 */
|
|
34
|
+
export const PII_SURFACE = "text_contains_pii";
|
|
35
|
+
/**
|
|
36
|
+
* Luhn の検算。支払い用の番号らしさを、無作為な数字の並びと分けます。
|
|
37
|
+
*
|
|
38
|
+
* **手順もここから出します。** 桁数と綴りだけを正本化して手順を手書きで残すと、
|
|
39
|
+
* 同じ算法が 4 か所(受付側と配る部品 3 つ)に残ります。実際に残っていました。
|
|
40
|
+
*/
|
|
41
|
+
export const passesLuhn = (digits) => {
|
|
42
|
+
let sum = 0;
|
|
43
|
+
let doubleDigit = false;
|
|
44
|
+
for (let index = digits.length - 1; index >= 0; index -= 1) {
|
|
45
|
+
let digit = Number(digits[index]);
|
|
46
|
+
if (doubleDigit) {
|
|
47
|
+
digit *= 2;
|
|
48
|
+
if (digit > 9)
|
|
49
|
+
digit -= 9;
|
|
50
|
+
}
|
|
51
|
+
sum += digit;
|
|
52
|
+
doubleDigit = !doubleDigit;
|
|
53
|
+
}
|
|
54
|
+
return sum % 10 === 0;
|
|
55
|
+
};
|
|
56
|
+
const EMAIL_RE = new RegExp(PII_EMAIL_PATTERN);
|
|
57
|
+
const SSN_RE = new RegExp(PII_SSN_PATTERN);
|
|
58
|
+
const UUID_RE = new RegExp(PII_UUID_PATTERN, "gi");
|
|
59
|
+
const DIGIT_RUN_RE = new RegExp(PII_DIGIT_RUN_PATTERN, "g");
|
|
60
|
+
const PHONE_SEPARATOR_RE = new RegExp(PII_PHONE_SEPARATOR_PATTERN);
|
|
61
|
+
/** 値の全体を見て決まる種類。 */
|
|
62
|
+
const WHOLE_VALUE_MATCHERS = {
|
|
63
|
+
email: EMAIL_RE,
|
|
64
|
+
ssn: SSN_RE,
|
|
65
|
+
};
|
|
66
|
+
/** 数字の連なりごとに決まる種類。 */
|
|
67
|
+
const DIGIT_RUN_MATCHERS = {
|
|
68
|
+
credit_card: (_candidate, digits) => digits.length >= PII_CREDIT_CARD_MIN_DIGITS &&
|
|
69
|
+
digits.length <= PII_CREDIT_CARD_MAX_DIGITS &&
|
|
70
|
+
passesLuhn(digits),
|
|
71
|
+
phone: (candidate, digits) => digits.length >= PII_PHONE_MIN_DIGITS &&
|
|
72
|
+
digits.length <= PII_PHONE_MAX_DIGITS &&
|
|
73
|
+
PHONE_SEPARATOR_RE.test(candidate),
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* この文字列が個人情報を**含む**か。埋もれた形も拾います。
|
|
77
|
+
*
|
|
78
|
+
* 任意の文字列(自由文を含む項目値)に対して問います。埋もれた形を拾わないと、
|
|
79
|
+
* 「contact alice@example.com for access」のような値が生の個人情報のまま
|
|
80
|
+
* 保管されます。値**そのもの**が識別子かを問いたい面は、もう一方の面
|
|
81
|
+
* (whole_value_is_pii)を使ってください。
|
|
82
|
+
*/
|
|
83
|
+
export const detectIdentityPII = (value) => {
|
|
84
|
+
if (typeof value !== "string" || value.length === 0)
|
|
85
|
+
return null;
|
|
86
|
+
const trimmed = value.trim();
|
|
87
|
+
// 見る順序は正本が決めます。ここには書きません。
|
|
88
|
+
for (const category of PII_CATEGORY_ORDER) {
|
|
89
|
+
if (WHOLE_VALUE_MATCHERS[category]?.test(trimmed))
|
|
90
|
+
return category;
|
|
91
|
+
}
|
|
92
|
+
// 汎用の一意識別子を先に取り除きます。取り除かないと、区切り付きの 16 進の
|
|
93
|
+
// 並びが電話の候補に見えます。
|
|
94
|
+
const withoutUuids = trimmed.replace(UUID_RE, " ");
|
|
95
|
+
for (const candidate of withoutUuids.match(DIGIT_RUN_RE) ?? []) {
|
|
96
|
+
const digits = candidate.replace(/[^0-9]/g, "");
|
|
97
|
+
for (const category of PII_CATEGORY_ORDER) {
|
|
98
|
+
if (DIGIT_RUN_MATCHERS[category]?.(candidate, digits))
|
|
99
|
+
return category;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/** 受付側が、どの許可設定によらず必ず落とす項目名。 */
|
|
2
|
+
export declare const INGEST_HARD_DENY_KEYS: readonly string[];
|
|
3
|
+
/**
|
|
4
|
+
* 配る部品が送り出す前に落とす項目名。
|
|
5
|
+
*
|
|
6
|
+
* 必ず {@link INGEST_HARD_DENY_KEYS} を含みます(同じ正本の配列を先頭に
|
|
7
|
+
* 並べて出しているため、包含関係は**構造で保証**されます。試験の主張ではありません)。
|
|
8
|
+
*/
|
|
9
|
+
export declare const SDK_ALWAYS_DENIED_KEYS: readonly string[];
|
|
10
|
+
/**
|
|
11
|
+
* 受付側が**計測の属性**(OpenTelemetry の span 属性)を畳む時に落とす項目名。
|
|
12
|
+
*
|
|
13
|
+
* この面は顧客の任意の属性が丸ごと来るので、本文・問い合わせ文・見出しの類を
|
|
14
|
+
* 面ごと落とします。広すぎて利用者の項目名の面へは広げられないため、面ごとの
|
|
15
|
+
* 追加として持ちます。{@link INGEST_HARD_DENY_KEYS} を必ず含みます(同じ正本の
|
|
16
|
+
* 配列を先頭に並べて出しているため、包含関係は**構造で保証**されます)。
|
|
17
|
+
*/
|
|
18
|
+
export declare const INGEST_SIGNAL_DENIED_KEYS: readonly string[];
|
|
19
|
+
/**
|
|
20
|
+
* 自分たちが出す**追跡情報(trace)の属性**で落とす項目名。
|
|
21
|
+
*
|
|
22
|
+
* この面は開発者が書いた属性がそのまま外へ出るので、広く落として困りません。
|
|
23
|
+
* `session` はこの面だけです。取り込みの面へ広げると、相関に使う
|
|
24
|
+
* `cluebase.session_id` まで落ちます。**面ごとに違うのは落とす語の一覧だけで、
|
|
25
|
+
* 突き合わせ方は 1 つ**です。
|
|
26
|
+
*/
|
|
27
|
+
export declare const INGEST_TRACE_DENIED_KEYS: readonly string[];
|
|
28
|
+
/**
|
|
29
|
+
* 見た目が同じ別の文字を ASCII へ畳む表。
|
|
30
|
+
*
|
|
31
|
+
* NFKC はこれらを畳みません(互換分解ではなく別の文字だからです)。畳まないと
|
|
32
|
+
* \`τօκen\` のような綴りが \`token\` に一致せず、資格情報が伏せられないまま
|
|
33
|
+
* 送出されます。
|
|
34
|
+
*
|
|
35
|
+
* **畳んだ先はすべて小文字です。これは正しい形です。** Unicode の規格(UTS #39)は、
|
|
36
|
+
* 見た目が同じ文字を畳んだ結果について「大文字小文字を保つ形ではない」「項目名の
|
|
37
|
+
* 正規化として使ってはならない」と明記しています。したがって**畳んだ後の大文字小文字を
|
|
38
|
+
* 当てにする突き合わせを書いてはいけません**(下の突き合わせ用の形は、大文字小文字も
|
|
39
|
+
* 区切りも捨てます)。
|
|
40
|
+
*/
|
|
41
|
+
export declare const ASCII_CONFUSABLES_FOLD: Readonly<Record<string, string>>;
|
|
42
|
+
export declare const foldAsciiConfusables: (value: string) => string;
|
|
43
|
+
/**
|
|
44
|
+
* 項目名を、区切りの見える形にします。
|
|
45
|
+
*
|
|
46
|
+
* \`userEmail\` / \`user_email\` / \`user-email\` / \`USER_EMAIL\` / \`User.Email\`
|
|
47
|
+
* はすべて \`user_email\` になります。**この形は「経路っぽい項目名か」の判定に使います。**
|
|
48
|
+
* 落とすかどうかの突き合わせには使いません(下の {@link toPrivacyMatchKey} を使います)。
|
|
49
|
+
*/
|
|
50
|
+
export declare const normalizePrivacyLookupKey: (key: string) => string;
|
|
51
|
+
/**
|
|
52
|
+
* 突き合わせる形。**区切りを落として詰めます。**
|
|
53
|
+
*
|
|
54
|
+
* 区切りを残してはいけません。区切りの位置は**送ってくる側が決められる**からです。
|
|
55
|
+
* 上の形は大文字の位置から区切りを補いますが、大文字を「見た目が同じ小文字の別文字」
|
|
56
|
+
* に替えられると、その区切りが消えます(\`accessтoken\` の \`т\` はキリル文字の小文字)。
|
|
57
|
+
* ASCII だけでも \`accesstoken\` \`ACCESSTOKEN\` のように区切りを 1 つも書かない綴りが
|
|
58
|
+
* 普通にあり、これらは**同形異字を使わなくても**素通りしていました。
|
|
59
|
+
*
|
|
60
|
+
* したがって**区切りに頼らない形にしてから突き合わせます。**
|
|
61
|
+
*/
|
|
62
|
+
export declare const toPrivacyMatchKey: (key: string) => string;
|
|
63
|
+
/**
|
|
64
|
+
* 落とす語を含むが、落としてはいけないと決めた項目名。
|
|
65
|
+
*
|
|
66
|
+
* 部分一致で落とす方式は必ず巻き添えを出すので、業界の既定は逃し口を
|
|
67
|
+
* **完全一致**の一覧として持つことです(Sentry の Safe Fields も完全一致)。
|
|
68
|
+
* 中身は「トークンの個数」だけです。資格情報ではなく数量で、生成 AI の費用の
|
|
69
|
+
* 分析に要ります。
|
|
70
|
+
*/
|
|
71
|
+
export declare const INGEST_DENY_EXEMPT_KEYS: readonly string[];
|
|
72
|
+
/** 落とす項目名の一覧を、突き合わせ用の集合にします。空になるものは捨てます。 */
|
|
73
|
+
export declare const toDeniedLookupKeySet: (deniedKeys: readonly string[]) => Set<string>;
|
|
74
|
+
/**
|
|
75
|
+
* 詰めた形への部分一致で突き合わせる、落とす語の最短の長さ。
|
|
76
|
+
*
|
|
77
|
+
* これ未満の語は、区切りの塊の**端に接している**時だけ落とします。
|
|
78
|
+
*/
|
|
79
|
+
export declare const PRIVACY_SUBSTRING_MATCH_MIN_LENGTH = 4;
|
|
80
|
+
/** 既に伏せてある値の綴り(そのまま一致する分)。 */
|
|
81
|
+
export declare const MASKED_VALUE_EXACT: readonly string[];
|
|
82
|
+
/** 既に伏せてある値の綴り(先頭が一致する分)。後ろは指紋です。 */
|
|
83
|
+
export declare const MASKED_VALUE_PREFIXES: readonly string[];
|
|
84
|
+
/**
|
|
85
|
+
* 連絡先(メール)の形。**指紋にする前に小文字へ畳んでよいかだけを決めます。**
|
|
86
|
+
*
|
|
87
|
+
* 合えば前後の空白を落として小文字にしてから指紋にし、合わなければ元の値のまま
|
|
88
|
+
* 指紋にします。**両側で同じ規則でなければ、同じ人の同じメールが経路ごとに別の
|
|
89
|
+
* 指紋になり、突き合わせが 0 件で返ります。**
|
|
90
|
+
*/
|
|
91
|
+
export declare const CONTACT_EMAIL_SHAPE = "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$";
|
|
92
|
+
/** その値は、指紋にする前に小文字へ畳んでよい形か。 */
|
|
93
|
+
export declare const isContactEmailShaped: (value: string) => boolean;
|
|
94
|
+
/**
|
|
95
|
+
* この項目名を落とすか。**落とす語の長さで、突き合わせ方を分けます。**
|
|
96
|
+
*
|
|
97
|
+
* | 落とす語 | 突き合わせ | 例 |
|
|
98
|
+
* |---|---|---|
|
|
99
|
+
* | 4 文字以上 | 詰めた形への**部分一致** | `token` が `accesstoken` `ACCESSTOKEN` `myAccessToken` `accessтoken` に当たる |
|
|
100
|
+
* | 4 文字未満 | 区切りの塊の**端に接する**か、塊を繋いだ形と一致 | `ssn` が `user_ssn` `ssnLast4` `userssn` `s.s.n` に当たり、`class_snapshot` には当たらない |
|
|
101
|
+
*
|
|
102
|
+
* **部分一致は業界の既定ですが、3 文字の頭字語に当てると顧客の正当な項目名を
|
|
103
|
+
* 無言で捨てます。** 実測(2026-08-14): 辞書由来の項目名 160 万件のうち
|
|
104
|
+
* **50,041 件**が誤って落ち、`class_snapshot`(`classsnapshot` の中の `ssn`)・
|
|
105
|
+
* `boss_snapshot`・`cross_snapshot`・`business_snapshot`・`press_note`・
|
|
106
|
+
* `adobe_id`(`adobe` の中の `dob`)が消えていました。
|
|
107
|
+
*
|
|
108
|
+
* **業界が同じことをしていない、とは書けません。** Rails の
|
|
109
|
+
* `ActiveSupport::ParameterFilter` は既定で `ssn` `cvv` `cvc` を持ち、部分一致で
|
|
110
|
+
* 当てています。Rails で許されるのは対象が**ログ出力**だけで、誤爆しても
|
|
111
|
+
* `[FILTERED]` と出るだけで元データが残るからです。**こちらは取り込みで項目
|
|
112
|
+
* そのものを捨てるので復元できません。**釣り合いが違うので、Rails の許容は
|
|
113
|
+
* こちらへ持ち込めません。
|
|
114
|
+
*
|
|
115
|
+
* **両方は満たせません。** 部分一致をやめた分だけ、詰めた綴り(`xssnvalue`)は
|
|
116
|
+
* 通ります。攻撃綴り 11,627 件での素通りは、この案で 560 件です(塊とちょうど
|
|
117
|
+
* 一致させる案は 984 件)。この案は**現行が落とすものしか落とさない**ので、
|
|
118
|
+
* 誤爆を新たに増やしません。
|
|
119
|
+
*/
|
|
120
|
+
export declare const isDeniedLookupKey: (key: string, deniedSet: ReadonlySet<string>) => boolean;
|