From fb012249d36f8456a833ca403a2ee980db1fd04d Mon Sep 17 00:00:00 2001 From: dstours Date: Fri, 17 Jul 2026 18:39:55 -0400 Subject: [PATCH 1/3] Polish and complete documentation --- .github/workflows/pages.yml | 2 +- README.md | 16 +- docs-site/index.html | 2 +- docs-site/src/App.tsx | 72 ++++++- docs/ARCHITECTURE.md | 137 +++++++++++++ docs/CHANNELS.md | 203 ++++++++++++++++++++ docs/CLI.md | 240 +++++++++++++++++++++++ docs/CONFIGURATION.md | 247 ++++++++++++++++++++++++ docs/DEVELOPMENT.md | 152 +++++++++++++++ docs/GITHUB_SETUP.md | 156 +++++++++++++++ docs/INSTALLATION.md | 173 +++++++++++++++++ docs/QUICKSTART.md | 319 +++++++++++++++++-------------- docs/README.md | 167 +++++++++------- docs/TROUBLESHOOTING.md | 186 ++++++++++++++++++ scripts/check-doc-consistency.ts | 8 + 15 files changed, 1848 insertions(+), 232 deletions(-) create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/CHANNELS.md create mode 100644 docs/CLI.md create mode 100644 docs/CONFIGURATION.md create mode 100644 docs/DEVELOPMENT.md create mode 100644 docs/GITHUB_SETUP.md create mode 100644 docs/INSTALLATION.md create mode 100644 docs/TROUBLESHOOTING.md diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 5b62fef..cb7d122 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -1,4 +1,4 @@ -name: Deploy experimental docs to GitHub Pages +name: Deploy docs to GitHub Pages on: push: diff --git a/README.md b/README.md index 7f22d1c..8ecf6df 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@

- A GitHub-native, encrypted control plane for authorized systems research. + GitHub-native command-and-control for authorized security research, with encrypted multi-channel transport and resilient failover.

> [!IMPORTANT] @@ -21,15 +21,25 @@ OctoC2 combines a TypeScript beacon, durable controller, local operator dashboard, and CLI. GitHub-backed and direct transports share one signed task protocol, one identity model, and the same result-ownership rules. +**Documentation:** [Browse the complete manual](docs/README.md) or open the +[documentation site](https://dstours.github.io/OctoC2/). + ## Start here | I want to… | Read or run | |---|---| | Browse all documentation | [Documentation index](docs/README.md) | -| Understand the system | [Architecture](#architecture) | -| Set up a local environment | [Local evaluation quickstart](docs/QUICKSTART.md) | +| Understand the system | [Architecture guide](docs/ARCHITECTURE.md) | +| Install the toolchain and binaries | [Installation](docs/INSTALLATION.md) | +| Configure the GitHub App, repos, and PATs | [GitHub setup](docs/GITHUB_SETUP.md) | +| Complete a first local run | [Quickstart](docs/QUICKSTART.md) | +| Configure every channel | [Channel guide](docs/CHANNELS.md) | +| Use operator commands | [CLI reference](docs/CLI.md) | +| Look up an environment variable | [Configuration reference](docs/CONFIGURATION.md) | | Configure listeners and certificates | [Operations and assurance](docs/PRODUCTION.md) | | Configure GitHub App recovery | [Recovery guide](docs/RECOVERY.md) | +| Diagnose a failure | [Troubleshooting](docs/TROUBLESHOOTING.md) | +| Contribute a change | [Development guide](docs/DEVELOPMENT.md) | | Review implementation and live evidence | [Verification traceability](docs/REMEDIATION_TRACEABILITY.md) | | Use the dashboard | [Dashboard guide](dashboard/README.md) | diff --git a/docs-site/index.html b/docs-site/index.html index 878690b..4cce84b 100644 --- a/docs-site/index.html +++ b/docs-site/index.html @@ -5,7 +5,7 @@ OctoC2 — Documentation - +
diff --git a/docs-site/src/App.tsx b/docs-site/src/App.tsx index 97e9db4..5465184 100644 --- a/docs-site/src/App.tsx +++ b/docs-site/src/App.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' -const owner = import.meta.env.VITE_GITHUB_OWNER || 'octoc2' +const owner = import.meta.env.VITE_GITHUB_OWNER || 'dstours' const repo = import.meta.env.VITE_GITHUB_REPO || 'OctoC2' const repositoryUrl = `https://github.com/${owner}/${repo}` const docsUrl = (path: string) => `${repositoryUrl}/blob/main/${path}` @@ -76,11 +76,47 @@ const transportGroups = [ ] const guides = [ + { + title: 'Installation', + description: 'Install the pinned toolchain, run each component, and build platform beacon binaries.', + href: docsUrl('docs/INSTALLATION.md'), + label: 'Install', + }, + { + title: 'GitHub setup', + description: 'Configure repositories, the GitHub App, least-privilege permissions, PAT roles, and rotation.', + href: docsUrl('docs/GITHUB_SETUP.md'), + label: 'Provision', + }, { title: 'Quickstart', - description: 'Install, configure role-separated credentials, and launch the local stack.', + description: 'Build and import a pre-enrolled beacon, then verify an accepted ping result.', href: docsUrl('docs/QUICKSTART.md'), - label: 'Start here', + label: 'First run', + }, + { + title: 'Architecture', + description: 'Understand components, identity boundaries, task lifecycle, durable state, and recovery.', + href: docsUrl('docs/ARCHITECTURE.md'), + label: 'Learn', + }, + { + title: 'Channel guide', + description: 'Compare every transport, permission, prerequisite, priority rule, and qualification step.', + href: docsUrl('docs/CHANNELS.md'), + label: 'Transports', + }, + { + title: 'Configuration', + description: 'Look up controller, listener, beacon, OIDC, recovery, dashboard, and CLI settings.', + href: docsUrl('docs/CONFIGURATION.md'), + label: 'Reference', + }, + { + title: 'CLI reference', + description: 'Use setup, enrollment, builds, inventory, tasks, results, proxy, and service commands.', + href: docsUrl('docs/CLI.md'), + label: 'Operate', }, { title: 'Operations & assurance', @@ -94,6 +130,18 @@ const guides = [ href: docsUrl('docs/RECOVERY.md'), label: 'Configure recovery', }, + { + title: 'Troubleshooting', + description: 'Diagnose GitHub errors, decrypt failures, acknowledgements, proxy, TLS, gRPC, OIDC, and state.', + href: docsUrl('docs/TROUBLESHOOTING.md'), + label: 'Diagnose', + }, + { + title: 'Development', + description: 'Work with shared contracts, tests, builds, generated protocol bindings, and change checks.', + href: docsUrl('docs/DEVELOPMENT.md'), + label: 'Contribute', + }, { title: 'Verification evidence', description: 'Trace implementation decisions to tests, live qualifications, and cleanup records.', @@ -178,6 +226,7 @@ function App() { Quickstart Transports Security + Guides GitHub @@ -194,11 +243,12 @@ function App() {
@@ -366,8 +416,8 @@ function App() {
{guides.map((guide) => ( @@ -385,12 +435,14 @@ function App() {
-
OctoC2GitHub-native systems research
+
OctoC2Encrypted multi-channel operations

Use only on systems and repositories you are explicitly authorized to test.

diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..6d7aaeb --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,137 @@ +# Architecture + +OctoC2 is a multi-transport control plane built around one signed task +protocol. The transport may change during failover, but task identity, +encryption, ownership, replay protection, and result acceptance do not. + +> [!IMPORTANT] +> **Authorized use only.** Apply these components and trust boundaries only to +> systems and repositories you own or have explicit permission to test. + +## Component model + +```text +Dashboard / octoctl + │ authenticated operator API or scoped GitHub API + ▼ +Controller ─────────────── durable SQLite state + │ identities · tasks · delivery leases + │ signed and results · replay records · cursors + │ encrypted envelopes + ▼ +GitHub APIs · HTTPS/WSS · gRPC/mTLS · signed relays + │ + ▼ +Pre-enrolled beacon ────── persistent identity and task ledger +``` + +| Component | Responsibility | +|---|---| +| Beacon (`implant/`) | Select transports, check in, decrypt and validate tasks, execute catalogued handlers, sign results, and recover configuration | +| Controller (`server/`) | Register identities, queue tasks, grant delivery leases, poll channels, verify results, persist state, and expose operator/direct APIs | +| Dashboard (`dashboard/`) | Present beacon health, task state, results, activity, and transport views to an authenticated operator | +| CLI (`octoctl/`) | Generate keys, provision environments, enroll/build beacons, queue tasks, inspect results, manage recovery, and configure proxies | +| Shared contracts (`shared/`) | Define canonical channel/task catalogs, signed envelopes, key IDs, validation rules, and result receipts | + +## Identity and cryptography + +OctoC2 deliberately separates cryptographic roles: + +| Identity | Algorithm | Trust boundary | +|---|---|---| +| Operator encryption | X25519 / libsodium `crypto_box` | Tasks are sealed to a beacon; results are sealed to the operator | +| Beacon signing | Ed25519 | Check-ins and results are bound to the enrolled beacon identity | +| Recovery signing | Ed25519 | Dead-drop records and key transitions are authenticated independently of GitHub | +| TLS server identity | X.509 | HTTPS/gRPC endpoint name and CA trust | +| gRPC client identity | X.509 + SHA-256 fingerprint binding | Exact certificate-to-beacon binding | +| GitHub identity | App installation token, PAT, or OIDC claims | Authorization to the selected GitHub surface | +| Direct API identity | Operator or per-beacon bearer token | Controller route authorization | + +Encryption does not replace authentication. A result is accepted only after +the signed envelope, enrolled beacon key, beacon/task ownership, task state, +replay record, and result digest all agree. + +## Task lifecycle + +1. An authenticated operator submits a catalogued task for a known beacon. +2. The controller validates arguments, seals the task to that beacon, signs the + envelope, and persists it as `pending`. +3. One eligible channel receives an exclusive delivery lease and publishes or + returns the task. +4. The beacon verifies and decrypts the envelope, records the task in its + durable ledger, and dispatches the matching handler once. +5. The beacon signs and encrypts the result, then returns it through the active + channel. +6. The controller verifies identity and ownership, persists the result, marks + the task complete or failed, and emits a result-acceptance receipt where the + channel supports acknowledgements. + +Delivery attempts may repeat; execution may not. The durable controller state, +exclusive leases, replay store, and beacon ledger make retries safe across +process restarts. + +## Transport abstraction and failover + +`ConnectionFactory` builds the configured tentacles, records health, applies +the priority list, and moves to an eligible fallback after a classified +failure. A transport is eligible only when its runtime configuration and the +canonical channel prerequisites are satisfied. + +GitHub transports exchange the same encrypted payload through different +artifacts. Direct transports use HTTPS or gRPC but preserve the same envelope +and ownership checks. Proxy and relay paths add signed routing; they do not +become a new source of task authority. + +See [Channels](CHANNELS.md) for prerequisites and status, and [Operations and +assurance](PRODUCTION.md) for failover, listener, and acceptance policy. + +## Durable state + +The controller stores beacon identities and lifecycle status, queued tasks, +delivery leases, results, processed-envelope replay records, GitHub cursors, +and channel-specific state in its configured data directory. The beacon stores +its signing identity and task ledger locally. + +Back up and restore these as security state, not disposable cache. Restoring +only part of the state can invalidate identity or replay assumptions. Never +copy one beacon's identity directory to another host. + +## Recovery + +When ordinary channels cannot authenticate, `DeadDropResolver` searches a +dedicated public recovery repository for a deterministic record. Each record +is signed by the recovery identity, sealed to one beacon, generation-numbered, +time-bounded, and constrained by the controller's complete replacement policy. + +A valid record can replace transport configuration and provide a narrowed, +short-lived GitHub App installation-token lease. It cannot grant authority +outside the server's exact per-beacon policy. See [Recovery](RECOVERY.md). + +## Feature boundaries + +- Thirteen channel kinds are selectable; the reserved PR+SSH catalog entry is + intentionally unavailable. +- Six task kinds are accepted: `shell`, `exec`, `ping`, `sleep`, `kill`, and + `evasion`. Argument schemas and risk levels are centralized in `shared/`. +- Remote module loading is rejected across public surfaces. +- HTTP and gRPC listeners are opt-in. HTTP requires TLS; gRPC requires mTLS and + a per-beacon bearer credential. +- App private keys and recovery signing secrets are controller-only. +- Channel availability is an environment claim, not a catalog claim. Local + tests, integration tests, and live task/result qualification are reported as + separate evidence levels. + +## Trust boundaries + +| Boundary | Required control | +|---|---| +| Operator → controller | Operator-only API token, trusted TLS when non-loopback | +| Beacon → controller | Per-beacon bearer, trusted TLS, and mTLS/fingerprint binding for gRPC | +| Component → GitHub | Least-privilege role credential scoped to exact repositories | +| Controller → beacon | Signed task, X25519 sealing, enrolled recipient identity | +| Beacon → controller | Signed result, ownership check, replay rejection, acceptance digest | +| Recovery repo → beacon | Recovery signature, exact beacon recipient, generation and expiry checks | +| Proxy/relay → endpoints | Signed route/configuration and exact repository or certificate bindings | + +For a deployment checklist and failure policy, continue with [Operations and +assurance](PRODUCTION.md). diff --git a/docs/CHANNELS.md b/docs/CHANNELS.md new file mode 100644 index 0000000..1a475c0 --- /dev/null +++ b/docs/CHANNELS.md @@ -0,0 +1,203 @@ +# Channel Guide + +OctoC2 calls each communication implementation a **tentacle**. All selectable +tentacles carry the canonical signed and encrypted task protocol; they differ +in artifact type, authentication, latency, and operational prerequisites. + +> [!IMPORTANT] +> **Authorized use only.** Enable a channel only across repositories, accounts, +> networks, and endpoints included in the approved test boundary. + +## Status vocabulary + +| Status | Meaning | +|---|---| +| Implemented | Complete implant and controller counterparts are present and covered by repository tests | +| Selectable | Runtime code exists, but successful use depends on explicit environment, identity, network, or external-service qualification | +| Unavailable | Reserved identifier; runtime selection is rejected | + +Selectable does not mean live-qualified in every environment. Use the +[traceability record](REMEDIATION_TRACEABILITY.md) to distinguish local tests, +integration tests, and live task/result evidence for the exact revision. + +## Catalog + +| ID | Kind | Path | Status | Principal prerequisites | +|---:|---|---|---|---| +| 1 | `issues` | GitHub issue comments | Implemented | Metadata read, Issues read/write, operator public key | +| 2 | `branch` | Per-beacon branch/files | Implemented | Metadata read, Contents read/write, default branch, operator public key | +| 3 | `actions` | Dispatch + Actions variables | Implemented | Metadata read, Actions and Variables read/write, operator public key | +| 4 | `codespaces` | gRPC over Codespaces SSH | Selectable | gRPC/mTLS, beacon bearer, SSH, user Codespaces token | +| 5 | `pages` | Deployments/statuses | Selectable | Metadata read, Deployments read/write, default branch | +| 6 | `gist` | Secret Gists | Implemented | Two distinct `gist` PATs for one dedicated account | +| 7 | `oidc` | HTTPS with Actions OIDC | Selectable | Actions `id-token: write`, HTTPS/TLS, exact claims binding | +| 7b | `secrets` | Actions variables | Selectable | Metadata read, Variables read/write, operator public key | +| 8 | `pull_request` | Reserved PR+SSH path | Unavailable | Not selectable | +| 9 | `stego` | LSB payload in PNG files | Selectable | Metadata read, Contents read/write, default branch | +| 10 | `proxy` | Signed control/decoy relay | Selectable | Distinct private decoy repo, Issues transport, signed route | +| 11 | `notes` | Git Notes refs | Implemented | Metadata read, Contents read/write, operator public key | +| 12 | `relay` | Authenticated gRPC/SSH consortium | Selectable | Signed relay config, gRPC/mTLS, bearer, optional SSH discovery | +| 13 | `http` | Direct HTTPS/WebSocket | Selectable | Opt-in HTTPS listener, trusted server certificate, beacon bearer | + +## Repository-backed channels + +### Issues + +Issues publishes encrypted tasks and results as comments. Install the App or +scope fine-grained tokens to the control repository with Metadata read and +Issues read/write. Set the `MONITORING_PUBKEY` Actions variable. A `404` often +means the token cannot see the private repo or the referenced issue was removed; +a `403` usually indicates permission or policy denial. + +### Branch + +Branch stores per-beacon transport files on a dedicated branch. It requires +Contents read/write and a resolvable default branch. The implementation can +bootstrap a missing transport branch; protection rules must still permit the +configured identity to write the transport paths. + +### Actions + +Actions combines repository dispatch with Actions variables. Grant both +Actions read/write and Variables read/write. Repository dispatch is accepted +only when sent to the exact configured repository; workflow permissions and +organization policy can still block it. + +### Pages + +Pages uses GitHub deployments and deployment statuses, not a public Pages site. +Grant Deployments read/write and ensure the repository has a default branch. +No public website needs to be enabled. + +### Secrets (`7b`) + +The historical channel name is `secrets`, but its transport uses Actions +**variables**, not repository secret values. Grant Variables read/write. Do not +put transport ciphertext in Actions secrets under the assumption that this +channel reads them. + +### Steganography + +Stego embeds encrypted payload bytes in PNG files and commits them through the +Contents API. It requires the same repository and key prerequisites as Branch. +Verify that security tooling, image optimization, or mirroring does not rewrite +the PNG, because byte transformation destroys the embedded payload. + +### Git Notes + +Notes writes encrypted records under Git Notes refs using Contents access. Repo +mirrors and cleanup jobs do not always preserve notes refs; include those refs +in backup and cleanup procedures. + +## User-level GitHub channels + +### Gists + +Gist uses secret Gists belonging to one dedicated GitHub account. Configure two +different classic PATs with `gist`: one on the controller and one on the +beacon. The repository App installation lease is not used. Remove test Gists +after a live qualification, but retain or revoke the PATs only according to the +operator's credential cleanup decision. + +### Codespaces + +Codespaces discovers an approved Codespace through the GitHub API and opens an +SSH tunnel to the controller's gRPC listener. It requires: + +- a classic PAT with `codespace` for the account that can access the named + Codespace; +- GitHub CLI authentication and SSH connectivity; +- gRPC server CA/certificate/key; +- a unique client certificate and exact fingerprint binding for the beacon; +- a per-beacon bearer credential. + +Direct gRPC can be used without Codespaces discovery by setting +`SVC_GRPC_DIRECT`. Codespaces never falls back to a repository App lease for its +user-level API. + +## Direct channels + +### HTTP + +HTTP uses the controller's opt-in HTTPS/WebSocket listener. Configure a server +certificate whose SAN covers the hostname in `SVC_HTTP_URL`, install the CA in +the beacon trust path, and give the beacon its exact bearer token. Credentials +are accepted in headers, not URLs. There is no plaintext or certificate-bypass +mode. + +### OIDC + +OIDC is intended for GitHub Actions jobs. The workflow requests +`id-token: write`, obtains an identity token for the configured audience, and +connects to the HTTPS controller. The controller binding must match repository, +beacon ID, subject, and workflow ref exactly; empty lists and wildcards are +rejected. OIDC proves the workflow identity and does not broaden task or result +ownership. + +### Relay + +Relay uses an explicitly provisioned consortium route over authenticated gRPC +and optional SSH discovery. Each hop must satisfy the same mTLS, bearer, and +identity requirements as direct gRPC. Relay configuration is signed; discovery +data is not itself task authority. + +## Proxy channel + +Proxy wraps the Issues channel in a signed two-repository route. The beacon +interacts with a private decoy repository; a workflow relays envelopes to the +private control repository and returns results. Provision both sides with +`octoctl proxy create` or `proxy provision`, then follow the [proxy workflow +contract](../templates/proxy/README.md). + +The control and decoy repositories must be different. Use exact repository +bindings, distinct dispatch credentials, signed envelopes, and route-level +deduplication. A proxy test is complete only after the live result is accepted +and relay artifacts are cleaned from both repositories. + +## Selection and failover + +Set a comma-separated priority list: + +```text +SVC_TENTACLE_PRIORITY=issues,branch,actions,http +``` + +Unknown and unavailable channel names are logged and ignored; if none remain, +the beacon falls back to Issues. Configure every listed channel completely and +review startup logs for ignored entries. `ConnectionFactory` selects healthy +channels in order, classifies failures, and retries or fails over according to +channel policy. + +Inspect configuration and health with: + +```bash +octoctl tentacles list +octoctl tentacles health +``` + +Force a specific route for an authorized qualification: + +```bash +octoctl task --kind ping --tentacle notes +octoctl results --last 1 +``` + +A successful registration alone is not channel qualification. Record encrypted +task publication, beacon receipt, signed result publication, controller +acceptance, and artifact cleanup through the same named channel. + +## Choosing a channel + +| Need | Prefer | +|---|---| +| Simplest private-repo baseline | Issues | +| Repository path without issue artifacts | Branch or Git Notes | +| GitHub Actions integration | Actions or OIDC | +| No control-repo visibility at the endpoint | Proxy with a distinct decoy repo | +| Low-latency private network path | HTTPS or direct gRPC | +| Gist-only user surface | Gist with dedicated credentials | +| Recovery after normal credentials fail | Signed recovery record, then the recovered eligible channel | + +Choose the least-complex channel that satisfies the approved test boundary. +Adding channels increases credentials, permissions, artifacts, and cleanup +obligations. diff --git a/docs/CLI.md b/docs/CLI.md new file mode 100644 index 0000000..467d223 --- /dev/null +++ b/docs/CLI.md @@ -0,0 +1,240 @@ +# CLI Reference + +`octoctl` is the operator interface for setup, enrollment, builds, tasking, +results, channel health, proxy routes, and local services. + +> [!IMPORTANT] +> **Authorized use only.** CLI commands can change remote repositories and +> endpoint state. Verify the target and task risk before submission. + +Run it from source: + +```bash +cd octoctl +bun run src/index.ts --help +``` + +Examples below use `octoctl` as an alias for that command. Every command +supports `--help`. + +## Operator environment + +| Variable | Used for | +|---|---| +| `OCTOC2_SERVER_URL` | HTTPS controller origin for live operator commands | +| `OCTOC2_HTTP_CA_CERT` | CA file used to verify the controller certificate | +| `OCTOC2_OPERATOR_API_TOKEN` | Operator authentication to controller REST/SSE routes | +| `OCTOC2_OPERATOR_GITHUB_TOKEN` | Direct operator access to the configured GitHub repo | +| `OCTOC2_REPO_OWNER`, `OCTOC2_REPO_NAME` | Control repository coordinates | +| `OCTOC2_OPERATOR_SECRET` | Base64url X25519 secret for task/result cryptography | +| `OCTOC2_DATA_DIR` | Offline controller data directory (default `./data`) | + +Prefer environment variables or protected files over command-line token +options, because process arguments may be observable by other local users. + +## Setup and keys + +### `setup` + +Run the guided deployment workflow: + +```bash +octoctl setup +octoctl setup --phase validate +``` + +Available phases are `credentials`, `validate`, `keygen`, `auth`, `tentacles`, +`env`, `build`, and `verify`. A phase is useful when revisiting one part of an +existing configuration. + +### `keygen` + +Generate an operator X25519 keypair: + +```bash +octoctl keygen +octoctl keygen --set-variable +``` + +`--set-variable` writes the public key to the control repository's +`MONITORING_PUBKEY` Actions variable and requires the direct operator GitHub +credential. Store the printed secret outside source control. + +## Beacon inventory and health + +```bash +octoctl beacons +octoctl beacons --status active +octoctl beacons --json --data-dir /secure/octoc2-data +``` + +Status filters are `active`, `dormant`, and `lost`. + +Inspect one beacon's channel activity from durable local state or the live API: + +```bash +octoctl tentacles list --beacon +octoctl tentacles health --beacon --server-url https://controller.example:8080 +octoctl tentacles list --beacon --verbose --json +``` + +`health` is an alias for `list`. Observed activity is not proof that every +channel prerequisite is currently satisfied. + +## Queue tasks + +```bash +octoctl task --kind ping +octoctl task --kind shell --cmd "whoami" +octoctl task --kind exec --args-json '{"cmd":"id","args":["-u"]}' +octoctl task --kind sleep --seconds 300 +octoctl task --kind ping --tentacle notes +``` + +Use `--args-json` for advanced schemas. `--tentacle` pins the delivery attempt +to one selectable channel and is useful for qualification; omit it for normal +priority/failover behavior. + +### Task catalog + +| Kind | Risk | Required arguments | Purpose | +|---|---|---|---| +| `ping` | Routine | `{}` | Connectivity and process metadata probe | +| `sleep` | Routine | `seconds`; optional `jitter` | Change check-in timing | +| `shell` | Elevated | `cmd`; optional `cwd`, `timeout` | Run through the platform shell | +| `exec` | Elevated | `cmd`; optional `args`, `cwd`, `timeout` | Execute a program directly | +| `kill` | Destructive | `{}` | Terminate the beacon process | +| `evasion` | Destructive | Explicit action-specific schema | Invoke an implemented lifecycle/evasion action | + +The shared validator rejects unknown fields, invalid types, non-finite values, +commands longer than 32 KiB, paths longer than 4 KiB, timeouts beyond five +minutes, and sleep values outside the accepted bounds. `load-module` is not an +accepted task kind. + +Treat `shell`, `exec`, `kill`, and `evasion` as change-controlled operations. +Use `ping` for initial transport verification. + +## Fetch results + +```bash +octoctl results +octoctl results --last 5 +octoctl results --since 2h +octoctl results --since 2026-07-17T12:00:00.000Z --json +``` + +The default window is 24 hours. Results returned by the controller have passed +its identity, ownership, signature, replay, and state checks. + +## Interactive and bulk operation + +```bash +octoctl beacon shell --beacon +octoctl beacon shell --beacon --tentacle notes --timeout 300 +``` + +The interactive shell uses the authenticated controller HTTP API. It does not +create an unauthenticated terminal listener. + +```bash +octoctl bulk shell \ + --beacon-ids , \ + --cmd "whoami" \ + --wait \ + --timeout 120 +``` + +Bulk shell is elevated and targets every listed beacon. Review the expanded ID +set before submitting it. + +## Build beacon + +Simple mode compiles without baking an enrollment identity: + +```bash +octoctl build-beacon --output ./beacon --platform linux-x64 +``` + +Full mode generates/bakes the beacon ID, X25519 identity, Ed25519 signing +identity, and non-secret endpoint selection: + +```bash +octoctl build-beacon \ + --outfile ./beacon-macos-arm64 \ + --target bun-darwin-arm64 \ + --tentacle-priority issues,notes +``` + +Key options: + +| Option | Meaning | +|---|---| +| `--beacon-id ` | Use an approved pre-assigned ID instead of generating one | +| `--target ` | Full-mode Bun compile target; default `bun-linux-x64` | +| `--relay ` | Add a relay consortium entry; repeatable | +| `--grpc-url ` | Bake a direct TLS gRPC endpoint | +| `--http-url ` | Bake the direct HTTPS endpoint | +| `--codespace-name`, `--github-user` | Bake non-secret Codespaces discovery fields | +| `--tentacle-priority ` | Bake priority order | +| `--no-random-title` | Use the default Issues title rather than a randomized title | + +Runtime PATs, bearer credentials, client private keys, and App private keys are +not build options. Supply them securely on the target. + +## Recovery records + +```bash +octoctl drop create \ + --beacon \ + --configuration-file ./recovery-config.json \ + --generation 2 \ + --recovery-signing-secret-file /secure/recovery-signing.key + +octoctl drop list --beacon +``` + +Repository coordinates, ref, writer token, signing public key/key ID, issuance, +expiry, and data directory have explicit flags or documented environment +fallbacks. Follow [Recovery](RECOVERY.md); a hand-authored partial record will +be rejected. + +## Proxy routes + +```bash +octoctl proxy create \ + --decoy-repo \ + --beacon \ + --ctrl-owner \ + --ctrl-repo \ + --proxy-installation-id + +octoctl proxy templates +octoctl proxy list +octoctl proxy rotate '' +``` + +`proxy create` can add `--create-repo` and `--scaffold`. Dispatch tokens and the +stable relay signing key should come from protected environment variables. See +the [proxy workflow contract](../templates/proxy/README.md). + +## Local service commands + +```bash +octoctl start --env .env +octoctl start server --env .env +octoctl status +octoctl stop dashboard +octoctl stop +octoctl update --branch main +``` + +`start` manages local background processes; it is not a production service +manager. `update` pulls the named branch and reinstalls dependencies, so review +local changes and upstream changes before using it. + +## Output and exit behavior + +Use `--json` where offered for automation. Successful commands exit `0`. +Validation, configuration, API, and filesystem failures exit non-zero and emit +an actionable message. Never parse human-oriented tables when JSON output is +available. diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md new file mode 100644 index 0000000..5fe5727 --- /dev/null +++ b/docs/CONFIGURATION.md @@ -0,0 +1,247 @@ +# Configuration Reference + +OctoC2 reads configuration from environment variables, protected files, and +pre-enrollment defines created by `octoctl build-beacon`. This page covers the +operator-facing variables; [Operations and assurance](PRODUCTION.md) and +[Recovery](RECOVERY.md) define their security invariants in depth. + +> [!IMPORTANT] +> **Authorized use only.** Keep every configured repository, identity, and +> listener within the approved environment and keep secrets out of source. + +## Loading and precedence + +- `octoctl start --env ` loads the selected dotenv file for locally + managed components. +- Directly started Bun processes inherit the shell environment. +- Explicit CLI options override their documented environment fallback. +- Full beacon builds bake identity and selected non-secret routing fields. +- Signed recovery configuration can replace the recovery-controlled runtime + fields only when its complete policy, signature, recipient, generation, and + expiry validate. + +Never commit `.env`, PEM files, bearer-token maps, PATs, enrollment artifacts, +or controller state. + +## Minimal controller + +| Variable | Required | Default | Purpose | +|---|---:|---|---| +| `OCTOC2_SERVER_GITHUB_TOKEN` | Yes | — | Server GitHub API credential | +| `OCTOC2_REPO_OWNER` | Yes | — | Control repository owner | +| `OCTOC2_REPO_NAME` | Yes | — | Control repository name | +| `OCTOC2_OPERATOR_SECRET` | Yes | — | Base64url 32-byte X25519 secret | +| `OCTOC2_POLL_INTERVAL_MS` | No | `30000` | GitHub channel polling interval | +| `OCTOC2_DATA_DIR` | No | `./data` | SQLite and controller state directory | +| `OCTOC2_SERVER_GIST_TOKEN` | For Gist | — | Dedicated controller Gist PAT | +| `OCTOC2_ENROLLMENT_DIR` | Recommended | — | Directory of pre-enrollment artifacts imported at startup | + +The controller resolves the operator public key from the +`MONITORING_PUBKEY` repository variable and checks it against the configured +operator secret. + +## HTTPS and operator API + +| Variable | Required when enabled | Default | Purpose | +|---|---:|---|---| +| `OCTOC2_HTTP_ENABLED` | — | `false` | Enable HTTPS/WSS operator and beacon API | +| `OCTOC2_HTTP_HOST` | No | `127.0.0.1` | Bind host | +| `OCTOC2_HTTP_PORT` | No | `8080` | Bind port | +| `OCTOC2_HTTP_SERVER_CERT` | Yes | — | Server certificate chain file | +| `OCTOC2_HTTP_SERVER_KEY` | Yes | — | Server private-key file | +| `OCTOC2_HTTP_CA_CERT` | Client-side | — | CA trust file used by CLI/development tooling | +| `OCTOC2_OPERATOR_API_TOKEN` | Yes | — | Operator-only bearer credential | +| `OCTOC2_BEACON_API_TOKENS` | Yes | — | JSON map of exact beacon ID to unique bearer token | +| `OCTOC2_DASHBOARD_ORIGIN` | No | local policy | Allowed dashboard origin | + +Ports must be integers from 1 through 65535. Non-loopback binds produce an +exposure warning and require an explicitly reviewed network boundary. + +Example credential map: + +```json +{"2f10b98a-0000-4000-8000-000000000001":"replace-with-a-unique-random-token"} +``` + +The GitHub token, operator API token, and every beacon token must be distinct. + +## gRPC + +| Variable | Required when enabled | Default | Purpose | +|---|---:|---|---| +| `OCTOC2_GRPC_ENABLED` | — | `false` | Enable direct/Codespaces/relay gRPC | +| `OCTOC2_GRPC_HOST` | No | `127.0.0.1` | Bind host | +| `OCTOC2_GRPC_PORT` | No | `50051` | Bind port | +| `OCTOC2_GRPC_CA_CERT` | Yes | — | CA used to verify client certificates | +| `OCTOC2_GRPC_SERVER_CERT` | Yes | — | Server certificate chain | +| `OCTOC2_GRPC_SERVER_KEY` | Yes | — | Server private key | +| `OCTOC2_GRPC_CLIENT_CERT_FINGERPRINTS` | Yes | — | JSON map of beacon ID to exact SHA-256 client-cert fingerprint | +| `OCTOC2_BEACON_API_TOKENS` | Yes | — | Per-beacon bearer map, also required for gRPC | + +Each beacon needs a distinct client certificate. A shared certificate or +wildcard fingerprint map is rejected by policy. + +## Lifecycle and replay windows + +| Variable | Default | Constraint or meaning | +|---|---:|---| +| `OCTOC2_LIFECYCLE_INTERVAL_MS` | `60000` | Sweep interval | +| `OCTOC2_BEACON_DORMANT_AFTER_MS` | `600000` | Mark a silent beacon dormant after 10 minutes | +| `OCTOC2_BEACON_LOST_AFTER_MS` | `86400000` | Mark it lost after 24 hours; must exceed dormant threshold | +| `OCTOC2_PROCESSED_MESSAGE_RETENTION_MS` | `2592000000` | Retain replay records for 30 days | +| `OCTOC2_CHECKIN_MAX_AGE_MS` | `1800000` | Reject check-ins older than 30 minutes | +| `OCTOC2_CHECKIN_MAX_FUTURE_SKEW_MS` | `300000` | Allow at most five minutes future clock skew | + +## OIDC + +Set `OCTOC2_OIDC_BINDINGS` to an array of exact bindings: + +```json +[ + { + "repository": "Owner/Repo", + "beaconId": "2f10b98a-0000-4000-8000-000000000001", + "subjects": ["repo:Owner/Repo:environment:prod"], + "workflowRefs": ["Owner/Repo/.github/workflows/transport.yml@refs/heads/main"] + } +] +``` + +`OCTOC2_OIDC_AUDIENCE` optionally replaces the default audience. Repository, +beacon ID, subject, and workflow ref are exact; wildcards and empty arrays are +not accepted. + +## Recovery publisher + +Set `OCTOC2_RECOVERY_PUBLISH_ENABLED=true`, then configure: + +- `OCTOC2_GITHUB_APP_ID` +- `OCTOC2_GITHUB_APP_PRIVATE_KEY_FILE` +- `OCTOC2_GITHUB_APP_POLICIES` +- `OCTOC2_RECOVERY_REPO_OWNER`, `OCTOC2_RECOVERY_REPO_NAME`, and optional + `OCTOC2_RECOVERY_REPO_REF` (default `main`) +- `OCTOC2_RECOVERY_WRITE_TOKEN` +- `OCTOC2_RECOVERY_SIGNING_SECRET_FILE` +- `OCTOC2_RECOVERY_SIGNING_PUBLIC_KEY` and optional derived + `OCTOC2_RECOVERY_SIGNING_KEY_ID` +- `OCTOC2_RECOVERY_POLICIES` +- optional `OCTOC2_RECOVERY_PUBLISH_INTERVAL_MS` +- optional paired `OCTOC2_RECOVERY_NEXT_SIGNING_PUBLIC_KEY` and + `OCTOC2_RECOVERY_NEXT_SIGNING_KEY_ID` during key rotation + +App and recovery policies must name the same exact beacon IDs. Policy formats +are documented in [Recovery](RECOVERY.md). + +## Beacon runtime + +### Identity and repository + +| Variable | Required | Purpose | +|---|---:|---| +| `OCTOC2_REPO_OWNER`, `OCTOC2_REPO_NAME` | Yes | Current control repository | +| `SVC_GITHUB_TOKEN` | One bootstrap path | Explicit scoped repository credential | +| `SVC_GITHUB_TOKEN_LEASE` | One bootstrap path | Server-issued, repository- and beacon-bound lease JSON | +| `SVC_BEACON_API_TOKEN` | Direct transports | Beacon's unique controller bearer credential | +| `SVC_GIST_TOKEN` | Gist | Dedicated beacon Gist PAT | +| `OCTOC2_OPERATOR_PUBKEY` | Without GitHub key lookup | Provisioned 32-byte X25519 public key | + +Configure either `SVC_GITHUB_TOKEN` or `SVC_GITHUB_TOKEN_LEASE`, never both. +App private-key variables are rejected on a beacon. Full builds provision the +beacon ID, X25519 pair, and Ed25519 signing identity; hand-setting those defines +is discouraged. + +### Selection and timing + +| Variable | Default | Constraint or purpose | +|---|---:|---| +| `SVC_TENTACLE_PRIORITY` | Auto-detect direct gRPC, HTTP, then Issues | Comma-separated selectable channel kinds | +| `SVC_SLEEP` | `60` | Integer seconds, 1 through 86400 | +| `SVC_JITTER` | `0.3` | Number from 0 through 1 | +| `SVC_CLEANUP_DAYS` | disabled | Result-comment retention; `0` means immediate cleanup | +| `SVC_RECOVERY_POLL_INTERVAL_MS` | `60000` | 10000 through 2700000 | +| `OCTOC2_LOG_LEVEL` | `info` | `debug`, `info`, `warn`, or `error` | +| `OCTOC2_STATE_DIR` | platform state path | Override persistent beacon state directory | + +Invalid entries in an explicit priority list are logged and ignored; if no +valid entry remains, Issues is selected. Review the startup log instead of +assuming a misspelled channel was selected. + +### HTTP, gRPC, and Codespaces + +| Variable | Purpose | +|---|---| +| `SVC_HTTP_URL` | Trusted HTTPS controller origin | +| `SVC_GRPC_DIRECT` | Direct TLS gRPC `host:port` | +| `SVC_GRPC_CA_CERT` | Trusted server CA file | +| `SVC_GRPC_CLIENT_CERT`, `SVC_GRPC_CLIENT_KEY` | Beacon-specific mTLS identity | +| `SVC_GRPC_CODESPACE_NAME` | Existing approved Codespace name | +| `SVC_GITHUB_USER` | GitHub username for Codespace SSH | +| `SVC_CODESPACES_GITHUB_TOKEN` | Dedicated user-scoped Codespaces PAT | +| `SVC_AUTO_PROVISION_CODESPACE` | Explicit `true`/`1` opt-in to create/start a Codespace | +| `SVC_GRPC_PORT` | Remote gRPC port, default `50051` | +| `SVC_GRPC_LOCAL_PORT` | Local SSH-forward port, default `50051` | + +Do not point `SVC_GRPC_DIRECT` at a GitHub Dev Tunnels HTTPS URL; use the SSH +tunnel path for gRPC or `SVC_HTTP_URL` for HTTPS. + +### Issues acknowledgement tuning + +| Variable | Default | +|---|---:| +| `SVC_POLL_TIMEOUT_MS` | `30000` | +| `SVC_POLL_RETRY_MS` | `10000` | +| `SVC_RESULT_ACK_TIMEOUT_MS` | `120000` | +| `SVC_RESULT_ACK_RETRY_MS` | `5000` | +| `SVC_ISSUE_TITLE` | Generated/default title policy | + +Increase acknowledgement windows only to accommodate measured GitHub or proxy +latency. A longer timeout does not fix missing permissions or a mismatched key. + +### Recovery bootstrap + +The beacon requires these source fields together: + +- `OCTOC2_RECOVERY_REPO_OWNER` +- `OCTOC2_RECOVERY_REPO_NAME` +- `OCTOC2_RECOVERY_REPO_REF` + +It also requires these trust fields together: + +- `OCTOC2_RECOVERY_SIGNING_PUBLIC_KEY` +- `OCTOC2_RECOVERY_SIGNING_KEY_ID` + +Source and trust groups must both be present. `SVC_PROXY_REPOS` is retired; +proxy routes and their short-lived credentials must arrive through signed +recovery configuration. + +## Dashboard + +| Variable | Purpose | +|---|---| +| `VITE_C2_SERVER_URL` | Bare HTTPS controller origin; no path, query, fragment, or userinfo | +| `VITE_GITHUB_OWNER`, `VITE_GITHUB_REPO` | Direct GitHub fallback repository | + +Dashboard credentials are entered at login and held in React memory only. The +development proxy verifies controller TLS. + +## Placeholder-only dotenv example + +```dotenv +OCTOC2_SERVER_GITHUB_TOKEN= +OCTOC2_REPO_OWNER= +OCTOC2_REPO_NAME= +OCTOC2_OPERATOR_SECRET= +OCTOC2_DATA_DIR= + +OCTOC2_HTTP_ENABLED=true +OCTOC2_HTTP_HOST=127.0.0.1 +OCTOC2_HTTP_PORT=8080 +OCTOC2_HTTP_SERVER_CERT= +OCTOC2_HTTP_SERVER_KEY= +OCTOC2_HTTP_CA_CERT= +OCTOC2_OPERATOR_API_TOKEN= +OCTOC2_BEACON_API_TOKENS={"":""} +``` + +Keep beacon runtime configuration in the target's protected environment, not +the controller dotenv when the target is a different machine. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md new file mode 100644 index 0000000..e8cf9a8 --- /dev/null +++ b/docs/DEVELOPMENT.md @@ -0,0 +1,152 @@ +# Development Guide + +OctoC2 is a Bun/TypeScript monorepo with strict shared contracts. Behavior +changes should update the canonical catalog or schema first, then every +producer, consumer, test, and guide affected by that contract. + +> [!IMPORTANT] +> **Authorized use only.** Keep development fixtures isolated and never commit +> live credentials, private-repository content, host data, or personal data. + +## Workspaces + +| Workspace | Entry point | Test command | +|---|---|---| +| `shared/` | library exports | `bun test --timeout 30000` | +| `implant/` | `src/index.ts` | `bun test --timeout 30000` | +| `server/` | `src/index.ts` | `bun test --timeout 30000` | +| `dashboard/` | `src/main.tsx` | `bun test --timeout 30000` | +| `octoctl/` | `src/index.ts` | `bun test --timeout 30000` | +| `proxy/` | workflow/runtime sources | `bun test --timeout 30000` | +| `docs-site/` | `src/main.tsx` | `bun run lint && bun run build` | + +Run `bun run typecheck` in each TypeScript workspace. Strict mode and +`exactOptionalPropertyTypes` are enabled; conditionally spread optional +properties instead of assigning `undefined`. + +## Toolchain and install + +Use Bun `1.3.14`, the checked-in lockfile, and the declared Node 22.14 tooling +range: + +```bash +bun install --frozen-lockfile +bun run toolchain:check +bun run deps:check +``` + +Do not update one workspace's dependency independently when the package is +centrally pinned or overridden at the root. + +## Repository checks + +```bash +bun run proto:check +bun run docs:check +bun run workflows:check +bun run toolchain:check +bun run lint +bun audit +``` + +`docs:check` validates consistency-sensitive claims. `workflows:check` verifies +that Actions dependencies are commit-pinned. `proto:check` ensures generated +gRPC bindings match their source. + +## Protocol changes + +Canonical channel definitions live in `shared/src/channels.ts`; task schemas +and risk classification live in `shared/src/tasks.ts`. Signed envelope and +identity changes also belong in `shared/`. + +When changing a wire contract: + +1. Update its canonical shared type and untrusted-input validator. +2. Add shared positive and negative tests. +3. Update controller and beacon producers/consumers together. +4. Preserve version/replay behavior or add an explicit migration. +5. Regenerate protocol bindings with `bun run proto:gen` if the protobuf changes. +6. Update CLI/dashboard choices and these guides. + +Never make one transport accept a broader payload than the shared contract. + +## Adding or changing a task + +Update the shared task catalog, its exact argument validator and risk level, +the implant `TaskExecutor` handler, the server/operator surfaces, and tests for +unknown fields, range limits, timeouts, and result metadata. Destructive tasks +must be explicit and must not be selected through a generic fallback. + +Remote module loading is deliberately absent. Do not reintroduce arbitrary +unsigned module execution under a new task alias. + +## Adding or changing a channel + +1. Add or update the canonical catalog entry and prerequisites. +2. Implement both implant tentacle and controller counterpart. +3. Register the implant through `registerTentacles.ts` so initial boot and + recovery rebuild remain consistent. +4. Use the shared token getter for repository App leases. +5. Define result-acceptance behavior, cursor scope, artifact ownership, replay + handling, teardown, and cleanup. +6. Add unit tests, a local integration path, and an authorized live + qualification plan. +7. Update [Channels](CHANNELS.md), [GitHub setup](GITHUB_SETUP.md), and the + traceability record without overstating the evidence level. + +Catalog presence is not proof of readiness. Keep incomplete paths selectable +only when their prerequisites and limitations are explicit; keep missing +counterparts unavailable. + +## Testing patterns + +- Use `bun:test`, not Jest or Vitest. +- Restore environment variables and module mocks after each test. +- When `mock.module()` arrays are indexed, cast through `as any` if required to + avoid TypeScript tuple-index inference errors. +- Prefer deterministic clocks, IDs, and in-memory or temporary state roots. +- Test malformed, stale, cross-beacon, replayed, and unauthorized envelopes in + addition to the success path. +- Verify hard-ceiling timeout branches terminate child processes. +- Preserve the dashboard's isolated test runner to avoid module-mock leakage. + +## Builds + +```bash +bun run build:server +bun run build:dashboard +bun run build:octoctl +cd implant && bun run build:all +``` + +For a normal behavior change, test the affected target plus its shared +contracts. Use `bun run smoke:builds` when a change can affect compile targets, +native dependencies, or runtime startup. + +## Documentation standards + +- Make the Markdown guides authoritative; keep the docs site as a concise map. +- Link to implementation status rather than claiming a transport is universally + available. +- Keep local, integration, and live evidence labels distinct. +- Use placeholder credentials only. Never paste `.env` contents, private repo + data, PAT fragments, hostnames, usernames, or live artifact payloads. +- Link current external setup claims to official GitHub documentation. +- Run `bun run docs:check` and validate relative links after edits. + +## Change checklist + +- [ ] Scope is limited to the requested behavior. +- [ ] No secret, PII, private URL, credential fragment, or generated state is in + the diff. +- [ ] Shared contracts and generated bindings agree. +- [ ] Changed workspaces pass tests and strict TypeScript checks. +- [ ] Security-relevant failure cases have regression coverage. +- [ ] Documentation, CLI help, dashboard choices, and channel/task catalogs + agree. +- [ ] Workflow actions remain commit-pinned. +- [ ] Live test artifacts are inventoried and cleaned when live testing occurs. +- [ ] The commit contains no local research notes or unrelated workspace files. + +For release-level assurance and stop conditions, use [Operations and +assurance](PRODUCTION.md) and [Remediation traceability](REMEDIATION_TRACEABILITY.md). diff --git a/docs/GITHUB_SETUP.md b/docs/GITHUB_SETUP.md new file mode 100644 index 0000000..5791050 --- /dev/null +++ b/docs/GITHUB_SETUP.md @@ -0,0 +1,156 @@ +# GitHub Setup + +OctoC2 uses GitHub repositories as explicit transport boundaries. This guide +defines a least-privilege topology, GitHub App registration, and the separate +credentials used by the controller, operator, beacon, proxy, and recovery +publisher. + +GitHub changes its registration UI over time. The field guidance below follows +the current official documentation for [registering a GitHub App](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app), +[choosing App permissions](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/choosing-permissions-for-a-github-app), +and [managing private keys](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/managing-private-keys-for-github-apps). + +> [!IMPORTANT] +> **Authorized use only.** Use dedicated test repositories and accounts. Do not grant organization-wide +> access when selected-repository installation is sufficient. + +## Repository topology + +| Repository | Visibility | Purpose | Installed App? | +|---|---|---|---| +| Control | Private | Primary GitHub-backed task and result transport | Yes | +| Decoy | Private | Distinct outer repository for the optional proxy route | Yes, when proxy is used | +| Recovery | Public | Signed and sealed dead-drop records discoverable without a working control credential | No; use a dedicated writer token | + +Keep control and decoy separate. A proxy route does not create a useful +boundary if both names refer to the same repository. The recovery repository +contains encrypted records and public verification metadata only—never raw +tokens, private keys, hostnames, usernames, or operator notes. + +## Register the GitHub App + +Create the App under the account or organization that owns the test +repositories: + +1. Open **Settings → Developer settings → GitHub Apps → New GitHub App**. +2. Choose a unique, ordinary App name. +3. Set **Homepage URL** to the project repository or owning account URL. +4. Leave **Callback URL** empty. OctoC2 does not request a user access token. +5. Leave **Request user authorization (OAuth) during installation** off. +6. Leave **Enable Device Flow** off. +7. Leave **Expire user authorization tokens** checked. It is GitHub's + recommended setting and has no effect when user tokens are not requested. +8. Leave **Setup URL** empty and **Redirect on update** off. +9. Under **Webhook**, clear **Active**. OctoC2 polls authenticated APIs and does + not require a webhook URL, secret, or event subscription. +10. Select **Only on this account** unless an approved deployment requires the + App to be installable elsewhere. + +### Repository permissions + +Always retain **Metadata: read-only**. Add only the permissions required by the +channels you enable: + +| Channel | Repository permission | +|---|---| +| Issues, proxy inner/outer exchange | Issues: read and write | +| Branch, Git Notes, steganography | Contents: read and write | +| Actions | Actions: read and write; Variables: read and write | +| Secrets (`7b`) | Variables: read and write | +| Pages | Deployments: read and write | + +Gists and Codespaces are user-level surfaces and do not use the repository App +installation token. OIDC authenticates with GitHub Actions identity tokens. +Direct HTTP and gRPC use controller credentials and certificates rather than +GitHub repository permissions. The reserved `pull_request` catalog entry is +not selectable. + +### Install and record the App identity + +After creating the App: + +1. Note its numeric **App ID**. +2. Generate a private key and save the downloaded PEM on the controller only. +3. Restrict the file to the controller service account (`chmod 600` on Unix). +4. Install the App using **Only select repositories** and select the control + repository plus the decoy repository if proxy is enabled. +5. Record the numeric installation ID from the installation settings URL or + query it through the GitHub API. + +The controller exchanges the App JWT for short-lived, repository-restricted +[installation access tokens](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-an-installation-access-token-for-a-github-app). +The App private key never belongs on a beacon, in an Actions secret, or in a +recovery record. + +## Credential roles + +No single PAT needs access to every surface. Create one credential per role and +scope it to the named account and repository. + +| Role | Account/repository placement | Minimum access | +|---|---|---| +| Controller repository token | Dedicated controller identity; control repo only | Metadata read plus enabled channel permissions from the table above | +| Operator GitHub token | Operator account; control repo only | Issues read/write and Variables read/write when using direct GitHub dashboard/CLI paths and `MONITORING_PUBKEY` | +| Beacon Gist token | Dedicated Gist account | Classic PAT with `gist` | +| Controller Gist token | Same dedicated Gist account, different token | Classic PAT with `gist` | +| Codespaces runtime token | Account that owns or can access the named Codespace | Classic PAT with `codespace`; do not reuse a repository token | +| Recovery writer | Recovery owner; recovery repo only | Fine-grained Contents read/write (Metadata is implicit) | +| Proxy dispatch token | Exact workflow target repo | Fine-grained Contents write as required for repository dispatch | + +GitHub recommends fine-grained PATs when they support the required operation; +see [managing personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens). +Gist and current Codespaces CLI/API paths require their noted user-scoped +classic PATs. A third GitHub account is not inherently required: separation is +by credential role, though a dedicated service account reduces the blast +radius and makes audit attribution clearer. + +OctoC2 application credentials are not PATs: + +| Credential | Purpose | +|---|---| +| `OCTOC2_OPERATOR_API_TOKEN` | Dashboard/CLI authentication to the controller | +| `OCTOC2_BEACON_API_TOKENS` | Exact beacon ID to bearer-token map for direct transports | +| `OCTOC2_OPERATOR_SECRET` | X25519 secret used to decrypt results and seal tasks | +| Beacon Ed25519 key | Persistent signing identity for check-ins and results | +| Recovery Ed25519 key | Server-only signing identity for dead-drop records | + +## Repository variables and secrets + +Set `MONITORING_PUBKEY` as an Actions repository variable on every repository +that directly carries encrypted tasks. Its value is the operator's X25519 +public key generated by `octoctl keygen`; it is not a secret. + +Proxy workflow repositories use the variables and secrets documented in the +[proxy workflow contract](../templates/proxy/README.md). Keep workflow dispatch +tokens in Actions secrets, bind route variables to exact repositories, and do +not reuse the controller or recovery-writer token. + +## Validate the setup + +Before starting a beacon: + +- Confirm the App installation lists only the intended control and decoy repos. +- Compare granted permissions with the enabled channel set. +- Confirm the control and decoy repositories are private. +- Confirm the recovery repository contains no plaintext secrets or personal + data. +- Verify `MONITORING_PUBKEY` matches the operator secret you will load. +- Verify controller and beacon Gist tokens differ. +- Verify the Codespaces PAT belongs to the account that can open the named + Codespace. +- Keep every private key and PAT out of Git, logs, screenshots, and compiled + binaries. + +Run `octoctl setup --phase validate` for the CLI's configured checks, then +continue with [configuration](CONFIGURATION.md) and the [quickstart](QUICKSTART.md). + +## Rotation + +Rotate one role at a time. Update the consuming component, restart it, and +verify a harmless task before revoking the replaced credential. GitHub App +private-key rotation can overlap keys; installation tokens are short-lived and +must be allowed to expire. Recovery signing-key rotation requires a signed key +transition and is covered in [Recovery](RECOVERY.md). + +Retain or revoke test PATs according to the operator's explicit cleanup plan. +Repository artifact cleanup does not imply credential deletion. diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md new file mode 100644 index 0000000..816cad8 --- /dev/null +++ b/docs/INSTALLATION.md @@ -0,0 +1,173 @@ +# Installation + +This guide installs the OctoC2 controller, dashboard, CLI, and beacon build +tooling from source. Complete [GitHub setup](GITHUB_SETUP.md) before attempting +an end-to-end run. + +> [!IMPORTANT] +> **Authorized use only.** Use OctoC2 only on systems and repositories you own or are explicitly +> authorized to test. Keep test repositories private and listener surfaces +> private unless an approved test requires otherwise. + +## Requirements + +| Requirement | Version or purpose | +|---|---| +| [Bun](https://bun.sh/docs/installation) | `1.3.14` (the pinned package manager and runtime) | +| Git | Clone, update, and Git Notes transport operations | +| Node.js | `22.14.x` only for tooling that explicitly invokes Node | +| GitHub CLI | Required only for Codespaces discovery and SSH tunneling | + +Use the exact Bun version. The lockfile, CI, native tests, compiled binaries, +and dependency-policy checks are qualified against that version. + +## Install the repository + +```bash +git clone https://github.com/dstours/OctoC2.git +cd OctoC2 +bun install --frozen-lockfile +bun run proto:gen +bun run deps:check +bun run docs:check +``` + +`--frozen-lockfile` prevents an install from silently changing the dependency +graph. `proto:gen` produces the checked-in protocol bindings used by gRPC. + +## Run components from source + +Open separate terminals from the repository root: + +```bash +# Controller +cd server +bun run src/index.ts +``` + +```bash +# Dashboard +cd dashboard +bun run dev +``` + +```bash +# CLI +cd octoctl +bun run src/index.ts --help +``` + +The dashboard development server listens on `http://127.0.0.1:5173`. The +controller's HTTP and gRPC listeners remain disabled unless explicitly +configured. + +For a shell-friendly CLI command during development, define an alias: + +```bash +alias octoctl='bun run --cwd=/absolute/path/to/OctoC2/octoctl src/index.ts' +octoctl --help +``` + +On PowerShell, use a function in the current session: + +```powershell +function octoctl { bun run --cwd C:\tools\OctoC2\octoctl src/index.ts @args } +octoctl --help +``` + +## Build standalone components + +```bash +bun run build:server +bun run build:dashboard +bun run build:octoctl +``` + +Default outputs are written to each workspace's `dist/` directory. The server +and CLI package scripts currently target Linux x64. Run the CLI from source on +other operator platforms unless you intentionally change and verify its Bun +compile target. + +### Build a beacon + +The implant workspace provides the supported compile targets: + +| Target | Command | Output | +|---|---|---| +| Linux x64 | `bun run build:linux-x64` | `dist/beacon-linux-x64` | +| Linux arm64 | `bun run build:linux-arm64` | `dist/beacon-linux-arm64` | +| Windows x64 | `bun run build:windows-x64` | `dist/beacon-windows-x64.exe` | +| macOS Apple silicon | `bun run build:darwin-arm64` | `dist/beacon-macos-arm64` | +| macOS Intel | `bun run build:darwin-x64` | `dist/beacon-macos-x64` | + +```bash +cd implant +bun run build:darwin-arm64 +``` + +The compiled beacon contains the Bun runtime; the target VM does not need Bun. +Transfer it through an approved path, make it executable on Unix-like systems, +and supply runtime secrets through the VM environment: + +```bash +chmod 700 ./beacon-macos-arm64 +./beacon-macos-arm64 +``` + +For a pre-enrolled build with a generated identity and explicit transport +settings, use `octoctl build-beacon`. See [CLI reference](CLI.md#build-beacon) +and never bake PATs, bearer tokens, private App keys, or recovery signing keys +into the binary. + +## Directory map + +| Path | Purpose | +|---|---| +| `implant/` | Beacon runtime and platform builds | +| `server/` | Controller, durable state, GitHub channel pollers, HTTP, and gRPC | +| `dashboard/` | Local operator web interface | +| `octoctl/` | Operator CLI and provisioning workflows | +| `shared/` | Canonical task, channel, envelope, and identity contracts | +| `proxy/` | Relay workflow implementation and tests | +| `templates/proxy/` | Files provisioned into control and decoy repositories | +| `docs-site/` | Public documentation landing site | + +## Verify the installation + +Run the repository-level policy checks first: + +```bash +bun run deps:check +bun run docs:check +bun run workflows:check +bun run toolchain:check +bun run proto:check +``` + +Then run workspace tests and type checks from each changed workspace: + +```bash +cd shared && bun test --timeout 30000 && bun run typecheck +cd ../implant && bun test --timeout 30000 && bun run typecheck +cd ../server && bun test --timeout 30000 && bun run typecheck +cd ../dashboard && bun test --timeout 30000 && bun run typecheck +cd ../octoctl && bun test --timeout 30000 && bun run typecheck +``` + +Continue with the [quickstart](QUICKSTART.md) to configure credentials, enroll a +beacon, and verify a harmless `ping` task. + +## Update an installation + +Stop running components, preserve the controller data directory, and then: + +```bash +git pull --ff-only +bun install --frozen-lockfile +bun run proto:check +bun run docs:check +``` + +Review release changes before rebuilding beacons. Do not replace an enrolled +beacon identity, controller database, or recovery generation as an incidental +part of a source update. diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index 18e4581..e140698 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -1,207 +1,234 @@ -# Local Evaluation Quickstart +# Quickstart + +This quickstart takes a fresh checkout to a pre-enrolled beacon and an accepted +`ping` result through a private GitHub Issues channel. It keeps direct network +listeners disabled for the first run. > [!IMPORTANT] -> **Authorized use only.** Use a private repository and scoped credentials, -> keep listeners on loopback or a reviewed private network, and test only -> systems and repositories you have explicit permission to access. +> **Authorized use only.** Use an isolated private repository and only deploy the beacon to a system you +> own or are explicitly authorized to test. -## 1. Install and verify the toolchain +## Before you start -Use Bun `1.3.14`. The repository pins Node.js `22.14.0` for tools that -explicitly require Node. +Complete these once: -```bash -bun install --frozen-lockfile -bun run proto:gen -``` +1. [Install](INSTALLATION.md) Bun `1.3.14` and the repository dependencies. +2. Create a private control repository and configure the least-privilege + credentials in [GitHub setup](GITHUB_SETUP.md). +3. Decide on a protected local directory for controller state, enrollment + artifacts, and secrets. + +For the Issues baseline you need: + +- a controller GitHub credential scoped to the control repo with Metadata read + and Issues read/write; +- an independently scoped beacon GitHub credential or a valid narrowed App + installation-token lease; +- an operator X25519 keypair; +- the same private owner/repo coordinates on controller and beacon. + +## Guided setup -Run the workspace checks before starting any component: +The wizard is the recommended first-run path: ```bash -bun run deps:check -bun run docs:check -bun run workflows:check -bun run toolchain:check -bun run proto:check -bun run lint -bun audit -cd shared && bun test --timeout 30000 && bun run typecheck -cd ../implant && bun test --timeout 30000 && bun run typecheck -cd ../server && bun test --timeout 30000 && bun run typecheck -cd ../dashboard && bun run test && bun run build -cd ../octoctl && bun test --timeout 30000 && bun run typecheck -cd ../proxy && bun run typecheck && bun run build -cd ../docs-site && bun run lint && bun run build -cd ../implant && bun run build:all -cd ../server && bun run build -cd ../octoctl && bun run build -cd ../proxy && bun run build -cd .. && bun run smoke:builds +cd OctoC2 +bun run octoctl/src/index.ts setup ``` -## 2. Create role-separated test credentials +It walks through credentials, validation, key generation, authentication, +channel selection, environment generation, pre-enrolled beacon compilation, +CLI installation, and verification. Review every proposed repository and +credential role before accepting it. -Do not reuse credentials across these roles: +You can revisit one phase later: -1. Controller-to-GitHub credential: `OCTOC2_SERVER_GITHUB_TOKEN` -2. Optional Gist-controller credential: `OCTOC2_SERVER_GIST_TOKEN` -3. Operator REST/SSE credential: `OCTOC2_OPERATOR_API_TOKEN` -4. Per-beacon HTTP/gRPC credentials: `OCTOC2_BEACON_API_TOKENS` -5. Optional direct-dashboard GitHub PAT - -The operator token must not appear in the beacon token map. None of the -controller API credentials may equal the GitHub credential. +```bash +bun run octoctl/src/index.ts setup --phase validate +``` -When Gist is enabled, use two distinct Gist-capable tokens belonging to the -same dedicated GitHub account: the controller token above and a beacon runtime -token supplied as `SVC_GIST_TOKEN`. The Gist controller token must not equal the repository, operator API, -or beacon API credentials. +The remainder of this guide shows the equivalent lifecycle so you can verify +the wizard's output or configure manually. -Generate the operator encryption keypair with the CLI: +## 1. Generate the operator key ```bash cd octoctl bun run src/index.ts keygen ``` -Keep the secret key out of the repository. +Store the secret as `OCTOC2_OPERATOR_SECRET` in the protected controller +environment. Set the public value as the control repository Actions variable +`MONITORING_PUBKEY`, or let the CLI do it with a direct operator token: + +```bash +bun run src/index.ts keygen --set-variable +``` + +Do not regenerate this key between build and first run. -## 3. Start the controller without listeners +## 2. Build and enroll a beacon -Set the required GitHub repository and operator-key variables, then run: +From the repository root, with `OCTOC2_REPO_OWNER` and +`OCTOC2_REPO_NAME` set: ```bash -cd server -bun run src/index.ts +bun run octoctl/src/index.ts build-beacon \ + --outfile ./out/beacon \ + --target bun-darwin-arm64 \ + --tentacle-priority issues ``` -HTTP and gRPC remain disabled unless their `*_ENABLED` variables are explicitly -set. +Choose the target that matches the authorized endpoint: -For a loopback-only HTTP evaluation, add: +- `bun-linux-x64` +- `bun-linux-arm64` +- `bun-windows-x64` +- `bun-darwin-arm64` +- `bun-darwin-x64` -```text -OCTOC2_HTTP_ENABLED=true -OCTOC2_HTTP_HOST=127.0.0.1 -OCTOC2_HTTP_PORT=8080 -OCTOC2_HTTP_SERVER_CERT=/absolute/path/to/http-server.crt -OCTOC2_HTTP_SERVER_KEY=/absolute/path/to/http-server.key -# For an internal CA, octoctl also reads: -OCTOC2_HTTP_CA_CERT=/absolute/path/to/http-ca.crt -OCTOC2_OPERATOR_API_TOKEN= -OCTOC2_BEACON_API_TOKENS={"":""} -OCTOC2_SERVER_URL=https://localhost:8080 +Full mode writes both the binary and a public enrollment artifact named +`.enrollment.json`. The artifact contains public enrollment material, +not the beacon's runtime PAT, but still belongs in the protected operator +workflow. + +Set `OCTOC2_ENROLLMENT_DIR` on the controller to the directory containing that +artifact. The controller must import it before the beacon connects. + +## 3. Configure the controller + +Create a protected dotenv file outside Git tracking: + +```dotenv +OCTOC2_SERVER_GITHUB_TOKEN= +OCTOC2_REPO_OWNER= +OCTOC2_REPO_NAME= +OCTOC2_OPERATOR_SECRET= +OCTOC2_DATA_DIR= +OCTOC2_ENROLLMENT_DIR= ``` -The HTTP certificate SAN must contain the hostname in `OCTOC2_SERVER_URL`. -Install the issuing CA in the browser and beacon host trust stores. Do not -disable certificate verification; use a publicly trusted certificate or a -locally trusted test CA. +The repository credential, operator secret, and beacon credential are +different roles. Do not place the controller token in the beacon environment. -For gRPC, also set `OCTOC2_GRPC_ENABLED=true`, keep -`OCTOC2_GRPC_HOST=127.0.0.1`, provide per-beacon credentials, and configure the -required CA/server certificate and key files. Bind each beacon ID to its -client certificate fingerprint: +Start the controller: -```text -OCTOC2_GRPC_CLIENT_CERT_FINGERPRINTS={"":""} +```bash +bun run octoctl/src/index.ts start server --env /secure/path/octoc2.env ``` -The server uses `OCTOC2_GRPC_CA_CERT`, `OCTOC2_GRPC_SERVER_CERT`, and -`OCTOC2_GRPC_SERVER_KEY`. Each beacon uses `SVC_GRPC_CA_CERT`, -`SVC_GRPC_CLIENT_CERT`, `SVC_GRPC_CLIENT_KEY`, and the matching -`SVC_BEACON_API_TOKEN`. +Or run it in the foreground after loading the same environment: -Obtain the fingerprint with -`openssl x509 -in beacon-client.crt -noout -fingerprint -sha256`. The -fingerprint map must have exactly the same keys as -`OCTOC2_BEACON_API_TOKENS`, and every fingerprint must be unique. The -controller refuses to start on a missing, extra, malformed, or reused binding. -Do not expose plaintext or unauthenticated gRPC. +```bash +cd server +bun run src/index.ts +``` + +Confirm the startup log reports one imported enrollment artifact, the expected +owner/repo, and disabled HTTP/gRPC listeners. The Issues, Branch, Actions, +Secrets, Pages, Stego, and Notes controller pollers start against the configured +repo; the beacon priority determines which channel it uses. -Direct gRPC requires no GitHub credential. Codespaces API/SSH mode also -requires a protected runtime `SVC_CODESPACES_GITHUB_TOKEN` with the necessary -user-level Codespaces access: use a classic PAT with only the `codespace` scope, -because fine-grained PATs do not supply GitHub CLI tunnel metadata. Do not use an App installation lease for that -user-level API, and do not bake this token into the beacon. Install GitHub CLI -(`gh`) on the beacon host; Codespaces forwarding uses its authenticated -connection service and never treats the PAT as an SSH password. +## 4. Start the beacon -## 4. Start the local dashboard +Transfer the compiled binary to the authorized endpoint through an approved +path. Supply only its runtime role configuration: ```bash -cd dashboard -bun run dev +export SVC_GITHUB_TOKEN='' +export OCTOC2_REPO_OWNER='' +export OCTOC2_REPO_NAME='' +export SVC_TENTACLE_PRIORITY='issues' +chmod 700 ./beacon +./beacon ``` -Open `http://127.0.0.1:5173`. +Use a protected service environment on Windows rather than shell `export`. +Never pass a PAT as a command-line argument. The first successful check-in must +match the pre-enrolled ID and signing public key. -- Live mode uses only the operator API token for controller HTTPS/SSE calls. -- Direct GitHub mode uses only the GitHub PAT. -- The optional operator private key is used for result decryption. -- All three values stay in memory and are cleared on logout or tab close. +## 5. Verify the first task/result -## 5. Explore the operator CLI +List the beacon from controller state: ```bash -cd octoctl -bun run src/index.ts --help +bun run octoctl/src/index.ts beacons +``` + +Queue a harmless task: + +```bash +bun run octoctl/src/index.ts task --kind ping --tentacle issues ``` -The CLI displays an authorization reminder at startup. Unsigned remote modules -are outside the supported surface: the `module` command is absent and -`task --kind load-module` is rejected. +Then retrieve the accepted result: -## 6. Interpret capability status correctly +```bash +bun run octoctl/src/index.ts results --last 1 --json +``` -The repository includes multiple channel implementations. The dashboard’s -channel grid reports recent observations from beacons; it is not a readiness -matrix and does not prove every implementation works end to end. +The run is complete when all of these are observed for the same task ID: -Before describing any capability as verified, record: +1. controller queues a validated task for the enrolled beacon; +2. encrypted task comment is published; +3. beacon receives and executes `ping` once; +4. beacon publishes a signed and encrypted result; +5. controller verifies and accepts the result; +6. beacon receives or observes result acceptance. -- the exact code revision; -- the isolated environment and credential roles; -- the test command and result; -- the transport actually observed; -- cleanup of test artifacts and credentials. +Successful registration by itself is not sufficient. -## 7. Treat the E2E workflow as a prerequisite gate +## 6. Add the dashboard or direct listeners -The protected `octoc2-live-e2e` workflow is manually dispatched. It builds a -fresh, pre-enrolled beacon artifact and validates the declared repository, -credential, recovery, proxy, and optional mTLS configuration. It does not start -the controller or beacon, execute a task, or establish live E2E success. +The dashboard requires the controller's opt-in HTTPS API. Configure trusted TLS, +the operator API token, and the exact beacon bearer map before enabling it. Use +the complete [Configuration](CONFIGURATION.md#https-and-operator-api) and +[Dashboard](../dashboard/README.md) guides, then: -The required topology is: +```dotenv +OCTOC2_HTTP_ENABLED=true +OCTOC2_HTTP_HOST=127.0.0.1 +OCTOC2_HTTP_SERVER_CERT= +OCTOC2_HTTP_SERVER_KEY= +OCTOC2_HTTP_CA_CERT= +OCTOC2_OPERATOR_API_TOKEN= +OCTOC2_BEACON_API_TOKENS={"":""} +``` + +```bash +cd dashboard +bun run dev +``` -- one isolated private C2 repository, which is also the proxy control - repository for this scenario; -- one separate private proxy decoy repository; -- one separate public recovery repository; -- one full pre-enrolled beacon identity whose public artifact is directly - importable from `OCTOC2_ENROLLMENT_DIR`; -- distinct controller GitHub, operator GitHub, operator API, per-beacon API, - recovery-writer, and proxy-dispatch credentials; -- server-only GitHub App and recovery-signing private keys. +Open `http://127.0.0.1:5173`. Keep both Vite and the controller on loopback for +local operation. -No GitHub App private key, shared GitHub credential, or static proxy credential -may be placed in the beacon environment or binary. +For gRPC/mTLS, Codespaces, OIDC, proxy, Gist, and other GitHub artifact paths, +follow the exact prerequisites in [Channels](CHANNELS.md). Qualify one channel +at a time with `ping` before adding it to a failover list. -To inspect the local fail-closed behavior without querying GitHub: +## 7. Stop and clean the test ```bash -bun run scripts/test-end-to-end.ts --dry-run +bun run octoctl/src/index.ts stop ``` -This command still requires the complete declarations and local files. Missing -prerequisites are reported explicitly and produce a nonzero exit code. Add -`--check-github` only in the approved protected environment. Add `--grpc` or -`--http` whenever those transports appear in the recovery priority so their -security prerequisites are validated. - -Any later live execution is a separate, explicitly authorized manual exercise. -Record the exact revision, observed transport, task/result evidence, artifact -cleanup, and credential revocation before using the term "Live E2E verified." -See [Deployment and assurance limits](PRODUCTION.md) for the full environment -contract. +Stop the beacon separately. Inventory and remove only artifacts created by the +test: issue comments/issues, transport branches/files, variables, deployments, +Gists, notes refs, proxy workflow artifacts, and temporary binaries. Preserve +controller/beacon state if the enrollment will be used again. + +Do not delete or revoke PATs unless the approved cleanup plan explicitly calls +for credential revocation. Artifact cleanup and credential lifecycle are +separate decisions. + +## Next steps + +- [Architecture](ARCHITECTURE.md) explains identity and result acceptance. +- [Configuration](CONFIGURATION.md) lists listener, lifecycle, beacon, OIDC, + dashboard, and recovery variables. +- [Channels](CHANNELS.md) covers every transport and permission. +- [CLI](CLI.md) documents all operator commands. +- [Troubleshooting](TROUBLESHOOTING.md) maps common log messages and API errors + to safe checks. +- [Operations and assurance](PRODUCTION.md) is the deployment checklist. diff --git a/docs/README.md b/docs/README.md index 42331d9..d386817 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,23 +1,75 @@ # OctoC2 Documentation +OctoC2 is a GitHub-native command-and-control framework for authorized +security research, with encrypted multi-channel transport and resilient +failover. This manual covers installation through operation, recovery, +verification, and development. + > [!IMPORTANT] > **Authorized use only.** Use OctoC2 only on systems and repositories you own -> or have explicit permission to test. Keep credentials scoped and controller -> surfaces private. +> or have explicit permission to test. Keep credentials least-privileged, +> listeners private, and live-test artifacts inventoried and cleaned. + +## Start here -This directory contains the operator and engineering guides for OctoC2. Start -with the guide that matches the job in front of you. +| If you are… | Read these in order | +|---|---| +| Evaluating locally | [Installation](INSTALLATION.md) → [GitHub setup](GITHUB_SETUP.md) → [Quickstart](QUICKSTART.md) | +| Deploying an authorized environment | [Architecture](ARCHITECTURE.md) → [Configuration](CONFIGURATION.md) → [Channels](CHANNELS.md) → [Operations](PRODUCTION.md) | +| Operating day to day | [CLI](CLI.md) → [Dashboard](../dashboard/README.md) → [Troubleshooting](TROUBLESHOOTING.md) | +| Configuring resilience | [Proxy contract](../templates/proxy/README.md) → [Recovery](RECOVERY.md) | +| Reviewing or contributing | [Development](DEVELOPMENT.md) → [Verification traceability](REMEDIATION_TRACEABILITY.md) | -## Choose a guide +The [quickstart](QUICKSTART.md) is the shortest path to a pre-enrolled beacon +and a verified `ping`. The other guides explain the decisions behind each step. -| Goal | Guide | What it covers | -|---|---|---| -| Launch the local stack | [Quickstart](QUICKSTART.md) | Toolchain, credentials, TLS, controller, dashboard, CLI, and verification | -| Configure an environment | [Operations and assurance](PRODUCTION.md) | Listener policy, replay safety, state lifecycle, certificates, and operational checks | -| Provision credential recovery | [Recovery](RECOVERY.md) | GitHub App policies, signed recovery records, token leases, renewal, and key rotation | -| Review implementation evidence | [Remediation traceability](REMEDIATION_TRACEABILITY.md) | Finding-to-code mapping, automated coverage, live qualifications, and cleanup evidence | -| Operate the web interface | [Dashboard guide](../dashboard/README.md) | Local development, TLS trust, login roles, and activity semantics | -| Configure a proxy route | [Proxy workflow contract](../templates/proxy/README.md) | Control/decoy topology, workflows, variables, secrets, signatures, and deduplication | +## Complete guide map + +### Learn and install + +| Guide | Covers | +|---|---| +| [Architecture](ARCHITECTURE.md) | Components, identities, task lifecycle, durable state, transport abstraction, recovery, and trust boundaries | +| [Installation](INSTALLATION.md) | Pinned toolchain, source install, component builds, platform beacon binaries, updates, and local verification | +| [GitHub setup](GITHUB_SETUP.md) | Control/decoy/recovery topology, GitHub App UI fields, App permissions, PAT roles, installation, and rotation | +| [Quickstart](QUICKSTART.md) | Guided and manual first run, enrollment import, controller/dashboard/beacon launch, first task, and cleanup | + +### Configure and operate + +| Guide | Covers | +|---|---| +| [Configuration](CONFIGURATION.md) | Controller, listener, lifecycle, OIDC, recovery, beacon, dashboard, and CLI variables | +| [Channel guide](CHANNELS.md) | Full channel catalog, permission/prerequisite matrix, per-channel setup, priority, failover, and qualification | +| [CLI reference](CLI.md) | Setup, key generation, inventory, tasks, results, builds, recovery, proxy, services, and JSON output | +| [Dashboard guide](../dashboard/README.md) | Local UI, TLS trust, login roles, live/direct modes, and activity semantics | +| [Operations and assurance](PRODUCTION.md) | Listener exposure, credentials, identity, replay, result acceptance, state, certificates, evidence, and stop conditions | +| [Troubleshooting](TROUBLESHOOTING.md) | Startup, GitHub errors, decryption, acknowledgements, proxy, TLS, gRPC, Codespaces, OIDC, state, and CI | + +### Resilience and verification + +| Guide | Covers | +|---|---| +| [Recovery](RECOVERY.md) | Deterministic dead-drops, exact App policies, short-lived leases, publication, renewal, and key rotation | +| [Proxy workflow contract](../templates/proxy/README.md) | Control/decoy workflows, variables, secrets, signed routes, deduplication, and artifact cleanup | +| [Remediation traceability](REMEDIATION_TRACEABILITY.md) | Finding-to-code mapping, automated evidence, live qualifications, and cleanup records | +| [Development](DEVELOPMENT.md) | Workspace conventions, contract changes, tests, builds, documentation standards, and change checklist | + +## Feature overview + +- One signed and encrypted task protocol across GitHub artifacts, HTTPS/WSS, + gRPC/mTLS, OIDC, Codespaces, proxy, and relay paths. +- Thirteen selectable channel kinds, with prerequisites and evidence status + reported separately; one reserved catalog entry remains unavailable. +- Pre-enrolled X25519 encryption and Ed25519 signing identities. +- Durable SQLite controller state, delivery leases, replay records, cursors, + results, and lifecycle state. +- Persistent beacon task ledger for at-most-once execution across restarts. +- Authenticated operator CLI and local dashboard with separated credential roles. +- Signed, generation-numbered recovery records carrying narrowed GitHub App + installation-token leases. +- Strict task catalog with routine, elevated, and destructive risk classes. +- Five platform beacon build targets: Linux x64/arm64, Windows x64, and macOS + Apple silicon/Intel. ## System map @@ -25,78 +77,51 @@ with the guide that matches the job in front of you. dashboard / octoctl │ operator API token ▼ - server ─────────────── SQLite state - │ │ - │ signed task envelopes │ identities, leases, - │ │ results, replay state - ▼ │ -GitHub APIs / HTTPS / gRPC ◄──────┘ +controller ───────────────── durable SQLite state + │ identities · tasks · delivery leases + │ signed + sealed results · replay records · cursors + ▼ +GitHub APIs · HTTPS · gRPC · signed relays │ ▼ - implant +pre-enrolled beacon ──────── persistent identity + task ledger ``` -The `shared/` workspace defines the canonical task catalog, channel catalog, -signed envelopes, key identifiers, and result-signature payloads used by every -component. - -## Common workflows - -### First local run - -1. Install the pinned Bun dependency graph. -2. Generate and verify protocol bindings. -3. Create an operator X25519 keypair. -4. Prepare a private control repository and role-separated credentials. -5. Configure trusted TLS before enabling HTTP or gRPC. -6. Start the controller, dashboard, and CLI. -7. Verify one harmless ping task and its accepted signed result. - -Follow the [quickstart](QUICKSTART.md) for commands and configuration. - -### Add a transport - -1. Check the channel catalog prerequisites in `shared/src/channels.ts`. -2. Grant only the declared repository permissions. -3. Keep App private keys on the controller; issue a narrowed installation-token - lease when the channel supports it. -4. Add the channel to the beacon priority list. -5. Run its implant and controller test suites. -6. Record live task/result evidence and artifact cleanup for external tests. - -### Prepare deterministic recovery - -1. Create a dedicated public recovery repository. -2. Provision the recovery Ed25519 signing identity. -3. Configure exact per-beacon GitHub App policies. -4. Configure the complete replacement policy for each beacon. -5. Publish signed, sealed records and verify proactive renewal before lease - expiry. - -The [recovery guide](RECOVERY.md) defines the exact record and policy format. +The `shared/` workspace is authoritative for channel IDs, task kinds, envelope +shapes, signatures, and validation rules. ## Configuration families | Prefix | Component | Purpose | |---|---|---| -| `OCTOC2_HTTP_*` | Controller and operator clients | HTTPS/WSS listener, certificates, URL, and CA trust | -| `OCTOC2_GRPC_*` | Controller | gRPC listener, mTLS material, and certificate fingerprints | -| `OCTOC2_BEACON_*` | Controller | Per-beacon API credentials and lifecycle thresholds | -| `OCTOC2_GITHUB_APP_*` | Controller | App identity, private key, and narrowed token policies | -| `OCTOC2_RECOVERY_*` | Controller and build tooling | Recovery repository, signing identity, publication, and policies | -| `SVC_*` | Beacon runtime | Enrolled identity, transport credentials, certificates, timing, and recovery trust | +| `OCTOC2_HTTP_*` | Controller/operator | HTTPS/WSS listener and CA trust | +| `OCTOC2_GRPC_*` | Controller | gRPC listener, mTLS, and exact certificate fingerprints | +| `OCTOC2_BEACON_*` | Controller/build | Beacon credentials, lifecycle, and pre-enrollment identity | +| `OCTOC2_GITHUB_APP_*` | Controller | App identity, private-key file, and exact token policies | +| `OCTOC2_RECOVERY_*` | Controller/build/beacon | Recovery repository, signing trust, publication, and policy | +| `SVC_*` | Beacon | Runtime transport credentials, endpoints, timing, and cleanup | +| `VITE_*` | Dashboard/docs site | Build-time controller/repository coordinates | -Configuration is fail-closed: malformed security state, missing required TLS -material, ambiguous identities, unsafe timing, and inconsistent credential -maps stop startup instead of selecting a weaker fallback. +Configuration is fail-closed at security boundaries: incomplete TLS material, +ambiguous credential roles, mismatched identities, malformed policy, and +unsafe timing stop startup or make the affected transport ineligible. -## Verification vocabulary +## Evidence vocabulary | Term | Meaning | |---|---| -| Unit tested | A local automated test exercises one component or contract | +| Unit tested | A deterministic local test covers one component or contract | | Integration tested | Multiple local components complete a recorded interaction | | Live qualified | An authorized external run observes the stated transport boundary and records cleanup | -| Live task/result | Registration, encrypted task delivery, implant receipt, signed result publication, and controller acceptance complete through the channel | +| Live task/result | Registration, encrypted task delivery, beacon receipt, signed result publication, and controller acceptance complete through the named channel | + +Use the most specific label supported by evidence for the exact revision. +Catalog presence, successful registration, and artifact publication alone do +not establish live task/result qualification. + +## Getting help -Use the most specific term supported by the evidence for the exact revision. +Check [Troubleshooting](TROUBLESHOOTING.md), then capture the exact revision, +component versions, redacted logs, channel, and failing command. Never attach +dotenv files, PATs, private keys, bearer values, private-repository content, +hostnames, or usernames to a public report. diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md new file mode 100644 index 0000000..fc2668e --- /dev/null +++ b/docs/TROUBLESHOOTING.md @@ -0,0 +1,186 @@ +# Troubleshooting + +> [!IMPORTANT] +> **Authorized use only.** Diagnose only systems and repositories included in +> the approved test boundary, and redact sensitive data from every artifact. + +Start with a harmless `ping`, one channel, and `OCTOC2_LOG_LEVEL=debug`. Record +timestamps from controller and beacon logs, but redact credentials, private +keys, authorization headers, repository URLs that identify a private customer, +hostnames, and usernames before sharing diagnostics. + +## First checks + +```bash +octoctl status +octoctl beacons --json +octoctl tentacles list --beacon --verbose +octoctl results --last 3 --json +``` + +Confirm system clocks are synchronized, the exact beacon ID matches the +enrollment, and the configured channel appears in the beacon's startup log. + +## Startup failures + +| Symptom | Likely cause | Check or fix | +|---|---|---| +| Missing required configuration | Repository, operator key, credential, or recovery bootstrap is incomplete | Run `octoctl setup --phase validate`; compare with [Configuration](CONFIGURATION.md) | +| Operator secret has invalid length | Wrong encoding or wrong key role | Regenerate with `octoctl keygen`; use the base64url X25519 secret | +| No pre-provisioned identity | Beacon was compiled in simple mode or state was removed | Build/enroll with full `octoctl build-beacon`; do not invent key fields | +| Provisioned identity does not match state | A binary/state directory from different enrollments was combined | Restore the matching pair; never overwrite identity checks | +| Both static token and lease configured | Ambiguous GitHub authority | Set exactly one of `SVC_GITHUB_TOKEN` and `SVC_GITHUB_TOKEN_LEASE` | +| App private key rejected on beacon | Server-only secret was placed on endpoint | Remove it from the endpoint; mint narrowed leases on the controller | +| Listener fails immediately | Invalid port, missing TLS files, unreadable key, or occupied port | Validate paths/permissions and inspect the named bind address | + +## GitHub API errors + +### `404 Not Found` + +For private repositories GitHub often returns `404` when the credential cannot +see the repository. Check the credential's resource owner, selected repository, +App installation, and expiration before assuming the artifact is missing. Also +confirm the issue/comment/branch/ref still exists and that owner/repo case is +correct. + +### `401 Unauthorized` + +The credential is absent, malformed, revoked, or expired. A short-lived App +lease may have expired; use the proactive recovery publisher or provision a new +lease. Do not substitute a different credential role merely to make the call +succeed. + +### `403 Forbidden` + +The identity is recognized but lacks the required permission, is blocked by an +organization policy, or hit a rate/abuse limit. Compare the channel with the +[permission matrix](GITHUB_SETUP.md#repository-permissions), inspect GitHub's +response headers, and wait only when the failure is actually rate-related. + +## Issues and proxy symptoms + +### Registration succeeds, but comments return 404 + +Registration only proves one API path worked. Verify the token can read and +write comments on the exact configured issue, and that the controller watches +the same owner/repo/issue scope. For proxy, inspect both the decoy issue and the +control-side relay artifact. + +### `failed to decrypt task comment` + +The comment may be a registration acknowledgement/result rather than a task, +may belong to another beacon, may be malformed, or may have been sealed with a +different operator/beacon key. Confirm `MONITORING_PUBKEY`, enrollment keys, +beacon ID, route issue number, and recovery generation. Do not weaken parsing +to accept an ambiguous payload. + +### `payload is neither a task array nor registration ack` + +An unrelated/stale comment was read or the two route endpoints disagree about +the envelope version. Confirm artifact scoping and deploy the same revision to +beacon, controller, and proxy workflows. Remove stale test artifacts only after +capturing the evidence needed to diagnose them. + +### Result acknowledgement timeout + +An artifact write is not the same as controller acceptance. Look for the result +in controller logs and inspect signature, ownership, task state, replay, and +result-digest rejection messages. Confirm the acknowledgement is returned on +the same issue/route. Increase `SVC_RESULT_ACK_TIMEOUT_MS` only when acceptance +eventually succeeds and measured queue latency exceeds the current window. + +### Proxy fails while Issues works + +Check that control and decoy are distinct, both workflows are present and +enabled, repository-dispatch secrets target the correct opposite repository, +the App installation includes the decoy, route signatures/fingerprints match, +and the signed recovery policy contains the current route. Inspect Actions logs +on both repos without printing secret values. + +## Direct transport symptoms + +### Certificate verification or hostname failure + +Use the hostname present in the server certificate SAN. Verify the configured +CA file, certificate chain, validity dates, and target clock. There is no +supported insecure TLS switch. + +### HTTP returns 401/403 + +The dashboard/CLI uses `OCTOC2_OPERATOR_API_TOKEN`; a beacon uses the token +mapped to its exact ID in `OCTOC2_BEACON_API_TOKENS`. Do not interchange them. +Credentials belong in authorization headers, never query strings. + +### gRPC cannot connect + +Confirm the listener is enabled, port is reachable, the client trusts the +server CA, the server trusts the client CA, the beacon certificate fingerprint +matches the exact ID, and the per-beacon bearer is present. A Dev Tunnels HTTPS +URL is not a raw gRPC endpoint. + +### Codespaces discovery or SSH fails + +Run `gh auth status` under the same OS user, verify the dedicated classic PAT +has `codespace`, confirm the named Codespace belongs to/permits that account, +and test approved SSH access. Deep-sleep/stopped Codespaces may need to start +before discovery completes. Auto-provisioning occurs only when explicitly +enabled. + +## OIDC rejection + +Compare the token claims to `OCTOC2_OIDC_BINDINGS`: repository, subject, +workflow ref, audience, and exact beacon ID. Confirm the workflow has +`permissions: id-token: write`. Ref or reusable-workflow changes can alter +claims. Do not add wildcards to bypass a mismatch. + +## State and recovery + +| Symptom | Check | +|---|---| +| Beacon appears under a new ID | Wrong/empty state directory or non-enrolled build | +| Task repeats after restart | Preserve the beacon ledger and controller database; inspect replay-retention changes | +| Recovery record ignored | Signature/key ID, recipient ID, generation monotonicity, expiry, repo/ref, and complete policy | +| Recovered token works on wrong repo | Treat as a policy failure; stop testing and inspect App installation/policy narrowing | +| Controller loses history | Restore the whole configured data directory, not selected JSON/SQLite files | + +## Dashboard + +- Blank API data: verify `VITE_C2_SERVER_URL` is a bare HTTPS origin and the + controller HTTP listener is enabled. +- Browser certificate warning: trust the issuing internal CA in the browser/OS; + do not disable verification in Vite. +- Login succeeds in GitHub mode but live actions fail: GitHub PAT and operator + API token are separate roles. +- CORS rejection: set the reviewed `OCTOC2_DASHBOARD_ORIGIN` to the exact UI + origin. + +## Development and CI + +Run the narrowest failing workspace first: + +```bash +bun test --timeout 30000 +bun run typecheck +``` + +Then run repository policy checks from the root. If dashboard tests leak mocks +or DOM state, use its isolated `src/testRunner.ts` rather than replacing it with +a generic root test invocation. If `proto:check` fails, regenerate bindings and +review the diff instead of editing generated files manually. + +## Safe live-test sequence + +1. Record the exact commit and channel. +2. Confirm private repository and credential scope. +3. Start controller, then beacon, and wait for an authenticated registration. +4. Submit one `ping` forced through the named channel. +5. Record publication, receipt, signed result, and controller acceptance. +6. Remove test issues/comments, refs, variables, deployments, Gists, images, + notes, and workflow artifacts created by the run. +7. Confirm normal non-test content remains. +8. Retain or revoke PATs only according to the explicit credential cleanup + decision; artifact cleanup does not authorize token deletion. + +Stop immediately if a route reaches an unapproved repository, a credential has +wider access than intended, plaintext secret/PII appears in an artifact or log, +or identity/ownership verification is bypassed. diff --git a/scripts/check-doc-consistency.ts b/scripts/check-doc-consistency.ts index b537f68..6fd04e0 100644 --- a/scripts/check-doc-consistency.ts +++ b/scripts/check-doc-consistency.ts @@ -5,6 +5,14 @@ const root = join(import.meta.dir, ".."); const publicDocs = [ "README.md", "docs/QUICKSTART.md", + "docs/INSTALLATION.md", + "docs/GITHUB_SETUP.md", + "docs/ARCHITECTURE.md", + "docs/CHANNELS.md", + "docs/CLI.md", + "docs/CONFIGURATION.md", + "docs/TROUBLESHOOTING.md", + "docs/DEVELOPMENT.md", "docs/PRODUCTION.md", "docs/RECOVERY.md", "docs/REMEDIATION_TRACEABILITY.md", From 03249c34aa015da683b89e87e43afc5a4c8ea6fa Mon Sep 17 00:00:00 2001 From: dstours Date: Fri, 17 Jul 2026 18:46:55 -0400 Subject: [PATCH 2/3] Enlarge README logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ecf6df..b597eff 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ -->

- OctoC2 + OctoC2

From 82496e3eb486e4b7499721426fb27dd9247d367a Mon Sep 17 00:00:00 2001 From: dstours Date: Fri, 17 Jul 2026 18:59:42 -0400 Subject: [PATCH 3/3] Render documentation articles in site --- bun.lock | 203 ++++++++++++++++++++++++++++ docs-site/package.json | 5 +- docs-site/src/App.tsx | 94 ++----------- docs-site/src/DocumentationPage.tsx | 197 +++++++++++++++++++++++++++ docs-site/src/docs.ts | 159 ++++++++++++++++++++++ docs-site/src/index.css | 61 ++++++++- scripts/check-doc-consistency.ts | 52 ++++++- 7 files changed, 686 insertions(+), 85 deletions(-) create mode 100644 docs-site/src/DocumentationPage.tsx create mode 100644 docs-site/src/docs.ts diff --git a/bun.lock b/bun.lock index c9f229a..d191cce 100644 --- a/bun.lock +++ b/bun.lock @@ -66,6 +66,9 @@ "dependencies": { "react": "19.2.4", "react-dom": "19.2.4", + "react-markdown": "10.1.0", + "rehype-slug": "6.0.0", + "remark-gfm": "4.0.1", }, "devDependencies": { "@eslint/js": "9.39.4", @@ -443,12 +446,22 @@ "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], + "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], + + "@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/libsodium-wrappers": ["@types/libsodium-wrappers@0.8.2", "", { "dependencies": { "libsodium-wrappers": "*" } }, "sha512-+2IDfSULPUskSjIYfZl9suIUsIE5PXwoKZiE/j0MZWd+M9nEGvJDsk/ztMZKNhL1lBL+1CaypW0dQSjqPW2dMg=="], + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + "@types/node": ["@types/node@22.19.15", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg=="], "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], @@ -457,6 +470,8 @@ "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.64.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.64.0", "@typescript-eslint/type-utils": "8.64.0", "@typescript-eslint/utils": "8.64.0", "@typescript-eslint/visitor-keys": "8.64.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.64.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.64.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.64.0", "@typescript-eslint/types": "8.64.0", "@typescript-eslint/typescript-estree": "8.64.0", "@typescript-eslint/visitor-keys": "8.64.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw=="], @@ -477,6 +492,8 @@ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.64.0", "", { "dependencies": { "@typescript-eslint/types": "8.64.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw=="], + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.3", "", {}, "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.3", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.1" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg=="], "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], @@ -505,6 +522,8 @@ "autoprefixer": ["autoprefixer@10.5.4", "", { "dependencies": { "browserslist": "^4.28.6", "caniuse-lite": "^1.0.30001806", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA=="], + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "baseline-browser-mapping": ["baseline-browser-mapping@2.10.43", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ=="], @@ -529,8 +548,18 @@ "caniuse-lite": ["caniuse-lite@1.0.30001806", "", {}, "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw=="], + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], @@ -553,6 +582,8 @@ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], @@ -577,12 +608,16 @@ "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], + "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="], "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], @@ -625,8 +660,12 @@ "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + "fast-content-type-parse": ["fast-content-type-parse@3.0.0", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -663,6 +702,8 @@ "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], + "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "globals": ["globals@17.4.0", "", {}, "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw=="], @@ -673,12 +714,22 @@ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "hast-util-heading-rank": ["hast-util-heading-rank@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA=="], + + "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], + + "hast-util-to-string": ["hast-util-to-string@3.0.1", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], "html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="], + "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], + "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], @@ -689,22 +740,34 @@ "ink": ["ink@5.2.1", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.1.3", "ansi-escapes": "^7.0.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^4.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.22.0", "indent-string": "^5.0.0", "is-in-ci": "^1.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.29.0", "scheduler": "^0.23.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^7.2.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=18.0.0", "react": ">=18.0.0", "react-devtools-core": "^4.19.1" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg=="], + "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], + + "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], + + "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], + "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + "is-in-ci": ["is-in-ci@1.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg=="], "is-interactive": ["is-interactive@2.0.0", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="], "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], @@ -779,6 +842,8 @@ "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], "lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], @@ -789,10 +854,98 @@ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + + "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="], + + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], + + "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], + + "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], + + "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], + + "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], + + "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + + "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], + + "mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="], + + "mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], + + "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], + + "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], + + "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], + + "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], + + "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], + + "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], @@ -831,6 +984,8 @@ "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], + "parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "^8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="], "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="], @@ -869,6 +1024,8 @@ "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], + "property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], + "protobufjs": ["protobufjs@8.7.1", "", { "dependencies": { "long": "^5.3.2" } }, "sha512-agdGHrXNTv0IrYscJPDou/PlEJk1c/hBZ9o/B5NH2i/nSPtPqacNxzgwf1CebXxFMjMrZH5sqv9uQuw96aGt/A=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], @@ -881,6 +1038,8 @@ "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + "react-markdown": ["react-markdown@10.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "html-url-attributes": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="], + "react-reconciler": ["react-reconciler@0.29.2", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg=="], "react-router": ["react-router@6.30.4", "", { "dependencies": { "@remix-run/router": "1.23.3" }, "peerDependencies": { "react": ">=16.8" } }, "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA=="], @@ -893,6 +1052,16 @@ "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], + "rehype-slug": ["rehype-slug@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "github-slugger": "^2.0.0", "hast-util-heading-rank": "^3.0.0", "hast-util-to-string": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A=="], + + "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], + + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], + + "remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], + + "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], @@ -927,18 +1096,26 @@ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], "stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="], "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], + + "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], + "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -969,6 +1146,10 @@ "tr46": ["tr46@6.0.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="], + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], @@ -987,6 +1168,18 @@ "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], + "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + "universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="], "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], @@ -995,6 +1188,10 @@ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + "vite": ["vite@8.1.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", "postcss": "^8.5.17", "rolldown": "~1.1.5", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw=="], "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="], @@ -1035,6 +1232,8 @@ "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + "@babel/core/@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -1117,10 +1316,14 @@ "log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], "ora/cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], "readdirp/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], diff --git a/docs-site/package.json b/docs-site/package.json index df3cc51..0125607 100644 --- a/docs-site/package.json +++ b/docs-site/package.json @@ -14,8 +14,11 @@ "preview": "vite preview" }, "dependencies": { + "react-markdown": "10.1.0", "react": "19.2.4", - "react-dom": "19.2.4" + "react-dom": "19.2.4", + "rehype-slug": "6.0.0", + "remark-gfm": "4.0.1" }, "devDependencies": { "@eslint/js": "9.39.4", diff --git a/docs-site/src/App.tsx b/docs-site/src/App.tsx index 5465184..4cc90d2 100644 --- a/docs-site/src/App.tsx +++ b/docs-site/src/App.tsx @@ -1,9 +1,10 @@ import { useState } from 'react' +import { DocumentationPage } from './DocumentationPage' +import { DOCUMENTATION, DOCUMENTATION_BY_ID, documentationUrl } from './docs' const owner = import.meta.env.VITE_GITHUB_OWNER || 'dstours' const repo = import.meta.env.VITE_GITHUB_REPO || 'OctoC2' const repositoryUrl = `https://github.com/${owner}/${repo}` -const docsUrl = (path: string) => `${repositoryUrl}/blob/main/${path}` const logoUrl = `${import.meta.env.BASE_URL}logo.png` const installCommand = `bun install --frozen-lockfile @@ -75,81 +76,6 @@ const transportGroups = [ }, ] -const guides = [ - { - title: 'Installation', - description: 'Install the pinned toolchain, run each component, and build platform beacon binaries.', - href: docsUrl('docs/INSTALLATION.md'), - label: 'Install', - }, - { - title: 'GitHub setup', - description: 'Configure repositories, the GitHub App, least-privilege permissions, PAT roles, and rotation.', - href: docsUrl('docs/GITHUB_SETUP.md'), - label: 'Provision', - }, - { - title: 'Quickstart', - description: 'Build and import a pre-enrolled beacon, then verify an accepted ping result.', - href: docsUrl('docs/QUICKSTART.md'), - label: 'First run', - }, - { - title: 'Architecture', - description: 'Understand components, identity boundaries, task lifecycle, durable state, and recovery.', - href: docsUrl('docs/ARCHITECTURE.md'), - label: 'Learn', - }, - { - title: 'Channel guide', - description: 'Compare every transport, permission, prerequisite, priority rule, and qualification step.', - href: docsUrl('docs/CHANNELS.md'), - label: 'Transports', - }, - { - title: 'Configuration', - description: 'Look up controller, listener, beacon, OIDC, recovery, dashboard, and CLI settings.', - href: docsUrl('docs/CONFIGURATION.md'), - label: 'Reference', - }, - { - title: 'CLI reference', - description: 'Use setup, enrollment, builds, inventory, tasks, results, proxy, and service commands.', - href: docsUrl('docs/CLI.md'), - label: 'Operate', - }, - { - title: 'Operations & assurance', - description: 'Listener policy, lifecycle behavior, replay protection, and certificate handling.', - href: docsUrl('docs/PRODUCTION.md'), - label: 'Operate safely', - }, - { - title: 'Recovery', - description: 'Provision signed recovery records and short-lived GitHub App token leases.', - href: docsUrl('docs/RECOVERY.md'), - label: 'Configure recovery', - }, - { - title: 'Troubleshooting', - description: 'Diagnose GitHub errors, decrypt failures, acknowledgements, proxy, TLS, gRPC, OIDC, and state.', - href: docsUrl('docs/TROUBLESHOOTING.md'), - label: 'Diagnose', - }, - { - title: 'Development', - description: 'Work with shared contracts, tests, builds, generated protocol bindings, and change checks.', - href: docsUrl('docs/DEVELOPMENT.md'), - label: 'Contribute', - }, - { - title: 'Verification evidence', - description: 'Trace implementation decisions to tests, live qualifications, and cleanup records.', - href: docsUrl('docs/REMEDIATION_TRACEABILITY.md'), - label: 'Review evidence', - }, -] - function CopyButton({ value }: { value: string }) { const [status, setStatus] = useState<'idle' | 'copied' | 'blocked'>('idle') @@ -211,6 +137,10 @@ function SectionHeading({ } function App() { + const guideId = new URLSearchParams(window.location.search).get('guide') + const selectedGuide = guideId ? DOCUMENTATION_BY_ID.get(guideId) : undefined + if (selectedGuide) return + return (

@@ -341,7 +271,7 @@ function App() { Dashboard address

Vite binds to 127.0.0.1:5173. The controller’s HTTPS and gRPC listeners remain disabled until their explicit enable flags and TLS material are present.

- Open the complete quickstart → + Open the complete quickstart →
@@ -392,7 +322,7 @@ function App() {
  • Delivery leases and replay state survive restarts.
  • Unsigned remote modules are rejected.
  • - Read the operating model → + Read the operating model →
    @@ -408,7 +338,7 @@ function App() {
    1

    PolicyDependencies, workflows, toolchains, generated proto, and documentation stay aligned.

    2

    BehaviorBun tests cover signatures, replay handling, delivery ownership, persistence, and transport behavior.

    3

    ArtifactsBuilds and smoke tests verify the dashboard, CLI, controller, proxy, and target beacon binaries.

    - Review verification traceability → + Review verification traceability → @@ -420,8 +350,8 @@ function App() { description="Follow the first-run path or jump directly to setup, channels, configuration, operations, recovery, troubleshooting, and development references." />
    - {guides.map((guide) => ( - + {DOCUMENTATION.map((guide) => ( + {guide.label}

    {guide.title}

    {guide.description}

    @@ -438,7 +368,7 @@ function App() {
    OctoC2Encrypted multi-channel operations
    - Documentation index ↗ + Documentation index Quickstart Security Verification diff --git a/docs-site/src/DocumentationPage.tsx b/docs-site/src/DocumentationPage.tsx new file mode 100644 index 0000000..41d4708 --- /dev/null +++ b/docs-site/src/DocumentationPage.tsx @@ -0,0 +1,197 @@ +import { useEffect, useState, type ReactNode } from 'react' +import ReactMarkdown, { type Components } from 'react-markdown' +import rehypeSlug from 'rehype-slug' +import remarkGfm from 'remark-gfm' +import { + DOCUMENTATION, + DOCUMENTATION_ID_BY_SOURCE, + documentationUrl, + type DocumentationEntry, +} from './docs' + +const owner = import.meta.env.VITE_GITHUB_OWNER || 'dstours' +const repo = import.meta.env.VITE_GITHUB_REPO || 'OctoC2' +const repositoryUrl = `https://github.com/${owner}/${repo}` +const logoUrl = `${import.meta.env.BASE_URL}logo.png` + +function normalizePath(path: string): string { + const output: string[] = [] + for (const part of path.replaceAll('\\', '/').split('/')) { + if (!part || part === '.') continue + if (part === '..') output.pop() + else output.push(part) + } + return output.join('/') +} + +function resolveArticleLink( + current: DocumentationEntry, + href: string, +): { href: string; external: boolean } { + if (!href || href.startsWith('#')) return { href, external: false } + if (/^(https?:|mailto:)/i.test(href)) return { href, external: true } + + const hashAt = href.indexOf('#') + const path = hashAt >= 0 ? href.slice(0, hashAt) : href + const anchor = hashAt >= 0 ? href.slice(hashAt) : '' + const directory = current.sourcePath.split('/').slice(0, -1).join('/') + const sourcePath = normalizePath(`${directory}/${path}`) + const articleId = DOCUMENTATION_ID_BY_SOURCE.get(sourcePath) + + if (articleId) return { href: documentationUrl(articleId, anchor), external: false } + return { + href: `${repositoryUrl}/blob/main/${sourcePath}${anchor}`, + external: true, + } +} + +function plainHeading(markdown: string): string { + return markdown + .replace(/\[([^\]]+)]\([^)]+\)/g, '$1') + .replace(/[`*_~]/g, '') + .trim() +} + +function headingSlug(markdown: string): string { + return plainHeading(markdown) + .toLowerCase() + .replace(/[^\p{L}\p{N}\s-]/gu, '') + .replace(/\s+/g, '-') + .replace(/-+/g, '-') + .replace(/^-|-$/g, '') +} + +function tableOfContents(content: string) { + return content + .split('\n') + .flatMap((line) => { + const match = /^(#{2,3})\s+(.+?)\s*$/.exec(line) + if (!match?.[1] || !match[2]) return [] + const title = plainHeading(match[2]) + return [{ level: match[1].length, title, id: headingSlug(match[2]) }] + }) +} + +function DocumentationHeader() { + return ( +
    +
    + + + OctoC2 + + Docs + + + GitHub +
    +
    + ) +} + +function GuideNavigation({ currentId }: { currentId: string }) { + const categories = ['Start', 'Operate', 'Resilience', 'Engineering'] as const + return ( + + ) +} + +function ArticleTableOfContents({ content }: { content: string }) { + const headings = tableOfContents(content) + return ( + + ) +} + +export function DocumentationPage({ entry }: { entry: DocumentationEntry }) { + const [content, setContent] = useState('') + const [loadError, setLoadError] = useState(false) + + useEffect(() => { + document.title = `${entry.title} — OctoC2 Documentation` + let active = true + entry.load() + .then((markdown) => { + if (active) setContent(markdown) + }) + .catch(() => { + if (active) setLoadError(true) + }) + return () => { active = false } + }, [entry]) + + const components: Components = { + a({ href = '', children }: { href?: string; children?: ReactNode }) { + const resolved = resolveArticleLink(entry, href) + return ( + + {children} + + ) + }, + } + const renderedContent = content.replace(/^> \[!IMPORTANT\]\r?\n/gm, '') + + return ( +
    + + +
    + +
    +
    + {entry.category} + View source ↗ +
    + {loadError ? ( +

    This guide could not be loaded. Return to the documentation index and try again.

    + ) : content ? ( + + {renderedContent} + + ) : ( +
    Loading guide…
    + )} +
    + +
    +
    + ) +} diff --git a/docs-site/src/docs.ts b/docs-site/src/docs.ts new file mode 100644 index 0000000..3b00d69 --- /dev/null +++ b/docs-site/src/docs.ts @@ -0,0 +1,159 @@ +export interface DocumentationEntry { + readonly id: string + readonly title: string + readonly label: string + readonly category: 'Start' | 'Operate' | 'Resilience' | 'Engineering' + readonly description: string + readonly sourcePath: string + readonly load: () => Promise +} + +export const DOCUMENTATION = [ + { + id: 'documentation', + title: 'Documentation index', + label: 'All guides', + category: 'Start', + description: 'Choose a reading path through the complete operator and engineering manual.', + sourcePath: 'docs/README.md', + load: () => import('../../docs/README.md?raw').then((module) => module.default), + }, + { + id: 'installation', + title: 'Installation', + label: 'Install', + category: 'Start', + description: 'Install the pinned toolchain, run each component, and build platform beacon binaries.', + sourcePath: 'docs/INSTALLATION.md', + load: () => import('../../docs/INSTALLATION.md?raw').then((module) => module.default), + }, + { + id: 'github-setup', + title: 'GitHub setup', + label: 'Provision', + category: 'Start', + description: 'Configure repositories, the GitHub App, least-privilege permissions, PAT roles, and rotation.', + sourcePath: 'docs/GITHUB_SETUP.md', + load: () => import('../../docs/GITHUB_SETUP.md?raw').then((module) => module.default), + }, + { + id: 'quickstart', + title: 'Quickstart', + label: 'First run', + category: 'Start', + description: 'Build and import a pre-enrolled beacon, then verify an accepted ping result.', + sourcePath: 'docs/QUICKSTART.md', + load: () => import('../../docs/QUICKSTART.md?raw').then((module) => module.default), + }, + { + id: 'architecture', + title: 'Architecture', + label: 'Learn', + category: 'Start', + description: 'Understand components, identity boundaries, task lifecycle, durable state, and recovery.', + sourcePath: 'docs/ARCHITECTURE.md', + load: () => import('../../docs/ARCHITECTURE.md?raw').then((module) => module.default), + }, + { + id: 'channels', + title: 'Channel guide', + label: 'Transports', + category: 'Operate', + description: 'Compare every transport, permission, prerequisite, priority rule, and qualification step.', + sourcePath: 'docs/CHANNELS.md', + load: () => import('../../docs/CHANNELS.md?raw').then((module) => module.default), + }, + { + id: 'configuration', + title: 'Configuration', + label: 'Reference', + category: 'Operate', + description: 'Look up controller, listener, beacon, OIDC, recovery, dashboard, and CLI settings.', + sourcePath: 'docs/CONFIGURATION.md', + load: () => import('../../docs/CONFIGURATION.md?raw').then((module) => module.default), + }, + { + id: 'cli', + title: 'CLI reference', + label: 'Commands', + category: 'Operate', + description: 'Use setup, enrollment, builds, inventory, tasks, results, proxy, and service commands.', + sourcePath: 'docs/CLI.md', + load: () => import('../../docs/CLI.md?raw').then((module) => module.default), + }, + { + id: 'dashboard', + title: 'Dashboard', + label: 'Interface', + category: 'Operate', + description: 'Configure the local operator interface, TLS trust, credential roles, and capability views.', + sourcePath: 'dashboard/README.md', + load: () => import('../../dashboard/README.md?raw').then((module) => module.default), + }, + { + id: 'operations', + title: 'Operations & assurance', + label: 'Assurance', + category: 'Operate', + description: 'Apply listener, lifecycle, replay, certificate, result-acceptance, and evidence policy.', + sourcePath: 'docs/PRODUCTION.md', + load: () => import('../../docs/PRODUCTION.md?raw').then((module) => module.default), + }, + { + id: 'recovery', + title: 'Recovery', + label: 'Recover', + category: 'Resilience', + description: 'Provision signed records, exact App policies, short-lived leases, renewal, and rotation.', + sourcePath: 'docs/RECOVERY.md', + load: () => import('../../docs/RECOVERY.md?raw').then((module) => module.default), + }, + { + id: 'proxy', + title: 'Proxy workflow', + label: 'Route', + category: 'Resilience', + description: 'Configure control and decoy workflows, signed routes, deduplication, and cleanup.', + sourcePath: 'templates/proxy/README.md', + load: () => import('../../templates/proxy/README.md?raw').then((module) => module.default), + }, + { + id: 'troubleshooting', + title: 'Troubleshooting', + label: 'Diagnose', + category: 'Resilience', + description: 'Diagnose GitHub errors, decrypt failures, acknowledgements, proxy, TLS, gRPC, OIDC, and state.', + sourcePath: 'docs/TROUBLESHOOTING.md', + load: () => import('../../docs/TROUBLESHOOTING.md?raw').then((module) => module.default), + }, + { + id: 'development', + title: 'Development', + label: 'Contribute', + category: 'Engineering', + description: 'Work with shared contracts, tests, builds, generated protocol bindings, and change checks.', + sourcePath: 'docs/DEVELOPMENT.md', + load: () => import('../../docs/DEVELOPMENT.md?raw').then((module) => module.default), + }, + { + id: 'verification', + title: 'Verification evidence', + label: 'Evidence', + category: 'Engineering', + description: 'Trace implementation decisions to tests, live qualifications, and cleanup records.', + sourcePath: 'docs/REMEDIATION_TRACEABILITY.md', + load: () => import('../../docs/REMEDIATION_TRACEABILITY.md?raw').then((module) => module.default), + }, +] as const satisfies readonly DocumentationEntry[] + +export const DOCUMENTATION_BY_ID: ReadonlyMap = new Map( + DOCUMENTATION.map((entry) => [entry.id, entry] as const), +) + +export const DOCUMENTATION_ID_BY_SOURCE: ReadonlyMap = new Map( + DOCUMENTATION.map((entry) => [entry.sourcePath, entry.id] as const), +) + +export function documentationUrl(id: string, anchor = ''): string { + return `${import.meta.env.BASE_URL}?guide=${encodeURIComponent(id)}${anchor}` +} diff --git a/docs-site/src/index.css b/docs-site/src/index.css index 14d678a..66240be 100644 --- a/docs-site/src/index.css +++ b/docs-site/src/index.css @@ -149,7 +149,7 @@ code { color: #cfe3ec; font-size: 0.9em; } .text-link { margin-top: 20px; color: var(--blue-bright); font-size: 0.75rem; font-weight: 650; } .guides-section { padding: 108px 0 128px; border-top: 1px solid var(--border-soft); } -.guide-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; } +.guide-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; } .guide-card { position: relative; min-height: 242px; padding: 24px; border: 1px solid var(--border); border-radius: 11px; background: rgba(255,255,255,0.015); transition: transform 170ms ease, border-color 170ms ease, background 170ms ease; } .guide-card:hover { transform: translateY(-3px); border-color: #42516a; background: rgba(255,255,255,0.03); } .guide-label { color: var(--blue-bright); font: 700 0.63rem/1.4 ui-monospace, monospace; text-transform: uppercase; letter-spacing: 0.08em; } @@ -157,6 +157,54 @@ code { color: #cfe3ec; font-size: 0.9em; } .guide-card p { margin: 0; color: var(--muted); font-size: 0.73rem; line-height: 1.65; } .guide-arrow { position: absolute; right: 22px; bottom: 20px; color: #5e6e84; font-size: 1rem; } +/* In-site documentation reader */ +.docs-reader { overflow: visible; } +.docs-layout { width: min(1440px, calc(100% - 40px)); margin: 0 auto; display: grid; grid-template-columns: 220px minmax(0, 1fr) 210px; gap: 34px; align-items: start; padding: 48px 0 110px; } +.docs-topnav { gap: 24px; } +.docs-sidebar, .docs-toc { position: sticky; top: 96px; max-height: calc(100vh - 120px); overflow-y: auto; scrollbar-width: thin; } +.docs-overview-link { display: block; margin-bottom: 28px; color: var(--blue-bright); font-size: 0.72rem; font-weight: 680; } +.docs-nav-links { display: grid; gap: 27px; } +.docs-nav-links section { display: grid; gap: 3px; } +.docs-nav-links h2 { margin: 0 0 8px; color: #5f6d82; font: 700 0.61rem/1.4 "SFMono-Regular", Consolas, monospace; letter-spacing: 0.1em; text-transform: uppercase; } +.docs-nav-links a { display: block; padding: 7px 10px; border-left: 1px solid var(--border-soft); color: #8e9bae; font-size: 0.72rem; line-height: 1.35; transition: color 150ms ease, border-color 150ms ease, background 150ms ease; } +.docs-nav-links a:hover { color: var(--text); border-color: #526279; } +.docs-nav-links a.active { color: var(--text); border-color: var(--blue); background: linear-gradient(90deg, rgba(105,210,231,0.09), transparent); } + +.docs-article { min-width: 0; padding: 40px 48px 64px; border: 1px solid var(--border); border-radius: 14px; background: rgba(12,17,27,0.78); box-shadow: 0 30px 90px rgba(0,0,0,0.18); } +.docs-article-meta { display: flex; align-items: center; justify-content: space-between; gap: 20px; margin-bottom: 36px; padding-bottom: 18px; border-bottom: 1px solid var(--border-soft); color: var(--muted); font: 680 0.65rem/1.4 "SFMono-Regular", Consolas, monospace; text-transform: uppercase; letter-spacing: 0.09em; } +.docs-article-meta a { color: var(--blue-bright); } +.docs-article h1 { margin: 0 0 25px; font-size: clamp(2.35rem, 4.6vw, 4rem); line-height: 1.03; letter-spacing: -0.05em; } +.docs-article h2 { margin: 58px 0 19px; padding-top: 4px; font-size: clamp(1.55rem, 2.4vw, 2.1rem); line-height: 1.18; letter-spacing: -0.025em; } +.docs-article h3 { margin: 38px 0 15px; font-size: 1.2rem; line-height: 1.3; } +.docs-article h4 { margin: 30px 0 12px; font-size: 0.98rem; } +.docs-article h2, .docs-article h3, .docs-article h4 { scroll-margin-top: 102px; } +.docs-article p, .docs-article li { color: #b2bdcc; font-size: 0.92rem; line-height: 1.78; } +.docs-article p { margin: 0 0 19px; } +.docs-article ul, .docs-article ol { margin: 0 0 24px; padding-left: 24px; } +.docs-article li + li { margin-top: 7px; } +.docs-article strong { color: #e9eef6; } +.docs-article a { color: var(--blue-bright); text-decoration: underline; text-decoration-color: rgba(146,232,245,0.32); text-underline-offset: 3px; } +.docs-article a:hover { text-decoration-color: currentColor; } +.docs-article :not(pre) > code { padding: 0.16em 0.38em; border: 1px solid #29364a; border-radius: 5px; color: #d6edf2; background: #101926; font-size: 0.82em; } +.docs-article pre { max-width: 100%; margin: 22px 0 28px; padding: 20px 22px; overflow-x: auto; border: 1px solid #273348; border-radius: 9px; color: #d8e1ee; background: #080d15; font-size: 0.77rem; line-height: 1.65; } +.docs-article pre code { color: inherit; } +.docs-article blockquote { margin: 24px 0 30px; padding: 17px 20px; border: 1px solid #2b4356; border-left: 3px solid var(--blue); border-radius: 0 8px 8px 0; background: rgba(105,210,231,0.045); } +.docs-article blockquote p { margin: 0; color: #c1cedd; } +.docs-article hr { height: 1px; margin: 46px 0; border: 0; background: var(--border-soft); } +.docs-article table { display: block; width: 100%; max-width: 100%; margin: 24px 0 32px; overflow-x: auto; border-spacing: 0; border-collapse: collapse; font-size: 0.78rem; } +.docs-article th, .docs-article td { min-width: 120px; padding: 11px 13px; border: 1px solid #283449; text-align: left; vertical-align: top; line-height: 1.55; } +.docs-article th { color: #e2e8f1; background: #111a28; } +.docs-article td { color: #aeb9c9; } +.docs-article input[type="checkbox"] { margin-right: 8px; accent-color: var(--blue); } +.docs-loading, .docs-load-error { min-height: 320px; padding: 36px 0; color: var(--muted); font-size: 0.86rem; } +.docs-load-error { color: var(--red-soft); } + +.docs-toc { display: grid; gap: 1px; padding-left: 15px; border-left: 1px solid var(--border-soft); } +.docs-toc strong { margin-bottom: 12px; color: #d4dce8; font-size: 0.72rem; } +.docs-toc a { padding: 5px 0; color: #718096; font-size: 0.67rem; line-height: 1.4; transition: color 150ms ease; } +.docs-toc a:hover { color: var(--text-secondary); } +.docs-toc a.nested { padding-left: 12px; } + footer { width: min(1180px, calc(100% - 40px)); margin: 0 auto; display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 28px; padding: 38px 0 46px; border-top: 1px solid var(--border-soft); } .footer-brand { display: flex; align-items: center; gap: 12px; } .footer-brand img { width: 34px; height: 34px; object-fit: contain; } @@ -177,6 +225,8 @@ footer > p { grid-column: 1 / -1; margin: 14px 0 0; color: #566276; font-size: 0 .workspace-grid { grid-template-columns: repeat(3, 1fr); } .security-section { gap: 45px; } .guide-grid { grid-template-columns: repeat(2, 1fr); } + .docs-layout { grid-template-columns: 210px minmax(0, 1fr); } + .docs-toc { display: none; } } @media (max-width: 760px) { @@ -203,6 +253,11 @@ footer > p { grid-column: 1 / -1; margin: 14px 0 0; color: #566276; font-size: 0 .callout a { grid-column: 2; white-space: normal; } .guide-grid { grid-template-columns: 1fr; } .guide-card { min-height: 200px; } + .docs-layout { width: min(100% - 28px, 900px); grid-template-columns: 1fr; gap: 24px; padding-top: 28px; } + .docs-sidebar { position: static; max-height: none; padding: 20px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); } + .docs-overview-link { margin-bottom: 18px; } + .docs-nav-links { grid-template-columns: repeat(2, 1fr); gap: 22px; } + .docs-article { padding: 32px 30px 52px; } footer { grid-template-columns: 1fr; } .footer-links { justify-content: flex-start; } } @@ -218,6 +273,10 @@ footer > p { grid-column: 1 / -1; margin: 14px 0 0; color: #566276; font-size: 0 .callout { grid-template-columns: 1fr; } .callout a { grid-column: 1; } pre { padding: 16px; font-size: 0.68rem; } + .docs-nav-links { grid-template-columns: 1fr; } + .docs-article { padding: 27px 20px 44px; } + .docs-article-meta { align-items: flex-start; } + .docs-article h1 { font-size: 2.35rem; } } @media (prefers-reduced-motion: reduce) { diff --git a/scripts/check-doc-consistency.ts b/scripts/check-doc-consistency.ts index 6fd04e0..afdb108 100644 --- a/scripts/check-doc-consistency.ts +++ b/scripts/check-doc-consistency.ts @@ -39,6 +39,14 @@ const runtimeConfig = await readFile( join(root, "server", "src", "config", "RuntimeConfig.ts"), "utf8", ); +const docsRegistry = await readFile( + join(root, "docs-site", "src", "docs.ts"), + "utf8", +); +const docsReader = await readFile( + join(root, "docs-site", "src", "DocumentationPage.tsx"), + "utf8", +); function requireText(path: (typeof publicDocs)[number], text: string): void { if (!contents.get(path)?.includes(text)) { @@ -112,6 +120,48 @@ if (!serverEntry.includes("const listeners = readListenerConfig();")) { errors.push("server/src/index.ts must use the validated listener configuration"); } +const containedArticles = [ + "docs/README.md", + "docs/INSTALLATION.md", + "docs/GITHUB_SETUP.md", + "docs/QUICKSTART.md", + "docs/ARCHITECTURE.md", + "docs/CHANNELS.md", + "docs/CONFIGURATION.md", + "docs/CLI.md", + "dashboard/README.md", + "docs/PRODUCTION.md", + "docs/RECOVERY.md", + "templates/proxy/README.md", + "docs/TROUBLESHOOTING.md", + "docs/DEVELOPMENT.md", + "docs/REMEDIATION_TRACEABILITY.md", +] as const; +for (const sourcePath of containedArticles) { + if (!docsRegistry.includes(`sourcePath: '${sourcePath}'`)) { + errors.push(`docs site must contain article ${sourcePath}`); + } +} +if ( + contents.get("docs-site/src/App.tsx")?.includes("/blob/main/docs/") || + contents.get("docs-site/src/App.tsx")?.includes("docsUrl(") +) { + errors.push("docs-site guide links must open the in-site article reader"); +} +for (const requiredReaderContract of [ + "DOCUMENTATION_ID_BY_SOURCE", + "documentationUrl(articleId, anchor)", + "ReactMarkdown", + "remarkGfm", + "rehypeSlug", +]) { + if (!docsReader.includes(requiredReaderContract)) { + errors.push( + `docs-site/src/DocumentationPage.tsx is missing reader contract: ${requiredReaderContract}`, + ); + } +} + if (errors.length > 0) { console.error("Documentation consistency violations:"); for (const error of errors) { @@ -119,5 +169,5 @@ if (errors.length > 0) { } process.exitCode = 1; } else { - console.log("Public documentation authorization, listener, credential, and port claims are consistent."); + console.log("Public documentation authorization, containment, listener, credential, and port claims are consistent."); }