Add allowlisted markdown indexing, read-only git plumbing via injected runners, and docker/readiness collection with path and loopback guards. Co-authored-by: Cursor <cursoragent@cursor.com>
176 lines
4.2 KiB
Go
176 lines
4.2 KiB
Go
package engineeringdashboard
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
defaultCommandTimeout = 2 * time.Second
|
|
defaultOutputLimit = 512 * 1024
|
|
)
|
|
|
|
// CommandRunner executes fixed argv without a shell.
|
|
type CommandRunner interface {
|
|
Run(ctx context.Context, dir string, name string, args ...string) (stdout string, stderr string, err error)
|
|
}
|
|
|
|
// ExecRunner is the production runner using os/exec.
|
|
type ExecRunner struct {
|
|
Timeout time.Duration
|
|
OutputLimit int
|
|
}
|
|
|
|
func (r ExecRunner) Run(ctx context.Context, dir string, name string, args ...string) (string, string, error) {
|
|
if name == "" {
|
|
return "", "", errors.New("empty command name")
|
|
}
|
|
if err := denyDangerousGitArgs(name, args); err != nil {
|
|
return "", "", err
|
|
}
|
|
timeout := r.Timeout
|
|
if timeout <= 0 {
|
|
timeout = defaultCommandTimeout
|
|
}
|
|
limit := r.OutputLimit
|
|
if limit <= 0 {
|
|
limit = defaultOutputLimit
|
|
}
|
|
cctx, cancel := context.WithTimeout(ctx, timeout)
|
|
defer cancel()
|
|
cmd := exec.CommandContext(cctx, name, args...)
|
|
cmd.Dir = dir
|
|
var stdout, stderr bytes.Buffer
|
|
cmd.Stdout = &limitedWriter{buf: &stdout, limit: limit}
|
|
cmd.Stderr = &limitedWriter{buf: &stderr, limit: limit}
|
|
err := cmd.Run()
|
|
out := sanitizeOutput(stdout.String())
|
|
errOut := sanitizeOutput(stderr.String())
|
|
if cctx.Err() == context.DeadlineExceeded {
|
|
return out, errOut, fmt.Errorf("command timed out: %s", name)
|
|
}
|
|
return out, errOut, err
|
|
}
|
|
|
|
type limitedWriter struct {
|
|
buf *bytes.Buffer
|
|
limit int
|
|
n int
|
|
}
|
|
|
|
func (w *limitedWriter) Write(p []byte) (int, error) {
|
|
remain := w.limit - w.n
|
|
if remain <= 0 {
|
|
return len(p), nil
|
|
}
|
|
if len(p) > remain {
|
|
p = p[:remain]
|
|
}
|
|
n, err := w.buf.Write(p)
|
|
w.n += n
|
|
return len(p), err
|
|
}
|
|
|
|
func sanitizeOutput(s string) string {
|
|
lower := strings.ToLower(s)
|
|
for _, needle := range []string{"password=", "secret=", "token=", "access_token=", "api_key="} {
|
|
if idx := strings.Index(lower, needle); idx >= 0 {
|
|
return s[:idx] + needle + "[redacted]"
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
func denyDangerousGitArgs(name string, args []string) error {
|
|
base := name
|
|
if i := strings.LastIndex(name, "/"); i >= 0 {
|
|
base = name[i+1:]
|
|
}
|
|
if base != "git" {
|
|
return nil
|
|
}
|
|
banned := map[string]bool{
|
|
"fetch": true, "push": true, "pull": true, "clone": true,
|
|
"commit": true, "checkout": true, "merge": true, "rebase": true,
|
|
"reset": true, "clean": true, "add": true, "tag": true,
|
|
}
|
|
for _, a := range args {
|
|
if strings.HasPrefix(a, "-") {
|
|
continue
|
|
}
|
|
if banned[a] {
|
|
return fmt.Errorf("git write/network command denied: %s", a)
|
|
}
|
|
break
|
|
}
|
|
joined := strings.Join(args, " ")
|
|
if strings.Contains(joined, "&&") || strings.Contains(joined, "|") || strings.Contains(joined, ";") {
|
|
return errors.New("shell metacharacters denied")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FakeRunner records invocations for tests.
|
|
type FakeRunner struct {
|
|
Calls [][]string
|
|
Map map[string]FakeResult // key: name + " " + joined args
|
|
}
|
|
|
|
// FakeResult is a canned command result.
|
|
type FakeResult struct {
|
|
Stdout string
|
|
Stderr string
|
|
Err error
|
|
}
|
|
|
|
func (f *FakeRunner) Run(_ context.Context, _ string, name string, args ...string) (string, string, error) {
|
|
key := name + " " + strings.Join(args, " ")
|
|
f.Calls = append(f.Calls, append([]string{name}, args...))
|
|
if f.Map != nil {
|
|
if res, ok := f.Map[key]; ok {
|
|
return res.Stdout, res.Stderr, res.Err
|
|
}
|
|
// prefix match for flexibility
|
|
for k, res := range f.Map {
|
|
if strings.HasPrefix(key, k) {
|
|
return res.Stdout, res.Stderr, res.Err
|
|
}
|
|
}
|
|
}
|
|
return "", "", fmt.Errorf("no fake result for %q", key)
|
|
}
|
|
|
|
func (f *FakeRunner) AssertNoForbidden(t interface{ Fatalf(string, ...any) }) {
|
|
for _, call := range f.Calls {
|
|
if len(call) == 0 {
|
|
continue
|
|
}
|
|
name := call[0]
|
|
base := name
|
|
if i := strings.LastIndex(name, "/"); i >= 0 {
|
|
base = name[i+1:]
|
|
}
|
|
if base == "sh" || base == "bash" || base == "zsh" {
|
|
t.Fatalf("forbidden shell observed: %v", call)
|
|
}
|
|
joined := strings.Join(call, " ")
|
|
if base == "git" {
|
|
for _, a := range call[1:] {
|
|
if strings.HasPrefix(a, "-") {
|
|
continue
|
|
}
|
|
switch a {
|
|
case "fetch", "push", "pull", "clone", "commit", "checkout", "merge", "rebase", "reset", "clean", "add", "tag":
|
|
t.Fatalf("forbidden git command observed: %s", joined)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|