Wei-Shaw/sub2api · error

ChatGPT DeviceCheck returned a malformed attestation

Error message

ChatGPT DeviceCheck returned a malformed attestation

What it means

After the DeviceCheck subprocess succeeds, its stdout must be a JSON token header between 20 bytes and 16 KiB. If stdout is too short, too long, empty, or not valid JSON, the attestation is deemed malformed and rejected. This guards the downstream Live protocol from forwarding garbage tokens.

Source

Thrown at backend/internal/platform/liveattestation/attestation_darwin.go:136

	var stderr bytes.Buffer
	command.Stdout = &stdout
	command.Stderr = &stderr
	if err := command.Run(); err != nil {
		if errors.Is(runCtx.Err(), context.DeadlineExceeded) {
			return "", errors.New("ChatGPT DeviceCheck token generation timed out")
		}
		reason := strings.TrimSpace(stderr.String())
		if len(reason) > 240 {
			reason = reason[:240]
		}
		if reason == "" {
			reason = err.Error()
		}
		return "", fmt.Errorf("ChatGPT DeviceCheck token generation failed: %s", reason)
	}
	header := strings.TrimSpace(stdout.String())
	if len(header) < 20 || len(header) > 16*1024 || !json.Valid([]byte(header)) {
		return "", errors.New("ChatGPT DeviceCheck returned a malformed attestation")
	}
	return header, nil
}

func (p *darwinProvider) findApplication() (string, error) {
	for _, appPath := range p.appPaths {
		info, err := os.Stat(appPath)
		if err == nil && info.IsDir() {
			return appPath, nil
		}
	}
	return "", ErrChatGPTAppMissing
}

func readBundleIdentifier(ctx context.Context, appPath string) (string, error) {
	infoPlist := filepath.Join(appPath, "Contents", "Info.plist")
	output, err := exec.CommandContext(
		ctx,

View on GitHub (pinned to 073e92d171)

Solutions

  1. Update (or pin) the official ChatGPT app to a version known compatible with Sub2API's attestation shim.
  2. Run the subprocess manually with the same env (SUB2API_DEVICECHECK_MODULE, SUB2API_ATTESTATION_BUNDLE_ID, SUB2API_ATTESTATION_SIGNALS) and inspect stdout.
  3. Ensure nothing (wrapper scripts, node --print banners, NVM shims) appends to stdout.
  4. Report to Sub2API if a ChatGPT app update changed the output shape — the parser needs updating.
Defensive patterns

Strategy: try-catch

Try / catch

if err != nil {
    if strings.Contains(err.Error(), "malformed attestation") {
        // almost always a ChatGPT app version change: check app version vs known-good list
        return appVersionError("update or pin the ChatGPT app bundle")
    }
    return err
}

Prevention

When it happens

Trigger: The bundled node process exits 0 but prints a non-JSON banner, an error message, extra logging, or nothing; stdout pollution from the runtime; or a truncated buffer due to the process being killed mid-write (but with a non-deadline error).

Common situations: ChatGPT app updated and its devicecheck.node contract changed (extra output, different format); environment variables leaking extra stdout; locale-specific messages printed by node; module returning an HTML error string instead of JSON.

Understand the failure class

Related errors


AI-assisted analysis of Wei-Shaw/sub2api@073e92d171 (2026-08-15). Data as JSON: /api/errors/656e9800982bcfc5. Report an issue: GitHub.