[GH-ISSUE #4621] [Bug] Oversized WASM input is reported as an invalid argument #2402

Closed
opened 2026-10-17 21:04:11 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @coygeek on GitHub (Oct 17, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4621

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

inspect-wasm-artifact rejects a valid 8388609-byte WASM module with code: invalid_request, category: invalid_input, retryable: true, and issue reason invalid_value. Its message says the input is invalid and remediation says to correct arguments. The issue message does preserve the 8388608-byte allowance, but the response lacks structured file-size resource metadata. This reproduced in 3/3 attempts on each tested build with real WABT.

The otherwise equivalent valid module at exactly 8388608 bytes succeeds on both builds. Direct WABT validation accepts both modules. The size limit remains enforced; the defect concerns the typed refusal and recovery metadata.

Reproduction

  1. Obtain either tested build. For the release, install rea-agents at the version below in a disposable directory. For source, install locked dependencies and run npm run build:cached. Put real wasm-validate, wasm-objdump, and wasm2wat version 1.0.42 together on PATH.
  2. Save the following as reproduce.py in a disposable directory and run python3 reproduce.py /absolute/path/to/build/scripts/rea.mjs. Repeat with the other build. The script creates valid custom-section modules, uses a fresh HOME/config/cache/runtime for every CLI call, executes no WASM code, and removes its fixtures.
import json, os, pathlib, shutil, subprocess, sys, tempfile
entry = pathlib.Path(sys.argv[1]).resolve()
node = shutil.which('node')
wabt = pathlib.Path(shutil.which('wasm-validate')).parent
root = pathlib.Path(tempfile.mkdtemp(dir=pathlib.Path.cwd()))
def wasm(size):
    payload = size - 13
    value, encoded = payload, []
    while value >= 128:
        encoded.append((value & 127) | 128)
        value >>= 7
    result = b'\0asm\1\0\0\0\0' + bytes(encoded + [value])
    result += b'\3pad' + bytes(payload - 4)
    assert len(result) == size
    return result
counter = 0
def run(name):
    global counter
    counter += 1
    case = root / str(counter)
    case.mkdir()
    for part in ['home', 'runtime', 'config', 'cache']:
        (case / part).mkdir()
    env = {'PATH': os.environ['PATH'], 'HOME': str(case / 'home'),
           'TMPDIR': str(case / 'runtime'), 'XDG_RUNTIME_DIR': str(case / 'runtime'),
           'XDG_CONFIG_HOME': str(case / 'config'), 'XDG_CACHE_HOME': str(case / 'cache'),
           'LANG': 'C', 'REA_LOG_LEVEL': 'silent', 'REA_WABT_BIN_DIRECTORY': str(wabt)}
    r = subprocess.run([node, str(entry), 'inspect-wasm-artifact', str(root / name),
                        '--format', 'json'], env=env, capture_output=True, text=True, timeout=90)
    reply = json.loads(r.stdout)
    details = reply.get('details', {})
    summary = {k: reply[k] for k in ['code', 'category', 'retryable', 'remediation'] if k in reply}
    summary.update({k: details[k] for k in ['issues', 'resource', 'reported_limits'] if k in details})
    summary['validation'] = reply.get('normalized_result', {}).get('validation')
    print(name, 'exit=' + str(r.returncode), json.dumps(summary).replace(str(root), '<fixture>'))
    assert r.stderr == ''
    assert list((case / 'runtime').iterdir()) == []
try:
    for name, size in [('over.wasm', 8388609), ('exact.wasm', 8388608)]:
        (root / name).write_bytes(wasm(size))
        subprocess.run([str(wabt / 'wasm-validate'), '--enable-all', str(root / name)], check=True)
    (root / 'empty.wasm').write_bytes(b'\0asm\1\0\0\0')
    (root / 'malformed.wasm').write_bytes(b'not wasm')
    for attempt in range(3):
        run('over.wasm')
    for name in ['exact.wasm', 'empty.wasm', 'malformed.wasm']:
        run(name)
finally:
    shutil.rmtree(root)
  1. Observe the oversized attempts return invalid_request / invalid_input with reason invalid_value. The exact-limit and empty-module controls succeed with validation valid. The malformed-module control returns invalid_request with reason invalid_format.

Expected behavior

Keep rejecting files above the 8 MiB allowance, but report this measured size-admission refusal as resource_constraint, with resource file-size, the configured byte limit in structured details, and recovery appropriate to a smaller suitable input. Repeating an unchanged oversized request should not be presented as retryable.

The WASM guide promises fail-closed rejection above 8 MiB without explicitly naming the input-limit code. This expectation follows from the shared error model, which distinguishes measured resource constraints and bounded admission refusals from malformed input, includes file-size, and projects that error as resource_constraint. It is corroborated by the main build's SQLite and Go input-limit results below. The documented WASM producer-output budget is a separate limit; it is not the input classification contract.

Impact and scope

Clients that handle resource limits by code, category, or details.resource cannot recognize this refusal consistently without interpreting its message text. This is a bounded machine-readable outcome defect. No limit bypass, source corruption, or downstream automation incident was observed. The primary selected module through CLI was exercised; MCP and optional selections were not tested. Natural workload frequency is unknown.

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The deterministic size-admission failure loses resource classification and structured limits and advertises retryability despite an unchanged fixed allowance.
  • Workaround status: available
  • Workaround basis: Callers can read the explicit size-limit issue message and handle this refusal separately. A smaller suitable valid module can be inspected, as the exact-limit control demonstrates; truncating required module bytes is not a valid workaround.

REA version

Published npm rea-agents 6.5.0, and current main source commit 0c7b6d834af2d044b23196e0e522cc2468327a1e, freshly cloned and built locally. Both public CLI entrypoints report 6.5.0.

Runtime and platform

Node.js v26.12.0; Darwin arm64; real WABT 1.0.42 for all three tools. Source build used npm 11.16.0 and this supported Node runtime rather than the preferred development Node version.

Target and artifact context

Self-generated WASM v1 modules containing one custom section named pad with zero-filled payload, totaling exactly 8388608 or 8388609 bytes; an eight-byte empty module; and eight malformed bytes. Comparison inputs on main were an initially ordinary SQLite database sparsely extended to 268435457 bytes and a sparse ordinary binary of the same size. Those comparisons exercise size admission before format parsing, without claiming valid over-limit SQLite or Go metadata.

Interface

CLI

Earliest failure stage

Setup or provider admission

Relevant output or evidence

  • Expected source: docs/wasm-artifacts.md:48-49; src/domain/analysisErrorCore.ts:23-39; src/domain/analysisErrorProjection.ts:200,208; docs/sqlite-databases.md:108-114; and src/go/GoBinaryProvider.ts:79-87 in the tested main tree.
  • Failure source: The public CLI results below; source breadcrumbs are src/artifacts/readStableArtifact.ts:65-70, src/wasm/WabtArtifactProvider.ts:232-239, src/wasm/WabtFailures.ts:165-170, and the allowance at src/wasm/WabtRelease.ts:8.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Observed output, with only the generated selected-file prefix replaced by <fixture>:

Both builds, inspect-wasm-artifact over.wasm, 3/3 each:
  exit 1; code invalid_request; category invalid_input; retryable true
  message: Analysis input is invalid. Check the arguments and try again.
  remediation.action: Correct the listed arguments and retry.
  issue: path=["path"], reason=invalid_value
  issue message: Selected artifact exceeds 8388608 bytes: <fixture>/over.wasm
Both builds, inspect-wasm-artifact exact.wasm and empty.wasm:
  exit 0; validation valid
Both builds, inspect-wasm-artifact malformed.wasm:
  exit 1; code invalid_request; category invalid_input; reason invalid_format
Main build, inspect-sqlite-database over.sqlite:
  exit 1; code/category resource_constraint; retryable false; resource file-size
  reported_limits: {"sqlite_snapshot_input_bytes":268435456,"observed_sqlite_snapshot_input_bytes":"268435457"}
Main build, inspect-go-binary over.bin:
  exit 1; code/category resource_constraint; retryable false; resource file-size
  reported_limits: {"maximum_input_bytes":268435456}

Both generated large modules passed wasm-validate --enable-all, exit 0. All REA CLI stderr was empty and each per-call runtime directory was empty afterward. The exact embedded reproduction was replayed against both builds. The comparison commands used the same isolated environment and --format json; their size-admission cases ran once each on main.

Typed outcome or recovery expected

For the same valid 8388609-byte module, keep the refusal but return resource_constraint / resource_constraint, resource file-size, the 8388608-byte allowance in structured details, and deterministic size-refusal recovery. Keep the exact-limit valid module successful and the malformed small module's distinct invalid-format outcome. These are proposed restoration criteria; no fix was applied.

Analysis or suspected cause

Source-derived reasoning: the stable reader produces an artifact limit failure with boundary file-size. After WABT admission, the WASM translator handles integrity and permissions specially, then maps other artifact failures to AnalysisInputError with reason invalid_value. This explains the observed result without malformed WASM or simulated producer output.

Additional context

Related #4201 concerns which optional argument is blamed; this report uses the primary selected module and concerns failure kind. #4619 concerns firmware growth during copying, and #4127 concerns SQLite's separate snapshot budget. PR #2117 handles WABT producer output, while PR #2694 proposes increasing that output allowance. Neither covers an already oversized selected WASM input. Open and closed issues and PRs were searched; no matching report or last-known-good revision was found.

Originally created by @coygeek on GitHub (Oct 17, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4621 ### Before submitting - [X] I searched the open and closed issues for a related report. - [X] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior `inspect-wasm-artifact` rejects a valid 8388609-byte WASM module with `code: invalid_request`, `category: invalid_input`, `retryable: true`, and issue reason `invalid_value`. Its message says the input is invalid and remediation says to correct arguments. The issue message does preserve the 8388608-byte allowance, but the response lacks structured file-size resource metadata. This reproduced in 3/3 attempts on each tested build with real WABT. The otherwise equivalent valid module at exactly 8388608 bytes succeeds on both builds. Direct WABT validation accepts both modules. The size limit remains enforced; the defect concerns the typed refusal and recovery metadata. ### Reproduction 1. Obtain either tested build. For the release, install `rea-agents` at the version below in a disposable directory. For source, install locked dependencies and run `npm run build:cached`. Put real `wasm-validate`, `wasm-objdump`, and `wasm2wat` version 1.0.42 together on PATH. 2. Save the following as `reproduce.py` in a disposable directory and run `python3 reproduce.py /absolute/path/to/build/scripts/rea.mjs`. Repeat with the other build. The script creates valid custom-section modules, uses a fresh HOME/config/cache/runtime for every CLI call, executes no WASM code, and removes its fixtures. ```python import json, os, pathlib, shutil, subprocess, sys, tempfile entry = pathlib.Path(sys.argv[1]).resolve() node = shutil.which('node') wabt = pathlib.Path(shutil.which('wasm-validate')).parent root = pathlib.Path(tempfile.mkdtemp(dir=pathlib.Path.cwd())) def wasm(size): payload = size - 13 value, encoded = payload, [] while value >= 128: encoded.append((value & 127) | 128) value >>= 7 result = b'\0asm\1\0\0\0\0' + bytes(encoded + [value]) result += b'\3pad' + bytes(payload - 4) assert len(result) == size return result counter = 0 def run(name): global counter counter += 1 case = root / str(counter) case.mkdir() for part in ['home', 'runtime', 'config', 'cache']: (case / part).mkdir() env = {'PATH': os.environ['PATH'], 'HOME': str(case / 'home'), 'TMPDIR': str(case / 'runtime'), 'XDG_RUNTIME_DIR': str(case / 'runtime'), 'XDG_CONFIG_HOME': str(case / 'config'), 'XDG_CACHE_HOME': str(case / 'cache'), 'LANG': 'C', 'REA_LOG_LEVEL': 'silent', 'REA_WABT_BIN_DIRECTORY': str(wabt)} r = subprocess.run([node, str(entry), 'inspect-wasm-artifact', str(root / name), '--format', 'json'], env=env, capture_output=True, text=True, timeout=90) reply = json.loads(r.stdout) details = reply.get('details', {}) summary = {k: reply[k] for k in ['code', 'category', 'retryable', 'remediation'] if k in reply} summary.update({k: details[k] for k in ['issues', 'resource', 'reported_limits'] if k in details}) summary['validation'] = reply.get('normalized_result', {}).get('validation') print(name, 'exit=' + str(r.returncode), json.dumps(summary).replace(str(root), '<fixture>')) assert r.stderr == '' assert list((case / 'runtime').iterdir()) == [] try: for name, size in [('over.wasm', 8388609), ('exact.wasm', 8388608)]: (root / name).write_bytes(wasm(size)) subprocess.run([str(wabt / 'wasm-validate'), '--enable-all', str(root / name)], check=True) (root / 'empty.wasm').write_bytes(b'\0asm\1\0\0\0') (root / 'malformed.wasm').write_bytes(b'not wasm') for attempt in range(3): run('over.wasm') for name in ['exact.wasm', 'empty.wasm', 'malformed.wasm']: run(name) finally: shutil.rmtree(root) ``` 3. Observe the oversized attempts return `invalid_request` / `invalid_input` with reason `invalid_value`. The exact-limit and empty-module controls succeed with validation `valid`. The malformed-module control returns `invalid_request` with reason `invalid_format`. ### Expected behavior Keep rejecting files above the 8 MiB allowance, but report this measured size-admission refusal as `resource_constraint`, with resource `file-size`, the configured byte limit in structured details, and recovery appropriate to a smaller suitable input. Repeating an unchanged oversized request should not be presented as retryable. The WASM guide promises fail-closed rejection above 8 MiB without explicitly naming the input-limit code. This expectation follows from the shared error model, which distinguishes measured resource constraints and bounded admission refusals from malformed input, includes `file-size`, and projects that error as `resource_constraint`. It is corroborated by the main build's SQLite and Go input-limit results below. The documented WASM producer-output budget is a separate limit; it is not the input classification contract. ### Impact and scope Clients that handle resource limits by `code`, `category`, or `details.resource` cannot recognize this refusal consistently without interpreting its message text. This is a bounded machine-readable outcome defect. No limit bypass, source corruption, or downstream automation incident was observed. The primary selected module through CLI was exercised; MCP and optional selections were not tested. Natural workload frequency is unknown. - Impact level: P2 - Assessment status: supported - Impact basis: The deterministic size-admission failure loses resource classification and structured limits and advertises retryability despite an unchanged fixed allowance. - Workaround status: available - Workaround basis: Callers can read the explicit size-limit issue message and handle this refusal separately. A smaller suitable valid module can be inspected, as the exact-limit control demonstrates; truncating required module bytes is not a valid workaround. ### REA version Published npm `rea-agents` 6.5.0, and current main source commit `0c7b6d834af2d044b23196e0e522cc2468327a1e`, freshly cloned and built locally. Both public CLI entrypoints report `6.5.0`. ### Runtime and platform Node.js v26.12.0; Darwin arm64; real WABT 1.0.42 for all three tools. Source build used npm 11.16.0 and this supported Node runtime rather than the preferred development Node version. ### Target and artifact context Self-generated WASM v1 modules containing one custom section named `pad` with zero-filled payload, totaling exactly 8388608 or 8388609 bytes; an eight-byte empty module; and eight malformed bytes. Comparison inputs on main were an initially ordinary SQLite database sparsely extended to 268435457 bytes and a sparse ordinary binary of the same size. Those comparisons exercise size admission before format parsing, without claiming valid over-limit SQLite or Go metadata. ### Interface CLI ### Earliest failure stage Setup or provider admission ### Relevant output or evidence - Expected source: `docs/wasm-artifacts.md:48-49`; `src/domain/analysisErrorCore.ts:23-39`; `src/domain/analysisErrorProjection.ts:200,208`; `docs/sqlite-databases.md:108-114`; and `src/go/GoBinaryProvider.ts:79-87` in the tested main tree. - Failure source: The public CLI results below; source breadcrumbs are `src/artifacts/readStableArtifact.ts:65-70`, `src/wasm/WabtArtifactProvider.ts:232-239`, `src/wasm/WabtFailures.ts:165-170`, and the allowance at `src/wasm/WabtRelease.ts:8`. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete Observed output, with only the generated selected-file prefix replaced by `<fixture>`: ```text Both builds, inspect-wasm-artifact over.wasm, 3/3 each: exit 1; code invalid_request; category invalid_input; retryable true message: Analysis input is invalid. Check the arguments and try again. remediation.action: Correct the listed arguments and retry. issue: path=["path"], reason=invalid_value issue message: Selected artifact exceeds 8388608 bytes: <fixture>/over.wasm Both builds, inspect-wasm-artifact exact.wasm and empty.wasm: exit 0; validation valid Both builds, inspect-wasm-artifact malformed.wasm: exit 1; code invalid_request; category invalid_input; reason invalid_format Main build, inspect-sqlite-database over.sqlite: exit 1; code/category resource_constraint; retryable false; resource file-size reported_limits: {"sqlite_snapshot_input_bytes":268435456,"observed_sqlite_snapshot_input_bytes":"268435457"} Main build, inspect-go-binary over.bin: exit 1; code/category resource_constraint; retryable false; resource file-size reported_limits: {"maximum_input_bytes":268435456} ``` Both generated large modules passed `wasm-validate --enable-all`, exit 0. All REA CLI stderr was empty and each per-call runtime directory was empty afterward. The exact embedded reproduction was replayed against both builds. The comparison commands used the same isolated environment and `--format json`; their size-admission cases ran once each on main. ### Typed outcome or recovery expected For the same valid 8388609-byte module, keep the refusal but return `resource_constraint` / `resource_constraint`, resource `file-size`, the 8388608-byte allowance in structured details, and deterministic size-refusal recovery. Keep the exact-limit valid module successful and the malformed small module's distinct invalid-format outcome. These are proposed restoration criteria; no fix was applied. ### Analysis or suspected cause Source-derived reasoning: the stable reader produces an artifact `limit` failure with boundary `file-size`. After WABT admission, the WASM translator handles integrity and permissions specially, then maps other artifact failures to `AnalysisInputError` with reason `invalid_value`. This explains the observed result without malformed WASM or simulated producer output. ### Additional context Related [#4201](https://github.com/morluto/rea/issues/4201) concerns which optional argument is blamed; this report uses the primary selected module and concerns failure kind. [#4619](https://github.com/morluto/rea/issues/4619) concerns firmware growth during copying, and [#4127](https://github.com/morluto/rea/issues/4127) concerns SQLite's separate snapshot budget. [PR #2117](https://github.com/morluto/rea/pull/2117) handles WABT producer output, while [PR #2694](https://github.com/morluto/rea/pull/2694) proposes increasing that output allowance. Neither covers an already oversized selected WASM input. Open and closed issues and PRs were searched; no matching report or last-known-good revision was found.
Author
Owner

@morluto commented on GitHub (Oct 17, 2026):

Addressed in 8451d2839. The 8 MiB allowance remains enforced; this changes the typed refusal and recovery.

The producing reader's file-size boundary stays distinct from configuration admission and producer-output limits. WABT artifact input-size failures now project as resource_constraint, resource file-size, retryable: false, with reported_limits.maximum_input_bytes. Go retains that classification and now supplies recovery for its fixed selected-input allowance. Generic evidence-write recovery is unchanged. Independent cleanup failure remains visible rather than being hidden.

Validation: 66 focused tests across four files passed, including public CLI sparse oversized-input refusals, Go provider controls, WABT configuration/cleanup controls, and shared error projections. Docs checks plus five conformance tests passed. The immediate-parent compiled CLI comparison independently reproduced WABT's old invalid-input response and Go's old evidence-reply recovery. WABT commands were version-only stubs; the sparse inputs were refused before content parsing. No real WABT, valid over-limit WASM, physical/native target behavior, or full CI result is claimed in this local validation.

Bounded completed CLI comparison (two alternating warm repeats/build plus separate first processes): Go baseline/candidate elapsed 0.35167/0.34207 s, CPU 0.52354/0.52181 s, sampled process-family RSS 336994304/342949888 bytes, reply tokens 152/150; WASM elapsed 0.80142/0.81300 s, CPU 1.08851/1.07869 s, RSS 519667712/518176768 bytes, tokens 104/152. Raw tokens use cl100k_base. Reply changes are intentional; no speed/token improvement is established. True cold, whole-agent/schema/request/retry and real-producer costs remain unmeasured. Heavy Qodo tests remained stopped.

<!-- gh-comment-id:6227532287 --> @morluto commented on GitHub (Oct 17, 2026): Addressed in `8451d2839`. The 8 MiB allowance remains enforced; this changes the typed refusal and recovery. The producing reader's file-size boundary stays distinct from configuration admission and producer-output limits. WABT artifact input-size failures now project as `resource_constraint`, resource `file-size`, `retryable: false`, with `reported_limits.maximum_input_bytes`. Go retains that classification and now supplies recovery for its fixed selected-input allowance. Generic evidence-write recovery is unchanged. Independent cleanup failure remains visible rather than being hidden. Validation: 66 focused tests across four files passed, including public CLI sparse oversized-input refusals, Go provider controls, WABT configuration/cleanup controls, and shared error projections. Docs checks plus five conformance tests passed. The immediate-parent compiled CLI comparison independently reproduced WABT's old invalid-input response and Go's old evidence-reply recovery. WABT commands were version-only stubs; the sparse inputs were refused before content parsing. No real WABT, valid over-limit WASM, physical/native target behavior, or full CI result is claimed in this local validation. Bounded completed CLI comparison (two alternating warm repeats/build plus separate first processes): Go baseline/candidate elapsed 0.35167/0.34207 s, CPU 0.52354/0.52181 s, sampled process-family RSS 336994304/342949888 bytes, reply tokens 152/150; WASM elapsed 0.80142/0.81300 s, CPU 1.08851/1.07869 s, RSS 519667712/518176768 bytes, tokens 104/152. Raw tokens use cl100k_base. Reply changes are intentional; no speed/token improvement is established. True cold, whole-agent/schema/request/retry and real-producer costs remain unmeasured. Heavy Qodo tests remained stopped.
Author
Owner

@coygeek commented on GitHub (Oct 18, 2026):

Closing as addressed on main by 8451d2839. WABT input-size admission now returns a nonretryable file-size resource constraint with maximum_input_bytes and suitable-input recovery, while preserving the existing input allowance. I verified the committed source and regression coverage; I have not rerun the runtime reproduction.

<!-- gh-comment-id:6244115419 --> @coygeek commented on GitHub (Oct 18, 2026): Closing as addressed on `main` by [8451d2839](https://github.com/morluto/rea/commit/8451d283920e86936576fdf67615807d1b4ca4a9). WABT input-size admission now returns a nonretryable file-size resource constraint with maximum_input_bytes and suitable-input recovery, while preserving the existing input allowance. I verified the committed source and regression coverage; I have not rerun the runtime reproduction.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#2402
No description provided.