@ssheleg/sheleg-dev 0.11.7 → 0.12.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.
Files changed (29) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/package.json +4 -3
  3. package/plugins/sheleg-dev/.claude-plugin/plugin.json +1 -1
  4. package/plugins/sheleg-dev/skills/ad-tracking/SKILL.md +24 -8
  5. package/plugins/sheleg-dev/skills/ad-tracking/references/consent-mode.md +57 -24
  6. package/plugins/sheleg-dev/skills/ad-tracking/references/event-tracking.md +0 -13
  7. package/plugins/sheleg-dev/skills/ad-tracking/references/gtag-api.md +0 -17
  8. package/plugins/sheleg-dev/skills/ad-tracking/references/performance-security.md +0 -11
  9. package/plugins/sheleg-dev/skills/crypto-payments/SKILL.md +24 -26
  10. package/plugins/sheleg-dev/skills/crypto-payments/references/callback-route-hardening.md +52 -0
  11. package/plugins/sheleg-dev/skills/crypto-payments/references/heleket-provider.md +42 -23
  12. package/plugins/sheleg-dev/skills/error-tracking/SKILL.md +34 -6
  13. package/plugins/sheleg-dev/skills/error-tracking/references/scrubbing.md +65 -2
  14. package/plugins/sheleg-dev/skills/error-tracking/references/setup.md +0 -1
  15. package/plugins/sheleg-dev/skills/frontend-performance/SKILL.md +51 -23
  16. package/plugins/sheleg-dev/skills/google-auth/SKILL.md +13 -1
  17. package/plugins/sheleg-dev/skills/google-auth/references/adc-and-service-accounts.md +13 -16
  18. package/plugins/sheleg-dev/skills/google-auth/references/oauth2-web-server.md +150 -79
  19. package/plugins/sheleg-dev/skills/google-auth/references/sign-in-with-google.md +36 -33
  20. package/plugins/sheleg-dev/skills/google-auth/references/workload-identity.md +0 -13
  21. package/plugins/sheleg-dev/skills/google-signin/SKILL.md +55 -23
  22. package/plugins/sheleg-dev/skills/google-signin/references/full-guide.md +150 -49
  23. package/plugins/sheleg-dev/skills/stripe-billing/SKILL.md +9 -7
  24. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/README.md +17 -1
  25. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/assert-money-invariants.mjs +171 -5
  26. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/manifest.json +333 -198
  27. package/plugins/sheleg-dev/skills/stripe-billing/fixtures/reference-handler.mjs +259 -35
  28. package/plugins/sheleg-dev/skills/stripe-billing/references/subscription-lifecycle.md +149 -28
  29. package/plugins/sheleg-dev/skills/stripe-billing/references/webhook-events.md +76 -5
@@ -89,13 +89,36 @@ covered by default instead of leaking until someone updates a list.
89
89
  ```python
90
90
  import re
91
91
 
92
- _URL_CREDENTIALS = re.compile(r"(?P<scheme>[a-zA-Z][a-zA-Z0-9+.-]*://)(?P<user>[^:/@\s]+):(?P<pw>[^@/\s]+)@")
93
92
  _REDACTED = "<redacted>"
94
93
 
94
+ # userinfo: scheme://[user][:password]@host — the username is OPTIONAL, so
95
+ # `redis://:AUDIT_FAKE_PASSWORD@host` (empty user) is caught, not skipped; the
96
+ # password may be percent-encoded. Keep the (possibly empty) user and the host,
97
+ # redact only the password.
98
+ _URL_USERINFO = re.compile(
99
+ r"(?P<scheme>[a-zA-Z][a-zA-Z0-9+.-]*://)(?P<user>[^:/?#@\s]*):(?P<pw>[^@/?#\s]+)@")
100
+
101
+ # a secret in the PATH: a Telegram bot token is `/bot<id>:<secret>/…`, and the
102
+ # secret is in the path, not the userinfo — the userinfo regex never sees it.
103
+ _URL_PATH_BOT_TOKEN = re.compile(r"(?P<pre>/bot)(?P<tok>\d+:[A-Za-z0-9_-]+)")
104
+
105
+ # a credential in the QUERY STRING: ?access_token=… &token=… &api_key=… — the
106
+ # value, never the key, is redacted so the shape stays readable.
107
+ _URL_QUERY_SECRET = re.compile(
108
+ r"(?P<pre>[?&](?:access_token|auth_token|api_key|apikey|auth|token|key|"
109
+ r"password|passwd|secret|session|sig|signature)=)(?P<val>[^&#\s]+)",
110
+ re.IGNORECASE)
111
+
95
112
  def scrub_text(value):
96
113
  if not isinstance(value, str):
97
114
  return value
98
- return _URL_CREDENTIALS.sub(rf"\g<scheme>\g<user>:{_REDACTED}@", value)
115
+ # Every secret is replaced BEFORE the string can be logged: userinfo first,
116
+ # then the path token, then the query value. A safe URL (a lone user, a
117
+ # non-secret query key) matches none of them and keeps its useful shape.
118
+ value = _URL_USERINFO.sub(rf"\g<scheme>\g<user>:{_REDACTED}@", value)
119
+ value = _URL_PATH_BOT_TOKEN.sub(rf"\g<pre>{_REDACTED}", value)
120
+ value = _URL_QUERY_SECRET.sub(rf"\g<pre>{_REDACTED}", value)
121
+ return value
99
122
 
100
123
  def scrub_values(event, _hint=None):
101
124
  def walk(node):
@@ -116,6 +139,11 @@ Three properties that matter:
116
139
 
117
140
  - **Keep the user and host.** A fully redacted URL is useless for debugging.
118
141
  Redact the password only.
142
+ - **Three places a credential hides — userinfo, path, query.** `user:pw@` is
143
+ the obvious one, but a Telegram token sits in the PATH (`/bot<id>:<secret>/`)
144
+ and an access token sits in the QUERY (`?access_token=…`); the scrubber
145
+ covers all three, and the username in userinfo may be EMPTY
146
+ (`redis://:pw@host`) or the redis leak walks straight through.
119
147
  - **Scheme-agnostic.** `amqps://`, `redis://`, `mongodb+srv://` leak the same way.
120
148
  - **Never return `None`.** In `before_send` that discards the event. A bug in
121
149
  the scrubber then reads as "Sentry is quiet", which is indistinguishable from
@@ -153,6 +181,41 @@ default, the test tells you to stop carrying it yourself.
153
181
  same shape. A test that asserts a password is scrubbed, while carrying that
154
182
  password in the repository, has moved the leak rather than closed it.
155
183
 
184
+ ## Every OUTBOUND CHANNEL has its own hook — or it is not covered
185
+
186
+ `before_send` sees ERROR EVENTS and nothing else. Sentry ships several outbound
187
+ channels, each with its own hook, and a scrubber wired into one of them says
188
+ nothing about the rest — the table below is the claim, per channel, and a
189
+ channel without a hook is **UNSUPPORTED: stated, never promised away as a
190
+ universal scrub**:
191
+
192
+ | Channel | Hook | Status |
193
+ |---|---|---|
194
+ | error events | `before_send` (wired above) | covered |
195
+ | transactions | `before_send_transaction` — `before_send` does NOT see these | covered once wired |
196
+ | breadcrumbs | `before_breadcrumb` | covered once wired |
197
+ | logs (Sentry Logs) | `before_send_log` where the SDK version ships it | covered on that SDK; on an older SDK the channel is UNKNOWN — gate or disable it |
198
+ | attachments | **no scrub hook exists** — attachment bytes go out untouched | UNSUPPORTED: do not attach files that may carry secrets |
199
+
200
+ ```python
201
+ import sentry_sdk
202
+
203
+ sentry_sdk.init(
204
+ dsn=DSN,
205
+ before_send=scrub_values,
206
+ before_send_transaction=scrub_values,
207
+ before_breadcrumb=lambda crumb, _hint: scrub_values(crumb),
208
+ # before_send_log=scrub_values, # only where this SDK version has it;
209
+ # # absent => the logs channel is UNKNOWN
210
+ )
211
+ ```
212
+
213
+ **The fixture suite runs the SAME secret fixtures through EVERY channel the
214
+ table claims** — an event, a transaction, a breadcrumb — and a channel the
215
+ hooks cannot reach is reported UNKNOWN/UNSUPPORTED, never green. A green that
216
+ covers one channel while four others ship raw is the universal-scrub promise
217
+ this section exists to refuse.
218
+
156
219
  ## Re-measuring after an SDK upgrade
157
220
 
158
221
  Both facts on this page are version-dependent. After any `sentry-sdk` bump:
@@ -8,7 +8,6 @@ refused and you need to tell a policy problem from an auth problem.
8
8
  - [Install](#install)
9
9
  - [Authenticate](#authenticate)
10
10
  - [Token taxonomy — which kind can do what](#token-taxonomy--which-kind-can-do-what)
11
- - [Find what already exists](#find-what-already-exists)
12
11
  - [Create a project](#create-a-project)
13
12
  - [Get the DSN and wire it in](#get-the-dsn-and-wire-it-in)
14
13
  - [Declare the MCP server](#declare-the-mcp-server)
@@ -28,24 +28,39 @@ Optimize frontend applications for maximum Lighthouse scores and real-user perfo
28
28
 
29
29
  ## Core Web Vitals, and the diagnostics beside them
30
30
 
31
- **Three metrics are Core Web Vitals. The other three are not**, and the difference
32
- is not pedantry: only the first three are what Google reports and ranks on, and
33
- only they are field-measurable. TBT is a *lab* metric — web.dev says it "is not
34
- part of the Core Web Vitals set because they are not field-measurable" — and it
35
- stands in for INP when you have no field data. Telling a client their Speed Index
36
- is a failing Core Web Vital is telling them about a thing Google does not measure.
37
-
38
- | Core Web Vital | Good | Needs Work | Poor |
39
- |--------|------|------------|------|
40
- | LCP (Largest Contentful Paint) | < 2.5s | 2.5-4.0s | > 4.0s |
41
- | INP (Interaction to Next Paint) | < 200ms | 200-500ms | > 500ms |
42
- | CLS (Cumulative Layout Shift) | < 0.1 | 0.1-0.25 | > 0.25 |
43
-
44
- | Lab diagnostic | Good | Needs Work | Poor | Stands in for |
45
- |--------|------|------------|------|---|
46
- | FCP (First Contentful Paint) | < 1.8s | 1.8-3.0s | > 3.0s | early LCP signal |
47
- | TBT (Total Blocking Time) | < 200ms | 200-600ms | > 600ms | INP, in the lab |
48
- | SI (Speed Index) | < 3.4s | 3.4-5.8s | > 5.8s | perceived load |
31
+ **Two INDEPENDENT axes, and conflating them is the common error.** Axis one:
32
+ **is it a Core Web Vital** — what Google reports and ranks on (LCP, INP, CLS)
33
+ — or not. Axis two: **where can you measure it** — in the FIELD (real users,
34
+ CrUX) or only in the LAB (a synthetic run). These do not line up: a metric can
35
+ be not-CWV and still field-measurable. **FCP is the case that breaks the
36
+ one-axis story — it is NOT a Core Web Vital, yet it IS field-measurable**
37
+ (CrUX reports FCP; web.dev lists both lab and field tools for it). The earlier
38
+ claim that "only the three CWV are field-measurable" was wrong.
39
+
40
+ | Metric | Core Web Vital? | Field-measurable? | Good | Needs Work | Poor |
41
+ |---|---|---|---|---|---|
42
+ | LCP | yes | yes (CrUX) | < 2.5s | 2.5-4.0s | > 4.0s |
43
+ | INP | yes | yes (CrUX) | < 200ms | 200-500ms | > 500ms |
44
+ | CLS | yes | yes (CrUX) | < 0.1 | 0.1-0.25 | > 0.25 |
45
+ | FCP | no | **yes (CrUX)** | < 1.8s | 1.8-3.0s | > 3.0s |
46
+ | TBT | no | no (lab only) | < 200ms | 200-600ms | > 600ms |
47
+ | SI | no | no (lab only) | < 3.4s | 3.4-5.8s | > 5.8s |
48
+
49
+ **A field claim carries its provenance, always: p75** (the 75th-percentile
50
+ value Google classifies on, never a mean), the **device/cohort** (phone vs
51
+ desktop, and which population), the **period** (CrUX is a 28-day trailing
52
+ window), and the **sample size** (a thin origin has no field data — that is
53
+ `unknown`, not `good`). A number without these four is a lab number wearing a
54
+ field label.
55
+
56
+ **TBT is a diagnostic CORRELATE of INP, never a substitute for its evidence.**
57
+ A good lab TBT is a hint that INP may be fine; it does NOT PROVE INP, because
58
+ INP is measured on real interactions the lab did not perform. So a page with
59
+ CrUX FCP data and a Lighthouse TBT but **no field INP** reports **FCP =
60
+ field-supported, INP = unknown** — never "INP passed by TBT". Telling a client
61
+ their Speed Index is a failing Core Web Vital is telling them about a thing
62
+ Google does not rank; telling them INP passed because TBT did is telling them
63
+ about a measurement nobody took.
49
64
 
50
65
  Thresholds and the classification verified against `web.dev/articles/vitals`,
51
66
  2026-08-16.
@@ -61,15 +76,28 @@ Thresholds and the classification verified against `web.dev/articles/vitals`,
61
76
  - **Use `font-display: swap`** -- Prevents invisible text during load (FOIT).
62
77
  - **Remove unused preconnects** -- If fonts are self-hosted, `preconnect` to font CDNs is dead weight.
63
78
 
79
+ **Before any of these changes: MEASURE, don't prescribe.** The items below are
80
+ optimization HEURISTICS, not blanket rules — each is conditional on evidence,
81
+ and applied blind it can lose function for a score. Before changing anything,
82
+ check three things: the **performance trace + the scenarios** (what a low-end
83
+ device on a slow network actually waits for), the **browser-support contract**
84
+ (who must the page still work for), and the **accessibility owner** (structure
85
+ is theirs, not the score's). A change ships only with acceptance covering:
86
+ low-end/slow-network scenarios, keyboard + heading structure intact, the
87
+ supported-browser matrix still served, NO new unapproved CSP origin, and
88
+ several comparable before/after measurements showing the win **with no
89
+ functional loss**.
90
+
64
91
  ### JavaScript (TBT, FCP, LCP)
65
92
 
66
- - **Code split aggressively** -- Lazy-load below-the-fold sections. Only hero + nav in initial bundle.
93
+ - **Code split by the MEASURED waterfall** -- lazy-load what the trace shows the first paint does not need; "only hero + nav in the initial bundle" is a starting hypothesis, not a rule — a section the scenario needs above the fold stays, whatever the score says.
67
94
  - **Defer third-party scripts** -- GA, analytics, chat widgets load after interactive (`afterInteractive` or `defer`).
68
- - **Target modern browsers** -- Set `browserslist` to avoid shipping polyfills for `Array.prototype.at`, `Object.fromEntries`, etc.:
95
+ - **Target the browsers your USERS run** -- `browserslist` follows a stated user-support policy (your analytics, your contract), not a default "last 2". The `last 2` line below is an EXAMPLE; shipping it without checking who it drops is how a working browser stops being served for a polyfill saving:
69
96
  ```
97
+ # example only — replace with your measured support matrix
70
98
  last 2 Chrome versions, last 2 Firefox versions, last 2 Safari versions, last 2 Edge versions
71
99
  ```
72
- - **Tree-shake imports** -- Named imports only. No `import * as`.
100
+ - **Tree-shake imports** -- prefer named imports where the bundle analyzer shows `import * as` pulling dead weight; it is a tree-shaking aid, not a ban — a namespace import that costs nothing measured is not a defect.
73
101
  - **Analyze bundles** -- Use `@next/bundle-analyzer`, `source-map-explorer`, or `vite-plugin-visualizer`.
74
102
 
75
103
  ### CSS & Animations (CLS)
@@ -98,7 +126,7 @@ Thresholds and the classification verified against `web.dev/articles/vitals`,
98
126
 
99
127
  ### Content Security Policy (Best Practices)
100
128
 
101
- - **Whitelist all loaded origins** -- CSP blocks = console errors = lower Best Practices score.
129
+ - **Approve origins by FUNCTIONAL NECESSITY, not for the score** -- an origin enters the CSP because the page genuinely needs it, and each is reviewed; whitelisting everything the page happens to load to silence console errors trades the whole point of a CSP for a Best-Practices number. No new origin ships unapproved.
102
130
  - **Audit after every third-party change** -- Adding analytics, fonts, or CDN scripts requires CSP updates.
103
131
  - **Tighten after migration** -- If you move from external fonts to self-hosted, remove the old CSP entries.
104
132
  - **Common CSP origins**:
@@ -111,7 +139,7 @@ Thresholds and the classification verified against `web.dev/articles/vitals`,
111
139
  These directly affect the Lighthouse Accessibility score:
112
140
 
113
141
  - **Contrast ratio** -- WCAG AA requires 4.5:1 for normal text, 3:1 for large text. Avoid opacity modifiers on text colors (e.g., `text-primary/80` in Tailwind reduces contrast). Use full-opacity color tokens.
114
- - **Heading hierarchy** -- Sequential: `h1` -> `h2` -> `h3`. Never skip levels. Use non-heading elements (`<p>`) for visual-only "headings" (e.g., footer column titles).
142
+ - **Heading hierarchy follows STRUCTURE, decided with the accessibility owner** -- sequential `h1`->`h2`->`h3`, no skipped levels. Demote a heading to `<p>` only when it is genuinely NOT a section title; downgrading a real footer-section heading to `<p>` to quiet a Lighthouse audit removes structure a screen-reader user navigates by. Accessibility is the owner here, not the score.
115
143
  - **Alt text** -- Decorative images next to identical text get `alt=""`. Informative images get descriptive alt. Never duplicate adjacent text.
116
144
  - **Tap targets** -- Minimum 48x48px on mobile. Applies to buttons, links, form inputs.
117
145
 
@@ -86,7 +86,7 @@ request = google.auth.transport.requests.Request()
86
86
  credentials.refresh(request)
87
87
  ```
88
88
 
89
- ADC search order: attached service account → `gcloud auth application-default login` file → `GOOGLE_APPLICATION_CREDENTIALS` env var.
89
+ ADC search order (env var FIRST — this line was reversed): `GOOGLE_APPLICATION_CREDENTIALS` env var → `gcloud auth application-default login` file → attached service account (metadata). Print the RESOLVED principal and source (no secret) before configuring — with more than one present, only the resolved source names which you are actually using.
90
90
 
91
91
  For detailed ADC setup and service account usage, see [references/adc-and-service-accounts.md](references/adc-and-service-accounts.md).
92
92
 
@@ -259,6 +259,18 @@ if credentials.expired and credentials.refresh_token:
259
259
  - Validate external credential configurations before use (check `token_url`, `service_account_impersonation_url` point to googleapis.com)
260
260
  - Prefer Workload Identity Federation over service account keys for non-GCP environments
261
261
  - For end-user web sign-in, apply the google-signin skill's full checklist — a partial restatement here is how the two skills drifted apart once already
262
+ - **Fail closed on a missing production secret.** The session signing secret,
263
+ and any credential-store key, is REQUIRED in production with NO dev fallback —
264
+ a hardcoded default signs every deployment's cookies with a key that lives in
265
+ the repo. Missing → refuse to boot, never a warning-and-continue (DV-07).
266
+ - **Logs are sanitized; a credential never reaches one.** No token, no
267
+ `client_secret`, no `refresh_token`, no session value in a log line, a stack
268
+ trace, or an error message — a token in a log is a token anyone with log
269
+ access holds. Redact by allow-list, not by trying to strip the secret out.
270
+ - **HTTPS is mandatory and the credential store can fail.** The auth cookie is
271
+ `Secure` and the callback refuses plain HTTP (an OAuth code over `http://` is a
272
+ code on the wire); and a credential-store read/write that FAILS is an auth
273
+ failure — re-prompt or 503, never proceed as if the credentials loaded.
262
274
  - **Python-specific**: reuse a single `google.auth.transport.requests.Request()` instance across verifications for connection pooling; do not create a new one per call in hot paths
263
275
 
264
276
  ## Reference Files
@@ -3,19 +3,6 @@
3
3
 
4
4
  ## Contents
5
5
 
6
- - [Table of Contents](#table-of-contents)
7
- - [ADC Overview](#adc-overview)
8
- - [ADC Search Order](#adc-search-order)
9
- - [Setting Up ADC](#setting-up-adc)
10
- - [Using ADC in Code](#using-adc-in-code)
11
- - [Service Account Keys (JWT)](#service-account-keys-jwt)
12
- - [Compute Credentials](#compute-credentials)
13
- - [Impersonated Credentials](#impersonated-credentials)
14
- - [Downscoped Client](#downscoped-client)
15
- - [ID Tokens](#id-tokens)
16
- - [Environment Variables](#environment-variables)
17
-
18
- ## Table of Contents
19
6
  - [ADC Overview](#adc-overview)
20
7
  - [ADC Search Order](#adc-search-order)
21
8
  - [Setting Up ADC](#setting-up-adc)
@@ -33,9 +20,19 @@ Application Default Credentials (ADC) automatically finds credentials based on t
33
20
 
34
21
  ## ADC Search Order
35
22
 
36
- 1. **Attached service account** — via GCP metadata server (Compute Engine, Cloud Run, GKE, etc.)
37
- 2. **`gcloud auth application-default login`** — local credential file
38
- 3. **`GOOGLE_APPLICATION_CREDENTIALS`** — env var pointing to a credential JSON file
23
+ Google resolves these **in this order — env var FIRST**, and the earlier match
24
+ wins. The old text here had it reversed (attached account first), which points
25
+ you at the wrong lever when the resolved identity is not what you expect:
26
+
27
+ 1. **`GOOGLE_APPLICATION_CREDENTIALS`** — env var pointing to a credential JSON file
28
+ 2. **`gcloud auth application-default login`** — local credential file (well-known path)
29
+ 3. **Attached service account** — via GCP metadata server (Compute Engine, Cloud Run, GKE, etc.)
30
+
31
+ **Before configuring, print the ACTUALLY-resolved principal and source** — which
32
+ of the three won, and the account email — **without the secret**. On a VM with a
33
+ metadata identity AND an env var set, only the resolved source tells you which
34
+ one your code is actually using; a config change made against the wrong source
35
+ is a change that does nothing.
39
36
 
40
37
  Credential file locations from `gcloud auth application-default login`:
41
38
  - **macOS/Linux**: `$HOME/.config/gcloud/application_default_credentials.json`