railiance-forge/tools/receive-secret-scan/batch.go
tegwick ea65606c12
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Batch small receive inputs within the unchanged scan budget
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a14bfc-9643-7872-aa12-98326dcf25e7
2026-10-18 13:25:08 +02:00

192 lines
6.3 KiB
Go

package main
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"syscall"
)
// Same bounded small-input profile as Repo Manager's staged adapter. Above
// PIPE_BUF, keep the original stdin reader/fragment path byte-for-byte.
const directoryInputBytes = 4096
const directoryBatchInputs = 64
const maxReportBytes = 1024 * 1024
type scanInput struct {
name string
content []byte
}
func verifyScannerFD(f *os.File) error {
st, err := f.Stat()
if err != nil {
return errors.New("scanner-file-refused")
}
native, ok := st.Sys().(*syscall.Stat_t)
if !ok || !st.Mode().IsRegular() || st.Mode().Perm()&022 != 0 || st.Mode().Perm()&0111 == 0 || st.Size() > 64*1024*1024 || (native.Uid != 0 && native.Uid != uint32(os.Getuid())) {
return errors.New("scanner-file-refused")
}
if _, err = f.Seek(0, 0); err != nil {
return errors.New("scanner-file-refused")
}
hash := sha256.New()
if _, err = io.Copy(hash, f); err != nil || hex.EncodeToString(hash.Sum(nil)) != scannerHash {
return errors.New("scanner-digest-mismatch")
}
if _, err = f.Seek(0, 0); err != nil {
return errors.New("scanner-file-refused")
}
return nil
}
// Trusted child mode establishes resource limits before exec of the exact
// independently pinned scanner descriptor. It accepts no repository commands.
func batchScannerProcess(args []string) int {
if len(args) != 2 {
return 127
}
if _, err := volatileIdentity(args[1]); err != nil {
return 127
}
f := os.NewFile(3, "pinned-scanner")
if f == nil || verifyScannerFD(f) != nil {
return 127
}
defer f.Close()
for resource, limit := range map[int]uint64{syscall.RLIMIT_FSIZE: maxReportBytes, syscall.RLIMIT_CPU: 20, syscall.RLIMIT_AS: 8 * 1024 * 1024 * 1024} {
if syscall.Setrlimit(resource, &syscall.Rlimit{Cur: limit, Max: limit}) != nil {
return 127
}
}
if os.Chdir(args[1]) != nil {
return 127
}
argv := []string{"gitleaks", "dir", "--config", filepath.Join(args[0], "scanner.toml"), "--gitleaks-ignore-path", filepath.Join(args[0], "ignore"), "--ignore-gitleaks-allow", "--redact=100", "--no-banner", "--no-color", "--log-level", "warn", "--exit-code", "20", "--timeout", "15", "--report-format", "template", "--report-template", filepath.Join(args[0], "template"), "--report-path", filepath.Join(args[0], "report.paths"), "--max-target-megabytes", "0", "payload"}
if syscall.Exec("/proc/self/fd/3", argv, []string{"PATH=/usr/bin:/bin", "HOME=" + args[1], "TMPDIR=" + args[1], "LANG=C"}) != nil {
return 127
}
return 127
}
var safeInputName = regexp.MustCompile(`^(?:[0-9a-f]{40}|[0-9a-f]{64}|ref-[0-9]{1,3})$`)
var leakSummary = regexp.MustCompile(`^[0-9]{1,2}:[0-9]{2}(?:AM|PM) WRN leaks found: [1-9][0-9]*\n$`)
func validateBatchDiagnostics(code int, diagnostics []byte) error {
if code != 0 && code != 20 || len(diagnostics) > 0 && !(code == 20 && leakSummary.Match(diagnostics)) {
return errors.New("scanner-failed")
}
return nil
}
func batchFindings(code int, raw []byte, known map[string]string) (map[string]bool, error) {
findings := map[string]bool{}
if len(raw) > 0 {
if !bytes.HasSuffix(raw, []byte("\n")) {
return nil, errors.New("scanner-report-refused")
}
for _, line := range strings.Split(string(raw[:len(raw)-1]), "\n") {
name, ok := known[line]
if !ok {
return nil, errors.New("scanner-report-refused")
}
findings[name] = true
}
}
if (code != 0 && code != 20) || (len(findings) > 0) != (code == 20) {
return nil, errors.New("scanner-report-refused")
}
return findings, nil
}
func scanSmall(ctx context.Context, scanner *os.File, config string, storage *volatileStorage, inputs []scanInput) (blocked string, err error) {
if len(inputs) == 0 || len(inputs) > directoryBatchInputs || !storage.live() {
return "", errors.New("cleanup-watchdog-unavailable")
}
id, e := volatileIdentity(storage.path)
if e != nil || id != storage.id {
return "", errors.New("volatile-storage-identity-changed")
}
payload := filepath.Join(storage.path, "payload")
if os.Mkdir(payload, 0700) != nil {
return "", errors.New("volatile-storage-unavailable")
}
defer func() {
if os.RemoveAll(payload) != nil && err == nil {
err = errors.New("volatile-storage-cleanup-failed")
}
}()
known := map[string]string{}
for _, item := range inputs {
if !safeInputName.MatchString(item.name) || len(item.content) > directoryInputBytes || known["payload/"+item.name] != "" {
return "", errors.New("invalid-scan-batch")
}
known["payload/"+item.name] = item.name
file, e := os.OpenFile(filepath.Join(payload, item.name), os.O_WRONLY|os.O_CREATE|os.O_EXCL|syscall.O_NOFOLLOW, 0600)
if e != nil {
return "", errors.New("volatile-storage-write-failed")
}
_, e = file.Write(item.content)
closed := file.Close()
if e != nil || closed != nil {
return "", errors.New("volatile-storage-write-failed")
}
}
if os.WriteFile(filepath.Join(config, "template"), []byte("{{range .}}{{.File}}\n{{end}}"), 0600) != nil {
return "", errors.New("scanner-config-unavailable")
}
report := filepath.Join(config, "report.paths")
if e = os.Remove(report); e != nil && !os.IsNotExist(e) {
return "", errors.New("scanner-report-refused")
}
cmd := exec.CommandContext(ctx, "/proc/self/exe", "--batch-scanner", config, storage.path)
cmd.ExtraFiles = []*os.File{scanner}
cmd.Env = []string{"PATH=/usr/bin:/bin", "LANG=C"}
diagnostics := &cappedBuffer{limit: maxReportBytes}
cmd.Stdout = io.Discard
cmd.Stderr = diagnostics
runErr := runChild(cmd)
if ctx.Err() != nil {
return "", errors.New("scan-deadline-exceeded")
}
code := 0
var exit *exec.ExitError
if runErr != nil {
if !errors.As(runErr, &exit) {
return "", errors.New("scanner-failed")
}
code = exit.ExitCode()
}
if e = validateBatchDiagnostics(code, diagnostics.Bytes()); e != nil {
return "", e
}
st, e := os.Lstat(report)
if e != nil || !st.Mode().IsRegular() || st.Size() > maxReportBytes {
return "", errors.New("scanner-report-refused")
}
raw, e := os.ReadFile(report)
if e != nil {
return "", errors.New("scanner-report-refused")
}
findings, e := batchFindings(code, raw, known)
if e != nil {
return "", e
}
if !storage.live() {
return "", errors.New("scanner-report-refused")
}
for _, item := range inputs {
if findings[item.name] {
return item.name, nil
}
}
return "", nil
}