Skip to content

Reject unsafe @func names in program validator and interpreter - #394

Merged
Tal Zaccai (TalZaccai) merged 3 commits into
mainfrom
talzaccai-fix-func-injection
Jul 27, 2026
Merged

Reject unsafe @func names in program validator and interpreter#394
Tal Zaccai (TalZaccai) merged 3 commits into
mainfrom
talzaccai-fix-func-injection

Conversation

@TalZaccai

Copy link
Copy Markdown
Contributor

Vulnerability

createModuleTextFromProgram concatenated the model-supplied @func name raw into generated TypeScript (api.${func}(...)) with no identifier check. A @func value containing comment tokens (/* ... */) could comment out later generated steps, so the type-checker-based validator (which is the sole validation gate for programs) would report success even though a later step called an undeclared function.

Separately, evaluateJsonProgram only checked typeof func === "string" before forwarding the name to the host's onCall dispatcher, so names like constructor, __proto__, toString, valueOf, hasOwnProperty could reach hosts that dispatch via api[func](...), resolving to Object.prototype members the schema never declared.

Fix

Added one shared isValidFunctionName predicate (strict identifier regex + rejects Object.prototype members) used by both:

  • createModuleTextFromProgram — invalid @func names are now treated as an invalid expression, so the whole program fails validation.
  • evaluateJsonProgram — invalid @func names now throw instead of being dispatched.

This guarantees the two code paths can never disagree about which programs are legal.

@ref bounds checking is unrelated and already fixed in #393 (already merged to main).

Tests

Added typescript/test/program.test.ts cases:

  • The comment-injection repro program fails createModuleTextFromProgram validation.
  • evaluateJsonProgram throws (and never dispatches) for the comment-injection name and for constructor/__proto__/toString/valueOf/hasOwnProperty.
  • A normal valid program still validates and dispatches correctly.

Wired out/program.test.js into the test npm script.

npm run build (tsc -p src, tsc -p test) passes; node --test out/validate.test.js out/zod.test.js out/program.test.js tests/model.test.mjs → 97/97 tests pass. Also manually verified the exact malicious repro from the report is now rejected by createModuleTextFromProgram and throws in evaluateJsonProgram rather than dispatching.

Co-authored-by: Copilot App 223556219+Copilot@users.noreply.github.com

Add a shared isValidFunctionName predicate used by both
createModuleTextFromProgram and evaluateJsonProgram so a program can
never be validated by one path but dispatched by the other.

- createModuleTextFromProgram now rejects @func values that aren't
  strict identifiers, closing a source-injection hole where comment
  tokens (/* ... */) in @func could comment out later steps so the
  type-checker-based validator never saw them.
- evaluateJsonProgram now throws for @func values that aren't strict
  identifiers or that resolve to an Object.prototype member
  (constructor, __proto__, toString, valueOf, hasOwnProperty, etc.),
  instead of forwarding them to the host onCall dispatcher.

@ref bounds checking is unrelated and already handled by #393.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens TypeChat’s JSON “program” validator and interpreter against unsafe @func names that could enable TypeScript source injection during validation and prototype-member dispatch during interpretation.

Changes:

  • Added a shared isValidFunctionName predicate and applied it in both createModuleTextFromProgram (validation) and evaluateJsonProgram (interpreter dispatch).
  • Updated interpreter behavior to throw on invalid @func names rather than dispatching them.
  • Added tests covering comment-injection @func values, Object.prototype member names, and a normal valid program path.
Show a summary per file
File Description
typescript/src/ts/program.ts Introduces isValidFunctionName and uses it to reject unsafe @func names in both module generation and runtime evaluation.
typescript/test/program.test.ts Adds regression tests for @func name injection/prototype-member cases and verifies valid programs still work.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (5)

typescript/test/program.test.ts:124

  • This it(...) block’s body isn’t indented, which diverges from the style used in the rest of the test file and makes it harder to scan.
    it("createModuleTextFromProgram rejects a comment-injection @func value", () => {
    const result = createModuleTextFromProgram(maliciousProgram);
    assert.equal(result.success, false);
    });

typescript/test/program.test.ts:130

  • This it(...) block’s contents aren’t indented consistently with surrounding tests. Re-indent to match the file’s existing style.
    it("createModuleTextFromProgram accepts a normal valid program", () => {
    const result = createModuleTextFromProgram({
        "@steps": [
            { "@func": "first", "@args": [] },
            { "@func": "second", "@args": [{ "@ref": 0 }] },

typescript/test/program.test.ts:142

  • The body of this async it(...) test isn’t indented, unlike the rest of the file. Re-indent for consistency/readability.
    it("evaluateJsonProgram throws instead of dispatching a comment-injection @func value", async () => {
    const calls: Array<{ func: string; args: unknown[] }> = [];
    await assert.rejects(
        evaluateJsonProgram(maliciousProgram, async (func, args) => {
            calls.push({ func, args });

typescript/test/program.test.ts:154

  • The for loop and nested it(...) block need consistent indentation (compare to earlier tests). This section is currently flush-left inside the describe, which hurts readability.
    for (const badName of ["constructor", "__proto__", "toString", "valueOf", "hasOwnProperty"]) {
    it(`evaluateJsonProgram throws instead of dispatching prototype member "${badName}"`, async () => {
        const program: Program = { "@steps": [{ "@func": badName, "@args": [] }] };
        let dispatched = false;
        await assert.rejects(

typescript/test/program.test.ts:169

  • This final it(...) test body isn’t indented consistently with the rest of the file, making it harder to read and maintain. Re-indent to match existing tests.
    it("evaluateJsonProgram dispatches a normal valid program correctly", async () => {
    const calls: Array<{ func: string; args: unknown[] }> = [];
    const result = await evaluateJsonProgram(
        {
            "@steps": [
  • Files reviewed: 2/2 changed files
  • Comments generated: 3
  • Review effort level: Low

Comment thread typescript/src/ts/program.ts
Comment thread typescript/src/ts/program.ts
Comment thread typescript/test/program.test.ts
…nt tests

- Reword isValidFunctionName JSDoc to describe the actual ASCII
  identifier constraint instead of implying full JS identifier support.
- evaluateJsonProgram's @func branch now mirrors the generator's shape
  checks (args must be an array when present, no unexpected extra
  keys) and throws instead of silently returning undefined when they
  don't hold.
- Re-indent the @func name validation test block to 4 spaces.
- Add tests covering non-array @Args and extra keys for both
  createModuleTextFromProgram and evaluateJsonProgram.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@TalZaccai
Tal Zaccai (TalZaccai) merged commit 9aaa342 into main Jul 27, 2026
12 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.

3 participants