Skip to content
quarry Artifact [eb075cf64d]
Login

Artifact [eb075cf64d]

Artifact eb075cf64d499c75574d182a96a777c698a87472c163194db8c7e892cc6d0b29:


package zitcli

import (
	"errors"
	"testing"
)

// A URL segment must not become a zit option.
//
// THE VULNERABILITY: the file, tree, doc and history routes put a
// request's path and version segments into argv positionally, and zit
// parses anything starting with "-" as an option with no "--" marker to
// stop it. So, anonymously, on every repository, needing no capability:
//
//	GET /api/v1/r/<repo>/file/trunk/--help
//	-> 200, and the file's "content" is zit's help text
//
// Found by jva's adversarial subagent, which re-read a ticket filed as a
// cosmetic 500 and asked what was on the other end of the 500.
func TestAUrlSegmentCannotBecomeAnOption(t *testing.T) {
	for _, bad := range []string{
		"--help",       // the proof-of-concept: returned 200 with zit's help
		"-trunk",       // seen in a leaked command line from the tree route
		"--no-cache=1", // a real flag with an argument attached
		"-R",           // NOT in this list by accident: see the next test
	} {
		if bad == "-R" {
			continue // quarry's own; covered below
		}
		err := checkArgs([]string{"cat", "-R", "/srv/repo", bad, "trunk"})
		if err == nil {
			t.Errorf("FAIL: %q would have been passed to zit as an option", bad)
			continue
		}
		if !errors.Is(err, ErrUnsafeArgument) {
			t.Errorf("FAIL: %q refused with the wrong error: %v", bad, err)
		}
		t.Logf("ok: %q refused", bad)
	}
}

// Quarry's OWN flags still pass, or the forge stops working. This is
// the half that makes the guard usable rather than a blanket ban, and
// the full suite is the real check on it.
func TestQuarrysOwnFlagsStillPass(t *testing.T) {
	for _, args := range [][]string{
		{"cat", "-R", "/srv/repo", "README.md", "trunk"},
		{"timeline", "-R", "/srv/repo", "-n", "40"},
		{"diff", "-R", "/srv/repo", "--from", "a", "--to", "b"},
		{"commit", "-R", "/srv/repo", "-m", "msg", "--user", "dan"},
		{"tag", "add", "--raw", "--propagate", "closed", "abc"},
		{"ls", "-R", "/srv/repo", "--long"},
	} {
		if err := checkArgs(args); err != nil {
			t.Errorf("FAIL: a legitimate invocation was refused: %v (%v)", err, args)
			continue
		}
		t.Logf("ok: %v", args[:2])
	}
}

// A negative number is an operand. Refusing it would break callers for
// no security gain, since no zit option is a bare number.
func TestANegativeNumberIsAnOperand(t *testing.T) {
	if err := checkArgs([]string{"timeline", "-R", "/srv/repo", "-n", "-20"}); err != nil {
		t.Fatalf("FAIL: a negative operand was refused: %v", err)
	}
	// But something that merely STARTS like one is not.
	if err := checkArgs([]string{"cat", "-12abc"}); err == nil {
		t.Fatal("FAIL: -12abc was accepted; only digits make a number")
	}
	t.Log("ok: -20 passes, -12abc does not")
}