@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.
- package/CHANGELOG.md +37 -0
- package/package.json +4 -3
- package/plugins/sheleg-dev/.claude-plugin/plugin.json +1 -1
- package/plugins/sheleg-dev/skills/ad-tracking/SKILL.md +24 -8
- package/plugins/sheleg-dev/skills/ad-tracking/references/consent-mode.md +57 -24
- package/plugins/sheleg-dev/skills/ad-tracking/references/event-tracking.md +0 -13
- package/plugins/sheleg-dev/skills/ad-tracking/references/gtag-api.md +0 -17
- package/plugins/sheleg-dev/skills/ad-tracking/references/performance-security.md +0 -11
- package/plugins/sheleg-dev/skills/crypto-payments/SKILL.md +24 -26
- package/plugins/sheleg-dev/skills/crypto-payments/references/callback-route-hardening.md +52 -0
- package/plugins/sheleg-dev/skills/crypto-payments/references/heleket-provider.md +42 -23
- package/plugins/sheleg-dev/skills/error-tracking/SKILL.md +34 -6
- package/plugins/sheleg-dev/skills/error-tracking/references/scrubbing.md +65 -2
- package/plugins/sheleg-dev/skills/error-tracking/references/setup.md +0 -1
- package/plugins/sheleg-dev/skills/frontend-performance/SKILL.md +51 -23
- package/plugins/sheleg-dev/skills/google-auth/SKILL.md +13 -1
- package/plugins/sheleg-dev/skills/google-auth/references/adc-and-service-accounts.md +13 -16
- package/plugins/sheleg-dev/skills/google-auth/references/oauth2-web-server.md +150 -79
- package/plugins/sheleg-dev/skills/google-auth/references/sign-in-with-google.md +36 -33
- package/plugins/sheleg-dev/skills/google-auth/references/workload-identity.md +0 -13
- package/plugins/sheleg-dev/skills/google-signin/SKILL.md +55 -23
- package/plugins/sheleg-dev/skills/google-signin/references/full-guide.md +150 -49
- package/plugins/sheleg-dev/skills/stripe-billing/SKILL.md +9 -7
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/README.md +17 -1
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/assert-money-invariants.mjs +171 -5
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/manifest.json +333 -198
- package/plugins/sheleg-dev/skills/stripe-billing/fixtures/reference-handler.mjs +259 -35
- package/plugins/sheleg-dev/skills/stripe-billing/references/subscription-lifecycle.md +149 -28
- 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
|
-
|
|
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
|
-
**
|
|
32
|
-
is
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
|
41
|
-
|
|
42
|
-
|
|
|
43
|
-
|
|
44
|
-
|
|
|
45
|
-
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
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
|
|
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
|
|
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** --
|
|
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
|
-
- **
|
|
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** --
|
|
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:
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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`
|