Harden workflow-name and owner-label validation against injection - #571
Merged
Conversation
The --owner-label flag had no effective validation: `validate:"omitempty"` on a plain string is a no-op, so it accepted arbitrary bytes including quotes, newlines and shell metacharacters. Add an owner_label validator (letters, numbers, spaces, dots, dashes, underscores; must start alphanumeric; max 64) and apply it on both `cre account link-key` and `cre workflow deploy`, trimming surrounding whitespace first. Also close the interactive bypass: Execute runs after ValidateInputs, so a label supplied at the ui.Input prompt never reached the validator at all. Add a WithValidate option to ui.Input (only InputForm supported one) for inline feedback, and re-check the value after the prompt returns. Workflow names already enforced ^[a-zA-Z0-9_-]+$, but only deploy, pause, delete and activate checked it. Validate the name once at settings load so hash, get and every future consumer inherit it, and tighten simulate's tag. Enforced only when the setting is non-empty, so commands that run without a workflow.yaml are unaffected; anything ever deployed already passed the same regex. Validate the workflow dirs declared by remote template manifests too. These were never checked, yet each becomes a path segment during scaffolding and the workflow name substituted into workflow.yaml — where rendering is a naive strings.NewReplacer into a double-quoted YAML scalar, so a quote or newline could inject arbitrary keys. The check runs before the project directory is created, so a bad manifest writes nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
anirudhwarrier
force-pushed
the
DEVSVCS-5077/harden-validation
branch
from
August 6, 2026 09:15
eb88f67 to
1538603
Compare
anirudhwarrier
marked this pull request as ready for review
August 6, 2026 09:36
Contributor
There was a problem hiding this comment.
Pull request overview
This pull request hardens validation for user-supplied identifiers (owner address label and workflow name) that flow into YAML templating, filesystem paths, and CLI output, reducing injection risk across multiple commands and settings-loading paths.
Changes:
- Adds an
owner_labelvalidator (regex + length) and applies it tolink-keyandworkflow deploy, including trimming flag inputs and closing the prompt-validation bypass. - Ensures workflow-name validation is consistently enforced by validating on settings load and tightening
simulate’s input tag. - Validates remote template manifest workflow directories in
cre initbefore any filesystem writes, with tests.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| internal/validation/workflow.go | Adds owner-label regex/length validation and validator hook. |
| internal/validation/workflow_test.go | Adds table-driven tests for IsValidOwnerLabel and tag behavior. |
| internal/validation/validation.go | Registers owner_label validator and adds translated error message. |
| internal/settings/workflow_settings.go | Validates workflow name during workflow.yaml settings load. |
| internal/settings/workflow_settings_test.go | Adds coverage for settings-load workflow-name validation. |
| cmd/workflow/simulate/simulate.go | Tightens simulate’s workflow-name validation tag. |
| cmd/workflow/deploy/deploy.go | Applies owner_label tag and trims --owner-label input. |
| cmd/workflow/deploy/deploy_test.go | Adds deploy validation test cases for malicious owner labels. |
| cmd/creinit/creinit.go | Validates template workflow dirs from remote manifests before scaffolding. |
| cmd/creinit/creinit_test.go | Tests that malicious template dirs are rejected and write nothing. |
| cmd/account/link_key/link_key.go | Applies owner_label validation, trims flag input, validates prompt input, and drops invalid email-derived defaults. |
| cmd/account/link_key/link_key_test.go | Adds real ValidateInputs tests for owner-label validation behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tarcisiozf
previously approved these changes
Aug 6, 2026
tarcisiozf
previously approved these changes
Aug 6, 2026
tarcisiozf
approved these changes
Aug 6, 2026
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Aug 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Hardens validation of two user-supplied identifiers — the owner address label
and the workflow name — that flow into string-templated YAML, file paths, and
copy-pasteable shell hints.
Ticket: DEVSVCS-5077
Why
--owner-labelhad no effective validation.validate:"omitempty"on aplain string is a no-op, so the flag accepted arbitrary bytes — quotes,
newlines, shell metacharacters — on both
cre account link-keyandcre workflow deploy. The value is sent to the backend, echoed to theterminal, and exported via telemetry. There was a standing
// TODO: Add validation for WorkflowOwnerLabelon the field.The interactive prompt bypassed validation entirely.
Executeruns afterValidateInputs, so a label typed at theui.Inputprompt never reached thevalidator at all — only flag-supplied values were ever checked, and those
weren't checked either per the above.
Workflow names were already constrained, but not everywhere. The regex
^[a-zA-Z0-9_-]+$is tight, but onlydeploy,pause,deleteandactivatecarried theworkflow_nametag.simulatewasrequiredonly,hashhad no validate tags,getchecked emptiness, and the value read fromworkflow.yamlwas never validated at load.Remote template manifests were trusted.
cre inituses a template'sdeclared
diras the workflow name. That value is fetched from a remote repoand was never validated, yet it becomes a path segment during scaffolding and
is substituted into
workflow.yaml.The sink that makes this exploitable is
GenerateFileFromTemplate: renderingis a naive
strings.NewReplacer, nottext/template, and the target is adouble-quoted YAML scalar (
workflow-name: "{{WorkflowName}}-staging"). Avalue containing
"or a newline escapes the scalar and injects YAML keys.Changes
owner_labelvalidator (internal/validation/workflow.go):^[a-zA-Z0-9][a-zA-Z0-9 ._-]*$, max 64. Human-readable — spaces and dotsallowed — but no quotes, backticks,
$,;,|,/, parens, controlcharacters or non-ASCII. Must start alphanumeric, so a label can never be
read as a flag or carry leading whitespace. Mirrors the existing
IsValidWorkflowName/isWorkflowNamepair.link_key.Inputsanddeploy.Inputs, withcli:"--owner-label"so errors name the flag. Flag reads are trimmed.ui.Inputcall validates inline, and theresult is re-checked after the prompt returns.
check, including
hashandget.simulate's tag tightened torequired,workflow_name.cre init, before the projectdirectory is created and before scaffolding, so a bad manifest writes
nothing.
Workflow names keep
^[a-zA-Z0-9_-]+$— the charset was already correct; onlyenforcement was missing.
Compatibility
Settings-load validation is enforced only when the value is non-empty, so
commands that run without a
workflow.yamlare unaffected. Practical risk islow: any name that has ever been deployed already passed the same regex via
deploy's validator. Existing e2e fixtures (
owner-label-1,test-owner-label) pass the new label regex unchanged.Testing
go build ./...andgo vet ./...clean.IsValidOwnerLabeland theowner_labeltagcovering quotes, command substitution, newlines, path traversal, ANSI
escapes, non-ASCII and length bounds.
ValidateInputstests forlink_key(the two existing tests weretautological — they re-implemented the guard in the assertion).
names still load.
creinittest asserting a malicious templatediris rejected and the tempdir is left completely empty. Verified non-vacuous: with the guard disabled
all 7 cases fail.
Note for reviewers
This branch was rebased onto #555, which independently added
ui.WithValidate(identical signature — my duplicate was dropped) and an email-derived default
owner label. Combining the two surfaced a bug neither has alone: an email local
part may contain characters a label may not (
first+tag@…), so pre-filling theprompt with an invalid default would block submission.
defaultOwnerLabelnowreturns
""when the derived value fails validation. Sanitizing the defaultinstead of dropping it would also be reasonable.