Skip to content

generate's blob download path prints the server-derived workflow id raw in 6 error strings, while its own Saved line is gated #574

Description

@ZacxDev

Found by the round-1 adversarial audit of cli#572 (which hardens the sibling download path). Pre-existing — not introduced by #572.

internal/cmd/generate_output.go has the same renderer-sanitizes-while-its-twin-does-not split that #566 item 2 was about, one command over. #572 gates downloadBlobTo's Saved line (:434, safeTerm(target)) — so the author already treated target as untrusted — while every error string on the same function's failure paths prints it raw.

The text is genuinely server-derived

renderOutName's own doc comment states it: "🔴 BOTH INPUTS ARE UNTRUSTED. The workflow id is SERVER-supplied…". {workflow} expands to filepath.Base(strings.TrimSpace(workflowID)), outputTarget joins that into target, and downloadBlobTo derives name := filepath.Base(target). So the server's workflow id reaches the leaf of both.

cmd/civitai/main.go:57 prints err.Error() unfiltered.

Sites

line string carries
:408 refusing to overwrite the existing file %s target
:419 download %s: %w name
:433 install %s: %w target
:388, :390, :392 blobStatusError's three arms name

blobStatusError (:382) is the structural twin of downloadStatusError — same signature, same defer civitai.TagStatus(status, err), same arms. #572 gated that one once at the top (name = safeTerm(name)) with a comment recording that four spellings of one rule drift apart. This twin was not gated.

Deliberately NOT in scope

:413 create output directory %s: %w is correctly ungated, for the same reason #572 removed its own copy of that gate: dir is filepath.Dir(target), which is outputTarget's outDir — user-supplied via --out-dir, never the server's leaf, because outputTarget refuses any name where name != filepath.Base(name). Sanitizing it would strip user-typed bytes, which internal/saferune's rule forbids. Do not "fix" it.

Known residual, stated rather than hidden

saferune deliberately retains \n and \t, so routing these through safeTerm does not close newline line-forgery on this path either. That class is #552's, and is not part of the condition below.

Closing condition

The six sites in the table route their server-derived argument through safeTermblobStatusError gated once at the top like its twin, not per-arm — and safeTermCoveredBy["downloadBlobTo"] and ["blobStatusError"] carry rows whose why names them.

Checked by: go test ./internal/cmd -count=1 exits non-zero when safeTerm is removed from blobStatusError, with the failure naming that function rather than a build error.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions