From 2f4ebd3b75bad6d5e7c19300218bb1665e95a231 Mon Sep 17 00:00:00 2001 From: przeprogramowani <51264853+przeprogramowani@users.noreply.github.com> Date: Fri, 18 Sep 2026 17:17:28 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20wymagania=20sieciowe=20dla=20urz=C4=85d?= =?UTF-8?q?ze=C5=84=20s=C5=82u=C5=BCbowych=20(allowlist=20PL+EN)=20(#53)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: add corporate network allowlist for security/sysadmin teams Polish sysadmin spec plus a machine-readable host list and a source-scan test so new CLI destinations cannot land undocumented. * chore(release): prepare v1.23.1 * docs: rewrite corporate allowlist in sysadmin language (PL+EN) Plain host/port/protocol tables, matching English document, and an allowlist test that covers both language files. * docs: drop localhost from the corporate network allowlist Public DNS names only; the source scan skips IP literals and single-label hosts so local-dev URLs stay out of the sysadmin spec. --------- Co-authored-by: Claude --- README.md | 4 + docs/network-allowlist.json | 107 ++++++++++++++++++++ docs/network-requirements.md | 146 +++++++++++++++++++++++++++ docs/reference/platform-support.md | 1 + docs/wymagania-sieciowe.md | 146 +++++++++++++++++++++++++++ package.json | 2 +- tests/network-allowlist.test.ts | 152 +++++++++++++++++++++++++++++ 7 files changed, 557 insertions(+), 1 deletion(-) create mode 100644 docs/network-allowlist.json create mode 100644 docs/network-requirements.md create mode 100644 docs/wymagania-sieciowe.md create mode 100644 tests/network-allowlist.test.ts diff --git a/README.md b/README.md index a471c30..7ef3251 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ prompts, and configs directly into your workspace. - **Node 20+** — this is the only runtime dependency. +Corporate firewall / proxy allowlist (for security and sysadmin teams): +Polish [docs/wymagania-sieciowe.md](docs/wymagania-sieciowe.md), +English [docs/network-requirements.md](docs/network-requirements.md). + ## Install ```bash diff --git a/docs/network-allowlist.json b/docs/network-allowlist.json new file mode 100644 index 0000000..bec778b --- /dev/null +++ b/docs/network-allowlist.json @@ -0,0 +1,107 @@ +{ + "version": 1, + "updated": "2026-09-18", + "description": "Hostnames that 10x-cli source may mention or contact. Keep in sync with docs/wymagania-sieciowe.md and docs/network-requirements.md. tests/network-allowlist.test.ts fails when src/ or those docs introduce an unlisted host.", + "hosts": [ + { + "hostname": "10x-toolkit-api.przeprogramowani.workers.dev", + "port": 443, + "protocol": "https", + "calledBy": "cli_process", + "required": true, + "inDocs": true, + "when": ["login", "every_authenticated_command", "doctor"], + "purpose": "Delivery API: auth, membership, catalog, lessons, artifacts, health" + }, + { + "hostname": "registry.npmjs.org", + "port": 443, + "protocol": "https", + "calledBy": "cli_process", + "required": true, + "inDocs": true, + "when": ["install", "update_check"], + "purpose": "Public npm registry for @przeprogramowani/10x-cli; doctor version lookup" + }, + { + "hostname": "10xbench.ai", + "port": 443, + "protocol": "https", + "calledBy": "cli_process", + "required": false, + "inDocs": true, + "when": ["bench"], + "purpose": "Public leaderboard JSON for `10x bench`" + }, + { + "hostname": "github.com", + "port": 443, + "protocol": "https", + "calledBy": "cli_process", + "required": false, + "inDocs": true, + "when": ["bench_kit", "optional_binary_install"], + "purpose": "git clone of 10x-bench-kit; optional GitHub Releases binary download" + }, + { + "hostname": "objects.githubusercontent.com", + "port": 443, + "protocol": "https", + "calledBy": "browser", + "required": false, + "inDocs": true, + "when": ["optional_binary_install"], + "purpose": "Unconfirmed GitHub Releases asset CDN" + }, + { + "hostname": "release-assets.githubusercontent.com", + "port": 443, + "protocol": "https", + "calledBy": "browser", + "required": false, + "inDocs": true, + "when": ["optional_binary_install"], + "purpose": "Unconfirmed GitHub Releases asset CDN" + }, + { + "hostname": "toolkit.przeprogramowani.pl", + "port": 443, + "protocol": "https", + "calledBy": "browser", + "required": true, + "inDocs": true, + "when": ["login_email"], + "purpose": "Magic-link callback opened in the user's browser" + }, + { + "hostname": "10xdevs.pl", + "port": 443, + "protocol": "https", + "calledBy": "browser", + "required": false, + "inDocs": true, + "when": ["enrollment_hint"], + "purpose": "Enrollment URL printed in error hints; CLI does not fetch it" + }, + { + "hostname": "app.circle.so", + "port": 443, + "protocol": "https", + "calledBy": "browser", + "required": false, + "inDocs": true, + "when": ["login_circle_human"], + "purpose": "Circle web app so the user can open the login DM; CLI process does not connect" + }, + { + "hostname": "git-scm.com", + "port": 443, + "protocol": "https", + "calledBy": "documentation_only", + "required": false, + "inDocs": false, + "when": ["bench_kit"], + "purpose": "Install-git hint in a bench-kit error message; CLI does not fetch it" + } + ] +} diff --git a/docs/network-requirements.md b/docs/network-requirements.md new file mode 100644 index 0000000..40d5595 --- /dev/null +++ b/docs/network-requirements.md @@ -0,0 +1,146 @@ +# 10x-cli network requirements + +Polish: [wymagania-sieciowe.md](wymagania-sieciowe.md). +Machine-readable host list: [network-allowlist.json](network-allowlist.json). + +10x-cli (`@przeprogramowani/10x-cli`) signs a 10xDevs course user in and writes lesson files to disk. It does not open a listening port. It does not upload project source code. + +--- + +## Allowlist + +Every row: HTTPS, TCP 443, from the device. + +### Required for course use + +| Host | Port | Protocol | Purpose | Without it | +|------|------|----------|---------|------------| +| `10x-toolkit-api.przeprogramowani.workers.dev` | 443 | HTTPS | Login, token refresh, lesson list and content, `10x doctor` | `10x auth`, `list`, `get`, `sync` fail; `doctor` reports the API unreachable | +| `registry.npmjs.org` | 443 | HTTPS | Install and update `@przeprogramowani/10x-cli` (`npx` / `npm install -g`) | npm install and upgrade fail. `10x doctor` still runs — it skips the version check | +| `toolkit.przeprogramowani.pl` | 443 | HTTPS | Login email link (`/auth/callback`) opened in the browser | Email login (`10x auth`) does not finish after the user clicks the link | + +`10x-cli` calls the API only at `10x-toolkit-api.przeprogramowani.workers.dev`. `toolkit.przeprogramowani.pl` is browser-only, for email login. + +### Browser — Circle login + +| Host | Port | Protocol | Purpose | Without it | +|------|------|----------|---------|------------| +| `10x-toolkit-api.przeprogramowani.workers.dev` | 443 | HTTPS | Approval page from the Circle message | `10x auth --method circle` waits until the link expires | +| `app.circle.so` | 443 | HTTPS | Reading the Circle message that contains the link | The user cannot open the message (the `10x` process does not connect here) | + +### Optional + +| Host | Port | Protocol | Purpose | Without it | +|------|------|----------|---------|------------| +| `10xbench.ai` | 443 | HTTPS | Model leaderboard (`10x bench`) | `10x bench` fails; lesson download is unchanged | +| `github.com` | 443 | HTTPS | `git clone` of the `10x-bench-kit` template; optional GitHub Releases binary | `10x bench-kit` cannot fetch the template. Course `get` / `sync` unchanged | +| `objects.githubusercontent.com` | 443 | HTTPS | GitHub Releases asset CDN (unconfirmed) | Downloading the standalone binary from Releases may fail | +| `release-assets.githubusercontent.com` | 443 | HTTPS | GitHub Releases asset CDN (unconfirmed) | same | +| `10xdevs.pl` | 443 | HTTPS | Course enrolment site (error-message hint only) | No effect on the CLI | + +`10x bench-kit` may also run `git ls-remote` against the `origin` of the repo where the command was invoked — that host is whatever the user’s git config already uses. + +--- + +## Protocol and TLS + +- HTTPS / TLS, TCP 443. Login: `POST`/`GET` and repeated HTTP (every 2 s, up to 5 min). No WebSocket or SSE. +- API and 10xbench timeout: 30 s. `doctor` → API: 5 s. `doctor` → npm: 2 s. +- User-Agent: `10x-cli`. +- The CLI does not listen on any port. + +Corporate TLS inspection (custom CA): + +| Setting | Fact | +|---------|------| +| `NODE_EXTRA_CA_CERTS` | Node.js appends a PEM CA file. Applies when `10x` runs on Node (npm install). | +| OS certificate store | The CLI does not enable `--use-system-ca`. | +| `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` | `npm` / `npx` usually honour these. Node 20 `fetch` (CLI minimum) does not unless `NODE_USE_ENV_PROXY=1` (Node 24+). On Node 20–22, proxy for `10x auth` / `10x get` is unconfirmed. | +| Standalone binary | Different runtime from Node. `NODE_EXTRA_CA_CERTS` / `HTTPS_PROXY`: unconfirmed. | + +If inspection breaks TLS: point `NODE_EXTRA_CA_CERTS` at the corporate root, or disable inspection for the hosts in the tables. + +--- + +## On the device + +| Component | Required | +|-----------|----------| +| Node.js ≥ 20 | Yes for npm/npx install | +| npm / npx | Yes for npm install. No for the standalone binary | +| git | `10x bench-kit` only | +| Docker / Podman | Not for the CLI | +| OS | macOS, Linux, Windows | + +Session tokens (email, access/refresh JWT, expiry) — not a Circle password: + +| OS | Directory | Files | Permissions (POSIX) | +|----|-----------|-------|---------------------| +| macOS / Linux | `$XDG_CONFIG_HOME/10x-cli` or `~/.config/10x-cli` | `auth.json`, `config.json` | directory `0700`, `auth.json` `0600` | +| Windows | `%APPDATA%\10x-cli` | same files | — | + +Lesson files are written into the user’s project directory (for example `.claude/skills/`, `CLAUDE.md`). + +--- + +## Data leaving the device + +| Data | When | +|------|------| +| Email address | `10x auth`; stored in `auth.json` | +| JWT access + refresh | Login and every API call (`Authorization: Bearer`) | +| Hostname and a short OS string | Start of `10x auth --method circle` only | +| User-Agent `10x-cli` | Every CLI request | +| Package-version GET (no token) | `10x doctor` → `registry.npmjs.org` | +| Public leaderboard JSON (no token) | `10x bench` | + +Project source code is not uploaded. + +--- + +## AI coding tools (separate allowlist) + +10x-cli does not call Anthropic, OpenAI, Google, or OpenRouter. It writes files for the chosen `--tool`. Model traffic is that tool’s traffic. + +| `--tool` | Vendor network requirements | +|----------|------------------------------| +| `claude-code` | [Claude Code — enterprise network](https://docs.claude.com/en/docs/claude-code/network-config) | +| `cursor` | [Cursor — network configuration](https://cursor.com/docs/enterprise/network-configuration) | +| `copilot` | [GitHub Copilot allowlist](https://docs.github.com/copilot/reference/copilot-allowlist-reference) | +| `codex` | [Codex — network](https://developers.openai.com/codex/agent-approvals-security/) — full local-CLI host list: confirm with OpenAI | +| `gemini` | [Gemini Code Assist network access](https://developers.google.com/gemini-code-assist/docs/network-access) — confirm for Gemini CLI | +| `kiro` | [Kiro — firewalls and proxies](https://kiro.dev/docs/web/firewalls/) | +| `devin-desktop` (`windsurf` alias) | [Devin Desktop — domains](https://docs.devin.ai/desktop/troubleshooting/windsurf-common-issues) | +| `generic` | No vendor | + +--- + +## How to verify + +```bash +curl -I --max-time 10 https://10x-toolkit-api.przeprogramowani.workers.dev/health +curl -I --max-time 10 https://registry.npmjs.org/@przeprogramowani/10x-cli/latest +curl -I --max-time 10 https://toolkit.przeprogramowani.pl/auth/callback +curl -I --max-time 10 https://10xbench.ai/api/leaderboard.json +curl -I --max-time 10 https://github.com/przeprogramowani/10x-bench-kit +``` + +TLS handshake matters (HTTP 200 is not required). On the device: `10x doctor`. + +| Blocked host | CLI message | +|--------------|-------------| +| API | `Could not reach the 10x-toolkit API.` | +| `doctor`, 5 s timeout | `{url} did not respond within 5s.` | +| `doctor`, other network error | `{url} is unreachable.` | +| npm (does not fail `doctor`) | `10x-cli {version} (update check skipped).` | +| `10x bench` | `Could not reach 10xbench.ai.` | +| `10x bench-kit` | `Could not download the template from https://github.com/przeprogramowani/10x-bench-kit.` | +| bad certificate / intercept | `network_error` plus the runtime message | + +Exit codes: `0` success, `1` error, `2` usage, `3` auth required, `4` forbidden, `5` not found. `doctor` with a dead API: **78**. + +--- + +## Keeping this list current + +Canonical hosts: [network-allowlist.json](network-allowlist.json). `tests/network-allowlist.test.ts` fails if `src/` or this document / the Polish version introduce a host outside the list. A new host = the same PR: JSON + EN + PL. diff --git a/docs/reference/platform-support.md b/docs/reference/platform-support.md index a8f919a..ef5886b 100644 --- a/docs/reference/platform-support.md +++ b/docs/reference/platform-support.md @@ -3,6 +3,7 @@ ## Requirements - **Node 20+** — the only runtime dependency on all platforms. +- Corporate network allowlist: Polish [wymagania-sieciowe.md](../wymagania-sieciowe.md), English [network-requirements.md](../network-requirements.md). ## Supported platforms diff --git a/docs/wymagania-sieciowe.md b/docs/wymagania-sieciowe.md new file mode 100644 index 0000000..f789cc8 --- /dev/null +++ b/docs/wymagania-sieciowe.md @@ -0,0 +1,146 @@ +# Wymagania sieciowe 10x-cli + +English: [network-requirements.md](network-requirements.md). +Machine-readable host list: [network-allowlist.json](network-allowlist.json). + +10x-cli (`@przeprogramowani/10x-cli`) loguje użytkownika kursu 10xDevs i zapisuje materiały lekcji na dysk. Nie otwiera portu nasłuchującego. Nie wysyła kodu źródłowego projektu. + +--- + +## Allowlista + +Wszystkie wpisy: HTTPS, TCP 443, z urządzenia. + +### Wymagane do kursu + +| Host | Port | Protokół | Cel | Bez tego | +|------|------|----------|-----|----------| +| `10x-toolkit-api.przeprogramowani.workers.dev` | 443 | HTTPS | Logowanie, odświeżanie tokenu, lista i treść lekcji, `10x doctor` | Nie działa `10x auth`, `list`, `get`, `sync`; `doctor` zgłasza, że API jest nieosiągalne | +| `registry.npmjs.org` | 443 | HTTPS | Instalacja i aktualizacja `@przeprogramowani/10x-cli` (`npx` / `npm install -g`) | Nie da się zainstalować ani zaktualizować pakietu npm. `10x doctor` nadal działa — pomija sprawdzenie wersji | +| `toolkit.przeprogramowani.pl` | 443 | HTTPS | Link z e-maila logowania (`/auth/callback`) otwierany w przeglądarce | Logowanie mailem (`10x auth`) nie dokończy się po kliknięciu linku | + +`10x-cli` woła API wyłącznie na `10x-toolkit-api.przeprogramowani.workers.dev`. `toolkit.przeprogramowani.pl` jest tylko dla przeglądarki przy logowaniu mailem. + +### Przeglądarka — logowanie Circle + +| Host | Port | Protokół | Cel | Bez tego | +|------|------|----------|-----|----------| +| `10x-toolkit-api.przeprogramowani.workers.dev` | 443 | HTTPS | Strona zatwierdzenia z wiadomości Circle | `10x auth --method circle` czeka, aż link wygaśnie | +| `app.circle.so` | 443 | HTTPS | Odczyt wiadomości Circle z linkiem | Użytkownik nie otworzy wiadomości (proces `10x` nie łączy się z tym hostem) | + +### Opcjonalne + +| Host | Port | Protokół | Cel | Bez tego | +|------|------|----------|-----|----------| +| `10xbench.ai` | 443 | HTTPS | Ranking modeli (`10x bench`) | `10x bench` nie działa; pobieranie lekcji bez zmian | +| `github.com` | 443 | HTTPS | `git clone` szablonu `10x-bench-kit`; opcjonalny binary z GitHub Releases | `10x bench-kit` nie pobierze szablonu. Kurs (`get` / `sync`) bez zmian | +| `objects.githubusercontent.com` | 443 | HTTPS | CDN assetów GitHub Releases (do potwierdzenia) | Pobranie standalone binary z Releases może się nie udać | +| `release-assets.githubusercontent.com` | 443 | HTTPS | CDN assetów GitHub Releases (do potwierdzenia) | j.w. | +| `10xdevs.pl` | 443 | HTTPS | Strona zapisu na kurs (tylko w komunikacie błędu) | Brak wpływu na CLI | + +`10x bench-kit` może też wywołać `git ls-remote` wobec `origin` repozytorium, w którym uruchomiono komendę — to dowolny host z konfiguracji git użytkownika. + +--- + +## Protokół i TLS + +- HTTPS / TLS, TCP 443. Logowanie: `POST`/`GET` i powtarzany HTTP (co 2 s, do 5 min). Brak WebSocket i SSE. +- Timeout API i 10xbench: 30 s. `doctor` → API: 5 s. `doctor` → npm: 2 s. +- User-Agent: `10x-cli`. +- CLI nie nasłuchuje na żadnym porcie. + +Przy korporacyjnym TLS inspection (własny CA): + +| Ustawienie | Fakt | +|------------|------| +| `NODE_EXTRA_CA_CERTS` | Node.js doda plik PEM z CA. Działa, gdy `10x` uruchamia Node (instalacja npm). | +| Magazyn CA systemu | CLI nie włącza `--use-system-ca`. | +| `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` | `npm` / `npx` zwykle je honorują. `fetch` w Node 20 (minimum CLI) ich nie używa bez `NODE_USE_ENV_PROXY=1` (Node 24+). Na Node 20–22 proxy dla `10x auth` / `10x get`: do potwierdzenia. | +| Standalone binary | Inny runtime niż Node. `NODE_EXTRA_CA_CERTS` / `HTTPS_PROXY`: do potwierdzenia. | + +Jeśli inspection psuje TLS: wskaż korporacyjny root w `NODE_EXTRA_CA_CERTS` albo wyłącz inspection dla hostów z tabeli. + +--- + +## Na urządzeniu + +| Składnik | Wymagane | +|----------|----------| +| Node.js ≥ 20 | Tak przy instalacji npm/npx | +| npm / npx | Tak przy instalacji z npm. Nie przy standalone binary | +| git | Tylko `10x bench-kit` | +| Docker / Podman | Nie dla CLI | +| System | macOS, Linux, Windows | + +Tokeny sesji (e-mail, access/refresh JWT, data ważności) — nie hasło do Circle: + +| System | Katalog | Pliki | Uprawnienia (POSIX) | +|--------|---------|-------|---------------------| +| macOS / Linux | `$XDG_CONFIG_HOME/10x-cli` lub `~/.config/10x-cli` | `auth.json`, `config.json` | katalog `0700`, `auth.json` `0600` | +| Windows | `%APPDATA%\10x-cli` | te same | — | + +Materiały lekcji zapisują się w katalogu projektu użytkownika (np. `.claude/skills/`, `CLAUDE.md`). + +--- + +## Dane wychodzące z urządzenia + +| Dane | Kiedy | +|------|-------| +| Adres e-mail | `10x auth`; zapis w `auth.json` | +| JWT access + refresh | Logowanie i każde wywołanie API (`Authorization: Bearer`) | +| Hostname i krótki opis OS | Tylko start `10x auth --method circle` | +| User-Agent `10x-cli` | Każde żądanie CLI | +| GET wersji pakietu (bez tokenu) | `10x doctor` → `registry.npmjs.org` | +| Publiczny JSON rankingu (bez tokenu) | `10x bench` | + +Kod źródłowy projektu nie jest wysyłany. + +--- + +## Narzędzia AI (osobna allowlista) + +10x-cli nie woła Anthropic, OpenAI, Google ani OpenRouter. Zapisuje pliki pod wybrane `--tool`. Ruch modelu to ruch tego narzędzia. + +| `--tool` | Wymagania sieciowe dostawcy | +|----------|------------------------------| +| `claude-code` | [Claude Code — enterprise network](https://docs.claude.com/en/docs/claude-code/network-config) | +| `cursor` | [Cursor — network configuration](https://cursor.com/docs/enterprise/network-configuration) | +| `copilot` | [GitHub Copilot allowlist](https://docs.github.com/copilot/reference/copilot-allowlist-reference) | +| `codex` | [Codex — network](https://developers.openai.com/codex/agent-approvals-security/) — pełna lista hostów lokalnego CLI: do potwierdzenia u OpenAI | +| `gemini` | [Gemini Code Assist network access](https://developers.google.com/gemini-code-assist/docs/network-access) — do potwierdzenia dla Gemini CLI | +| `kiro` | [Kiro — firewalls and proxies](https://kiro.dev/docs/web/firewalls/) | +| `devin-desktop` (alias `windsurf`) | [Devin Desktop — domains](https://docs.devin.ai/desktop/troubleshooting/windsurf-common-issues) | +| `generic` | Brak dostawcy | + +--- + +## Weryfikacja + +```bash +curl -I --max-time 10 https://10x-toolkit-api.przeprogramowani.workers.dev/health +curl -I --max-time 10 https://registry.npmjs.org/@przeprogramowani/10x-cli/latest +curl -I --max-time 10 https://toolkit.przeprogramowani.pl/auth/callback +curl -I --max-time 10 https://10xbench.ai/api/leaderboard.json +curl -I --max-time 10 https://github.com/przeprogramowani/10x-bench-kit +``` + +Ważne jest zestawienie TLS (niekoniecznie HTTP 200). Na urządzeniu: `10x doctor`. + +| Blokada | Komunikat CLI | +|---------|----------------| +| API | `Could not reach the 10x-toolkit API.` | +| `doctor`, timeout 5 s | `{url} did not respond within 5s.` | +| `doctor`, inny błąd sieci | `{url} is unreachable.` | +| npm (nie blokuje `doctor`) | `10x-cli {wersja} (update check skipped).` | +| `10x bench` | `Could not reach 10xbench.ai.` | +| `10x bench-kit` | `Could not download the template from https://github.com/przeprogramowani/10x-bench-kit.` | +| zły certyfikat / intercept | `network_error` i komunikat runtime | + +Kody wyjścia: `0` sukces, `1` błąd, `2` użycie, `3` brak sesji, `4` brak uprawnień, `5` nie znaleziono. `doctor` przy martwym API: **78**. + +--- + +## Aktualizacja tej listy + +Kanoniczne hosty: [network-allowlist.json](network-allowlist.json). Test `tests/network-allowlist.test.ts` failuje, gdy `src/` albo ten dokument / wersja angielska dodadzą hosta spoza listy. Nowy host = ten sam PR: JSON + PL + EN. diff --git a/package.json b/package.json index 9f9a945..121d391 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@przeprogramowani/10x-cli", - "version": "1.23.0", + "version": "1.23.1", "description": "Open-source CLI for 10xDevs course content", "repository": { "type": "git", diff --git a/tests/network-allowlist.test.ts b/tests/network-allowlist.test.ts new file mode 100644 index 0000000..939a662 --- /dev/null +++ b/tests/network-allowlist.test.ts @@ -0,0 +1,152 @@ +/** + * Fails when src/ or the sysadmin docs mention a hostname that is not listed + * in docs/network-allowlist.json. Hosts with inDocs: true must appear in both + * the Polish and English documents. + */ +import { describe, expect, it } from "bun:test"; +import { readdirSync, readFileSync, statSync } from "node:fs"; +import { join, relative, resolve } from "node:path"; + +const ROOT = resolve(import.meta.dir, ".."); +const SRC = join(ROOT, "src"); +const ALLOWLIST_PATH = join(ROOT, "docs/network-allowlist.json"); +const DOC_PATHS = [ + "docs/wymagania-sieciowe.md", + "docs/network-requirements.md", +] as const; +const HOST_RE = /https?:\/\/([a-zA-Z0-9.-]+)/g; +/** Vendor documentation links in the AI-tool table — not 10x-cli destinations. */ +const VENDOR_DOC_HOSTS = new Set([ + "docs.claude.com", + "cursor.com", + "docs.github.com", + "developers.openai.com", + "developers.google.com", + "kiro.dev", + "docs.devin.ai", +]); + +interface AllowlistHost { + hostname: string; + inDocs?: boolean; +} + +interface AllowlistFile { + version: number; + hosts: AllowlistHost[]; +} + +function walkTsFiles(dir: string): string[] { + const out: string[] = []; + for (const name of readdirSync(dir)) { + const path = join(dir, name); + const st = statSync(path); + if (st.isDirectory()) out.push(...walkTsFiles(path)); + else if (name.endsWith(".ts")) out.push(path); + } + return out; +} + +/** Drop block comments, then line comments that are not inside http(s)://. */ +function stripComments(source: string): string { + const withoutBlocks = source.replace(/\/\*[\s\S]*?\*\//g, " "); + return withoutBlocks + .split("\n") + .map((line) => { + let i = 0; + while (i < line.length) { + if (line.startsWith("https://", i)) { + i += "https://".length; + continue; + } + if (line.startsWith("http://", i)) { + i += "http://".length; + continue; + } + if (line[i] === "/" && line[i + 1] === "/") return line.slice(0, i); + i += 1; + } + return line; + }) + .join("\n"); +} + +function hostsInText(text: string): Set { + const found = new Set(); + HOST_RE.lastIndex = 0; + let match: RegExpExecArray | null; + while ((match = HOST_RE.exec(text)) !== null) { + const host = match[1]?.replace(/\.+$/, ""); + if (host) found.add(host); + } + return found; +} + +/** Allowlist only public DNS names (a dot, not an IP literal). */ +function isPublicHostname(host: string): boolean { + if (!host.includes(".")) return false; + if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(host)) return false; + return true; +} + +function hostsInSource(): Map { + const found = new Map(); + for (const file of walkTsFiles(SRC)) { + const text = stripComments(readFileSync(file, "utf8")); + for (const host of hostsInText(text)) { + if (!isPublicHostname(host)) continue; + const rel = relative(ROOT, file); + const list = found.get(host) ?? []; + if (!list.includes(rel)) list.push(rel); + found.set(host, list); + } + } + return found; +} + +describe("docs/network-allowlist.json", () => { + const raw = JSON.parse(readFileSync(ALLOWLIST_PATH, "utf8")) as AllowlistFile; + const listed = new Set(raw.hosts.map((h) => h.hostname)); + const inDocs = raw.hosts.filter((h) => h.inDocs).map((h) => h.hostname); + const docTexts = Object.fromEntries( + DOC_PATHS.map((path) => [path, readFileSync(join(ROOT, path), "utf8")]), + ); + + it("is a versioned host list", () => { + expect(raw.version).toBe(1); + expect(raw.hosts.length).toBeGreaterThan(0); + expect(listed.has("10x-toolkit-api.przeprogramowani.workers.dev")).toBe(true); + expect(listed.has("registry.npmjs.org")).toBe(true); + }); + + it("lists every public hostname referenced from src/", () => { + const missing: string[] = []; + for (const [host, files] of hostsInSource()) { + if (!listed.has(host)) missing.push(`${host} (${files.join(", ")})`); + } + expect(missing).toEqual([]); + }); + + it("lists every non-vendor hostname referenced from both sysadmin docs", () => { + const missing: string[] = []; + for (const path of DOC_PATHS) { + const text = docTexts[path] ?? ""; + for (const host of hostsInText(text)) { + if (!isPublicHostname(host) || VENDOR_DOC_HOSTS.has(host)) continue; + if (!listed.has(host)) missing.push(`${host} (${path})`); + } + } + expect(missing).toEqual([]); + }); + + it("requires inDocs hosts in both the Polish and English documents", () => { + const missing: string[] = []; + for (const path of DOC_PATHS) { + const text = docTexts[path] ?? ""; + for (const host of inDocs) { + if (!text.includes(host)) missing.push(`${host} missing from ${path}`); + } + } + expect(missing).toEqual([]); + }); +});