Skip to content

Commit eecfe9b

Browse files
committed
fix
1 parent 0ebacf5 commit eecfe9b

File tree

5 files changed

+49
-54
lines changed

5 files changed

+49
-54
lines changed

custom/conf/app.example.ini

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2182,10 +2182,10 @@ ROUTER = console
21822182
;; * no-sanitizer: Disable the sanitizer and render the content inside current page. It's **insecure** and may lead to XSS attack if the content contains malicious code.
21832183
;; * iframe: Render the content in a separate standalone page and embed it into current page by iframe. The iframe is in sandbox mode with same-origin disabled, and the JS code are safely isolated from parent page.
21842184
;RENDER_CONTENT_MODE=sanitized
2185-
;; when RENDER_CONTENT_MODE is iframe, below two items will be avaible
21862185
;;
2187-
;RENDER_CONTENT_IFRAME_SANDBOX=allow-scripts
2188-
;RENDER_CONTENT_EXTERNAL_CSP=sandbox allow-scripts
2186+
;; When RENDER_CONTENT_MODE is iframe, these two options are available
2187+
;RENDER_CONTENT_IFRAME_SANDBOX="allow-scripts"
2188+
;RENDER_CONTENT_EXTERNAL_CSP="iframe-src 'self'; sandbox allow-scripts"
21892189

21902190
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
21912191
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

docs/content/doc/advanced/config-cheat-sheet.en-us.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,8 +1044,8 @@ IS_INPUT_FILE = false
10441044
- sanitized: Sanitize the content and render it inside current page, default to only allow a few HTML tags and attributes. Customized sanitizer rules can be defined in `[markup.sanitizer.*]`.
10451045
- no-sanitizer: Disable the sanitizer and render the content inside current page. It's **insecure** and may lead to XSS attack if the content contains malicious code.
10461046
- iframe: Render the content in a separate standalone page and embed it into current page by iframe. The iframe is in sandbox mode with same-origin disabled, and the JS code are safely isolated from parent page.
1047-
- RENDER_CONTENT_IFRAME_SANDBOX: **allow-scripts** When `RENDER_CONTENT_MODE` is `iframe`, this will be the allowed sandbox of iframe properties.
1048-
- RENDER_CONTENT_EXTERNAL_CSP: **sandbox allow-scripts** When `RENDER_CONTENT_MODE` is `iframe`, this will be the allowed CSP of external renderer response.
1047+
- RENDER_CONTENT_IFRAME_SANDBOX: **"allow-scripts"** When `RENDER_CONTENT_MODE` is `iframe`, this will be the allowed sandbox of iframe properties.
1048+
- RENDER_CONTENT_EXTERNAL_CSP: **"iframe-src 'self'; sandbox allow-scripts"** When `RENDER_CONTENT_MODE` is `iframe`, this will be the allowed CSP of external renderer response.
10491049

10501050
Two special environment variables are passed to the render command:
10511051

modules/markup/renderer.go

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ type RenderContext struct {
5555
ShaExistCache map[string]bool
5656
cancelFn func()
5757
TableOfContents []Header
58+
59+
// InStandalonePage is used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page
60+
// It is for maintenance and security purpose, to avoid rendering external JS into embedded page unexpectedly.
61+
// The caller of the Render must set security headers correctly before setting it to true
62+
InStandalonePage bool
5863
}
5964

6065
// Cancel runs any cleanup functions that have been registered for this Ctx
@@ -98,18 +103,18 @@ type PostProcessRenderer interface {
98103
NeedPostProcess() bool
99104
}
100105

101-
// PostProcessRenderer defines an interface for external renderers
106+
// ExternalRenderer defines an interface for external renderers
102107
type ExternalRenderer interface {
103108
// SanitizerDisabled disabled sanitize if return true
104109
SanitizerDisabled() bool
105110

106111
// DisplayInIFrame represents whether render the content with an iframe
107112
DisplayInIFrame() bool
108113

109-
// IframeSandbox represents iframe sandbox allowed options
114+
// IframeSandbox represents iframe sandbox attribute for the <iframe> tag
110115
IframeSandbox() string
111116

112-
// ExternalCSP represents external render CSP
117+
// ExternalCSP represents the Content-Security-Policy header for external render
113118
ExternalCSP() string
114119
}
115120

@@ -157,13 +162,14 @@ func DetectRendererType(filename string, input io.Reader) string {
157162
return ""
158163
}
159164

160-
// GetRenderer returned the renderer according type or relativepath
161-
func GetRenderer(tp, relativePath string) (Renderer, error) {
162-
if tp != "" {
163-
if renderer, ok := renderers[tp]; ok {
165+
// GetRenderer returned the renderer according type or relative path
166+
func GetRenderer(renderType, relativePath string) (Renderer, error) {
167+
if renderType != "" {
168+
if renderer, ok := renderers[renderType]; ok {
164169
return renderer, nil
165170
}
166-
return nil, ErrUnsupportedRenderType{tp}
171+
// FIXME: is it correct? if it returns here, then relativePath won't take effect
172+
return nil, ErrUnsupportedRenderType{renderType}
167173
}
168174

169175
if relativePath != "" {
@@ -173,7 +179,8 @@ func GetRenderer(tp, relativePath string) (Renderer, error) {
173179
}
174180
return nil, ErrUnsupportedRenderExtension{extension}
175181
}
176-
return nil, errors.New("Render options both filename and type missing")
182+
183+
return nil, errors.New("render options both filename and type missing")
177184
}
178185

179186
// Render renders markup file to HTML with all specific handling stuff.
@@ -231,6 +238,13 @@ sandbox="%s"
231238

232239
// RenderDirect renders markup file to HTML with all specific handling stuff.
233240
func RenderDirect(ctx *RenderContext, renderer Renderer, input io.Reader, output io.Writer) error {
241+
if r, ok := renderer.(ExternalRenderer); ok && r.DisplayInIFrame() {
242+
// to prevent from rendering external JS into embedded page unexpectedly, which would lead to XSS attack
243+
if !ctx.InStandalonePage {
244+
return errors.New("external render with iframe can only render in standalone page")
245+
}
246+
}
247+
234248
var wg sync.WaitGroup
235249
var err error
236250
pr, pw := io.Pipe()

modules/setting/markup.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ type MarkupRenderer struct {
3636
NeedPostProcess bool
3737
MarkupSanitizerRules []MarkupSanitizerRule
3838
RenderContentMode string
39-
RenderContentIframeSandbox string // allow-scripts
39+
RenderContentIframeSandbox string
4040
RenderContentExternalCSP string
4141
}
4242

@@ -177,6 +177,6 @@ func newMarkupRenderer(name string, sec *ini.Section) {
177177
NeedPostProcess: sec.Key("NEED_POSTPROCESS").MustBool(true),
178178
RenderContentMode: renderContentMode,
179179
RenderContentIframeSandbox: sec.Key("RENDER_CONTENT_IFRAME_SANDBOX").MustString("allow-scripts"),
180-
RenderContentExternalCSP: sec.Key("RENDER_CONTENT_EXTERNAL_CSP").MustString("sandbox allow-scripts"),
180+
RenderContentExternalCSP: sec.Key("RENDER_CONTENT_EXTERNAL_CSP").MustString("iframe-src 'self'; sandbox allow-scripts"),
181181
})
182182
}

routers/web/repo/render.go

Lines changed: 19 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,16 @@
55
package repo
66

77
import (
8-
"bytes"
9-
"fmt"
10-
"io"
118
"net/http"
129
"path"
1310

14-
"code.gitea.io/gitea/modules/charset"
1511
"code.gitea.io/gitea/modules/context"
1612
"code.gitea.io/gitea/modules/git"
1713
"code.gitea.io/gitea/modules/markup"
18-
"code.gitea.io/gitea/modules/typesniffer"
1914
"code.gitea.io/gitea/modules/util"
2015
)
2116

22-
// RenderFile renders a file by repos path
17+
// RenderFile uses an external render to render a file by repos path
2318
func RenderFile(ctx *context.Context) {
2419
blob, err := ctx.Repo.Commit.GetBlobByPath(ctx.Repo.TreePath)
2520
if err != nil {
@@ -38,53 +33,39 @@ func RenderFile(ctx *context.Context) {
3833
}
3934
defer dataRc.Close()
4035

41-
buf := make([]byte, 1024)
42-
n, _ := util.ReadAtMost(dataRc, buf)
43-
buf = buf[:n]
44-
45-
st := typesniffer.DetectContentType(buf)
46-
isTextFile := st.IsText()
47-
48-
rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc))
49-
50-
if markupType := markup.Type(blob.Name()); markupType == "" {
51-
if isTextFile {
52-
_, err = io.Copy(ctx.Resp, rd)
53-
if err != nil {
54-
ctx.ServerError("Copy", err)
55-
}
56-
return
57-
}
58-
ctx.Error(http.StatusInternalServerError, "Unsupported file type render")
59-
return
60-
}
61-
6236
treeLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
6337
if ctx.Repo.TreePath != "" {
6438
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
6539
}
6640

67-
var allowSameOriginStr string
68-
6941
renderer, err := markup.GetRenderer("", ctx.Repo.TreePath)
7042
if err != nil {
7143
ctx.ServerError("GetRenderer", err)
7244
return
7345
}
7446

75-
if r, ok := renderer.(markup.ExternalRenderer); ok {
76-
allowSameOriginStr = r.ExternalCSP()
47+
externalRender, ok := renderer.(markup.ExternalRenderer)
48+
if !ok {
49+
ctx.Error(http.StatusBadRequest, "External render only")
50+
return
51+
}
52+
53+
externalCSP := externalRender.ExternalCSP()
54+
if externalCSP == "" {
55+
ctx.Error(http.StatusBadRequest, "External render must have valid Content-Security-Header")
56+
return
7757
}
7858

79-
ctx.Resp.Header().Add("Content-Security-Policy", fmt.Sprintf("frame-src 'self'; %s", allowSameOriginStr))
59+
ctx.Resp.Header().Add("Content-Security-Policy", externalCSP)
8060

8161
if err = markup.RenderDirect(&markup.RenderContext{
82-
Ctx: ctx,
83-
RelativePath: ctx.Repo.TreePath,
84-
URLPrefix: path.Dir(treeLink),
85-
Metas: ctx.Repo.Repository.ComposeDocumentMetas(),
86-
GitRepo: ctx.Repo.GitRepo,
87-
}, renderer, rd, ctx.Resp); err != nil {
62+
Ctx: ctx,
63+
RelativePath: ctx.Repo.TreePath,
64+
URLPrefix: path.Dir(treeLink),
65+
Metas: ctx.Repo.Repository.ComposeDocumentMetas(),
66+
GitRepo: ctx.Repo.GitRepo,
67+
InStandalonePage: true,
68+
}, renderer, dataRc, ctx.Resp); err != nil {
8869
ctx.ServerError("RenderDirect", err)
8970
return
9071
}

0 commit comments

Comments
 (0)