Skip to content

fix(wasi): reject dynamic Net URL at compile time (symmetry with Fs)#20

Merged
nelsonduarte merged 1 commit into
mainfrom
fix/wasi-net-dynamic-url-reject
Jun 29, 2026
Merged

fix(wasi): reject dynamic Net URL at compile time (symmetry with Fs)#20
nelsonduarte merged 1 commit into
mainfrom
fix/wasi-net-dynamic-url-reject

Conversation

@nelsonduarte

Copy link
Copy Markdown
Owner

Problem

A security/usability ASYMMETRY in the experimental --wasi mode, found in the pilot:

  • A dynamic (non-literal) Fs path is refused at COMPILE time (clear WasmEmissionError in _validate_wasi_caps).
  • A dynamic Net URL (e.g. interpolated "...?q=${name}") was NOT refused at compile time. The call site instead emitted a RUNTIME fail-closed (Err(IoError) without touching the network). A program with an Err(_) -> () arm swallowed that error silently and degraded its output with no warning (in the pilot, capa_governance_pack returned "no CVE matches" instead of 4, because the URL was interpolated).

We want SYMMETRY: a dynamic Net URL should also be refused at compile time, making the problem visible to the programmer instead of a silent degradation.

Fix

In _validate_wasi_caps (capa/ir/_emit_wasm/_wasi.py), right after the existing Fs dynamic-path refusal, add the symmetric Net refusal: when a request-building Net op (get/post) is used AND the NetCeiling is NOT closed (some dynamic URL reaches a request op), raise a WasmEmissionError at compile time with a clear message analogous to the Fs one, using the NetCeiling.closed flag (exact mirror of the Fs logic).

Coverage: the net ceiling is computed before _validate_wasi_caps runs and under the same get/post-used condition, and closed reflects EVERY get/post call site across the whole inlined module. So every dynamic-URL program is refused at compile time; no dynamic URL can still reach runtime unrefused.

The runtime fail-closed in the call-site emitter (_emit_wasi_net_get_call / _emit_wasi_net_post_call) is RETAINED as defence-in-depth.

Env is intentionally NOT aligned: it stays at Level 2 inherit_env on a dynamic key.

Tests

  • Updated the two tests that previously asserted the runtime fail-closed (TestWasiNetCeiling / TestWasiNetPostCeiling) to assert the compile-time rejection.
  • Added TestWasiNetDynamicUrlRejections: interpolated URL, param URL, let-bound literal, post param URL, mixed literal+dynamic, plus a literal-still-compiles positive control.
  • Existing literal-URL Net tests (TestWasiNetGet/Post, attenuation) stay green; Fs behaviour unchanged.

Full suite: Ran 3437 tests ... OK (skipped=18).

Behaviour change

A --wasi program with a dynamic URL that previously "ran" (silent fail-closed) now does NOT compile under --wasi (visibility > silent degradation). Make the URL a string literal or use the default capa:host backend (drop --wasi). capa_governance_pack (interpolated URL) will be refused under --wasi until its URL is a literal; correct, handled in the program-update phase / when const-prop lands.

In --wasi mode a dynamic (non-literal) URL passed to Net.get/post was
not refused at compile time. Instead the call site emitted a runtime
fail-closed (Err(IoError) without touching the network), which an
Err(_) -> () arm could swallow silently, degrading output with no
warning (observed in the capa_governance_pack pilot, where an
interpolated URL returned "no CVE matches" instead of 4).

Fs already refuses a dynamic path at compile time in _validate_wasi_caps
when its preopen ceiling is not closed. This adds the symmetric Net rule:
when Net.get/post is used and the NetCeiling is not closed (some dynamic
URL reaches a request op), raise a clear WasmEmissionError at compile
time, mirroring the Fs message and using the NetCeiling.closed flag.

The net ceiling is computed before _validate_wasi_caps runs and whenever
get/post is used, so every dynamic URL is covered by the compile-time
refusal; no dynamic URL can still reach runtime unrefused. The runtime
fail-closed in the call-site emitter is kept as defence-in-depth.

Env is intentionally not aligned: it stays at Level 2 inherit_env on a
dynamic key.

Tests: updated the two previously-runtime-fail-closed tests to assert
compile-time rejection, and added TestWasiNetDynamicUrlRejections
(interpolated URL, param URL, let-bound literal, post param, mixed
literal+dynamic, plus a literal-still-compiles control). Full suite:
3437 tests OK (18 skipped).
@nelsonduarte nelsonduarte merged commit bd81c4c into main Jun 29, 2026
13 checks passed
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.

1 participant