ssrwire 0.1.0 → 0.2.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 +22 -1
- package/CONTRIBUTING.md +7 -5
- package/PUBLISHING.md +92 -78
- package/README.md +53 -11
- package/SECURITY.md +8 -4
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +82 -7
- package/dist/audit.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -0
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/redact.d.ts.map +1 -1
- package/dist/redact.js +12 -0
- package/dist/redact.js.map +1 -1
- package/dist/reporters.d.ts.map +1 -1
- package/dist/reporters.js +42 -2
- package/dist/reporters.js.map +1 -1
- package/dist/stability.d.ts +9 -0
- package/dist/stability.d.ts.map +1 -0
- package/dist/stability.js +246 -0
- package/dist/stability.js.map +1 -0
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -1
- package/examples/github-actions.yml +2 -2
- package/examples/ssrwire.config.yml +4 -0
- package/package.json +1 -1
- package/src/audit.ts +116 -8
- package/src/cli.ts +4 -0
- package/src/config.ts +8 -0
- package/src/index.ts +4 -0
- package/src/redact.ts +12 -0
- package/src/reporters.ts +51 -2
- package/src/stability.ts +356 -0
- package/src/types.ts +41 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ All notable changes to SSRWire are documented here. The project follows
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.2.0] - 2026-08-24
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Bounded `repeat` configuration and `--repeat` CLI sampling for 1–10 requests
|
|
13
|
+
per target-agent pair.
|
|
14
|
+
- Per-agent minimum, median, nearest-rank p95, maximum, and spread summaries for
|
|
15
|
+
response-header, first-byte, required-signal, and completion timings.
|
|
16
|
+
- Response and streamed-HTML stability findings across repeated samples.
|
|
17
|
+
- Sample attribution in terminal and JSON reports.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Added an npm package badge to the README.
|
|
22
|
+
- Samples for one target-agent pair run sequentially while independent pairs
|
|
23
|
+
retain bounded concurrency.
|
|
24
|
+
- Repeated policy findings are coalesced with occurrence and sample evidence.
|
|
25
|
+
- Exact body-fingerprint variation alone is informational; timing variation
|
|
26
|
+
alone remains evidence rather than a policy failure.
|
|
27
|
+
|
|
8
28
|
## [0.1.0] - 2026-08-22
|
|
9
29
|
|
|
10
30
|
### Added
|
|
@@ -16,5 +36,6 @@ All notable changes to SSRWire are documented here. The project follows
|
|
|
16
36
|
- Terminal, JSON, and SARIF reports with CI-safe exit codes.
|
|
17
37
|
- YAML configuration, one-off URL checks, Docker support, and GitHub Actions examples.
|
|
18
38
|
|
|
19
|
-
[Unreleased]: https://github.com/lame13/ssrwire/compare/v0.
|
|
39
|
+
[Unreleased]: https://github.com/lame13/ssrwire/compare/v0.2.0...HEAD
|
|
40
|
+
[0.2.0]: https://github.com/lame13/ssrwire/compare/v0.1.0...v0.2.0
|
|
20
41
|
[0.1.0]: https://github.com/lame13/ssrwire/releases/tag/v0.1.0
|
package/CONTRIBUTING.md
CHANGED
|
@@ -29,6 +29,7 @@ Keep each change narrow. Include tests for behavior changes, especially for:
|
|
|
29
29
|
- redirects, timeouts, aborted bodies, and size limits;
|
|
30
30
|
- malformed, duplicated, late, or body-located metadata;
|
|
31
31
|
- user-agent differences and comparison findings;
|
|
32
|
+
- repeated-sample ordering, aggregation, and instability classification;
|
|
32
33
|
- redaction of configured header values;
|
|
33
34
|
- terminal, JSON, SARIF, and exit-code behavior.
|
|
34
35
|
|
|
@@ -39,11 +40,12 @@ positions instead.
|
|
|
39
40
|
|
|
40
41
|
## Scope
|
|
41
42
|
|
|
42
|
-
SSRWire is an HTTP stream observer
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
buffering can coalesce data
|
|
43
|
+
SSRWire is an HTTP stream observer and bounded consistency sampler, not a
|
|
44
|
+
browser, crawler, packet capture, load generator, performance benchmark, or
|
|
45
|
+
framework plugin. New features should preserve that boundary. In particular,
|
|
46
|
+
avoid conclusions that claim to reveal the server's original flush calls:
|
|
47
|
+
proxies, compression, TLS, HTTP stacks, and client buffering can coalesce data
|
|
48
|
+
before SSRWire sees it.
|
|
47
49
|
|
|
48
50
|
By contributing, you agree that your contribution is licensed under the MIT
|
|
49
51
|
License.
|
package/PUBLISHING.md
CHANGED
|
@@ -1,109 +1,124 @@
|
|
|
1
|
-
# Publish SSRWire from a local machine
|
|
1
|
+
# Publish SSRWire 0.2.0 from a local machine
|
|
2
2
|
|
|
3
|
-
This repository intentionally includes no npm publishing workflow.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
release
|
|
3
|
+
This repository intentionally includes no npm publishing workflow. Publish from
|
|
4
|
+
a foreground local terminal only after the GitHub `CI` workflow passes. Do not
|
|
5
|
+
configure an `NPM_TOKEN`, trusted publisher, OIDC identity, or automated npm
|
|
6
|
+
release job.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
The `main` branch is protected. Land every release change through a pull
|
|
9
|
+
request, and do not use an administrator bypass or a direct push to `main`.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
## 1. Update the existing repository
|
|
12
|
+
|
|
13
|
+
Work from a clean clone of the existing public repository. Copy the 0.2.0
|
|
14
|
+
source files into that clone while preserving its `.git` directory.
|
|
11
15
|
|
|
12
16
|
```bash
|
|
13
|
-
unzip ssrwire.zip # skip when already inside a source checkout
|
|
14
17
|
cd ssrwire
|
|
18
|
+
git switch main
|
|
19
|
+
git pull --ff-only origin main
|
|
20
|
+
git status --short
|
|
21
|
+
git switch -c release/0.2.0
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`git status --short` must be empty before creating the release branch and
|
|
25
|
+
applying the release files.
|
|
26
|
+
|
|
27
|
+
## 2. Verify the release locally
|
|
28
|
+
|
|
29
|
+
```bash
|
|
15
30
|
nvm use 24
|
|
16
31
|
node --version
|
|
17
32
|
npm --version
|
|
18
33
|
npm ci
|
|
19
34
|
npm run check
|
|
20
35
|
npm pack --dry-run
|
|
36
|
+
node dist/bin.js --version
|
|
21
37
|
```
|
|
22
38
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
brew install gh
|
|
27
|
-
gh auth login -h github.com --web
|
|
28
|
-
gh auth status -h github.com
|
|
29
|
-
```
|
|
39
|
+
The final command must print `0.2.0`. Inspect the dry-run file list. It must not
|
|
40
|
+
contain `.env`, `.github`, `node_modules`, `test`, ZIP files, or tarballs.
|
|
30
41
|
|
|
31
|
-
|
|
42
|
+
Review the release diff and version references:
|
|
32
43
|
|
|
33
44
|
```bash
|
|
34
|
-
git
|
|
35
|
-
git
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
biome.json examples package.json package-lock.json scripts src test \
|
|
39
|
-
tsconfig.build.json tsconfig.json vitest.config.ts
|
|
40
|
-
git commit -m "Initial SSRWire release"
|
|
45
|
+
git diff --check
|
|
46
|
+
git diff --stat
|
|
47
|
+
git diff -- package.json package-lock.json CHANGELOG.md README.md PUBLISHING.md
|
|
48
|
+
rg '0\.1\.0' README.md examples package.json package-lock.json src test
|
|
41
49
|
```
|
|
42
50
|
|
|
43
|
-
|
|
51
|
+
The final search should return nothing. Historical entries in `CHANGELOG.md`
|
|
52
|
+
are excluded intentionally.
|
|
53
|
+
|
|
54
|
+
## 3. Commit, push the release branch, and open a pull request
|
|
44
55
|
|
|
45
56
|
```bash
|
|
46
|
-
gh
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
gh auth status -h github.com || gh auth login -h github.com --web
|
|
58
|
+
git add --all
|
|
59
|
+
git diff --cached --check
|
|
60
|
+
git diff --cached --stat
|
|
61
|
+
git commit -m "feat: release SSRWire 0.2.0"
|
|
62
|
+
git push --set-upstream origin release/0.2.0
|
|
63
|
+
gh pr create --base main --head release/0.2.0 --fill
|
|
64
|
+
PR_NUMBER="$(gh pr view release/0.2.0 --json number --jq .number)"
|
|
65
|
+
test -n "$PR_NUMBER"
|
|
66
|
+
gh pr checks "$PR_NUMBER" --watch --fail-fast
|
|
53
67
|
```
|
|
54
68
|
|
|
55
|
-
|
|
69
|
+
Do not merge while any Node, package-smoke, or Docker job is failing. Satisfy
|
|
70
|
+
all review and branch-protection requirements, then merge with GitHub's allowed
|
|
71
|
+
strategy. Running `gh pr merge "$PR_NUMBER"` without a strategy flag lets the
|
|
72
|
+
CLI prompt for one of the repository's permitted methods. Do not select an
|
|
73
|
+
administrator bypass.
|
|
56
74
|
|
|
57
75
|
```bash
|
|
58
|
-
gh
|
|
59
|
-
--add-topic technical-seo \
|
|
60
|
-
--add-topic seo \
|
|
61
|
-
--add-topic ssr \
|
|
62
|
-
--add-topic streaming-html \
|
|
63
|
-
--add-topic nextjs \
|
|
64
|
-
--add-topic nuxt \
|
|
65
|
-
--add-topic astro \
|
|
66
|
-
--add-topic crawler \
|
|
67
|
-
--add-topic typescript \
|
|
68
|
-
--add-topic cli \
|
|
69
|
-
--add-topic seo-tools \
|
|
70
|
-
--add-topic github-actions \
|
|
71
|
-
--add-topic sarif
|
|
76
|
+
gh pr merge "$PR_NUMBER"
|
|
72
77
|
```
|
|
73
78
|
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
After GitHub reports the pull request as merged, update local `main` and wait
|
|
80
|
+
for the CI run on the exact merged commit:
|
|
76
81
|
|
|
77
82
|
```bash
|
|
78
|
-
|
|
83
|
+
git switch main
|
|
84
|
+
git pull --ff-only origin main
|
|
85
|
+
git status --short
|
|
86
|
+
gh pr view "$PR_NUMBER" --json state,mergedAt,mergeCommit
|
|
87
|
+
COMMIT_SHA="$(git rev-parse HEAD)"
|
|
88
|
+
RUN_ID="$(gh run list --workflow CI --branch main --commit "$COMMIT_SHA" \
|
|
89
|
+
--limit 1 --json databaseId --jq '.[0].databaseId')"
|
|
90
|
+
test -n "$RUN_ID"
|
|
91
|
+
gh run watch "$RUN_ID" --exit-status
|
|
79
92
|
```
|
|
80
93
|
|
|
81
|
-
|
|
82
|
-
|
|
94
|
+
`git status --short` must be empty. Do not publish until the merged-commit CI
|
|
95
|
+
run succeeds.
|
|
83
96
|
|
|
84
|
-
|
|
97
|
+
## 4. Verify npm state
|
|
85
98
|
|
|
86
|
-
|
|
99
|
+
```bash
|
|
100
|
+
npm view ssrwire version dist-tags homepage keywords repository.url --json
|
|
101
|
+
npm config get registry
|
|
102
|
+
npm config get provenance
|
|
103
|
+
```
|
|
87
104
|
|
|
88
|
-
|
|
105
|
+
The published version and `latest` tag must still be `0.1.0`. If npm already
|
|
106
|
+
reports `0.2.0`, stop: never reuse a version that npm accepted.
|
|
89
107
|
|
|
90
|
-
|
|
108
|
+
In the npm package settings, select **Require two-factor authentication and
|
|
109
|
+
disallow tokens**. npm documents this as the strongest package publishing
|
|
110
|
+
setting:
|
|
91
111
|
|
|
92
|
-
|
|
93
|
-
|
|
112
|
+
- <https://docs.npmjs.com/requiring-2fa-for-package-publishing-and-settings-modification/>
|
|
113
|
+
- <https://docs.npmjs.com/about-two-factor-authentication/>
|
|
94
114
|
|
|
95
|
-
|
|
96
|
-
npm ci
|
|
97
|
-
npm run check
|
|
98
|
-
npm pack --dry-run
|
|
99
|
-
npm view ssrwire
|
|
100
|
-
```
|
|
115
|
+
## 5. Publish interactively
|
|
101
116
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
session remains fresh for publication:
|
|
117
|
+
Remove inherited automation credentials before starting the interactive
|
|
118
|
+
session:
|
|
105
119
|
|
|
106
120
|
```bash
|
|
121
|
+
unset NODE_AUTH_TOKEN NPM_TOKEN NPM_CONFIG_OTP npm_config_otp
|
|
107
122
|
npm login --auth-type=web --registry=https://registry.npmjs.org
|
|
108
123
|
npm whoami --registry=https://registry.npmjs.org
|
|
109
124
|
npm publish --access public --registry=https://registry.npmjs.org
|
|
@@ -111,24 +126,23 @@ npm view ssrwire version dist-tags homepage keywords repository.url --json
|
|
|
111
126
|
npm logout --registry=https://registry.npmjs.org
|
|
112
127
|
```
|
|
113
128
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
129
|
+
Complete npm's browser, passkey, or two-factor-authentication challenge when
|
|
130
|
+
prompted. Do not use a token with bypass 2FA, put an OTP in a command argument,
|
|
131
|
+
or add an npm credential to the repository or GitHub Actions. The package-level
|
|
132
|
+
"disallow tokens" setting ensures that publication remains interactive.
|
|
118
133
|
|
|
119
|
-
If
|
|
120
|
-
|
|
121
|
-
version again.
|
|
134
|
+
If npm's publish-time scanning delays package visibility, wait. Do not publish
|
|
135
|
+
`0.2.0` again or change the tag to work around propagation.
|
|
122
136
|
|
|
123
|
-
|
|
137
|
+
## 6. Tag the exact published commit
|
|
124
138
|
|
|
125
139
|
```bash
|
|
126
140
|
node scripts/clean.mjs
|
|
127
141
|
rm -rf node_modules/.vite
|
|
128
142
|
git status --short
|
|
129
|
-
git tag -a v0.
|
|
130
|
-
git push origin v0.
|
|
131
|
-
gh release create v0.
|
|
143
|
+
git tag -a v0.2.0 -m "SSRWire v0.2.0"
|
|
144
|
+
git push origin v0.2.0
|
|
145
|
+
gh release create v0.2.0 --generate-notes --title "SSRWire v0.2.0"
|
|
132
146
|
```
|
|
133
147
|
|
|
134
|
-
`git status --short` must print nothing before tagging
|
|
148
|
+
`git status --short` must print nothing before tagging.
|
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# SSRWire
|
|
2
2
|
|
|
3
3
|
[](https://github.com/lame13/ssrwire/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/ssrwire)
|
|
4
5
|
[](LICENSE)
|
|
5
6
|
|
|
6
7
|
Inspect streamed SSR HTML, metadata timing, and crawler-specific delivery from
|
|
@@ -25,6 +26,7 @@ Modern SSR output is not always one complete HTML document delivered at once:
|
|
|
25
26
|
crawler while blocking for an HTML-limited bot;
|
|
26
27
|
- browser, search-crawler, and social-crawler user agents may receive different
|
|
27
28
|
titles, canonicals, robots directives, redirects, or statuses;
|
|
29
|
+
- the same URL and user agent may receive inconsistent SSR output between requests;
|
|
28
30
|
- an interrupted or oversized stream may never deliver the expected elements;
|
|
29
31
|
- a working hydrated page can hide thin or incomplete source HTML.
|
|
30
32
|
|
|
@@ -84,7 +86,7 @@ form of `npx ssrwire check URL`.
|
|
|
84
86
|
|
|
85
87
|
## What it observes
|
|
86
88
|
|
|
87
|
-
For each target and
|
|
89
|
+
For each target, agent, and configured sample, SSRWire captures:
|
|
88
90
|
|
|
89
91
|
- response status, final URL, redirect chain, and an allowlisted response-header snapshot;
|
|
90
92
|
- time to response headers, first response-body bytes, and completed body;
|
|
@@ -109,6 +111,9 @@ It then checks:
|
|
|
109
111
|
| JSON-LD block/count exceeds the bounded analysis budget | Warning |
|
|
110
112
|
| Critical metadata in `<body>` for a profile that requires head metadata | Error |
|
|
111
113
|
| Status, final URL, title, canonical, or robots drift between profiles | Warning |
|
|
114
|
+
| Completion, status, final URL, or redirect-chain drift between samples | Warning |
|
|
115
|
+
| Metadata value or document-location drift between complete samples | Warning |
|
|
116
|
+
| Exact body fingerprint drift without metadata drift | Information |
|
|
112
117
|
| First byte or required-signal arrival over a configured limit | Warning |
|
|
113
118
|
| Timeout, truncation, network error, or another incomplete probe | Error / incomplete run |
|
|
114
119
|
|
|
@@ -198,6 +203,7 @@ headers:
|
|
|
198
203
|
timeoutMs: 15000
|
|
199
204
|
maxBytes: 10485760
|
|
200
205
|
maxRedirects: 10
|
|
206
|
+
repeat: 1
|
|
201
207
|
```
|
|
202
208
|
|
|
203
209
|
A target can also be a plain URL string when defaults are sufficient:
|
|
@@ -215,7 +221,8 @@ Defaults:
|
|
|
215
221
|
- agents: `browser`, `googlebot`, `bingbot`, and `twitterbot`;
|
|
216
222
|
- timeout: 15 seconds per probe;
|
|
217
223
|
- response limit: 10 MiB;
|
|
218
|
-
- redirect limit: 10
|
|
224
|
+
- redirect limit: 10;
|
|
225
|
+
- samples per target and agent: 1, with an allowed range of 1–10.
|
|
219
226
|
|
|
220
227
|
Unknown configuration keys are rejected. URLs must be absolute HTTP or HTTPS
|
|
221
228
|
URLs and cannot contain embedded credentials.
|
|
@@ -276,6 +283,38 @@ transfer-size or packet-boundary evidence. They count bytes delivered by the
|
|
|
276
283
|
Fetch implementation to SSRWire; those bytes are post-content-decoding when a
|
|
277
284
|
server ignores SSRWire's `Accept-Encoding: identity` request.
|
|
278
285
|
|
|
286
|
+
## Repeated sampling
|
|
287
|
+
|
|
288
|
+
Use repeated sampling when one successful request does not prove that SSR output
|
|
289
|
+
is stable:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
npx ssrwire check --repeat 3
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
`repeat` is the total number of samples, not a retry count. SSRWire retains
|
|
296
|
+
failures instead of replacing them with a later success. Samples for one
|
|
297
|
+
target-agent pair run sequentially; different target-agent pairs may still run
|
|
298
|
+
concurrently. SSRWire does not add delays, cache-busting parameters, or special
|
|
299
|
+
cache headers.
|
|
300
|
+
|
|
301
|
+
The request count is `targets × agents × repeat`, plus redirect hops. Configured
|
|
302
|
+
same-origin headers are sent for every sample and retain the existing
|
|
303
|
+
cross-origin stripping and report-redaction behavior.
|
|
304
|
+
|
|
305
|
+
For repeated runs, terminal reports include individual sample numbers and a
|
|
306
|
+
per-agent timing table. JSON retains every probe and adds per-agent stability
|
|
307
|
+
summaries. The summaries report sample count, minimum, median, nearest-rank p95,
|
|
308
|
+
maximum, and spread for available header, first-byte, required-signal, and
|
|
309
|
+
completion timings. With small sample counts, nearest-rank p95 will often equal
|
|
310
|
+
the maximum.
|
|
311
|
+
|
|
312
|
+
Timing spread alone is evidence, not a failure. Network and cache variation can
|
|
313
|
+
change timings without changing the response contract. SSRWire warns when HTTP
|
|
314
|
+
response evidence or streamed metadata changes across samples. Exact body-hash
|
|
315
|
+
variation by itself is informational because timestamps, nonces, and other
|
|
316
|
+
legitimate dynamic values commonly change source HTML.
|
|
317
|
+
|
|
279
318
|
## CLI reference
|
|
280
319
|
|
|
281
320
|
```text
|
|
@@ -294,6 +333,7 @@ Check options:
|
|
|
294
333
|
| `--timeout <ms>` | Override request timeout |
|
|
295
334
|
| `--max-bytes <bytes>` | Override response-body limit |
|
|
296
335
|
| `--max-redirects <count>` | Override redirect limit |
|
|
336
|
+
| `--repeat <count>` | Run 1–10 sequential samples per URL and agent |
|
|
297
337
|
| `-f, --format <format>` | `terminal`, `json`, or `sarif` |
|
|
298
338
|
| `-o, --output <path>` | Write the report to a file |
|
|
299
339
|
| `--fail-on <level>` | `error`, `warning`, or `never` |
|
|
@@ -304,9 +344,9 @@ removed.
|
|
|
304
344
|
|
|
305
345
|
## Reports and exit codes
|
|
306
346
|
|
|
307
|
-
- `terminal`: compact
|
|
347
|
+
- `terminal`: compact sample, aggregate-timing, and finding tables for local use.
|
|
308
348
|
- `json`: structured machine-readable evidence, including every probe and timing
|
|
309
|
-
signal.
|
|
349
|
+
signal plus repeated-run stability summaries.
|
|
310
350
|
- `sarif`: findings suitable for GitHub Code Scanning and other SARIF 2.1.0
|
|
311
351
|
consumers.
|
|
312
352
|
|
|
@@ -369,7 +409,7 @@ by the CLI:
|
|
|
369
409
|
```ts
|
|
370
410
|
import { loadConfig, renderJson, runAudit } from "ssrwire";
|
|
371
411
|
|
|
372
|
-
const config = await loadConfig({ urls: ["https://example.com/"] });
|
|
412
|
+
const config = await loadConfig({ urls: ["https://example.com/"], repeat: 3 });
|
|
373
413
|
const audit = await runAudit(config);
|
|
374
414
|
process.stdout.write(renderJson(audit));
|
|
375
415
|
```
|
|
@@ -381,14 +421,16 @@ versions.
|
|
|
381
421
|
## Scope
|
|
382
422
|
|
|
383
423
|
SSRWire does not execute JavaScript, inspect a hydrated DOM, measure Core Web
|
|
384
|
-
Vitals, discover URLs, validate indexing, bypass access controls,
|
|
385
|
-
crawler's rendering pipeline. Use
|
|
386
|
-
|
|
387
|
-
navigation.
|
|
424
|
+
Vitals, discover URLs, validate indexing, bypass access controls, perform load
|
|
425
|
+
testing, or emulate a crawler's rendering pipeline. Use
|
|
426
|
+
[RoutePlay](https://github.com/lame13/routeplay) for server HTML versus a cold
|
|
427
|
+
browser versus real client-side navigation. Use
|
|
428
|
+
[RouteLint](https://github.com/lame13/routelint) for route discovery,
|
|
429
|
+
indexability, and technical SEO policy.
|
|
388
430
|
|
|
389
431
|
Run SSRWire only against targets you are authorized to inspect. Keep target
|
|
390
|
-
lists intentionally small
|
|
391
|
-
|
|
432
|
+
lists and repeat counts intentionally small. It is a consistency sampler, not a
|
|
433
|
+
load generator.
|
|
392
434
|
|
|
393
435
|
## Development
|
|
394
436
|
|
package/SECURITY.md
CHANGED
|
@@ -20,7 +20,11 @@ report. You should receive an initial response within seven days.
|
|
|
20
20
|
## Operational safety
|
|
21
21
|
|
|
22
22
|
SSRWire sends real HTTP requests to every configured target for every selected
|
|
23
|
-
agent profile
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
agent profile and sample. The base request count is
|
|
24
|
+
`targets × agents × repeat`, plus redirect hops. Samples for one target-agent
|
|
25
|
+
pair are sequential, but different pairs may run concurrently. Keep `repeat`
|
|
26
|
+
bounded and use SSRWire only against systems you are authorized to test.
|
|
27
|
+
|
|
28
|
+
Treat custom headers as secrets and remember that they are sent on every
|
|
29
|
+
same-origin sample. Prefer environment-variable interpolation, and do not
|
|
30
|
+
commit populated `.env` files or generated reports containing private URLs.
|
package/dist/audit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,WAAW,EAIX,aAAa,EAEd,MAAM,YAAY,CAAC;AAgHpB,wBAAsB,QAAQ,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,CAAC,CAyE1E"}
|
package/dist/audit.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { analyzeTarget, summarizeAudit } from "./analyze.js";
|
|
2
2
|
import { probeUrl } from "./http-probe.js";
|
|
3
3
|
import { redactAudit } from "./redact.js";
|
|
4
|
+
import { analyzeStability } from "./stability.js";
|
|
4
5
|
import { VERSION } from "./version.js";
|
|
5
6
|
const DEFAULT_CONCURRENCY = 4;
|
|
6
7
|
async function runPool(inputs, limit, worker) {
|
|
@@ -21,8 +22,63 @@ async function runPool(inputs, limit, worker) {
|
|
|
21
22
|
await Promise.all(workers);
|
|
22
23
|
return results;
|
|
23
24
|
}
|
|
25
|
+
function repeatCount(value) {
|
|
26
|
+
const repeat = value ?? 1;
|
|
27
|
+
if (!Number.isInteger(repeat) || repeat < 1 || repeat > 10) {
|
|
28
|
+
throw new RangeError("repeat must be an integer between 1 and 10.");
|
|
29
|
+
}
|
|
30
|
+
return repeat;
|
|
31
|
+
}
|
|
32
|
+
function mergeEvidence(sampled, repeat) {
|
|
33
|
+
const merged = {};
|
|
34
|
+
const keys = [
|
|
35
|
+
...new Set(sampled.flatMap(({ finding }) => Object.keys(finding.evidence ?? {}))),
|
|
36
|
+
].sort();
|
|
37
|
+
for (const key of keys) {
|
|
38
|
+
const values = sampled.flatMap(({ finding }) => {
|
|
39
|
+
const value = finding.evidence?.[key];
|
|
40
|
+
return value === undefined ? [] : [value];
|
|
41
|
+
});
|
|
42
|
+
const unique = [
|
|
43
|
+
...new Map(values.map((value) => [`${typeof value}:${String(value)}`, value])).values(),
|
|
44
|
+
];
|
|
45
|
+
const first = unique[0];
|
|
46
|
+
if (first !== undefined) {
|
|
47
|
+
merged[key] = unique.length === 1 ? first : unique.map(String).join(" | ");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
merged.sampleNumbers = [...new Set(sampled.map((item) => item.sample))]
|
|
51
|
+
.sort((a, b) => a - b)
|
|
52
|
+
.join(", ");
|
|
53
|
+
merged.occurrences = sampled.length;
|
|
54
|
+
merged.totalSamples = repeat;
|
|
55
|
+
return merged;
|
|
56
|
+
}
|
|
57
|
+
function coalesceFindings(sampled, repeat) {
|
|
58
|
+
const groups = new Map();
|
|
59
|
+
for (const item of sampled) {
|
|
60
|
+
const { finding } = item;
|
|
61
|
+
const key = JSON.stringify([
|
|
62
|
+
finding.code,
|
|
63
|
+
finding.severity,
|
|
64
|
+
finding.message,
|
|
65
|
+
finding.url,
|
|
66
|
+
finding.agent ?? "",
|
|
67
|
+
]);
|
|
68
|
+
const group = groups.get(key) ?? [];
|
|
69
|
+
group.push(item);
|
|
70
|
+
groups.set(key, group);
|
|
71
|
+
}
|
|
72
|
+
return [...groups.values()].map((group) => {
|
|
73
|
+
const first = group[0]?.finding;
|
|
74
|
+
if (first === undefined)
|
|
75
|
+
throw new Error("Cannot coalesce an empty finding group.");
|
|
76
|
+
return { ...first, evidence: mergeEvidence(group, repeat) };
|
|
77
|
+
});
|
|
78
|
+
}
|
|
24
79
|
export async function runAudit(config) {
|
|
25
80
|
const started = performance.now();
|
|
81
|
+
const repeat = repeatCount(config.repeat);
|
|
26
82
|
const tasks = [];
|
|
27
83
|
for (const [targetIndex, target] of config.targets.entries()) {
|
|
28
84
|
for (const [agentIndex, agent] of config.agents.entries()) {
|
|
@@ -42,25 +98,44 @@ export async function runAudit(config) {
|
|
|
42
98
|
}
|
|
43
99
|
}
|
|
44
100
|
const secrets = Object.values(config.headers);
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
101
|
+
const lanes = await runPool(tasks, DEFAULT_CONCURRENCY, async (task) => {
|
|
102
|
+
const probes = [];
|
|
103
|
+
for (let sample = 1; sample <= repeat; sample += 1) {
|
|
104
|
+
const probe = await probeUrl(task.options);
|
|
105
|
+
probes.push(repeat === 1 ? probe : { ...probe, sample });
|
|
106
|
+
}
|
|
107
|
+
return { task, probes };
|
|
48
108
|
});
|
|
49
109
|
const results = config.targets.map((target, targetIndex) => {
|
|
50
|
-
const targetProbes =
|
|
51
|
-
.filter((
|
|
110
|
+
const targetProbes = lanes
|
|
111
|
+
.filter((lane) => lane.task.targetIndex === targetIndex)
|
|
52
112
|
.sort((a, b) => a.task.agentIndex - b.task.agentIndex)
|
|
53
|
-
.
|
|
113
|
+
.flatMap((lane) => lane.probes);
|
|
114
|
+
if (repeat === 1) {
|
|
115
|
+
return {
|
|
116
|
+
target,
|
|
117
|
+
probes: targetProbes,
|
|
118
|
+
findings: analyzeTarget(target, targetProbes),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const sampledFindings = [];
|
|
122
|
+
for (let sample = 1; sample <= repeat; sample += 1) {
|
|
123
|
+
const sampleProbes = targetProbes.filter((probe) => probe.sample === sample);
|
|
124
|
+
sampledFindings.push(...analyzeTarget(target, sampleProbes).map((finding) => ({ sample, finding })));
|
|
125
|
+
}
|
|
126
|
+
const stability = analyzeStability(target, targetProbes);
|
|
54
127
|
return {
|
|
55
128
|
target,
|
|
56
129
|
probes: targetProbes,
|
|
57
|
-
findings:
|
|
130
|
+
findings: [...coalesceFindings(sampledFindings, repeat), ...stability.findings],
|
|
131
|
+
stability: stability.stability,
|
|
58
132
|
};
|
|
59
133
|
});
|
|
60
134
|
const audit = {
|
|
61
135
|
version: VERSION,
|
|
62
136
|
generatedAt: new Date().toISOString(),
|
|
63
137
|
durationMs: Math.round(performance.now() - started),
|
|
138
|
+
...(repeat === 1 ? {} : { repeat }),
|
|
64
139
|
results,
|
|
65
140
|
summary: summarizeAudit(results),
|
|
66
141
|
};
|
package/dist/audit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAsKA,wBAAsB,IAAI,CAAC,IAAI,GAAE,SAAS,MAAM,EAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4ChF"}
|
package/dist/cli.js
CHANGED
|
@@ -25,6 +25,7 @@ agents:
|
|
|
25
25
|
timeoutMs: 15000
|
|
26
26
|
maxBytes: 10485760
|
|
27
27
|
maxRedirects: 10
|
|
28
|
+
repeat: 1
|
|
28
29
|
|
|
29
30
|
# Keep preview credentials in environment variables. SSRWire redacts configured values from reports.
|
|
30
31
|
# headers:
|
|
@@ -60,6 +61,7 @@ function addCheckOptions(command) {
|
|
|
60
61
|
.option("--timeout <ms>", "request timeout in milliseconds", parseInteger)
|
|
61
62
|
.option("--max-bytes <bytes>", "maximum response bytes", parseInteger)
|
|
62
63
|
.option("--max-redirects <count>", "maximum redirects", parseInteger)
|
|
64
|
+
.option("--repeat <count>", "sequential samples per URL and agent", parseInteger)
|
|
63
65
|
.option("-f, --format <format>", "terminal, json, or sarif", parseFormat, "terminal")
|
|
64
66
|
.option("-o, --output <path>", "write the report to a file")
|
|
65
67
|
.option("--fail-on <level>", "error, warning, or never", parseFailOn, "error")
|
|
@@ -97,6 +99,7 @@ async function check(urls, options) {
|
|
|
97
99
|
...(options.timeout === undefined ? {} : { timeoutMs: options.timeout }),
|
|
98
100
|
...(options.maxBytes === undefined ? {} : { maxBytes: options.maxBytes }),
|
|
99
101
|
...(options.maxRedirects === undefined ? {} : { maxRedirects: options.maxRedirects }),
|
|
102
|
+
...(options.repeat === undefined ? {} : { repeat: options.repeat }),
|
|
100
103
|
});
|
|
101
104
|
const audit = await runAudit(config);
|
|
102
105
|
const color = options.color &&
|