fix(actions): harden log cursor, activity type and actor filter handling (#39721)

Fixes a few Actions bugs where user input or nondeterminism caused
failures:

- The job log view indexed task steps with the client-supplied cursor
step without a bounds check, so an out-of-range value panicked and
returned a 500.
- Workflow activity type filters (`types:`) were compiled with
`glob.MustCompile`, so an invalid pattern in a workflow file panicked
during event detection. Invalid patterns now simply don't match.
- Listing workflow runs with an unknown `actor` returned a 500; it now
returns 404.
- The v4 artifact `ListArtifacts` response was built by ranging over a
map, so its order changed between calls. It now follows database order.
- The error from marshalling a workflow job when inserting run jobs was
silently ignored.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
bircniandwxiaoguang authored and GitHub committed 2026-10-10 19:52:23 +00:00
1 parent 321d3ec104
commit 8a9e8c4e8c
13 files changed
+70 -28

No files matched your search

@@ -682,7 +682,7 @@ jobs:
taskToken3 := task3.Context.GetFields()["gitea_runtime_token"].GetStringValue()
// the new attempt inherits what the previous attempt uploaded
assert.ElementsMatch(t, []string{"job1-only", "job1-shared"}, listArtifactNamesForRunV4(t, run.ID, job3.ID, taskToken3))
assert.Equal(t, []string{"job1-only", "job1-shared"}, listArtifactNamesForRunV4(t, run.ID, job3.ID, taskToken3))
assert.Equal(t, strings.Repeat("A", 32), downloadArtifactContentV4ByTask(t, run.ID, job3.ID, taskToken3, "job1-only"))
assert.Contains(t, listArtifactNamesForRun(t, run.ID, taskToken3), "job1-v3")
@@ -699,7 +699,7 @@ jobs:
inheritedSharedID := listArtifactIDForRunV4(t, run.ID, job3.ID, taskToken3, "job1-shared")
require.Len(t, listArtifactsByIDV4(t, run.ID, job3.ID, inheritedSharedID, taskToken3), 1)
uploadTestArtifactFileV4(t, run.ID, job3.ID, taskToken3, "job1-shared", strings.Repeat("C", 32))
assert.ElementsMatch(t, []string{"job1-only", "job1-shared"}, listArtifactNamesForRunV4(t, run.ID, job3.ID, taskToken3))
assert.Equal(t, []string{"job1-only", "job1-shared"}, listArtifactNamesForRunV4(t, run.ID, job3.ID, taskToken3))
assert.Equal(t, strings.Repeat("C", 32), downloadArtifactContentV4ByTask(t, run.ID, job3.ID, taskToken3, "job1-shared"))
// a shadowed artifact is not listed by id either: a download resolves by name
@@ -899,7 +899,7 @@ func testActionRunAttemptArtifactV4(t *testing.T, repo *repo_model.Repository, s
uploadTestArtifactFileV4(t, run.ID, job1.ID, taskToken1, "artifact-attempt-1", strings.Repeat("A", 32))
uploadTestArtifactFileV4(t, run.ID, job1.ID, taskToken1, "artifact-shared", strings.Repeat("C", 32))
attempt1Names := listArtifactNamesForRunV4(t, run.ID, job1.ID, taskToken1)
assert.ElementsMatch(t, []string{"artifact-attempt-1", "artifact-shared"}, attempt1Names)
assert.Equal(t, []string{"artifact-attempt-1", "artifact-shared"}, attempt1Names)
runner.execTask(t, task1, &mockTaskOutcome{result: runnerv1.Result_RESULT_SUCCESS})
@@ -915,7 +915,7 @@ func testActionRunAttemptArtifactV4(t *testing.T, repo *repo_model.Repository, s
uploadTestArtifactFileV4(t, run.ID, job2.ID, taskToken2, "artifact-attempt-2", strings.Repeat("B", 32))
uploadTestArtifactFileV4(t, run.ID, job2.ID, taskToken2, "artifact-shared", strings.Repeat("D", 32))
attempt2Names := listArtifactNamesForRunV4(t, run.ID, job2.ID, taskToken2)
assert.ElementsMatch(t, []string{"artifact-attempt-2", "artifact-shared"}, attempt2Names)
assert.Equal(t, []string{"artifact-attempt-2", "artifact-shared"}, attempt2Names)
assert.NotContains(t, attempt2Names, "artifact-attempt-1")
// "artifact-attempt-1" belongs to the first attempt, so the rerun token cannot access it
@@ -69,6 +69,12 @@ func testAPIActionsGetWorkflowRun(t *testing.T) {
MakeRequest(t, req, http.StatusOK)
})
t.Run("ListRunsUnknownActor", func(t *testing.T) {
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs?actor=no-such-user", repo.FullName())).
AddTokenAuth(token)
MakeRequest(t, req, http.StatusNotFound)
})
t.Run("GetJobSteps", func(t *testing.T) {
// Insert task steps for task_id 53 (job 198) so the API can return them once the backend loads them
_, err := db.GetEngine(t.Context()).Insert(&actions_model.ActionTaskStep{