Operating system
macOS
System version
15.x (darwin/amd64) — bug is platform-independent, reproducible everywhere
Installation type
Original sing-box Command Line
Version
sing-box version unknown
Environment: go1.25.5 darwin/amd64
Built today from master via go install github.com/sagernet/sing-box/cmd/sing-box@latest.
Module versions resolved: sing-box@v1.13.11, sing@v0.8.9. Code paths cited below are
current on dev-next (protocol/vless/outbound.go HEAD as of this writing).
Description
VLESS outbound construction panics with panic: unknown value instead of returning a
normal error when packet_encoding is set to a string that is not one of the three valid
values ("", "xudp", "packetaddr"). The crash takes down the entire process — both
sing-box check and sing-box run exit with a Go panic stack trace, instead of emitting
the intended FATAL initialize outbound[0]: unknown packet encoding: <value>.
This is a clear oversight, not intended behaviour, for two reasons:
1. The intent at the panic site is to return an error.
protocol/vless/outbound.go:79-87:
switch *options.PacketEncoding {
case "":
case "packetaddr":
outbound.packetAddr = true
case "xudp":
outbound.xudp = true
default:
return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
}
The default branch wants to return nil, E.New(...). The panic comes from a missing
deref: options.PacketEncoding is *string (see option/vless.go:26), and the
unreferenced pointer is what gets passed into E.New → format.ToString. The switch one
line up correctly dereferences (*options.PacketEncoding); only the error-construction
call forgot to.
2. The exact same code in VMess gets it right.
protocol/vmess/outbound.go:74-82:
switch options.PacketEncoding {
case "":
case "packetaddr":
outbound.packetAddr = true
case "xudp":
outbound.xudp = true
default:
return nil, E.New("unknown packet encoding: ", options.PacketEncoding)
}
option/vmess.go:27: PacketEncoding string (not pointer). Same input behaves correctly
— the user gets FATAL initialize outbound[0]: unknown packet encoding: none, no panic.
The only structural difference between the two cases is that VLESS uses *string (to
distinguish "omitted → default xudp" from explicit "" → disabled, see line 76:
if options.PacketEncoding == nil { outbound.xudp = true }), and the deref was forgotten
in the E.New call.
Why the panic, mechanically.
common/format/fmt.go:14-65 (F.ToString) switches on the dynamic type of each argument
and handles string, bool, the integer types, error, and Stringer. There is no
case for pointer types. *string falls through to the default:
default:
panic("unknown value")
E.New is errors.New(F.ToString(message...)) (common/exceptions/error.go:25-27), so
any E.New call that is handed a pointer crashes the process. This is reachable from
user-controlled config input via the VLESS outbound path described above.
Reproduction
Local, no remote server, no TUN, no GUI. Single config file:
config.json:
{
"log": {"level": "debug"},
"outbounds": [
{
"type": "vless",
"tag": "vless-out",
"server": "127.0.0.1",
"server_port": 443,
"uuid": "00000000-0000-0000-0000-000000000000",
"packet_encoding": "none"
}
]
}
Steps:
go install github.com/sagernet/sing-box/cmd/sing-box@latest
sing-box check -c config.json
Expected: FATAL initialize outbound[0]: unknown packet encoding: none, non-zero exit.
Actual: panic: unknown value, full stack trace below.
For comparison, swapping "type": "vless" → "type": "vmess" (and adding
"security": "auto") produces the expected FATAL output cleanly.
Logs
Full output of sing-box check -c config.json:
panic: unknown value
goroutine 1 [running]:
github.com/sagernet/sing/common/format.ToString({0xc00073e7e0?, 0x1013f65c0?, 0x1bb?})
/Users/macbook/go/pkg/mod/github.com/sagernet/sing@v0.8.9/common/format/fmt.go:60 +0x5c5
github.com/sagernet/sing/common/exceptions.New(...)
/Users/macbook/go/pkg/mod/github.com/sagernet/sing@v0.8.9/common/exceptions/error.go:26
github.com/sagernet/sing-box/protocol/vless.NewOutbound({_, _}, {_, _}, {_, _}, {_, _}, {{{0x0, 0x0}, ...}, ...})
/Users/macbook/go/pkg/mod/github.com/sagernet/sing-box@v1.13.11/protocol/vless/outbound.go:86 +0x975
github.com/sagernet/sing-box/adapter/outbound.Register[...].func2(...)
/Users/macbook/go/pkg/mod/github.com/sagernet/sing-box@v1.13.11/adapter/outbound/registry.go:23 +0x15c
github.com/sagernet/sing-box/adapter/outbound.(*Registry).CreateOutbound(...)
/Users/macbook/go/pkg/mod/github.com/sagernet/sing-box@v1.13.11/adapter/outbound/registry.go:64 +0x255
github.com/sagernet/sing-box/adapter/outbound.(*Manager).Create(...)
/Users/macbook/go/pkg/mod/github.com/sagernet/sing-box@v1.13.11/adapter/outbound/manager.go:265 +0xcf
github.com/sagernet/sing-box.New(...)
/Users/macbook/go/pkg/mod/github.com/sagernet/sing-box@v1.13.11/box.go:288 +0x276e
main.check()
/Users/macbook/go/pkg/mod/github.com/sagernet/sing-box@v1.13.11/cmd/sing-box/cmd_check.go:34 +0x1a5
[...spf13/cobra frames omitted, full trace available on request...]
Why this matters in practice
xray-style subscription generators (xray-knife, manual generators) emit
packetEncoding=none in VLESS share-URIs to mean "no special encoding" — semantically
the same as omitted, but a literal string in URI form. Subscription clients that
faithfully translate URI → outbound JSON pass "packet_encoding": "none" straight into
sing-box, which panics during outbound construction. Affected clients have to add
client-side allow-list filtering to avoid feeding this crash. Fixing it upstream removes
the foot-gun for every downstream client.
Suggested direction (no PR attached)
Two independent one-liners, either of which fixes the immediate report; both are worth
having for defence in depth:
-
Deref at the call site. protocol/vless/outbound.go:86:
E.New("unknown packet encoding: ", *options.PacketEncoding) — matches the deref
already done by the switch above and the error message style used by VMess.
-
Make format.ToString graceful for pointer types. Add a reflect-based pointer
handler (or explicit case *string/case *int/...) in common/format/fmt.go. This
protects every other E.New call site in the codebase from the same class of bug,
not just this one.
Happy to clarify any of the above.
Supporter
Integrity requirements
Operating system
macOS
System version
15.x (darwin/amd64) — bug is platform-independent, reproducible everywhere
Installation type
Original sing-box Command Line
Version
Built today from
masterviago install github.com/sagernet/sing-box/cmd/sing-box@latest.Module versions resolved:
sing-box@v1.13.11,sing@v0.8.9. Code paths cited below arecurrent on
dev-next(protocol/vless/outbound.goHEAD as of this writing).Description
VLESS outbound construction panics with
panic: unknown valueinstead of returning anormal error when
packet_encodingis set to a string that is not one of the three validvalues (
"","xudp","packetaddr"). The crash takes down the entire process — bothsing-box checkandsing-box runexit with a Go panic stack trace, instead of emittingthe intended
FATAL initialize outbound[0]: unknown packet encoding: <value>.This is a clear oversight, not intended behaviour, for two reasons:
1. The intent at the panic site is to return an error.
protocol/vless/outbound.go:79-87:The
defaultbranch wants toreturn nil, E.New(...). The panic comes from a missingderef:
options.PacketEncodingis*string(seeoption/vless.go:26), and theunreferenced pointer is what gets passed into
E.New→format.ToString. The switch oneline up correctly dereferences (
*options.PacketEncoding); only the error-constructioncall forgot to.
2. The exact same code in VMess gets it right.
protocol/vmess/outbound.go:74-82:option/vmess.go:27:PacketEncoding string(not pointer). Same input behaves correctly— the user gets
FATAL initialize outbound[0]: unknown packet encoding: none, no panic.The only structural difference between the two cases is that VLESS uses
*string(todistinguish "omitted → default xudp" from explicit
""→ disabled, see line 76:if options.PacketEncoding == nil { outbound.xudp = true }), and the deref was forgottenin the
E.Newcall.Why the panic, mechanically.
common/format/fmt.go:14-65(F.ToString) switches on the dynamic type of each argumentand handles
string,bool, the integer types,error, andStringer. There is nocase for pointer types.
*stringfalls through to the default:default: panic("unknown value")E.Newiserrors.New(F.ToString(message...))(common/exceptions/error.go:25-27), soany
E.Newcall that is handed a pointer crashes the process. This is reachable fromuser-controlled config input via the VLESS outbound path described above.
Reproduction
Local, no remote server, no TUN, no GUI. Single config file:
config.json:{ "log": {"level": "debug"}, "outbounds": [ { "type": "vless", "tag": "vless-out", "server": "127.0.0.1", "server_port": 443, "uuid": "00000000-0000-0000-0000-000000000000", "packet_encoding": "none" } ] }Steps:
go install github.com/sagernet/sing-box/cmd/sing-box@latestsing-box check -c config.jsonExpected:
FATAL initialize outbound[0]: unknown packet encoding: none, non-zero exit.Actual:
panic: unknown value, full stack trace below.For comparison, swapping
"type": "vless"→"type": "vmess"(and adding"security": "auto") produces the expectedFATALoutput cleanly.Logs
Full output of
sing-box check -c config.json:Why this matters in practice
xray-style subscription generators (xray-knife, manual generators) emit
packetEncoding=nonein VLESS share-URIs to mean "no special encoding" — semanticallythe same as omitted, but a literal string in URI form. Subscription clients that
faithfully translate URI → outbound JSON pass
"packet_encoding": "none"straight intosing-box, which panics during outbound construction. Affected clients have to add
client-side allow-list filtering to avoid feeding this crash. Fixing it upstream removes
the foot-gun for every downstream client.
Suggested direction (no PR attached)
Two independent one-liners, either of which fixes the immediate report; both are worth
having for defence in depth:
Deref at the call site.
protocol/vless/outbound.go:86:E.New("unknown packet encoding: ", *options.PacketEncoding)— matches the derefalready done by the switch above and the error message style used by VMess.
Make
format.ToStringgraceful for pointer types. Add areflect-based pointerhandler (or explicit
case *string/case *int/...) incommon/format/fmt.go. Thisprotects every other
E.Newcall site in the codebase from the same class of bug,not just this one.
Happy to clarify any of the above.
Supporter
Integrity requirements