texas2step 2.2.3__tar.gz → 2.3.0__tar.gz

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 (68) hide show
  1. {texas2step-2.2.3 → texas2step-2.3.0}/PKG-INFO +1 -1
  2. {texas2step-2.2.3 → texas2step-2.3.0}/docs/access.md +152 -23
  3. {texas2step-2.2.3 → texas2step-2.3.0}/docs/api.md +17 -12
  4. {texas2step-2.2.3 → texas2step-2.3.0}/docs/configuration.md +4 -2
  5. {texas2step-2.2.3 → texas2step-2.3.0}/docs/install.md +35 -8
  6. {texas2step-2.2.3 → texas2step-2.3.0}/docs/methodology.md +8 -1
  7. {texas2step-2.2.3 → texas2step-2.3.0}/docs/roadmap.md +23 -0
  8. {texas2step-2.2.3 → texas2step-2.3.0}/docs/security.md +33 -4
  9. {texas2step-2.2.3 → texas2step-2.3.0}/docs/traffic.md +3 -1
  10. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/__init__.py +1 -1
  11. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/access.py +47 -20
  12. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/api/access.py +104 -27
  13. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/api/meta.py +16 -6
  14. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/api/results.py +10 -0
  15. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/api/wire.py +25 -3
  16. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/app.py +130 -33
  17. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/cli.py +190 -12
  18. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/cluster.py +30 -5
  19. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/config.py +14 -4
  20. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/config_template.toml +4 -1
  21. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/lockouts.py +175 -9
  22. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/rtc.py +24 -5
  23. texas2step-2.3.0/src/texas2step/static/assets/app.css +1 -0
  24. texas2step-2.3.0/src/texas2step/static/assets/app.js +8 -0
  25. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/static/index.html +2 -2
  26. texas2step-2.2.3/src/texas2step/static/assets/app.css +0 -1
  27. texas2step-2.2.3/src/texas2step/static/assets/app.js +0 -8
  28. {texas2step-2.2.3 → texas2step-2.3.0}/.gitignore +0 -0
  29. {texas2step-2.2.3 → texas2step-2.3.0}/LICENSE.md +0 -0
  30. {texas2step-2.2.3 → texas2step-2.3.0}/README.md +0 -0
  31. {texas2step-2.2.3 → texas2step-2.3.0}/deploy/nginx/texas2step-http.conf +0 -0
  32. {texas2step-2.2.3 → texas2step-2.3.0}/deploy/nginx/texas2step-log.conf +0 -0
  33. {texas2step-2.2.3 → texas2step-2.3.0}/deploy/nginx/texas2step-speedtest.conf +0 -0
  34. {texas2step-2.2.3 → texas2step-2.3.0}/deploy/nginx/texas2step.conf +0 -0
  35. {texas2step-2.2.3 → texas2step-2.3.0}/deploy/texas2step-update.service +0 -0
  36. {texas2step-2.2.3 → texas2step-2.3.0}/deploy/texas2step-update.timer +0 -0
  37. {texas2step-2.2.3 → texas2step-2.3.0}/deploy/texas2step.default +0 -0
  38. {texas2step-2.2.3 → texas2step-2.3.0}/deploy/texas2step.service +0 -0
  39. {texas2step-2.2.3 → texas2step-2.3.0}/docs/development.md +0 -0
  40. {texas2step-2.2.3 → texas2step-2.3.0}/docs/experience.md +0 -0
  41. {texas2step-2.2.3 → texas2step-2.3.0}/docs/images/hero-dark.jpg +0 -0
  42. {texas2step-2.2.3 → texas2step-2.3.0}/docs/images/results-dark.jpg +0 -0
  43. {texas2step-2.2.3 → texas2step-2.3.0}/docs/images/results-light.jpg +0 -0
  44. {texas2step-2.2.3 → texas2step-2.3.0}/docs/images/upload-light.jpg +0 -0
  45. {texas2step-2.2.3 → texas2step-2.3.0}/legacy/README.md +0 -0
  46. {texas2step-2.2.3 → texas2step-2.3.0}/pyproject.toml +0 -0
  47. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/__main__.py +0 -0
  48. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/api/__init__.py +0 -0
  49. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/api/measure.py +0 -0
  50. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/config_edit.py +0 -0
  51. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/config_merge.py +0 -0
  52. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/engine.py +0 -0
  53. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/iface.py +0 -0
  54. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/logs.py +0 -0
  55. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/page.py +0 -0
  56. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/payload.py +0 -0
  57. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/quality.py +0 -0
  58. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/static/assets/swagger/LICENSE +0 -0
  59. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/static/assets/swagger/NOTICE +0 -0
  60. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/static/assets/swagger/swagger-ui-bundle.js +0 -0
  61. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/static/assets/swagger/swagger-ui-bundle.js.LICENSE.txt +0 -0
  62. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/static/assets/swagger/swagger-ui.css +0 -0
  63. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/static/favicon.svg +0 -0
  64. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/stats.py +0 -0
  65. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/store.py +0 -0
  66. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/traffic.py +0 -0
  67. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/update.py +0 -0
  68. {texas2step-2.2.3 → texas2step-2.3.0}/src/texas2step/words.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: texas2step
3
- Version: 2.2.3
3
+ Version: 2.3.0
4
4
  Summary: Web-centric throughput, latency, jitter and packet-loss measurement between endpoints.
5
5
  Project-URL: Homepage, https://github.com/robert-patrick-texas/texas2step
6
6
  Project-URL: Documentation, https://robert-patrick-texas.github.io/texas2step/
@@ -18,7 +18,8 @@ no user names or passwords. The desk's own tools stay on your networks: saved re
18
18
  works out the same code from it, with nothing shared between them while they run.
19
19
  * **A code buys a pass.** The visitor enters the code once. The server answers with a
20
20
  pass, good for 12 hours (`access_pass_hours`), which the browser then sends with every
21
- request, to every server in the picker. A server with the same key accepts it.
21
+ request, to every server in the picker. A server with the same key accepts it. To end
22
+ every pass at once, see [Ending every pass](#ending-every-pass).
22
23
  * **The desk's tools stay inside.** Visitors from addresses in `internal_networks` need no
23
24
  code, and only they get History, the API tab, saved results and the code itself.
24
25
  Everyone else gets the Test, Monitor and Diag tabs.
@@ -88,7 +89,8 @@ To turn codes off again: `sudo texas2step config set access_code=off --write`, t
88
89
  | `access_timezone` | `America/New_York` | The time zone whose midnight starts a new code; the same on every server |
89
90
  | `access_words` | `""` | Your own word list; empty uses the built-in words |
90
91
  | `access_digits` | `2` | The digits that end a code: 2, 3 or 4 |
91
- | `access_pass_hours` | `12` | How long an entered code lets a browser keep testing |
92
+ | `access_pass_hours` | `12` | How long a pass lasts from when it was issued (1 to 168). Each server applies its own as passes are used, so a change applies to passes already given; use the same on every server. |
93
+ | `access_note` | `Contact the service desk for assistance.` | The line under the code box, such as how to reach the desk; `""` for none |
92
94
  | `access_key_file` | `/etc/texas2step/access.key` | The key |
93
95
  | `access_phrases_file` | `/etc/texas2step/access-phrases.toml` | [Standing phrases](#standing-phrases), if the file exists |
94
96
  | `internal_networks` | `[]` | Your own networks: no code, and the desk's tools. A list of IPv4 and IPv6 addresses and networks in CIDR form, mixed freely: `["192.168.0.0/16", "2001:db8::/32", "203.0.113.7"]`. An address alone is a single host. Bits past the prefix are ignored, so `10.2.1.30/24` means all of `10.2.1.0/24`. Give the addresses as the server sees them: staff who come in through your firewall's NAT arrive from its public address. |
@@ -112,8 +114,9 @@ tests until it changes, but for a short list, or a `fixed` code, consider
112
114
  `access_digits = 3` or `4`.
113
115
 
114
116
  **When it changes.** A new code starts at midnight in `access_timezone`. The previous
115
- code keeps working for 24 hours after the change, for a call that spans midnight. A
116
- `fixed` code changes only with a new key:
117
+ code keeps working for 24 hours after the change, for a call that spans midnight. To
118
+ change it before then, or to change a `fixed` code, [shuffle the codes](#shuffling-the-codes),
119
+ or make a new key:
117
120
 
118
121
  ```bash
119
122
  sudo texas2step access key --new
@@ -123,6 +126,42 @@ That makes a new key at once: every code and pass from the old one stops working
123
126
  the new key to the other servers as in step 1, and restart the service on each. Without
124
127
  `--yes`, it asks first.
125
128
 
129
+ ### Shuffling the codes
130
+
131
+ When a code has gone further than it should, shuffle the codes. The current code and the
132
+ previous one stop working at once, and a new one takes their place:
133
+
134
+ ```console
135
+ $ sudo texas2step access shuffle
136
+ Shuffle the codes? The current and previous codes stop working, and the desk reads the new one. [y/N] y
137
+ Codes shuffled here. Today's code is now copper.lantern.42; the codes before it don't work.
138
+ Passes already given keep working: add --end-passes to end them as well.
139
+ Other servers: sent to dallas, houston.
140
+ ```
141
+
142
+ A shuffle is a random value mixed into every code from then on. It is kept in
143
+ `access_lockouts_file`, so it needs no new key and no restart, and outlasts a restart.
144
+ Every code entered after it is checked against it, on every worker. The desk reads the
145
+ new code at the top of History, or with `sudo texas2step access show`, which also says
146
+ when the codes were shuffled. There is no previous code until the next change: at
147
+ midnight, the shuffled code becomes yesterday's and works for 24 hours as usual. Phrases
148
+ are not codes, so they keep working.
149
+
150
+ Every server must hold the same shuffle, or they give different codes. With
151
+ [servers that defend together](#servers-that-defend-together), it goes to every server in
152
+ `[[locations]]`, and each keeps the latest it is given. Without them, the command prints
153
+ the value for the others; run that on each:
154
+
155
+ ```bash
156
+ sudo texas2step access shuffle --to 3f9a0c7e12b4d856
157
+ ```
158
+
159
+ `access show`'s fingerprint includes the shuffle, so servers that agree show the same one.
160
+
161
+ Passes already given keep working. Add `--end-passes` to [end them](#ending-every-pass)
162
+ too, which has everyone outside enter the new code: the same as a new key, without
163
+ copying a key to every server.
164
+
126
165
  **Typing it.** Case and separators don't matter: `Maple River 42`, `maple-river-42` and
127
166
  `maple.river.42` are the same code, and `7` counts for `07`.
128
167
 
@@ -204,7 +243,8 @@ journalctl -u texas2step --grep 'access phrase'
204
243
  stays guessable: several words that don't belong together, such as
205
244
  `copper lantern harbor violet`, rather than `vip access`. texas2step takes any phrase
206
245
  you choose. To withdraw one, `sudo texas2step access phrase remove vip` on every server,
207
- and restart; passes it already bought last until they expire (`access_pass_hours`).
246
+ and restart. Passes it already bought last until they run out (`access_pass_hours`),
247
+ unless you [end every pass](#ending-every-pass).
208
248
  A new key doesn't change the phrases.
209
249
 
210
250
  A phrase buys the same pass as the day's code, so the visitor gets what any visitor
@@ -234,18 +274,29 @@ texas2step run https://speedtest.example.net/texas2step --code maple.river.42
234
274
 
235
275
  | | Needs |
236
276
  |---|---|
237
- | The page at `/`, which asks for the code; its scripts, styles and logo; `/api/v1/health` (for load balancers, without the version); `/api/v1/access` | Nothing |
238
- | The Monitor and Diag pages, the tests, and saving a result | A pass, or an address in `internal_networks`. Without a pass, `/monitor` and `/diag` send the visitor to `/`. |
277
+ | The page, at `/`, `/monitor` or `/diag`, which asks for the code; its scripts, styles and logo; `/api/v1/health` (for load balancers, without the version); `/api/v1/access` | Nothing |
278
+ | The tests, Monitor and Diag's measurements, and saving a result | A pass, or an address in `internal_networks` |
239
279
  | History, the API tab, `/r/<id>`, reading saved results, `/api/docs`, `/api/openapi.json`, `/api/v1/measure/*`, the current code, [lockouts and blocks](#lockouts-and-blocks) | An address in `internal_networks`; others get 404 |
240
280
 
241
- The pass goes with every request the page makes, as `?pass=`. A page load can't carry
242
- that, so a right code also sets the pass as a cookie for the server's own pages:
243
- `HttpOnly`, `Secure` over HTTPS, `SameSite=Strict`, gone when the pass expires.
244
-
245
- Until a visitor enters a code, nothing tells them the version: the page leaves it out and
246
- `/api/v1/health` answers only `{"status":"ok"}`. Inside `internal_networks`, and once in,
247
- it is there as before. This slows an attacker down rather than stopping one: the page's
248
- scripts are those of a published release.
281
+ The page keeps the pass in the browser's local storage and sends it with every request
282
+ it makes. To its own server it goes in an `Authorization: Bearer` header, and a
283
+ WebSocket's in its subprotocol, which keeps the pass out of the URLs that
284
+ TLS-inspecting proxies log. To the other servers in the picker it goes as `?pass=`: a
285
+ header there would make the browser ask permission (a CORS preflight) before each timed
286
+ request and skew the timings. The server takes the pass in any of these, so the CLI's
287
+ `?pass=` works as before. There is no cookie: a page needs no pass to load, and asks for
288
+ the code itself. A cookie an earlier version set is removed at the next right code.
289
+
290
+ With `internal_networks` set, only those networks see the version. The page,
291
+ `/api/v1/info` and `/api/v1/health` leave it out for everyone else, even after they enter
292
+ a code, and a result they save gets it from the server. Without `internal_networks`, a
293
+ visitor sees it once they have entered a code; until then the page leaves it out and
294
+ `/api/v1/health` answers only `{"status":"ok"}`. This slows an attacker down rather than
295
+ stopping one: the page's scripts are those of a published release.
296
+
297
+ The desk's functions also refuse, with `403`, a browser's request made for a page on
298
+ another site, so that a website an employee opens can't use them through the employee's
299
+ browser ([Security](security.md#the-desks-tools)).
249
300
 
250
301
  Loopback addresses are always inside, so commands on the server itself need no code. The
251
302
  service sees each visitor's address through nginx (`trusted_proxies`), which the packaged
@@ -262,6 +313,30 @@ inside.
262
313
  Without `internal_networks`, every visitor needs a code, and anyone with one can read the
263
314
  saved results. `texas2step access show` warns about that.
264
315
 
316
+ ### Inside on one address family
317
+
318
+ A visitor with both IPv4 and IPv6 comes from two addresses, and `internal_networks` may
319
+ list only one of them: an office's IPv6 network, say, but not the NAT address its IPv4
320
+ leaves by. The page asks its own server, at the address it was opened at, whether the
321
+ code is needed, and the browser reaches that over either family. When the answer is no
322
+ but another address the page tests refuses it, the page asks for the code over itself,
323
+ in a box the visitor can put aside with **Not now**. That other address is this server's
324
+ IPv4-only or IPv6-only name ([IPv4 and IPv6](configuration.md#ipv4-and-ipv6)), or another
325
+ server in the picker. The pass the code buys works over both families and on every
326
+ server with the same key.
327
+
328
+ The box comes up by itself until the visitor puts it aside, which holds until the page
329
+ is loaded again. Meanwhile Test and Monitor say the function is restricted over that
330
+ address, with a button to enter the code, and Diag's rows for it say **Restricted**, with
331
+ the same button.
332
+ Diag then tries that address every 10 seconds instead of every second, and opens no
333
+ WebSocket there until the code is in. A server with `access_code = "off"` has no pass to
334
+ give, so its page doesn't ask: it only says which address wants a code.
335
+
336
+ A pass that hasn't run out but is refused anyway, as from a network blocked by hand
337
+ ([lockouts and blocks](#lockouts-and-blocks)), isn't asked for again by itself. To have
338
+ neither family asked, list both of a site's addresses in `internal_networks`.
339
+
265
340
  ## Lockouts and blocks
266
341
 
267
342
  Wrong codes count against the visitor's network, its IPv4 /24 or IPv6 /48, not its
@@ -276,7 +351,8 @@ codes in a row from a network, it is locked out:
276
351
  * A network quiet for 30 minutes starts counting again from nothing, and so does one that
277
352
  enters a right code.
278
353
  * Visitors from `internal_networks` and loopback need no code, so they are never counted
279
- or locked out. Passes already given keep working.
354
+ or locked out. Passes already given keep working through a lockout; a block set by hand
355
+ ends them (below).
280
356
 
281
357
  | Setting | Default | |
282
358
  |---|---|---|
@@ -324,6 +400,14 @@ longer if it keeps trying; blocking it again keeps it going. A block can't overl
324
400
  take effect at once, with no restart, and with
325
401
  [servers that defend together](#servers-that-defend-together), on every server.
326
402
 
403
+ A block also ends the passes used from the network it covers. Until it ends, their
404
+ requests are refused as if they carried none (`401`), and the WebSockets and UDP
405
+ sessions they have open close within a few seconds. The page goes back to asking for the
406
+ code, which the blocked network can't enter. A lockout from wrong codes ends no passes,
407
+ so callers who share a network with a guesser keep testing with the pass they hold. A
408
+ block set or ended through the API counts at once. One from the command line reaches
409
+ each of the service's workers within 5 seconds, and so does one that ends.
410
+
327
411
  **From automation.** The same, as an API that answers only `internal_networks` and the
328
412
  server itself, so a script can carry a block from the server that saw an attack to all
329
413
  the others, and renew it there:
@@ -335,8 +419,9 @@ curl -s -X POST https://houston.example.net/texas2step/api/v1/access/blocks \
335
419
  curl -s -X DELETE 'https://houston.example.net/texas2step/api/v1/access/blocks?network=198.51.100.0/24'
336
420
  ```
337
421
 
338
- A list gives each lockout's `network`, `source`, `wrong_codes`, `since` and `until`, and
339
- `counting`, the networks with wrong codes in a row. A block that overlaps
422
+ A list gives each lockout's `network`, `source`, `wrong_codes`, `since`, `until` and
423
+ `by_hand` (a block, which ends passes too), and `counting`, the networks with wrong codes
424
+ in a row. A block that overlaps
340
425
  `internal_networks`, or is wider than a /16 or a /32, is refused with `400`. These calls
341
426
  change only the server they are sent to; for one that reaches every server by itself, see
342
427
  [Servers that defend together](#servers-that-defend-together).
@@ -356,6 +441,44 @@ every 30 minutes. One with 10,000 networks gets about 1.4 million a day:
356
441
  For a server open to the Internet, use `access_digits = 4`, and standing phrases of
357
442
  several unrelated words.
358
443
 
444
+ ## Ending every pass
445
+
446
+ A pass holds the time it was issued, signed with the key: `1793030400.q7GxN0x2rY8vLmKp3TfWcA9b`.
447
+ No server keeps a list of them. Each server checks a pass as it is used:
448
+
449
+ * The signature matches, so a server with the same key issued it.
450
+ * It was issued less than `access_pass_hours` ago, by this server's setting now. Lower
451
+ the setting, and restart, and passes already given end sooner; raise it, and they last
452
+ longer.
453
+ * It wasn't issued more than 5 minutes ahead of this server's clock.
454
+ * It was issued after passes were last ended.
455
+
456
+ To end every pass given so far, such as when a code or a phrase went further than it
457
+ should have:
458
+
459
+ ```console
460
+ $ sudo texas2step access passes --end
461
+ End every pass issued so far? Everyone testing from outside enters the code again. [y/N] y
462
+ Every pass issued before 2026-10-25 14:02 EDT is ended here, within seconds; tests running with one stop too.
463
+ Other servers: sent to dallas, houston.
464
+ ```
465
+
466
+ The time is kept in `access_lockouts_file`, beside the lockouts, so it needs no restart
467
+ and outlasts one. Each of the service's workers reads it within 5 seconds. From then on,
468
+ a pass issued before it is refused as if there were none (`401`), and the WebSockets and
469
+ UDP sessions opened with one close within a few seconds. The page asks for the code
470
+ again. The codes and phrases still work, so everyone with the current code is back in at
471
+ once, with a new pass. Visitors from `internal_networks` need no pass, so nothing changes
472
+ for them.
473
+
474
+ With [servers that defend together](#servers-that-defend-together), the end goes to every
475
+ server in `[[locations]]`, and each keeps the later of its own and the one it is sent.
476
+ Without them, run the command on each server: until then, the others still take the
477
+ passes. `sudo texas2step access passes` says how long passes last here and when they were
478
+ last ended, and `access show` does too. To end the codes too, use
479
+ `sudo texas2step access shuffle --end-passes` ([Shuffling the codes](#shuffling-the-codes)).
480
+ A new key also ends every pass, and every code with it.
481
+
359
482
  ## Servers that defend together
360
483
 
361
484
  When a lockout begins on one server, the others can block that network too, so an
@@ -370,9 +493,12 @@ access_peer_networks = ["198.51.100.20", "2001:db8:20::20", "203.0.113.30"]
370
493
  * **What goes to the others.** A lockout here goes, as a block, to every server in
371
494
  `[[locations]]`, at once. While the attacker keeps trying here, it goes again every
372
495
  third of `access_lockout_minutes`, so the others' blocks don't run out first.
373
- `sudo texas2step access block` and `access lockouts --clear` go to every server too:
374
- `--clear` on any one of them lets a caller back in everywhere. The
375
- [API's blocks](#lockouts-and-blocks) stay on the server they are sent to.
496
+ `sudo texas2step access block`, `access lockouts --clear`, `access passes --end` and
497
+ `access shuffle` go to every server too: `--clear` on any one of them lets a caller
498
+ back in everywhere. The
499
+ [API's blocks](#lockouts-and-blocks) stay on the server they are sent to. A block from
500
+ `access block` ends passes on every server, as here; a lockout ends none anywhere. A
501
+ server on an earlier version sends either as a lockout.
376
502
  * **What a server takes.** Only requests from an address in its `access_peer_networks`,
377
503
  signed with a key the servers work out from the access key they share. Use the
378
504
  addresses the other servers' requests leave from, which behind NAT may not be the
@@ -384,6 +510,10 @@ access_peer_networks = ["198.51.100.20", "2001:db8:20::20", "203.0.113.30"]
384
510
  compromised: a block from another server covers at most a /24 or a /48, never
385
511
  `internal_networks` or loopback, lasts at most a week, and isn't passed on. A server
386
512
  holds at most 1,000 of them. A wider block by hand stays on the server where it was set.
513
+ Another server can end every pass issued before its request, which at worst has
514
+ everyone enter the code again, but not set that time later than the receiving server's
515
+ clock allows, which would stamp new passes there too far ahead. It can shuffle the
516
+ codes, which at worst has the desk read the new one.
387
517
  * **Counts stay on each server.** Wrong codes count where they are entered, so with three
388
518
  servers an attacker gets up to about seven guesses, two at each and one more, before
389
519
  the whole cluster locks them out.
@@ -488,8 +618,7 @@ many as you need (`results_max_rows`). `sudo texas2step results clear` deletes t
488
618
  | Your `access_words` file | Yours: kept by `apt purge` |
489
619
  | `/var/lib/texas2step/access.db` | Wrong codes, lockouts and blocks, and with servers that defend together, the one-time values of their requests; made by the service when access codes are on; removed with `apt purge` |
490
620
 
491
- The pass lives in the visitor's browser (local storage, and the cookie for this server's
492
- pages) until it expires. Passes are not written to the journal: the request log shows
621
+ The pass lives in the visitor's browser, in local storage, until it expires. Passes are not written to the journal: the request log shows
493
622
  `pass=*` in their place. nginx's access log doesn't have them either: the packaged
494
623
  snippets log texas2step's requests to `/var/log/nginx/access.log` without their query
495
624
  strings (the `texas2step` log format, in `/etc/nginx/conf.d/texas2step-log.conf`).
@@ -78,7 +78,8 @@ sending the offer. The offer must include data channels:
78
78
  profiles the session runs. A voice or video session counts toward a
79
79
  [traffic limit](traffic.md#monitors-call-profiles). The answer is
80
80
  `{"op":"profile","ok":true,"name":"voice"}`, or `"ok": false` with a `reason`: the
81
- server withholds video (`monitor_video = false`), or a traffic limit is reached, when
81
+ server withholds that profile (`monitor_voice` or `monitor_video` is `false`, as
82
+ `/api/v1/info`'s `monitor` says), or a traffic limit is reached, when
82
83
  the site's `message` and `url` come with it. Past a limit while a session runs, the
83
84
  server sends `{"op":"limit","message":…,"url":…}` unasked and from then on echoes only
84
85
  probes of up to 64 bytes.
@@ -91,11 +92,11 @@ every port in its `rtc_port_min`–`rtc_port_max` range is taken.
91
92
 
92
93
  | Method | Path | Returns |
93
94
  |---|---|---|
94
- | GET | `/api/v1/info` | name, version, server identity, capabilities (`stamps`: the UDP probe can [stamp](#udp-probe-protocol) its echoes), limits, ICE servers, UI defaults (`one_stream`: whether a test downloads over one stream too, unless asked otherwise), `monitor` (`profile`: Monitor's until a visitor picks; `video`: whether the Video call profile is offered); with a [traffic limit](traffic.md), `traffic` (below) |
95
+ | GET | `/api/v1/info` | name, version (left out for clients outside `internal_networks`), server identity, capabilities (`stamps`: the UDP probe can [stamp](#udp-probe-protocol) its echoes), limits, ICE servers, UI defaults (`one_stream`: whether a test downloads over one stream too, unless asked otherwise), `monitor` (`profile`: Monitor's until a visitor picks; `voice`, `video`: whether the Voice call and Video call profiles are offered); with a [traffic limit](traffic.md), `traffic` (below) |
95
96
  | GET | `/api/v1/whoami` | client IP, port, family, scheme, HTTP version, user agent |
96
97
  | GET | `/api/v1/diag/http` | both ends of this HTTP connection, its HTTP version and TLS, the server's TCP RTT and clock (below) |
97
98
  | GET | `/api/v1/locations` | servers offered in the picker (this one first) |
98
- | GET | `/api/v1/health` | `{"status": "ok", "version": …}`; without the version for clients that need an [access code](#access-codes) |
99
+ | GET | `/api/v1/health` | `{"status": "ok", "version": …}`; without the version for clients outside `internal_networks`, or that need an [access code](#access-codes) |
99
100
 
100
101
  With a traffic limit, `traffic` says whether this client may start a test, and if not,
101
102
  what it is told:
@@ -175,21 +176,25 @@ curl -s -X POST https://dallas.example.net/texas2step/api/v1/measure/full \
175
176
  ## Access codes
176
177
 
177
178
  On a server with `access_code` on ([Access codes](access.md)), a client from outside
178
- `internal_networks` sends a pass as `?pass=<pass>` with each request under `/api/v1/`
179
- (except `/health` and `/access`); without a valid one it gets `401`. The WebSocket takes
180
- it the same way. With `internal_networks` set, clients from outside them get `404` for
179
+ `internal_networks` sends a pass with each request under `/api/v1/` (except `/health`
180
+ and `/access`); without a valid one it gets `401`. It goes as `?pass=<pass>`, or in an
181
+ `Authorization: Bearer <pass>` header; a WebSocket can also carry it in its subprotocol,
182
+ offering `texas2step` and `texas2step.pass.<pass>`, and is answered with `texas2step`. With `internal_networks` set, clients from outside them get `404` for
181
183
  reading results, `/api/v1/measure/*`, `/api/docs`, `/api/openapi.json`, the current code and
182
- the lockouts. `/api/v1/health` leaves out the version for clients that would need a pass.
184
+ the lockouts, and the browser's requests to these made for a page on another site get
185
+ `403`. With `internal_networks` set, `/api/v1/info` and `/api/v1/health` leave out the
186
+ version for clients outside them; without them, `/api/v1/health` leaves it out for
187
+ clients that would need a pass.
183
188
 
184
189
  | Method | Path | Purpose |
185
190
  |---|---|---|
186
- | GET | `/api/v1/access?pass=<pass>` | `{"required", "desk"}`: whether this client needs a pass, and gets the desk's functions; with `pass`, also whether that pass is valid and when it expires |
187
- | POST | `/api/v1/access` | `{"code": "maple.river.42"}` (any case and separators, or a standing phrase) returns `{"pass", "expires"}`, and sets the pass as a cookie for this server's pages. A wrong code is `403`. After `access_lockout_after` wrong codes in a row from one /24 or /48, every code from it gets that same `403`, unread, until `access_lockout_minutes` pass without one ([Lockouts and blocks](access.md#lockouts-and-blocks)). A body over 1 KB is `413` |
191
+ | GET | `/api/v1/access?pass=<pass>` | `{"required", "desk", "codes", "note"}`: whether this client needs a pass, gets the desk's functions, and whether this server gives passes at all (`access_code` is on), and the site's line under the code box (`access_note`, empty for none); with `pass`, also whether that pass is valid and when it expires |
192
+ | POST | `/api/v1/access` | `{"code": "maple.river.42"}` (any case and separators, or a standing phrase) returns `{"pass", "expires"}`. A wrong code is `403`. After `access_lockout_after` wrong codes in a row from one /24 or /48, every code from it gets that same `403`, unread, until `access_lockout_minutes` pass without one ([Lockouts and blocks](access.md#lockouts-and-blocks)). A body over 1 KB is `413` |
188
193
  | GET | `/api/v1/access/code` | The current code, when it changes, and the previous one while it still works; only from `internal_networks` |
189
- | GET | `/api/v1/access/blocks` | `{"lockouts": [{"network", "source", "wrong_codes", "since", "until"}], "counting"}`; only from `internal_networks` and the server itself |
190
- | POST | `/api/v1/access/blocks` | `{"network": "198.51.100.0/24", "minutes": 120}` locks a network out by hand, or keeps its lockout going; `minutes` defaults to `access_lockout_minutes`. One that overlaps `internal_networks`, or is wider than a /16 or a /32, is `400` |
194
+ | GET | `/api/v1/access/blocks` | `{"lockouts": [{"network", "source", "wrong_codes", "since", "until", "by_hand"}], "counting"}` (`by_hand`: a block, which ends the passes used from the network too); only from `internal_networks` and the server itself |
195
+ | POST | `/api/v1/access/blocks` | `{"network": "198.51.100.0/24", "minutes": 120}` locks a network out by hand, or keeps its lockout going, and ends the passes used from it; `minutes` defaults to `access_lockout_minutes`. One that overlaps `internal_networks`, or is wider than a /16 or a /32, is `400` |
191
196
  | DELETE | `/api/v1/access/blocks?network=<network>` | Ends the lockouts that overlap it (`all`: every one); returns `{"cleared": [...]}` |
192
- | POST | `/api/v1/access/peer` | For [servers that defend together](access.md#servers-that-defend-together): `{"from", "to", "action": "block" \| "clear" \| "check", "network", "minutes"}`, signed in `X-Texas2step-Time`, `-Nonce` and `-Signature`. Only from `access_peer_networks`, with `access_share_lockouts` on; `404` to everyone else |
197
+ | POST | `/api/v1/access/peer` | For [servers that defend together](access.md#servers-that-defend-together): `{"from", "to", "action": "block" \| "clear" \| "check" \| "end-passes" \| "shuffle", "network", "minutes", "kind": "lockout" \| "block", "at", "value"}` (`kind`: a block set by hand, which ends passes there too; `lockout` when left out. `end-passes` ends every pass issued before `at`, in Unix seconds; `shuffle` mixes `value`, 16 hex digits, into the codes from `at`, the latest shuffle staying; neither `at` may be later than the receiving server's clock allows), signed in `X-Texas2step-Time`, `-Nonce` and `-Signature`. Only from `access_peer_networks`, with `access_share_lockouts` on; `404` to everyone else |
193
198
 
194
199
  ```bash
195
200
  pass=$(curl -s -X POST https://speedtest.example.net/texas2step/api/v1/access \
@@ -38,7 +38,8 @@ file up to date with the new version's template and keeps your values; see
38
38
  | `page_test` / `page_monitor` / `page_diag` / `page_history` / `page_api` | `true` | Show that tab of the web UI. Any can be hidden, but not all. A hidden page's address opens the first page shown. The API stays on: `results_enabled` and `measure_enabled` turn parts of it off, and `/api/docs` stays reachable without the API tab. |
39
39
  | `default_streams` / `default_seconds` / `default_packets` | 4 / 10 / 500 | Defaults advertised by `/api/v1/info` |
40
40
  | `monitor_profile` | `path` | Monitor's profile until a visitor picks one: `path` (small probes, to follow the path), `voice` or `video` (a call's packets, scored every 10 seconds); see [Live monitor](methodology.md#live-monitor) |
41
- | `monitor_video` | `true` | Offer Monitor's Video call profile, about 1.4 Mb/s each way while a tab runs it. Voice and video count toward a [traffic limit](traffic.md#monitors-call-profiles). |
41
+ | `monitor_voice` | `true` | Offer Monitor's Voice call profile, about 150 kb/s each way while a tab runs it. With `false`, the page shows no Voice call button and the server refuses the profile. |
42
+ | `monitor_video` | `true` | Offer Monitor's Video call profile, about 1.4 Mb/s each way while a tab runs it. With `false`, the page shows no Video call button and the server refuses the profile. With both `false`, Monitor runs Path only, and shows no profile switch. Voice and video count toward a [traffic limit](traffic.md#monitors-call-profiles). |
42
43
  | `default_one_stream` | `true` | Whether a test also downloads over [one stream](methodology.md#one-stream), unless the visitor chooses in Test options or the command line with `--one-stream` or `--no-one-stream`. It can add up to half again to a test's traffic, so a server whose traffic is billed may want `false`; see [Traffic limits](traffic.md#what-a-test-moves). |
43
44
  | `cors_origins` | `["*"]` | Origins allowed to call the API from a browser |
44
45
  | `trusted_proxies` | `["127.0.0.1", "::1"]` | Peers whose `X-Forwarded-For`, `X-Forwarded-Proto`, `X-Forwarded-Prefix` and `X-Client-Protocol` are believed |
@@ -48,7 +49,8 @@ file up to date with the new version's template and keeps your values; see
48
49
  | `access_timezone` | `America/New_York` | The time zone whose midnight starts a new code |
49
50
  | `access_words` | `""` | A file of your own words for codes, one entry per line; empty uses the built-in words |
50
51
  | `access_digits` | 2 | The digits that end a code: 2, 3 or 4 |
51
- | `access_pass_hours` | 12 | How long an entered code lets a browser keep testing |
52
+ | `access_pass_hours` | 12 | How long a pass lasts from when it was issued; applied as passes are used, so a change applies to passes already given. See [Ending every pass](access.md#ending-every-pass). |
53
+ | `access_note` | `Contact the service desk for assistance.` | The line under the code box, such as how to reach the desk; `""` for none |
52
54
  | `access_key_file` | `/etc/texas2step/access.key` | The key codes come from; the same on every server of an organization |
53
55
  | `access_phrases_file` | `/etc/texas2step/access-phrases.toml` | Standing phrases that work as codes, if the file exists; see [Standing phrases](access.md#standing-phrases) |
54
56
  | `access_lockout_after` | 3 | Wrong codes in a row from one /24 or /48 that lock it out of entering codes; see [Lockouts and blocks](access.md#lockouts-and-blocks) |
@@ -51,7 +51,8 @@ To do the same by hand, follow steps 1 to 5 below instead.
51
51
  ```
52
52
 
53
53
  It logs to `texas2step-install.log` beside itself. When it finishes, the service
54
- answers both on `127.0.0.1:8080` and through nginx, and it prints the address to open.
54
+ answers both on `127.0.0.1:8080` (or the `--port` you gave) and through nginx, and it
55
+ prints the address to open.
55
56
  You can run it again safely: it skips each step that is already done.
56
57
 
57
58
  What the check looks at, and what the script then does:
@@ -66,7 +67,7 @@ What the check looks at, and what the script then does:
66
67
  | Peers | Each `--peer URL` is asked who it is (`/api/v1/info`). A peer that can't be reached is left out, with the command to add it later. | `texas2step config add-location ... --write` |
67
68
  | nginx | nginx is installed and running, its configuration passes `nginx -t`, and it loads `conf.d/*.conf`. It finds your HTTPS site, and checks that nothing else on that site uses `/texas2step/`. If texas2step is already published, nginx is left alone. | Adds `include snippets/texas2step.conf;` after the site's `server_name` line, runs `nginx -t` and reloads. The file is backed up to `/var/backups/texas2step/` first, and put back if the test fails. |
68
69
  | UDP | The probe's port range: the one you set, or 61000–61999. It checks this range against the firewall and the kernel's ephemeral ports. | Pins the range in the settings. Opens it in ufw or firewalld if one of them is active. |
69
- | Port 8080 | Nothing else listens there | |
70
+ | Port | Nothing else listens on texas2step's TCP port: 8080, `LISTEN_PORT` in `/etc/default/texas2step`, or `--port`. nginx's `upstream` in `/etc/nginx/conf.d/texas2step.conf` points at that port. | With `--port`, sets `LISTEN_PORT` and the `upstream` before the package is installed, so the service never starts on 8080. An `upstream` that points elsewhere is pointed at the port. |
70
71
 
71
72
  Options:
72
73
 
@@ -79,6 +80,7 @@ Options:
79
80
  | `--path PATH` | Publish texas2step at `PATH` instead of `/texas2step/`, e.g. `/speedtest`. The check moves the package's snippet to that path and shows it in the install script, which writes it as `snippets/texas2step-at-speedtest.conf` and includes that. Each later run writes it again from the new package. Use the same path in `--url-v4` and `--url-v6`. |
80
81
  | `--no-nginx` | Install the service only, and publish it yourself |
81
82
  | `--udp-ports MIN-MAX` | The probe's UDP range, e.g. `61000-61999` |
83
+ | `--port PORT` | The TCP port texas2step listens on, on `127.0.0.1` behind nginx, when another program needs 8080. Choose one from 1024 to 32767. Later runs and `texas2step update` keep it. See [Another port](#another-port). |
82
84
  | `--url-v4 URL`, `--url-v6 URL` | This server's IPv4-only and IPv6-only base URLs, for the header's IP drop-down; see [IPv4 and IPv6](configuration.md#ipv4-and-ipv6). The check looks up each name: it should have only an A record (or only AAAA), pointing at this server. After the install, the script checks that nginx answers for each name. |
83
85
  | `--no-firewall` | Leave the firewall alone; the report lists the rule to add |
84
86
  | `--allow-downgrade` | Allow installing an older version than the one installed |
@@ -128,6 +130,32 @@ yourself.
128
130
  To make the second server by cloning the first's virtual machine instead, see
129
131
  [Clone a server](#clone-a-server).
130
132
 
133
+ ### Another port
134
+
135
+ texas2step listens on `127.0.0.1:8080`, and nginx reaches it there through the
136
+ `upstream` in `/etc/nginx/conf.d/texas2step.conf`. When another program needs 8080,
137
+ give the setup tool another port, the same way for a new install or an installed server:
138
+
139
+ ```bash
140
+ sudo ./texas2step-setup --port 8081
141
+ sudo ./texas2step-install.sh
142
+ ```
143
+
144
+ The install script sets `LISTEN_PORT=8081` in `/etc/default/texas2step` and points the
145
+ `upstream` at `127.0.0.1:8081`, keeping a dated copy of each file it changes in
146
+ `/var/backups/texas2step/`. It does this before installing the package, so the service
147
+ never starts on 8080, even on a new install. Then it restarts texas2step, reloads nginx
148
+ and checks both.
149
+
150
+ Choose a port from 1024 to 32767. texas2step runs as its own user, which can't listen
151
+ below 1024, and Linux takes the ports of outgoing connections from 32768 up. If the port
152
+ is in use, the check says by what. Both files are kept on upgrades, and the setup tool
153
+ and `texas2step update` read the port from `LISTEN_PORT`. When a new version changes the
154
+ `upstream` file, the update installs the new one with your port in it.
155
+
156
+ If you changed `LISTEN_PORT` by hand and not the `upstream`, the next run of the setup
157
+ tool points the `upstream` at it.
158
+
131
159
  ## 1. Install the package
132
160
 
133
161
  Download the newest `.deb` from the
@@ -264,12 +292,11 @@ choose the default theme (Light, Dark, America, Energy, Federal, Military or Tex
264
292
  that site directly. Sites can also measure each other with `POST /api/v1/measure/full`,
265
293
  but only toward listed locations.
266
294
 
267
- **Listen address and workers.** These are set in `/etc/default/texas2step`. If you move
268
- the service off `127.0.0.1:8080`, change the `upstream` in
269
- `/etc/nginx/conf.d/texas2step.conf` to match. More workers help on multi-gigabit links,
270
- where Python's per-connection CPU cost is the limit. Each worker keeps its own UDP
271
- sessions, which is fine, because a session lives entirely in the worker that answered
272
- it.
295
+ **Listen address and workers.** These are set in `/etc/default/texas2step`. To move the
296
+ service to another port, see [Another port](#another-port). More workers help on
297
+ multi-gigabit links, where Python's per-connection CPU cost is the limit. Each worker
298
+ keeps its own UDP sessions, which is fine, because a session lives entirely in the
299
+ worker that answered it.
273
300
 
274
301
  ## 4. Firewall and UDP
275
302
 
@@ -272,7 +272,10 @@ counted as lost. Statistics cover the visible window (1, 5 or 15 minutes).
272
272
  A **profile** sets what is sent. Anyone can switch it beside the Start button. Options
273
273
  holds the exact sizes and rates, and the page's address carries them, so the service
274
274
  desk can send a caller a link such as `/monitor?profile=voice`. The site picks the
275
- profile a visitor starts with (`monitor_profile`).
275
+ profile a visitor starts with (`monitor_profile`), and can withhold the call profiles
276
+ (`monitor_voice`, `monitor_video`): the switch then shows only the ones offered, and none
277
+ at all when only Path is. A link or a saved choice for a withheld profile starts with
278
+ the nearest one offered.
276
279
 
277
280
  | Profile | Sends | Each way, as the network carries it |
278
281
  |---|---|---|
@@ -365,6 +368,10 @@ and closes its connections. Its rows and events stay, and the timelines show the
365
368
  An attempt not answered within 2 s (1.5 s for WebSocket and UDP) counts as missed. A
366
369
  method that has answered before shows a miss as loss (red). One that hasn't answered
367
370
  yet stays grey and keeps trying. One the server doesn't offer is grey and isn't tried.
371
+ One whose address asks for the access code, as a server's IPv4-only name can for a
372
+ visitor inside on IPv6 only, says **Restricted** (amber) and doesn't count as loss:
373
+ its HTTPS is tried every 10 s until the code is in, and no WebSocket is opened there
374
+ meanwhile ([Inside on one address family](access.md#inside-on-one-address-family)).
368
375
  The clock offset is the server's time minus the browser's at the middle of the round
369
376
  trip, ± half the round trip.
370
377
 
@@ -116,6 +116,29 @@ links and footer credit. Three places still say texas2step:
116
116
 
117
117
  Newest first. Each release's notes have the details and its upgrade steps.
118
118
 
119
+ ### [2.3.0](https://github.com/robert-patrick-texas/texas2step/releases/tag/v2.3.0) (2026-10-25)
120
+
121
+ - [x] Passes carry when they were issued: `access_pass_hours` applies to passes already
122
+ given, and `texas2step access passes --end` ends every pass, on every server with
123
+ lockouts shared.
124
+ - [x] `texas2step access shuffle` changes the codes at once, without a new key.
125
+ - [x] A visitor inside on one address family and outside on the other is asked for the
126
+ code in a box over the page; Diag says which address is restricted.
127
+ - [x] The code screen's words are the organization's, with `access_note` under the box.
128
+ - [x] `texas2step-setup --port` for servers where another program needs 8080.
129
+ - [x] `monitor_voice` withholds Monitor's Voice call profile, as `monitor_video` does
130
+ the Video call one.
131
+
132
+ ### [2.2.4](https://github.com/robert-patrick-texas/texas2step/releases/tag/v2.2.4) (2026-10-25)
133
+
134
+ - [x] The desk's functions refuse requests made for pages on other sites; with
135
+ `internal_networks` set, only those networks see the version.
136
+ - [x] API answers are kept in no browser cache.
137
+ - [x] A block set by hand ends the passes used from its network, sessions already open
138
+ included.
139
+ - [x] No cookie: the pass stays in local storage and goes to the page's own server in a
140
+ header; a Content-Security-Policy on the page and the API explorer.
141
+
119
142
  ### [2.2.3](https://github.com/robert-patrick-texas/texas2step/releases/tag/v2.2.3) (2026-10-24)
120
143
 
121
144
  - [x] Spaces around any setting or typed value are ignored; a stray one in
@@ -37,10 +37,20 @@ pass.
37
37
  the service (`0640 root:texas2step`). Anyone holding it can work out every code, so it
38
38
  is kept out of the world-readable `config.toml`. `texas2step access show` prints a
39
39
  fingerprint to compare servers by, which reveals nothing about the key.
40
- * **Passes.** A right code buys a pass for `access_pass_hours` (12 by default): an expiry
41
- time signed with the key, checked by every server that holds it. The browser sends it
42
- as `?pass=`; this server's own pages get it as a cookie that is `HttpOnly`,
43
- `SameSite=Strict`, and `Secure` over HTTPS.
40
+ * **Passes.** A right code buys a pass for `access_pass_hours` (12 by default): the time
41
+ it was issued, signed with the key, checked by every server that holds it against its
42
+ `access_pass_hours` as it is then. The page keeps it in
43
+ local storage, with no cookie, and sends it to its own server in an `Authorization`
44
+ header (a WebSocket's in its subprotocol), so it stays out of URLs that proxies log;
45
+ to the other servers, as `?pass=`. A pass is a bearer token: it isn't tied to
46
+ an address or a browser, and works until it runs out. A
47
+ [block set by hand](access.md#lockouts-and-blocks) ends it for the network blocked,
48
+ WebSockets and UDP sessions already open included.
49
+ [`texas2step access passes --end`](access.md#ending-every-pass) ends every pass issued
50
+ so far, on every server with lockouts shared, while the codes keep working.
51
+ [`texas2step access shuffle`](access.md#shuffling-the-codes) replaces the current and
52
+ previous codes with a new one, and with `--end-passes` ends the passes too. A new access
53
+ key ends every pass and code.
44
54
  * **Standing phrases.** For the desk away from a PC, in their own `0640` file
45
55
  (`access-phrases.toml`), with an optional last day. Each use is a journal warning with
46
56
  the phrase's name, never the phrase. The examples published in the docs are refused.
@@ -97,10 +107,22 @@ server itself), and `404` to everyone else, as if they weren't there:
97
107
  * the API explorer (`/api/docs`, `/api/openapi.json`);
98
108
  * the server-side measurements (`/api/v1/measure/*`).
99
109
 
110
+ They also refuse, with `403`, a browser's request made for a page on another site. They
111
+ trust the address a request comes from, and CORS lets any page read the answer, so a
112
+ website an employee opens inside could otherwise read History, the current code and the
113
+ blocks through that employee's browser, or set blocks. The browser says where a request
114
+ came from in its `Sec-Fetch-Site` header, or, without one, in its `Origin`. A link from
115
+ elsewhere still opens the desk's pages, because the other site can't read them. Scripts,
116
+ the CLI and monitoring send neither header and work as before.
117
+
100
118
  Server-side measurements only reach the servers in `[[locations]]`, unless
101
119
  `allow_any_target` is set, so the server can't be used to make requests to other hosts.
102
120
  At most two run at once on each worker.
103
121
 
122
+ **The version** would tell an attacker which flaws to try. With `internal_networks` set,
123
+ only those networks see it: the page, `/api/v1/info` and `/api/v1/health` leave it out for
124
+ everyone else, even after they enter a code. A result they save gets it from the server.
125
+
104
126
  ## Abuse of the service
105
127
 
106
128
  A speed test exists to move a lot of data, so each part has a cap:
@@ -138,6 +160,13 @@ A speed test exists to move a lot of data, so each part has a cap:
138
160
  figures) belong to the service user. The traffic figures hold no addresses.
139
161
  * **No third parties.** The web page loads nothing from other sites: no CDN, fonts,
140
162
  analytics or telemetry. No public STUN server is used unless listed in `ice_servers`.
163
+ * **A Content-Security-Policy** on the page and the API explorer lets only their own
164
+ scripts run, and the one written into each page by its hash, and lets them connect
165
+ only to this server and the others in `[[locations]]`. A script injected some other
166
+ way can't run, or send what it finds to another site.
167
+ * **Its own hostname.** A browser shares local storage across a whole origin, the scheme,
168
+ host and port. Where texas2step sits beside other applications on one hostname, any page
169
+ there could read the pass, so serve it from a hostname of its own where you can.
141
170
 
142
171
  ## Cryptography
143
172
 
@@ -142,7 +142,9 @@ acknowledgements the server sends back, as measured over IPv6. A tab left runnin
142
142
  When the limit is reached, the server holds those sessions to the small probes of the
143
143
  Path profile, which aren't counted, and Monitor says so with the site's message and link.
144
144
  Until the limit opens again, a call profile is refused at its start.
145
- `monitor_video = false` in the settings withholds the Video call profile altogether.
145
+ `monitor_video = false` in the settings withholds the Video call profile altogether, and
146
+ `monitor_voice = false` the Voice call one: the page doesn't offer them, and the server
147
+ refuses them. With both, Monitor runs only the uncounted Path profile.
146
148
 
147
149
  ## What isn't counted
148
150
 
@@ -1,3 +1,3 @@
1
1
  """texas2step: web-centric throughput, latency, jitter and packet-loss measurement."""
2
2
 
3
- __version__ = "2.2.3"
3
+ __version__ = "2.3.0"