Skip to content

Add Wasm/WASI execution and reliable proxy operations - #156

Open
shedowe19 wants to merge 13 commits into
developfrom
codex/wasm-platform-and-reliability
Open

shedowe19 wants to merge 13 commits into
developfrom
codex/wasm-platform-and-reliability

Conversation

@shedowe19

@shedowe19 shedowe19 commented Oct 17, 2026 •

Copy link
Copy Markdown
Owner

ShieldPM now runs administrator-uploaded Proxy-Wasm HTTP filters and WASI Preview 1 applications directly on Proxy Hosts, with a separate manual command runner. The requested Host Workspace has been removed. The change also fixes the production startup and host-restoration failures reported during instance testing.

Execution modes

Mode User flow Behavior
Proxy-Wasm ABI 0.2.1 Proxy Host → Wasm Upload bytecode, select ordered filters and supply encrypted, write-only JSON configuration. Each module and the chain execute in real Nginx preflight before activation. Header/body callbacks share the native request context, including HTTP/2 and chunked bodies.
WASI HTTP application Proxy Host → Wasm → HTTP application Ordinary Preview 1 commands receive exact request bytes on stdin and return stdout as HTTP 200, or use http-json-v1 for explicit status, headers and binary responses.
WASI request middleware Proxy Host → Wasm → HTTP filter http-json-v1 receives method, URI, query, headers and base64 body; return checked header/body changes and continue to the upstream, or return an explicit local response.
Standalone WASI Preview 1 Settings → WASI Applications Upload and manage commands exporting memory and _start. Run with explicit arguments, environment and stdin; inspect stdout, stderr and the original unsigned WASI exit code. Stop, navigation or HTTP disconnect terminates the real process.

Standalone commands use the existing Node 26 built-in WASI adapter in a fixed disposable child process. The runtime allows at most two executions, 100–60,000 ms deadlines (10 seconds by default), 64 KiB inputs and 1 MiB combined normalized output. It passes no inherited environment or preopened directories. Input values and output are excluded from audit records. Upload and execution require settings:update; compilation-only inspection never instantiates uploaded code.

The private catalogs and immutable SHA-256 artifacts use bounded sizes, trusted directory selection, normalized paths, no-follow/single-inode reads, hash verification and serialized publication. Proxy-Wasm configuration and retained-worker references remain protected. Deletion removes catalog availability while preserving immutable bytes for accepted runs.

Unified host upload detects the actual bytecode contract and routes it to the appropriate catalog; standalone commands no longer hit the Proxy-Wasm ABI error there. The legacy runtime-specific endpoints remain strict and provide actionable guidance. Host WASI settings are encrypted and write-only, with stable-ID omission preservation and explicit {} reset. Existing host rights permit selecting administrator-approved code; uploading or independently running code remains administrative.

Per-request WASI uses a separate authenticated Unix listener, not an unauthenticated public API route. An immutable chain binds the approved digest, settings and filter token. Requests are bounded to 64 KiB body and 128 headers/32 KiB; arguments/environment retain their existing bounds. Guest framing/routing-header injection is rejected, HEAD/204/304 have no body, and disconnect terminates the actual child. Earlier Proxy-Wasm body callbacks finish before a later WASI handler can answer. A handler is last among request filters. The real preflight, accepted worker leases, rollback and catalog reference checks cover application entries too. A corrected invalid JSON draft now reliably re-enables the real host Save button.

Production runtime corrections

  • Remove the unsafe Nginx beautifier: it corrupted valid quoted JSON literals, turning $status} into $statu. Install rendered bytes unchanged, preserve custom directives and include generator-source hashes in the startup fingerprint so existing damaged files are regenerated without a version bump.
  • Recover valid manual and Docker hosts after a failed startup batch. A failed host remains explicit and prevents writing the success fingerprint, allowing retry on the next start.
  • Keep manual host writes limited to requested fields and preserve shared certificate/ACL records. Restore all five MySQL temporal columns, including populated git_last_sync and maintenance windows, without timezone/DST/dateStrings drift. Actual MariaDB reproduces error 1292 before the fix. The supplied production SQL log is truncated, so this is a verified compatible cause rather than an assertion about an unseen driver message.
  • Bind certificate ISO instants through mysql2's configured connection timezone; preserve other database/API contracts.
  • Retry exact monitor persistence conflicts in bounded fresh transactions, with one network probe and notifications only after commit.
  • Treat the implicit absent local Docker socket as optional while retaining warnings for explicitly configured endpoints and different transport failures.

Other implemented features

Weighted upstream pools, encrypted host revisions, incident history and durable notifications, real proxy caching independent of WAF selection, browser-session revocation and encrypted full-instance backup/restore remain included. The review's validator, domain-claim, Analytics, request-cancellation, scheduler, installer and accessibility improvements are retained. Host Workspace UI, routes, services, tags and profiles are removed; its historical migration stays compatible with already updated databases.

Host rollback follow-up (October 18)

A real MariaDB test now reproduces ER_CHECKREAD/1020 at the reported patchCapturedHost stack when a concurrent status writer commits after domain-claim reads. The supplied production excerpt still omits the native SQL error, so this reproduction does not identify its missing code conclusively.

  • Retry only the trusted database compensation in at most three fresh transactions, with 25/50 ms delays and exact MySQL/MariaDB code/errno matching for snapshot, deadlock and lock-timeout conflicts. Files, reloads and activation remain outside retry; permanent SQL errors are not replayed.
  • Preserve the original activation failure and any compensation failure privately. Emit one compact, bounded [Host rollback] diagnostic with safe identifiers, without SQL, snapshots or credentials; never report a failed compensation as restored.
  • Added full-production-migration MariaDB coverage for all 68 host columns, regular/onion domains, timestamps, file bytes/mode, one conflict, exhaustion after three conflicts and an actual permanent error 1054. All 31/31 MariaDB cases across seven files pass locally and in GitHub CI. Related revision/error/service regressions 85/85, backend types, formatting, TScanner (760 files) and secret scan pass.

Current follow-up commit: 1b5a976301ba537ddd06021c616418f96a3cf402.

Validation

  • GitHub Node 26 backend: 2,902 passed, 99 explicit environment/runtime skips, 278 passing files and 13 skipped files; startup lifecycle 12/12 and the HTTP-contract file 103/103 passed. Backend types passed.
  • Node 26 frontend: 1,058/1,058 passed in 228 files; types, all 13 locale catalogs and clean production build passed.
  • Genuine Chromium: 11/11 flows passed, zero skipped/flaky cases, including host WASI upload/selection, corrected JSON draft recovery, exact API serialization and Axe. Browser execution responses are mocked and do not substitute for real execution.
  • Prior isolated real Nginx evidence (unchanged runtime): 58/58 selected runtime cases passed, including 19 new generated-host WASI cases: HTTP/1.0, chunking, HTTP/2, all 256 byte values at the 64-KiB boundary, temporary body files, mixed Proxy-Wasm/WASI ordering, auth, binary responses, bodyless statuses and failed candidate preservation.
  • Real runner/HTTP contracts: 143/143 passed; private bridge adversarial cases cover token/path/hash/tamper defenses, immutable settings, actual guest execution and disconnect-triggered SIGKILL/recovery. All these tests are also included in the full backend suite.
  • Local executor prohibits Unix sockets and UID changes, so real HTTP proofs use the explicitly test-only loopback transport. The new image smoke enforces production Unix and checks seven genuine generated hosts as root and UID 1000 on both architectures. The final MariaDB 11.8.3 migration/runtime suite passed 31/31.
  • Changed-file Biome, TScanner (760 files, zero errors/advisories), CI spelling check, diff/credential scans and Docker-smoke orchestration tests (7/7) passed. No scanner/CodeQL rules were disabled.
  • Compressed frontend budget passed unchanged: 1,211,756 bytes total JavaScript, 106,550 bytes largest chunk and 16,461 bytes CSS.

All eight final GitHub workflows passed on 1b5a9763, including CodeQL with no new alerts, all three Quality jobs and both architecture image/native/LXC checks. Production Unix WASI execution passed as UID 0 and UID 1000 on AMD64 and ARM64, with seven generated hosts per run.

The local complete backend run had 2,900 passes, 100 explicit skips and one timeout in the unchanged WASI large-payload test under its original five-second budget. The unchanged HTTP-contract file passed 103/103 in isolation. The final exact-head GitHub Node 26 suite above passed all 2,902 cases without changing those budgets.

Runtime contract and documentation

Standalone support covers trusted administrator Preview 1 command modules. Node WASI is explicitly not a hostile-code security sandbox; per-memory and V8 heap limits do not cap total process RSS. Proxy-Wasm retains API7 0.7.0's existing header end-of-stream flag and generic body-rewriting limits. WASI request middleware can rewrite request bodies and handlers can produce their own responses; it does not implement asynchronous transformations of upstream responses, Preview 2/components, mounted directories or persistent application servers. These boundaries and the separate real/browser proofs are documented.

Host WASI integration and verification · October 18 runtime report · WASI applications · Proxy-Wasm filters

No application/dependency version changes, release, merge or production deployment.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-17T18:31:55.494826Z 95255e8 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Comment thread backend/internal/access-list.js Fixed
Comment thread backend/internal/access-list.js Fixed
Comment thread backend/internal/access-list.js Fixed
Comment thread backend/internal/host-traffic-policy.js Fixed
Comment thread backend/internal/host-traffic-policy.js Fixed
Comment thread backend/lib/instance-backup.js Fixed
Comment thread backend/routes/backups.js Fixed
Comment thread backend/routes/backups.js Fixed
Comment thread backend/routes/backups.js Fixed
Comment thread backend/test/internal/wireguard-async-status.spec.js Fixed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 95255e86c3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread backend/internal/host-workspace.js Outdated
Comment on lines +141 to +146
if (filters.monitor)
query.whereExists(
ProxyHost.knex()("proxy_host_monitor")
.select("id")
.where("host_id", ProxyHost.knex().ref("proxy_host.id"))
.where("state", filters.monitor),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Filter monitors by their effective state

When the workspace monitor filter is set to paused, this predicate searches the persisted proxy_host_monitor.state, but publicStatus() derives paused from a disabled proxy host or disabled monitor while leaving the stored state as its previous up, down, or unknown value. As a result, the Paused filter returns no matching hosts, while the Up/Down filters can incorrectly include paused hosts; the query needs to account for both host and monitor enablement.

Useful? React with 👍 / 👎.

Comment on lines +3 to +4
const revision = list.meta?.authorization_revision;
const suffix = typeof revision === "string" && /^[a-f0-9]{64}$/.test(revision) ? `.${revision}` : "";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Rebuild imported access-list artifacts

When a GitOps backup containing an access list is restored on another instance, its exported meta.authorization_revision is imported unchanged, so this helper selects /data/access/<id>.<revision>. GitOps imports the database rows directly and neither copies nor rebuilds that immutable authorization artifact, causing the final Nginx validation to fail for attached hosts and leaving the imported access-list metadata pointing at a nonexistent file. Rebuild the imported users/CA locally and persist the resulting revision before regenerating host configurations.

Useful? React with 👍 / 👎.

Comment thread backend/internal/nginx.js Outdated
Comment on lines +108 to +115
if (
internalNginx.getFileFriendlyHostType(host_type) === "proxy_host" &&
host.enabled &&
host.meta?.wasm_filters?.some((filter) => filter.enabled)
) {
const { prepareHost } = await import("./wasm.js");
host.wasm = await prepareHost(host);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mark Wasm hosts active after the reload

For every enabled Wasm host, prepareHost() writes a validated status before the production configuration is staged, but no activation path calls the newly added markHostActive() after Nginx reloads successfully. Consequently GET /nginx/wasm/hosts/:id and the Wasm tab can never report active; disabling all filters also leaves the previous validated or error status behind. Invoke the status transition only after the complete configuration batch is committed, passing null when the host no longer has an active chain.

Useful? React with 👍 / 👎.

Comment thread backend/internal/wasm.js
Comment on lines +212 to +213
const configurationPath = path.join(root(), "hosts", `${host.id}-${digest(contents)}.json`);
await immutableFile(configurationPath, contents);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove obsolete Wasm credential files after activation

When an API-key filter is rotated or removed, the decrypted key is embedded in contents and written to a new content-addressed host JSON file, while the previous /data/wasm/hosts/<id>-*.json is never deleted anywhere in the repository. Each rotation therefore leaves the old credential recoverable indefinitely, and deleting the proxy host leaves every historical key file behind. Retain the previous file only until Nginx activation or rollback completes, then remove unreferenced files for that host and clear them on host deletion.

Useful? React with 👍 / 👎.

Comment thread backend/internal/access-list.js Fixed
Comment thread backend/internal/access-list.js Fixed
Comment thread backend/test/internal/gitops-access-list-import.spec.js Fixed
Keep manual updates separate from loaded relation graphs, restore driver-aware timestamps, retry monitor transaction conflicts, and recover valid hosts after a failed startup batch. Treat an absent implicit Docker socket as optional while preserving discovery.
Add administrator-managed Proxy-Wasm uploads, encrypted per-host module configuration, four native HTTP callbacks and real execution preflight. Preserve custom module metadata across worker activation and remove the withdrawn workspace UI and API while retaining migration compatibility.
@shedowe19 shedowe19 changed the title Add Wasm policies, upstream pools and operations management Add custom Wasm modules and reliable proxy operations Oct 17, 2026
Comment thread backend/internal/wasm-modules.js Fixed
Comment thread backend/internal/wasm-modules.js Fixed
Comment thread backend/internal/wasm-modules.js Fixed
Comment thread backend/internal/wasm-modules.js Fixed
Comment thread backend/internal/wasm-modules.js Fixed
Comment thread backend/internal/wasm-modules.js Fixed
Comment thread backend/internal/wasm-modules.js Fixed
@shedowe19 shedowe19 changed the title Add custom Wasm modules and reliable proxy operations Add Wasm/WASI execution and reliable proxy operations Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants