feat(checks): Script check type — sandboxed JavaScript (RFC 0010) - #219
Merged
Conversation
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
Contributor
|
RFC guard passed. Target RFC is accepted: 0010. |
cl8dep
marked this pull request as ready for review
July 24, 2026 16:12
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.
6 tasks
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the Script check type (RFC 0010): the operator writes a small sandboxed-JavaScript
check()that drives its own HTTP through apiro:httpmodule and returns a raw verdict{ up, message?, dimensions? }. It is an ordinary RFC 0016ICheckwhoseProbeAsyncruns 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 numericdimensionsthe script emits (the same pattern the HTTP check uses withBodyRuleFailures), 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:httpresolves) 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 thepiro:httpmodule, invokes the exportedcheck(), and maps the return toCheckProbeResult. Deny-by-default sandbox;TimeoutInterval/MaxStatements/LimitMemory; a disallowedimport(e.g.node:fs) is a checkError, not a Down.ScriptHttp— thepiro:httpmodule: GET-only, response-size capped, returns{ statusCode, body, json, headers }.ScriptSsrfGuard— connect-time SSRF guard on a dedicatedpiro-script-httpclient: rejects loopback, RFC-1918, link-local / cloud-metadata, and IPv6 ULA/link-local, validating the resolved IP (defeats DNS rebinding). Separate from the sharedpiro-httpso existing checks are unaffected; retrofitting the guard to the other clients is RFC 0010 phase 5 (deferred).ScriptCheckConfigwith a starter-template default;CheckType.Scriptadded (string discriminator, no migration).Debug run
ITestableCheck— an opt-in SDK seam (ICheck.ProbeAsyncstays pure) implemented byScriptCheck.ProbeForTest, which capturesconsole.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}/testreturning{ 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 intoFieldControl'sCodecase (replaces the textarea stub for everyCodefield).piro:httpAPI and thecheck()return shape (a lightweightCompletionSource, no TS language service).ScriptTestPanelon the check detail page (Script only): runs the debug endpoint and shows outcome / message / dimensions / logs.Testing
dotnet testpasses (unit + integration)pnpm exec tsc -bpasses in affected frontend app(s) (apps/admin)188 unit tests green (20 new
ScriptChecktests: config guard, manifest, return mapping, SSRF-range classification, end-to-end script-drives-piro:http, disallowed-import).apps/admintsc -bandpnpm buildclean. Note:apps/webis untouched.Screenshots
Checklist
.env/appsettings.*.jsonvalues committed