Skip to content

feat(actions): Add artifact preview in Actions run view - #36754

Open
bircni wants to merge 34 commits into
go-gitea:mainfrom
bircni:feature/artifact-viewer
Open

feat(actions): Add artifact preview in Actions run view#36754
bircni wants to merge 34 commits into
go-gitea:mainfrom
bircni:feature/artifact-viewer

Conversation

@bircni

@bircni bircni commented Feb 25, 2026

Copy link
Copy Markdown
Member

Closes #33579.

Adds browser preview support for Actions artifacts in the run view, so files can be viewed without downloading the ZIP. Clicking an artifact name now opens a lightweight file browser at /preview; clicking a file opens its rendered or raw content in a new tab.

Previews are read-only and sandboxed: HTML is rendered with a strict CSP (sandbox, no allow-same-origin, connect-src 'none'), only text/* and application/pdf are previewable, and a configurable [actions] ARTIFACT_PREVIEW_MAX_SIZE bounds how large an artifact can be before preview is disabled. The existing ZIP download action is unchanged.

image

docs PR: https://gitea.com/gitea/docs/pulls/533

@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Feb 25, 2026
@bircni

bircni commented Feb 25, 2026

Copy link
Copy Markdown
Member Author

you can test with http://localhost:3000/devtest/repo-action-view

@silverwind

Copy link
Copy Markdown
Member

What's up with the font in second screenshot? Is that just the devtest mockup? We should not render serif fonts.

Comment thread web_src/js/components/RepoActionView.vue Outdated
@silverwind

Copy link
Copy Markdown
Member

How do we secure this iframe from exploiting the parent page? Common practices include:

<iframe
  sandbox="allow-scripts"
  referrerpolicy="no-referrer"
  csp="default-src 'self' 'unsafe-inline'"
></iframe>

And these HTTP headers on the parent page:

X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'

@wxiaoguang
wxiaoguang marked this pull request as draft February 26, 2026 04:52
@wxiaoguang

Copy link
Copy Markdown
Contributor

AI seldom designs correct tests or mocks.

You can't make the the template be a mock.

Comment thread templates/repo/actions/artifact_preview.tmpl Outdated
Comment thread routers/web/repo/actions/view.go Outdated
@bircni

bircni commented Feb 26, 2026

Copy link
Copy Markdown
Member Author

I suggest the following:

  • Improve iframe hardening:
    • switch iframe to referrerpolicy="no-referrer",
    • keep strict CSP sandbox on HTML raw preview responses,
    • keep PDF behavior compatible with browser rendering (no sandboxing that breaks PDF viewers).
  • Fix relative links in previewed HTML by supporting path-based raw routes (.../preview/raw/*) and using path-based iframe src instead of only ?path=....
  • Address v4 storage read amplification by removing full-object copy for ZIP parsing and using seek/read-at based ZIP access over artifact storage.
  • Add an option to enable or disable the feature from app.ini?

@silverwind

silverwind commented Feb 26, 2026

Copy link
Copy Markdown
Member

sandbox would be a hard requirement for me to secure the iframe. allow-scripts is fine to allow the content to run scripts. If more permissions are needed, add more of these allow-* attributes.

Add an option to enable or disable the feature from app.ini?

I don't fully understand the use case of this yet, but I guess it wouldn't hurt being able to disable it.

@wxiaoguang

wxiaoguang commented Feb 27, 2026

Copy link
Copy Markdown
Contributor
  • Address v4 storage read amplification by removing full-object copy for ZIP parsing and using seek/read-at based ZIP access over artifact storage.

I don't think it works for ObjectStorage.

Maybe you need to store the file list to somewhere else, don't depend on the big zip file.

Update: #36754 (comment)

  • Add an option to enable or disable the feature from app.ini?

As long as the design is good enough, no need to use an option to toggle the future.

@ChristopherHX

Copy link
Copy Markdown
Contributor

I want to rework artifacts to support other file types, previewing a zip is not something I think is good.

My Reasoning https://github.blog/changelog/2026-02-26-github-actions-now-supports-uploading-and-downloading-non-zipped-artifacts/, so the backend can get some love.

@bircni

bircni commented Feb 27, 2026

Copy link
Copy Markdown
Member Author

I want to rework artifacts to support other file types, previewing a zip is not something I think is good.

My Reasoning https://github.blog/changelog/2026-02-26-github-actions-now-supports-uploading-and-downloading-non-zipped-artifacts/, so the backend can get some love.

Oh yes please!!
I need this

@ChristopherHX

ChristopherHX commented Feb 28, 2026

Copy link
Copy Markdown
Contributor

Created a draft of artifacts v4 with custom mime types #36786, maybe I should remove the unsecure inline content-disposition l had experimentally enabled there.

This is GitHub.com https://github.com/actions/toolkit/actions/runs/22458120907

As I expected they use the blob storage url that has a domain sensitive sandbox for inline content disposition.

@silverwind

silverwind commented Mar 1, 2026

Copy link
Copy Markdown
Member

Either iframe with sandbox attribute or HTML with Content-Security-Policy: sandbox should be equivalently safe. content-disposition has no real security implications that I know of, it just controls whether content is displayed or downloaded.

@ChristopherHX

ChristopherHX commented Mar 1, 2026

Copy link
Copy Markdown
Contributor

With iframe this is looks to be ok.

As long nobody can navigate you to the direct url to exploit your browser sandbox (bypassing iframe via special crafted link)

I read today that (citation needed, source is not 100% trustworthy)

> Content-Security-Policy: sandbox

Might be bypassed if the link is opened outside an iframe.

content-disposition has no real security implications that I know of

The type attachment prevents previewing the html + js in context of our gitea instance by forcing the browser to always download the file.

Effectively makes the html not part of the gitea website.

(only client side security)

EDIT msedge and firefox appl the sandbox rule without iframe from http headers

@wxiaoguang

wxiaoguang commented Mar 1, 2026

Copy link
Copy Markdown
Contributor

I think you can refer to our external render: Make external iframe render work #35730

If it is right, you can use the same approach. If you find security problems, let's fix 😄

@ChristopherHX

Copy link
Copy Markdown
Contributor

Ok I know it better now, you are right CSP header works fine served stand alone (tested manually in firefox and msedge)


Not sure if we should really allow file previews within zipped artifacts via backend unpacking, since this makes serve direct impossible.

@bircni

bircni commented Mar 13, 2026

Copy link
Copy Markdown
Member Author

@ChristopherHX is this still needed or already covered by another PR from you?
If so I would close this PR

@ChristopherHX

Copy link
Copy Markdown
Contributor

@ChristopherHX is this still needed or already covered by another PR from you?

Not covered by me (basically everything you did here)

  • preview of v1-v3 artifacts
  • preview of v4+ zipped artifacts
    Only the following is covered my me (new type of artifact upload with mime type)
  • Open Single File Artifacts as preview in next tab
    • (v7 upload with archive: false during upload)
    • html, pdf, pictures etc. Browser decides to download

@bircni

bircni commented Mar 13, 2026

Copy link
Copy Markdown
Member Author
  Only the following is covered my me (new type of artifact upload with mime type)

* Open Single File Artifacts as preview in next tab
  
  * (v7 upload with archive: false during upload)
  * html, pdf, pictures etc. Browser decides to download

I think its enough to only support the newr ones - what do you think?

@bircni bircni closed this Mar 16, 2026
@bircni bircni reopened this Mar 31, 2026
@bircni
bircni force-pushed the feature/artifact-viewer branch from 4a73b1a to 979572f Compare March 31, 2026 18:42
Comment thread web_src/css/index.css Outdated
Comment thread web_src/css/features/actions-artifact-preview.css Outdated
req := NewRequestf(t, "GET", "/%s/actions/runs/791/artifacts/artifact-download", repo.FullName())
resp := session.MakeRequest(t, req, http.StatusOK)
assert.Contains(t, resp.Header().Get("Content-Disposition"), "attachment; filename=artifact-download.zip")
}

@silverwind silverwind Aug 21, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make these are fast and consolidated, I assume they are rather slow currently with all the serial sequencing.

@silverwind

silverwind commented Aug 21, 2026

Copy link
Copy Markdown
Member

Also I recommend a security review pass on this. HTML rendering business is always security-critical.

@bircni

bircni commented Aug 21, 2026

Copy link
Copy Markdown
Member Author

PR description needs to be rewritten as per latest AGENTS.md. And I recommend doing a AI cleanup pass to ensure everything conforms and is clean and minimal.

@silverwind fixed and fixed your reviews - will do a security review now

@bircni

bircni commented Aug 21, 2026

Copy link
Copy Markdown
Member Author

Also I recommend a security review pass on this. HTML rendering business is always security-critical.

Security Review: feature/artifact-viewer

No findings met the reporting threshold (confidence ≥ 8).

One candidate was identified and filtered out:

  • Arbitrary user-controlled HTML/JS rendered as a top-level document on the instance origin (routers/web/repo/actions/view_artifact.go:258-264, :403-414) — filtered at confidence 3/10. The mechanism is real, but the asserted XSS impact is neutralized: the CSP sandbox allow-scripts (without allow-same-origin) is applied on every path that can emit text/html, giving the document an opaque origin with no access to cookies, localStorage, or the CSRF token, and connect-src 'none' blocks fetch/XHR. Absent allow-forms/allow-popups/allow-top-navigation/allow-downloads tokens block form submission, popups, self-navigation, and downloads. What remains is a hardening/content-policy concern (deviation from the "do not render user HTML as a page" rule in modules/httplib/serve.go, and missing frame-ancestors 'none'/base-uri 'none'), which falls under the hardening exclusion.

Areas verified as sound, for the record:

  • Path traversalnormalizeArtifactPreviewPath applies util.PathJoinRelX identically on both the listing and the lookup side; v4 paths are matched against zip central-directory entries only, and legacy paths resolve to artifact.StoragePath from the DB. No filesystem path is derived from request input.
  • Authorization — the three new routes sit in the existing /{username}/{reponame}/actions group behind optSignIn, RepoAssignment, MustBeNotEmpty, reqRepoActionsReader, MustEnableActions, identical to the pre-existing download route; artifact lookup is scoped by run ID plus resolved attempt ID and re-checks ArtifactStatusUploadConfirmed.
  • Content-type sniffing — gated on the sniffed type (IsText() || IsPDF()), so SVG is rejected; the extension override only narrows to text/css/text/javascript/text/html, and text/html is exactly the case carrying the explicit CSP. X-Content-Type-Options: nosniff is always set.
  • Template injection / XSS in the preview page — no Safe/Str2html/template.HTML in the new templates; hrefs are built via url.PathEscape and PathEscapeSegments.
  • Error pagesWritePreviewRawError emits only constants through html.EscapeString under a stricter no-scripts sandbox CSP.
  • Listing cache — keyed on globally unique artifactID:UpdatedUnix, stores only path strings; no cross-repo disclosure.

@bircni
bircni requested a review from silverwind August 22, 2026 21:21
@bircni
bircni requested a review from a team August 29, 2026 07:23
# Conflicts:
#	custom/conf/app.example.ini
#	modules/setting/actions.go
Comment thread custom/conf/app.example.ini Outdated
;; Artifacts could have their own retention periods by setting the `retention-days` option in `actions/upload-artifact` step.
;ARTIFACT_RETENTION_DAYS = 90
;; Max artifact size in bytes that can be browsed or previewed in the web UI. Set to 0 to disable artifact previews, or -1 for no limit.
;ARTIFACT_PREVIEW_MAX_SIZE = 104857600

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

104MB? It might too big?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reduced the default to 10 MiB. Administrators can increase it or set -1 for no limit

@bircni

bircni commented Aug 30, 2026

Copy link
Copy Markdown
Member Author

--> Conclusion
No qualifying high- or medium-severity vulnerabilities found. The new code is notably defensive in the two places that matter most.

Reviewed by CodeT with Socrates, Stoic and the Security Gang Teams

cc @lunny

@wxiaoguang

Copy link
Copy Markdown
Contributor

I think it means that on a public instance, any user can run a full static site under the instance's domain to let other visitors to view it?

Although its origin is sandboxed, but I think we were preventing from such case because it allows attackers to publish malicious phishing content under the instance's domain?

@bircni

bircni commented Aug 30, 2026

Copy link
Copy Markdown
Member Author

I think it means that on a public instance, any user can run a full static site under the instance's domain to let other visitors to view it?

Although its origin is sandboxed, but I think we were preventing from such case because it allows attackers to publish malicious phishing content under the instance's domain?

Should we just disable it per default?
I think that would be good

@wxiaoguang

wxiaoguang commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Or only serve it to signed-in users? Then crawlers won't read the phishing content.

If a user already has an account, maybe it's fine to assume that they should know what the site is for?

And I don't see why an anonymous user should preview the artifact output.


Also add a banner to prompt saying that "the content is automatically generated by XXX", then it won't mislead users to view it as a standalone static site.

Comment thread routers/web/repo/actions/view_artifact.go Outdated
@bircni

bircni commented Aug 30, 2026

Copy link
Copy Markdown
Member Author

@wxiaoguang added the following

  • Require sign-in for artifact previews
  • generated-content warning banner
  • Display content in a sandboxed iframe
  • Simplify CSP to sandbox allow-scripts
  • Keep preview navigation in the same tab

@bircni
bircni requested a review from wxiaoguang August 30, 2026 10:50
@wxiaoguang

This comment was marked as resolved.

@bircni

This comment was marked as resolved.

@wxiaoguang

wxiaoguang commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Is it intentionally that the layout is top-bottom?

If I remember correctly, all "file explorers / viewers" use left-right layout?

Details image

@bircni

bircni commented Aug 30, 2026

Copy link
Copy Markdown
Member Author

@wxiaoguang

  • moved it to a left bar - thanks for saying!
  • improved the ui
  • Showing the coverage fixture in standalone devtest now too

@bircni bircni added the release/highlight Marks a PR as a highlight-worthy change for the release notes. label Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs-update-needed The document needs to be updated synchronously lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. release/highlight Marks a PR as a highlight-worthy change for the release notes. topic/gitea-actions related to the actions of Gitea type/feature Completely new functionality. Can only be merged if feature freeze is not active.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add ability to preview artifact in Gitea Action

7 participants