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

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions