mirror of
https://github.com/gogs/gogs.git
synced 2026-02-28 09:10:57 +01:00
Compare commits
12 Commits
release-ar
...
v0.14.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5dcb6c64bd | ||
|
|
b4bdb270d1 | ||
|
|
0120bf0c86 | ||
|
|
094b632182 | ||
|
|
a5c2cc0c6e | ||
|
|
41b186cbfd | ||
|
|
51cf4cbe7e | ||
|
|
5e6014c421 | ||
|
|
f5c8030c1f | ||
|
|
8c5c0125c4 | ||
|
|
3f03530042 | ||
|
|
36c26c4ccc |
13
.claude/commands/ghsa.md
Normal file
13
.claude/commands/ghsa.md
Normal file
@@ -0,0 +1,13 @@
|
||||
Analyze and help fix the GitHub Security Advisory (GHSA) at: $ARGUMENTS
|
||||
|
||||
Steps:
|
||||
1. Fetch the GHSA page using `gh api repos/gogs/gogs/security-advisories` and understand the vulnerability details (description, severity, affected versions, CWE).
|
||||
2. Verify the reported vulnerability actually exists, and why.
|
||||
3. Identify the affected code in this repository.
|
||||
4. Propose a fix with a clear explanation of the root cause and how the fix addresses it. Check for prior art in the codebase to stay consistent with existing patterns.
|
||||
5. Implement the fix. Only add tests when there is something meaningful to test at our layer.
|
||||
6. Run all the usual build and test commands.
|
||||
7. If a changelog entry is warranted (user will specify), add it to CHANGELOG.md with a placeholder for the PR link.
|
||||
8. Create a branch named after the GHSA ID, commit, and push.
|
||||
9. Create a pull request with a proper title and description, do not reveal too much detail and link the GHSA.
|
||||
10. If a changelog entry was added, update it with the PR link, then commit and push again.
|
||||
12
CHANGELOG.md
12
CHANGELOG.md
@@ -4,6 +4,18 @@ All notable changes to Gogs are documented in this file.
|
||||
|
||||
## 0.15.0+dev (`main`)
|
||||
|
||||
### Fixed
|
||||
|
||||
- _Security:_ Cross-repository LFS object overwrite via missing content hash verification. [#8166](https://github.com/gogs/gogs/pull/8166) - [GHSA-gmf8-978x-2fg2](https://github.com/gogs/gogs/security/advisories/GHSA-gmf8-978x-2fg2)
|
||||
- _Security:_ DOM-based XSS via issue meta selection on the issue page. [#8178](https://github.com/gogs/gogs/pull/8178) - [GHSA-vgjm-2cpf-4g7c](https://github.com/gogs/gogs/security/advisories/GHSA-vgjm-2cpf-4g7c)
|
||||
- Unable to update files via web editor and API. [#8184](https://github.com/gogs/gogs/pull/8184)
|
||||
|
||||
### Removed
|
||||
|
||||
- Support for passing API access tokens via URL query parameters (`token`, `access_token`). Use the `Authorization` header instead. [#8177](https://github.com/gogs/gogs/pull/8177) - [GHSA-x9p5-w45c-7ffc](https://github.com/gogs/gogs/security/advisories/GHSA-x9p5-w45c-7ffc)
|
||||
|
||||
- Git clone via the built-in SSH server hangs. [#8132](https://github.com/gogs/gogs/issues/8132)
|
||||
|
||||
## 0.14.0
|
||||
|
||||
### Added
|
||||
|
||||
@@ -279,6 +279,8 @@ ACCESS_CONTROL_ALLOW_ORIGIN =
|
||||
STORAGE = local
|
||||
; The root path to store LFS objects on local file system.
|
||||
OBJECTS_PATH = data/lfs-objects
|
||||
; The path to temporarily store LFS objects during upload verification.
|
||||
OBJECTS_TEMP_PATH = data/tmp/lfs-objects
|
||||
|
||||
[attachment]
|
||||
; Whether to enabled upload attachments in general.
|
||||
|
||||
2
go.mod
2
go.mod
@@ -20,7 +20,7 @@ require (
|
||||
github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6
|
||||
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561
|
||||
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
|
||||
github.com/gogs/git-module v1.8.6
|
||||
github.com/gogs/git-module v1.8.7
|
||||
github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4
|
||||
github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0
|
||||
github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a
|
||||
|
||||
4
go.sum
4
go.sum
@@ -146,8 +146,8 @@ github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 h1:aBzukfDxQlCTVS0NBU
|
||||
github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
|
||||
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 h1:yXtpJr/LV6PFu4nTLgfjQdcMdzjbqqXMEnHfq0Or6p8=
|
||||
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14/go.mod h1:jPoNZLWDAqA5N3G5amEoiNbhVrmM+ZQEcnQvNQ2KaZk=
|
||||
github.com/gogs/git-module v1.8.6 h1:4Io9vWZYQyIjdIPxfKgeYZXnDKNgydc6OZTxII5xCH4=
|
||||
github.com/gogs/git-module v1.8.6/go.mod h1:IiMSJqi8XH62Kjqjt5Rw8IawSo+DHfM2dDjkSzWLjhs=
|
||||
github.com/gogs/git-module v1.8.7 h1:GDyfzB1Z8ytld3LajTfUE4PuIcGcuCHpWB6j8/oD7Tk=
|
||||
github.com/gogs/git-module v1.8.7/go.mod h1:IiMSJqi8XH62Kjqjt5Rw8IawSo+DHfM2dDjkSzWLjhs=
|
||||
github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4 h1:C7NryI/RQhsIWwC2bHN601P1wJKeuQ6U/UCOYTn3Cic=
|
||||
github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
|
||||
github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0 h1:K02vod+sn3M1OOkdqi2tPxN2+xESK4qyITVQ3JkGEv4=
|
||||
|
||||
2
gogs.go
2
gogs.go
@@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
conf.App.Version = "0.14.0+dev"
|
||||
conf.App.Version = "0.14.2"
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -70,7 +70,7 @@ func Init(customConf string) error {
|
||||
if err = File.Append(customConf); err != nil {
|
||||
return errors.Wrapf(err, "append %q", customConf)
|
||||
}
|
||||
} else {
|
||||
} else if !HookMode {
|
||||
log.Warn("Custom config %q not found. Ignore this warning if you're running for the first time", customConf)
|
||||
}
|
||||
|
||||
@@ -142,9 +142,11 @@ func Init(customConf string) error {
|
||||
}
|
||||
|
||||
if IsWindowsRuntime() || semverutil.Compare(sshVersion, "<", "5.1") {
|
||||
log.Warn(`SSH minimum key size check is forced to be disabled because server is not eligible:
|
||||
if !HookMode {
|
||||
log.Warn(`SSH minimum key size check is forced to be disabled because server is not eligible:
|
||||
1. Windows server
|
||||
2. OpenSSH version is lower than 5.1`)
|
||||
}
|
||||
} else {
|
||||
SSH.MinimumKeySizes = map[string]int{}
|
||||
for _, key := range File.Section("ssh.minimum_key_sizes").Keys() {
|
||||
@@ -344,10 +346,13 @@ func Init(customConf string) error {
|
||||
return errors.Wrap(err, "mapping [lfs] section")
|
||||
}
|
||||
LFS.ObjectsPath = ensureAbs(LFS.ObjectsPath)
|
||||
LFS.ObjectsTempPath = ensureAbs(LFS.ObjectsTempPath)
|
||||
|
||||
handleDeprecated()
|
||||
for _, warning := range checkInvalidOptions(File) {
|
||||
log.Warn("%s", warning)
|
||||
if !HookMode {
|
||||
for _, warning := range checkInvalidOptions(File) {
|
||||
log.Warn("%s", warning)
|
||||
}
|
||||
}
|
||||
|
||||
if err = File.Section("cache").MapTo(&Cache); err != nil {
|
||||
|
||||
@@ -361,8 +361,9 @@ type DatabaseOpts struct {
|
||||
var Database DatabaseOpts
|
||||
|
||||
type LFSOpts struct {
|
||||
Storage string
|
||||
ObjectsPath string
|
||||
Storage string
|
||||
ObjectsPath string
|
||||
ObjectsTempPath string
|
||||
}
|
||||
|
||||
// LFS settings
|
||||
@@ -444,7 +445,7 @@ func checkInvalidOptions(config *ini.File) (warnings []string) {
|
||||
"service": "auth",
|
||||
}
|
||||
for oldSection, newSection := range renamedSections {
|
||||
if config.Section(oldSection).KeyStrings() != nil {
|
||||
if len(config.Section(oldSection).KeyStrings()) > 0 {
|
||||
warnings = append(warnings, fmt.Sprintf("section [%s] is invalid, use [%s] instead", oldSection, newSection))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,6 @@ func TestCheckInvalidOptions(t *testing.T) {
|
||||
"section [service] is invalid, use [auth] instead",
|
||||
"option [server] ROOT_URL is invalid, use [server] EXTERNAL_URL instead",
|
||||
"option [server] LANDING_PAGE is invalid, use [server] LANDING_URL instead",
|
||||
|
||||
"option [server] NONEXISTENT_OPTION is invalid",
|
||||
}
|
||||
|
||||
|
||||
@@ -146,18 +146,12 @@ func authenticatedUserID(store AuthStore, c *macaron.Context, sess session.Store
|
||||
|
||||
// Check access token.
|
||||
if isAPIPath(c.Req.URL.Path) {
|
||||
tokenSHA := c.Query("token")
|
||||
if len(tokenSHA) <= 0 {
|
||||
tokenSHA = c.Query("access_token")
|
||||
}
|
||||
if tokenSHA == "" {
|
||||
// Well, check with header again.
|
||||
auHead := c.Req.Header.Get("Authorization")
|
||||
if len(auHead) > 0 {
|
||||
auths := strings.Fields(auHead)
|
||||
if len(auths) == 2 && auths[0] == "token" {
|
||||
tokenSHA = auths[1]
|
||||
}
|
||||
var tokenSHA string
|
||||
auHead := c.Req.Header.Get("Authorization")
|
||||
if auHead != "" {
|
||||
auths := strings.Fields(auHead)
|
||||
if len(auths) == 2 && auths[0] == "token" {
|
||||
tokenSHA = auths[1]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
api "github.com/gogs/go-gogs-client"
|
||||
|
||||
"gogs.io/gogs/internal/errutil"
|
||||
"gogs.io/gogs/internal/process"
|
||||
)
|
||||
|
||||
// Release represents a release of repository.
|
||||
@@ -359,11 +358,13 @@ func DeleteReleaseOfRepoByID(repoID, id int64) error {
|
||||
return errors.Newf("GetRepositoryByID: %v", err)
|
||||
}
|
||||
|
||||
_, stderr, err := process.ExecDir(-1, repo.RepoPath(),
|
||||
fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID),
|
||||
"git", "tag", "-d", rel.TagName)
|
||||
if err != nil && !strings.Contains(stderr, "not found") {
|
||||
return errors.Newf("git tag -d: %v - %s", err, stderr)
|
||||
gitRepo, err := git.Open(repo.RepoPath())
|
||||
if err != nil {
|
||||
return errors.Newf("open repository: %v", err)
|
||||
}
|
||||
err = gitRepo.DeleteTag(rel.TagName)
|
||||
if err != nil && !strings.Contains(err.Error(), "not found") {
|
||||
return errors.Newf("delete tag: %v", err)
|
||||
}
|
||||
|
||||
if _, err = x.Id(rel.ID).Delete(new(Release)); err != nil {
|
||||
|
||||
@@ -158,6 +158,7 @@ func NewRepoContext() {
|
||||
}
|
||||
|
||||
RemoveAllWithNotice("Clean up repository temporary data", filepath.Join(conf.Server.AppDataPath, "tmp"))
|
||||
RemoveAllWithNotice("Clean up LFS temporary data", conf.LFS.ObjectsTempPath)
|
||||
}
|
||||
|
||||
// Repository contains information of a repository.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package lfsutil
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -10,7 +12,10 @@ import (
|
||||
"gogs.io/gogs/internal/osutil"
|
||||
)
|
||||
|
||||
var ErrObjectNotExist = errors.New("Object does not exist")
|
||||
var (
|
||||
ErrObjectNotExist = errors.New("object does not exist")
|
||||
ErrOIDMismatch = errors.New("content hash does not match OID")
|
||||
)
|
||||
|
||||
// Storager is an storage backend for uploading and downloading LFS objects.
|
||||
type Storager interface {
|
||||
@@ -39,6 +44,8 @@ var _ Storager = (*LocalStorage)(nil)
|
||||
type LocalStorage struct {
|
||||
// The root path for storing LFS objects.
|
||||
Root string
|
||||
// The path for storing temporary files during upload verification.
|
||||
TempDir string
|
||||
}
|
||||
|
||||
func (*LocalStorage) Storage() Storage {
|
||||
@@ -58,29 +65,50 @@ func (s *LocalStorage) Upload(oid OID, rc io.ReadCloser) (int64, error) {
|
||||
return 0, ErrInvalidOID
|
||||
}
|
||||
|
||||
var err error
|
||||
fpath := s.storagePath(oid)
|
||||
defer func() {
|
||||
rc.Close()
|
||||
dir := filepath.Dir(fpath)
|
||||
|
||||
if err != nil {
|
||||
_ = os.Remove(fpath)
|
||||
}
|
||||
}()
|
||||
defer rc.Close()
|
||||
|
||||
err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm)
|
||||
if err != nil {
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return 0, errors.Wrap(err, "create directories")
|
||||
}
|
||||
w, err := os.Create(fpath)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "create file")
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
written, err := io.Copy(w, rc)
|
||||
// If the object file already exists, skip the upload and return the
|
||||
// existing file's size.
|
||||
if fi, err := os.Stat(fpath); err == nil {
|
||||
_, _ = io.Copy(io.Discard, rc)
|
||||
return fi.Size(), nil
|
||||
}
|
||||
|
||||
// Write to a temp file and verify the content hash before publishing.
|
||||
// This ensures the final path always contains a complete, hash-verified
|
||||
// file, even when concurrent uploads of the same OID race.
|
||||
if err := os.MkdirAll(s.TempDir, os.ModePerm); err != nil {
|
||||
return 0, errors.Wrap(err, "create temp directory")
|
||||
}
|
||||
tmp, err := os.CreateTemp(s.TempDir, "upload-*")
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "copy file")
|
||||
return 0, errors.Wrap(err, "create temp file")
|
||||
}
|
||||
tmpPath := tmp.Name()
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
hash := sha256.New()
|
||||
written, err := io.Copy(tmp, io.TeeReader(rc, hash))
|
||||
if closeErr := tmp.Close(); err == nil && closeErr != nil {
|
||||
err = closeErr
|
||||
}
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "write object file")
|
||||
}
|
||||
|
||||
if computed := hex.EncodeToString(hash.Sum(nil)); computed != string(oid) {
|
||||
return 0, ErrOIDMismatch
|
||||
}
|
||||
|
||||
if err := os.Rename(tmpPath, fpath); err != nil && !os.IsExist(err) {
|
||||
return 0, errors.Wrap(err, "publish object file")
|
||||
}
|
||||
return written, nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gogs.io/gogs/internal/osutil"
|
||||
)
|
||||
|
||||
func TestLocalStorage_storagePath(t *testing.T) {
|
||||
@@ -46,50 +49,54 @@ func TestLocalStorage_storagePath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLocalStorage_Upload(t *testing.T) {
|
||||
base := t.TempDir()
|
||||
s := &LocalStorage{
|
||||
Root: filepath.Join(os.TempDir(), "lfs-objects"),
|
||||
Root: filepath.Join(base, "lfs-objects"),
|
||||
TempDir: filepath.Join(base, "tmp", "lfs"),
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = os.RemoveAll(s.Root)
|
||||
|
||||
const helloWorldOID = OID("c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a") // "Hello world!"
|
||||
|
||||
t.Run("invalid OID", func(t *testing.T) {
|
||||
written, err := s.Upload("bad_oid", io.NopCloser(strings.NewReader("")))
|
||||
assert.Equal(t, int64(0), written)
|
||||
assert.Equal(t, ErrInvalidOID, err)
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
oid OID
|
||||
content string
|
||||
expWritten int64
|
||||
expErr error
|
||||
}{
|
||||
{
|
||||
name: "invalid oid",
|
||||
oid: "bad_oid",
|
||||
expErr: ErrInvalidOID,
|
||||
},
|
||||
t.Run("valid OID", func(t *testing.T) {
|
||||
written, err := s.Upload(helloWorldOID, io.NopCloser(strings.NewReader("Hello world!")))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(12), written)
|
||||
})
|
||||
|
||||
{
|
||||
name: "valid oid",
|
||||
oid: "ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f",
|
||||
content: "Hello world!",
|
||||
expWritten: 12,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
written, err := s.Upload(test.oid, io.NopCloser(strings.NewReader(test.content)))
|
||||
assert.Equal(t, test.expWritten, written)
|
||||
assert.Equal(t, test.expErr, err)
|
||||
})
|
||||
}
|
||||
t.Run("valid OID but wrong content", func(t *testing.T) {
|
||||
oid := OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f")
|
||||
written, err := s.Upload(oid, io.NopCloser(strings.NewReader("Hello world!")))
|
||||
assert.Equal(t, int64(0), written)
|
||||
assert.Equal(t, ErrOIDMismatch, err)
|
||||
|
||||
// File should have been cleaned up.
|
||||
assert.False(t, osutil.IsFile(s.storagePath(oid)))
|
||||
})
|
||||
|
||||
t.Run("duplicate upload returns existing size", func(t *testing.T) {
|
||||
written, err := s.Upload(helloWorldOID, io.NopCloser(strings.NewReader("should be ignored")))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(12), written)
|
||||
|
||||
// Verify original content is preserved.
|
||||
var buf bytes.Buffer
|
||||
err = s.Download(helloWorldOID, &buf)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "Hello world!", buf.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestLocalStorage_Download(t *testing.T) {
|
||||
oid := OID("ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f")
|
||||
s := &LocalStorage{
|
||||
Root: filepath.Join(os.TempDir(), "lfs-objects"),
|
||||
Root: filepath.Join(t.TempDir(), "lfs-objects"),
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = os.RemoveAll(s.Root)
|
||||
})
|
||||
|
||||
fpath := s.storagePath(oid)
|
||||
err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package markup
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
@@ -32,14 +34,28 @@ func NewSanitizer() {
|
||||
sanitizer.policy.AllowAttrs("type").Matching(lazyregexp.New(`^checkbox$`).Regexp()).OnElements("input")
|
||||
sanitizer.policy.AllowAttrs("checked", "disabled").OnElements("input")
|
||||
|
||||
// Data URLs
|
||||
sanitizer.policy.AllowURLSchemes("data")
|
||||
// Only allow data URIs with safe image MIME types to prevent XSS via
|
||||
// "data:text/html" payloads.
|
||||
sanitizer.policy.AllowURLSchemeWithCustomPolicy("data", isSafeDataURI)
|
||||
|
||||
// Custom URL-Schemes
|
||||
sanitizer.policy.AllowURLSchemes(conf.Markdown.CustomURLSchemes...)
|
||||
})
|
||||
}
|
||||
|
||||
// isSafeDataURI returns whether the given data URI uses a safe image MIME type.
|
||||
func isSafeDataURI(u *url.URL) bool {
|
||||
// The opaque data of a data URI has the form "mediatype;base64,data" or
|
||||
// "mediatype,data". We only allow common image MIME types.
|
||||
mediatype, _, _ := strings.Cut(u.Opaque, ";")
|
||||
mediatype, _, _ = strings.Cut(mediatype, ",")
|
||||
switch strings.TrimSpace(strings.ToLower(mediatype)) {
|
||||
case "image/png", "image/jpeg", "image/gif", "image/webp", "image/x-icon":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Sanitize takes a string that contains a HTML fragment or document and applies policy whitelist.
|
||||
func Sanitize(s string) string {
|
||||
return sanitizer.policy.Sanitize(s)
|
||||
|
||||
@@ -26,6 +26,20 @@ func Test_Sanitizer(t *testing.T) {
|
||||
{input: `<input type="hidden">`, expVal: ``},
|
||||
{input: `<input type="checkbox">`, expVal: `<input type="checkbox">`},
|
||||
{input: `<input checked disabled autofocus>`, expVal: `<input checked="" disabled="">`},
|
||||
|
||||
// Data URIs: safe image types should be allowed
|
||||
{input: `<img src="data:image/png;base64,abc">`, expVal: `<img src="data:image/png;base64,abc">`},
|
||||
{input: `<img src="data:image/jpeg;base64,abc">`, expVal: `<img src="data:image/jpeg;base64,abc">`},
|
||||
{input: `<img src="data:image/gif;base64,abc">`, expVal: `<img src="data:image/gif;base64,abc">`},
|
||||
{input: `<img src="data:image/webp;base64,abc">`, expVal: `<img src="data:image/webp;base64,abc">`},
|
||||
|
||||
// Data URIs: text/html must be stripped to prevent XSS (GHSA-xrcr-gmf5-2r8j)
|
||||
{input: `<a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4=">Click</a>`, expVal: `Click`},
|
||||
{input: `<a href="data:text/html,<script>alert(1)</script>">XSS</a>`, expVal: `XSS`},
|
||||
{input: `<img src="data:text/html;base64,abc">`, expVal: ``},
|
||||
|
||||
// Data URIs: SVG must be stripped (can contain embedded JavaScript)
|
||||
{input: `<img src="data:image/svg+xml;base64,abc">`, expVal: ``},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.input, func(t *testing.T) {
|
||||
|
||||
@@ -91,7 +91,7 @@ func (h *basicHandler) serveUpload(c *macaron.Context, repo *database.Repository
|
||||
s := h.DefaultStorager()
|
||||
written, err := s.Upload(oid, c.Req.Request.Body)
|
||||
if err != nil {
|
||||
if err == lfsutil.ErrInvalidOID {
|
||||
if err == lfsutil.ErrInvalidOID || err == lfsutil.ErrOIDMismatch {
|
||||
responseJSON(c.Resp, http.StatusBadRequest, responseError{
|
||||
Message: err.Error(),
|
||||
})
|
||||
@@ -105,8 +105,8 @@ func (h *basicHandler) serveUpload(c *macaron.Context, repo *database.Repository
|
||||
err = h.store.CreateLFSObject(c.Req.Context(), repo.ID, oid, written, s.Storage())
|
||||
if err != nil {
|
||||
// NOTE: It is OK to leave the file when the whole operation failed
|
||||
// with a DB error, a retry on client side can safely overwrite the
|
||||
// same file as OID is seen as unique to every file.
|
||||
// with a DB error, a retry on client side will skip the upload as
|
||||
// the file already exists on disk.
|
||||
internalServerError(c.Resp)
|
||||
log.Error("Failed to create object [repo_id: %d, oid: %s]: %v", repo.ID, oid, err)
|
||||
return
|
||||
|
||||
@@ -30,7 +30,7 @@ func RegisterRoutes(r *macaron.Router) {
|
||||
store: store,
|
||||
defaultStorage: lfsutil.Storage(conf.LFS.Storage),
|
||||
storagers: map[lfsutil.Storage]lfsutil.Storager{
|
||||
lfsutil.StorageLocal: &lfsutil.LocalStorage{Root: conf.LFS.ObjectsPath},
|
||||
lfsutil.StorageLocal: &lfsutil.LocalStorage{Root: conf.LFS.ObjectsPath, TempDir: conf.LFS.ObjectsTempPath},
|
||||
},
|
||||
}
|
||||
r.Combo("/:oid", verifyOID()).
|
||||
|
||||
@@ -181,7 +181,7 @@ func renderFile(c *context.Context, entry *git.TreeEntry, treeLink, rawLink stri
|
||||
|
||||
output.Reset()
|
||||
for i := 0; i < len(lines); i++ {
|
||||
output.WriteString(fmt.Sprintf(`<span id="L%d">%d</span>`, i+1, i+1))
|
||||
fmt.Fprintf(&output, `<span id="L%d">%d</span>`, i+1, i+1)
|
||||
}
|
||||
c.Data["LineNums"] = gotemplate.HTML(output.String())
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ func handleServerConn(keyID string, chans <-chan ssh.NewChannel) {
|
||||
_ = req.Reply(true, nil)
|
||||
go func() {
|
||||
_, _ = io.Copy(input, ch)
|
||||
input.Close()
|
||||
}()
|
||||
_, _ = io.Copy(ch, stdout)
|
||||
_, _ = io.Copy(ch.Stderr(), stderr)
|
||||
@@ -187,7 +188,6 @@ func setupHostKeys(appDataPath string, algorithms []string) ([]ssh.Signer, error
|
||||
conf.SSH.KeygenPath,
|
||||
"-t", algo,
|
||||
"-f", keyPath,
|
||||
"-m", "PEM",
|
||||
"-N", run.Arg(""),
|
||||
}
|
||||
err = run.Cmd(context.Background(), args...).Run().Wait()
|
||||
|
||||
@@ -240,29 +240,19 @@ function initCommentForm() {
|
||||
}
|
||||
switch (input_id) {
|
||||
case "#milestone_id":
|
||||
$list
|
||||
.find(".selected")
|
||||
.html(
|
||||
'<a class="item" href=' +
|
||||
$(this).data("href") +
|
||||
">" +
|
||||
$(this).text() +
|
||||
"</a>"
|
||||
);
|
||||
var $milestoneAnchor = $('<a class="item"></a>');
|
||||
$milestoneAnchor.attr("href", $(this).data("href"));
|
||||
$milestoneAnchor.text($(this).text());
|
||||
$list.find(".selected").empty().append($milestoneAnchor);
|
||||
break;
|
||||
case "#assignee_id":
|
||||
$list
|
||||
.find(".selected")
|
||||
.html(
|
||||
'<a class="item" href=' +
|
||||
$(this).data("href") +
|
||||
">" +
|
||||
'<img class="ui avatar image" src=' +
|
||||
$(this).data("avatar") +
|
||||
">" +
|
||||
$(this).text() +
|
||||
"</a>"
|
||||
);
|
||||
var $assigneeAnchor = $('<a class="item"></a>');
|
||||
$assigneeAnchor.attr("href", $(this).data("href"));
|
||||
$assigneeAnchor.append(
|
||||
$('<img class="ui avatar image">').attr("src", $(this).data("avatar"))
|
||||
);
|
||||
$assigneeAnchor.append($("<span></span>").text($(this).text()));
|
||||
$list.find(".selected").empty().append($assigneeAnchor);
|
||||
}
|
||||
$(".ui" + select_id + ".list .no-select").addClass("hide");
|
||||
$(input_id).val($(this).data("id"));
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="ui eleven wide column">
|
||||
{{if .IsProtected}}<i class="octicon octicon-shield"></i> {{end}}<a class="markdown" href="{{$.RepoLink}}/src/{{EscapePound .Name}}"><code>{{.Name}}</code></a>
|
||||
{{$timeSince := TimeSince .Commit.Committer.When $.Lang}}
|
||||
<span class="ui text light grey">{{$.i18n.Tr "repo.branches.updated_by" $timeSince .Commit.Committer.Name | Safe}}</span>
|
||||
<span class="ui text light grey">{{$.i18n.Tr "repo.branches.updated_by" $timeSince (Sanitize .Commit.Committer.Name) | Safe}}</span>
|
||||
</div>
|
||||
<div class="ui four wide column">
|
||||
{{if and (and (eq $.BranchName .Name) $.IsRepositoryAdmin) (not $.Repository.IsMirror)}}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div class="ui eleven wide column">
|
||||
{{if .DefaultBranch.IsProtected}}<i class="octicon octicon-shield"></i> {{end}}<a class="markdown" href="{{$.RepoLink}}/src/{{EscapePound .DefaultBranch.Name}}"><code>{{.DefaultBranch.Name}}</code></a>
|
||||
{{$timeSince := TimeSince .DefaultBranch.Commit.Committer.When $.Lang}}
|
||||
<span class="ui text light grey">{{$.i18n.Tr "repo.branches.updated_by" $timeSince .DefaultBranch.Commit.Committer.Name | Safe}}</span>
|
||||
<span class="ui text light grey">{{$.i18n.Tr "repo.branches.updated_by" $timeSince (Sanitize .DefaultBranch.Commit.Committer.Name) | Safe}}</span>
|
||||
</div>
|
||||
{{if and $.IsRepositoryAdmin (not $.Repository.IsMirror)}}
|
||||
<div class="ui four wide column">
|
||||
@@ -33,7 +33,7 @@
|
||||
<div class="ui eleven wide column">
|
||||
{{if .IsProtected}}<i class="octicon octicon-shield"></i> {{end}}<a class="markdown" href="{{$.RepoLink}}/src/{{EscapePound .Name}}"><code>{{.Name}}</code></a>
|
||||
{{$timeSince := TimeSince .Commit.Committer.When $.Lang}}
|
||||
<span class="ui text light grey">{{$.i18n.Tr "repo.branches.updated_by" $timeSince .Commit.Committer.Name | Safe}}</span>
|
||||
<span class="ui text light grey">{{$.i18n.Tr "repo.branches.updated_by" $timeSince (Sanitize .Commit.Committer.Name) | Safe}}</span>
|
||||
</div>
|
||||
{{if and $.IsRepositoryWriter $.AllowPullRequest}}
|
||||
<div class="ui four wide column">
|
||||
@@ -55,7 +55,7 @@
|
||||
<div class="ui eleven wide column">
|
||||
{{if .IsProtected}}<i class="octicon octicon-shield"></i> {{end}}<a class="markdown" href="{{$.RepoLink}}/src/{{EscapePound .Name}}"><code>{{.Name}}</code></a>
|
||||
{{$timeSince := TimeSince .Commit.Committer.When $.Lang}}
|
||||
<span class="ui text light grey">{{$.i18n.Tr "repo.branches.updated_by" $timeSince .Commit.Committer.Name | Safe}}</span>
|
||||
<span class="ui text light grey">{{$.i18n.Tr "repo.branches.updated_by" $timeSince (Sanitize .Commit.Committer.Name) | Safe}}</span>
|
||||
</div>
|
||||
{{if and $.IsRepositoryWriter $.AllowPullRequest}}
|
||||
<div class="ui four wide column">
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
{{end}}
|
||||
<div class="ui sub header">
|
||||
{{$timeSince := TimeSince .Author.When $.Lang}}
|
||||
{{.i18n.Tr "repo.wiki.last_commit_info" .Author.Name $timeSince | Safe}}
|
||||
{{.i18n.Tr "repo.wiki.last_commit_info" (Sanitize .Author.Name) $timeSince | Safe}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="markdown has-emoji">
|
||||
|
||||
Reference in New Issue
Block a user