Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Fixes

- Reject oversized `filter` array indexes instead of silently selecting element zero.
- Detect local Cosmos DB emulator connections by the parsed HTTP(S) endpoint host, preventing misleading remote URLs or unrelated connection-string fields from automatically disabling TLS certificate validation.

## 1.1.271-preview — 2026-10-02
Expand Down
17 changes: 17 additions & 0 deletions CosmosDBShell.Tests/CommandTests/FilterCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ namespace CosmosShell.Tests.CommandTests;

public class FilterCommandTests
{
[Theory]
[InlineData(".[2147483648]")]
[InlineData(".[2147483648]?")]
[InlineData(".[999999999999999999999999999999]")]
public async Task ExecuteAsync_RejectsOversizedIndex_WithoutChangingInput(string expression)
{
using var shell = ShellInterpreter.CreateInstance();
var input = new ShellJson(JsonSerializer.SerializeToElement(new[] { 10, 20 }));
var state = new CommandState { Result = input };
var command = new FilterCommand { ExpressionText = expression };

await Assert.ThrowsAsync<CommandException>(() =>
command.ExecuteAsync(shell, state, string.Empty, CancellationToken.None));

Assert.Same(input, state.Result);
}

[Fact]
public async Task ExecuteAsync_AppliesPathExpression_AndPreservesStructuredResult()
{
Expand Down
22 changes: 22 additions & 0 deletions CosmosDBShell.Tests/Parser/FilterPathExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,28 @@ public async Task Index_OutOfRange_ReturnsNull()
Assert.Equal(JsonValueKind.Null, Assert.IsType<ShellJson>(result).Value.ValueKind);
}

[Theory]
[InlineData(".[2147483648]")]
[InlineData(".items[2147483648]?")]
[InlineData(".[999999999999999999999999999999]")]
public void Index_Overflow_ReportsErrorAtIndex(string input)
{
var lexer = new Lexer(input);
var expression = new ExpressionParser(lexer).ParseFilterExpression();

Assert.IsType<ErrorExpression>(expression);
Assert.True(lexer.Errors.HasErrors);
var error = Assert.Single(lexer.Errors);
Assert.Equal(input.IndexOf('[') + 1, error.Start);
}

[Fact]
public async Task Index_MaximumInt32_ReturnsNull()
{
var result = await EvalAsync(".[2147483647]", new[] { 10, 20, 30 });
Assert.Equal(JsonValueKind.Null, Assert.IsType<ShellJson>(result).Value.ValueKind);
}

[Fact]
public async Task Index_OnNonArrayWithoutOptional_Throws()
=> await Assert.ThrowsAsync<CommandException>(() => EvalAsync(".[0]", new { a = 1 }));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1722,7 +1722,12 @@ private Expression ParseFilterPathExpression(Token firstToken)
}

var indexToken = this.Consume(TokenType.Number, MessageService.GetString("expression_error_expected_array_index"));
int index = int.TryParse(indexToken.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedIndex) ? parsedIndex : 0;
if (!int.TryParse(indexToken.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var index))
{
this.ReportError(MessageService.GetArgsString("expression_error_invalid_number", "value", indexToken.Value), indexToken);
return new ErrorExpression(indexToken.Start, indexToken.Length);
}

var indexedCloseBracket = this.Consume(TokenType.CloseBracket, MessageService.GetString("expression_error_expected_close_bracket"));
var indexQuestionToken = this.TryConsumeQuestion();
end = indexedCloseBracket.Start + indexedCloseBracket.Length;
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ A terminal-native shell for Azure Cosmos DB — navigate databases like a filesy
- Tail the change feed of a container with `watch` (alias `tail`)
- Database and container management commands prefer Azure Resource Manager when connected with Entra ID, with data-plane fallback for key, emulator, and static-token connections
- Pipelines and scripting with variables, loops, functions
- Transform piped JSON output with `filter` using jq-inspired expressions (field access, indexing, `map`, `length`, pipelines)
- Transform piped JSON output with `filter` using jq-inspired expressions (field access, indexing, `map`, `length`, pipelines). Indexes larger than `2147483647` are rejected; valid indexes beyond the array length return `null`.
- Edit local files in your external editor with `edit`, and customize REPL colors with `theme` (`list`, `show`, `use`, `load`, `validate`, `save`, `edit`; built-in default/light/dark/monochrome)
- Multi-line input at the prompt — automatic continuation for unclosed blocks/strings, plus explicit `\` line continuation ([docs](docs/navigation.md#multi-line-input))
- MCP server for AI/tool integration
Expand Down
1 change: 1 addition & 0 deletions docs/filter-v1-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ filter <expression>

- `expression` is required.
- The expression is usually quoted at the shell level, for example: `filter '.items[0]'`.
- Array indexes must be integer literals between `0` and `2147483647`. Larger indexes are rejected, including optional paths (`?`); valid indexes beyond the array length return `null`.
- Input comes from the current pipeline value.
- Output is written back into the shell's structured command result.

Expand Down
Loading