microsoft/openvmm

Public

mirrored fromhttps://github.com/microsoft/openvmmAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
ade3e88da56fafed47e001656d9017fb15f23194

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

.github/workflows/unsafe-label.yml

142lines · modecode

1name: "Unsafe Check"
2
3# SECURITY NOTE: This workflow uses pull_request_target to get write permissions
4# for managing labels and comments. To avoid executing untrusted code, we:
5# 1. Do NOT checkout the PR code at all
6# 2. Fetch file contents exclusively via GitHub API
7# 3. Never execute, eval, or interpret the fetched content
8# This eliminates filesystem-based attacks (symlinks, large files, race conditions)
9
10on:
11 pull_request_target
12
13permissions:
14 contents: read
15 pull-requests: write
16
17jobs:
18 check-unsafe:
19 runs-on: ubuntu-latest
20 steps:
21 - name: Check for unsafe code and manage labels
22 uses: actions/github-script@v7
23 with:
24 script: |
25 const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB limit per file
26
27 // Get the list of changed files with pagination
28 let allFiles = [];
29 let page = 1;
30 while (page <= 30) {
31 const { data: files } = await github.rest.pulls.listFiles({
32 owner: context.repo.owner,
33 repo: context.repo.repo,
34 pull_number: context.payload.pull_request.number,
35 per_page: 100,
36 page: page,
37 });
38
39 if (files.length === 0) break;
40 allFiles = allFiles.concat(files);
41 if (files.length < 100) break;
42 page++;
43 }
44
45 // Filter to just Rust files that weren't deleted
46 const rustFiles = allFiles.filter(file =>
47 file.filename.endsWith('.rs') && file.status !== 'removed'
48 );
49
50 console.log(`Checking ${rustFiles.length} Rust files for unsafe code...`);
51
52 let unsafeFound = false;
53 const prHead = context.payload.pull_request.head;
54
55 // Check each Rust file for unsafety by fetching content via API
56 for (const file of rustFiles) {
57 // Skip files that are too large (size is in bytes)
58 if (file.size > MAX_FILE_SIZE) {
59 throw new Error(`${file.filename}: file too large (${file.size} bytes)`);
60 }
61
62 // Fetch file content from the PR head via API (not filesystem)
63 // This avoids all filesystem-based attacks
64 const { data: fileContent } = await github.rest.repos.getContent({
65 owner: prHead.repo.owner.login,
66 repo: prHead.repo.name,
67 path: file.filename,
68 ref: prHead.sha,
69 });
70
71 if (fileContent.type !== 'file') {
72 throw new Error(`${file.filename}: not a regular file (type: ${fileContent.type})`);
73 }
74
75 // Decode base64 content
76 const content = Buffer.from(fileContent.content, 'base64').toString('utf8');
77
78 // Look for "unsafe ", the space ensures we don't catch words like the "unsafe_code" lint
79 // Also look for "unsafe(" to catch unsafe attributes
80 // The separate rustfmt check will ensure all code matches these formatting standards
81 const unsafeRegex = /unsafe[( ]/;
82 if (unsafeRegex.test(content)) {
83 console.log(`Found unsafe code in: ${file.filename}`);
84 unsafeFound = true;
85 }
86 }
87
88 // Manage the label (use pull_request number from payload for pull_request_target)
89 const prNumber = context.payload.pull_request.number;
90
91 if (unsafeFound) {
92 console.log('Adding unsafe label...');
93 await github.rest.issues.addLabels({
94 issue_number: prNumber,
95 owner: context.repo.owner,
96 repo: context.repo.repo,
97 labels: ['unsafe']
98 });
99
100 // Post a warning comment
101 const comment = `⚠️ **Unsafe Code Detected**
102
103 This PR modifies files containing \`unsafe\` Rust code. Extra scrutiny is required during review.
104
105 For more on why we check whole files, instead of just diffs, check out [the Rustonomicon](https://doc.rust-lang.org/nomicon/working-with-unsafe.html)`;
106
107 // Check if we already posted this comment
108 const { data: comments } = await github.rest.issues.listComments({
109 issue_number: prNumber,
110 owner: context.repo.owner,
111 repo: context.repo.repo,
112 });
113
114 const botComment = comments.find(c =>
115 c.user.type === 'Bot' && c.body.includes('Unsafe Code Detected')
116 );
117
118 if (!botComment) {
119 console.log('Posting warning comment...');
120 await github.rest.issues.createComment({
121 issue_number: prNumber,
122 owner: context.repo.owner,
123 repo: context.repo.repo,
124 body: comment
125 });
126 } else {
127 console.log('Warning comment already exists');
128 }
129 } else {
130 console.log('No unsafe code found, removing label if present...');
131 try {
132 await github.rest.issues.removeLabel({
133 issue_number: prNumber,
134 owner: context.repo.owner,
135 repo: context.repo.repo,
136 name: 'unsafe'
137 });
138 } catch (error) {
139 // Label might not exist, that's okay
140 console.log('Label does not exist or could not be removed:', error.message);
141 }
142 }
143