Skip to content

[Safe Outputs Conformance] MCE-006: getErrorMessage can leak [object Object] for non-string thrown message fields #55014

Description

@github-actions

Conformance Check Failure

Check ID: MCE-006
Severity: HIGH
Category: Implementation (error serialization robustness)

Problem Description

scripts/check-safe-outputs-conformance.sh reports:

[HIGH] MCE-006: MCP server core does not use String() for error message serialization — risk of '[object Object]' in responses (Section 8.2)

The checker looks for the literal pattern String(e.message) or String(err.message) in actions/setup/js/mcp_server_core.cjs. That literal pattern isn't present — instead, the file delegates message extraction to a shared helper, getErrorMessage(), imported from actions/setup/js/error_helpers.cjs:

// mcp_server_core.cjs (~line 998-1005)
const hasMessage = typeof e === "object" && e !== null && "message" in e && Boolean(e.message);
const message = hasMessage ? getErrorMessage(e) : "Internal error";
server.replyError(id, code, message);

Investigating getErrorMessage() shows it is more sophisticated than a plain String(e.message) call (it also detects and cleans up HTML error pages), but it has a narrow real gap that matches the spirit of what the checker is trying to catch:

// error_helpers.cjs
function getErrorMessage(error) {
  let message;
  if (error instanceof Error) {
    message = error.message;
  } else if (error && typeof error === "object" && "message" in error && typeof error.message === "string") {
    message = error.message;
  } else {
    message = String(error);   // <-- falls back to stringifying the WHOLE error object
  }
  ...
}

If a thrown value is a plain object whose .message property is itself non-string (e.g. { message: { reason: "x" } }), the middle branch's typeof error.message === "string" check fails, and the code falls through to String(error) — stringifying the entire object rather than just its message. For a plain object without a custom toString, this produces "[object Object]" in the final MCP error response, which is exactly the failure mode Section 8.2 of the spec prohibits.

This is a narrower edge case than a total absence of serialization (most thrown values are Error instances or have string .message), but it's a real gap, not purely a checker false positive.

Affected Components

  • Files: actions/setup/js/error_helpers.cjs (getErrorMessage, lines 26-44), actions/setup/js/mcp_server_core.cjs (lines 998-1006)
  • Checker: scripts/check-safe-outputs-conformance.sh (~line 1031) — its literal-pattern check also doesn't recognize the indirection through getErrorMessage(), so it can't currently distinguish "no serialization" from "serialization via a shared helper."
🔍 Current vs Expected Behavior

Current Behavior

When a handler throws a plain object with a non-string message field, getErrorMessage() stringifies the whole error object instead of the message, which can produce "[object Object]" in the JSON-RPC error response sent back over MCP.

Expected Behavior

Per spec Section 8.2, error messages returned in JSON-RPC responses MUST be readable strings, never "[object Object]". getErrorMessage() should coerce a non-string .message value to a string explicitly (e.g. String(error.message)) rather than falling through to stringifying the container object.

Remediation Steps

This task can be assigned to a Copilot coding agent with the following steps:

  1. In actions/setup/js/error_helpers.cjs, update getErrorMessage() so that when error is a non-Error object with a message property that exists but is not a string, it coerces that message value to a string instead of stringifying the whole object, e.g.:
    } else if (error && typeof error === "object" && "message" in error) {
      message = typeof error.message === "string" ? error.message : String(error.message);
    } else {
      message = String(error);
    }
  2. Add/update a unit test (there is likely an existing error_helpers.test.cjs or similar) covering a thrown plain object with a non-string message field, asserting the result is never the literal string "[object Object]".
  3. Update scripts/check-safe-outputs-conformance.sh MCE-006 (~line 1031) to also recognize serialization performed via the shared getErrorMessage helper (e.g. by checking that mcp_server_core.cjs imports getErrorMessage from error_helpers.cjs AND that error_helpers.cjs itself guards against non-string .message values), instead of only grepping for the literal String(e.message) pattern in the core file.
  4. Re-run the conformance checker and confirm MCE-006 passes.

Verification

After remediation, verify the fix by running:

bash scripts/check-safe-outputs-conformance.sh

The check MCE-006 should pass without errors.

References

  • Safe Outputs Specification: docs/src/content/docs/specs/safe-outputs-specification.md
  • Conformance Checker: scripts/check-safe-outputs-conformance.sh
  • Run ID: 32621246743
  • Date: 2026-08-23

Generated by ✅ Daily Safe Outputs Conformance Checker · agent · 88 AIC · ⌖ 8.17 AIC · ⊞ 7.5K ·

  • expires on Aug 23, 2026, 9:54 PM UTC-08:00

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions