From 53abbbaee95f63330419411474ba8e8b6d558ccc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 28 Mar 2026 03:27:46 +0000 Subject: [PATCH] Validate command node type and handle multiple results with debug log Agent-Logs-Url: https://github.com/mikefarah/yq/sessions/928aabc5-ad71-41d8-94ab-403942e3f92d Co-authored-by: mikefarah <1151925+mikefarah@users.noreply.github.com> --- .../doc/operators/headers/system-operators.md | 2 +- pkg/yqlib/operator_system.go | 26 ++++++++++++++----- pkg/yqlib/operator_system_test.go | 7 +++++ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/pkg/yqlib/doc/operators/headers/system-operators.md b/pkg/yqlib/doc/operators/headers/system-operators.md index 2df8fdaa..9b84e8df 100644 --- a/pkg/yqlib/doc/operators/headers/system-operators.md +++ b/pkg/yqlib/doc/operators/headers/system-operators.md @@ -7,7 +7,7 @@ The `system` operator allows you to run an external command and use its output a ## Usage ```bash -yq --null-input --enable-system-operator '.field = system("command"; "arg1")' +yq --enable-system-operator --null-input '.field = system("command"; "arg1")' ``` The operator takes: diff --git a/pkg/yqlib/operator_system.go b/pkg/yqlib/operator_system.go index f2e8c9b7..2fb58869 100644 --- a/pkg/yqlib/operator_system.go +++ b/pkg/yqlib/operator_system.go @@ -22,6 +22,20 @@ func resolveSystemArgs(argsNode *CandidateNode) []string { return nil } +func resolveCommandNode(commandNodes Context) (string, error) { + if commandNodes.MatchingNodes.Front() == nil { + return "", fmt.Errorf("system operator: command expression returned no results") + } + if commandNodes.MatchingNodes.Len() > 1 { + log.Debugf("system operator: command expression returned %d results, using first", commandNodes.MatchingNodes.Len()) + } + cmdNode := commandNodes.MatchingNodes.Front().Value.(*CandidateNode) + if cmdNode.Kind != ScalarNode || cmdNode.Tag == "!!null" { + return "", fmt.Errorf("system operator: command must be a string scalar") + } + return cmdNode.Value, nil +} + func systemOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { if !ConfiguredSecurityPreferences.EnableSystemOps { log.Warning("system operator is disabled, use --enable-system-operator flag to enable") @@ -51,10 +65,10 @@ func systemOperator(d *dataTreeNavigator, context Context, expressionNode *Expre if err != nil { return Context{}, err } - if commandNodes.MatchingNodes.Front() == nil { - return Context{}, fmt.Errorf("system operator: command expression returned no results") + command, err = resolveCommandNode(commandNodes) + if err != nil { + return Context{}, err } - command = commandNodes.MatchingNodes.Front().Value.(*CandidateNode).Value argsNodes, err := d.GetMatchingNodes(nodeContext, block.RHS) if err != nil { @@ -68,10 +82,10 @@ func systemOperator(d *dataTreeNavigator, context Context, expressionNode *Expre if err != nil { return Context{}, err } - if commandNodes.MatchingNodes.Front() == nil { - return Context{}, fmt.Errorf("system operator: command expression returned no results") + command, err = resolveCommandNode(commandNodes) + if err != nil { + return Context{}, err } - command = commandNodes.MatchingNodes.Front().Value.(*CandidateNode).Value } var stdin bytes.Buffer diff --git a/pkg/yqlib/operator_system_test.go b/pkg/yqlib/operator_system_test.go index 385e1561..867e1921 100644 --- a/pkg/yqlib/operator_system_test.go +++ b/pkg/yqlib/operator_system_test.go @@ -98,6 +98,13 @@ func TestSystemOperatorEnabledScenarios(t *testing.T) { expression: `.a = system("` + falsePath + `")`, expectedError: "system command '" + falsePath + "' failed: exit status 1", }, + { + description: "Null command returns error", + skipDoc: true, + document: "a: hello", + expression: `.a = system(null)`, + expectedError: "system operator: command must be a string scalar", + }, } for _, tt := range scenarios {