fix: prevent enumeration of private repo (#614)

DongoDB created

Change summary

pkg/ssh/cmd/branch.go                | 12 +++++-------
pkg/ssh/cmd/cmd.go                   | 12 +++++++++++-
pkg/ssh/cmd/collab.go                |  6 +++---
pkg/ssh/cmd/delete.go                |  2 +-
pkg/ssh/cmd/description.go           | 13 +++++--------
pkg/ssh/cmd/hidden.go                | 13 +++++--------
pkg/ssh/cmd/private.go               | 11 ++++-------
pkg/ssh/cmd/project_name.go          | 13 +++++--------
pkg/ssh/cmd/rename.go                |  2 +-
pkg/ssh/cmd/tag.go                   |  2 +-
testscript/testdata/repo-perms.txtar | 28 ++++++++++++++--------------
11 files changed, 55 insertions(+), 59 deletions(-)

Detailed changes

pkg/ssh/cmd/branch.go 🔗

@@ -61,18 +61,16 @@ func branchListCommand() *cobra.Command {
 
 func branchDefaultCommand() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "default REPOSITORY [BRANCH]",
-		Short: "Set or get the default branch",
-		Args:  cobra.RangeArgs(1, 2),
+		Use:               "default REPOSITORY [BRANCH]",
+		Short:             "Set or get the default branch",
+		Args:              cobra.RangeArgs(1, 2),
+		PersistentPreRunE: checkIfReadable,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 			be := backend.FromContext(ctx)
 			rn := strings.TrimSuffix(args[0], ".git")
 			switch len(args) {
 			case 1:
-				if err := checkIfReadable(cmd, args); err != nil {
-					return err
-				}
 				rr, err := be.Repository(ctx, rn)
 				if err != nil {
 					return err
@@ -149,7 +147,7 @@ func branchDeleteCommand() *cobra.Command {
 		Aliases:           []string{"remove", "rm", "del"},
 		Short:             "Delete a branch",
 		Args:              cobra.ExactArgs(2),
-		PersistentPreRunE: checkIfCollab,
+		PersistentPreRunE: checkIfReadableAndCollab,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 			be := backend.FromContext(ctx)

pkg/ssh/cmd/cmd.go 🔗

@@ -121,7 +121,7 @@ func checkIfReadable(cmd *cobra.Command, args []string) error {
 	user := proto.UserFromContext(ctx)
 	auth := be.AccessLevelForUser(cmd.Context(), rn, user)
 	if auth < access.ReadOnlyAccess {
-		return proto.ErrUnauthorized
+		return proto.ErrRepoNotFound
 	}
 	return nil
 }
@@ -185,3 +185,13 @@ func checkIfCollab(cmd *cobra.Command, args []string) error {
 	}
 	return nil
 }
+
+func checkIfReadableAndCollab(cmd *cobra.Command, args []string) error {
+	if err := checkIfReadable(cmd, args); err != nil {
+		return err
+	}
+	if err := checkIfCollab(cmd, args); err != nil {
+		return err
+	}
+	return nil
+}

pkg/ssh/cmd/collab.go 🔗

@@ -28,7 +28,7 @@ func collabAddCommand() *cobra.Command {
 		Short:             "Add a collaborator to a repo",
 		Long:              "Add a collaborator to a repo. LEVEL can be one of: no-access, read-only, read-write, or admin-access. Defaults to read-write.",
 		Args:              cobra.RangeArgs(2, 3),
-		PersistentPreRunE: checkIfCollab,
+		PersistentPreRunE: checkIfReadableAndCollab,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 			be := backend.FromContext(ctx)
@@ -54,7 +54,7 @@ func collabRemoveCommand() *cobra.Command {
 		Use:               "remove REPOSITORY USERNAME",
 		Args:              cobra.ExactArgs(2),
 		Short:             "Remove a collaborator from a repo",
-		PersistentPreRunE: checkIfCollab,
+		PersistentPreRunE: checkIfReadableAndCollab,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 			be := backend.FromContext(ctx)
@@ -73,7 +73,7 @@ func collabListCommand() *cobra.Command {
 		Use:               "list REPOSITORY",
 		Short:             "List collaborators for a repo",
 		Args:              cobra.ExactArgs(1),
-		PersistentPreRunE: checkIfCollab,
+		PersistentPreRunE: checkIfReadableAndCollab,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 			be := backend.FromContext(ctx)

pkg/ssh/cmd/delete.go 🔗

@@ -11,7 +11,7 @@ func deleteCommand() *cobra.Command {
 		Aliases:           []string{"del", "remove", "rm"},
 		Short:             "Delete a repository",
 		Args:              cobra.ExactArgs(1),
-		PersistentPreRunE: checkIfCollab,
+		PersistentPreRunE: checkIfReadableAndCollab,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 			be := backend.FromContext(ctx)

pkg/ssh/cmd/description.go 🔗

@@ -9,20 +9,17 @@ import (
 
 func descriptionCommand() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:     "description REPOSITORY [DESCRIPTION]",
-		Aliases: []string{"desc"},
-		Short:   "Set or get the description for a repository",
-		Args:    cobra.MinimumNArgs(1),
+		Use:               "description REPOSITORY [DESCRIPTION]",
+		Aliases:           []string{"desc"},
+		Short:             "Set or get the description for a repository",
+		Args:              cobra.MinimumNArgs(1),
+		PersistentPreRunE: checkIfReadable,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 			be := backend.FromContext(ctx)
 			rn := strings.TrimSuffix(args[0], ".git")
 			switch len(args) {
 			case 1:
-				if err := checkIfReadable(cmd, args); err != nil {
-					return err
-				}
-
 				desc, err := be.Description(ctx, rn)
 				if err != nil {
 					return err

pkg/ssh/cmd/hidden.go 🔗

@@ -7,20 +7,17 @@ import (
 
 func hiddenCommand() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:     "hidden REPOSITORY [TRUE|FALSE]",
-		Short:   "Hide or unhide a repository",
-		Aliases: []string{"hide"},
-		Args:    cobra.MinimumNArgs(1),
+		Use:               "hidden REPOSITORY [TRUE|FALSE]",
+		Short:             "Hide or unhide a repository",
+		Aliases:           []string{"hide"},
+		Args:              cobra.MinimumNArgs(1),
+		PersistentPreRunE: checkIfReadable,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 			be := backend.FromContext(ctx)
 			repo := args[0]
 			switch len(args) {
 			case 1:
-				if err := checkIfReadable(cmd, args); err != nil {
-					return err
-				}
-
 				hidden, err := be.IsHidden(ctx, repo)
 				if err != nil {
 					return err

pkg/ssh/cmd/private.go 🔗

@@ -10,9 +10,10 @@ import (
 
 func privateCommand() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:   "private REPOSITORY [true|false]",
-		Short: "Set or get a repository private property",
-		Args:  cobra.RangeArgs(1, 2),
+		Use:               "private REPOSITORY [true|false]",
+		Short:             "Set or get a repository private property",
+		Args:              cobra.RangeArgs(1, 2),
+		PersistentPreRunE: checkIfReadable,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 			be := backend.FromContext(ctx)
@@ -20,10 +21,6 @@ func privateCommand() *cobra.Command {
 
 			switch len(args) {
 			case 1:
-				if err := checkIfReadable(cmd, args); err != nil {
-					return err
-				}
-
 				isPrivate, err := be.IsPrivate(ctx, rn)
 				if err != nil {
 					return err

pkg/ssh/cmd/project_name.go 🔗

@@ -9,20 +9,17 @@ import (
 
 func projectName() *cobra.Command {
 	cmd := &cobra.Command{
-		Use:     "project-name REPOSITORY [NAME]",
-		Aliases: []string{"project"},
-		Short:   "Set or get the project name for a repository",
-		Args:    cobra.MinimumNArgs(1),
+		Use:               "project-name REPOSITORY [NAME]",
+		Aliases:           []string{"project"},
+		Short:             "Set or get the project name for a repository",
+		Args:              cobra.MinimumNArgs(1),
+		PersistentPreRunE: checkIfReadable,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 			be := backend.FromContext(ctx)
 			rn := strings.TrimSuffix(args[0], ".git")
 			switch len(args) {
 			case 1:
-				if err := checkIfReadable(cmd, args); err != nil {
-					return err
-				}
-
 				pn, err := be.ProjectName(ctx, rn)
 				if err != nil {
 					return err

pkg/ssh/cmd/rename.go 🔗

@@ -11,7 +11,7 @@ func renameCommand() *cobra.Command {
 		Aliases:           []string{"mv", "move"},
 		Short:             "Rename an existing repository",
 		Args:              cobra.ExactArgs(2),
-		PersistentPreRunE: checkIfCollab,
+		PersistentPreRunE: checkIfReadableAndCollab,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 			be := backend.FromContext(ctx)

pkg/ssh/cmd/tag.go 🔗

@@ -64,7 +64,7 @@ func tagDeleteCommand() *cobra.Command {
 		Aliases:           []string{"remove", "rm", "del"},
 		Short:             "Delete a tag",
 		Args:              cobra.ExactArgs(2),
-		PersistentPreRunE: checkIfCollab,
+		PersistentPreRunE: checkIfReadableAndCollab,
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := cmd.Context()
 			be := backend.FromContext(ctx)

testscript/testdata/repo-perms.txtar 🔗

@@ -36,33 +36,33 @@ soft repo collab list repo1
 
 # regular user can't access it
 ! usoft repo info repo1
-stderr 'unauthorized'
+stderr 'repository not found'
 ! usoft repo tree repo1
-stderr 'unauthorized'
+stderr 'repository not found'
 ! usoft repo tag list repo1
-stderr 'unauthorized'
+stderr 'repository not found'
 ! usoft repo tag delete repo1 v1.0.0
-stderr 'unauthorized'
+stderr 'repository not found'
 ! usoft repo blob repo1 README.md
-stderr 'unauthorized'
+stderr 'repository not found'
 ! usoft repo description repo1
-stderr 'unauthorized'
+stderr 'repository not found'
 ! usoft repo description repo1 'new desc'
-stderr 'unauthorized'
+stderr 'repository not found'
 ! usoft repo project-name repo1
-stderr 'unauthorized'
+stderr 'repository not found'
 ! usoft repo private repo1 true
-stderr 'unauthorized'
+stderr 'repository not found'
 ! usoft repo private repo1
-stderr 'unauthorized'
+stderr 'repository not found'
 ! usoft repo rename repo1 repo11
-stderr 'unauthorized'
+stderr 'repository not found'
 ! usoft repo branch default repo1
-stderr 'unauthorized'
+stderr 'repository not found'
 ! usoft repo branch default repo1 main
-stderr 'unauthorized'
+stderr 'repository not found'
 ! usoft repo delete repo1
-stderr 'unauthorized'
+stderr 'repository not found'
 
 # add user1 as collab
 ! soft repo collab add repo1 user1 foobar