main
vue 385 lines 11.7 KB
Raw
1 <template>
2 <div class="@container flex flex-col gap-6">
3 <!-- Status Legend -->
4 <n-card size="small" title="Interpreting Results" embedded>
5 <div class="grid gap-3 @md:grid-cols-2">
6 <div class="flex items-center gap-2">
7 <n-tag type="success" size="small">PASS</n-tag>
8 <span class="text-sm">Control meets baseline</span>
9 </div>
10 <div class="flex items-center gap-2">
11 <n-tag type="error" size="small">FAIL</n-tag>
12 <span class="text-sm">Remediation recommended</span>
13 </div>
14 <div class="flex items-center gap-2">
15 <n-tag type="warning" size="small">WARN</n-tag>
16 <span class="text-sm">Attention required</span>
17 </div>
18 <div class="flex items-center gap-2">
19 <n-tag type="default" size="small">SKIP</n-tag>
20 <span class="text-sm">Cannot evaluate</span>
21 </div>
22 </div>
23 <n-divider />
24 <div class="flex flex-col gap-2 text-sm">
25 <strong>Skip Reasons:</strong>
26 <div class="flex flex-col gap-1.5">
27 <div>
28 <code>not_authorized</code>
29 <span class="text-secondary">— Token/user missing permission</span>
30 </div>
31 <div>
32 <code>not_supported</code>
33 <span class="text-secondary">— Plan/feature not available</span>
34 </div>
35 <div>
36 <code>error</code>
37 <span class="text-secondary">— Transient/API error; retry or inspect details</span>
38 </div>
39 </div>
40 </div>
41 </n-card>
42
43 <!-- Controls Coverage -->
44 <n-collapse>
45 <n-collapse-item title="Controls Coverage" name="controls">
46 <template #header-extra>
47 <n-tag size="small" type="info">What We Check</n-tag>
48 </template>
49
50 <div class="flex flex-col gap-3">
51 <n-card size="small" title="Organization-Level (Governance)" embedded>
52 <div class="divide-border flex flex-col divide-y">
53 <div
54 v-for="control in orgControls"
55 :key="control.id"
56 class="flex items-start gap-2 py-2 first:pt-0 last:pb-0"
57 >
58 <Icon
59 class="mt-0.5"
60 :size="18"
61 :name="control.critical ? 'ion:alert-circle' : 'ion:checkmark-circle'"
62 :class="control.critical ? 'text-error' : 'text-success'"
63 />
64
65 <div class="flex flex-col gap-0">
66 <div class="text-sm font-medium">{{ control.name }}</div>
67 <div class="text-secondary text-xs">{{ control.description }}</div>
68 </div>
69 </div>
70 </div>
71 </n-card>
72
73 <n-card size="small" title="Repository-Level (Posture)" embedded>
74 <div class="divide-border flex flex-col divide-y">
75 <div
76 v-for="control in repoControls"
77 :key="control.id"
78 class="flex items-start gap-2 py-2 first:pt-0 last:pb-0"
79 >
80 <Icon
81 class="mt-0.5"
82 :size="18"
83 :name="control.critical ? 'ion:alert-circle' : 'ion:checkmark-circle'"
84 :class="control.critical ? 'text-error' : 'text-success'"
85 />
86
87 <div class="flex flex-col gap-0">
88 <div class="text-sm font-medium">{{ control.name }}</div>
89 <div class="text-secondary text-xs">{{ control.description }}</div>
90 </div>
91 </div>
92 </div>
93 </n-card>
94 </div>
95 </n-collapse-item>
96
97 <!-- API Permissions -->
98 <n-collapse-item title="Required API Permissions" name="permissions">
99 <template #header-extra>
100 <n-tag size="small" type="warning">Read-Only</n-tag>
101 </template>
102
103 <div class="flex flex-col gap-2">
104 <p class="*: flex items-center gap-2 text-sm">
105 <Icon name="carbon:information" />
106 This audit is intentionally
107 <strong>read-only</strong>
108 . No write or admin scopes are required.
109 </p>
110
111 <n-tabs type="segment" animated>
112 <n-tab-pane name="fine-grained" tab="Fine-Grained PAT (Recommended)">
113 <div class="flex flex-col gap-3">
114 <p class="text-warning flex items-center gap-2 text-sm">
115 <Icon name="carbon:warning" class="text-warning" />
116 Create a fine-grained PAT restricted to only the target organization and repos you
117 intend to audit.
118 </p>
119
120 <n-card size="small" title="Organization Permissions (READ)" embedded>
121 <n-list class="bg-transparent!">
122 <n-list-item v-for="perm in fineGrainedOrgPerms" :key="perm.name">
123 <template #prefix>
124 <n-tag :type="perm.required ? 'error' : 'default'" size="small">
125 {{ perm.required ? "Required" : "Optional" }}
126 </n-tag>
127 </template>
128 <div>
129 <div class="text-sm font-medium">{{ perm.name }}</div>
130 <div class="text-secondary text-xs">{{ perm.description }}</div>
131 </div>
132 </n-list-item>
133 </n-list>
134 </n-card>
135
136 <n-card size="small" title="Repository Permissions (READ)" embedded>
137 <n-list class="bg-transparent!">
138 <n-list-item v-for="perm in fineGrainedRepoPerms" :key="perm.name">
139 <template #prefix>
140 <n-tag :type="perm.required ? 'error' : 'default'" size="small">
141 {{ perm.required ? "Required" : "Optional" }}
142 </n-tag>
143 </template>
144 <div>
145 <div class="text-sm font-medium">{{ perm.name }}</div>
146 <div class="text-secondary text-xs">{{ perm.description }}</div>
147 </div>
148 </n-list-item>
149 </n-list>
150 </n-card>
151 </div>
152 </n-tab-pane>
153
154 <n-tab-pane name="classic" tab="Classic PAT (Fallback)">
155 <div class="flex flex-col gap-3">
156 <p class="text-warning flex items-center gap-2 text-sm">
157 <Icon name="carbon:warning" class="text-warning" />
158 Classic PATs have broader scope. Use fine-grained PATs when possible.
159 </p>
160
161 <n-card size="small" title="Required Scopes" embedded>
162 <n-list class="bg-transparent!">
163 <n-list-item v-for="scope in classicScopes" :key="scope.name">
164 <template #prefix>
165 <n-tag :type="scope.required ? 'error' : 'default'" size="small">
166 {{ scope.required ? "Required" : "Optional" }}
167 </n-tag>
168 </template>
169 <div>
170 <div class="text-sm font-medium">{{ scope.name }}</div>
171 <div class="text-secondary text-xs">
172 {{ scope.description }}
173 </div>
174 </div>
175 </n-list-item>
176 </n-list>
177 </n-card>
178 </div>
179 </n-tab-pane>
180 </n-tabs>
181 </div>
182 </n-collapse-item>
183
184 <!-- API Endpoints -->
185 <n-collapse-item title="API Endpoints Used" name="endpoints">
186 <template #header-extra>
187 <n-tag size="small">GET Only</n-tag>
188 </template>
189
190 <div class="flex flex-col gap-3">
191 <n-card size="small" title="Organization Endpoints" embedded>
192 <n-list class="bg-transparent!">
193 <n-list-item v-for="endpoint in orgEndpoints" :key="endpoint.path">
194 <div class="font-mono text-sm">
195 <span class="text-green-500">GET</span>
196 {{ endpoint.path }}
197 </div>
198 <div v-if="endpoint.note" class="text-secondary mt-1 text-xs">
199 {{ endpoint.note }}
200 </div>
201 </n-list-item>
202 </n-list>
203 </n-card>
204
205 <n-card size="small" title="Repository Endpoints" embedded>
206 <n-list class="bg-transparent!">
207 <n-list-item v-for="endpoint in repoEndpoints" :key="endpoint.path">
208 <div class="font-mono text-sm">
209 <span class="text-green-500">GET</span>
210 {{ endpoint.path }}
211 </div>
212 <div v-if="endpoint.note" class="text-secondary mt-1 text-xs">
213 {{ endpoint.note }}
214 </div>
215 </n-list-item>
216 </n-list>
217 </n-card>
218 </div>
219 </n-collapse-item>
220 </n-collapse>
221 </div>
222 </template>
223
224 <script setup lang="ts">
225 import { NCard, NCollapse, NCollapseItem, NDivider, NList, NListItem, NTabPane, NTabs, NTag } from "naive-ui"
226 import Icon from "@/components/common/Icon.vue"
227
228 // Controls data
229 const orgControls = [
230 {
231 id: "mfa",
232 name: "MFA Enforcement",
233 description: "Require two-factor authentication for all organization members",
234 critical: true
235 },
236 {
237 id: "org-owners",
238 name: "Org Owner Minimization",
239 description: "Limit the number of organization owners to reduce risk",
240 critical: false
241 },
242 {
243 id: "outside-collab",
244 name: "Outside Collaborator Monitoring",
245 description: "Track external collaborators with access to repositories",
246 critical: false
247 },
248 {
249 id: "saml-sso",
250 name: "SAML SSO Enforced",
251 description: "Enforce single sign-on for centralized authentication",
252 critical: true
253 },
254 {
255 id: "pat-expiry",
256 name: "PAT Expiration Enforced",
257 description: "Require personal access tokens to have expiration dates",
258 critical: false
259 },
260 {
261 id: "actions-policy",
262 name: "GitHub Actions Policy",
263 description: "Control allowed actions, default permissions, and fork PR approvals",
264 critical: false
265 },
266 {
267 id: "audit-log",
268 name: "Audit Log Monitoring",
269 description: "Monitor control-plane changes via audit log detection pack",
270 critical: false
271 }
272 ]
273
274 const repoControls = [
275 {
276 id: "visibility",
277 name: "Repository Visibility",
278 description: "Ensure appropriate public/private visibility settings",
279 critical: false
280 },
281 {
282 id: "branch-protection",
283 name: "Branch Protection",
284 description: "Enforce approvals, status checks, and prevent force pushes",
285 critical: true
286 },
287 {
288 id: "secret-scanning",
289 name: "Secret Scanning + Push Protection",
290 description: "Detect and block secrets in code before they're exposed",
291 critical: true
292 },
293 {
294 id: "dependabot",
295 name: "Dependabot Alerts",
296 description: "Monitor dependencies for known vulnerabilities",
297 critical: true
298 },
299 {
300 id: "code-scanning",
301 name: "Code Scanning",
302 description: "Identify security vulnerabilities in source code",
303 critical: false
304 },
305 {
306 id: "environments",
307 name: "Deployment Environments",
308 description: "Protect deployment environments with reviewers and rules",
309 critical: false
310 }
311 ]
312
313 // Fine-grained PAT permissions
314 const fineGrainedOrgPerms = [
315 {
316 name: "Administration",
317 description: "Required for org policy, Actions settings, SSO indicator, PAT policy",
318 required: true
319 },
320 {
321 name: "Members",
322 description: "Required for outside collaborator visibility and membership endpoints",
323 required: true
324 },
325 {
326 name: "Audit Log",
327 description: "Required for audit-log detections; otherwise those checks will SKIP",
328 required: false
329 }
330 ]
331
332 const fineGrainedRepoPerms = [
333 {
334 name: "Administration",
335 description: "Required for branch protection, environments, and repo settings",
336 required: true
337 },
338 {
339 name: "Contents",
340 description: "Often required for workflow-related metadata visibility",
341 required: true
342 },
343 {
344 name: "Actions",
345 description: "Recommended for Actions-related repo metadata",
346 required: false
347 },
348 {
349 name: "Security events",
350 description: "Required for secret scanning, Dependabot alerts, code scanning status",
351 required: true
352 }
353 ]
354
355 // Classic PAT scopes
356 const classicScopes = [
357 {
358 name: "read:org",
359 description: "Needed for org membership and org settings",
360 required: true
361 },
362 {
363 name: "repo",
364 description: "Required to read branch protection and repo config on private repos",
365 required: true
366 }
367 ]
368
369 // API endpoints
370 const orgEndpoints = [
371 { path: "/orgs/{org}", note: null },
372 { path: "/orgs/{org}/repos", note: null },
373 { path: "/orgs/{org}/actions/permissions", note: null },
374 { path: "/orgs/{org}/actions/permissions/workflow", note: null },
375 { path: "/orgs/{org}/actions/permissions/selected-actions", note: null },
376 { path: "/orgs/{org}/personal-access-tokens/policies", note: "May be plan/role gated" },
377 { path: "/orgs/{org}/audit-log", note: "Detections; plan/role gated" }
378 ]
379
380 const repoEndpoints = [
381 { path: "/repos/{org}/{repo}", note: "Includes security_and_analysis where available" },
382 { path: "/repos/{org}/{repo}/branches/{branch}/protection", note: null },
383 { path: "/repos/{org}/{repo}/environments", note: null }
384 ]
385 </script>