flatbuffers icon indicating copy to clipboard operation
flatbuffers copied to clipboard

[TS codegen] Escape JSDoc terminator in doc comments to prevent comment closure and import‑time code execution

Open Ky0toFu opened this issue 2 months ago • 3 comments

Summary

The TypeScript generator in flatc writes schema documentation comments directly into a JSDoc block without escaping the terminator. A crafted line like */…/* in a .fbs doc comment prematurely closes the block and injects top‑level JavaScript into the generated module, which then executes on import/bundling.

This is a first‑party codegen bug in this repository (not a third‑party dependency).

Affected code

  • File: src/idl_gen_ts.cpp
  • Function: GenDocComment
  • Behavior: concatenates " *" + line + "\n" into a /** … */ JSDoc block without escaping */.
// Generate a documentation comment, if available.
static void GenDocComment(const std::vector<std::string>& dc,
                          std::string* code_ptr,
                          const char* indent = nullptr) {
  if (dc.empty()) { return; }
  std::string& code = *code_ptr;
  if (indent) code += indent;
  code += "/**\n";
  for (auto it = dc.begin(); it != dc.end(); ++it) {
    if (indent) code += indent;
    code += " *" + *it + "\n";  // no escaping of "*/"
  }
  if (indent) code += indent;
  code += " */\n";
}

Minimal reproduction (safe payload)

  1. malicious.fbs
namespace Poc;

/// */console.log('INJECTED_FROM_DOC_COMMENT')/*
table A { x:int; }

root_type A;
  1. Generate TypeScript
flatc --ts -o out malicious.fbs
  1. Observe injection in generated module
/**
 * */console.log('INJECTED_FROM_DOC_COMMENT')/*
 */
export class A {
  1. Any TS→JS path will execute the payload at import‑time (example)
npx --yes esbuild out/poc/A.ts --format=cjs --bundle --outfile=out/bundle.cjs --external:flatbuffers
node out/bundle.cjs  # prints: INJECTED_FROM_DOC_COMMENT

Expected vs actual

  • Expected: doc comments should never be able to close comments and introduce executable code into generated sources.
  • Actual: unescaped */ terminates JSDoc and injects top‑level JS that runs on import/bundling.

Proposed fix (minimal and safe)

Escape */ in each doc comment line before emission to the JSDoc block.

// inside the for-loop over doc lines
auto safe = *it;
for (size_t pos = 0; (pos = safe.find("*/", pos)) != std::string::npos; ) {
  safe.replace(pos, 2, "*\\/");
  pos += 3; // advance past the replacement
}
code += " *" + safe + "\n";

Alternative (even safer)

Render documentation as line comments in TypeScript (// …) so early termination by input is impossible by construction.

Notes

  • This issue is opened to coordinate a code‑hardening fix in the TS generator.
  • A full end‑to‑end PoC (including generated output and import‑time execution) is available upon request and trivial to reproduce with the steps above.

Environment (used for reproduction)

  • Node.js ≥ 18 (tested on v22.17.1)
  • flatc 25.9.23 (Windows release)

Ky0toFu avatar Oct 17 '25 15:10 Ky0toFu

hey @Ky0toFu - thank you for a very comprehensive issue form! Are you interested in opening a PR for this issue? I'll defer to others (@bjornharrtell) on preference between your two fix proposals -- I would think that line comments would be the better, more robust fix as long as it doesn't negatively affect any users' tooling (like tooltips in an IDE)

jtdavis777 avatar Dec 03 '25 05:12 jtdavis777

I have no preference 😊

bjornharrtell avatar Dec 03 '25 06:12 bjornharrtell

Thanks for the feedback!

Given the potential impact on IDEs/tooling that rely on /** ... */ for hover docs, I'll start with the minimal safe fix that escapes */ in the generated JSDoc.

This prevents doc comments from closing the block (and injecting code) while fully preserving JSDoc behavior for end users.

I'll open a PR shortly.

Ky0toFu avatar Dec 03 '25 06:12 Ky0toFu

🥳

jtdavis777 avatar Dec 03 '25 12:12 jtdavis777