Cmd became array-typed in 7.23 (was str through 7.22.2), silently splitting on commas and breaking export/import

TL;DR

Starting in RouterOS 7.23 (not present in 7.22.1/7.22.2), a container's cmd value that contains
a comma -- e.g. -forwarder com=1.1.1.1,8.8.8.8 for multiple DNS servers -- gets silently cut in
the wrong place, breaking the container. Escaping the comma (\\,) fixes it live, but the moment
you /container export and /import that same config back (backup/restore, migrating to a new
router), the escaping is lost and it breaks again -- with no error either time. Confirmed on every
7.23.x release; confirmed absent on 7.22.1/7.22.2. Two independent repros below (manual/GUI and
scripted).

Environment

  • RouterOS: reproduced on every 7.23.x release (7.23, 7.23.1, 7.23.2, 7.23.3); confirmed absent
    on 7.22.1 and 7.22.2 -- see the version table below. Primary report is against 7.23.3 (stable).
  • Container package: matching version in each case
  • Affected property: /container add cmd=... / /container set cmd=...

Summary

This is a regression introduced in RouterOS 7.23 (present in every 7.23.x release tested,
absent in 7.22.1/7.22.2): /container's cmd property changed from a plain string type to an
array type. On the array-typed versions (7.23+), assigning cmd a string containing a literal
comma (as part of one logical argument, not as an intentional array separator) causes RouterOS to
split that string into extra array elements at the comma -- and this splitting is not
reversible
: /container export serializes the resulting array back out as one comma-joined
string with no escaping, so re-/importing the exported config reproduces the split, even
starting from a container whose cmd was correctly configured.

This breaks any use case that legitimately needs a comma inside one cmd argument -- our
specific case is the official nextdns/nextdns container image, whose -forwarder flag
documents [DOMAIN=]SERVER_ADDR[,SERVER_ADDR...] as its supported syntax for multiple upstream
DNS servers per domain (comma-separated, for failover). Worth noting this isn't just "annoying" --
it's the kind of regression that can pass a config's own validation/testing at deploy time and
then silently break later, purely from an OS upgrade (7.22.x -> 7.23.x) with no config change at
all, since a cmd value that worked fine as a plain string suddenly starts splitting once the
underlying property type changes.

Version bisection (all versions from 7.22.1 through 7.23.3 tested)

RouterOS cmd type Comma in a value splits it?
7.22.1 str No
7.22.2 str No
7.23 array Yes
7.23.1 array Yes
7.23.2 array Yes
7.23.3 array Yes

Tested via [:typeof [/container get [find name=...] value-name=cmd]] and
[:len [...]] immediately after /container add ... cmd=, on the same
physical CHR instance downgraded/upgraded between each version via /system/package/downgrade
(works bidirectionally) -- ruling out any hardware/environment difference between test runs.

Repro 1 -- manual/GUI, export/import round-trip

  1. Add a container with a cmd argument that needs a literal comma in one value, e.g.:
    1. /container add ``remote-image=registry-1.docker.io/nextdns/nextdns:latest`` interface=<veth> \ cmd="run -forwarder com=192.168.255.55,192.168.255.56" root-dir=<dir> layer-dir=<dir> \ name=test-cmd-comma
  2. /container print detail where name=test-cmd-comma -- note cmd is already split into two
    array elements (com=192.168.255.55 and 192.168.255.56), not the intended one.
  3. Work around it once by escaping the comma: /container set [find name=test-cmd-comma] cmd="run -forwarder com=192.168.255.55\,192.168.255.56" (note: this requires two literal
    backslash characters typed in the CLI/GUI, i.e. \\, in scripted contexts, since a single \,
    is not a recognized escape sequence and is a syntax error on its own -- confirmed).
  4. /container print detail again -- cmd is now correctly a single element,
    run -forwarder com=192.168.255.55,192.168.255.56, comma intact, no visible backslash.
  5. /container export file=test-export -- inspect the resulting file. It contains
    cmd="run -forwarder com=192.168.255.55,192.168.255.56" -- correct-looking, but the escaping
    is gone (nothing needed it while cmd was a single element).
  6. /container remove [find name=test-cmd-comma], then /import file-name=test-export.rsc (or
    just copy-paste the add cmd="..." line from the export back into the terminal).
  7. /container print detail once more -- cmd is split into two elements again, identical to
    step 2. The exported config does not reproduce the state it was exported from.

Repro 2 -- scripted, no GUI needed

:local cmdVal ("run -forwarder com=192.168.255.55\\,192.168.255.56")

/container add remote-image=registry-1.docker.io/nextdns/nextdns:latest interface=<veth> \

    cmd=$cmdVal root-dir=<dir> layer-dir=<dir> name=test-cmd-comma

:put ("typeof: " . [:typeof [/container get [find name=test-cmd-comma] value-name=cmd]])

:put ("len: " . [:len [/container get [find name=test-cmd-comma] value-name=cmd]])

# -> typeof: array, len: 1 (correct)
/container export file=test-export

/container remove [find name=test-cmd-comma]

/import file-name=test-export.rsc
:put ("typeof: " . [:typeof [/container get [find name=test-cmd-comma] value-name=cmd]])

:put ("len: " . [:len [/container get [find name=test-cmd-comma] value-name=cmd]])

# -> typeof: array, len: 2 (WRONG -- was 1 before the export/import round-trip)


Expected behavior

Either:

  • /container export should escape (or otherwise encode) a comma that's part of a single array
    element's value, so /importing the exported config reproduces the identical cmd array; or
  • if the intent is genuinely that cmd values can never contain a literal comma, /container add/set cmd= should reject (or clearly document) that a comma always means "start a new
    argument," so \,escaping isn't silently accepted as a working (but non-persistent) escape
    hatch.

Actual behavior

A cmd value containing an escaped comma is accepted, stored, and functions correctly at
runtime -- but /container export discards the escaping, so the very next /import (or a
full router backup/restore) silently corrupts it back to the split, broken state.

Impact

Any container whose cmd needs a literal comma in one argument (multiple space-separated CLI
args, at least one of which itself needs a comma-separated sub-list -- e.g. the official
nextdns/nextdns image's -forwarder DOMAIN=IP1,IP2 syntax) will break the first time its
config is exported and re-imported, e.g. during a router backup/restore or migration to a new
device -- with no warning, no error, just a silently different (and non-functional) cmd.

Separately, and worse: because this is a genuine type regression (not something that always
existed), a cmd value that worked correctly for a long time can start failing purely from an
in-place RouterOS upgrade from 7.22.x to 7.23.x, with zero configuration change on our end. And
the two "fixes" are mutually exclusive across that boundary -- escaping the comma (\,) is
required to keep it working on 7.23+, but doing so on 7.22.x actively breaks it a different way
(introduces a literal, unwanted \ character into the value -- confirmed, not theoretical). A
config/script that defensively escapes "just in case" is not safe across both version families.

Additional context

Two separate things changed, worth not conflating:

  1. The type change we're reporting here (cmd str -> array, comma-splitting) -- pinned
    down precisely to RouterOS 7.23 via the version bisection above. Not documented anywhere we
    could find, official or community.
  2. A separate, earlier change: "basic string splitting" for the cmd argument (tokenizing
    on whitespace when actually launching the container) was added in RouterOS 7.20, per
    community documentation
    (
    MikroTik Solutions: Container Limitations),
    which itself notes uncertainty about exactly where in the changelogs that was introduced.
    This is a different mechanism at a different layer (container-launch-time argv construction,
    not the property's storage type) and a different version (7.20, not 7.23) -- it's what turns a
    single cmd string/element into individual argv tokens for the container process, and doesn't
    itself involve commas at all.

Official docs at Container - RouterOS - MikroTik Documentation list cmd
only as a "string type" with no version-specific note that this changed in 7.23, no description of
the array behavior on newer versions, and no mention of comma/escaping semantics at all -- happy
to help narrow this down further if useful (e.g. exact changelog entry, if MikroTik can identify
it internally), but from our side this entire type-and-escaping behavior isn't documented
anywhere we could find, on any version.