[GH-ISSUE #3933] [Bug] Direct JavaScript string literals bypass the semantic string-byte limit #1541

Closed
opened 2026-10-16 21:06:53 +00:00 by bsakaguchi · 1 comment
Owner

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

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

Analyzing a local application with a 200000-character string literal retains the whole normalized value and reports complete application graph coverage without the documented string-byte resource limit. A template literal without substitutions behaves the same way. Concatenating two 100000-character strings produces the same value but correctly returns a resource-limit unknown and partial application graph coverage. This makes the internal semantic allocation safeguard depend on how the source expresses the string.

I observed all three commands exit 0 and return ordinary analysis Evidence. Each direct-string and template result contains one semantic literal with a 200000-character value. Their application graph coverage is complete, with an empty limit list and no resource-limit unknown. The addition result contains no semantic literal and reports application coverage partial, with javascript_semantic_primitive_bytes equal to 1048576 bytes. Its resource-limit detail is Primitive string-byte budget exceeded (maximum 1048576 estimated JSON bytes). Unknown value at binding.

The semantic graph's own coverage status is unknown in all three cases because other facts remain unresolved. The incorrect complete status described here belongs specifically to the application graph. This reproduction demonstrates retention and serialization beyond the documented conservative semantic estimate; it does not demonstrate memory exhaustion or a crash, and the ASCII string's actual JSON encoding is smaller than that estimate.

Reproduction

Run the following from the baseline checkout root with its built runtime. It creates disposable application directories, gives the CLI an isolated home directory, runs the public command for each expression, saves the returned Evidence, and prints only the relevant comparison.

export REPRO_DIR="$(mktemp -d)"
node --input-type=module <<'JS'
import fs from 'node:fs';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
const root = process.env.REPRO_DIR;
const home = path.join(root, 'home');
fs.mkdirSync(home, { recursive: true });
const cases = [
  ['literal', JSON.stringify('a'.repeat(200000))],
  ['addition', JSON.stringify('a'.repeat(100000)) + ' + ' + JSON.stringify('a'.repeat(100000))],
  ['template', '`' + 'a'.repeat(200000) + '`'],
];
for (const [name, expression] of cases) {
  const app = path.join(root, name);
  fs.mkdirSync(app);
  fs.writeFileSync(path.join(app, 'package.json'), '{"main":"main.js"}');
  fs.writeFileSync(path.join(app, 'main.js'),
    'const big = ' + expression + '; module.exports = { big };\n');
  const run = spawnSync(process.execPath, [
    'scripts/rea.mjs', 'analyze-javascript-application', app, '--format', 'json',
  ], { encoding: 'utf8', maxBuffer: 16000000, timeout: 60000,
    env: { ...process.env, HOME: home } });
  if (run.status !== 0) throw new Error(run.error?.message ?? run.stderr);
  fs.writeFileSync(path.join(root, name + '.json'), run.stdout);
  const result = JSON.parse(run.stdout).normalized_result;
  console.log(JSON.stringify({
    name,
    exit: run.status,
    literal_lengths: result.semantic_graph.nodes
      .filter(node => node.kind === 'literal' && typeof node.properties.value === 'string')
      .map(node => node.properties.value.length),
    application_coverage: result.graph.coverage.status,
    application_limits: result.graph.coverage.limits,
    semantic_coverage: result.semantic_graph.coverage.status,
    resource_unknowns: result.semantic_graph.unknowns
      .filter(unknown => unknown.reason === 'resource-limit')
      .map(unknown => unknown.detail),
  }));
}
JS

Expected behavior

The documented internal bound retains at most 1048576 estimated JSON bytes across the string candidates of one normalized value. It counts six bytes per UTF-16 code unit, plus the surrounding quotes. The 200000-character value therefore counts as 1200002 bytes and must become unknown with a typed resource-limit explanation and partial coverage. Keeping original source text is explicitly permitted, but does not exempt the normalized value from this bound. All three expressions should report the same byte-limit outcome.

Impact and scope

Valid local source inputs bypass the documented internal allocation safeguard and omit its caller-visible limit classification. The observed commands complete successfully; no severe availability failure, incorrect string content, or security-boundary crossing was established.

Workaround: The documented byte safeguard is internal and has no caller-selectable control that applies it to direct literals. Omitting or changing the analyzed source changes the evidence; transport limits do not restore the internal semantic bound.

REA version

rea 6.5.0 at source commit 4692bb3330

Runtime and platform

Node 24.18.0; macOS arm64

Target and artifact context

Three local static JavaScript application directories with equivalent 200000-character values expressed as a string literal, addition, and a template without substitutions.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

  • Expected source: docs/javascript-artifact-reconstruction.md:418-441, especially the normalized string-byte contract at lines 427-439; src/domain/javascript/javascriptSemanticResourceLimits.ts:10-35 defines the limit and estimate. tests/boundary/filesystem/javascript-applications/javascriptArtifactReconstruction.test.ts:243-296 asserts application coverage reporting when semantic resource limits are reached.
  • Failure source: The executed CLI comparison above reads the returned Evidence's literal values, application coverage, and resource-limit unknowns. Source-derived explanation: src/domain/javascript/javascriptSemanticValues.ts:473-477 returns exact nonnumeric literals directly; src/domain/javascript/javascriptAstValues.ts:38-60 recognizes strings and cooked templates without substitutions; the normalization byte check is in src/domain/javascript/javascriptSemanticPrimitives.ts:33-40.

Typed outcome or recovery expected

Repeat the three public CLI calls. The direct-string and template cases must stop retaining the oversized normalized exact value and report its resource-limit unknown and partial application coverage with the 1048576-byte classification. The addition case must continue to report the same limit. Preserve the inventoried source and artifact evidence as documented. Keep unrelated semantic unknowns visible; changing the overall semantic graph status to complete would not establish restoration. A proposed additional control is a short literal that remains exact without a byte-limit warning.

Analysis or suspected cause

Source-derived explanation and exact locators are in Relevant output or evidence. Provider and transport limits are stated there separately from reproduced results.

Additional context

Related open PR #2092 changes serialized graph admission and result-retention budgets; this report concerns the separately documented per-value semantic string estimate. PR #2094 adds constant folding and Electron recovery. Neither describes this direct-literal normalization defect. The observed excess concerns the documented conservative allocation estimate, not actual ASCII JSON size. No memory exhaustion or crash was established.

Freshness check: the cited source and contract files have no changes between the tested source commit and upstream main at 22ec8bdb2f.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3933 ### 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 Analyzing a local application with a 200000-character string literal retains the whole normalized value and reports complete application graph coverage without the documented string-byte resource limit. A template literal without substitutions behaves the same way. Concatenating two 100000-character strings produces the same value but correctly returns a resource-limit unknown and partial application graph coverage. This makes the internal semantic allocation safeguard depend on how the source expresses the string. I observed all three commands exit 0 and return ordinary analysis Evidence. Each direct-string and template result contains one semantic literal with a 200000-character value. Their application graph coverage is `complete`, with an empty limit list and no resource-limit unknown. The addition result contains no semantic literal and reports application coverage `partial`, with `javascript_semantic_primitive_bytes` equal to 1048576 bytes. Its resource-limit detail is `Primitive string-byte budget exceeded (maximum 1048576 estimated JSON bytes). Unknown value at binding.` The semantic graph's own coverage status is `unknown` in all three cases because other facts remain unresolved. The incorrect complete status described here belongs specifically to the application graph. This reproduction demonstrates retention and serialization beyond the documented conservative semantic estimate; it does not demonstrate memory exhaustion or a crash, and the ASCII string's actual JSON encoding is smaller than that estimate. ### Reproduction Run the following from the baseline checkout root with its built runtime. It creates disposable application directories, gives the CLI an isolated home directory, runs the public command for each expression, saves the returned Evidence, and prints only the relevant comparison. ```sh export REPRO_DIR="$(mktemp -d)" node --input-type=module <<'JS' import fs from 'node:fs'; import path from 'node:path'; import { spawnSync } from 'node:child_process'; const root = process.env.REPRO_DIR; const home = path.join(root, 'home'); fs.mkdirSync(home, { recursive: true }); const cases = [ ['literal', JSON.stringify('a'.repeat(200000))], ['addition', JSON.stringify('a'.repeat(100000)) + ' + ' + JSON.stringify('a'.repeat(100000))], ['template', '`' + 'a'.repeat(200000) + '`'], ]; for (const [name, expression] of cases) { const app = path.join(root, name); fs.mkdirSync(app); fs.writeFileSync(path.join(app, 'package.json'), '{"main":"main.js"}'); fs.writeFileSync(path.join(app, 'main.js'), 'const big = ' + expression + '; module.exports = { big };\n'); const run = spawnSync(process.execPath, [ 'scripts/rea.mjs', 'analyze-javascript-application', app, '--format', 'json', ], { encoding: 'utf8', maxBuffer: 16000000, timeout: 60000, env: { ...process.env, HOME: home } }); if (run.status !== 0) throw new Error(run.error?.message ?? run.stderr); fs.writeFileSync(path.join(root, name + '.json'), run.stdout); const result = JSON.parse(run.stdout).normalized_result; console.log(JSON.stringify({ name, exit: run.status, literal_lengths: result.semantic_graph.nodes .filter(node => node.kind === 'literal' && typeof node.properties.value === 'string') .map(node => node.properties.value.length), application_coverage: result.graph.coverage.status, application_limits: result.graph.coverage.limits, semantic_coverage: result.semantic_graph.coverage.status, resource_unknowns: result.semantic_graph.unknowns .filter(unknown => unknown.reason === 'resource-limit') .map(unknown => unknown.detail), })); } JS ``` ### Expected behavior The documented internal bound retains at most 1048576 estimated JSON bytes across the string candidates of one normalized value. It counts six bytes per UTF-16 code unit, plus the surrounding quotes. The 200000-character value therefore counts as 1200002 bytes and must become unknown with a typed resource-limit explanation and partial coverage. Keeping original source text is explicitly permitted, but does not exempt the normalized value from this bound. All three expressions should report the same byte-limit outcome. ### Impact and scope Valid local source inputs bypass the documented internal allocation safeguard and omit its caller-visible limit classification. The observed commands complete successfully; no severe availability failure, incorrect string content, or security-boundary crossing was established. Workaround: The documented byte safeguard is internal and has no caller-selectable control that applies it to direct literals. Omitting or changing the analyzed source changes the evidence; transport limits do not restore the internal semantic bound. ### REA version rea 6.5.0 at source commit 4692bb33300c7cb77b095b6cc0c33da861c1b860 ### Runtime and platform Node 24.18.0; macOS arm64 ### Target and artifact context Three local static JavaScript application directories with equivalent 200000-character values expressed as a string literal, addition, and a template without substitutions. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence - Expected source: `docs/javascript-artifact-reconstruction.md:418-441`, especially the normalized string-byte contract at lines 427-439; `src/domain/javascript/javascriptSemanticResourceLimits.ts:10-35` defines the limit and estimate. `tests/boundary/filesystem/javascript-applications/javascriptArtifactReconstruction.test.ts:243-296` asserts application coverage reporting when semantic resource limits are reached. - Failure source: The executed CLI comparison above reads the returned Evidence's literal values, application coverage, and resource-limit unknowns. Source-derived explanation: `src/domain/javascript/javascriptSemanticValues.ts:473-477` returns exact nonnumeric literals directly; `src/domain/javascript/javascriptAstValues.ts:38-60` recognizes strings and cooked templates without substitutions; the normalization byte check is in `src/domain/javascript/javascriptSemanticPrimitives.ts:33-40`. ### Typed outcome or recovery expected Repeat the three public CLI calls. The direct-string and template cases must stop retaining the oversized normalized exact value and report its resource-limit unknown and partial application coverage with the 1048576-byte classification. The addition case must continue to report the same limit. Preserve the inventoried source and artifact evidence as documented. Keep unrelated semantic unknowns visible; changing the overall semantic graph status to complete would not establish restoration. A proposed additional control is a short literal that remains exact without a byte-limit warning. ### Analysis or suspected cause Source-derived explanation and exact locators are in Relevant output or evidence. Provider and transport limits are stated there separately from reproduced results. ### Additional context Related open PR #2092 changes serialized graph admission and result-retention budgets; this report concerns the separately documented per-value semantic string estimate. PR #2094 adds constant folding and Electron recovery. Neither describes this direct-literal normalization defect. The observed excess concerns the documented conservative allocation estimate, not actual ASCII JSON size. No memory exhaustion or crash was established. Freshness check: the cited source and contract files have no changes between the tested source commit and upstream main at 22ec8bdb2f7d4626587f23b1eed4a207df600386.
Author
Owner

@XinwangTao commented on GitHub (Oct 15, 2026):

Confirmed: the byte-budget safeguard should not depend on how the source expresses the string — a 200k literal and a concatenation of two 100k literals produce the same retained value, so both should trip the same resource limit instead of letting literal form report complete coverage. Normalizing semantic string allocation before the budget check (or applying it after evaluation regardless of literal vs. expression) would close the bypass.

<!-- gh-comment-id:6186236355 --> @XinwangTao commented on GitHub (Oct 15, 2026): Confirmed: the byte-budget safeguard should not depend on how the source expresses the string — a 200k literal and a concatenation of two 100k literals produce the same retained value, so both should trip the same resource limit instead of letting literal form report complete coverage. Normalizing semantic string allocation before the budget check (or applying it after evaluation regardless of literal vs. expression) would close the bypass.
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#1541
No description provided.