har-o-scope 0.1.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/LICENSE +661 -0
- package/README.md +179 -0
- package/completions/har-o-scope.bash +64 -0
- package/completions/har-o-scope.fish +43 -0
- package/completions/har-o-scope.zsh +63 -0
- package/dist/cli/colors.d.ts +17 -0
- package/dist/cli/colors.d.ts.map +1 -0
- package/dist/cli/colors.js +54 -0
- package/dist/cli/demo.d.ts +7 -0
- package/dist/cli/demo.d.ts.map +1 -0
- package/dist/cli/demo.js +62 -0
- package/dist/cli/formatters.d.ts +12 -0
- package/dist/cli/formatters.d.ts.map +1 -0
- package/dist/cli/formatters.js +249 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +260 -0
- package/dist/cli/rules.d.ts +3 -0
- package/dist/cli/rules.d.ts.map +1 -0
- package/dist/cli/rules.js +36 -0
- package/dist/cli/sarif.d.ts +9 -0
- package/dist/cli/sarif.d.ts.map +1 -0
- package/dist/cli/sarif.js +104 -0
- package/dist/lib/analyze.d.ts +10 -0
- package/dist/lib/analyze.d.ts.map +1 -0
- package/dist/lib/analyze.js +83 -0
- package/dist/lib/classifier.d.ts +8 -0
- package/dist/lib/classifier.d.ts.map +1 -0
- package/dist/lib/classifier.js +74 -0
- package/dist/lib/diff.d.ts +15 -0
- package/dist/lib/diff.d.ts.map +1 -0
- package/dist/lib/diff.js +130 -0
- package/dist/lib/errors.d.ts +56 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +65 -0
- package/dist/lib/evaluate.d.ts +19 -0
- package/dist/lib/evaluate.d.ts.map +1 -0
- package/dist/lib/evaluate.js +189 -0
- package/dist/lib/health-score.d.ts +18 -0
- package/dist/lib/health-score.d.ts.map +1 -0
- package/dist/lib/health-score.js +74 -0
- package/dist/lib/html-report.d.ts +15 -0
- package/dist/lib/html-report.d.ts.map +1 -0
- package/dist/lib/html-report.js +299 -0
- package/dist/lib/index.d.ts +26 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +24 -0
- package/dist/lib/normalizer.d.ts +18 -0
- package/dist/lib/normalizer.d.ts.map +1 -0
- package/dist/lib/normalizer.js +201 -0
- package/dist/lib/rule-engine.d.ts +12 -0
- package/dist/lib/rule-engine.d.ts.map +1 -0
- package/dist/lib/rule-engine.js +122 -0
- package/dist/lib/sanitizer.d.ts +10 -0
- package/dist/lib/sanitizer.d.ts.map +1 -0
- package/dist/lib/sanitizer.js +129 -0
- package/dist/lib/schema.d.ts +85 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +1 -0
- package/dist/lib/trace-sanitizer.d.ts +30 -0
- package/dist/lib/trace-sanitizer.d.ts.map +1 -0
- package/dist/lib/trace-sanitizer.js +85 -0
- package/dist/lib/types.d.ts +161 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/unbatched-detect.d.ts +7 -0
- package/dist/lib/unbatched-detect.d.ts.map +1 -0
- package/dist/lib/unbatched-detect.js +59 -0
- package/dist/lib/validator.d.ts +4 -0
- package/dist/lib/validator.d.ts.map +1 -0
- package/dist/lib/validator.js +409 -0
- package/package.json +98 -0
- package/rules/generic/issue-rules.yaml +292 -0
- package/rules/generic/shared/base-conditions.yaml +28 -0
- package/rules/generic/shared/filters.yaml +12 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# Generic issue detection rules for har-o-scope.
|
|
2
|
+
# 16 YAML rules (+ 1 TypeScript rule: unbatched-api-calls).
|
|
3
|
+
# Zero platform-specific content. Works with any web application.
|
|
4
|
+
|
|
5
|
+
rules:
|
|
6
|
+
|
|
7
|
+
# ── Server ──────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
slow-ttfb:
|
|
10
|
+
category: server
|
|
11
|
+
severity: warning
|
|
12
|
+
severity_escalation:
|
|
13
|
+
critical_threshold: 5
|
|
14
|
+
inherits: ["is_not_streaming", "is_not_websocket"]
|
|
15
|
+
title: "{count} request{s} with slow TTFB (> 800ms)"
|
|
16
|
+
description: "{count} requests had Time To First Byte exceeding 800ms. This indicates server-side processing delays, slow database queries, or backend bottlenecks."
|
|
17
|
+
recommendation: "Investigate server-side processing time. Check database query performance, API response times, and server resource utilization. Consider caching frequently requested resources."
|
|
18
|
+
condition:
|
|
19
|
+
match_all:
|
|
20
|
+
- field: "timings.wait"
|
|
21
|
+
gt: 800
|
|
22
|
+
impact:
|
|
23
|
+
field: "timings.wait"
|
|
24
|
+
baseline: 200
|
|
25
|
+
root_cause_weight:
|
|
26
|
+
server: 3
|
|
27
|
+
|
|
28
|
+
# ── Network ─────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
stalled-requests:
|
|
31
|
+
category: network
|
|
32
|
+
severity: warning
|
|
33
|
+
severity_escalation:
|
|
34
|
+
critical_threshold: 10
|
|
35
|
+
inherits: ["is_not_streaming", "is_not_websocket"]
|
|
36
|
+
title: "{count} stalled request{s} (blocked > 1000ms)"
|
|
37
|
+
description: "{count} requests spent over 1 second waiting in the browser's connection queue. This often indicates HTTP/1.1 connection saturation (6 connections per domain limit)."
|
|
38
|
+
recommendation: "Reduce concurrent requests to the same domain. Enable HTTP/2 to multiplex requests over a single connection. Consider domain sharding as a last resort for HTTP/1.1."
|
|
39
|
+
condition:
|
|
40
|
+
match_all:
|
|
41
|
+
- field: "timings.blocked"
|
|
42
|
+
gt: 1000
|
|
43
|
+
impact:
|
|
44
|
+
field: "timings.blocked"
|
|
45
|
+
root_cause_weight:
|
|
46
|
+
client: 3
|
|
47
|
+
|
|
48
|
+
slow-dns:
|
|
49
|
+
category: network
|
|
50
|
+
severity: warning
|
|
51
|
+
severity_escalation:
|
|
52
|
+
critical_threshold: 5
|
|
53
|
+
title: "{count} request{s} with slow DNS lookup (> 100ms)"
|
|
54
|
+
description: "{count} requests had DNS resolution times exceeding 100ms. This suggests slow DNS resolvers or many unique domains being resolved."
|
|
55
|
+
recommendation: "Consider using a faster DNS provider (e.g., 1.1.1.1, 8.8.8.8). Reduce the number of unique domains. Use dns-prefetch hints for critical third-party domains."
|
|
56
|
+
condition:
|
|
57
|
+
match_all:
|
|
58
|
+
- field: "timings.dns"
|
|
59
|
+
gt: 100
|
|
60
|
+
impact:
|
|
61
|
+
field: "timings.dns"
|
|
62
|
+
root_cause_weight:
|
|
63
|
+
network: 3
|
|
64
|
+
|
|
65
|
+
slow-tls:
|
|
66
|
+
category: network
|
|
67
|
+
severity: warning
|
|
68
|
+
title: "{count} request{s} with slow TLS handshake (> 200ms)"
|
|
69
|
+
description: "{count} requests had TLS negotiation times exceeding 200ms. This may indicate geographic distance to the server, certificate chain issues, or missing OCSP stapling."
|
|
70
|
+
recommendation: "Check server TLS configuration. Enable OCSP stapling. Consider using a CDN to reduce round-trip distance. Verify the certificate chain is minimal."
|
|
71
|
+
condition:
|
|
72
|
+
match_all:
|
|
73
|
+
- field: "timings.ssl"
|
|
74
|
+
gt: 200
|
|
75
|
+
impact:
|
|
76
|
+
field: "timings.ssl"
|
|
77
|
+
baseline: 50
|
|
78
|
+
root_cause_weight:
|
|
79
|
+
network: 2
|
|
80
|
+
|
|
81
|
+
http1-downgrade:
|
|
82
|
+
category: network
|
|
83
|
+
severity: info
|
|
84
|
+
severity_escalation:
|
|
85
|
+
warning_ratio: 0.5
|
|
86
|
+
inherits: ["is_not_websocket"]
|
|
87
|
+
title: "{count} request{s} using HTTP/1.1"
|
|
88
|
+
description: "{count} requests used HTTP/1.1 instead of HTTP/2 or HTTP/3. HTTP/1.1 limits browsers to 6 concurrent connections per domain, causing request queueing on busy pages."
|
|
89
|
+
recommendation: "Ensure your server supports HTTP/2. Check if a corporate proxy or VPN is forcing an HTTP/1.1 downgrade. Most modern servers and CDNs support HTTP/2 by default."
|
|
90
|
+
condition:
|
|
91
|
+
match_all:
|
|
92
|
+
- field: "httpVersion"
|
|
93
|
+
not_matches: "h2|h3|HTTP/2|HTTP/3"
|
|
94
|
+
- field: "httpVersion"
|
|
95
|
+
not_equals: "unknown"
|
|
96
|
+
impact:
|
|
97
|
+
value: 0
|
|
98
|
+
root_cause_weight:
|
|
99
|
+
network: 2
|
|
100
|
+
|
|
101
|
+
connection-reuse:
|
|
102
|
+
category: network
|
|
103
|
+
severity: warning
|
|
104
|
+
min_count: 3
|
|
105
|
+
inherits: ["is_not_websocket"]
|
|
106
|
+
title: "Poor connection reuse: {count} new TCP connections"
|
|
107
|
+
description: "{count} requests established new TCP connections (connect time > 0). Reusing existing connections avoids the overhead of TCP handshake + TLS negotiation."
|
|
108
|
+
recommendation: "Ensure Connection: keep-alive is enabled. Reduce the number of unique domains. Enable HTTP/2 for multiplexed connections."
|
|
109
|
+
condition:
|
|
110
|
+
match_all:
|
|
111
|
+
- field: "timings.connect"
|
|
112
|
+
gt: 0
|
|
113
|
+
impact:
|
|
114
|
+
field: "timings.connect"
|
|
115
|
+
root_cause_weight:
|
|
116
|
+
network: 2
|
|
117
|
+
|
|
118
|
+
# ── Optimization ────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
missing-compression:
|
|
121
|
+
category: optimization
|
|
122
|
+
severity: info
|
|
123
|
+
severity_escalation:
|
|
124
|
+
warning_threshold: 11
|
|
125
|
+
title: "{count} text response{s} without compression"
|
|
126
|
+
description: "{count} text-based responses are not using gzip, Brotli, or other compression. Compressed responses are typically 60-80% smaller."
|
|
127
|
+
recommendation: "Enable gzip or Brotli compression on the server for text-based responses (HTML, CSS, JS, JSON, XML). Most web servers support this with a single configuration change."
|
|
128
|
+
condition:
|
|
129
|
+
match_all:
|
|
130
|
+
- field: "entry.response.content.mimeType"
|
|
131
|
+
matches: "text|javascript|json|xml|html|css"
|
|
132
|
+
- field: "contentSize"
|
|
133
|
+
gt: 1024
|
|
134
|
+
- no_response_header:
|
|
135
|
+
name: "content-encoding"
|
|
136
|
+
value_matches: "gzip|br|deflate|zstd"
|
|
137
|
+
impact:
|
|
138
|
+
value: 0
|
|
139
|
+
|
|
140
|
+
missing-cache-headers:
|
|
141
|
+
category: optimization
|
|
142
|
+
severity: info
|
|
143
|
+
inherits: ["is_static_resource"]
|
|
144
|
+
title: "{count} static resource{s} without cache headers"
|
|
145
|
+
description: "{count} static resources (scripts, styles, images, fonts) lack Cache-Control, ETag, or Last-Modified headers. The browser cannot cache these effectively and will re-fetch on every page load."
|
|
146
|
+
recommendation: "Add Cache-Control headers to static resources. Use max-age=31536000 with content-hashed filenames for immutable assets. Add ETag or Last-Modified for conditional revalidation."
|
|
147
|
+
condition:
|
|
148
|
+
match_all:
|
|
149
|
+
- no_response_header:
|
|
150
|
+
name: "cache-control"
|
|
151
|
+
- no_response_header:
|
|
152
|
+
name: "etag"
|
|
153
|
+
- no_response_header:
|
|
154
|
+
name: "last-modified"
|
|
155
|
+
impact:
|
|
156
|
+
value: 0
|
|
157
|
+
|
|
158
|
+
large-payload:
|
|
159
|
+
category: optimization
|
|
160
|
+
severity: info
|
|
161
|
+
severity_escalation:
|
|
162
|
+
warning_threshold: 5
|
|
163
|
+
title: "{count} oversized resource{s} (> 1 MB)"
|
|
164
|
+
description: "{count} responses exceeded 1 MB in transfer size. Large payloads slow down page loads, especially on slower connections."
|
|
165
|
+
recommendation: "Optimize large resources: compress images (WebP/AVIF), minify JS/CSS, paginate large API responses, lazy-load below-the-fold content."
|
|
166
|
+
condition:
|
|
167
|
+
match_all:
|
|
168
|
+
- field: "transferSizeResolved"
|
|
169
|
+
gt: 1048576
|
|
170
|
+
impact:
|
|
171
|
+
value: 0
|
|
172
|
+
|
|
173
|
+
cors-preflight:
|
|
174
|
+
category: optimization
|
|
175
|
+
severity: info
|
|
176
|
+
severity_escalation:
|
|
177
|
+
warning_threshold: 10
|
|
178
|
+
min_count: 3
|
|
179
|
+
title: "{count} CORS preflight request{s}"
|
|
180
|
+
description: "{count} OPTIONS preflight requests were sent before the actual requests. Each preflight adds a full round-trip of latency."
|
|
181
|
+
recommendation: "Configure Access-Control-Max-Age headers to cache preflight responses. Simplify requests to avoid triggering preflights (use simple methods and headers where possible)."
|
|
182
|
+
condition:
|
|
183
|
+
match_all:
|
|
184
|
+
- field: "entry.request.method"
|
|
185
|
+
equals: "OPTIONS"
|
|
186
|
+
impact:
|
|
187
|
+
field: "timings.total"
|
|
188
|
+
|
|
189
|
+
excessive-cookies:
|
|
190
|
+
category: optimization
|
|
191
|
+
severity: warning
|
|
192
|
+
title: "Excessive Set-Cookie headers on {count} response{s}"
|
|
193
|
+
description: "{count} responses included Set-Cookie headers. Cookies are sent with every subsequent request, increasing bandwidth usage. Large cookie payloads can also trigger warnings from server infrastructure."
|
|
194
|
+
recommendation: "Audit cookie usage. Use sessionStorage or localStorage for client-only data. Set appropriate cookie domains and paths to limit cookie scope. Consider using a cookieless domain for static assets."
|
|
195
|
+
condition:
|
|
196
|
+
match_all:
|
|
197
|
+
- has_response_header:
|
|
198
|
+
name: "set-cookie"
|
|
199
|
+
min_count: 10
|
|
200
|
+
impact:
|
|
201
|
+
value: 0
|
|
202
|
+
|
|
203
|
+
redirect-chains:
|
|
204
|
+
category: performance
|
|
205
|
+
severity: warning
|
|
206
|
+
min_count: 3
|
|
207
|
+
title: "{count} redirect{s} detected"
|
|
208
|
+
description: "{count} requests resulted in redirects (301, 302, 303, 307, 308). Each redirect adds a full round-trip. Chains of 3+ redirects significantly impact page load time."
|
|
209
|
+
recommendation: "Eliminate unnecessary redirects. Update links to point directly to the final URL. Use 301 (permanent) redirects where appropriate so browsers cache the redirect."
|
|
210
|
+
condition:
|
|
211
|
+
match_all:
|
|
212
|
+
- field: "entry.response.status"
|
|
213
|
+
in: [301, 302, 303, 307, 308]
|
|
214
|
+
impact:
|
|
215
|
+
field: "timings.total"
|
|
216
|
+
root_cause_weight:
|
|
217
|
+
server: 1
|
|
218
|
+
network: 1
|
|
219
|
+
|
|
220
|
+
# ── Security ────────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
mixed-content:
|
|
223
|
+
category: security
|
|
224
|
+
severity: critical
|
|
225
|
+
prerequisite:
|
|
226
|
+
any_entry_matches:
|
|
227
|
+
field: "entry.request.url"
|
|
228
|
+
matches: "^https://"
|
|
229
|
+
title: "{count} mixed content request{s} (HTTP on HTTPS page)"
|
|
230
|
+
description: "{count} requests were made over insecure HTTP from a secure HTTPS page. Browsers may block these requests or show security warnings."
|
|
231
|
+
recommendation: "Update all resource URLs to use HTTPS. Configure Content-Security-Policy with upgrade-insecure-requests. Check for hardcoded HTTP URLs in your code."
|
|
232
|
+
condition:
|
|
233
|
+
match_all:
|
|
234
|
+
- field: "entry.request.url"
|
|
235
|
+
matches: "^http://"
|
|
236
|
+
- field: "entry.request.url"
|
|
237
|
+
not_matches: "^http://localhost|^http://127\\.0\\.0\\.1|^http://\\[::1\\]"
|
|
238
|
+
impact:
|
|
239
|
+
value: 0
|
|
240
|
+
root_cause_weight:
|
|
241
|
+
client: 2
|
|
242
|
+
|
|
243
|
+
# ── Errors ──────────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
broken-resources:
|
|
246
|
+
category: errors
|
|
247
|
+
severity: critical
|
|
248
|
+
title: "{count} broken resource{s} (HTTP 4xx/5xx)"
|
|
249
|
+
description: "{count} requests returned error status codes. 4xx errors indicate client-side issues (missing resources, unauthorized access). 5xx errors indicate server failures."
|
|
250
|
+
recommendation: "Review the failing URLs. Fix 404s by updating links or deploying missing assets. Investigate 500s in server logs. Check 403s for permission or CORS issues."
|
|
251
|
+
condition:
|
|
252
|
+
match_all:
|
|
253
|
+
- field: "entry.response.status"
|
|
254
|
+
gte: 400
|
|
255
|
+
impact:
|
|
256
|
+
value: 0
|
|
257
|
+
root_cause_weight:
|
|
258
|
+
server: 2
|
|
259
|
+
client: 1
|
|
260
|
+
|
|
261
|
+
# ── Informational ───────────────────────────────────────────────
|
|
262
|
+
|
|
263
|
+
long-poll-detected:
|
|
264
|
+
category: informational
|
|
265
|
+
severity: info
|
|
266
|
+
title: "{count} long-poll or SSE connection{s} detected"
|
|
267
|
+
description: "{count} requests appear to be long-polling or Server-Sent Events connections (high wait time with small response body, or text/event-stream MIME type). These are expected behavior, not performance issues."
|
|
268
|
+
recommendation: "No action needed. Long-polling and SSE are legitimate real-time communication patterns. They will appear as slow requests in waterfall charts but are working as designed."
|
|
269
|
+
condition:
|
|
270
|
+
match_all:
|
|
271
|
+
- field: "isLongPoll"
|
|
272
|
+
equals: true
|
|
273
|
+
impact:
|
|
274
|
+
value: 0
|
|
275
|
+
|
|
276
|
+
# ── Aggregate ───────────────────────────────────────────────────
|
|
277
|
+
|
|
278
|
+
excessive-requests:
|
|
279
|
+
type: aggregate
|
|
280
|
+
category: client
|
|
281
|
+
severity: warning
|
|
282
|
+
severity_escalation:
|
|
283
|
+
critical_threshold: 500
|
|
284
|
+
aggregate_condition:
|
|
285
|
+
min_entries: 200
|
|
286
|
+
title: "High request count: {total} requests"
|
|
287
|
+
description: "This page made {total} HTTP requests. High request counts degrade performance, especially on HTTP/1.1 connections where browsers limit concurrent connections to 6 per domain."
|
|
288
|
+
recommendation: "Reduce request count by bundling scripts and styles, using CSS sprites or SVG icons, lazy-loading below-the-fold resources, and eliminating unnecessary analytics or tracking calls."
|
|
289
|
+
impact:
|
|
290
|
+
value: 0
|
|
291
|
+
root_cause_weight:
|
|
292
|
+
client: 5
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Reusable condition fragments for generic rules.
|
|
2
|
+
# Reference by name in a rule's `inherits` array.
|
|
3
|
+
|
|
4
|
+
conditions:
|
|
5
|
+
|
|
6
|
+
is_not_streaming:
|
|
7
|
+
field: "isLongPoll"
|
|
8
|
+
equals: false
|
|
9
|
+
|
|
10
|
+
is_not_websocket:
|
|
11
|
+
field: "resourceType"
|
|
12
|
+
not_equals: "websocket"
|
|
13
|
+
|
|
14
|
+
is_http1:
|
|
15
|
+
field: "httpVersion"
|
|
16
|
+
matches: "^HTTP/1"
|
|
17
|
+
|
|
18
|
+
is_success_status:
|
|
19
|
+
field: "entry.response.status"
|
|
20
|
+
gte: 200
|
|
21
|
+
|
|
22
|
+
is_text_content:
|
|
23
|
+
field: "entry.response.content.mimeType"
|
|
24
|
+
matches: "text|javascript|json|xml|html|css"
|
|
25
|
+
|
|
26
|
+
is_static_resource:
|
|
27
|
+
field: "resourceType"
|
|
28
|
+
in: ["script", "stylesheet", "image", "font"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Noise exclusion patterns for generic rules.
|
|
2
|
+
# Reference by name in a rule's `exclude` array.
|
|
3
|
+
|
|
4
|
+
filters:
|
|
5
|
+
|
|
6
|
+
analytics_tracking:
|
|
7
|
+
field: "entry.request.url"
|
|
8
|
+
matches: "google-analytics|googletagmanager|mixpanel|facebook.*/tr|linkedin.*/px|doubleclick|segment\\.io|amplitude"
|
|
9
|
+
|
|
10
|
+
status_0_or_cancelled:
|
|
11
|
+
field: "entry.response.status"
|
|
12
|
+
equals: 0
|