diff --git a/pkg/importinpututil/spec_test.go b/pkg/importinpututil/spec_test.go index bfe9fa9415d..94a6e613f33 100644 --- a/pkg/importinpututil/spec_test.go +++ b/pkg/importinpututil/spec_test.go @@ -185,7 +185,7 @@ func TestSpec_PublicAPI_FormatResolvedValue_MarshalFailure(t *testing.T) { t.Run(tt.name, func(t *testing.T) { s, ok := importinpututil.FormatResolvedValue(tt.value) assert.False(t, ok, "FormatResolvedValue should return ok=false when JSON marshalling fails: %s", tt.name) - assert.Equal(t, "", s, "FormatResolvedValue should return empty string when JSON marshalling fails: %s", tt.name) + assert.Empty(t, s, "FormatResolvedValue should return empty string when JSON marshalling fails: %s", tt.name) }) } } diff --git a/pkg/parser/schema_errors.go b/pkg/parser/schema_errors.go index 3e07304d1b0..e8921843407 100644 --- a/pkg/parser/schema_errors.go +++ b/pkg/parser/schema_errors.go @@ -306,19 +306,20 @@ func findFrontmatterBounds(lines []string) (startIdx int, endIdx int, frontmatte // of the valid values / children for that field. Used to append helpful hints when an // additionalProperties error occurs on these fields so users quickly know what is allowed. // -// The permissions scope list mirrors the properties defined in main_workflow_schema.json -// under permissions.oneOf[1].properties. Update this list when the schema changes. +// Both /permissions and /on/permissions mirror #/$defs/github_actions_permissions in +// main_workflow_schema.json. Update this list when the schema changes. var knownFieldValidValues = map[string]string{ - // This list mirrors permissions.oneOf[1].properties in main_workflow_schema.json. + // Both entries mirror $defs/github_actions_permissions in main_workflow_schema.json. // Update both when the schema changes. - "/permissions": "Valid permission scopes: actions, all, attestations, checks, copilot-requests, contents, deployments, discussions, id-token, issues, metadata, models, organization-projects, packages, pages, pull-requests, repository-projects, security-events, statuses, vulnerability-alerts", + "/permissions": "Valid permission scopes: actions, all, attestations, checks, copilot-requests, contents, deployments, discussions, id-token, issues, metadata, models, organization-projects, packages, pages, pull-requests, repository-projects, security-events, statuses, vulnerability-alerts", + "/on/permissions": "Valid permission scopes: actions, all, attestations, checks, copilot-requests, contents, deployments, discussions, id-token, issues, metadata, models, organization-projects, packages, pages, pull-requests, repository-projects, security-events, statuses, vulnerability-alerts", } // knownFieldScopes maps well-known JSON schema paths to a slice of valid scope names. // This enables spell-check ("Did you mean?") suggestions for unknown-property errors. // -// The permissions scope list mirrors permissions.oneOf[1].properties in main_workflow_schema.json. -// Update both when the schema changes. +// Both /permissions and /on/permissions mirror #/$defs/github_actions_permissions in +// main_workflow_schema.json. Update this list when the schema changes. var knownFieldScopes = map[string][]string{ "/permissions": { "actions", "all", "attestations", "checks", "copilot-requests", "contents", "deployments", @@ -326,11 +327,18 @@ var knownFieldScopes = map[string][]string{ "organization-projects", "packages", "pages", "pull-requests", "repository-projects", "security-events", "statuses", "vulnerability-alerts", }, + "/on/permissions": { + "actions", "all", "attestations", "checks", "copilot-requests", "contents", "deployments", + "discussions", "id-token", "issues", "metadata", "models", + "organization-projects", "packages", "pages", "pull-requests", + "repository-projects", "security-events", "statuses", "vulnerability-alerts", + }, } // knownFieldDocs maps well-known JSON schema paths to documentation URLs. var knownFieldDocs = map[string]string{ - "/permissions": "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token", + "/permissions": "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token", + "/on/permissions": "https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/controlling-permissions-for-github_token", } // unknownPropertyPattern extracts the property name(s) from a rewritten "Unknown property(ies):" message. diff --git a/pkg/parser/schema_test.go b/pkg/parser/schema_test.go index aa24ffd3ece..0bb4c478847 100644 --- a/pkg/parser/schema_test.go +++ b/pkg/parser/schema_test.go @@ -1975,3 +1975,46 @@ func TestValidateMainWorkflowFrontmatterWithSchemaAndLocation_AwfApiProxyTargets } }) } + +// TestValidateMainWorkflowFrontmatter_OnPermissionsVulnerabilityAlerts validates that +// vulnerability-alerts is accepted as a scope in on.permissions (regression for #40063). +func TestValidateMainWorkflowFrontmatter_OnPermissionsVulnerabilityAlerts(t *testing.T) { + frontmatter := map[string]any{ + "on": map[string]any{ + "schedule": []any{map[string]any{"cron": "0 0 * * *"}}, + "workflow_dispatch": nil, + "permissions": map[string]any{ + "vulnerability-alerts": "read", + }, + "steps": []any{ + map[string]any{ + "id": "check", + "run": "echo checking", + }, + }, + }, + "engine": "copilot", + } + err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/tmp/gh-aw/on-permissions-vulnerability-alerts-test.md") + if err != nil { + t.Errorf("vulnerability-alerts: read should be accepted in on.permissions, got error: %v", err) + } +} + +// TestValidateMainWorkflowFrontmatter_OnPermissionsUnknownScopeRejected validates that +// unknown scopes in on.permissions are still rejected. +func TestValidateMainWorkflowFrontmatter_OnPermissionsUnknownScopeRejected(t *testing.T) { + frontmatter := map[string]any{ + "on": map[string]any{ + "workflow_dispatch": nil, + "permissions": map[string]any{ + "unknown-scope": "read", + }, + }, + "engine": "copilot", + } + err := ValidateMainWorkflowFrontmatterWithSchemaAndLocation(frontmatter, "/tmp/gh-aw/on-permissions-unknown-scope-test.md") + if err == nil { + t.Error("unknown scope in on.permissions should be rejected by schema validation") + } +} diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 589e5fdd076..3d132dff1b6 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -2130,63 +2130,7 @@ }, "permissions": { "description": "Additional permissions for the pre-activation job. Use to declare extra scopes required by on.steps (e.g., issues: read for GitHub API calls in steps).", - "oneOf": [ - { - "type": "object", - "description": "Map of permission scope to level", - "properties": { - "actions": { - "type": "string", - "enum": ["read", "write", "none"] - }, - "checks": { - "type": "string", - "enum": ["read", "write", "none"] - }, - "contents": { - "type": "string", - "enum": ["read", "write", "none"] - }, - "deployments": { - "type": "string", - "enum": ["read", "write", "none"] - }, - "discussions": { - "type": "string", - "enum": ["read", "write", "none"] - }, - "issues": { - "type": "string", - "enum": ["read", "write", "none"] - }, - "packages": { - "type": "string", - "enum": ["read", "write", "none"] - }, - "pages": { - "type": "string", - "enum": ["read", "write", "none"] - }, - "pull-requests": { - "type": "string", - "enum": ["read", "write", "none"] - }, - "repository-projects": { - "type": "string", - "enum": ["read", "write", "none"] - }, - "security-events": { - "type": "string", - "enum": ["read", "write", "none"] - }, - "statuses": { - "type": "string", - "enum": ["read", "write", "none"] - } - }, - "additionalProperties": false - } - ], + "$ref": "#/$defs/github_actions_permissions", "examples": [ { "issues": "read" @@ -2194,6 +2138,9 @@ { "issues": "read", "pull-requests": "read" + }, + { + "vulnerability-alerts": "read" } ] }, diff --git a/pkg/workflow/on_steps_test.go b/pkg/workflow/on_steps_test.go index ab8eda8bb44..482eb2b9992 100644 --- a/pkg/workflow/on_steps_test.go +++ b/pkg/workflow/on_steps_test.go @@ -377,6 +377,19 @@ func TestExtractOnPermissions(t *testing.T) { expectNil: false, expectScopes: map[string]string{"issues": "read", "pull-requests": "read"}, }, + { + name: "on_permissions_vulnerability_alerts", + frontmatter: map[string]any{ + "on": map[string]any{ + "workflow_dispatch": nil, + "permissions": map[string]any{ + "vulnerability-alerts": "read", + }, + }, + }, + expectNil: false, + expectScopes: map[string]string{"vulnerability-alerts": "read"}, + }, } for _, tt := range tests {