-
Notifications
You must be signed in to change notification settings - Fork 50
Description
Summary
Five Token-Permissions violations were discovered across three workflow files (#456, and two companion issues for extension-publish-prerelease.yml and extension-publish.yml). These went undetected because no CI validation enforces the repository convention that every workflow must have a top-level permissions: block and every job must have a job-level permissions: block.
This issue adds automated enforcement to prevent regression after the fixes are applied.
Problem Analysis
Root Cause
The workflow instructions (.github/instructions/hve-core/workflows.instructions.md) describe the desired permissions pattern, but:
- Ambiguous language — "Additional permissions MUST be granted at the job level" reads as "only when extra permissions are needed," not "every job must declare permissions."
- No enforcement script — The Enforcement Statement section lists
Test-DependencyPinning.ps1,Test-SHAStaleness.ps1, andInvoke-YamlLint.ps1, but none validate permissions declarations. - actionlint gap — The existing YAML linter (
Invoke-YamlLint.ps1) validates syntax and some best practices but does not enforce permissions completeness.
Current Enforcement Coverage
| Script | What It Checks | Permissions? |
|---|---|---|
Test-DependencyPinning.ps1 |
SHA pinning of actions | No |
Test-SHAStaleness.ps1 |
Stale SHA detection | No |
Test-ActionVersionConsistency.ps1 |
Version comment consistency | No |
Invoke-YamlLint.ps1 |
YAML syntax via actionlint | No |
Deliverable 1: Test-WorkflowPermissions.ps1
Create scripts/security/Test-WorkflowPermissions.ps1 modeled after Test-ActionVersionConsistency.ps1.
Requirements
- Import
SecurityClasses.psm1(viausing module) andCIHelpers.psm1 - Extend
DependencyViolation.ViolationTypeValidateSet inSecurityClasses.psm1to include'MissingPermissions' - Scan all
.github/workflows/*.ymlfiles - Two checks per file:
- Top-level permissions block — Verify a
permissions:key exists at root level (not indented underjobs:) - Job-level permissions block — For every
jobs.<id>:entry, verify apermissions:key exists as a direct child
- Top-level permissions block — Verify a
- Support
-FailOnMissingswitch for CI enforcement - Support JSON and markdown output formats (matching existing patterns)
- Use
Export-CICDArtifactfromCIHelpers.psm1for artifact upload
Detection Approach
Parse YAML line-by-line (consistent with existing scripts in scripts/security/):
- Top-level permissions: A line matching
^permissions:(no leading whitespace) - Job block start:
^jobs:followed by^ \w+:(2-space indent) - Job-level permissions: Within a job block,
^ permissions:(4-space indent)
Output Format
JSON array of DependencyViolation objects:
[
{
"File": ".github/workflows/example.yml",
"Line": 1,
"ViolationType": "MissingPermissions",
"Severity": "Error",
"Message": "No top-level permissions block defined",
"Metadata": { "scope": "workflow" }
}
]Deliverable 2: workflow-permissions-scan.yml
Create .github/workflows/workflow-permissions-scan.yml as a reusable workflow following the dependency-pinning-scan.yml pattern:
- Trigger:
workflow_call - Permissions:
contents: read - Jobs: checkout → run
Test-WorkflowPermissions.ps1→ upload results artifact
Deliverable 3: AI Instructions Updates
workflows.instructions.md — Permissions Section
Current:
Workflows MUST declare explicit permissions following the principle of least privilege. The default permission set is
contents: read. Additional permissions MUST be granted at the job level and only when required for a specific capability.
Proposed:
Workflows MUST declare explicit permissions following the principle of least privilege. Every workflow MUST have a top-level
permissions:block that sets a restrictive default. Every job MUST have a job-levelpermissions:block — even when the job only needscontents: reador no permissions at all (permissions: {}). The default permission set iscontents: read. Permissions beyondcontents: readMUST be granted at the job level only when required for a specific capability.
workflows.instructions.md — Enforcement Statement
Add to the existing enforcement tool list:
- `scripts/security/Test-WorkflowPermissions.ps1` — Validates every workflow has a top-level permissions block and every job has a job-level permissions blockDeliverable 4: npm Script and CI Pipeline Integration
package.json
Add:
"lint:permissions": "pwsh -NoProfile -NonInteractive -Command \"& { . ./scripts/security/Test-WorkflowPermissions.ps1 -FailOnMissing }\""Add lint:permissions to the lint:all chain.
pr-validation.yml
Add a new job calling workflow-permissions-scan.yml, following the existing reusable workflow invocation pattern used by dependency-pinning-scan.yml.
Implementation Order
1. SecurityClasses.psm1 — Extend ViolationType ValidateSet
2. Test-WorkflowPermissions.ps1 — New validation script
3. workflow-permissions-scan.yml — New reusable workflow
4. pr-validation.yml — Add permissions validation job
5. package.json — Add lint:permissions, update lint:all
6. workflows.instructions.md — Strengthen permissions rules, add enforcement entry
Verification
- Run
npm run lint:permissionslocally — should pass with zero violations after the fix issues are resolved. - Introduce a test violation (remove a job-level permissions block) and confirm the script detects it.
- PR validation pipeline includes the new check and blocks on violations.
References
.github/instructions/hve-core/workflows.instructions.md— Current workflow conventionsscripts/security/Test-ActionVersionConsistency.ps1— Template script patternscripts/security/Modules/SecurityClasses.psm1— Shared violation classscripts/lib/Modules/CIHelpers.psm1— CI artifact export helpers.github/workflows/dependency-pinning-scan.yml— Template reusable workflow pattern.github/workflows/pr-validation.yml— CI orchestration target
How to Build This
This is a multi-artifact implementation task using the task-implementor workflow.
Workflow: /task-research → /task-plan → /task-implement → /task-review
Tip
Between each phase, type /clear or start a new chat to reset context.
Phase 1: Research
Source Material
- This issue body
#file:scripts/security/Test-DependencyPinning.ps1(pattern reference for PowerShell validation scripts)#file:scripts/security/Test-ActionVersionPinning.ps1(pattern reference)#file:.github/workflows/main.yml(target workflow with permissions blocks)#file:.github/workflows/pr-validation.yml(target workflow)#file:package.json(existing npm scripts)#file:PSScriptAnalyzer.psd1(PowerShell linting rules)
Steps
- Type
/clearto start a fresh context. - Attach or open the files listed above.
- Copy and run this prompt:
/task-research topic="workflow permissions validation script design"
Research how to create a PowerShell script (Test-WorkflowPermissions.ps1) that validates
GitHub Actions workflow permission blocks. Investigate:
1. How existing Test-DependencyPinning.ps1 and Test-ActionVersionPinning.ps1 scripts are structured
(parameter blocks, Pester integration, SARIF output, npm script wiring)
2. The permissions model in GitHub Actions YAML (top-level vs job-level, valid permission names,
read/write/none values)
3. Which of the 25 workflow files in .github/workflows/ have permissions blocks and what patterns
they follow
4. How to detect missing permissions blocks, overly broad permissions, and write permissions
that should be read-only
5. SARIF output format for reporting findings to GitHub Security tab
6. How to wire the new script as an npm command in package.json following existing patterns
Output: Research document at .copilot-tracking/research/{{YYYY-MM-DD}}-workflow-permissions-research.md
Phase 2: Plan
Source Material
- Research document from Phase 1
Steps
- Type
/clearto start a fresh context. - Open the research document from Phase 1.
- Copy and run this prompt:
/task-plan
Create an implementation plan for the workflow permissions validation feature
covering Test-WorkflowPermissions.ps1, npm script wiring, and CI integration.
The plan should address the PowerShell script structure, SARIF output, Pester tests,
and the npm run command registration following patterns from existing security scripts.
Output: Plan at .copilot-tracking/plans/ and details at .copilot-tracking/details/
Phase 3: Implement
Source Material
- Plan from Phase 2
Steps
- Type
/clearto start a fresh context. - Open the plan document from Phase 2.
- Copy and run this prompt:
/task-implement
Implement the workflow permissions validation plan. Create Test-WorkflowPermissions.ps1
following the patterns established by Test-DependencyPinning.ps1, add the npm script
to package.json, and integrate with CI workflows.
Output: New and modified files, changes log at .copilot-tracking/changes/
Phase 4: Review
Source Material
- Plan from Phase 2
- Changes log from Phase 3
Steps
- Type
/clearto start a fresh context. - Open the plan and changes log.
- Copy and run this prompt:
/task-review
Review the workflow permissions validation implementation. Run these validation commands:
- npm run lint:ps (PowerShell linting)
- npm run test:ps (Pester tests)
- npm run lint:yaml (YAML validation for any modified workflows)
Verify the script follows PSScriptAnalyzer rules and matches existing security script patterns.
Output: Review log at .copilot-tracking/reviews/
After Review
- Pass: All criteria met. Create a PR referencing this issue.
- Iterate: Review found issues. Run
/clear, return to Phase 3 with the review feedback. - Escalate: Fundamental design issue discovered. Run
/clear, return to Phase 1 to research the gap.
Authoring Standards
- PowerShell scripts follow PSScriptAnalyzer rules from
PSScriptAnalyzer.psd1 - Include comment-based help blocks
- Use
CIHelpersmodule patterns from existing scripts - SARIF output matches the schema used by Test-DependencyPinning.ps1
- npm scripts follow the
lint:prefix naming convention
Success Criteria
-
Test-WorkflowPermissions.ps1validates permissions blocks across all workflow files - SARIF output uploads to GitHub Security tab
- npm script
lint:permissionsregistered inpackage.json - Pester tests cover key validation scenarios
-
npm run lint:pspasses -
npm run test:pspasses