-
Notifications
You must be signed in to change notification settings - Fork 2
feat(interp): host-binary exec via host:<name>=<path> [DEMO ONLY] #228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
julesmcrt
wants to merge
6
commits into
jules.macret/host-remediation/truncate
Choose a base branch
from
jules.macret/host-remediation/logrotate-host-exec
base: jules.macret/host-remediation/truncate
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
c43bfe1
feat(interp): host-binary exec via host:<name>=<path> [DEMO ONLY]
julesmcrt fa5e47a
fix(interp): gate host: entries on !isKnown so they cannot authorize …
julesmcrt 8aedb5c
fix(interp): make host-exec tests actually exercise host path; use ab…
julesmcrt f28fe13
fix(interp): surface ctx.Err() from host-exec so timeout/cancel propa…
julesmcrt fd93d4b
fix(interp): read host-exec env from runner overlay, not ambient proc…
julesmcrt d4bd28b
fix(interp): require vr.Exported in host-exec env filter to match bash
julesmcrt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| // 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. | ||
|
|
||
| //go:build linux | ||
|
|
||
| // DEMO ONLY: this file enables rshell to execute a small allowlist of host | ||
| // binaries. Running host binaries fundamentally changes rshell's threat | ||
| // model — the entire reason rshell exists is to *not* execute host binaries. | ||
| // This entry-point exists for the host-remediation demo (see | ||
| // docs/RULES.md / SHELL_FEATURES.md "demo only" section) and would need a | ||
| // real design pass before becoming a product feature. | ||
|
|
||
| package interp | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "os/exec" | ||
| ) | ||
|
|
||
| // hostEnvAllowlist is the set of environment variable names forwarded to | ||
| // host binaries. Anything else in the runner environment is stripped so | ||
| // that host invocations do not leak ambient configuration that the rest | ||
| // of the shell deliberately keeps out. | ||
| var hostEnvAllowlist = []string{"PATH", "HOME", "LANG"} | ||
|
|
||
| // runHostCommand executes the host binary at path with args[1:] as its argv, | ||
| // plumbing the runner's stdin/stdout/stderr through. It returns the binary's | ||
| // exit code as a uint8 (so $? works) and propagates context cancellation by | ||
| // killing the process with SIGKILL — exec.CommandContext's default Cancel | ||
| // uses os.Kill (SIGKILL on Unix), which matches the timeout behaviour the | ||
| // rest of the runner applies to builtins. | ||
| // | ||
| // args[0] is the user-visible command name; args[1:] is the binary's argv. | ||
| // The binary path itself comes from the hostCommands allowlist entry, not | ||
| // from args, so PATH lookup is intentionally not performed. | ||
| func (r *Runner) runHostCommand(ctx context.Context, path string, args []string) uint8 { | ||
| cmd := exec.CommandContext(ctx, path, args[1:]...) | ||
| cmd.Dir = r.Dir | ||
| cmd.Env = r.filterHostEnv() | ||
| if r.stdin != nil { | ||
| cmd.Stdin = r.stdin | ||
| } | ||
| cmd.Stdout = r.stdout | ||
| cmd.Stderr = r.stderr | ||
|
|
||
| err := cmd.Run() | ||
| // If the runner's context expired (MaxExecutionTime, parent cancel, | ||
| // or builtin-style cooperative cancel), exec.CommandContext kills | ||
| // the child with SIGKILL and cmd.Run returns *exec.ExitError with | ||
| // ExitCode() == -1. We must surface ctx.Err() back through Run() | ||
| // rather than mapping the signal to a numeric exit code, otherwise | ||
| // Run() returns ExitStatus(130) and the CLI's timeout path | ||
| // (context.DeadlineExceeded → "execution timed out", exit 124) | ||
| // never fires and run-span telemetry is misclassified as success. | ||
| // Use exit.fatal so the err is recorded; the returned uint8 is only | ||
| // observed when r.exit.err is nil, so the value is symbolic here. | ||
| if ctxErr := ctx.Err(); ctxErr != nil { | ||
| r.exit.fatal(ctxErr) | ||
| return 130 | ||
| } | ||
| if err == nil { | ||
| return 0 | ||
| } | ||
| var exitErr *exec.ExitError | ||
| if errors.As(err, &exitErr) { | ||
| // ExitCode returns -1 if the process was terminated by a signal | ||
| // without a context cancel having fired (e.g. an external | ||
| // SIGKILL from outside this process). Map that to 130 — the | ||
| // shell-conventional "terminated" code — so the caller can still | ||
| // observe a non-zero exit. | ||
| code := exitErr.ExitCode() | ||
| if code < 0 { | ||
| return 130 | ||
| } | ||
| return uint8(code) | ||
|
julesmcrt marked this conversation as resolved.
|
||
| } | ||
| // Failure to start or some other I/O error: surface to stderr and | ||
| // return 127 (the shell convention for "command not found / not | ||
| // executable"). | ||
| r.errf("rshell: %s: %v\n", args[0], err) | ||
| return 127 | ||
| } | ||
|
|
||
| // filterHostEnv builds a minimal env slice for host binaries from the | ||
| // runner's environment overlay (r.writeEnv) — NOT the ambient Go | ||
| // process env — forwarding only the names in hostEnvAllowlist that | ||
| // are also marked Exported. Matches bash semantics: a script-level | ||
| // assignment like `PATH=/tmp; hostcmd` does not propagate to the | ||
| // child unless PATH was previously exported (via interp.Env or an | ||
| // `export` statement). Inline command assignments | ||
| // (`PATH=/safe hostcmd`) propagate because call() forces | ||
| // vr.Exported = true before dispatch. | ||
| // | ||
| // Reading from r.writeEnv is what makes the runner's documented | ||
| // "empty by default, no host env inherited" guarantee hold for host | ||
| // binaries: an unset PATH/HOME/LANG in the runner is simply omitted, | ||
| // regardless of what the surrounding Go process exports. | ||
| func (r *Runner) filterHostEnv() []string { | ||
| out := make([]string, 0, len(hostEnvAllowlist)) | ||
| for _, name := range hostEnvAllowlist { | ||
| vr := r.writeEnv.Get(name) | ||
| if !vr.Declared() || !vr.Exported { | ||
| continue | ||
| } | ||
| out = append(out, name+"="+vr.String()) | ||
| } | ||
| return out | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // 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. | ||
|
|
||
| //go:build !linux | ||
|
|
||
| // DEMO ONLY: see host_exec_linux.go. The host-binary entry-point is a | ||
| // Linux-only demo; on darwin/windows we simply refuse to dispatch. | ||
|
|
||
| package interp | ||
|
|
||
| import "context" | ||
|
|
||
| // runHostCommand is a stub for non-Linux platforms. It writes an explanatory | ||
| // error to the runner's stderr and returns 127 (shell-conventional "command | ||
| // not found / not executable") so callers can rely on a non-zero exit. | ||
| func (r *Runner) runHostCommand(_ context.Context, _ string, args []string) uint8 { | ||
| r.errf("rshell: %s: host execution not supported on this platform\n", args[0]) | ||
| return 127 | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.