1#[derive(Debug, Clone)]
2pub struct CiInstallOptions<'a> {
3 pub fail_on: &'a str,
4 pub ignore_tests: bool,
5 pub scan_path: &'a str,
6 pub baseline_path: Option<&'a str>,
7 pub upload_sarif: bool,
8}
9
10pub fn github_actions_workflow(options: &CiInstallOptions<'_>) -> String {
11 let baseline_input = options
12 .baseline_path
13 .map(|path| format!(" baseline: \"{path}\"\n"))
14 .unwrap_or_default();
15
16 format!(
17 r#"name: AgentShield
18
19on:
20 pull_request:
21 push:
22 branches: [main]
23
24permissions:
25 actions: read
26 contents: read
27 security-events: write
28
29jobs:
30 agentshield:
31 runs-on: ubuntu-latest
32 steps:
33 - uses: actions/checkout@v4
34 - uses: aiconnai/agentshield@main
35 with:
36 path: "{scan_path}"
37 fail-on: "{fail_on}"
38 ignore-tests: {ignore_tests}
39{baseline_input} upload-sarif: {upload_sarif}
40 strict: true
41"#,
42 scan_path = options.scan_path,
43 fail_on = options.fail_on,
44 ignore_tests = options.ignore_tests,
45 baseline_input = baseline_input,
46 upload_sarif = options.upload_sarif,
47 )
48}
49
50pub fn github_actions_security_suite_workflow(options: &CiInstallOptions<'_>) -> String {
51 let baseline_input = options
52 .baseline_path
53 .map(|path| format!(" baseline: \"{path}\"\n"))
54 .unwrap_or_default();
55
56 format!(
57 r#"name: Security Suite
58
59on:
60 pull_request:
61 push:
62 branches: [main]
63 workflow_dispatch:
64
65permissions:
66 actions: read
67 contents: read
68 security-events: write
69
70jobs:
71 codeql:
72 name: CodeQL
73 runs-on: ubuntu-latest
74 steps:
75 - uses: actions/checkout@v6
76 - uses: github/codeql-action/init@v4
77 with:
78 queries: security-extended
79 - uses: github/codeql-action/analyze@v4
80
81 gitleaks:
82 name: Gitleaks
83 runs-on: ubuntu-latest
84 steps:
85 - uses: actions/checkout@v6
86 with:
87 fetch-depth: 0
88 - uses: gitleaks/gitleaks-action@v3
89 env:
90 GITHUB_TOKEN: ${{{{ secrets.GITHUB_TOKEN }}}}
91 # Required by Gitleaks for organization-owned repositories.
92 GITLEAKS_LICENSE: ${{{{ secrets.GITLEAKS_LICENSE }}}}
93
94 semgrep:
95 name: Semgrep CE
96 runs-on: ubuntu-latest
97 container:
98 image: semgrep/semgrep
99 if: github.actor != 'dependabot[bot]'
100 steps:
101 - uses: actions/checkout@v6
102 - run: semgrep scan --config auto
103
104 agentshield:
105 name: AgentShield
106 runs-on: ubuntu-latest
107 steps:
108 - uses: actions/checkout@v6
109 - uses: aiconnai/agentshield@main
110 with:
111 path: "{scan_path}"
112 fail-on: "{fail_on}"
113 ignore-tests: {ignore_tests}
114{baseline_input} upload-sarif: {upload_sarif}
115 strict: true
116"#,
117 scan_path = options.scan_path,
118 fail_on = options.fail_on,
119 ignore_tests = options.ignore_tests,
120 baseline_input = baseline_input,
121 upload_sarif = options.upload_sarif,
122 )
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn ci_workflow_uses_expected_action_inputs() {
131 let workflow = github_actions_workflow(&CiInstallOptions {
132 fail_on: "high",
133 ignore_tests: true,
134 scan_path: ".",
135 baseline_path: None,
136 upload_sarif: true,
137 });
138
139 assert!(workflow.contains("uses: aiconnai/agentshield@main"));
140 assert!(workflow.contains("fail-on: \"high\""));
141 assert!(workflow.contains("ignore-tests: true"));
142 assert!(workflow.contains("upload-sarif: true"));
143 assert!(workflow.contains("strict: true"));
144 assert!(workflow.contains("actions: read"));
145 assert!(!workflow.contains("baseline:"));
146 }
147
148 #[test]
149 fn ci_workflow_can_use_baseline_file() {
150 let workflow = github_actions_workflow(&CiInstallOptions {
151 fail_on: "high",
152 ignore_tests: true,
153 scan_path: ".",
154 baseline_path: Some(".agentshield-baseline.json"),
155 upload_sarif: true,
156 });
157
158 assert!(workflow.contains("baseline: \".agentshield-baseline.json\""));
159 assert!(workflow.contains("upload-sarif: true"));
160 assert!(workflow.contains("strict: true"));
161 assert!(workflow.contains("actions: read"));
162 }
163
164 #[test]
165 fn ci_workflow_security_suite_includes_complementary_scanners() {
166 let workflow = github_actions_security_suite_workflow(&CiInstallOptions {
167 fail_on: "high",
168 ignore_tests: true,
169 scan_path: ".",
170 baseline_path: Some(".agentshield-baseline.json"),
171 upload_sarif: true,
172 });
173
174 assert!(workflow.contains("name: Security Suite"));
175 assert!(workflow.contains("uses: github/codeql-action/init@v4"));
176 assert!(workflow.contains("uses: gitleaks/gitleaks-action@v3"));
177 assert!(workflow.contains("semgrep scan --config auto"));
178 assert!(workflow.contains("uses: aiconnai/agentshield@main"));
179 assert!(workflow.contains("baseline: \".agentshield-baseline.json\""));
180 assert!(workflow.contains("strict: true"));
181 assert!(workflow.contains("actions: read"));
182 }
183}