Files
f0d695ec97
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Failing after 7s
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Failing after 6s
QRun-IO/ci-policy: native / native-result (push) Successful in 6s
chore: preserve paused QBit work and document remaining issues
Save the current implementation and README, status, and known-issue documentation at the user-requested stop. No completed release or deployment is claimed.

Co-Authored-By: Claude <noreply@anthropic.com>

Co-Authored-By: GPT-6 Codex <noreply@openai.com>
2026-11-04 10:45:28 -06:00
..

Isolated TypeScript worker

This directory implements strict TypeScript compilation, an ESM execution worker, and a host transport that creates one disposable gVisor container per compilation or execution. It is not a qualified production sandbox yet. See QUALIFICATION.md before enabling untrusted projects.

The Java QBit owns authentication, record authorization, published artifact signatures, capability grants, quotas, hook semantics, and audit persistence. This worker owns compilation and isolated execution. There are no Java QQQ objects, application tokens, database credentials, network connections, or application filesystem mounts inside the worker.

Build and verification

Use Node 24.22.2 and npm 11.19.1:

npm ci --ignore-scripts
npm run lint
npm test

TypeScript 6.0.3 and all development dependencies are pinned in package-lock.json. No user project packages, lifecycle scripts, compiler configuration, plugins, or build hooks are installed or loaded. The Dockerfile requires separate operator-supplied NODE_BUILD_IMAGE and NODE_RUNTIME_IMAGE arguments. Release tooling must verify the approved immutable Node 24.22.2 Trixie builder and Distroless nonroot runtime digests. Neither stage has a default image tag. The final worker image must also be supplied by immutable digest and already exist on the Docker host (--pull=never). The current local candidate and verification evidence are in ../../qbit-documents/artifacts/worker-image-20261031-0240/README.md. Historical 0020 and 0059 candidates remain immutable evidence. None is qualified for hostile execution.

Host integration

node dist/coordinator-cli.js reads one JSON line on stdin, emits any broker requests as JSON lines on stdout, reads their JSON-line answers from stdin, and emits one final response. It exits after that invocation. stderr contains only a generic failure message. The host process never imports or evaluates the submitted compiler output. src/entrypoint.ts is the separate container entry point; its environment marker is a misuse guard, not security isolation.

The deployment supplies these server-owned environment values:

Variable Meaning
QQQ_WORKER_IMAGE Approved final worker image reference ending in @sha256:<64 hex digits>
QQQ_WORKER_QUALIFICATION_ID Identifier of external qualification evidence for this deployment
QQQ_REQUIRED_QUALIFICATION_ID Independently configured identifier required by the application
QQQ_WORKER_OWNER_ID Stable unique deployment identity for container ownership labels
QQQ_WORKER_STATE_DIRECTORY Existing absolute directory owned exclusively by the coordinator/reaper account, on durable local POSIX storage supporting directory fsync

Missing values, mismatched identifiers, non-Linux Docker, or missing runsc reject the invocation. The identifier comparison checks configuration; it cannot establish that the operator actually performed qualification. A runtime named runsc alone does not prove its binary/configuration is authentic. The reviewed deployment must control that configuration and evidence.

The Java WorkerProvider adapter must start this CLI with fixed executable/arguments/environment, bound its overall lifetime and protocol buffers, call BrokerChannel synchronously on the initiating authenticated QQQ thread, and send its reply to stdin. It must never expose the Docker socket to scripts. Node's broker promise remains asynchronous inside the worker. The sibling Java QBit now supplies CoordinatorWorkerProvider implementing this bridge; its transport tests use trusted fixed subprocess fixtures. This directory does not register itself in QQQ, and neither test suite constitutes sandbox qualification.

Protocol version 1

Every request has protocol: "qbit-scripting-worker/1", type, and requestId. Frames are newline-delimited UTF-8 JSON objects. Input is bounded at 4 MiB before parsing; nesting is bounded. No arbitrary command arguments cross the protocol.

Compile requests contain strict: true, toolchainId: "typescript-6.0.3-node-24", entryPoint, files: [{path, content}], and sourceSha256. The canonical source hash sorts ASCII paths, then hashes each UTF-8 path and content with two unsigned big-endian 32-bit length prefixes. Sources are limited to 200 files, 2 MiB aggregate UTF-8, 262,144 characters per file, and relative .ts paths. The compiler uses server-owned strict options with noEmitOnError, only shipped ES2022 libraries, and approved relative static imports or import type from @qqq/sdk. It emits no artifact on diagnostics.

The successful compileResult contains success, strict, toolchainId, bundle, bundleSha256, and sourceMap. The bundle is an opaque deterministic JSON module archive (format: "qqq-esm/1"), not a concatenated JavaScript program. It contains entryPoint and files with relative .js paths. Source maps are returned separately, including original sources, and must receive the same access protection as the source. Java must verify and sign the resulting immutable published artifact. The worker SHA-256 is integrity metadata, not proof of trusted origin.

Execute requests contain the published bundle, artifactSha256, source entryPoint, event, payload, capabilityToken, and integer limits. The worker verifies the bundle hash, writes modules to its disposable /tmp filesystem, and invokes main({event, payload}, qqq). Successful responses have type: "executeResult", outcome: "OK", result, and informational usage; failures expose no user stack traces. Worker results and usage are untrusted and require Java validation.

For record hooks, Java validates ALLOW, PATCH, or REJECT. For published context scripts, event is CONTEXT, payload is {context: <bounded JSON object>}, and the script's result is an arbitrary bounded JSON object, without hook coercion. Example:

import type { Context, Qqq } from "@qqq/sdk";

export async function main(context: Context, qqq: Qqq)
{
   const products = await qqq.query("product", ["id", "name"], [], 20);
   return { record: context.payload.context, products };
}

qqq.query(table, fields, criteria, limit) emits a broker frame with operation: "query", request ID, token, and arguments. The host checks token, operation, and call count before forwarding. Java separately authorizes table, fields, criteria, record scope, tenant, and result budgets using its server-created grant. The reply has type: "brokerResult", protocol, ok, and rows; denial causes script failure. Only one broker call can be outstanding through the SDK. This SDK intentionally has no generic URL fetch, arbitrary QQQ operation, or mutation method.

Bounds and termination

Compilation has a 10-second attached execution wall deadline, 5 CPU seconds, 256 MiB container memory, 32 PIDs, 64 file descriptors, and 4 MiB aggregate output. Execution accepts wall deadlines up to 10 seconds, whole-second CPU budgets from 1 to 5 seconds, memory from 64 to 256 MiB, output up to 128 KiB, and up to 50 broker calls. Unsupported budgets are rejected instead of rounded upward. Output counts stdout and stderr, including broker protocol traffic. Broker replies are at most 64 KiB per frame. These are initial bounds, not measured throughput or a performance SLA.

The launcher sets runsc, no network, read-only root filesystem, nonroot UID/GID 65532, all capabilities dropped, no new privileges, no IPC namespace sharing, no ports or mounts, bounded memory/swap/CPU/PIDs, and a 64 MiB noexec/nosuid/nodev /tmp. Cgroup CPU rate and whole-second per-process RLIMIT_CPU are additional constraints, not a proven aggregate CPU accounting implementation. In particular, child processes can affect total CPU usage; qualification must validate the deployment's aggregate resource policy.

An external host timer, AbortSignal, output limit, or malformed protocol terminates the attached client. Cleanup inspects the exact prepared container name, verifies deployment and attempt labels, then uses docker rm --force with its immutable 64-character container ID. Removal failure prevents success and must alert operations. Docker info/create/inspect/remove have separate 10-second timeouts, so total API latency may exceed the script's attached wall limit.

Before creation, the launcher durably writes attempt evidence and a 60-second lease. node dist/reaper-cli.js recovers expired leases after coordinator death, including a crash between Docker creation and receipt of its ID. It never enumerates or removes unrelated Docker containers. Successful removal/absence is durably recorded before deleting the active lease; failed cleanup remains retryable. Evidence contains attempt IDs and lifecycle state, not source, records, or capability tokens. The evidence directory is append-only by convention and must be protected/retained by the deployment; this journal does not replace the Java QBit's user-visible audit.

Deploy the independent reaper using the supplied service/timer examples in deploy. Its live crash-recovery and durable-storage behavior still require qualification. Windows synthetic tests inject the directory-sync operation only; production never skips an unsupported fsync and fails closed. No host-crash cleanup guarantee is claimed while the reaper is not deployed or the Docker host is unavailable.

Strict TypeScript is a correctness feature. Native module execution deliberately treats the entire worker process as compromised by hostile script code. Type declarations, import restrictions, object freezing, environment markers, and worker-generated telemetry do not provide hostile-code containment. The OS sandbox, trusted host validation, and independently qualified deployment do.

Node security baseline (2026-10-31)

The historical 24.22.1 correction followed the official security release, which fixes the bundled Brotli decoder and updates built-in Undici to7.30.1. The original Node24.22.0 binary contained built-in Undici7.29.1, while the image scanner separately found npm's bundled Undici6.28.0 under /usr/local/lib/node_modules/npm/node_modules/undici; removing npm does not patch Node itself. Current exact host coordinator, compiler and container entry checks require Node24.22.2, whose official release includes V8 backports and a stream async-context correction. No global runtime installation or existing image replacement is implied.

Current review candidate: ../../qbit-documents/artifacts/worker-image-20261031-0240 (Node24.22.2, Distroless runtime,17 trusted tests plus actual final-image compilation passed). The runtime uses the absolute /nodejs/bin/node entrypoint and retains the complete TypeScript library and license files. Local scan reports32 OS findings, none high/critical, and no Node-package findings; this is not a blanket security or license clearance. The base index signature verifies. Its legacy SPDX attestation also verifies with unmodified patched Cosign2.6.5, including wrong-signer/digest refusal controls; Cosign3.1.4's earlier predicate failures remain preserved. See the verification follow-up. Hostile runtime qualification remains false.