koala73/worldmonitor · error · TypeError

Malformed command: expected an array whose first element is…

Error message

Malformed command: expected an array whose first element is the command name

What it means

assertCommandAllowed in the Redis REST proxy first checks that the request body is a well-formed command: an array whose first element is a non-empty string command name. Anything else (missing array, null body, non-string first element, empty name) throws a TypeError rendered as a 400 'Malformed command' instead of the 403 'Command not allowed' authorization channel. This separation keeps authorization failures distinct from malformed input.

Solutions

  1. Send the command as a JSON array whose first element is the command name, e.g. ["GET", "key"].
  2. Ensure the Content-Type is application/json and the body parses to an array, not an object or string.
  3. Do not send an empty command name; use the real Redis command as element 0.
  4. Fix client code to serialize commands as arrays (e.g. JSON.stringify(['SET','k','v'])).

Example fix

// before
fetch(proxy, { method: 'POST', body: JSON.stringify({ command: 'GET', key: 'foo' }) });
// after
fetch(proxy, { method: 'POST', body: JSON.stringify(['GET', 'foo']) });
Defensive patterns

Strategy: validation

Validate before calling

const args = JSON.parse(rawBody);
if (!Array.isArray(args) || typeof args[0] !== 'string' || args[0].trim() === '') throw new Error('command must be ["CMD", ...args]');

Type guard

const isCommand = (v) => Array.isArray(v) && v.length > 0 && typeof v[0] === 'string' && v[0].trim() !== '';

Try / catch

try { await proxyCommand(args); } catch (e) { if (String(e.message).startsWith('Malformed command')) { log('bad payload shape', args); return 400; } throw e; }

Prevention

When it happens

Trigger: POSTing to the proxy with a JSON body that is not an array (e.g. `{"cmd":"GET"}`), an array whose first element is null/a number, or `[""]`; also sending a body that fails to decode into the expected array shape.

Common situations: Client libraries sending object-wrapped commands instead of arrays; empty POST bodies; double-encoded JSON strings; curl tests with wrong payload shape.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-22). Data as JSON: /api/errors/cc354bbf80cd6ba4. Report an issue: GitHub.

Appendix: source

Thrown at docker/redis-rest-proxy.mjs:689

// decision and nothing else. Without it a caller can only catch every throw
// from this function, and `String(args[0])` throws a TypeError on a null or
// undefined body element — so a malformed request comes back as an
// authorization failure, which is precisely the misdirection #8265 was. Tagged
// on the error object rather than raised as a named subclass because
// tests/redis-rest-proxy-command-parity.test.mjs extracts this function's
// source and evals it standalone; a class declared elsewhere in this file
// would be undefined there.
function assertCommandAllowed(args) {
  // Shape first, authorization second. String(args[0]) turns a missing verb
  // into "undefined" and a null one into "null", and the allowlist then
  // refuses those as if they were commands — so `[[]]` and `[[null]]` came
  // back as 403 "Command not allowed: UNDEFINED"/"NULL" while a null ELEMENT
  // (which throws before this line) came back as 500. Same malformed body,
  // two status classes, two of them in the authorization channel. A
  // well-formed command that is simply not allowed is the only thing past
  // this point.
  if (!Array.isArray(args) || typeof args[0] !== 'string' || args[0].trim() === '') {
    throw new TypeError('Malformed command: expected an array whose first element is the command name');
  }
  const cmd = args[0].toUpperCase();
  if (cmd === 'EVAL') {
    if (!isAllowedEval(args)) {
      console.error('Command not allowed: EVAL (script not in the pinned allowlist)');
      throw Object.assign(new Error('Command not allowed: EVAL (script not in the pinned allowlist)'), { commandNotAllowed: true });
    }
  } else if (!ALLOWED_COMMANDS.has(cmd)) {
    console.error(`Command not allowed: ${cmd}`);
    throw Object.assign(new Error(`Command not allowed: ${cmd}`), { commandNotAllowed: true });
  }
  return cmd;
}

function commandForExecution(args) {
  const cmd = assertCommandAllowed(args);
  const command = [cmd, ...args.slice(1).map(String)];
  if (cmd === 'EVAL') {

View on GitHub (pinned to e586b8b4b8)