From 8f14f2793719ab23a2dcb8f8160fd44796d68e60 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Wed, 15 Apr 2026 02:42:15 +0000 Subject: [PATCH] Add vulnerability-alerts permission Add vulnerability-alerts as a new read-only permission key for GITHUB_TOKEN. This permission allows workflows to read Dependabot alerts. - New VulnerabilityAlerts property in Permissions class - Feature-gated via AllowVulnerabilityAlertsPermission - Capped at read (write requests normalized to read) - Schema updated (permission-level-read-or-no-access) - Updated security-events description --- .../Conversion/PermissionsHelper.cs | 9 ++++---- .../Conversion/WorkflowTemplateConverter.cs | 19 +++++++++++++++- src/Sdk/WorkflowParser/Permissions.cs | 22 +++++++++++++++++++ src/Sdk/WorkflowParser/WorkflowFeatures.cs | 8 +++++++ src/Sdk/WorkflowParser/workflow-v1.0.json | 10 ++++++--- 5 files changed, 60 insertions(+), 8 deletions(-) diff --git a/src/Sdk/WorkflowParser/Conversion/PermissionsHelper.cs b/src/Sdk/WorkflowParser/Conversion/PermissionsHelper.cs index b5340895087..956d4518b46 100644 --- a/src/Sdk/WorkflowParser/Conversion/PermissionsHelper.cs +++ b/src/Sdk/WorkflowParser/Conversion/PermissionsHelper.cs @@ -32,7 +32,7 @@ internal static void ValidateEmbeddedPermissions( return; } - var effectiveMax = explicitMax ?? CreatePermissionsFromPolicy(context, permissionsPolicy, includeIdToken: isTrusted, includeModels: context.GetFeatures().AllowModelsPermission); + var effectiveMax = explicitMax ?? CreatePermissionsFromPolicy(context, permissionsPolicy, includeIdToken: isTrusted, includeModels: context.GetFeatures().AllowModelsPermission, includeVulnerabilityAlerts: context.GetFeatures().AllowVulnerabilityAlertsPermission); if (requested.ViolatesMaxPermissions(effectiveMax, out var permissionLevelViolations)) { @@ -59,18 +59,19 @@ private static Permissions CreatePermissionsFromPolicy( TemplateContext context, string permissionsPolicy, bool includeIdToken, - bool includeModels) + bool includeModels, + bool includeVulnerabilityAlerts) { switch (permissionsPolicy) { case WorkflowConstants.PermissionsPolicy.LimitedRead: - return new Permissions(PermissionLevel.NoAccess, includeIdToken: false, includeAttestations: false, includeModels: false) + return new Permissions(PermissionLevel.NoAccess, includeIdToken: false, includeAttestations: false, includeModels: false, includeVulnerabilityAlerts: false) { Contents = PermissionLevel.Read, Packages = PermissionLevel.Read, }; case WorkflowConstants.PermissionsPolicy.Write: - return new Permissions(PermissionLevel.Write, includeIdToken: includeIdToken, includeAttestations: true, includeModels: includeModels); + return new Permissions(PermissionLevel.Write, includeIdToken: includeIdToken, includeAttestations: true, includeModels: includeModels, includeVulnerabilityAlerts: includeVulnerabilityAlerts); default: throw new ArgumentException($"Unexpected permission policy: '{permissionsPolicy}'"); } diff --git a/src/Sdk/WorkflowParser/Conversion/WorkflowTemplateConverter.cs b/src/Sdk/WorkflowParser/Conversion/WorkflowTemplateConverter.cs index 0c53f87fcbd..9a7a4d0fce7 100644 --- a/src/Sdk/WorkflowParser/Conversion/WorkflowTemplateConverter.cs +++ b/src/Sdk/WorkflowParser/Conversion/WorkflowTemplateConverter.cs @@ -1877,7 +1877,7 @@ private static Permissions ConvertToPermissions(TemplateContext context, Templat permissionsStr.AssertUnexpectedValue(permissionsStr.Value); break; } - return new Permissions(permissionLevel, includeIdToken: true, includeAttestations: true, includeModels: context.GetFeatures().AllowModelsPermission); + return new Permissions(permissionLevel, includeIdToken: true, includeAttestations: true, includeModels: context.GetFeatures().AllowModelsPermission, includeVulnerabilityAlerts: context.GetFeatures().AllowVulnerabilityAlertsPermission); } var mapping = token.AssertMapping("permissions"); @@ -1957,6 +1957,23 @@ private static Permissions ConvertToPermissions(TemplateContext context, Templat context.Error(key, $"The permission 'models' is not allowed"); } break; + case "vulnerability-alerts": + if (context.GetFeatures().AllowVulnerabilityAlertsPermission) + { + if (permissionLevel == PermissionLevel.Write) + { + permissions.VulnerabilityAlerts = PermissionLevel.Read; + } + else + { + permissions.VulnerabilityAlerts = permissionLevel; + } + } + else + { + context.Error(key, $"The permission 'vulnerability-alerts' is not allowed"); + } + break; default: break; } diff --git a/src/Sdk/WorkflowParser/Permissions.cs b/src/Sdk/WorkflowParser/Permissions.cs index 0a211e5b015..5f478d19904 100644 --- a/src/Sdk/WorkflowParser/Permissions.cs +++ b/src/Sdk/WorkflowParser/Permissions.cs @@ -32,6 +32,7 @@ public Permissions(Permissions copy) SecurityEvents = copy.SecurityEvents; IdToken = copy.IdToken; Models = copy.Models; + VulnerabilityAlerts = copy.VulnerabilityAlerts; } public Permissions( @@ -61,6 +62,19 @@ public Permissions( : PermissionLevel.NoAccess; } + public Permissions( + PermissionLevel permissionLevel, + bool includeIdToken, + bool includeAttestations, + bool includeModels, + bool includeVulnerabilityAlerts) + : this(permissionLevel, includeIdToken, includeAttestations, includeModels) + { + VulnerabilityAlerts = includeVulnerabilityAlerts + ? (permissionLevel == PermissionLevel.Write ? PermissionLevel.Read : permissionLevel) + : PermissionLevel.NoAccess; + } + private static KeyValuePair[] ComparisonKeyMapping(Permissions left, Permissions right) { return new[] @@ -81,6 +95,7 @@ public Permissions( new KeyValuePair("security-events", (left.SecurityEvents, right.SecurityEvents)), new KeyValuePair("id-token", (left.IdToken, right.IdToken)), new KeyValuePair("models", (left.Models, right.Models)), + new KeyValuePair("vulnerability-alerts", (left.VulnerabilityAlerts, right.VulnerabilityAlerts)), }; } @@ -154,6 +169,13 @@ public PermissionLevel Models set; } + [DataMember(Name = "vulnerability-alerts", EmitDefaultValue = false)] + public PermissionLevel VulnerabilityAlerts + { + get; + set; + } + [DataMember(Name = "packages", EmitDefaultValue = false)] public PermissionLevel Packages { diff --git a/src/Sdk/WorkflowParser/WorkflowFeatures.cs b/src/Sdk/WorkflowParser/WorkflowFeatures.cs index c3fa33af74b..c9d7df22ef1 100644 --- a/src/Sdk/WorkflowParser/WorkflowFeatures.cs +++ b/src/Sdk/WorkflowParser/WorkflowFeatures.cs @@ -41,6 +41,13 @@ public class WorkflowFeatures [DataMember(EmitDefaultValue = false)] public bool AllowModelsPermission { get; set; } + /// + /// Gets or sets a value indicating whether users may use the "vulnerability-alerts" permission. + /// Used during parsing only. + /// + [DataMember(EmitDefaultValue = false)] + public bool AllowVulnerabilityAlertsPermission { get; set; } + /// /// Gets or sets a value indicating whether the expression function fromJson performs strict JSON parsing. /// Used during evaluation only. @@ -67,6 +74,7 @@ public static WorkflowFeatures GetDefaults() Snapshot = false, // Default to false since this feature is still in an experimental phase StrictJsonParsing = false, // Default to false since this is temporary for telemetry purposes only AllowModelsPermission = false, // Default to false since we want this to be disabled for all non-production environments + AllowVulnerabilityAlertsPermission = false, // Default to false since we want this to be disabled for all non-production environments AllowServiceContainerCommand = false, // Default to false since this feature is gated by actions_service_container_command }; } diff --git a/src/Sdk/WorkflowParser/workflow-v1.0.json b/src/Sdk/WorkflowParser/workflow-v1.0.json index 66bda31fa55..ff2d3185d28 100644 --- a/src/Sdk/WorkflowParser/workflow-v1.0.json +++ b/src/Sdk/WorkflowParser/workflow-v1.0.json @@ -496,8 +496,8 @@ "check-suite-activity": { "description": "The types of check suite activity that trigger the workflow. Supported activity types: `completed`.", "one-of": [ - "check-suite-activity-type", - "check-suite-activity-types" + "check-suite-activity-type", + "check-suite-activity-types" ] }, "check-suite-activity-types": { @@ -1865,11 +1865,15 @@ }, "security-events": { "type": "permission-level-any", - "description": "Code scanning and Dependabot alerts." + "description": "Code scanning alerts." }, "statuses": { "type": "permission-level-any", "description": "Commit statuses." + }, + "vulnerability-alerts": { + "type": "permission-level-read-or-no-access", + "description": "Dependabot alerts." } } }