Skip to content

failure-summary: an installation token can reach a check-run summary via JSON.stringify(cause) #111

Description

@debuggingfuture

A step failure whose cause is neither a string nor an Error is rendered into the check-run summary with JSON.stringify:

https://github.com/fractalboxdev/flare-dispatch/blob/e8a1300/apps/dispatcher/src/failure-summary.ts#L59-L67

const cause =
  typeof e.cause === "string"
    ? e.cause
    : e.cause instanceof Error
      ? e.cause.message
      : JSON.stringify(e.cause);

CheckoutFailed is exactly that shape — it carries cause: Schema.Unknown (packages/core/src/errors.ts:12-16), so the whole object is serialized, nested cause included.

Why that is a credential path

gitClone authenticates the clone URL with a GitHub App installation token before handing it to the sandbox:

https://github.com/fractalboxdev/flare-dispatch/blob/e8a1300/packages/runtime-cf/src/sandbox-clone-url.ts#L21-L23

https://x-access-token:<token>@github.com/<owner>/<name>.git

If box.gitCheckout(cloneUrl, …) rejects with anything that carries the URL — and git routinely echoes the remote it failed to reach — that URL becomes CheckoutFailed.cause, and stepFailedMd stringifies it into a check-run summary, which is visible to anyone who can see the check. The token is short-lived (~1h) but live at the moment it is published.

Observed

PR #89 produced this summary for real, from a genuine checkout failure:

**Step `checkout` failed**:

CheckoutFailed: { "repo": "fractalboxdev/flare-dispatch", "sha": "36cd4922d9a1…", "cause": Error: Network connection lost. }

Here the inner cause was a benign network error, so nothing leaked. It demonstrates the path, not the leak: the same rendering with a git-originated cause is what publishes the token.

Suggested fix

Two independent changes, either of which closes it; both are worth having:

  1. Do not serialize an unknown cause. stepFailedMd should fall back to a fixed string rather than JSON.stringify — an unrecognised cause is precisely the case where nothing is known about what its fields hold. runs/org-spec-audit.ts took this approach for its own describe() in org-spec-audit: the estate sweep for questions specs can't answer — and two fixes it needed to run #89.
  2. Match CheckoutFailed explicitly and render repo + sha only. Those are the whole diagnosis; the git error tail adds nothing worth carrying a credential to get.

Worth also scrubbing x-access-token:<token>@ at the summary boundary as a backstop, since the URL can reach it through causes other than CheckoutFailed.

Notes

Found while reviewing #89; unrelated to that PR's diff, which is why it is filed separately rather than fixed there.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions