mirror of
https://gitea.com/gitea/runner.git
synced 2026-08-27 22:37:46 +00:00
fix: improve behaviour across masking, commands and status (#1194)
Fixes 51 bugs discovered via comparison with `actions/runner`. Every fix has test coverage.
### Secrets
- A short secret registered no shifted-base64 form, so `base64("user:$TOKEN")` printed in the clear
- Encoded forms came only from the whole trimmed value, missing padded and per-line spellings
- Masks split only on `\n`, so `::add-mask::a%0Db` registered neither half
- Adds XML, expression-string and quote-trimming encoders
### Workflow commands
- Split at the last `::` or `]` rather than the first, so `::add-mask::a::b` registered no mask
- A command on the last line without a newline was ignored, and `::ADD-MASK::` did nothing
- `##[...]` did not decode `%3B`/`%5D`, properties lost anything after a second `=`
- `$GITHUB_ENV` and `::set-env::` now refuse `NODE_OPTIONS`
### Status
- `continue-on-error` reported failed, a cancelled job reported success, an `if:` error reported cancelled
- File commands ran after `continue-on-error`, failing the job while the step stayed green
- A bad job output aborted the whole run instead of that job
### Steps and actions
- `${{ matrix.* }}` and `${{ strategy.* }}` were empty inside composite actions
- Composite inputs leaked into nested actions as `INPUT_*`, `with:` matched case-sensitively, `pre` failures were dropped
- Docker actions dropped `runs.env` when the caller passed `with: args:`, and caller `args`/`entrypoint` beat the manifest
- An implicit shell ran with `pipefail`, a `shell:` without `{0}` passed without running
- `container.env` overrode job env and every `$GITHUB_ENV` write, heredocs lost leading blank lines, `$GITHUB_PATH` was not BOM-decoded
Written by Claude Opus 5.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1194
Reviewed-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
@@ -783,8 +783,9 @@ var cmdRegex = regexp.MustCompile(`^::([^ :]+)( [^:]*)?::(.*)$`)
|
||||
// handleCommand takes value still escaped, so that the web UI decodes it exactly once. Only
|
||||
// the branches that consume the payload here decode it.
|
||||
func (r *Reporter) handleCommand(originalContent, command, properties, value string) *string {
|
||||
command = strings.ToLower(command) // GitHub matches command names case-insensitively
|
||||
if r.stopCommandEndToken != "" {
|
||||
if command != r.stopCommandEndToken {
|
||||
if !strings.EqualFold(command, r.stopCommandEndToken) {
|
||||
return &originalContent
|
||||
}
|
||||
// Resumed here rather than from the switch, because the end token is arbitrary and a
|
||||
@@ -871,13 +872,20 @@ func parseCommandProperties(properties string) map[string]string {
|
||||
return props
|
||||
}
|
||||
|
||||
func cutPrefixFold(s, prefix string) (string, bool) {
|
||||
if len(s) < len(prefix) || !strings.EqualFold(s[:len(prefix)], prefix) {
|
||||
return s, false
|
||||
}
|
||||
return s[len(prefix):], true
|
||||
}
|
||||
|
||||
func (r *Reporter) parseLogRow(entry *log.Entry) *runnerv1.LogRow {
|
||||
content := strings.TrimRight(entry.Message, "\r\n")
|
||||
|
||||
// cmdRegex only covers the ::cmd:: form, so the ##[add-mask] one would otherwise reach
|
||||
// the log carrying its own secret. Registered and dropped like its ::add-mask:: twin.
|
||||
if arg, ok := strings.CutPrefix(content, "##[add-mask]"); ok {
|
||||
r.addMask(runner.UnescapeCommandData(arg))
|
||||
if arg, ok := cutPrefixFold(content, "##[add-mask]"); ok {
|
||||
r.addMask(runner.UnescapeLegacyCommand(arg))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"gitea.com/gitea/runner/internal/pkg/metrics"
|
||||
|
||||
connect_go "connectrpc.com/connect"
|
||||
"gitea.dev/actionslib/pkg/model"
|
||||
runnerv1 "gitea.dev/actionslib/runner/v1"
|
||||
log "github.com/sirupsen/logrus"
|
||||
logrustest "github.com/sirupsen/logrus/hooks/test"
|
||||
@@ -273,14 +274,14 @@ func TestReporter_Fire(t *testing.T) {
|
||||
"stepID": []string{"0", "0"},
|
||||
"stepNumber": 0,
|
||||
"raw_output": true,
|
||||
"stepResult": "failure",
|
||||
"stepResult": model.StepStatusFailure,
|
||||
}}))
|
||||
assert.Equal(t, runnerv1.Result_RESULT_UNSPECIFIED, reporter.state.Steps[0].Result)
|
||||
require.NoError(t, reporter.Fire(&log.Entry{Message: "step result", Data: map[string]any{
|
||||
"stage": "Main",
|
||||
"stepNumber": 0,
|
||||
"raw_output": true,
|
||||
"stepResult": "success",
|
||||
"stepResult": model.StepStatusSuccess,
|
||||
}}))
|
||||
assert.Equal(t, runnerv1.Result_RESULT_SUCCESS, reporter.state.Steps[0].Result)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user