Skip to repository content75 lines · 2.7 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T04:47:47.864Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
check_permissions.rs
1use annotate_snippets::{AnnotationKind, Group, Level, Snippet};
2use serde_yaml::Value;
3use std::{ops::Range, path::Path};
4
5pub enum PermissionsError {
6 /// No top-level `permissions:` key.
7 Missing,
8 /// Top-level default grants more than `contents: read`.
9 ExcessiveDefault,
10}
11
12/// Validates a workflow's top-level (default) `permissions:`.
13///
14/// Every workflow must declare one, and it may grant at most `contents: read`;
15/// jobs that need more must request it at the job level.
16pub fn validate_permissions(workflow: &Value) -> Result<(), PermissionsError> {
17 let Some(permissions) = workflow.get("permissions") else {
18 return Err(PermissionsError::Missing);
19 };
20
21 let is_minimal = match permissions {
22 Value::Mapping(mapping) => mapping.iter().all(|(scope, level)| {
23 let level = level.as_str();
24 level == Some("none") || (scope.as_str() == Some("contents") && level == Some("read"))
25 }),
26 // String forms such as `read-all`/`write-all` always exceed the allowance.
27 _ => false,
28 };
29
30 if is_minimal {
31 Ok(())
32 } else {
33 Err(PermissionsError::ExcessiveDefault)
34 }
35}
36
37impl PermissionsError {
38 pub fn annotation_group<'a>(&self, file_path: &Path, raw_content: &'a str) -> Group<'a> {
39 let (title, span, label) = match self {
40 PermissionsError::Missing => (
41 "Workflow is missing a top-level `permissions:` key",
42 first_line_span(raw_content, "name:"),
43 "Add a top-level `permissions:` key so this workflow defaults to the least privilege it needs",
44 ),
45 PermissionsError::ExcessiveDefault => (
46 "Top-level workflow permissions must grant at most `contents: read`",
47 first_line_span(raw_content, "permissions:"),
48 "Lower the default to `contents: read` (or `{}`) and move elevated permissions to the jobs that need them",
49 ),
50 };
51
52 Level::ERROR.primary_title(title).element(
53 Snippet::source(raw_content)
54 .path(file_path.display().to_string())
55 .annotations(std::iter::once(
56 AnnotationKind::Primary.span(span).label(label),
57 )),
58 )
59 }
60}
61
62/// Span of the first column-0 line starting with `prefix`, else empty.
63fn first_line_span(raw_content: &str, prefix: &str) -> Range<usize> {
64 let mut offset = 0;
65 for line in raw_content.lines() {
66 let start = offset;
67 offset += line.len() + 1;
68 if line.starts_with(prefix) {
69 return start..start + line.len();
70 }
71 }
72
73 Default::default()
74}
75