mirror of
https://github.com/gogs/gogs.git
synced 2026-02-28 17:20:59 +01:00
Compare commits
13 Commits
v0.12.8-rc
...
v0.12.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ce5171ae1 | ||
|
|
47297c0510 | ||
|
|
a2113bd13a | ||
|
|
7be49a8f2f | ||
|
|
7f147eb573 | ||
|
|
012a1ba19e | ||
|
|
a24b22c909 | ||
|
|
20923a8829 | ||
|
|
4c02b480dc | ||
|
|
deeb3f73e4 | ||
|
|
1bc379f4d4 | ||
|
|
8e8b185ea1 | ||
|
|
42ad18a245 |
42
.github/workflows/go.yml
vendored
42
.github/workflows/go.yml
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x ]
|
||||
platform: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||
platform: [ ubuntu-latest, macos-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Install Go
|
||||
@@ -86,3 +86,43 @@ jobs:
|
||||
The job "${{ github.job }}" of ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} completed with "${{ job.status }}".
|
||||
|
||||
View the job run at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
# Running tests with race detection consumes too much memory on Windows,
|
||||
# see https://github.com/golang/go/issues/46099 for details.
|
||||
test-windows:
|
||||
name: Test
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x ]
|
||||
platform: [ windows-latest ]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Run tests with coverage
|
||||
run: go test -v -coverprofile=coverage -covermode=atomic ./...
|
||||
- name: Upload coverage report to Codecov
|
||||
uses: codecov/codecov-action@v1.5.0
|
||||
with:
|
||||
file: ./coverage
|
||||
flags: unittests
|
||||
- name: Send email on failure
|
||||
uses: dawidd6/action-send-mail@v3
|
||||
if: ${{ failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' }}
|
||||
with:
|
||||
server_address: smtp.mailgun.org
|
||||
server_port: 465
|
||||
username: ${{ secrets.SMTP_USERNAME }}
|
||||
password: ${{ secrets.SMTP_PASSWORD }}
|
||||
subject: GitHub Actions (${{ github.repository }}) job result
|
||||
to: github-actions-8ce6454@unknwon.io
|
||||
from: GitHub Actions (${{ github.repository }})
|
||||
reply_to: noreply@unknwon.io
|
||||
body: |
|
||||
The job "${{ github.job }}" of ${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }} completed with "${{ job.status }}".
|
||||
|
||||
View the job run at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
30
CHANGELOG.md
30
CHANGELOG.md
@@ -19,13 +19,13 @@ All notable changes to Gogs are documented in this file.
|
||||
- MSSQL as database backend is deprecated, installation page no longer shows it as an option. Existing installations and manually craft configuration file continue to work. [#6295](https://github.com/gogs/gogs/pull/6295)
|
||||
- Use [Task](https://github.com/go-task/task) as the build tool. [#6297](https://github.com/gogs/gogs/pull/6297)
|
||||
- The required Go version to compile source code changed to 1.16.
|
||||
- Access tokens are now stored using their SHA256 hashes instead of raw values. [#7008](https://github.com/gogs/gogs/pull/7008)
|
||||
- Support using `[security] LOCAL_NETWORK_ALLOWLIST = *` to allow all hostnames. [#7111](https://github.com/gogs/gogs/pull/7111)
|
||||
|
||||
### Fixed
|
||||
|
||||
- _Security:_ XSS in cookies. [#6953](https://github.com/gogs/gogs/issues/6953)
|
||||
- _Security:_ OS Command Injection in file uploading. [#6968](https://github.com/gogs/gogs/issues/6968)
|
||||
- _Security:_ Remote Command Execution in file editing. [#6555](https://github.com/gogs/gogs/issues/6555)
|
||||
- Unable to use LDAP authentication on ARM machines. [#6761](https://github.com/gogs/gogs/issues/6761)
|
||||
- Unable to send webhooks to local network addresses after configured `[security] LOCAL_NETWORK_ALLOWLIST`. [#7074](https://github.com/gogs/gogs/issues/7074)
|
||||
|
||||
### Removed
|
||||
|
||||
@@ -47,6 +47,30 @@ All notable changes to Gogs are documented in this file.
|
||||
- Configuration option `[database] PASSWD` is no longer used, please use `[database] PASSWORD`.
|
||||
- Remove option to use Makefile as the build tool. [#6980](https://github.com/gogs/gogs/pull/6980)
|
||||
|
||||
## 0.12.9
|
||||
|
||||
### Fixed
|
||||
|
||||
- _Security:_ OS Command Injection in file editor. [#7000](https://github.com/gogs/gogs/issues/7000)
|
||||
- _Security:_ Sanitize `DisplayName` in repository issue list. [#7009](https://github.com/gogs/gogs/pull/7009)
|
||||
- _Security:_ Path Traversal in file editor on Windows. [#7001](https://github.com/gogs/gogs/issues/7001)
|
||||
- _Security:_ Path Traversal in Git HTTP endpoints. [#7002](https://github.com/gogs/gogs/issues/7002)
|
||||
- Unable to init repository during creation on Windows. [#6967](https://github.com/gogs/gogs/issues/6967)
|
||||
- Mysterious panic on `Value not found for type *repo.HTTPContext`. [#6963](https://github.com/gogs/gogs/issues/6963)
|
||||
|
||||
## 0.12.8
|
||||
|
||||
### Changed
|
||||
|
||||
- All users (including admins) need to use the configuration option `[security] LOCAL_NETWORK_ALLOWLIST` to allow repository migration and webhooks to be able to access local network addresses, which is a comma separated list of hostnames. [#6988](https://github.com/gogs/gogs/pull/6988)
|
||||
|
||||
### Fixed
|
||||
|
||||
- _Security:_ SSRF in webhook. [#6901](https://github.com/gogs/gogs/issues/6901)
|
||||
- _Security:_ XSS in cookies. [#6953](https://github.com/gogs/gogs/issues/6953)
|
||||
- _Security:_ OS Command Injection in file uploading. [#6968](https://github.com/gogs/gogs/issues/6968)
|
||||
- _Security:_ Remote Command Execution in file editing. [#6555](https://github.com/gogs/gogs/issues/6555)
|
||||
|
||||
## 0.12.7
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -170,6 +170,7 @@ ENABLE_LOGIN_STATUS_COOKIE = false
|
||||
; The cookie name to store user login status.
|
||||
LOGIN_STATUS_COOKIE_NAME = login_status
|
||||
; A comma separated list of hostnames that are explicitly allowed to be accessed within the local network.
|
||||
; Use "*" to allow all hostnames.
|
||||
LOCAL_NETWORK_ALLOWLIST =
|
||||
|
||||
[email]
|
||||
|
||||
2
gogs.go
2
gogs.go
@@ -19,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
conf.App.Version = "0.12.8"
|
||||
conf.App.Version = "0.12.10"
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -621,11 +621,6 @@ func (pr *PullRequest) UpdateCols(cols ...string) error {
|
||||
|
||||
// UpdatePatch generates and saves a new patch.
|
||||
func (pr *PullRequest) UpdatePatch() (err error) {
|
||||
if pr.HeadRepo == nil {
|
||||
log.Trace("PullRequest[%d].UpdatePatch: ignored cruppted data", pr.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
headGitRepo, err := git.Open(pr.HeadRepo.RepoPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("open repository: %v", err)
|
||||
@@ -759,6 +754,11 @@ func (prs PullRequestList) LoadAttributes() error {
|
||||
|
||||
func addHeadRepoTasks(prs []*PullRequest) {
|
||||
for _, pr := range prs {
|
||||
if pr.HeadRepo == nil {
|
||||
log.Trace("addHeadRepoTasks[%d]: missing head repository", pr.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID)
|
||||
if err := pr.UpdatePatch(); err != nil {
|
||||
log.Error("UpdatePatch: %v", err)
|
||||
|
||||
@@ -912,7 +912,7 @@ func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
|
||||
|
||||
if _, stderr, err = process.ExecDir(-1,
|
||||
tmpPath, fmt.Sprintf("initRepoCommit (git push): %s", tmpPath),
|
||||
"git", "push", "origin", "master"); err != nil {
|
||||
"git", "push"); err != nil {
|
||||
return fmt.Errorf("git push: %s", stderr)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -455,10 +455,12 @@ type UploadRepoFileOptions struct {
|
||||
// path of the repository.
|
||||
func isRepositoryGitPath(path string) bool {
|
||||
return strings.HasSuffix(path, ".git") ||
|
||||
strings.Contains(path, ".git"+string(os.PathSeparator)) ||
|
||||
strings.Contains(path, ".git/") ||
|
||||
strings.Contains(path, `.git\`) ||
|
||||
// Windows treats ".git." the same as ".git"
|
||||
strings.HasSuffix(path, ".git.") ||
|
||||
strings.Contains(path, ".git."+string(os.PathSeparator))
|
||||
strings.Contains(path, ".git./") ||
|
||||
strings.Contains(path, `.git.\`)
|
||||
}
|
||||
|
||||
func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions) error {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -16,23 +15,37 @@ func Test_isRepositoryGitPath(t *testing.T) {
|
||||
path string
|
||||
wantVal bool
|
||||
}{
|
||||
{path: filepath.Join(".", ".git"), wantVal: true},
|
||||
{path: filepath.Join(".", ".git", ""), wantVal: true},
|
||||
{path: filepath.Join(".", ".git", "hooks", "pre-commit"), wantVal: true},
|
||||
{path: filepath.Join(".git", "hooks"), wantVal: true},
|
||||
{path: filepath.Join("dir", ".git"), wantVal: true},
|
||||
{path: ".git", wantVal: true},
|
||||
{path: "./.git", wantVal: true},
|
||||
{path: ".git/hooks/pre-commit", wantVal: true},
|
||||
{path: ".git/hooks", wantVal: true},
|
||||
{path: "dir/.git", wantVal: true},
|
||||
|
||||
{path: filepath.Join(".", ".git."), wantVal: true},
|
||||
{path: filepath.Join(".", ".git.", ""), wantVal: true},
|
||||
{path: filepath.Join(".", ".git.", "hooks", "pre-commit"), wantVal: true},
|
||||
{path: filepath.Join(".git.", "hooks"), wantVal: true},
|
||||
{path: filepath.Join("dir", ".git."), wantVal: true},
|
||||
{path: ".gitignore", wantVal: false},
|
||||
{path: "dir/.gitkeep", wantVal: false},
|
||||
|
||||
{path: filepath.Join(".gitignore"), wantVal: false},
|
||||
{path: filepath.Join("dir", ".gitkeep"), wantVal: false},
|
||||
// Windows-specific
|
||||
{path: `.git\`, wantVal: true},
|
||||
{path: `.git\hooks\pre-commit`, wantVal: true},
|
||||
{path: `.git\hooks`, wantVal: true},
|
||||
{path: `dir\.git`, wantVal: true},
|
||||
|
||||
{path: `.\.git.`, wantVal: true},
|
||||
{path: `.\.git.\`, wantVal: true},
|
||||
{path: `.git.\hooks\pre-commit`, wantVal: true},
|
||||
{path: `.git.\hooks`, wantVal: true},
|
||||
{path: `dir\.git.`, wantVal: true},
|
||||
|
||||
{path: "./.git.", wantVal: true},
|
||||
{path: "./.git./", wantVal: true},
|
||||
{path: ".git./hooks/pre-commit", wantVal: true},
|
||||
{path: ".git./hooks", wantVal: true},
|
||||
{path: "dir/.git.", wantVal: true},
|
||||
|
||||
{path: `dir\.gitkeep`, wantVal: false},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
t.Run(test.path, func(t *testing.T) {
|
||||
assert.Equal(t, test.wantVal, isRepositoryGitPath(test.path))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -689,8 +690,13 @@ func TestWebhook(repo *Repository, event HookEventType, p api.Payloader, webhook
|
||||
}
|
||||
|
||||
func (t *HookTask) deliver() {
|
||||
if netutil.IsBlockedLocalHostname(t.URL, conf.Security.LocalNetworkAllowlist) {
|
||||
t.ResponseContent = "Payload URL resolved to a local network address that is implicitly blocked."
|
||||
payloadURL, err := url.Parse(t.URL)
|
||||
if err != nil {
|
||||
t.ResponseContent = fmt.Sprintf(`{"body": "Cannot parse payload URL: %v"}`, err)
|
||||
return
|
||||
}
|
||||
if netutil.IsBlockedLocalHostname(payloadURL.Hostname(), conf.Security.LocalNetworkAllowlist) {
|
||||
t.ResponseContent = `{"body": "Payload URL resolved to a local network address that is implicitly blocked."}`
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ func init() {
|
||||
// allowlist).
|
||||
func IsBlockedLocalHostname(hostname string, allowlist []string) bool {
|
||||
for _, allow := range allowlist {
|
||||
if hostname == allow {
|
||||
if hostname == allow || allow == "*" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,10 @@ func TestIsLocalHostname(t *testing.T) {
|
||||
{hostname: "google.com", want: false},
|
||||
{hostname: "165.232.140.255", want: false},
|
||||
|
||||
{hostname: "192.168.123.45", allowlist: []string{"10.0.0.17"}, want: true},
|
||||
{hostname: "gogs.local", allowlist: []string{"gogs.local"}, want: false},
|
||||
{hostname: "192.168.123.45", allowlist: []string{"10.0.0.17"}, want: true}, // #11
|
||||
{hostname: "gogs.local", allowlist: []string{"gogs.local"}, want: false}, // #12
|
||||
|
||||
{hostname: "192.168.123.45", allowlist: []string{"*"}, want: false}, // #13
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Clean cleans up given path and returns a relative path that goes straight down.
|
||||
// Clean cleans up given path and returns a relative path that goes straight
|
||||
// down to prevent path traversal.
|
||||
func Clean(p string) string {
|
||||
p = strings.ReplaceAll(p, `\`, "/")
|
||||
return strings.Trim(path.Clean("/"+p), "/")
|
||||
}
|
||||
|
||||
@@ -12,38 +12,73 @@ import (
|
||||
|
||||
func TestClean(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
expVal string
|
||||
path string
|
||||
wantVal string
|
||||
}{
|
||||
{
|
||||
path: "../../../readme.txt",
|
||||
expVal: "readme.txt",
|
||||
path: "../../../readme.txt",
|
||||
wantVal: "readme.txt",
|
||||
},
|
||||
{
|
||||
path: "a/../../../readme.txt",
|
||||
expVal: "readme.txt",
|
||||
path: "a/../../../readme.txt",
|
||||
wantVal: "readme.txt",
|
||||
},
|
||||
{
|
||||
path: "/../a/b/../c/../readme.txt",
|
||||
expVal: "a/readme.txt",
|
||||
path: "/../a/b/../c/../readme.txt",
|
||||
wantVal: "a/readme.txt",
|
||||
},
|
||||
{
|
||||
path: "/a/readme.txt",
|
||||
expVal: "a/readme.txt",
|
||||
path: "../../objects/info/..",
|
||||
wantVal: "objects",
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
expVal: "",
|
||||
path: "/a/readme.txt",
|
||||
wantVal: "a/readme.txt",
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
wantVal: "",
|
||||
},
|
||||
|
||||
{
|
||||
path: "/a/b/c/readme.txt",
|
||||
expVal: "a/b/c/readme.txt",
|
||||
path: "/a/b/c/readme.txt",
|
||||
wantVal: "a/b/c/readme.txt",
|
||||
},
|
||||
|
||||
// Windows-specific
|
||||
{
|
||||
path: `..\..\..\readme.txt`,
|
||||
wantVal: "readme.txt",
|
||||
},
|
||||
{
|
||||
path: `a\..\..\..\readme.txt`,
|
||||
wantVal: "readme.txt",
|
||||
},
|
||||
{
|
||||
path: `\..\a\b\..\c\..\readme.txt`,
|
||||
wantVal: "a/readme.txt",
|
||||
},
|
||||
{
|
||||
path: `\a\readme.txt`,
|
||||
wantVal: "a/readme.txt",
|
||||
},
|
||||
{
|
||||
path: `..\..\..\../README.md`,
|
||||
wantVal: "README.md",
|
||||
},
|
||||
{
|
||||
path: `\`,
|
||||
wantVal: "",
|
||||
},
|
||||
|
||||
{
|
||||
path: `\a\b\c\readme.txt`,
|
||||
wantVal: `a/b/c/readme.txt`,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
assert.Equal(t, test.expVal, Clean(test.path))
|
||||
t.Run(test.path, func(t *testing.T) {
|
||||
assert.Equal(t, test.wantVal, Clean(test.path))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"gogs.io/gogs/internal/conf"
|
||||
"gogs.io/gogs/internal/db"
|
||||
"gogs.io/gogs/internal/lazyregexp"
|
||||
"gogs.io/gogs/internal/pathutil"
|
||||
"gogs.io/gogs/internal/tool"
|
||||
)
|
||||
|
||||
@@ -99,7 +100,7 @@ func HTTPContexter() macaron.Handler {
|
||||
!strings.Contains(action, "info/") &&
|
||||
!strings.Contains(action, "HEAD") &&
|
||||
!strings.Contains(action, "objects/") {
|
||||
c.NotFound()
|
||||
c.Error(http.StatusBadRequest, fmt.Sprintf("Unrecognized action %q", action))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -402,15 +403,21 @@ func HTTP(c *HTTPContext) {
|
||||
}
|
||||
|
||||
if route.method != c.Req.Method {
|
||||
c.NotFound()
|
||||
c.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
file := strings.TrimPrefix(reqPath, m[1]+"/")
|
||||
dir, err := getGitRepoPath(m[1])
|
||||
cleaned := pathutil.Clean(m[1])
|
||||
if m[1] != "/"+cleaned {
|
||||
c.Error(http.StatusBadRequest, "Request path contains suspicious characters")
|
||||
return
|
||||
}
|
||||
|
||||
file := strings.TrimPrefix(reqPath, cleaned)
|
||||
dir, err := getGitRepoPath(cleaned)
|
||||
if err != nil {
|
||||
log.Warn("HTTP.getGitRepoPath: %v", err)
|
||||
c.NotFound()
|
||||
c.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -429,5 +436,5 @@ func HTTP(c *HTTPContext) {
|
||||
return
|
||||
}
|
||||
|
||||
c.NotFound()
|
||||
c.Error(http.StatusNotFound)
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
{{end}}
|
||||
|
||||
<p class="desc">
|
||||
{{$.i18n.Tr "repo.issues.opened_by" $timeStr .Poster.HomeLink .Poster.DisplayName | Safe}}
|
||||
{{$.i18n.Tr "repo.issues.opened_by" $timeStr .Poster.HomeLink .Poster.DisplayName | Sanitize | Safe}}
|
||||
{{if .Milestone}}
|
||||
<a class="milestone" href="{{$.Link}}?type={{$.ViewType}}&state={{$.State}}&labels={{$.SelectLabels}}&milestone={{.Milestone.ID}}&assignee={{$.AssigneeID}}">
|
||||
<span class="octicon octicon-milestone"></span> {{.Milestone.Name | Sanitize}}
|
||||
|
||||
Reference in New Issue
Block a user