Batch small receive inputs within the unchanged scan budget
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a14bfc-9643-7872-aa12-98326dcf25e7
This commit is contained in:
parent
b42206e52b
commit
ea65606c12
6 changed files with 995 additions and 22 deletions
|
|
@ -9,7 +9,8 @@ The source adapter is not evidence of installed server enforcement.
|
|||
The forge-owned, standalone Linux adapter composes with Forgejo's generated
|
||||
pre-receive dispatcher. It uses pinned Gitleaks 8.30.1 Linux amd64
|
||||
(SHA256 `88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509`).
|
||||
It requires `/usr/bin/git` and `/proc` but no Python runtime, issuer credential,
|
||||
It requires `/usr/bin/git`, `/proc`, a private writable tmpfs at `/dev/shm`,
|
||||
and Linux pidfd support (5.3+ on amd64/arm64) but no Python runtime, issuer credential,
|
||||
network access, source checkout, or repository-controlled workflow. The current
|
||||
Forgejo 11.0.3 image has no Python interpreter; this keeps the runtime change to
|
||||
two reviewed executables and a hook fragment, subject to native admission.
|
||||
|
|
@ -67,6 +68,26 @@ bytes are checked before scanning payloads. Limits reject rather
|
|||
than skip. Large imports need an admitted path; do not disable the gate to pass
|
||||
an unreviewed import. Git/scanner versions and limits require native load testing.
|
||||
|
||||
FORGE-WP-0010 corrects per-small-input scanner startup overhead without changing
|
||||
these limits. It reuses Repo Manager's bounded profile: at most 64 inputs of at
|
||||
most 4096 bytes per directory invocation. Larger inputs retain the existing stdin
|
||||
reader and fragment behavior. Raw inputs use 0700 private tmpfs directories and
|
||||
0600 files with safe object/ref identifiers; no source path controls a filename.
|
||||
Missing or nonvolatile storage refuses instead of falling back to ordinary disk.
|
||||
|
||||
A separately supervised child pins its receiver with pidfd, acknowledges readiness
|
||||
before payload writes, and cleans up on pipe EOF. A 180-second backstop terminates
|
||||
the exact stalled receiver before cleanup. Git/scanner children die when their
|
||||
creating receiver thread dies; it is pinned to its OS thread during each child.
|
||||
Normal cleanup and failed cleanup are checked. Directory identity replacement
|
||||
refuses deletion. This is not protection against a hostile same-UID host process.
|
||||
|
||||
Directory scans use the same pinned scanner/default config and explicit empty
|
||||
ignore file. Bounded reports contain only known safe payload identifiers. Missing,
|
||||
malformed, oversized or inconsistent reports, unknown diagnostics and scanner or
|
||||
watchdog failures refuse. Only the exact pinned scanner's positive-finding summary
|
||||
is accepted as a warning. The original stdin route is otherwise unchanged.
|
||||
|
||||
The adapter does not rescan objects already reachable before the push, scan
|
||||
unreachable extra pack objects, LFS payloads, other repositories, runtime logs,
|
||||
artifacts or credentials outside Git. It is not an authorization boundary against
|
||||
|
|
@ -82,8 +103,10 @@ wikis, archives, mirrors and new-repository lifecycle before claiming DSM covera
|
|||
|
||||
## Build and disposable acceptance
|
||||
|
||||
Use the admitted Go toolchain (the current candidate uses official Go 1.26.9,
|
||||
verified against the SHA256 from go.dev release metadata). No third-party Go
|
||||
Use an admitted Go toolchain. The original deployed revision used Go 1.26.9,
|
||||
verified against the SHA256 from go.dev release metadata. The FORGE-WP-0010
|
||||
source candidate is separately tested with installed Go 1.26.7; its new source,
|
||||
compiler and exact executable still require native admission. No third-party Go
|
||||
modules are required. Keep binaries and logs outside the repository:
|
||||
|
||||
```sh
|
||||
|
|
|
|||
192
tools/receive-secret-scan/batch.go
Normal file
192
tools/receive-secret-scan/batch.go
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
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
|
||||
}
|
||||
323
tools/receive-secret-scan/batch_test.go
Normal file
323
tools/receive-secret-scan/batch_test.go
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if code, handled := internalProcess(os.Args[1:]); handled {
|
||||
os.Exit(code)
|
||||
}
|
||||
if len(os.Args) == 2 && os.Args[1] == "--test-storage-refusal" {
|
||||
storage, err := newVolatileStorage()
|
||||
if err == nil {
|
||||
storage.close()
|
||||
os.Exit(3)
|
||||
}
|
||||
json.NewEncoder(os.Stdout).Encode(map[string]string{"status": "unverified", "reason": err.Error()})
|
||||
os.Exit(0)
|
||||
}
|
||||
if len(os.Args) == 2 && os.Args[1] == "--test-storage-parent" {
|
||||
storage, err := newVolatileStorage()
|
||||
if err != nil {
|
||||
os.Exit(3)
|
||||
}
|
||||
if os.WriteFile(filepath.Join(storage.path, "input"), []byte("synthetic private test input"), 0600) != nil {
|
||||
os.Exit(3)
|
||||
}
|
||||
json.NewEncoder(os.Stdout).Encode(map[string]any{"path": storage.path, "watchdog": storage.command.Process.Pid})
|
||||
child := exec.Command("/proc/self/exe", "--test-storage-reader", storage.path)
|
||||
child.Stdout = os.Stdout
|
||||
if runChild(child) != nil {
|
||||
os.Exit(4)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
if len(os.Args) == 3 && os.Args[1] == "--test-storage-reader" {
|
||||
f, err := os.Open(filepath.Join(os.Args[2], "input"))
|
||||
if err != nil {
|
||||
os.Exit(3)
|
||||
}
|
||||
defer f.Close()
|
||||
fmt.Fprintln(os.Stdout, os.Getpid())
|
||||
time.Sleep(time.Minute)
|
||||
os.Exit(0)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestVolatileStorageLifecycle(t *testing.T) {
|
||||
storage, err := newVolatileStorage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !storage.live() {
|
||||
t.Fatal("watchdog did not remain live")
|
||||
}
|
||||
if err = os.WriteFile(filepath.Join(storage.path, "input"), []byte("synthetic input"), 0600); err != nil {
|
||||
t.Fatal("cannot write private fixture")
|
||||
}
|
||||
if err = storage.close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = os.Lstat(storage.path); !os.IsNotExist(err) {
|
||||
t.Fatal("private storage retained")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVolatileIdentityRefusesReplacement(t *testing.T) {
|
||||
storage, err := newVolatileStorage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Keep the original inode alive so a new directory cannot reuse it.
|
||||
original := storage.path + "-original"
|
||||
if os.Rename(storage.path, original) != nil || os.Mkdir(storage.path, 0700) != nil {
|
||||
t.Fatal("cannot prepare replacement")
|
||||
}
|
||||
defer os.RemoveAll(original)
|
||||
defer os.RemoveAll(storage.path)
|
||||
if err = storage.close(); err == nil {
|
||||
t.Fatal("replacement directory accepted")
|
||||
}
|
||||
if _, err = os.Stat(storage.path); err != nil {
|
||||
t.Fatal("replacement directory was removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReceiverDeathCleansStorageAndStopsReader(t *testing.T) {
|
||||
cmd := exec.Command("/proc/self/exe", "--test-storage-parent")
|
||||
pipe, err := cmd.StdoutPipe()
|
||||
if err != nil || cmd.Start() != nil {
|
||||
t.Fatal("cannot start disposable parent")
|
||||
}
|
||||
defer cmd.Process.Kill()
|
||||
reader := bufio.NewReader(pipe)
|
||||
line, err := reader.ReadBytes('\n')
|
||||
var receipt struct {
|
||||
Path string
|
||||
Watchdog int
|
||||
}
|
||||
if err != nil || json.Unmarshal(line, &receipt) != nil || receipt.Path == "" {
|
||||
t.Fatal("disposable parent did not prepare storage")
|
||||
}
|
||||
var childPID int
|
||||
if _, err = fmt.Fscanln(reader, &childPID); err != nil || childPID < 2 {
|
||||
t.Fatal("disposable reader did not open input")
|
||||
}
|
||||
if cmd.Process.Kill() != nil {
|
||||
t.Fatal("cannot kill disposable parent")
|
||||
}
|
||||
cmd.Wait()
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
_, pathErr := os.Lstat(receipt.Path)
|
||||
stat, procErr := os.ReadFile(fmt.Sprintf("/proc/%d/stat", childPID))
|
||||
terminal := os.IsNotExist(procErr)
|
||||
if procErr == nil {
|
||||
// Zombies are terminal; the private PID namespace reaps them.
|
||||
fields := strings.Fields(string(stat)[strings.LastIndex(string(stat), ")")+1:])
|
||||
terminal = len(fields) > 0 && fields[0] == "Z"
|
||||
}
|
||||
if os.IsNotExist(pathErr) && terminal {
|
||||
return
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
t.Fatal("receiver loss retained private input or live reader")
|
||||
}
|
||||
|
||||
func TestSmallInputParity(t *testing.T) {
|
||||
scanner := os.Getenv("FORGE_TEST_GITLEAKS")
|
||||
if scanner == "" {
|
||||
t.Skip("requires explicitly admitted disposable Gitleaks binary")
|
||||
}
|
||||
f, err := pinScanner(scanner)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
storage, err := newVolatileStorage()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := storage.close(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
config := t.TempDir()
|
||||
write(t, filepath.Join(config, "scanner.toml"), []byte("[extend]\nuseDefault = true\n"))
|
||||
write(t, filepath.Join(config, "ignore"), nil)
|
||||
privateKey := key(t)
|
||||
var random [32]byte
|
||||
if _, err := rand.Read(random[:]); err != nil {
|
||||
t.Fatal("cannot generate synthetic token")
|
||||
}
|
||||
canary := []byte("glpat-" + hex.EncodeToString(random[:]))
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
input []byte
|
||||
want bool
|
||||
}{
|
||||
{"empty", nil, false},
|
||||
{"clean", []byte("reviewed clean input\n"), false},
|
||||
{"exact-limit", bytes.Repeat([]byte("x"), directoryInputBytes), false},
|
||||
{"private-key", privateKey, true},
|
||||
{"exact-limit-private-key", append(bytes.Repeat([]byte("\n"), directoryInputBytes-len(privateKey)), privateKey...), true},
|
||||
{"binary-private-key", append([]byte{0, 1, 0}, privateKey...), true},
|
||||
{"token", canary, true},
|
||||
{"binary-token", append([]byte{0, 0}, canary...), true},
|
||||
{"inline-suppression", append([]byte("# gitleaks:allow\n"), privateKey...), true},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
stdin, e := scan(context.Background(), f, config, tc.input)
|
||||
if e != nil || stdin != tc.want {
|
||||
t.Fatal("stdin reference outcome differs", e)
|
||||
}
|
||||
name := strings.Repeat("a", 40)
|
||||
blocked, e := scanSmall(context.Background(), f, config, storage, []scanInput{{name, tc.input}})
|
||||
if e != nil || (blocked != "") != stdin || blocked != "" && blocked != name {
|
||||
t.Fatal("directory and original stdin detection differ", e)
|
||||
}
|
||||
})
|
||||
}
|
||||
t.Run("sixty-four-distinct-inputs", func(t *testing.T) {
|
||||
var inputs []scanInput
|
||||
for i := 0; i < directoryBatchInputs; i++ {
|
||||
inputs = append(inputs, scanInput{fmt.Sprintf("%040x", i), []byte("clean")})
|
||||
}
|
||||
inputs[63].content = privateKey
|
||||
blocked, e := scanSmall(context.Background(), f, config, storage, inputs)
|
||||
if e != nil || blocked != inputs[63].name {
|
||||
t.Fatal("last batch input was not detected", e)
|
||||
}
|
||||
})
|
||||
t.Run("expired-context", func(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
_, e := scanSmall(ctx, f, config, storage, []scanInput{{strings.Repeat("b", 40), []byte("clean")}})
|
||||
if e == nil || e.Error() != "scan-deadline-exceeded" {
|
||||
t.Fatal("expired scan did not fail closed")
|
||||
}
|
||||
})
|
||||
t.Run("invalid-inputs", func(t *testing.T) {
|
||||
name := strings.Repeat("c", 40)
|
||||
for _, inputs := range [][]scanInput{
|
||||
{{"../escape", []byte("clean")}},
|
||||
{{name, bytes.Repeat([]byte("x"), directoryInputBytes+1)}},
|
||||
{{name, []byte("clean")}, {name, []byte("clean")}},
|
||||
} {
|
||||
if _, e := scanSmall(context.Background(), f, config, storage, inputs); e == nil {
|
||||
t.Fatal("invalid batch input accepted")
|
||||
}
|
||||
if _, e := os.Lstat(filepath.Join(storage.path, "payload")); !os.IsNotExist(e) {
|
||||
t.Fatal("invalid batch left input files")
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("scanner-failure", func(t *testing.T) {
|
||||
write(t, filepath.Join(config, "scanner.toml"), []byte("invalid = ["))
|
||||
if _, e := scanSmall(context.Background(), f, config, storage, []scanInput{{strings.Repeat("d", 40), []byte("clean")}}); e == nil {
|
||||
t.Fatal("scanner failure accepted")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBatchAndLargerInputPushBoundaries(t *testing.T) {
|
||||
adapter, scanner := binary(t)
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
size int
|
||||
secret bool
|
||||
}{
|
||||
{"exact-limit-key", directoryInputBytes, true},
|
||||
{"larger-stdin-key", directoryInputBytes + 1, true},
|
||||
{"larger-stdin-clean", 70 * 1024, false},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
f := setup(t, adapter, scanner)
|
||||
content := []byte("reviewed clean input\n")
|
||||
want := 0
|
||||
if tc.secret {
|
||||
content = key(t)
|
||||
want = 1
|
||||
}
|
||||
content = append(content, bytes.Repeat([]byte("\n"), tc.size-len(content))...)
|
||||
f.commit(t, "input.bin", content, "disposable boundary fixture")
|
||||
f.push(t, want, "HEAD:refs/heads/main")
|
||||
})
|
||||
}
|
||||
t.Run("multiple-ref-name-detection", func(t *testing.T) {
|
||||
f := setup(t, adapter, scanner)
|
||||
var random [32]byte
|
||||
if _, err := rand.Read(random[:]); err != nil {
|
||||
t.Fatal("fixture generation failed")
|
||||
}
|
||||
out := f.push(t, 1, "HEAD:refs/heads/clean-new", "HEAD:refs/heads/glpat-"+hex.EncodeToString(random[:]))
|
||||
if !bytes.Contains(out, []byte(`"reason":"ref-name-detection"`)) {
|
||||
t.Fatal("multi-ref name was not scanned")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBatchRefusesUnverifiedReportsAndDiagnostics(t *testing.T) {
|
||||
known := map[string]string{"payload/ref-0": "ref-0"}
|
||||
for _, tc := range []struct {
|
||||
code int
|
||||
raw string
|
||||
}{
|
||||
{0, "payload/ref-0\n"}, {20, ""}, {0, "\n"},
|
||||
{20, "payload/ref-0"}, {20, "payload/ref-0\n\n"},
|
||||
{20, "payload/ref-unknown\n"}, {20, "../payload/ref-0\n"},
|
||||
{20, "payload/ref-0\r\n"}, {2, ""},
|
||||
} {
|
||||
if _, err := batchFindings(tc.code, []byte(tc.raw), known); err == nil {
|
||||
t.Fatal("unverified report accepted")
|
||||
}
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
code int
|
||||
raw string
|
||||
}{
|
||||
{0, ""}, {20, "payload/ref-0\n"}, {20, "payload/ref-0\npayload/ref-0\n"},
|
||||
} {
|
||||
if _, err := batchFindings(tc.code, []byte(tc.raw), known); err != nil {
|
||||
t.Fatal("valid report refused")
|
||||
}
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
code int
|
||||
text string
|
||||
}{
|
||||
{0, "12:03PM WRN incomplete scan\n"}, {20, "12:03PM WRN incomplete scan\n"},
|
||||
{20, "12:03PM WRN leaks found: 1\n12:03PM WRN incomplete scan\n"},
|
||||
{0, "12:03PM WRN leaks found: 1\n"}, {2, ""},
|
||||
} {
|
||||
if validateBatchDiagnostics(tc.code, []byte(tc.text)) == nil {
|
||||
t.Fatal("unverified diagnostic accepted")
|
||||
}
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
code int
|
||||
text string
|
||||
}{
|
||||
{0, ""}, {20, ""}, {20, "12:03PM WRN leaks found: 1\n"},
|
||||
} {
|
||||
if validateBatchDiagnostics(tc.code, []byte(tc.text)) != nil {
|
||||
t.Fatal("valid diagnostic refused")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,6 @@ package main
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
|
|
@ -95,7 +93,7 @@ func git(ctx context.Context, input []byte, limit int, args ...string) ([]byte,
|
|||
b := &cappedBuffer{limit: limit}
|
||||
cmd.Stdout = b
|
||||
cmd.Stderr = io.Discard
|
||||
if cmd.Run() != nil {
|
||||
if runChild(cmd) != nil {
|
||||
return nil, errors.New("git-" + args[0] + "-failed")
|
||||
}
|
||||
return b.Bytes(), nil
|
||||
|
|
@ -133,17 +131,8 @@ func pinScanner(path string) (*os.File, error) {
|
|||
f.Close()
|
||||
}
|
||||
}()
|
||||
st, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, errors.New("scanner-file-refused")
|
||||
}
|
||||
native, valid := st.Sys().(*syscall.Stat_t)
|
||||
if !valid || !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 nil, errors.New("scanner-file-refused")
|
||||
}
|
||||
hash := sha256.New()
|
||||
if _, err = io.Copy(hash, f); err != nil || hex.EncodeToString(hash.Sum(nil)) != scannerHash {
|
||||
return nil, errors.New("scanner-digest-mismatch")
|
||||
if err = verifyScannerFD(f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ok = true
|
||||
return f, nil
|
||||
|
|
@ -156,7 +145,10 @@ func scan(ctx context.Context, f *os.File, dir string, content []byte) (bool, er
|
|||
cmd.Stdin = bytes.NewReader(content)
|
||||
cmd.Stdout = io.Discard
|
||||
cmd.Stderr = io.Discard
|
||||
err := cmd.Run()
|
||||
err := runChild(cmd)
|
||||
if ctx.Err() != nil {
|
||||
return false, errors.New("scan-deadline-exceeded")
|
||||
}
|
||||
if err == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
|
@ -321,22 +313,86 @@ func check(ctx context.Context, updates []update, binary string) (out result) {
|
|||
return fail(errors.New("temporary-directory-unavailable"))
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
var storage *volatileStorage
|
||||
defer func() {
|
||||
if storage != nil {
|
||||
if e := storage.close(); e != nil {
|
||||
out.Status = "unverified"
|
||||
out.Reason = e.Error()
|
||||
}
|
||||
}
|
||||
}()
|
||||
getStorage := func() error {
|
||||
if storage == nil {
|
||||
var e error
|
||||
storage, e = newVolatileStorage()
|
||||
return e
|
||||
}
|
||||
if !storage.live() {
|
||||
return errors.New("cleanup-watchdog-unavailable")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if os.WriteFile(filepath.Join(dir, "scanner.toml"), []byte("[extend]\nuseDefault = true\n"), 0600) != nil || os.WriteFile(filepath.Join(dir, "ignore"), nil, 0600) != nil {
|
||||
return fail(errors.New("scanner-config-unavailable"))
|
||||
}
|
||||
// Ref names can themselves contain recognized tokens. Never echo them.
|
||||
for _, u := range updates {
|
||||
blocked, e := scan(ctx, scanner, dir, []byte(u.Ref))
|
||||
for start := 0; start < len(updates); start += directoryBatchInputs {
|
||||
end := start + directoryBatchInputs
|
||||
if end > len(updates) {
|
||||
end = len(updates)
|
||||
}
|
||||
if end-start == 1 {
|
||||
blocked, e := scan(ctx, scanner, dir, []byte(updates[start].Ref))
|
||||
if e != nil {
|
||||
return fail(e)
|
||||
}
|
||||
out.ScannedRefs++
|
||||
if blocked {
|
||||
out.Status = "blocked"
|
||||
out.Reason = "ref-name-detection"
|
||||
return out
|
||||
}
|
||||
continue
|
||||
}
|
||||
if e := getStorage(); e != nil {
|
||||
return fail(e)
|
||||
}
|
||||
var batch []scanInput
|
||||
for i := start; i < end; i++ {
|
||||
batch = append(batch, scanInput{fmt.Sprintf("ref-%d", i), []byte(updates[i].Ref)})
|
||||
}
|
||||
blocked, e := scanSmall(ctx, scanner, dir, storage, batch)
|
||||
if e != nil {
|
||||
return fail(e)
|
||||
}
|
||||
out.ScannedRefs++
|
||||
if blocked {
|
||||
out.ScannedRefs += len(batch)
|
||||
if blocked != "" {
|
||||
out.Status = "blocked"
|
||||
out.Reason = "ref-name-detection"
|
||||
return out
|
||||
}
|
||||
}
|
||||
var pending []scanInput
|
||||
flush := func() error {
|
||||
if len(pending) == 0 {
|
||||
return nil
|
||||
}
|
||||
if e := getStorage(); e != nil {
|
||||
return e
|
||||
}
|
||||
blocked, e := scanSmall(ctx, scanner, dir, storage, pending)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
out.Scanned += len(pending)
|
||||
pending = nil
|
||||
if blocked != "" {
|
||||
out.Status = "blocked"
|
||||
out.BlockedID = blocked
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for i, info := range objectMetadata {
|
||||
kind, size := info.kind, info.size
|
||||
raw, e := git(ctx, nil, int(size)+1, "cat-file", kind, ids[i])
|
||||
|
|
@ -352,6 +408,18 @@ func check(ctx context.Context, updates []update, binary string) (out result) {
|
|||
return fail(e)
|
||||
}
|
||||
}
|
||||
if len(raw) <= directoryInputBytes {
|
||||
pending = append(pending, scanInput{ids[i], raw})
|
||||
if len(pending) == directoryBatchInputs {
|
||||
if e = flush(); e != nil {
|
||||
return fail(e)
|
||||
}
|
||||
if out.Status == "blocked" {
|
||||
return out
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
blocked, e := scan(ctx, scanner, dir, raw)
|
||||
if e != nil {
|
||||
return fail(e)
|
||||
|
|
@ -363,6 +431,12 @@ func check(ctx context.Context, updates []update, binary string) (out result) {
|
|||
return out
|
||||
}
|
||||
}
|
||||
if err = flush(); err != nil {
|
||||
return fail(err)
|
||||
}
|
||||
if out.Status == "blocked" {
|
||||
return out
|
||||
}
|
||||
after, _, err := refs(ctx)
|
||||
if err != nil {
|
||||
return fail(err)
|
||||
|
|
@ -374,6 +448,9 @@ func check(ctx context.Context, updates []update, binary string) (out result) {
|
|||
return out
|
||||
}
|
||||
func main() {
|
||||
if code, handled := internalProcess(os.Args[1:]); handled {
|
||||
os.Exit(code)
|
||||
}
|
||||
flags := flag.NewFlagSet("receive-secret-scan", flag.ContinueOnError)
|
||||
flags.SetOutput(io.Discard)
|
||||
binary := flags.String("scanner", "", "reviewed Gitleaks executable")
|
||||
|
|
|
|||
268
tools/receive-secret-scan/volatile.go
Normal file
268
tools/receive-secret-scan/volatile.go
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
// Linux volatile input custody; no raw input is written to ordinary disk.
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type storageIdentity struct {
|
||||
dev uint64
|
||||
ino uint64
|
||||
uid uint32
|
||||
}
|
||||
|
||||
func volatileIdentity(path string) (storageIdentity, error) {
|
||||
var id storageIdentity
|
||||
if filepath.Dir(path) != "/dev/shm" || !strings.HasPrefix(filepath.Base(path), "forge-receive-scan-") {
|
||||
return id, errors.New("volatile-storage-refused")
|
||||
}
|
||||
fd, err := syscall.Open(path, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW|syscall.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return id, errors.New("volatile-storage-unavailable")
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
var st syscall.Stat_t
|
||||
var fs syscall.Statfs_t
|
||||
if syscall.Fstat(fd, &st) != nil || syscall.Fstatfs(fd, &fs) != nil || fs.Type != 0x01021994 || st.Mode&07777 != 0700 || st.Uid != uint32(os.Getuid()) {
|
||||
return id, errors.New("volatile-storage-refused")
|
||||
}
|
||||
return storageIdentity{uint64(st.Dev), st.Ino, st.Uid}, nil
|
||||
}
|
||||
|
||||
func removeVolatile(path string, expected storageIdentity) error {
|
||||
if _, err := os.Lstat(path); os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
id, err := volatileIdentity(path)
|
||||
if err != nil || id != expected {
|
||||
return errors.New("volatile-storage-identity-changed")
|
||||
}
|
||||
if os.RemoveAll(path) != nil {
|
||||
return errors.New("volatile-storage-cleanup-failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pin the exact parent using Linux pidfd, not a reusable numeric process ID.
|
||||
func parentDescriptor(parent int) (*os.File, error) {
|
||||
if runtime.GOARCH != "amd64" && runtime.GOARCH != "arm64" {
|
||||
return nil, errors.New("cleanup-platform-unsupported")
|
||||
}
|
||||
if parent <= 1 || os.Getppid() != parent {
|
||||
return nil, errors.New("cleanup-parent-refused")
|
||||
}
|
||||
fd, _, errno := syscall.Syscall(434, uintptr(parent), 0, 0) // pidfd_open, Linux 5.3+
|
||||
if errno != 0 {
|
||||
return nil, errors.New("cleanup-parent-unavailable")
|
||||
}
|
||||
f := os.NewFile(fd, "parent-pidfd")
|
||||
if os.Getppid() != parent || fd >= 1024 {
|
||||
f.Close()
|
||||
return nil, errors.New("cleanup-parent-refused")
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func waitParent(f *os.File, duration time.Duration) bool {
|
||||
fd := int(f.Fd())
|
||||
var deadline time.Time
|
||||
if duration >= 0 {
|
||||
deadline = time.Now().Add(duration)
|
||||
}
|
||||
for {
|
||||
// select may mutate both arguments, including when interrupted.
|
||||
var set syscall.FdSet
|
||||
set.Bits[fd/64] |= 1 << uint(fd%64)
|
||||
var timeout *syscall.Timeval
|
||||
if duration >= 0 {
|
||||
remaining := time.Until(deadline)
|
||||
if remaining < 0 {
|
||||
remaining = 0
|
||||
}
|
||||
value := syscall.NsecToTimeval(remaining.Nanoseconds())
|
||||
timeout = &value
|
||||
}
|
||||
n, err := syscall.Select(fd+1, &set, nil, nil, timeout)
|
||||
if err == syscall.EINTR {
|
||||
continue
|
||||
}
|
||||
return err == nil && n > 0
|
||||
}
|
||||
}
|
||||
|
||||
func signalParent(f *os.File, signal syscall.Signal) {
|
||||
_, _, _ = syscall.Syscall6(424, f.Fd(), uintptr(signal), 0, 0, 0, 0) // pidfd_send_signal
|
||||
}
|
||||
|
||||
func cleanupProcess(args []string) int {
|
||||
if len(args) != 5 {
|
||||
return 4
|
||||
}
|
||||
dev, e1 := strconv.ParseUint(args[1], 10, 64)
|
||||
ino, e2 := strconv.ParseUint(args[2], 10, 64)
|
||||
uid, e3 := strconv.ParseUint(args[3], 10, 32)
|
||||
parent, e4 := strconv.Atoi(args[4])
|
||||
if e1 != nil || e2 != nil || e3 != nil || e4 != nil {
|
||||
return 4
|
||||
}
|
||||
expected := storageIdentity{dev, ino, uint32(uid)}
|
||||
id, err := volatileIdentity(args[0])
|
||||
if err != nil || id != expected {
|
||||
return 3
|
||||
}
|
||||
process, err := parentDescriptor(parent)
|
||||
if err != nil {
|
||||
return 4
|
||||
}
|
||||
defer process.Close()
|
||||
if _, err = os.Stdout.Write([]byte("R")); err != nil {
|
||||
return 4
|
||||
}
|
||||
eof := make(chan bool, 1)
|
||||
go func() { var b [1]byte; n, e := os.Stdin.Read(b[:]); eof <- n == 0 && e == io.EOF }()
|
||||
select {
|
||||
case ok := <-eof:
|
||||
if !ok {
|
||||
return 4
|
||||
}
|
||||
case <-time.After(180 * time.Second):
|
||||
signalParent(process, syscall.SIGTERM)
|
||||
if !waitParent(process, 5*time.Second) {
|
||||
signalParent(process, syscall.SIGKILL)
|
||||
if !waitParent(process, -1) {
|
||||
return 4
|
||||
}
|
||||
}
|
||||
}
|
||||
if removeVolatile(args[0], expected) != nil {
|
||||
return 3
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type volatileStorage struct {
|
||||
path string
|
||||
id storageIdentity
|
||||
pipe io.WriteCloser
|
||||
command *exec.Cmd
|
||||
done chan error
|
||||
}
|
||||
|
||||
func newVolatileStorage() (*volatileStorage, error) {
|
||||
root, err := syscall.Open("/dev/shm", syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW|syscall.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, errors.New("volatile-storage-unavailable")
|
||||
}
|
||||
var fs syscall.Statfs_t
|
||||
err = syscall.Fstatfs(root, &fs)
|
||||
syscall.Close(root)
|
||||
if err != nil || fs.Type != 0x01021994 {
|
||||
return nil, errors.New("volatile-storage-unavailable")
|
||||
}
|
||||
path, err := os.MkdirTemp("/dev/shm", "forge-receive-scan-")
|
||||
if err != nil {
|
||||
return nil, errors.New("volatile-storage-unavailable")
|
||||
}
|
||||
id, err := volatileIdentity(path)
|
||||
if err != nil {
|
||||
os.RemoveAll(path)
|
||||
return nil, err
|
||||
}
|
||||
cmd := exec.Command("/proc/self/exe", "--cleanup-storage", path, fmt.Sprint(id.dev), fmt.Sprint(id.ino), fmt.Sprint(id.uid), strconv.Itoa(os.Getpid()))
|
||||
cmd.Env = []string{"PATH=/usr/bin:/bin", "LANG=C"}
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
writer, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
removeVolatile(path, id)
|
||||
return nil, errors.New("cleanup-watchdog-unavailable")
|
||||
}
|
||||
reader, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
writer.Close()
|
||||
removeVolatile(path, id)
|
||||
return nil, errors.New("cleanup-watchdog-unavailable")
|
||||
}
|
||||
if err = cmd.Start(); err != nil {
|
||||
writer.Close()
|
||||
reader.Close()
|
||||
removeVolatile(path, id)
|
||||
return nil, errors.New("cleanup-watchdog-unavailable")
|
||||
}
|
||||
v := &volatileStorage{path, id, writer, cmd, make(chan error, 1)}
|
||||
go func() { v.done <- cmd.Wait() }()
|
||||
ready := make(chan bool, 1)
|
||||
go func() {
|
||||
var b [1]byte
|
||||
_, e := io.ReadFull(reader, b[:])
|
||||
reader.Close()
|
||||
ready <- e == nil && b[0] == 'R'
|
||||
}()
|
||||
select {
|
||||
case ok := <-ready:
|
||||
if ok && v.live() {
|
||||
return v, nil
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
}
|
||||
v.close()
|
||||
return nil, errors.New("cleanup-watchdog-unavailable")
|
||||
}
|
||||
|
||||
func (v *volatileStorage) live() bool {
|
||||
select {
|
||||
case err := <-v.done:
|
||||
v.done <- err
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (v *volatileStorage) close() error {
|
||||
err := removeVolatile(v.path, v.id)
|
||||
v.pipe.Close()
|
||||
select {
|
||||
case outcome := <-v.done:
|
||||
if outcome != nil && err == nil {
|
||||
err = errors.New("cleanup-watchdog-failed")
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
v.command.Process.Kill()
|
||||
<-v.done
|
||||
if err == nil {
|
||||
err = errors.New("cleanup-watchdog-failed")
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Source-reading/writing children cannot survive loss of their exact parent.
|
||||
func runChild(cmd *exec.Cmd) error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Pdeathsig: syscall.SIGKILL}
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func internalProcess(args []string) (int, bool) {
|
||||
if len(args) == 0 {
|
||||
return 0, false
|
||||
}
|
||||
switch args[0] {
|
||||
case "--cleanup-storage":
|
||||
return cleanupProcess(args[1:]), true
|
||||
case "--batch-scanner":
|
||||
return batchScannerProcess(args[1:]), true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
90
workplans/FORGE-WP-0010-receive-scanner-budget.md
Normal file
90
workplans/FORGE-WP-0010-receive-scanner-budget.md
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
id: FORGE-WP-0010
|
||||
type: workplan
|
||||
title: "Complete bounded receive scans without per-small-input startup overhead"
|
||||
domain: financials
|
||||
repo: railiance-forge
|
||||
status: proposed
|
||||
owner: codex
|
||||
topic_slug: railiance-forge
|
||||
created: "2026-10-18"
|
||||
updated: "2026-10-18"
|
||||
flavor: implementation
|
||||
depends_on: []
|
||||
state_hub_workstream_id: "04d8ebb9-e9fe-58d8-bdc8-2aa985098c7a"
|
||||
---
|
||||
|
||||
CUST-WP-0080 discovered an availability defect in the already enforced receive
|
||||
control: the normal 141-object public Custodian source delta exhausts the
|
||||
unchanged 90-second budget, with no secret finding. The unmodified owner source
|
||||
reproduces the refusal in an isolated bare repository. This is a new correction,
|
||||
not reopening finished FORGE-WP-0006 or promoting FORGE-WP-0008 residual coverage.
|
||||
|
||||
Reuse the existing Repo Manager small-input profile: at most 64 inputs of at most
|
||||
4096 bytes per directory scan, identical pinned Gitleaks/default rules and strict
|
||||
report/diagnostic validation. Larger inputs retain the original stdin fragment
|
||||
path. Preserve every traversal/ref/object/byte/time bound, no override or new
|
||||
runtime service. Raw batch inputs require checked private tmpfs, independent
|
||||
parent-bound cleanup and child termination on receiver loss. Source preparation
|
||||
does not grant native installation, package/owner acceptance or production proof.
|
||||
|
||||
## Reproduce and implement the bounded adapter correction
|
||||
|
||||
```task
|
||||
id: FORGE-WP-0010-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "c61c0392-6c0a-5a36-bbd1-4ee6aeb2afea"
|
||||
```
|
||||
|
||||
Retain the exact original failure and source delta. Implement the existing
|
||||
small-input batching contract in the Linux receive adapter. Verify synthetic
|
||||
positive/negative parity, real receive-pack quarantine, all prior boundaries,
|
||||
limits, expired context, diagnostic/report refusal, normal cleanup, receiver
|
||||
death and changed storage identity. Complete the exact original clean delta
|
||||
within the same deadline. Failures remain evidence; source proof closes no
|
||||
native acceptance gate. Independent package/owner review remains under T02.
|
||||
|
||||
Source result: the exact previously refused 141-object/one-ref delta completes
|
||||
in 52.53 seconds with all objects counted, under the unchanged 90-second budget.
|
||||
The complete suite passes 49 leaf cases with zero skips; five additional boundary
|
||||
cases bring the combined distinct leaf coverage to 54. These include 4096-byte
|
||||
and larger-input detection, multi-ref-name detection, normal and receiver-loss
|
||||
cleanup, scanner/report/diagnostic failures and changed directory identity.
|
||||
Unavailable and ordinary-disk storage fail closed in separate actual helper
|
||||
processes. The original low-entropy canary failure and misconfigured storage
|
||||
fixture/setup failures are retained rather than counted as successful tests.
|
||||
Source/proof: Custodian `docs/changes/netkingdom-target-2026-10-18/receive-scanner-batch-r1/`.
|
||||
|
||||
## Prepare and approve the immutable native revision
|
||||
|
||||
```task
|
||||
id: FORGE-WP-0010-T02
|
||||
status: progress
|
||||
priority: high
|
||||
state_hub_task_id: "b49d34ef-e6e5-5674-b523-ebcf80e0dcca"
|
||||
```
|
||||
|
||||
Obtain independent source/package review. Pin exact source/build tool provenance, executable digest, startup-guard and
|
||||
hook composition. Capture read-only current state, no-op checks and exact
|
||||
before/after/rollback. Retain permissions, scanner digest, hook ordering and
|
||||
fail-closed behavior. Obtain the founder's explicit go for this live change
|
||||
under the existing environment rule; previous activation is not approval for
|
||||
this different executable. Preserve the original revision and every refinement
|
||||
in the current supervised-action record.
|
||||
|
||||
## Verify admitted native behavior and ordinary publication
|
||||
|
||||
```task
|
||||
id: FORGE-WP-0010-T03
|
||||
status: wait
|
||||
priority: high
|
||||
state_hub_task_id: "863c91e4-7c25-575f-b8b6-f98d0074ce5a"
|
||||
```
|
||||
|
||||
Waiting on T02: exact executable/startup admission and founder-authorized native
|
||||
activation. Verify actual hook clean/refusal/unavailable/cleanup behavior and
|
||||
the legitimate ordinary source publication without splitting or seeding refs.
|
||||
Reconcile owner task/source status and independent evidence. Existing remaining
|
||||
coverage, private incident response and target-wide acceptance retain their
|
||||
original owners; no residual implementation is inferred.
|
||||
Loading…
Add table
Add a link
Reference in a new issue