-
Notifications
You must be signed in to change notification settings - Fork 323
Add compiler check disallowing secrets expressions in custom steps #24450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8ceb7e4
7dacab3
080a35a
3089b3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,137 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // This file contains strict mode validation for secrets in custom steps. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // It validates that secrets expressions are not used in custom steps (steps and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // post-steps injected in the agent job). In strict mode this is an error; in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // non-strict mode a warning is emitted instead. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // The goal is to minimise the number of secrets present in the agent job: the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // only secrets that should appear there are those required to configure the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // agentic engine itself. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package workflow | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "os" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/github/gh-aw/pkg/console" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // validateStepsSecrets checks both the "steps" and "post-steps" frontmatter sections | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // for secrets expressions (e.g. ${{ secrets.MY_SECRET }}). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // In strict mode the presence of any such expression is treated as an error. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // In non-strict mode a warning is emitted instead. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (c *Compiler) validateStepsSecrets(frontmatter map[string]any) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, sectionName := range []string{"steps", "post-steps"} { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err := c.validateStepsSectionSecrets(frontmatter, sectionName); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return err | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // validateStepsSectionSecrets inspects a single steps section (named by sectionName) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // inside frontmatter for any secrets.* expressions. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func (c *Compiler) validateStepsSectionSecrets(frontmatter map[string]any, sectionName string) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rawValue, exists := frontmatter[sectionName] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !exists { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| strictModeValidationLog.Printf("No %s section found, skipping secrets validation", sectionName) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| steps, ok := rawValue.([]any) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !ok { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| strictModeValidationLog.Printf("%s section is not a list, skipping secrets validation", sectionName) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var secretRefs []string | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, step := range steps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| refs := extractSecretsFromStepValue(step) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| secretRefs = append(secretRefs, refs...) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+44
to
+55
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| steps, ok := rawValue.([]any) | |
| if !ok { | |
| strictModeValidationLog.Printf("%s section is not a list, skipping secrets validation", sectionName) | |
| return nil | |
| } | |
| var secretRefs []string | |
| for _, step := range steps { | |
| refs := extractSecretsFromStepValue(step) | |
| secretRefs = append(secretRefs, refs...) | |
| } | |
| var secretRefs []string | |
| switch value := rawValue.(type) { | |
| case []any: | |
| for _, step := range value { | |
| refs := extractSecretsFromStepValue(step) | |
| secretRefs = append(secretRefs, refs...) | |
| } | |
| case string: | |
| // YAML block scalars or other scalar values may still be injected verbatim | |
| // into the generated job, so they must be scanned as well. | |
| secretRefs = append(secretRefs, extractSecretsFromStepValue(value)...) | |
| default: | |
| typeMsg := fmt.Sprintf("%s section must be a list of steps or a string, got %T", sectionName, rawValue) | |
| strictModeValidationLog.Printf(typeMsg) | |
| if c.strictMode { | |
| return fmt.Errorf("strict mode: %s", typeMsg) | |
| } | |
| warningMsg := fmt.Sprintf( | |
| "Warning: %s. Secrets validation was skipped for this malformed section.", | |
| typeMsg, | |
| ) | |
| fmt.Fprintln(os.Stderr, console.FormatWarningMessage(warningMsg)) | |
| c.IncrementWarningCount() | |
| return nil | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
validateStepsSecretsis invoked here with onlyresult.Frontmatter, but the agent job steps can also include imported steps (importsResult.MergedSteps/importsResult.CopilotSetupSteps) that are merged later inprocessAndMergeSteps. As written, secrets expressions in imported steps can bypass this new check and still be injected into the agent job. Consider extending the validation to also scan the imported steps YAML (similar tovalidateCheckoutPersistCredentials) so users can't evade the restriction by moving secret-bearing steps into an imported workflow.