Add workflows permission scope to WorkflowParser

Add 'workflows' as a recognized permission scope for GITHUB_TOKEN,
gated behind AllowWorkflowsPermission feature flag.

Changes:
- Permissions.cs: Add Workflows property, copy constructor, comparison
  key mapping. Excluded from write-all/read-all bulk constructors.
- WorkflowTemplateConverter.cs: Parse 'workflows' permission with
  feature flag guard. Read downgrades to NoAccess (write-only scope).
- WorkflowFeatures.cs: Add AllowWorkflowsPermission flag, default false.
This commit is contained in:
Salman Muin Kayser Chishti
2026-04-10 23:33:55 +01:00
parent 4a587ada27
commit 273538003e
3 changed files with 43 additions and 6 deletions
@@ -1957,6 +1957,24 @@ namespace GitHub.Actions.WorkflowParser.Conversion
context.Error(key, $"The permission 'models' is not allowed"); context.Error(key, $"The permission 'models' is not allowed");
} }
break; break;
case "workflows":
if (context.GetFeatures().AllowWorkflowsPermission)
{
// Workflows only supports write; downgrade read to none
if (permissionLevel == PermissionLevel.Read)
{
permissions.Workflows = PermissionLevel.NoAccess;
}
else
{
permissions.Workflows = permissionLevel;
}
}
else
{
context.Error(key, $"The permission 'workflows' is not allowed");
}
break;
default: default:
break; break;
} }
+12 -1
View File
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization; using System.Runtime.Serialization;
using GitHub.Actions.WorkflowParser.Conversion; using GitHub.Actions.WorkflowParser.Conversion;
@@ -32,6 +32,7 @@ namespace GitHub.Actions.WorkflowParser
SecurityEvents = copy.SecurityEvents; SecurityEvents = copy.SecurityEvents;
IdToken = copy.IdToken; IdToken = copy.IdToken;
Models = copy.Models; Models = copy.Models;
Workflows = copy.Workflows;
} }
public Permissions( public Permissions(
@@ -59,6 +60,8 @@ namespace GitHub.Actions.WorkflowParser
Models = includeModels Models = includeModels
? (permissionLevel == PermissionLevel.Write ? PermissionLevel.Read : permissionLevel) ? (permissionLevel == PermissionLevel.Write ? PermissionLevel.Read : permissionLevel)
: PermissionLevel.NoAccess; : PermissionLevel.NoAccess;
// Workflows is excluded from write-all / read-all; must be explicitly requested
Workflows = PermissionLevel.NoAccess;
} }
private static KeyValuePair<string, (PermissionLevel, PermissionLevel)>[] ComparisonKeyMapping(Permissions left, Permissions right) private static KeyValuePair<string, (PermissionLevel, PermissionLevel)>[] ComparisonKeyMapping(Permissions left, Permissions right)
@@ -81,6 +84,7 @@ namespace GitHub.Actions.WorkflowParser
new KeyValuePair<string, (PermissionLevel, PermissionLevel)>("security-events", (left.SecurityEvents, right.SecurityEvents)), new KeyValuePair<string, (PermissionLevel, PermissionLevel)>("security-events", (left.SecurityEvents, right.SecurityEvents)),
new KeyValuePair<string, (PermissionLevel, PermissionLevel)>("id-token", (left.IdToken, right.IdToken)), new KeyValuePair<string, (PermissionLevel, PermissionLevel)>("id-token", (left.IdToken, right.IdToken)),
new KeyValuePair<string, (PermissionLevel, PermissionLevel)>("models", (left.Models, right.Models)), new KeyValuePair<string, (PermissionLevel, PermissionLevel)>("models", (left.Models, right.Models)),
new KeyValuePair<string, (PermissionLevel, PermissionLevel)>("workflows", (left.Workflows, right.Workflows)),
}; };
} }
@@ -196,6 +200,13 @@ namespace GitHub.Actions.WorkflowParser
set; set;
} }
[DataMember(Name = "workflows", EmitDefaultValue = false)]
public PermissionLevel Workflows
{
get;
set;
}
public Permissions Clone() public Permissions Clone()
{ {
return new Permissions(this); return new Permissions(this);
+9 -1
View File
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@@ -41,6 +41,13 @@ namespace GitHub.Actions.WorkflowParser
[DataMember(EmitDefaultValue = false)] [DataMember(EmitDefaultValue = false)]
public bool AllowModelsPermission { get; set; } public bool AllowModelsPermission { get; set; }
/// <summary>
/// Gets or sets a value indicating whether users may use the "workflows" permission.
/// Used during parsing only.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public bool AllowWorkflowsPermission { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the expression function fromJson performs strict JSON parsing. /// Gets or sets a value indicating whether the expression function fromJson performs strict JSON parsing.
/// Used during evaluation only. /// Used during evaluation only.
@@ -67,6 +74,7 @@ namespace GitHub.Actions.WorkflowParser
Snapshot = false, // Default to false since this feature is still in an experimental phase 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 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 AllowModelsPermission = false, // Default to false since we want this to be disabled for all non-production environments
AllowWorkflowsPermission = false, // Default to false; gated by feature flag for controlled rollout
AllowServiceContainerCommand = false, // Default to false since this feature is gated by actions_service_container_command AllowServiceContainerCommand = false, // Default to false since this feature is gated by actions_service_container_command
}; };
} }