Skip to content

feat(checks): Script check type — sandboxed JavaScript (RFC 0010) - #219

Merged
cl8dep merged 3 commits into
mainfrom
feat/script-check
Jul 24, 2026
Merged

feat(checks): Script check type — sandboxed JavaScript (RFC 0010)#219
cl8dep merged 3 commits into
mainfrom
feat/script-check

Conversation

@cl8dep

@cl8dep cl8dep commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

Implements the Script check type (RFC 0010): the operator writes a small sandboxed-JavaScript check() that drives its own HTTP through a piro:http module and returns a raw verdict { up, message?, dimensions? }. It is an ordinary RFC 0016 ICheck whose ProbeAsync runs a fresh Jint engine per probe. Consistent with the SDK, the check reports raw state only — DEGRADED vs DOWN is the alert policy's decision from the numeric dimensions the script emits (the same pattern the HTTP check uses with BodyRuleFailures), so a script never names a severity.

This is not an OS script/binary runner: there is no shell command, child process, or exit-code contract. Execution is deny-by-default (no CLR interop, no filesystem, only piro:http resolves) with a whole-script timeout, statement, and memory ceiling, and an SSRF guard on the one egress path.

Related

Changes

Backend (Piro.Checks/Script)

  • ScriptCheck : Check<ScriptCheckConfig> — builds a fresh Jint engine per probe (Jint is not thread-safe), registers the piro:http module, invokes the exported check(), and maps the return to CheckProbeResult. Deny-by-default sandbox; TimeoutInterval / MaxStatements / LimitMemory; a disallowed import (e.g. node:fs) is a check Error, not a Down.
  • ScriptHttp — the piro:http module: GET-only, response-size capped, returns { statusCode, body, json, headers }.
  • ScriptSsrfGuard — connect-time SSRF guard on a dedicated piro-script-http client: rejects loopback, RFC-1918, link-local / cloud-metadata, and IPv6 ULA/link-local, validating the resolved IP (defeats DNS rebinding). Separate from the shared piro-http so existing checks are unaffected; retrofitting the guard to the other clients is RFC 0010 phase 5 (deferred).
  • ScriptCheckConfig with a starter-template default; CheckType.Script added (string discriminator, no migration).

Debug run

  • ITestableCheck — an opt-in SDK seam (ICheck.ProbeAsync stays pure) implemented by ScriptCheck.ProbeForTest, which captures console.log. Same engine and module as production; only the console binding differs, so there is no "worked in test, failed live" gap.
  • CheckAppService.TestAsync + POST /services/{s}/checks/{c}/test returning { outcome, message, latencyMs, dimensions, logs } without persisting a datapoint or firing an alert. Accepts candidate config so unsaved edits can be tested.

Frontend (apps/admin)

  • CodeEditor (CodeMirror 6) styled to match the shadcn Textarea, theme-aware, wired into FieldControl's Code case (replaces the textarea stub for every Code field).
  • Context-aware autocomplete for the piro:http API and the check() return shape (a lightweight CompletionSource, no TS language service).
  • ScriptTestPanel on the check detail page (Script only): runs the debug endpoint and shows outcome / message / dimensions / logs.

Testing

  • dotnet test passes (unit + integration)
  • pnpm exec tsc -b passes in affected frontend app(s) (apps/admin)
  • Manually verified the change end-to-end

188 unit tests green (20 new ScriptCheck tests: config guard, manifest, return mapping, SSRF-range classification, end-to-end script-drives-piro:http, disallowed-import). apps/admin tsc -b and pnpm build clean. Note: apps/web is untouched.

Screenshots

Checklist

Implements the Script check: the operator writes a small JavaScript check() that
drives its own HTTP through a piro:http module and returns a raw verdict
{ up, message?, dimensions? }. Reconciled to the RFC 0016 check SDK — an ordinary
ICheck whose ProbeAsync runs; the check reports raw state and never decides severity
(DEGRADED is the alert policy's call from the emitted dimensions).

Backend (Piro.Checks/Script):
- ScriptCheck : Check<ScriptCheckConfig> — builds a fresh Jint engine per probe,
  registers the piro:http module, invokes check(), maps the return to CheckProbeResult.
  Deny-by-default sandbox: no CLR interop, no filesystem, only piro:http resolves;
  whole-script wall-clock timeout, MaxStatements, LimitMemory.
- ScriptHttp — the piro:http module: GET-only, response-size capped, returns
  { statusCode, body, json, headers }.
- ScriptSsrfGuard — connect-time SSRF guard on a dedicated piro-script-http client:
  rejects loopback, RFC-1918, link-local/cloud-metadata, IPv6 ULA/link-local, validating
  the resolved IP (defeats DNS rebinding). Separate from the shared piro-http so existing
  checks are unaffected (retrofit to other clients is RFC 0010 phase 5, deferred).
- ScriptCheckConfig with a starter template default; adds CheckType.Script (string
  discriminator, no migration).

Debug run (Fase 3):
- ITestableCheck (opt-in SDK seam; ICheck.ProbeAsync stays pure) implemented by
  ScriptCheck.ProbeForTest, which captures console.log. Same engine/module as
  production — only the console binding differs.
- CheckAppService.TestAsync + POST /services/{s}/checks/{c}/test returning
  { outcome, message, latencyMs, dimensions, logs } without persisting or alerting;
  accepts candidate config so unsaved edits can be tested.

Frontend (apps/admin, Fase 4):
- CodeEditor (CodeMirror 6), styled to match the shadcn Textarea and theme-aware,
  wired into FieldControl's Code case (replaces the textarea stub for every Code field).
- Context-aware autocomplete for the piro:http API and the check() return shape
  (lightweight CompletionSource, no TS language service).
- ScriptTestPanel on the check detail page (Script only): runs the debug endpoint and
  shows outcome/message/dimensions/logs.

Tests: 20 ScriptCheck unit tests (config guard, manifest, return mapping, SSRF ranges,
end-to-end script-drives-http, disallowed import). 188 unit tests green; admin tsc -b clean.

Closes #39
@cl8dep cl8dep added enhancement New feature or request backend Backend / API work frontend Frontend / UI work implements-rfc Implements a previously approved RFC proposal labels Jul 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

RFC guard passed. Target RFC is accepted: 0010.

@cl8dep
cl8dep marked this pull request as ready for review July 24, 2026 16:12
@cl8dep cl8dep self-assigned this Jul 24, 2026
@cl8dep cl8dep added enhancement New feature or request backend Backend / API work frontend Frontend / UI work implements-rfc Implements a previously approved RFC proposal labels Jul 24, 2026
cl8dep added 2 commits July 24, 2026 13:23
The test endpoint now returns the mapped ServiceStatus (UP/DOWN/FAILURE) instead of the
raw CheckOutcome, so the panel renders it with the shared StatusPill and drops the
frontend OUTCOME_TO_STATUS table — the outcome→status mapping lives only in the backend
(the same mapping a scheduled run applies), not duplicated on the client.
…meric id

The rfc-implement-guard read existing comments with `gh pr view --json comments`, whose
`id` is a GraphQL node id (IC_...). PATCH repos/.../issues/comments/{id} expects the
numeric database id, so updating the guard's own comment 404'd and failed the check even
when the RFC check itself passed. Fetch comments via the REST issues/comments endpoint,
which returns the numeric id the PATCH needs.
@cl8dep
cl8dep merged commit 22021a5 into main Jul 24, 2026
4 of 7 checks passed
@cl8dep
cl8dep deleted the feat/script-check branch July 24, 2026 16:34
github-actions Bot added a commit that referenced this pull request Jul 24, 2026
cl8dep added a commit that referenced this pull request Jul 27, 2026
feat(checks): Script check type — sandboxed JavaScript (RFC 0010)
cl8dep pushed a commit that referenced this pull request Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Backend / API work enhancement New feature or request frontend Frontend / UI work implements-rfc Implements a previously approved RFC proposal

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Script check type — sandboxed JavaScript that returns a status verdict

1 participant