Skip to content

bug: GITHUB_OUTPUT heredoc relies on a trailing newline and a predictable delimiter #80

Description

@aryamthecodebreaker

Summary

The Action writes its multiline report output using a heredoc whose framing depends on two unstated assumptions.

packages/action/src/index.ts:

const delimiter = `fixmap_${Date.now()}`;
appendFileSync(process.env.GITHUB_OUTPUT, `report<<${delimiter}\n${output}${delimiter}\n`);

1. No newline before the closing delimiter. The string is ...${output}${delimiter}\n — this only produces a valid heredoc because both renderMarkdownReport and renderJsonReport happen to end with \n. Nothing enforces that. A renderer that ever stops emitting a trailing newline turns the last report line into ...text fixmap_1753…, and the Actions runner then fails the step with an unterminated-heredoc error whose cause is nowhere near this file.

2. A predictable delimiter. GitHub's documentation specifically calls for a randomly generated delimiter. Date.now() is guessable to the second, and the runner hard-fails the step if any line of the value equals the delimiter.

Reaching case 2 from untrusted PR content looks impractical today — report text is either repo-derived file paths or alphanumeric tokens (TOKEN_SPLIT strips _), and every rendered line is prefixed with - — so this is hardening, not a live vulnerability. It is cheap to close properly.

Related, same function

  • GITHUB_STEP_SUMMARY has a 1 MiB cap; appendFileSync of a large report on a big monorepo will fail the step with no explanation from FixMap.
  • The three appendFileSync calls are separate, non-atomic writes to the same file.

Suggested fix

import { randomUUID } from "node:crypto";

const delimiter = `fixmap_${randomUUID()}`;
const body = output.endsWith("\n") ? output : `${output}\n`;
appendFileSync(process.env.GITHUB_OUTPUT,
  `report<<${delimiter}\n${body}${delimiter}\n` +
  `context-count=${report.contextFiles.length}\n` +
  `test-route-count=${report.testRoutes.length}\n`
);

and truncate the step-summary write with an explicit "report truncated, see the report output" footer when it would exceed the limit.

Tests asserting the exact bytes written to GITHUB_OUTPUT would pin all of this down — see the entry-point coverage issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggood first issueApproachable contribution with a clear scope

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions