# Connector coverage and compatibility

No single plugin can introspect every framework, host or operating system. SiteWatch combines independent public checks, specialized local connectors and a common authenticated reporting contract. Public checks work for reachable HTTP/HTTPS websites regardless of their implementation. Internal evidence depends on actual installation permissions.

The owner-authenticated **Node agent bundle** contains the Node agent, its required core module, Windows and Linux/macOS installers, and the [Node bundle installation guide](../connectors/node-agent-bundle-readme.md). The owner-authenticated **Python service bundle** contains the standalone Python connector and installers for Windows, Linux/systemd, and macOS/launchd. Extract either bundle as a unit, transfer the per-site `.env` securely, and run its installer with the config path and project root. Bundles contain no site token. Detailed commands and OS requirements are in each bundle's `README.md`.

## Choose what to connect

Start with the evidence you need. A connector is optional for public website checks; add one only when you need evidence available on the website host, application, or private network.

| What you want to monitor | Easiest path | What it provides and what to know |
|---|---|---|
| Public website availability, pages, forms, SSL, technical SEO and security headers | Add the website URL; no site installation required | SiteWatch checks reachable public HTTP(S) routes. It cannot see private code, host state, or every page beyond the crawl limits. |
| WordPress core/plugin/theme state and optional changed-page rechecks | Install the [WordPress plugin](../README.md#install-the-wordpress-connector) | The host must reach SiteWatch over public HTTPS. WP-Cron can depend on site traffic; plugin and hosting behavior still need live-site verification. |
| Host and project inventory across supported language manifests; optional file evidence | Install the [Node agent](../connectors/node-agent-bundle-readme.md) beside the project | Requires Node.js 24+. It can inspect supported manifests for other languages; it does not require the monitored application to run on Node. Windows fixtures pass; native Linux/macOS service acceptance remains open. |
| Python host and project inventory without a Node runtime | Install the [Python agent](PYTHON_CONNECTOR.md) | Requires Python 3.10+ and uses the standard library. Windows fixtures pass; native Linux/macOS service acceptance remains open. |
| PHP CLI/runtime and Composer dependency evidence | Install the [PHP agent](PHP_CONNECTOR.md) | Requires PHP CLI 7.4+; it does not instrument PHP web requests or inspect pages, forms, database health, or source files. Native Unix service behavior remains unverified. |
| A private health endpoint reachable only inside a network | Run the [customer-side private probe](LOCATION_PROBES.md) inside that network | The internal target stays on the customer host. SiteWatch receives only the bounded probe result; install and test the runner on the target network before relying on it. |
| Application-owned health or inventory data from a custom stack | Send the [report-v1 contract](#custom-connector-http-contract-report-v1) with the provided [PowerShell or shell sender](../connectors/custom-http/README.md) | Your application must create truthful evidence. The sender transports it; it does not discover or validate application health. |
| Traces, logs and metrics from a supported OpenTelemetry SDK | Use the [OTLP integration](OPENTELEMETRY.md); use the Collector bridge for gRPC-only SDKs | SiteWatch retains a bounded aggregate subset. The documented Collector example and Node/Python fixtures are not proof of production compatibility for every language or operating system. |
| AI source changes through a GitHub repository | GitHub App connection, repository picker, and separately approved exact-patch draft-PR flow; see the [GitHub App connector guide](GITHUB_APP_CONNECTOR.md) and [adapter notes](../connectors/github/README.md) | Installation ownership/PKCE checks, metadata-only repository listing, one revalidated repository selection, a repository-scoped draft-PR action, and a separately confirmed read of check runs for the exact PR commit are implemented with fake-provider fixtures. No merge occurs. A registered App, live repository/webhook acceptance, granted optional read permissions, and branch-protection verification remain pending. |

For a low-friction first setup, add the public website and confirm its first scan. Then install one connector only if you need internal host or framework evidence. The owner-authenticated setup screen creates the per-site config; keep it outside the web root and source repository. A private Tailscale dashboard URL will not work from a shared WordPress host unless that host can also join your tailnet.


## Tested compatibility at a glance

“Implemented” means the connector exists; the following table records where its runtime behavior has actually been exercised. Installer files for an OS do not by themselves prove that the service works there.

On October 26, 2026, Git for Windows Bash 5.2 parsed all nine shipped `scripts/` and `connectors/` shell entry points with `bash -n`, and Windows PowerShell 5.1 parsed all 11 shipped PowerShell scripts. These checks are syntax-only; they do not establish POSIX runtime compatibility, installer behavior, or real systemd/launchd behavior. The GitHub Actions workflow defines Ubuntu, macOS, and Windows jobs, but this checkout has no Git remote, so no hosted run can be inspected here. Native Linux/macOS service-manager acceptance remains open.

| Connector path | Runtime evidence completed | Still needs validation |
|---|---|---|
| Public URL checks | Windows local test suite; bounded public HTTP/HTTPS requests | Live-site behavior varies by DNS, firewall, CDN and bot policy; public checks cannot see private code or host state |
| Customer-run private service probe | Windows dashboard journey and isolated HTTP fixture; request target remains local, DNS answers are allowlisted/pinned, and telemetry is reduced to status, latency and TLS expiry | Native Linux/macOS service execution, real private DNS, trusted internal TLS, and private-host reachability still need validation; see [private probe setup and limits](LOCATION_PROBES.md) |
| WordPress plugin | Connector 0.3.10 passed the full isolated four-case WordPress Playground matrix on WordPress 6.8.11 and 7.1.3 with PHP 7.4.33 and 8.4.25, using pinned CLI 3.1.60. It inventories base-directory must-use plugins and recognized WordPress drop-ins and leaves unsupported update/integrity states unknown; native Windows PHP 8.4.27 and 7.4.33 smoke tests also pass. | Hosted CI result, live Hostinger delivery, WP-Cron scheduling, POSIX service behavior and production hosting acceptance |
| Node host agent | Node 24 on Windows; report and inventory tests pass | Native Linux/macOS service-manager execution and remote matrix results |
| Python host agent | Python 3.13 on Windows; report, health-secret isolation and inventory fixtures pass | Python 3.10 runtime and native Linux/macOS service-manager results |
| OpenTelemetry application telemetry | Language-neutral OTLP/HTTP setup in the dashboard; bounded traces, logs and metrics receiver; Node and Python exporter fixtures pass | SiteWatch receives OTLP/HTTP; owners of gRPC-only applications can use the documented Collector bridge. Actual Collector startup, Java/.NET/Go SDK interoperability and production transport remain unverified; receiver retains a bounded SiteWatch-supported subset, not raw telemetry |
| OpenTelemetry Collector bridge | Versioned configuration example accepts local OTLP/gRPC and OTLP/HTTP and forwards all signals over TLS-verified HTTP/Protobuf; YAML/privacy/listener/routing regression passes | Actual Collector startup/export, Java/.NET/Go SDK interoperability and Linux/macOS/Windows service deployment still need acceptance tests |
| Standalone PHP agent | Checksum-verified temporary PHP 8.4.27 Windows CLI smoke; PHP 7.4.33 Playground smoke for collection, privacy, Composer bounds and mocked delivery | Playground cannot validate POSIX permission/config-file and symlink behavior; native Linux/macOS acceptance remains pending |
| Custom report-v1 senders | Windows PowerShell and POSIX senders both reached the isolated HTTP fixture; the POSIX sender passed under Git Bash on Windows | Native Linux/macOS HTTP delivery and deployment-specific certificate/firewall behavior remain pending |
| Playwright aggregate reporter | Isolated Windows browser journey and reporter fixture | Customer-run suites on native Linux/macOS/CI and restricted service accounts |

This checkout has no Git remote, so configured GitHub Actions matrices have no hosted result to inspect. See the detailed connector rows below for scope, installer behavior, privacy boundaries and remaining limits.

| Connector | Implemented evidence | Execution status / limits |
|---|---|---|
| Node 24+ cross-language project agent + agent-core.mjs + zap-sarif.mjs | OS/runtime, disk/memory, optional health endpoint, npm/pnpm lockfiles/frameworks, Pipenv/requirements/Composer/.NET declarations and lockfiles, Go checksum references, crates.io entries from Cargo.lock, RubyGems from Gemfile.lock, Maven direct declarations from bounded `pom.xml` parsing, and resolved Maven modules from Gradle's `gradle.lockfile`; opt-in source heuristics/integrity and ClamAV; opt-in approved source-file repair/rollback | The agent runs on a machine with Node 24+ but can inspect a project written in another language; it does not require the monitored application itself to use Node.js. In addition to Node package declarations, the agent derives a bounded set of recognized Python, PHP, Ruby, Go, Rust, JVM and ASP.NET frameworks from matching package identities and retains the originating manifest evidence. A package match indicates presence in dependency evidence; it does not prove that framework is loaded or running. Lockfiles are parsed with file, line, package and dependency-count bounds and no external agent parser dependency. Top-level NuGet lockfile discovery streams at most 10,000 directory entries and reports an incomplete dependency-file count if the cap or directory read prevents full discovery. NuGet inventory reads `packages.lock.json` and up to 31 project-specific `packages.<name>.lock.json` files from the configured project root, accepts lock formats 1–3, validates package IDs and resolved NuGet versions, deduplicates case-insensitively across target frameworks, and reports unreadable/over-cap lockfile sources as incomplete OSV coverage. This follows NuGet's documented project-specific lockfile naming; the agent does not run restore/build. Maven POM inventory includes direct dependencies with simple property substitution and versions resolved from local `dependencyManagement`, labels version ranges as non-exact, and excludes conditional-profile, dependency-management-only, unresolved, optional, test, provided and system entries; it does not resolve imported BOMs or parent POMs and does not establish the installed graph. pnpm v6/v9 package-key records exclude workspace links and unresolved aliases; Pipenv accepts exact public PyPI versions and excludes Git/path/private-index records; Ruby only uses public GEM-source entries; Gradle entries need exact versions and configurations. For uv, generate CycloneDX locally; see SBOM.md. These are advisory dependency evidence, not proof of installed or loaded code. Windows installer creates a limited per-user sign-in task; Linux/macOS installer creates systemd/launchd user services. On Linux, the installer probes whether the per-user manager can run a `PrivateUsers` unit; when available it applies strict filesystem protections and allows writes only to the agent state directory. If the host cannot provide that namespace, setup warns and retains the owner-account service without claiming those filesystem restrictions. Windows installer behavior is exercised locally; native Linux sandbox execution and macOS service execution remain pending the CI matrix or real hosts. The installed Git Bash passed parser-only checks for all nine repository shell scripts; these checks do not execute installers or validate systemd/launchd behavior. Agent must run next to the real project. |
| Python 3.10+ standalone agent | OS/runtime, disk, exact PyPI resolutions from public-source `Pipfile.lock`, PEP 751 `pylock.toml`/named lockfiles, Poetry `poetry.lock`, uv `uv.lock`, pinned `requirements.txt` declarations, common framework declarations, optional health endpoint and opt-in ClamAV signature scan; bounded CycloneDX 1.4–1.7 and SPDX 2.2/2.3 package inventories for eight ecosystems | Private-index, Git, path and editable lock entries are excluded. PEP 751 files accept only lock version 1.0 files with required `created-by` metadata and exact package records explicitly tied to the public PyPI index; up to 32 named files are examined. Poetry `lock-version` is read only from its `[metadata]` table; formats 1.0, 1.1 and 2.0–2.2 are bounded. Source-less Poetry package records are treated as public PyPI only when `pyproject.toml` contains no configured Poetry sources, and explicit package sources are omitted. This follows Poetry's [default-source rules](https://python-poetry.org/docs/repositories/) and its [current lockfile structure](https://github.com/python-poetry/poetry/blob/main/poetry.lock). uv lockfiles are limited to schema v1, revisions 1–3, and package records that explicitly identify the public PyPI registry; duplicate fields, unknown schemas/revisions, private registries and unstated sources are omitted, consistent with uv's [lockfile versioning](https://docs.astral.sh/uv/concepts/resolution/#lockfile-versioning) and its warning that the format may change. SBOMs use the shared [format, version, and safety limits](SBOM.md); declared versions do not prove packages are installed. Framework evidence is manifest/lock evidence; exact versions are labeled as declarations/resolutions with installation unverified, records without established public provenance are counted as omitted, and conflicting versions are left unresolved. Windows Python 3.13 reporting/health-secret isolation and inventory fixtures pass locally. CI runs config, inventory and malware/scanner-safety fixtures on Ubuntu, macOS, and Windows using Python 3.10 and 3.13; remote results are pending. Linux/systemd service setup uses a capability probe before enabling private-user namespace filesystem restrictions, while macOS uses a launchd user service. Installer service execution remains pending Linux/macOS CI or real-host validation. No repairs or heuristic source scan. |
| PHP 7.4+ standalone CLI agent | PHP CLI/OS family, recognized framework versions from Composer lock, opt-in exact Composer dependency identities | No third-party packages or project code execution; Composer parsing is bounded to 1 MiB/2,000 records/400 reported dependencies. Package names stay private unless explicitly enabled. PHP 7.4/8.4 fixtures are configured in CI; this host has no PHP runtime and no remote CI result. It reports the CLI runtime, not web-SAPI health. See [PHP connector setup](PHP_CONNECTOR.md). |
| WordPress 6+ / PHP 7.4+ plugin | Running CMS/PHP versions, active/inactive plugin inventory with exact slugs, active theme slug/version, timestamped cached update evidence for plugins/core/active theme, DB query, PHP memory, daily core checksums, incremental SHA-256 plugin checksums for supported WordPress.org packages, and bounded active-theme comparison against its exact WordPress.org release ZIP | Version 0.3.8 counts fresh, version-matched plugin updates across all installed plugins while bounding detailed components to 250 and disclosing omissions. It retains the 0.3.6 daily theme comparison, request/file/time limits, and omission of file paths/hashes. Custom themes, absent official package metadata, and unavailable ZIP support stay unsupported or incomplete. The current 0.3.8 package and smoke passed WordPress 6.8.11/7.1.3 with PHP 7.4.33/8.4.25 in the isolated four-case Playground matrix. The changed-page hook, public same-origin URL queue, owner opt-in, minimized heartbeat event, and accepted-event acknowledgement ran inside WordPress. Live staging, Hostinger delivery, and WP-Cron behavior remain unverified. The connector does not claim a malware scan or repair capability. |
| Custom HTTPS reporting | Bounded checks/metrics and allowlisted inventory from other backends | A common contract and copyable PowerShell/Unix curl senders are included; applications must still collect truthful signals and create their own report JSON. |
| Customer-run private service probe | One private HTTP(S) health URL per customer-run probe, DNS answer validation and IP pinning, no redirects/body reads, and status/latency/TLS-expiry reporting | Internal URL and path are stored only in the downloaded local config; authentication headers and response assertions are unsupported. Every DNS result must remain in loopback, RFC1918, Tailscale or supported IPv6 ULA ranges; AWS EC2's IPv6 metadata address is denied. This does not provide managed global probes. See [setup and limits](LOCATION_PROBES.md). |
| Independent SiteWatch health watchdog | Standalone Node 24+ utility polls another SiteWatch server's `/healthz`, debounces outages/recovery, identifies a stalled internal scheduler, and sends transition alerts to a configured HTTPS webhook; macOS can optionally show native desktop alerts when no webhook is configured | Run it on a separate host. It has no SiteWatch dependency and stores only bounded counters/pending events. Windows Task Scheduler and macOS launchd/Linux systemd user installers are included; real second-host delivery and those OS service runs remain unverified. |
| Playwright Test reporter | Reuses a customer's existing browser suite and reports one aggregate pass/fail result through an external job heartbeat | The test runner and browsers stay in the customer's CI. Only the aggregate status is sent; test names, URLs, page content, screenshots, traces, and assertion text are not transmitted. See [Playwright browser checks](PLAYWRIGHT_CHECKS.md). |
| Native/mobile/browser-only apps | Public API checks where applicable | Native iOS/Android SDKs and browser error/performance SDKs are future work. Never embed a connector bearer token in frontend/mobile distributed code. |
| Cloud/hosting/registrar APIs and OpenTelemetry | OTLP trace, log and metrics ingestion over HTTP JSON and Protobuf. Metrics accept bounded gauge, sum, histogram, exponential-histogram and summary points; only minute aggregates are retained. Official Node JSON/Protobuf and Python Protobuf SDK metric exporters pass isolated gzip/HTTP integration tests. Trace exporters have Node/Python fixtures, and logs retain minute-level severity counts only. Owners can optionally configure sustained trace/log/metric alerts. Optional aggregate Prometheus scrape endpoint. | gRPC, Java/.NET/Go and Collector interoperability, Python log exporters, provider OAuth/agents, and first-party hosting/registrar account integrations remain unimplemented or unverified. A Python metrics check is configured for the existing three-OS CI matrix, but no hosted result is available because this checkout has no Git remote. DNS cannot identify the origin billing account reliably. Metric dimensions and raw points, plus log bodies, attributes and trace IDs, are discarded; retries may count records again and may inflate aggregate/alert counts. |

The Node agent's NuGet inventory follows Microsoft's documented `packages.lock.json` and `packages.<project_name>.lock.json` naming. SiteWatch reads the default file and up to 31 project-specific lockfiles beside the configured project root, validates supported formats and exact resolved package versions, and surfaces unreadable or over-cap lockfile sources as incomplete advisory coverage. It does not run `dotnet restore` or a project build. Microsoft's documentation notes that lockfile format versions depend on enabled NuGet features, so unsupported versions remain explicitly unscanned. See [NuGet package references and lockfiles](https://learn.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files) and [NuGet version formats](https://learn.microsoft.com/en-us/nuget/concepts/package-versioning).

The WordPress connector reads WordPress's existing `update_plugins`, `update_core` and `update_themes` site transients; it does not trigger an update request or install an update. It verifies WordPress core files against the official core checksum API and incrementally verifies checksum-supported plugin files against WordPress.org's SHA-256 manifests. For the active theme, it uses current WordPress update metadata to confirm an official package source, then compares installed files with the exact installed-version ZIP from `downloads.wordpress.org` in a daily background job. This does not use an undocumented theme checksum API. Package requests, file counts, file sizes, total bytes, and scan time are capped; the heartbeat reads cached evidence and omits file names and hashes. A difference reports changed/missing/extra counts. Custom themes, missing official packages, and hosts without ZIP support remain unsupported or incomplete. Differences can come from legitimate customization, so they are review signals and are not a malware verdict. These checks are bounded integrity comparisons, not a malware scanner. See the official [WP-CLI plugin checksum reference](https://developer.wordpress.org/cli/commands/plugin/verify-checksums/), [theme installation package behavior](https://developer.wordpress.org/cli/commands/theme/install/), and [WordPress security check guidance](https://developer.wordpress.org/news/2024/09/website-security-checks-wp-cli-for-site-owners-and-administrators/). Update states are reported only when the cache is recent and matches the installed component version; otherwise the component remains unknown. See the [update-core reference](https://developer.wordpress.org/reference/functions/get_core_updates/), [theme update checker](https://developer.wordpress.org/reference/functions/wp_update_themes/), and [plugin update checker](https://developer.wordpress.org/reference/functions/wp_update_plugins/).

## Cross-platform verification boundary

Rechecked on October 26, 2026: this Windows host runs Node 24 and Python 3.13; Python 3.10, Java, .NET SDK, Go, Docker, Podman, Terraform, and GitHub CLI are unavailable. PHP 8.4.27 CLI was downloaded temporarily from PHP.net and hash-verified; both PHP connector syntax checks and smoke scripts pass. Native PHP 7.4.33 lint/smoke also passes, as compatibility evidence only because PHP 7.4 is end-of-life. Git Bash is installed at its standard Program Files path: all nine repository shell scripts pass parser-only checks, and the POSIX custom report sender passes its isolated HTTP fixture under Git Bash on Windows. WSL is installed but has no Linux distribution. WordPress connector 0.3.10 passed all four pinned Playground combinations (WordPress 6.8.11/7.1.3 on PHP 7.4.33/8.4.25) with CLI 3.1.60. The CI workflow defines Ubuntu/macOS/Windows PHP coverage, but this checkout has no Git remote and hosted results are unavailable. Native Linux/macOS service-manager acceptance, public delivery and container image scanning remain pending.

## Node and Python configuration

- Set SITEWATCH_URL, SITEWATCH_SITE_ID and SITEWATCH_TOKEN through backend secret configuration.
- The one-time setup dialog can download an agent `.env` file containing these three per-site values. Treat it as a password: transfer it to the monitored host securely, restrict file access to the agent account, and store it outside the web root and source control. Node 24+ can run a one-time connection check with `node --env-file=sitewatch-agent-ID.env node-agent.mjs --once`; PHP and Python read the same file with `php-agent.php --config-file PATH` and `python python-agent.py --config-file PATH --once`. Python and PHP reject POSIX config files readable by group/other users; use `chmod 600`. The PHP loader also rejects symbolic links and reads from the same regular-file handle whose permissions it checked. Windows ACLs must be restricted by the installer or host owner because POSIX mode bits do not represent Windows ACLs. Process environment values take precedence. WordPress uses its own settings screen.
- Python's automated per-user service installer instructions are in [Python connector setup](PYTHON_CONNECTOR.md). The config stays private and the service runs under the installing account; OS-specific service behavior still requires platform validation.
- SITEWATCH_PROJECT_ROOT points at the application source/lockfiles. Framework ranges and lockfile versions are labeled as declarations/resolved records; they do not prove what is currently executing on every host.
- SITEWATCH_SARIF_PATH optionally points to an absolute external SARIF 2.1.0 report path for the Node agent to import. Its parent directory must exist and resolve outside the project root; when unset, the agent reads `security.sarif` from its private agent state directory. Relative paths are rejected. Never put the original report in a served web root. See [SARIF scanner setup](SARIF.md).
- SITEWATCH_ZAP_SARIF_PATH optionally points to a private OWASP ZAP SARIF 2.1.0 report outside the project root. When unset, the Node agent reads `zap.sarif` from its private agent state directory. The heartbeat agent only imports reports; the separate, interactive `zap-dast-runner.mjs` can run a customer-authorized bounded scan using Docker. It stores only bounded same-site rule/path/severity/confidence data. See [ZAP DAST runner and limits](ZAP_DAST.md).
- SITEWATCH_HEALTH_URL optionally checks a local read-only JSON endpoint. SITEWATCH_HEALTH_TOKEN supplies its separate credential. Raw endpoint responses are bounded and not forwarded.
- SITEWATCH_SCAN_FILES=true enables a bounded source walk, heuristic review and integrity baseline. Excludes hidden paths, dependencies, backups, secrets and symlinks; caps depth/files/size. The first baseline is an observation, not proof files were clean when enrolled. Changed baselines are not silently trusted.
- SITEWATCH_AI_CRAWLER_LOG_PATH optionally points either agent to one local Apache/Nginx combined access log. SiteWatch reads at most the last 1 MiB and 20,000 lines, and caches the aggregate for 15 minutes; large or malformed samples are marked partial. It reports only recognized AI/search crawler labels and 24-hour/seven-day counts split by HTTP result. Raw lines, IP addresses, user-agent strings, request paths and query values are never sent or stored by SiteWatch. User-agent labels are unverified and spoofable; a configured log can be incomplete or wrong, so no observed requests do not prove that bots did not visit. Compressed logs and custom non-combined formats are unsupported. The feature is opt-in and disabled until you set the path. The agent account must be able to read the file; keep the path outside the public web root where practical.
  - Example private config entry: `SITEWATCH_AI_CRAWLER_LOG_PATH="/var/log/nginx/access.log"` on Linux/macOS, or `SITEWATCH_AI_CRAWLER_LOG_PATH="C:\SiteWatchPrivate\access.log"` on Windows. Use the exact combined-log file configured on your web server; standard combined logs include request and user-agent fields, but only aggregate counts leave the monitored host.
- SITEWATCH_CLAMSCAN optionally points to an installed ClamAV executable for either agent. Maintain its signatures yourself. `SITEWATCH_CLAMSCAN_INTERVAL` sets the scan interval in seconds (default 3600; allowed 60–14400). Each heartbeat checks the ClamAV version; successful and incomplete results are cached in agent memory until the interval expires or the signature version changes. The reported scan time remains the time of the actual scan, so old results become stale instead of appearing freshly scanned. Restarting an agent clears this cache and triggers a scan. The agent enables ClamAV's `--alert-exceeds-max`; configured file/archive/recursion limits become informational coverage findings and make the scan incomplete, never a malware verdict. This follows [ClamAV's scanner guidance](https://docs.clamav.net/manual/Usage/Scanning.html), which documents that oversize files may otherwise be skipped and assumed clean. The Ubuntu CI job now installs ClamAV and runs a real-engine integration test with an isolated custom signature and harmless EICAR/clean fixtures; its result-parser coverage does not validate production signature-feed freshness. ClamAV's official [signature documentation](https://docs.clamav.net/manual/Signatures.html) describes custom `.ndb` databases and the `clamscan --database` option. A heuristic finding is not a confirmed infection; no automatic delete or quarantine occurs.
- SITEWATCH_ALLOW_FILE_REPAIR=true additionally requires project root and health endpoint. The dashboard must independently verify ownership and receive the opted-in capability before preparing repairs. Approvals bind exact content, expire after ten minutes, and are claimed once.
- SITEWATCH_AGENT_STATE points outside the webroot; default is a private per-project folder under the running user's home. Never serve or commit this directory. A claimed task interrupted before acknowledgement becomes uncertain and requires review rather than automatic re-execution.

## Running continuously

Use an unprivileged account and the OS service manager. Download the Node files and installer for the monitored host's OS. On Windows, run PowerShell as the account that should run the agent:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\install-node-agent-windows.ps1 .\sitewatch-agent-ID.env C:\path\to\your\project
```

The Windows installer validates the private config without printing credentials, copies it with an ACL limited to your account and SYSTEM, stores state outside the project, and creates a least-privilege Task Scheduler task that runs at sign-in. It does not store the token in task arguments or run while the account is logged out. Re-run it after config changes. The task name is scoped to the site ID; a conflicting task is left untouched.

The same connector supports per-user systemd on Linux and launchd on macOS:

```sh
bash scripts/install-node-agent-unix.sh /secure/path/sitewatch-agent-ID.env /path/to/your/project
```

If using separate downloads, put the Node installer beside `node-agent.mjs`, `agent-core.mjs`, and `zap-sarif.mjs`; it also works from a full source checkout. It requires Node 24+, copies the downloaded agent config to a mode-600 user config file, places agent state outside the project, and does not put tokens in service command arguments. Linux services run as the user and may stop when the user session ends unless the host administrator separately enables lingering. macOS LaunchAgents run while that user is logged in. Re-run the installer after config changes to reload the service. The Node and Python installers have behavioral fixtures in `npm run verify`: on Linux/macOS they use temporary homes and stub service managers to verify private files, generated service definitions, command arguments and invalid-config refusal. These checks do not verify a real systemd/launchd lifecycle, reboot behavior, or service-manager policy. Their native Linux/macOS results await the remote CI matrix or equivalent real hosts. Python installer usage is documented in [Python connector setup](PYTHON_CONNECTOR.md). Linux output is handled by the host journal; macOS writes under the private agent state directory and should be included in that host's log-rotation policy. Ensure the monitored host can reach the dashboard; Tailscale-only access requires it to be on the permitted tailnet.

Keep the original downloaded config private and remove it from temporary download folders after installation. The Node installer itself never prints the token.

Python continuous running uses the per-user service installers in its bundle. Full backup scheduling, restore drills, host patch management, WAF protection, active exploit scanning and broad automatic update deployment are separate capabilities, not implicit results of installing a heartbeat agent.

The Node agent also reads bounded local CycloneDX JSON and SPDX 2.2/2.3 JSON SBOMs for eight package ecosystems. See SBOM.md for filenames, identity/coverage limits and tested compatibility. No package generator or project code is executed. It also reads bounded Go go.sum references and Cargo.lock packages from the standard crates.io registry; Go checksum files can contain stale or unselected versions, while git/path Cargo dependencies are omitted. Checksums are not retained and project build tools are not run.

Optional `inventory.backups` fields are shown in Website details as connector/operator-reported backup evidence. For the Node agent, create `backup-status.json` in its private state directory (`SITEWATCH_AGENT_STATE`, or by default `$HOME/.sitewatch-agent/<project hash>`). Use this schema, with Unix epoch timestamps in milliseconds:

```json
{
  "lastSuccessfulAt": 1792742400000,
  "restoreTestedAt": 1792656000000,
  "coveredScopes": ["files", "database"],
  "restoreTestedScopes": ["files", "database"],
  "destination": "external_service",
  "retentionDays": 30
}
```

Write `lastSuccessfulAt` only after your backup system reports success. Write `restoreTestedAt` only after a real restore test succeeds. `coveredScopes` and `restoreTestedScopes` accept `files` and `database`; `destination` accepts `same_host`, `host_managed`, `external_service`, or `unknown`; `retentionDays` is 1–3650. These values describe what the operator reports, not independently observed storage or recovery. The agent reads a bounded regular JSON file and forwards only these allowlisted fields; paths, passwords and other fields are discarded. The source is labeled as locally supplied and `verified: false`. Missing or stale evidence is displayed as unknown; even a current report does not independently prove that a backup exists or can be restored. SiteWatch's encrypted installation recovery covers the SiteWatch database and settings, not customer websites. This is a status display only; the connectors do not schedule, store or restore customer-site backups.

Metric units: bundled byte-based collectors report MiB/GiB, despite legacy _mb/_gb keys. Custom connectors should send metric_units declarations as described in TELEMETRY.md; unknown units are retained as unverified and excluded from comparable charts.

## Custom connector HTTP contract (report v1)

Any runtime that can make an HTTPS POST can report through the same endpoint; it does not need to use a SiteWatch SDK. The [report v1 JSON Schema](../connectors/custom-http/report-v1.schema.json) supports editor validation and language-specific client generation; the [custom HTTPS connector kit](../connectors/custom-http/README.md) includes Windows PowerShell and Linux/macOS shell senders. In the dashboard, create a website with its connector enabled and store the displayed one-time token in the monitored host's secret manager. Keep it server-side and per website. Do not place it in browser/mobile code, source control, command-line arguments, or routine logs.

Send a JSON request to `POST {SITEWATCH_URL}/api/ingest/{SITEWATCH_SITE_ID}` with `Authorization: Bearer {SITEWATCH_TOKEN}` and `Content-Type: application/json`. Example body:

```json
{
  "version": 1,
  "agent": "example-company/php-agent/1.0",
  "runtime": "PHP 8.4",
  "checks": [
    {"name": "application process", "status": "ok", "message": "worker is responding"},
    {"name": "database", "status": "warning", "message": "replica lag is above target"}
  ],
  "metrics": {"uptime_seconds": 86400, "memory_used_mb": 128},
  "metric_units": {"memory_used_mb": "MiB"},
  "inventory": {
    "platform": {"os": "Linux", "osVersion": "6.x", "arch": "x86_64", "runtime": "PHP 8.4"},
    "frameworks": [{"name": "Laravel", "version": "12.0", "source": "composer.lock"}],
    "dependencies": [{"ecosystem": "Packagist", "name": "vendor/package", "version": "1.2.3", "exact": true, "source": "composer.lock"}],
    "capabilities": ["runtime_metrics", "dependency_inventory"],
    "collectedAt": 1792224000000
  }
}
```

Send `version: 1` for the current report contract. For backwards compatibility, reports that omit `version` are treated as v1; an explicit unsupported version is rejected. The server accepts 1–20 uniquely named checks with `ok`, `warning` or `error` status. A check message is limited to 200 characters; the entire request body is limited to 256 KiB. Supported numeric metrics are `memory_used_mb`, `memory_limit_mb`, `disk_free_gb`, `uptime_seconds` and `updates_available`. Declared byte units must be explicit (`MiB`/`MB`, `GiB`/`GB`); unknown values and extra inventory fields are discarded. Inventory is normalized through the same allowlists and size limits as bundled agents. Never send passwords, environment contents, response bodies, private keys, or unrestricted logs.

`202 Accepted` means the authenticated report was recorded, not that the host was verified clean. `400` means the report is invalid, `401` means the site/token pairing is wrong or connector access is disabled, `413` means the payload exceeded the body limit, and `429` includes `Retry-After`. Heartbeats must be at least ten seconds apart; clients should honor `Retry-After` and use bounded exponential backoff for transient network/5xx failures. Do not retry credential or validation failures unchanged. Use HTTPS; a self-hosted Tailscale-only dashboard requires the agent host to be on the permitted tailnet.

The endpoint authenticates with a per-site bearer token and accepts JSON independent of client language. It does not execute uploaded code, infer that unreported capabilities are clean, or guarantee compatibility with an operating system/framework. Keep custom fields inside documented top-level sections and coordinate schema changes before relying on new fields; unknown inventory keys are intentionally ignored.

Validation: the JSON example above is submitted unchanged through the authenticated ingestion route by `test/monitoring.test.mjs`; the test confirms acceptance and sanitized inventory storage. The schema/example regression in `test/custom-http-connector.test.mjs` checks the version, check-count/status limits and that the deliberately warning sample passes the server's validator. These fixtures verify contract consistency, not a separately implemented external runtime or remote deployment.
