Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
dc30e2d
empty
AlexandreYang Mar 16, 2026
d9d0ef7
Merge branch 'main' into alex/ping
AlexandreYang Mar 16, 2026
0abfa86
Implement ping builtin using datadog-traceroute ICMP E2E probe
AlexandreYang Mar 16, 2026
371cf65
fix: use lowercase protocol string for datadog-traceroute ICMP matching
AlexandreYang Mar 16, 2026
34cf0f9
[iter 1] Add MaxInterval bound, clarify allowlist comments
AlexandreYang Mar 16, 2026
2f6522b
[iter 1] Fix packet loss percentage, document interval limitation, ad…
AlexandreYang Mar 16, 2026
e33a60f
[iter 1] Add missing LICENSE-3rdparty.csv entries for datadog-tracero…
AlexandreYang Mar 16, 2026
a935536
[iter 2] Skip failed E2E probes, fix NaN interval, harden fuzz test
AlexandreYang Mar 16, 2026
04ec64a
[iter 2] Fix Fuzz (ping) CI timeout by enforcing context cancellation
AlexandreYang Mar 16, 2026
6b71c70
[iter 3] Add result package to allowedsymbols, fix fuzz exit code, ad…
AlexandreYang Mar 16, 2026
b392924
[iter 3] Fix FuzzPingFlags: filter pipe/compound shell constructs fro…
AlexandreYang Mar 16, 2026
155a2ed
[iter 8] Use RSHELL_PING_TEST env var for ping integration tests
AlexandreYang Mar 16, 2026
27759bc
Add RSHELL_PING_TEST env guard and CI job for ping integration tests
AlexandreYang Mar 16, 2026
c6a9a1e
[iter 9] Fix CI: ping integration sudo PATH, macOS ICMP privileges, l…
AlexandreYang Mar 16, 2026
818ed1a
[iter 9] Skip multi-ping integration test on Windows
AlexandreYang Mar 16, 2026
0ed122e
[iter 1] Address review comments: exact reply count assertion, docume…
AlexandreYang Mar 17, 2026
90666eb
[iter 1] Skip ping integration test entirely on Windows
AlexandreYang Mar 17, 2026
e24a3fa
[iter 2] Fix float promotion in packet loss display and add Windows i…
AlexandreYang Mar 17, 2026
0943be7
[iter 2] Fix Windows ping integration test: accept exit code 0 or 1
AlexandreYang Mar 17, 2026
9dfefe3
[iter 4] Expand ping CI test filter and tolerate Windows ICMP failures
AlexandreYang Mar 17, 2026
94f6acc
[iter 6] Enforce ping-count assertions in Windows integration path
AlexandreYang Mar 17, 2026
f807fec
[iter 7] Fix Windows ping integration test: tolerate partial packet loss
AlexandreYang Mar 17, 2026
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
3 changes: 3 additions & 0 deletions .github/workflows/fuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ jobs:
- pkg: ./builtins/tests/ls/
name: ls
corpus_path: builtins/tests/ls
- pkg: ./builtins/ping/
name: ping
corpus_path: builtins/ping
- pkg: ./interp/tests/
name: interp
corpus_path: interp/tests
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,29 @@ jobs:
exit 1
fi

test-ping:
name: Test ping integration (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version-file: .go-version
- name: Run ping integration tests (Linux/macOS)
if: runner.os == 'Linux' || runner.os == 'macOS'
env:
RSHELL_PING_TEST: "1"
run: sudo -E env "PATH=$PATH" go test -race -v -run 'TestPing.*Integration|TestPingPentest|TestPingContextTimeout|TestPingCountClampedToMax|TestPingTimeoutClampedToMax' ./builtins/ping/ -timeout 120s
- name: Run ping integration tests (Windows)
if: runner.os == 'Windows'
env:
RSHELL_PING_TEST: "1"
run: go test -race -v -run 'TestPing.*Integration|TestPingPentest|TestPingContextTimeout|TestPingCountClampedToMax|TestPingTimeoutClampedToMax' ./builtins/ping/ -timeout 120s

test-against-bash:
name: Test against Bash (Docker)
runs-on: ubuntu-latest
Expand Down
50 changes: 47 additions & 3 deletions LICENSE-3rdparty.csv
Original file line number Diff line number Diff line change
@@ -1,9 +1,53 @@
Component,Origin,License,Copyright
github.com/stretchr/testify,https://github.com/stretchr/testify,MIT,"Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors"
gopkg.in/yaml.v3,https://github.com/go-yaml/yaml,MIT AND Apache-2.0,"Copyright (c) 2006-2011 Kirill Simonov, 2011-2019 Canonical Ltd"
mvdan.cc/sh/v3,https://github.com/mvdan/sh,BSD-3-Clause,"Copyright (c) 2016, Daniel Marti"
github.com/DataDog/datadog-traceroute,https://github.com/DataDog/datadog-traceroute,Apache-2.0,Copyright 2024-present Datadog Inc.
github.com/DataDog/datadog-agent/comp/core/telemetry,https://github.com/DataDog/datadog-agent,Apache-2.0,Copyright 2016-present Datadog Inc.
github.com/DataDog/datadog-agent/comp/def,https://github.com/DataDog/datadog-agent,Apache-2.0,Copyright 2016-present Datadog Inc.
github.com/DataDog/datadog-agent/pkg/network/driver,https://github.com/DataDog/datadog-agent,Apache-2.0,Copyright 2016-present Datadog Inc.
github.com/DataDog/datadog-agent/pkg/telemetry,https://github.com/DataDog/datadog-agent,Apache-2.0,Copyright 2016-present Datadog Inc.
github.com/DataDog/datadog-agent/pkg/util/fxutil,https://github.com/DataDog/datadog-agent,Apache-2.0,Copyright 2016-present Datadog Inc.
github.com/DataDog/datadog-agent/pkg/util/log,https://github.com/DataDog/datadog-agent,Apache-2.0,Copyright 2016-present Datadog Inc.
github.com/DataDog/datadog-agent/pkg/util/optional,https://github.com/DataDog/datadog-agent,Apache-2.0,Copyright 2016-present Datadog Inc.
github.com/DataDog/datadog-agent/pkg/util/scrubber,https://github.com/DataDog/datadog-agent,Apache-2.0,Copyright 2016-present Datadog Inc.
github.com/DataDog/datadog-agent/pkg/util/winutil,https://github.com/DataDog/datadog-agent,Apache-2.0,Copyright 2016-present Datadog Inc.
github.com/DataDog/datadog-agent/pkg/version,https://github.com/DataDog/datadog-agent,Apache-2.0,Copyright 2016-present Datadog Inc.
github.com/beorn7/perks,https://github.com/beorn7/perks,MIT,Copyright (C) 2013 Blake Mizerany
github.com/cenkalti/backoff/v5,https://github.com/cenkalti/backoff,MIT,Copyright (c) 2014 Cenk Alti
github.com/cespare/xxhash/v2,https://github.com/cespare/xxhash,MIT,Copyright (c) 2016 Caleb Spare
github.com/cihub/seelog,https://github.com/cihub/seelog,BSD-3-Clause,"Copyright (c) 2012, Cloud Instruments Co. Ltd."
github.com/davecgh/go-spew,https://github.com/davecgh/go-spew,ISC,Copyright (c) 2012-2016 Dave Collins
github.com/go-logr/logr,https://github.com/go-logr/logr,Apache-2.0,Copyright 2019 The logr Authors
github.com/go-logr/stdr,https://github.com/go-logr/stdr,Apache-2.0,Copyright 2021 The logr Authors
github.com/golang/mock,https://github.com/golang/mock,Apache-2.0,Copyright 2010 Google Inc.
github.com/google/gopacket,https://github.com/google/gopacket,BSD-3-Clause,Copyright (c) 2012 Google Inc. All rights reserved.
github.com/google/uuid,https://github.com/google/uuid,BSD-3-Clause,"Copyright (c) 2009, 2014 Google Inc. All rights reserved."
github.com/inconshreveable/mousetrap,https://github.com/inconshreveable/mousetrap,Apache-2.0,Copyright 2014 Alan Shreve
github.com/munnerz/goautoneg,https://github.com/munnerz/goautoneg,BSD-3-Clause,"Copyright (c) 2011, Open Knowledge Foundation Ltd."
github.com/patrickmn/go-cache,https://github.com/patrickmn/go-cache,MIT,Copyright (c) 2012-2017 Patrick Mylund Nielsen and the go-cache contributors
github.com/pkg/errors,https://github.com/pkg/errors,BSD-2-Clause,Copyright (c) 2015 Dave Cheney
github.com/pmezard/go-difflib,https://github.com/pmezard/go-difflib,BSD-3-Clause,"Copyright (c) 2013, Patrick Mezard"
github.com/prometheus/client_golang,https://github.com/prometheus/client_golang,Apache-2.0,Copyright 2012-2015 The Prometheus Authors
github.com/prometheus/client_model,https://github.com/prometheus/client_model,Apache-2.0,Copyright 2012-2015 The Prometheus Authors
github.com/prometheus/common,https://github.com/prometheus/common,Apache-2.0,Copyright 2015 The Prometheus Authors
github.com/prometheus/procfs,https://github.com/prometheus/procfs,Apache-2.0,Copyright 2014-2015 The Prometheus Authors
github.com/spf13/cobra,https://github.com/spf13/cobra,Apache-2.0,Copyright 2013-2023 The Cobra Authors
github.com/spf13/pflag,https://github.com/spf13/pflag,BSD-3-Clause,"Copyright (c) 2012 Alex Ogier, The Go Authors"
github.com/stretchr/testify,https://github.com/stretchr/testify,MIT,"Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors"
go.opentelemetry.io/auto/sdk,https://github.com/open-telemetry/opentelemetry-go,Apache-2.0,Copyright The OpenTelemetry Authors
go.opentelemetry.io/otel,https://github.com/open-telemetry/opentelemetry-go,Apache-2.0,Copyright The OpenTelemetry Authors
go.opentelemetry.io/otel/exporters/prometheus,https://github.com/open-telemetry/opentelemetry-go,Apache-2.0,Copyright The OpenTelemetry Authors
go.opentelemetry.io/otel/metric,https://github.com/open-telemetry/opentelemetry-go,Apache-2.0,Copyright The OpenTelemetry Authors
go.opentelemetry.io/otel/sdk,https://github.com/open-telemetry/opentelemetry-go,Apache-2.0,Copyright The OpenTelemetry Authors
go.opentelemetry.io/otel/sdk/metric,https://github.com/open-telemetry/opentelemetry-go,Apache-2.0,Copyright The OpenTelemetry Authors
go.opentelemetry.io/otel/trace,https://github.com/open-telemetry/opentelemetry-go,Apache-2.0,Copyright The OpenTelemetry Authors
go.uber.org/atomic,https://github.com/uber-go/atomic,MIT,Copyright (c) 2016 Uber Technologies Inc.
go.uber.org/dig,https://github.com/uber-go/dig,MIT,"Copyright (c) 2017-2018 Uber Technologies, Inc."
go.uber.org/fx,https://github.com/uber-go/fx,MIT,"Copyright (c) 2016-2018 Uber Technologies, Inc."
go.uber.org/multierr,https://github.com/uber-go/multierr,MIT,"Copyright (c) 2017-2021 Uber Technologies, Inc."
go.uber.org/zap,https://github.com/uber-go/zap,MIT,"Copyright (c) 2016-2017 Uber Technologies, Inc."
go.yaml.in/yaml/v2,https://github.com/go-yaml/yaml,Apache-2.0,Copyright 2011-2016 Canonical Ltd.
golang.org/x/net,https://github.com/golang/net,BSD-3-Clause,Copyright 2009 The Go Authors
golang.org/x/sync,https://github.com/golang/sync,BSD-3-Clause,Copyright 2009 The Go Authors
golang.org/x/sys,https://github.com/golang/sys,BSD-3-Clause,Copyright 2009 The Go Authors
google.golang.org/protobuf,https://github.com/protocolbuffers/protobuf-go,BSD-3-Clause,Copyright (c) 2018 The Go Authors. All rights reserved.
gopkg.in/yaml.v3,https://github.com/go-yaml/yaml,MIT AND Apache-2.0,"Copyright (c) 2006-2011 Kirill Simonov, 2011-2019 Canonical Ltd"
mvdan.cc/sh/v3,https://github.com/mvdan/sh,BSD-3-Clause,"Copyright (c) 2016, Daniel Marti"
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Linux, macOS, and Windows.

```
tests/scenarios/
├── cmd/ # builtin command tests (echo, cat, grep, head, tail, test, uniq, wc, ...)
├── cmd/ # builtin command tests (echo, cat, grep, head, tail, test, uniq, wc, ping, ...)
└── shell/ # shell feature tests (pipes, variables, control flow, ...)
```

Expand Down
1 change: 1 addition & 0 deletions SHELL_FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Blocked features are rejected before execution with exit code 2.
- ✅ `help` — display all available builtin commands with brief descriptions; for detailed flag info, use `<command> --help`
- ✅ `sort [-rnubfds] [-k KEYDEF] [-t SEP] [-c|-C] [FILE]...` — sort lines of text files; `-o`, `--compress-program`, and `-T` are rejected (filesystem write / exec)
- ✅ `ls [-1aAdFhlpRrSt] [--offset N] [--limit N] [FILE]...` — list directory contents; `--offset`/`--limit` are non-standard pagination flags (single-directory only, silently ignored with `-R` or multiple arguments, capped at 1,000 entries per call); offset operates on filesystem order (not sorted order) for O(n) memory
- ✅ `ping [-c N] [-W N] [-i N] HOST` — send ICMP ECHO_REQUEST to network hosts using the datadog-traceroute library; `-c` sets probe count (default 4, max 1000), `-W` sets timeout in seconds (max 60), `-i` sets interval between probes (accepted and validated but may not be effective with current library version; the underlying library computes E2E probe spacing internally)
- ✅ `printf FORMAT [ARGUMENT]...` — format and print data to stdout; supports `%s`, `%b`, `%c`, `%d`, `%i`, `%o`, `%u`, `%x`, `%X`, `%e`, `%E`, `%f`, `%F`, `%g`, `%G`, `%%`; format reuse for excess arguments; `%n` rejected (security risk); `-v` rejected
- ✅ `sed [-n] [-e SCRIPT] [-E|-r] [SCRIPT] [FILE]...` — stream editor for filtering and transforming text; uses RE2 regex engine; `-i`/`-f` rejected; `e`/`w`/`W`/`r`/`R` commands blocked
- ✅ `strings [-a] [-n MIN] [-t o|d|x] [-o] [-f] [-s SEP] [FILE]...` — print printable character sequences in files (default min length 4); offsets via `-t`/`-o`; filename prefix via `-f`; custom separator via `-s`
Expand Down
35 changes: 23 additions & 12 deletions allowedsymbols/symbols_builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,14 @@ var builtinPerCommandSymbols = map[string][]string{
"slices.SortFunc", // sorts a slice with a comparison function; pure function, no I/O.
"time.Time", // time value type; pure data, no side effects.
},
"ping": {
"context.Context", // deadline/cancellation plumbing; pure interface, no side effects.
"github.com/DataDog/datadog-traceroute/result.Results", // result struct returned by RunTraceroute; pure data type, no side effects.
"github.com/DataDog/datadog-traceroute/traceroute.NewTraceroute", // creates a traceroute runner for ICMP probes; network I/O (authorized for ping).
"github.com/DataDog/datadog-traceroute/traceroute.TracerouteParams", // parameter struct for traceroute configuration; pure data type.
"time.Duration", // duration type; pure integer alias, no I/O.
"time.Second", // constant representing one second; no side effects.
},
"printf": {
"context.Context", // deadline/cancellation plumbing; pure interface, no side effects.
"errors.As", // error type assertion; pure function, no I/O.
Expand Down Expand Up @@ -297,18 +305,21 @@ var builtinPerCommandSymbols = map[string][]string{
}

var builtinAllowedSymbols = []string{
"bufio.NewScanner", // line-by-line input reading (e.g. head, cat); no write or exec capability.
"bufio.Scanner", // scanner type for buffered input reading; no write or exec capability.
"bufio.SplitFunc", // type for custom scanner split functions; pure type, no I/O.
"bytes.Equal", // compares two byte slices for equality; pure function, no I/O.
"bytes.IndexByte", // finds a byte in a byte slice; pure function, no I/O.
"bytes.NewReader", // wraps a byte slice as an io.Reader; pure in-memory, no I/O.
"context.Context", // deadline/cancellation plumbing; pure interface, no side effects.
"errors.As", // error type assertion; pure function, no I/O.
"errors.Is", // error comparison; pure function, no I/O.
"errors.New", // creates a simple error value; pure function, no I/O.
"fmt.Errorf", // error formatting; pure function, no I/O.
"fmt.Sprintf", // string formatting; pure function, no I/O.
"bufio.NewScanner", // line-by-line input reading (e.g. head, cat); no write or exec capability.
"bufio.Scanner", // scanner type for buffered input reading; no write or exec capability.
"bufio.SplitFunc", // type for custom scanner split functions; pure type, no I/O.
"bytes.Equal", // compares two byte slices for equality; pure function, no I/O.
"bytes.IndexByte", // finds a byte in a byte slice; pure function, no I/O.
"bytes.NewReader", // wraps a byte slice as an io.Reader; pure in-memory, no I/O.
"context.Context", // deadline/cancellation plumbing; pure interface, no side effects.
"errors.As", // error type assertion; pure function, no I/O.
"errors.Is", // error comparison; pure function, no I/O.
"errors.New", // creates a simple error value; pure function, no I/O.
"fmt.Errorf", // error formatting; pure function, no I/O.
"fmt.Sprintf", // string formatting; pure function, no I/O.
"github.com/DataDog/datadog-traceroute/result.Results", // result struct returned by RunTraceroute; pure data type, no side effects.
"github.com/DataDog/datadog-traceroute/traceroute.NewTraceroute", // creates a traceroute runner for ICMP probes; network I/O (authorized for ping builtin only via per-command allowlist).
"github.com/DataDog/datadog-traceroute/traceroute.TracerouteParams", // parameter struct for traceroute configuration; pure data type.
"io.EOF", // sentinel error value; pure constant.
"io.MultiReader", // combines multiple Readers into one sequential Reader; no I/O side effects.
"io.NopCloser", // wraps a Reader with a no-op Close; no side effects.
Expand Down
125 changes: 125 additions & 0 deletions builtins/ping/builtin_ping_pentest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2026-present Datadog, Inc.

package ping_test

import (
"context"
"os"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

// --- Integer edge cases for -c (count) — validation only, no network ---

func TestPingPentestCountOverflow(t *testing.T) {
dir := t.TempDir()
// Value exceeding int64 range: pflag returns a parse error.
_, stderr, code := cmdRun(t, "ping -c 99999999999999999999 localhost", dir)
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "ping:")
}

func TestPingPentestCountEmpty(t *testing.T) {
dir := t.TempDir()
// -c with no value: pflag error.
_, stderr, code := cmdRun(t, "ping -c", dir)
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "ping:")
}

// --- Integer edge cases for -W (timeout) — validation only ---

func TestPingPentestTimeoutOverflow(t *testing.T) {
dir := t.TempDir()
_, stderr, code := cmdRun(t, "ping -c 1 -W 99999999999999999999 localhost", dir)
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "ping:")
}

// --- Flag and argument injection ---

func TestPingPentestDoubleDash(t *testing.T) {
if os.Getenv("RSHELL_PING_TEST") == "" {
t.Skip("skipping ping integration test; set RSHELL_PING_TEST=1 and run with sudo on Linux")
}
dir := t.TempDir()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// -- separates flags from positional args.
// "ping -- --follow" should treat "--follow" as the hostname, not a flag.
_, stderr, _ := runScriptCtx(ctx, t, "ping -c 1 -- --follow", dir)
assert.NotContains(t, stderr, "unknown flag")
}

func TestPingPentestFlagViaExpansion(t *testing.T) {
dir := t.TempDir()
// Flag-like value via variable expansion.
_, stderr, code := cmdRun(t, `flag="-f"; ping $flag localhost`, dir)
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "unknown shorthand flag")
}

func TestPingPentestMultipleUnknownFlags(t *testing.T) {
dir := t.TempDir()
_, stderr, code := cmdRun(t, "ping -f -F --exec localhost", dir)
assert.Equal(t, 1, code)
assert.Contains(t, stderr, "ping:")
}

// --- Hostname edge cases (these hit the network, need short-mode guard) ---

func TestPingPentestEmptyHostname(t *testing.T) {
if os.Getenv("RSHELL_PING_TEST") == "" {
t.Skip("skipping ping integration test; set RSHELL_PING_TEST=1 and run with sudo on Linux")
}
dir := t.TempDir()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, _, code := runScriptCtx(ctx, t, `ping -c 1 ""`, dir)
assert.Equal(t, 1, code)
}

func TestPingPentestHostnameWithSpaces(t *testing.T) {
if os.Getenv("RSHELL_PING_TEST") == "" {
t.Skip("skipping ping integration test; set RSHELL_PING_TEST=1 and run with sudo on Linux")
}
dir := t.TempDir()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, _, code := runScriptCtx(ctx, t, `ping -c 1 "host with spaces"`, dir)
assert.Equal(t, 1, code)
}

func TestPingPentestHostnameInjection(t *testing.T) {
if os.Getenv("RSHELL_PING_TEST") == "" {
t.Skip("skipping ping integration test; set RSHELL_PING_TEST=1 and run with sudo on Linux")
}
dir := t.TempDir()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Hostname with shell metacharacters in quotes — passed as literal hostname.
// Should fail on DNS, not execute injected commands.
_, _, code := runScriptCtx(ctx, t, `ping -c 1 '; echo pwned'`, dir)
assert.Equal(t, 1, code)
}

// --- Count clamping (needs network) ---

func TestPingPentestCountMaxIntClamped(t *testing.T) {
if os.Getenv("RSHELL_PING_TEST") == "" {
t.Skip("skipping ping integration test; set RSHELL_PING_TEST=1 and run with sudo on Linux")
}
dir := t.TempDir()
// Large count gets clamped to MaxCount (1000). With a 5s timeout context,
// the traceroute will be interrupted before completing all probes.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, stderr, _ := runScriptCtx(ctx, t, "ping -c 2147483647 127.0.0.1", dir)
// Should NOT get "invalid count" — it was clamped, not rejected.
assert.NotContains(t, stderr, "invalid count")
}
Loading
Loading