diff --git a/modules/repository/repo.go b/modules/repository/repo.go
index d06d75fa9c..1387cee66d 100644
--- a/modules/repository/repo.go
+++ b/modules/repository/repo.go
@@ -334,7 +334,9 @@ func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitR
 		}
 
 		if err := PushUpdateAddTag(ctx, repo, gitRepo, tagName, sha1, refname); err != nil {
-			return fmt.Errorf("unable to PushUpdateAddTag: %q to Repo[%d:%s/%s]: %w", tagName, repo.ID, repo.OwnerName, repo.Name, err)
+			// sometimes, some tags will be sync failed. i.e. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tag/?h=v2.6.11
+			// this is a tree object, not a tag object which created before git
+			log.Error("unable to PushUpdateAddTag: %q to Repo[%d:%s/%s]: %v", tagName, repo.ID, repo.OwnerName, repo.Name, err)
 		}
 
 		return nil
diff --git a/options/locales/gitea_en-US.ini b/options/locales/gitea_en-US.ini
index 0116fa08df..562d409016 100644
--- a/options/locales/gitea_en-US.ini
+++ b/options/locales/gitea_en-US.ini
@@ -246,6 +246,7 @@ email_title = Email Settings
 smtp_addr = SMTP Host
 smtp_port = SMTP Port
 smtp_from = Send Email As
+smtp_from_invalid = The "Send Email As" address is invalid
 smtp_from_helper = Email address Gitea will use. Enter a plain email address or use the "Name" <email@example.com> format.
 mailer_user = SMTP Username
 mailer_password = SMTP Password
diff --git a/routers/install/install.go b/routers/install/install.go
index 648425df3b..ec0f4fd80a 100644
--- a/routers/install/install.go
+++ b/routers/install/install.go
@@ -7,6 +7,7 @@ package install
 import (
 	"fmt"
 	"net/http"
+	"net/mail"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -423,6 +424,11 @@ func SubmitInstall(ctx *context.Context) {
 	}
 
 	if len(strings.TrimSpace(form.SMTPAddr)) > 0 {
+		if _, err := mail.ParseAddress(form.SMTPFrom); err != nil {
+			ctx.RenderWithErr(ctx.Tr("install.smtp_from_invalid"), tplInstall, &form)
+			return
+		}
+
 		cfg.Section("mailer").Key("ENABLED").SetValue("true")
 		cfg.Section("mailer").Key("SMTP_ADDR").SetValue(form.SMTPAddr)
 		cfg.Section("mailer").Key("SMTP_PORT").SetValue(form.SMTPPort)
diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go
index 9f787f5125..6e635ab43d 100644
--- a/routers/web/repo/actions/actions.go
+++ b/routers/web/repo/actions/actions.go
@@ -61,17 +61,17 @@ func List(ctx *context.Context) {
 
 	var workflows []Workflow
 	if empty, err := ctx.Repo.GitRepo.IsEmpty(); err != nil {
-		ctx.Error(http.StatusInternalServerError, err.Error())
+		ctx.ServerError("IsEmpty", err)
 		return
 	} else if !empty {
 		commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch)
 		if err != nil {
-			ctx.Error(http.StatusInternalServerError, err.Error())
+			ctx.ServerError("GetBranchCommit", err)
 			return
 		}
 		entries, err := actions.ListWorkflows(commit)
 		if err != nil {
-			ctx.Error(http.StatusInternalServerError, err.Error())
+			ctx.ServerError("ListWorkflows", err)
 			return
 		}
 
@@ -96,7 +96,7 @@ func List(ctx *context.Context) {
 			workflow := Workflow{Entry: *entry}
 			content, err := actions.GetContentFromEntry(entry)
 			if err != nil {
-				ctx.Error(http.StatusInternalServerError, err.Error())
+				ctx.ServerError("GetContentFromEntry", err)
 				return
 			}
 			wf, err := model.ReadWorkflow(bytes.NewReader(content))
@@ -173,7 +173,7 @@ func List(ctx *context.Context) {
 
 	runs, total, err := actions_model.FindRuns(ctx, opts)
 	if err != nil {
-		ctx.Error(http.StatusInternalServerError, err.Error())
+		ctx.ServerError("FindAndCount", err)
 		return
 	}
 
@@ -182,7 +182,7 @@ func List(ctx *context.Context) {
 	}
 
 	if err := runs.LoadTriggerUser(ctx); err != nil {
-		ctx.Error(http.StatusInternalServerError, err.Error())
+		ctx.ServerError("LoadTriggerUser", err)
 		return
 	}
 
@@ -190,7 +190,7 @@ func List(ctx *context.Context) {
 
 	actors, err := actions_model.GetActors(ctx, ctx.Repo.Repository.ID)
 	if err != nil {
-		ctx.Error(http.StatusInternalServerError, err.Error())
+		ctx.ServerError("GetActors", err)
 		return
 	}
 	ctx.Data["Actors"] = repo.MakeSelfOnTop(ctx.Doer, actors)
diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go
index 24f8f88976..1d4ef4d649 100644
--- a/routers/web/shared/user/header.go
+++ b/routers/web/shared/user/header.go
@@ -6,6 +6,7 @@ package user
 import (
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/organization"
+	project_model "code.gitea.io/gitea/models/project"
 	repo_model "code.gitea.io/gitea/models/repo"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/context"
@@ -126,5 +127,21 @@ func LoadHeaderCount(ctx *context.Context) error {
 	}
 	ctx.Data["RepoCount"] = repoCount
 
+	var projectType project_model.Type
+	if ctx.ContextUser.IsOrganization() {
+		projectType = project_model.TypeOrganization
+	} else {
+		projectType = project_model.TypeIndividual
+	}
+	projectCount, err := project_model.CountProjects(ctx, project_model.SearchOptions{
+		OwnerID:  ctx.ContextUser.ID,
+		IsClosed: util.OptionalBoolOf(false),
+		Type:     projectType,
+	})
+	if err != nil {
+		return err
+	}
+	ctx.Data["ProjectCount"] = projectCount
+
 	return nil
 }
diff --git a/services/auth/auth.go b/services/auth/auth.go
index 4adf549204..6dc0327531 100644
--- a/services/auth/auth.go
+++ b/services/auth/auth.go
@@ -38,6 +38,7 @@ func isContainerPath(req *http.Request) bool {
 var (
 	gitRawOrAttachPathRe = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/(?:(?:git-(?:(?:upload)|(?:receive))-pack$)|(?:info/refs$)|(?:HEAD$)|(?:objects/)|(?:raw/)|(?:releases/download/)|(?:attachments/))`)
 	lfsPathRe            = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/info/lfs/`)
+	archivePathRe        = regexp.MustCompile(`^/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+/archive/`)
 )
 
 func isGitRawOrAttachPath(req *http.Request) bool {
@@ -54,6 +55,10 @@ func isGitRawOrAttachOrLFSPath(req *http.Request) bool {
 	return false
 }
 
+func isArchivePath(req *http.Request) bool {
+	return archivePathRe.MatchString(req.URL.Path)
+}
+
 // handleSignIn clears existing session variables and stores new ones for the specified user object
 func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore, user *user_model.User) {
 	// We need to regenerate the session...
diff --git a/services/auth/oauth2.go b/services/auth/oauth2.go
index 08a2a05539..3b9b7def37 100644
--- a/services/auth/oauth2.go
+++ b/services/auth/oauth2.go
@@ -127,7 +127,7 @@ func (o *OAuth2) userIDFromToken(ctx context.Context, tokenSHA string, store Dat
 func (o *OAuth2) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
 	// These paths are not API paths, but we still want to check for tokens because they maybe in the API returned URLs
 	if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isAuthenticatedTokenRequest(req) &&
-		!isGitRawOrAttachPath(req) {
+		!isGitRawOrAttachPath(req) && !isArchivePath(req) {
 		return nil, nil
 	}
 
diff --git a/services/auth/sspi.go b/services/auth/sspi.go
index d4f7e3ec60..0ec1bc2998 100644
--- a/services/auth/sspi.go
+++ b/services/auth/sspi.go
@@ -11,7 +11,6 @@ import (
 	"sync"
 
 	"code.gitea.io/gitea/models/auth"
-	"code.gitea.io/gitea/models/avatars"
 	user_model "code.gitea.io/gitea/models/user"
 	"code.gitea.io/gitea/modules/base"
 	gitea_context "code.gitea.io/gitea/modules/context"
@@ -163,12 +162,9 @@ func (s *SSPI) shouldAuthenticate(req *http.Request) (shouldAuth bool) {
 func (s *SSPI) newUser(ctx context.Context, username string, cfg *sspi.Source) (*user_model.User, error) {
 	email := gouuid.New().String() + "@localhost.localdomain"
 	user := &user_model.User{
-		Name:            username,
-		Email:           email,
-		Passwd:          gouuid.New().String(),
-		Language:        cfg.DefaultLanguage,
-		UseCustomAvatar: true,
-		Avatar:          avatars.DefaultAvatarLink(),
+		Name:     username,
+		Email:    email,
+		Language: cfg.DefaultLanguage,
 	}
 	emailNotificationPreference := user_model.EmailNotificationsDisabled
 	overwriteDefault := &user_model.CreateUserOverwriteOptions{
diff --git a/templates/org/menu.tmpl b/templates/org/menu.tmpl
index c3ec142e9c..8a97711ce2 100644
--- a/templates/org/menu.tmpl
+++ b/templates/org/menu.tmpl
@@ -9,6 +9,9 @@
 		{{if .CanReadProjects}}
 		<a class="{{if .PageIsViewProjects}}active {{end}}item" href="{{$.Org.HomeLink}}/-/projects">
 			{{svg "octicon-project-symlink"}} {{ctx.Locale.Tr "user.projects"}}
+			{{if .ProjectCount}}
+				<div class="ui small label">{{.ProjectCount}}</div>
+			{{end}}
 		</a>
 		{{end}}
 		{{if and .IsPackageEnabled .CanReadPackages}}
diff --git a/templates/repo/diff/options_dropdown.tmpl b/templates/repo/diff/options_dropdown.tmpl
index 3bcb877cc6..b7c46dd846 100644
--- a/templates/repo/diff/options_dropdown.tmpl
+++ b/templates/repo/diff/options_dropdown.tmpl
@@ -13,7 +13,7 @@
 			<a class="item" href="{{$.RepoLink}}/commit/{{PathEscape .Commit.ID.String}}.diff" download="{{ShortSha .Commit.ID.String}}.diff">{{ctx.Locale.Tr "repo.diff.download_diff"}}</a>
 		{{end}}
 		<a id="expand-files-btn" class="item">{{ctx.Locale.Tr "repo.pulls.expand_files"}}</a>
-		<a id="collapse-files-btn"class="item">{{ctx.Locale.Tr "repo.pulls.collapse_files"}}</a>
+		<a id="collapse-files-btn" class="item">{{ctx.Locale.Tr "repo.pulls.collapse_files"}}</a>
 		{{if .Issue.Index}}
 			{{if .ShowOutdatedComments}}
 				<a class="item" href="?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated=false">
diff --git a/templates/repo/editor/commit_form.tmpl b/templates/repo/editor/commit_form.tmpl
index 34dde576a1..c8f7befcf0 100644
--- a/templates/repo/editor/commit_form.tmpl
+++ b/templates/repo/editor/commit_form.tmpl
@@ -9,7 +9,7 @@
 			{{ctx.Locale.Tr "repo.editor.commit_changes"}}
 		{{- end}}</h3>
 		<div class="field">
-			<input name="commit_summary" placeholder="{{if .PageIsDelete}}{{ctx.Locale.Tr "repo.editor.delete" .TreePath}}{{else if .PageIsUpload}}{{ctx.Locale.Tr "repo.editor.upload_files_to_dir" .TreePath}}{{else if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.add_tmpl"}}{{else if .PageIsPatch}}{{ctx.Locale.Tr "repo.editor.patch"}}{{else}}{{ctx.Locale.Tr "repo.editor.update" .TreePath}}{{end}}" value="{{.commit_summary}}" autofocus>
+			<input name="commit_summary" maxlength="100" placeholder="{{if .PageIsDelete}}{{ctx.Locale.Tr "repo.editor.delete" .TreePath}}{{else if .PageIsUpload}}{{ctx.Locale.Tr "repo.editor.upload_files_to_dir" .TreePath}}{{else if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.add_tmpl"}}{{else if .PageIsPatch}}{{ctx.Locale.Tr "repo.editor.patch"}}{{else}}{{ctx.Locale.Tr "repo.editor.update" .TreePath}}{{end}}" value="{{.commit_summary}}" autofocus>
 		</div>
 		<div class="field">
 			<textarea name="commit_message" placeholder="{{ctx.Locale.Tr "repo.editor.commit_message_desc"}}" rows="5">{{.commit_message}}</textarea>
@@ -60,7 +60,7 @@
 			<div class="quick-pull-branch-name {{if not (eq .commit_choice "commit-to-new-branch")}}gt-hidden{{end}}">
 				<div class="new-branch-name-input field {{if .Err_NewBranchName}}error{{end}}">
 					{{svg "octicon-git-branch"}}
-					<input type="text" name="new_branch_name" value="{{.new_branch_name}}" class="input-contrast gt-mr-2 js-quick-pull-new-branch-name" placeholder="{{ctx.Locale.Tr "repo.editor.new_branch_name_desc"}}" {{if eq .commit_choice "commit-to-new-branch"}}required{{end}} title="{{ctx.Locale.Tr "repo.editor.new_branch_name"}}">
+					<input type="text" name="new_branch_name" maxlength="100" value="{{.new_branch_name}}" class="input-contrast gt-mr-2 js-quick-pull-new-branch-name" placeholder="{{ctx.Locale.Tr "repo.editor.new_branch_name_desc"}}" {{if eq .commit_choice "commit-to-new-branch"}}required{{end}} title="{{ctx.Locale.Tr "repo.editor.new_branch_name"}}">
 					<span class="text-muted js-quick-pull-normalization-info"></span>
 				</div>
 			</div>
diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl
index cfc266731b..a6dce81c08 100644
--- a/templates/repo/editor/edit.tmpl
+++ b/templates/repo/editor/edit.tmpl
@@ -15,7 +15,7 @@
 					{{range $i, $v := .TreeNames}}
 						<div class="breadcrumb-divider">/</div>
 						{{if eq $i $l}}
-							<input id="file-name" value="{{$v}}" placeholder="{{ctx.Locale.Tr "repo.editor.name_your_file"}}" data-editorconfig="{{$.EditorconfigJson}}" required autofocus>
+							<input id="file-name" maxlength="500" value="{{$v}}" placeholder="{{ctx.Locale.Tr "repo.editor.name_your_file"}}" data-editorconfig="{{$.EditorconfigJson}}" required autofocus>
 							<span data-tooltip-content="{{ctx.Locale.Tr "repo.editor.filename_help"}}">{{svg "octicon-info"}}</span>
 						{{else}}
 							<span class="section"><a href="{{$.BranchLink}}/{{index $.TreePaths $i | PathEscapeSegments}}">{{$v}}</a></span>
diff --git a/templates/repo/editor/patch.tmpl b/templates/repo/editor/patch.tmpl
index 44c30bd5f9..c9a78cc35f 100644
--- a/templates/repo/editor/patch.tmpl
+++ b/templates/repo/editor/patch.tmpl
@@ -15,7 +15,7 @@
 					<a class="section" href="{{$.BranchLink}}">{{.BranchName}}</a>
 					<span>{{ctx.Locale.Tr "repo.editor.or"}} <a href="{{$.BranchLink}}">{{ctx.Locale.Tr "repo.editor.cancel_lower"}}</a></span>
 					<input type="hidden" id="tree_path" name="tree_path" value="" required>
-					<input id="file-name" type="hidden" value="diff.patch">
+					<input id="file-name" maxlength="500" type="hidden" value="diff.patch">
 				</div>
 			</div>
 			<div class="field">
diff --git a/templates/repo/editor/upload.tmpl b/templates/repo/editor/upload.tmpl
index d362a5602a..0a7c49dae3 100644
--- a/templates/repo/editor/upload.tmpl
+++ b/templates/repo/editor/upload.tmpl
@@ -13,7 +13,7 @@
 					{{range $i, $v := .TreeNames}}
 						<div class="breadcrumb-divider">/</div>
 						{{if eq $i $l}}
-							<input type="text" id="file-name" value="{{$v}}" placeholder="{{ctx.Locale.Tr "repo.editor.add_subdir"}}" autofocus>
+							<input type="text" id="file-name" maxlength="500" value="{{$v}}" placeholder="{{ctx.Locale.Tr "repo.editor.add_subdir"}}" autofocus>
 							<span data-tooltip-content="{{ctx.Locale.Tr "repo.editor.filename_help"}}">{{svg "octicon-info"}}</span>
 						{{else}}
 							<span class="section"><a href="{{$.BranchLink}}/{{index $.TreePaths $i | PathEscapeSegments}}">{{$v}}</a></span>
diff --git a/templates/user/overview/header.tmpl b/templates/user/overview/header.tmpl
index 69a4e9a856..e27c9a981e 100644
--- a/templates/user/overview/header.tmpl
+++ b/templates/user/overview/header.tmpl
@@ -13,6 +13,9 @@
 	{{if or .ContextUser.IsIndividual (and .ContextUser.IsOrganization .CanReadProjects)}}
 	<a href="{{.ContextUser.HomeLink}}/-/projects" class="{{if .PageIsViewProjects}}active {{end}}item">
 		{{svg "octicon-project-symlink"}} {{ctx.Locale.Tr "user.projects"}}
+		{{if .ProjectCount}}
+			<div class="ui small label">{{.ProjectCount}}</div>
+		{{end}}
 	</a>
 	{{end}}
 	{{if and .IsPackageEnabled (or .ContextUser.IsIndividual (and .ContextUser.IsOrganization .CanReadPackages))}}
diff --git a/web_src/js/bootstrap.js b/web_src/js/bootstrap.js
index f8d0c0cac0..e46c91e5e6 100644
--- a/web_src/js/bootstrap.js
+++ b/web_src/js/bootstrap.js
@@ -29,17 +29,26 @@ export function showGlobalErrorMessage(msg) {
  * @param {ErrorEvent} e
  */
 function processWindowErrorEvent(e) {
+  const err = e.error ?? e.reason;
+  const assetBaseUrl = String(new URL(__webpack_public_path__, window.location.origin));
+
+  // error is likely from browser extension or inline script. Do not show these in production builds.
+  if (!err.stack?.includes(assetBaseUrl) && window.config?.runModeIsProd) return;
+
+  let message;
   if (e.type === 'unhandledrejection') {
-    showGlobalErrorMessage(`JavaScript promise rejection: ${e.reason}. Open browser console to see more details.`);
-    return;
+    message = `JavaScript promise rejection: ${err.message}.`;
+  } else {
+    message = `JavaScript error: ${e.message} (${e.filename} @ ${e.lineno}:${e.colno}).`;
   }
+
   if (!e.error && e.lineno === 0 && e.colno === 0 && e.filename === '' && window.navigator.userAgent.includes('FxiOS/')) {
     // At the moment, Firefox (iOS) (10x) has an engine bug. See https://github.com/go-gitea/gitea/issues/20240
     // If a script inserts a newly created (and content changed) element into DOM, there will be a nonsense error event reporting: Script error: line 0, col 0.
     return; // ignore such nonsense error event
   }
 
-  showGlobalErrorMessage(`JavaScript error: ${e.message} (${e.filename} @ ${e.lineno}:${e.colno}). Open browser console to see more details.`);
+  showGlobalErrorMessage(`${message} Open browser console to see more details.`);
 }
 
 function initGlobalErrorHandler() {
diff --git a/web_src/js/utils.js b/web_src/js/utils.js
index c82e42d349..980354d3ea 100644
--- a/web_src/js/utils.js
+++ b/web_src/js/utils.js
@@ -2,13 +2,14 @@ import {encode, decode} from 'uint8-to-base64';
 
 // transform /path/to/file.ext to file.ext
 export function basename(path = '') {
-  return path ? path.replace(/^.*\//, '') : '';
+  const lastSlashIndex = path.lastIndexOf('/');
+  return lastSlashIndex < 0 ? path : path.substring(lastSlashIndex + 1);
 }
 
 // transform /path/to/file.ext to .ext
 export function extname(path = '') {
-  const [_, ext] = /.+(\.[^.]+)$/.exec(path) || [];
-  return ext || '';
+  const lastPointIndex = path.lastIndexOf('.');
+  return lastPointIndex < 0 ? '' : path.substring(lastPointIndex);
 }
 
 // test whether a variable is an object