Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1 | //! The security service's paid suite, beside what [`crate::security`] |
| 2 | //! holds: custom secret patterns, push protection bypasses and their | |
| 3 | //! review, validity checks, code scanning from SARIF uploads, the | |
| 4 | //! dependency graph with its SBOM and pull request review, and the | |
| 5 | //! workspace's security overview. | |
| 6 | //! | |
| 7 | //! What is free and what is paid: secret scanning, push protection, | |
| 8 | //! vulnerability alerts and security updates are free everywhere. On a | |
| 9 | //! public repository everything here is free too. On a private one, the | |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 10 | //! features in [`PaidFeature`] come with the g1t plan (billing's |
| 11 | //! `Feature::Security`, which has no price of its own: its scans are | |
| 12 | //! metered like everything else); a refusal is a `PaymentRequired` failure | |
| 13 | //! whose message says how to start the plan. | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 14 | |
| 15 | use serde::{Deserialize, Serialize}; | |
| 16 | ||
| 17 | use crate::User; | |
| 18 | use crate::repos::RepoPath; | |
| 19 | use crate::security::{AlertActivity, AlertState, DismissReason, SecretFinding, SeverityCounts}; | |
| 20 | ||
| 21 | /// A feature of the suite that a private repository needs the activation | |
| 22 | /// for. | |
| 23 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 24 | #[serde(rename_all = "snake_case")] | |
| 25 | pub enum PaidFeature { | |
| 26 | CustomPatterns, | |
| 27 | ValidityChecks, | |
| 28 | DelegatedBypass, | |
| 29 | CodeScanning, | |
| 30 | DependencyReview, | |
| 31 | SecurityOverview, | |
| 32 | } | |
| 33 | ||
| 34 | impl PaidFeature { | |
| 35 | pub const ALL: [PaidFeature; 6] = [ | |
| 36 | PaidFeature::CustomPatterns, | |
| 37 | PaidFeature::ValidityChecks, | |
| 38 | PaidFeature::DelegatedBypass, | |
| 39 | PaidFeature::CodeScanning, | |
| 40 | PaidFeature::DependencyReview, | |
| 41 | PaidFeature::SecurityOverview, | |
| 42 | ]; | |
| 43 | ||
| 44 | pub fn as_str(self) -> &'static str { | |
| 45 | match self { | |
| 46 | PaidFeature::CustomPatterns => "custom_patterns", | |
| 47 | PaidFeature::ValidityChecks => "validity_checks", | |
| 48 | PaidFeature::DelegatedBypass => "delegated_bypass", | |
| 49 | PaidFeature::CodeScanning => "code_scanning", | |
| 50 | PaidFeature::DependencyReview => "dependency_review", | |
| 51 | PaidFeature::SecurityOverview => "security_overview", | |
| 52 | } | |
| 53 | } | |
| 54 | ||
| 55 | /// For a sentence: "Custom patterns". | |
| 56 | pub fn title(self) -> &'static str { | |
| 57 | match self { | |
| 58 | PaidFeature::CustomPatterns => "Custom patterns", | |
| 59 | PaidFeature::ValidityChecks => "Validity checks", | |
| 60 | PaidFeature::DelegatedBypass => "Delegated bypass", | |
| 61 | PaidFeature::CodeScanning => "Code scanning", | |
| 62 | PaidFeature::DependencyReview => "Dependency review", | |
| 63 | PaidFeature::SecurityOverview => "The security overview", | |
| 64 | } | |
| 65 | } | |
| 66 | } | |
| 67 | ||
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 68 | /// What a refusal for want of the plan says. |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 69 | pub fn needs_activation(feature: PaidFeature, workspace: &str) -> String { |
| 70 | format!( | |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 71 | "{} on private repositories comes with the g1t plan, which {workspace} does not have; its scans are charged at cost plus 20%. \ |
| 72 | An owner can start the plan at /{workspace}/-/billing. Public repositories have it free.", | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 73 | feature.title() |
| 74 | ) | |
| 75 | } | |
| 76 | ||
| 77 | /// The alert types, as the API and webhooks name them. | |
| 78 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 79 | #[serde(rename_all = "snake_case")] | |
| 80 | pub enum AlertType { | |
| 81 | SecretScanning, | |
| 82 | CodeScanning, | |
| 83 | Vulnerability, | |
| 84 | } | |
| 85 | ||
| 86 | impl AlertType { | |
| 87 | pub const ALL: [AlertType; 3] = [AlertType::SecretScanning, AlertType::CodeScanning, AlertType::Vulnerability]; | |
| 88 | ||
| 89 | pub fn as_str(self) -> &'static str { | |
| 90 | match self { | |
| 91 | AlertType::SecretScanning => "secret_scanning", | |
| 92 | AlertType::CodeScanning => "code_scanning", | |
| 93 | AlertType::Vulnerability => "vulnerability", | |
| 94 | } | |
| 95 | } | |
| 96 | ||
| 97 | pub fn parse(text: &str) -> Option<AlertType> { | |
| 98 | AlertType::ALL.into_iter().find(|kind| kind.as_str() == text) | |
| 99 | } | |
| 100 | ||
| 101 | /// Which type an alert id is: `sec_…`, `cod_…` or `vul_…`. | |
| 102 | pub fn of_id(id: &str) -> Option<AlertType> { | |
| 103 | match id.split('_').next()? { | |
| 104 | "sec" => Some(AlertType::SecretScanning), | |
| 105 | "cod" => Some(AlertType::CodeScanning), | |
| 106 | "vul" => Some(AlertType::Vulnerability), | |
| 107 | _ => None, | |
| 108 | } | |
| 109 | } | |
| 110 | ||
| 111 | /// The webhook event prefix: `secret_scanning_alert`. | |
| 112 | pub fn event_prefix(self) -> &'static str { | |
| 113 | match self { | |
| 114 | AlertType::SecretScanning => "secret_scanning_alert", | |
| 115 | AlertType::CodeScanning => "code_scanning_alert", | |
| 116 | AlertType::Vulnerability => "vulnerability_alert", | |
| 117 | } | |
| 118 | } | |
| 119 | } | |
| 120 | ||
| 121 | // --- Settings --------------------------------------------------------------- | |
| 122 | ||
| 123 | /// A repository's security settings beyond security updates. | |
| 124 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 125 | #[serde(rename_all = "camelCase")] | |
| 126 | pub struct RepoSecuritySettings { | |
| 127 | /// When a pull request's code scanning check fails: `none`, `errors`, | |
| 128 | /// or new results of `critical`, `high`, `medium` or `any` security | |
| 129 | /// severity (and errors). The check is `Code scanning`; require it in | |
| 130 | /// branch protection to block merges. | |
| 131 | pub code_scanning_gate: String, | |
| 132 | /// Whether pull requests get the `Dependency review` check. | |
| 133 | pub dependency_review: bool, | |
| 134 | /// The lowest severity of a known vulnerability in an added package | |
| 135 | /// that fails it: `critical`, `high`, `medium`, `low`, or `none`. | |
| 136 | pub review_fail_on: String, | |
| 137 | /// SPDX license ids an added package may not have. | |
| 138 | pub review_deny_licenses: Vec<String>, | |
| 139 | /// Whether the review also comments its summary on the pull request. | |
| 140 | pub review_comment: bool, | |
| Security tells people the way they can live with. Each scan publishes one summary event per alert type (secret_scanning_alert.summary, code_scanning_alert.summary, vulnerability_alert.summary: what it opened, by severity, what it fixed, what is open now, the worst new alert and the newest one's title, with the owners and security managers to tell) beside the per-alert events webhooks keep, so the inbox holds one thread per repository and alert type that the next scan brings up to date instead of one item per finding, and the ids are capped at 200 while the counts are not. Critical and high alerts and a refused push reach the inbox at once (and toast like a mention, as the new security notification kind); medium and low wait for each person's security digest, one inbox item and email per workspace, daily or weekly as they choose in their notification settings, at once, or never; a scan that opened nothing refreshes the thread quietly ("2 of 3 fixed") and marks it done when nothing is left open; and security alerts are emailed by default. A repository can turn alerts for secrets found in its history off while push protection stays on. Reporting arrives: security advisories (G1TSA ids, drafts published and withdrawn by Admins and security managers, visible to anyone who can see the repository once published) and private vulnerability reporting (a repository setting; anyone who can see it may report, signed out with an email on a public one, five a day each, and the owners and security managers are told at once), each with its RPC methods, REST routes under /repos/{owner}/{name}/security/advisories and /security/reports, MCP actions, scopes and reference examples. | 141 | /// Whether secrets found in the history (and in pushes too large to |
| 142 | /// scan first) open alerts and tell people. Push protection is never | |
| 143 | /// off: a push that adds a secret is refused whatever this says. | |
| 144 | #[serde(default = "yes")] | |
| 145 | pub history_alerts: bool, | |
| 146 | /// Whether anyone who can see the repository may report a | |
| 147 | /// vulnerability privately to its maintainers (`report_vulnerability`). | |
| 148 | #[serde(default)] | |
| 149 | pub private_vulnerability_reporting: bool, | |
| 150 | } | |
| 151 | ||
| 152 | fn yes() -> bool { | |
| 153 | true | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 154 | } |
| 155 | ||
| 156 | impl Default for RepoSecuritySettings { | |
| 157 | fn default() -> Self { | |
| 158 | RepoSecuritySettings { | |
| 159 | code_scanning_gate: "high".to_owned(), | |
| 160 | dependency_review: true, | |
| 161 | review_fail_on: "high".to_owned(), | |
| 162 | review_deny_licenses: Vec::new(), | |
| 163 | review_comment: true, | |
| Security tells people the way they can live with. Each scan publishes one summary event per alert type (secret_scanning_alert.summary, code_scanning_alert.summary, vulnerability_alert.summary: what it opened, by severity, what it fixed, what is open now, the worst new alert and the newest one's title, with the owners and security managers to tell) beside the per-alert events webhooks keep, so the inbox holds one thread per repository and alert type that the next scan brings up to date instead of one item per finding, and the ids are capped at 200 while the counts are not. Critical and high alerts and a refused push reach the inbox at once (and toast like a mention, as the new security notification kind); medium and low wait for each person's security digest, one inbox item and email per workspace, daily or weekly as they choose in their notification settings, at once, or never; a scan that opened nothing refreshes the thread quietly ("2 of 3 fixed") and marks it done when nothing is left open; and security alerts are emailed by default. A repository can turn alerts for secrets found in its history off while push protection stays on. Reporting arrives: security advisories (G1TSA ids, drafts published and withdrawn by Admins and security managers, visible to anyone who can see the repository once published) and private vulnerability reporting (a repository setting; anyone who can see it may report, signed out with an email on a public one, five a day each, and the owners and security managers are told at once), each with its RPC methods, REST routes under /repos/{owner}/{name}/security/advisories and /security/reports, MCP actions, scopes and reference examples. | 164 | history_alerts: true, |
| 165 | private_vulnerability_reporting: false, | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 166 | } |
| 167 | } | |
| 168 | } | |
| 169 | ||
| 170 | /// A workspace's security settings. | |
| 171 | #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| 172 | #[serde(rename_all = "camelCase")] | |
| 173 | pub struct WorkspaceSecuritySettings { | |
| 174 | /// Push protection bypasses need an owner's (or the repository's | |
| 175 | /// admins') approval: a person with Write requests one instead. | |
| 176 | pub delegated_bypass: bool, | |
| 177 | /// Ask each secret's issuer whether it still works, where that can be | |
| 178 | /// done safely. | |
| 179 | pub validity_checks: bool, | |
| 180 | } | |
| 181 | ||
| 182 | /// `security_settings`: a repository's settings, what its workspace sets, | |
| 183 | /// and whether the paid features are on for it. Write and up. | |
| 184 | #[derive(Debug, Serialize, Deserialize)] | |
| 185 | pub struct SecuritySettingsArgs { | |
| 186 | pub viewer: Option<User>, | |
| 187 | pub repo: RepoPath, | |
| 188 | } | |
| 189 | ||
| 190 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 191 | #[serde(rename_all = "camelCase")] | |
| 192 | pub struct SecuritySettingsView { | |
| 193 | pub settings: RepoSecuritySettings, | |
| 194 | pub workspace: WorkspaceSecuritySettings, | |
| 195 | /// Whether the repository is private. | |
| 196 | pub private: bool, | |
| 197 | /// Whether the paid features are on: a public repository, or the | |
| 198 | /// workspace has the activation (or is comped). | |
| 199 | pub entitled: bool, | |
| 200 | /// Security updates. | |
| 201 | pub upkeep: bool, | |
| 202 | } | |
| 203 | ||
| 204 | /// `set_security_settings`: Admin on the repository. | |
| 205 | #[derive(Debug, Serialize, Deserialize)] | |
| 206 | pub struct SetSecuritySettingsArgs { | |
| 207 | pub actor: User, | |
| 208 | pub repo: RepoPath, | |
| 209 | pub settings: RepoSecuritySettings, | |
| 210 | } | |
| 211 | ||
| 212 | /// `workspace_security_settings`: members only. Returns | |
| 213 | /// `Outcome<WorkspaceSecurityView>`. | |
| 214 | #[derive(Debug, Serialize, Deserialize)] | |
| 215 | pub struct WorkspaceSecuritySettingsArgs { | |
| 216 | pub viewer: Option<User>, | |
| 217 | pub workspace: String, | |
| 218 | } | |
| 219 | ||
| 220 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 221 | #[serde(rename_all = "camelCase")] | |
| 222 | pub struct WorkspaceSecurityView { | |
| 223 | pub settings: WorkspaceSecuritySettings, | |
| 224 | /// Whether the workspace has the activation (or is comped). | |
| 225 | pub activated: bool, | |
| 226 | } | |
| 227 | ||
| 228 | /// `set_workspace_security_settings`: owners only. | |
| 229 | #[derive(Debug, Serialize, Deserialize)] | |
| 230 | pub struct SetWorkspaceSecuritySettingsArgs { | |
| 231 | pub actor: User, | |
| 232 | pub workspace: String, | |
| 233 | pub settings: WorkspaceSecuritySettings, | |
| 234 | } | |
| 235 | ||
| 236 | // --- Custom patterns -------------------------------------------------------- | |
| 237 | ||
| 238 | /// A pattern as push protection and scans use it: see `g1t_scan::custom`. | |
| 239 | #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] | |
| 240 | #[serde(rename_all = "camelCase")] | |
| 241 | pub struct PatternSpec { | |
| 242 | pub id: String, | |
| 243 | pub name: String, | |
| 244 | pub pattern: String, | |
| 245 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 246 | pub before: Option<String>, | |
| 247 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 248 | pub after: Option<String>, | |
| 249 | } | |
| 250 | ||
| 251 | /// A custom pattern, as people manage it. | |
| 252 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 253 | #[serde(rename_all = "camelCase")] | |
| 254 | pub struct CustomPattern { | |
| 255 | /// `pat_…`. | |
| 256 | pub id: String, | |
| 257 | /// `repository` or `workspace`. | |
| 258 | pub scope: String, | |
| 259 | pub workspace: String, | |
| 260 | /// The repository's name, for a repository's own pattern. | |
| 261 | pub repo: Option<String>, | |
| 262 | pub name: String, | |
| 263 | pub pattern: String, | |
| 264 | pub before: Option<String>, | |
| 265 | pub after: Option<String>, | |
| 266 | pub test_strings: Vec<String>, | |
| 267 | /// `draft` (saved, not used) or `published` (push protection and scans | |
| 268 | /// use it). | |
| 269 | pub state: String, | |
| 270 | pub created_by: String, | |
| 271 | pub created_at: String, | |
| 272 | pub updated_by: String, | |
| 273 | pub updated_at: String, | |
| 274 | /// Open alerts it has found. | |
| 275 | pub open_alerts: u32, | |
| 276 | } | |
| 277 | ||
| 278 | /// `custom_patterns`: a repository's patterns and those it inherits from | |
| 279 | /// its workspace (with `repo`), or the workspace's own. Write and up for a | |
| 280 | /// repository, members for a workspace. Returns `Outcome<PatternList>`. | |
| 281 | #[derive(Debug, Serialize, Deserialize)] | |
| 282 | pub struct CustomPatternsArgs { | |
| 283 | pub viewer: Option<User>, | |
| 284 | pub workspace: String, | |
| 285 | #[serde(default)] | |
| 286 | pub repo: Option<RepoPath>, | |
| 287 | } | |
| 288 | ||
| 289 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 290 | #[serde(rename_all = "camelCase")] | |
| 291 | pub struct PatternList { | |
| 292 | pub patterns: Vec<CustomPattern>, | |
| 293 | pub entitled: bool, | |
| 294 | } | |
| 295 | ||
| 296 | /// `save_custom_pattern`: creates (no `id`) or changes a pattern. Admin on | |
| 297 | /// the repository, or an owner for a workspace's. Publishing one rescans | |
| 298 | /// the default branch's history with it. Returns `Outcome<SavedPattern>`. | |
| 299 | #[derive(Debug, Serialize, Deserialize)] | |
| 300 | #[serde(rename_all = "camelCase")] | |
| 301 | pub struct SaveCustomPatternArgs { | |
| 302 | pub actor: User, | |
| 303 | pub workspace: String, | |
| 304 | #[serde(default)] | |
| 305 | pub repo: Option<RepoPath>, | |
| 306 | #[serde(default)] | |
| 307 | pub id: Option<String>, | |
| 308 | pub name: String, | |
| 309 | pub pattern: String, | |
| 310 | #[serde(default)] | |
| 311 | pub before: Option<String>, | |
| 312 | #[serde(default)] | |
| 313 | pub after: Option<String>, | |
| 314 | #[serde(default)] | |
| 315 | pub test_strings: Vec<String>, | |
| 316 | /// Publish it (push protection and scans use it), or keep it a draft. | |
| 317 | #[serde(default)] | |
| 318 | pub publish: bool, | |
| 319 | } | |
| 320 | ||
| 321 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 322 | #[serde(rename_all = "camelCase")] | |
| 323 | pub struct SavedPattern { | |
| 324 | pub pattern: CustomPattern, | |
| 325 | /// For each test string, where the pattern matched it (start and end, | |
| 326 | /// in characters), or nothing. | |
| 327 | pub tests: Vec<Option<(u32, u32)>>, | |
| 328 | } | |
| 329 | ||
| 330 | /// `delete_custom_pattern`: with the roles of saving one. Its alerts stay. | |
| 331 | #[derive(Debug, Serialize, Deserialize)] | |
| 332 | pub struct DeleteCustomPatternArgs { | |
| 333 | pub actor: User, | |
| 334 | pub workspace: String, | |
| 335 | #[serde(default)] | |
| 336 | pub repo: Option<RepoPath>, | |
| 337 | pub id: String, | |
| 338 | } | |
| 339 | ||
| 340 | /// `dry_run_pattern`: runs a pattern over the default branch of the | |
| 341 | /// repository (or, for a workspace, up to ten of its repositories, or | |
| 342 | /// those named) without saving anything. Returns `Outcome<DryRun>`. | |
| 343 | #[derive(Debug, Serialize, Deserialize)] | |
| 344 | #[serde(rename_all = "camelCase")] | |
| 345 | pub struct DryRunPatternArgs { | |
| 346 | pub actor: User, | |
| 347 | pub workspace: String, | |
| 348 | #[serde(default)] | |
| 349 | pub repo: Option<RepoPath>, | |
| 350 | /// For a workspace: repository names; empty is the first ten. | |
| 351 | #[serde(default)] | |
| 352 | pub repos: Vec<String>, | |
| 353 | pub pattern: String, | |
| 354 | #[serde(default)] | |
| 355 | pub before: Option<String>, | |
| 356 | #[serde(default)] | |
| 357 | pub after: Option<String>, | |
| 358 | } | |
| 359 | ||
| 360 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 361 | #[serde(rename_all = "camelCase")] | |
| 362 | pub struct DryRun { | |
| 363 | pub repos: Vec<DryRunRepo>, | |
| 364 | } | |
| 365 | ||
| 366 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 367 | #[serde(rename_all = "camelCase")] | |
| 368 | pub struct DryRunRepo { | |
| 369 | pub name: String, | |
| 370 | #[serde(flatten)] | |
| 371 | pub result: PatternMatches, | |
| 372 | } | |
| 373 | ||
| 374 | /// `match_pattern` (repos): runs `pattern` over the files of the default | |
| 375 | /// branch, up to its limits. Returns `PatternMatches`. | |
| 376 | #[derive(Debug, Serialize, Deserialize)] | |
| 377 | #[serde(rename_all = "camelCase")] | |
| 378 | pub struct MatchPatternArgs { | |
| 379 | pub repo_id: String, | |
| 380 | pub pattern: PatternSpec, | |
| 381 | /// Matches returned, at most. | |
| 382 | pub limit: u32, | |
| 383 | } | |
| 384 | ||
| 385 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 386 | #[serde(rename_all = "camelCase")] | |
| 387 | pub struct PatternMatches { | |
| 388 | pub files_scanned: u32, | |
| 389 | pub matches: Vec<PatternMatch>, | |
| 390 | /// More files or matches than were looked at. | |
| 391 | pub truncated: bool, | |
| 392 | /// The commit read. | |
| 393 | pub commit: Option<String>, | |
| 394 | } | |
| 395 | ||
| 396 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 397 | #[serde(rename_all = "camelCase")] | |
| 398 | pub struct PatternMatch { | |
| 399 | pub path: String, | |
| 400 | pub line: u32, | |
| 401 | /// The line with the match masked but for its first characters. | |
| 402 | pub preview: String, | |
| 403 | } | |
| 404 | ||
| 405 | /// `patterns_for` (called by repos during a push): the published custom | |
| 406 | /// patterns a repository is scanned with, its own and its workspace's, | |
| 407 | /// when it is entitled to them. Returns `Vec<PatternSpec>`. | |
| 408 | #[derive(Debug, Serialize, Deserialize)] | |
| 409 | #[serde(rename_all = "camelCase")] | |
| 410 | pub struct PatternsForArgs { | |
| 411 | pub repo_id: String, | |
| 412 | pub namespace: String, | |
| 413 | #[serde(default)] | |
| 414 | pub private: Option<bool>, | |
| 415 | } | |
| 416 | ||
| 417 | // --- Secret alerts: locations, bypasses, validity --------------------------- | |
| 418 | ||
| 419 | /// Why a person pushed past push protection. | |
| 420 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 421 | #[serde(rename_all = "snake_case")] | |
| 422 | pub enum BypassReason { | |
| 423 | /// It is not a secret: the alert is closed as a false positive. | |
| 424 | FalsePositive, | |
| 425 | /// It is a value for tests: the alert is closed as used in tests. | |
| 426 | UsedInTests, | |
| 427 | /// It is real: the alert stays open, to be rotated. | |
| 428 | WillFixLater, | |
| 429 | } | |
| 430 | ||
| 431 | impl BypassReason { | |
| 432 | pub const ALL: [BypassReason; 3] = [BypassReason::FalsePositive, BypassReason::UsedInTests, BypassReason::WillFixLater]; | |
| 433 | ||
| 434 | pub fn as_str(self) -> &'static str { | |
| 435 | match self { | |
| 436 | BypassReason::FalsePositive => "false_positive", | |
| 437 | BypassReason::UsedInTests => "used_in_tests", | |
| 438 | BypassReason::WillFixLater => "will_fix_later", | |
| 439 | } | |
| 440 | } | |
| 441 | ||
| 442 | pub fn parse(text: &str) -> Option<BypassReason> { | |
| 443 | BypassReason::ALL.into_iter().find(|reason| reason.as_str() == text) | |
| 444 | } | |
| 445 | ||
| 446 | pub fn label(self) -> &'static str { | |
| 447 | match self { | |
| 448 | BypassReason::FalsePositive => "It's a false positive", | |
| 449 | BypassReason::UsedInTests => "It's used in tests", | |
| 450 | BypassReason::WillFixLater => "I'll fix it later", | |
| 451 | } | |
| 452 | } | |
| 453 | ||
| 454 | /// The dismissal it amounts to, when it closes the alert. | |
| 455 | pub fn dismissal(self) -> Option<DismissReason> { | |
| 456 | match self { | |
| 457 | BypassReason::FalsePositive => Some(DismissReason::FalsePositive), | |
| 458 | BypassReason::UsedInTests => Some(DismissReason::UsedInTests), | |
| 459 | BypassReason::WillFixLater => None, | |
| 460 | } | |
| 461 | } | |
| 462 | } | |
| 463 | ||
| 464 | /// A push protection bypass, made or asked for. | |
| 465 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 466 | #[serde(rename_all = "camelCase")] | |
| 467 | pub struct Bypass { | |
| 468 | pub reason: BypassReason, | |
| 469 | pub comment: Option<String>, | |
| 470 | /// Who pushed past it. | |
| 471 | pub by: String, | |
| 472 | pub at: String, | |
| 473 | /// Who approved it, when delegated bypass asked for approval. | |
| 474 | pub approved_by: Option<String>, | |
| 475 | } | |
| 476 | ||
| 477 | /// Where a secret was found: each file, line and commit it is in. | |
| 478 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 479 | #[serde(rename_all = "camelCase")] | |
| 480 | pub struct SecretLocation { | |
| 481 | pub path: String, | |
| 482 | pub line: u32, | |
| 483 | pub commit: String, | |
| 484 | /// `push` or `history`. | |
| 485 | pub source: String, | |
| 486 | pub found_at: String, | |
| 487 | } | |
| 488 | ||
| 489 | /// A request to bypass push protection, when delegated bypass is on. | |
| 490 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 491 | #[serde(rename_all = "camelCase")] | |
| 492 | pub struct BypassRequest { | |
| 493 | /// `byp_…`. | |
| 494 | pub id: String, | |
| 495 | pub repo_id: String, | |
| 496 | pub workspace: String, | |
| 497 | pub repo: String, | |
| 498 | pub secret_id: String, | |
| 499 | /// "an AWS access key". | |
| 500 | pub label: String, | |
| 501 | pub path: String, | |
| 502 | pub line: u32, | |
| 503 | pub preview: String, | |
| 504 | pub requester: String, | |
| 505 | pub reason: BypassReason, | |
| 506 | pub comment: Option<String>, | |
| 507 | /// `pending`, `approved`, `denied` or `cancelled`. | |
| 508 | pub state: String, | |
| 509 | pub reviewer: Option<String>, | |
| 510 | pub review_comment: Option<String>, | |
| 511 | pub created_at: String, | |
| 512 | pub reviewed_at: Option<String>, | |
| 513 | } | |
| 514 | ||
| 515 | /// `secret_alert`: one secret alert with every place it was found, what | |
| 516 | /// happened to it and its bypass requests. Write and up. Returns | |
| 517 | /// `Outcome<SecretAlertDetail>`. | |
| 518 | #[derive(Debug, Serialize, Deserialize)] | |
| 519 | pub struct SecretAlertArgs { | |
| 520 | pub viewer: Option<User>, | |
| 521 | pub repo: RepoPath, | |
| 522 | pub id: String, | |
| 523 | } | |
| 524 | ||
| 525 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 526 | #[serde(rename_all = "camelCase")] | |
| 527 | pub struct SecretAlertDetail { | |
| 528 | pub secret: SecretFinding, | |
| 529 | pub locations: Vec<SecretLocation>, | |
| 530 | pub activity: Vec<AlertActivity>, | |
| 531 | pub requests: Vec<BypassRequest>, | |
| 532 | /// Whether this kind of secret can be checked with its issuer. | |
| 533 | pub checkable: bool, | |
| 534 | /// Whether the viewer may bypass it directly, may only ask, or neither. | |
| 535 | pub can_bypass: bool, | |
| 536 | pub can_request_bypass: bool, | |
| 537 | } | |
| 538 | ||
| 539 | /// `bypass`: lets a blocked secret through push protection with a reason, | |
| 540 | /// recorded on the alert and in the audit log. Write and up; with | |
| 541 | /// delegated bypass on, only those who review requests (owners and the | |
| 542 | /// repository's admins) bypass directly, and anyone else's call makes a | |
| 543 | /// request instead. Returns `Outcome<BypassResult>`. | |
| 544 | #[derive(Debug, Serialize, Deserialize)] | |
| 545 | pub struct BypassArgs { | |
| 546 | pub actor: User, | |
| 547 | pub repo: RepoPath, | |
| 548 | pub id: String, | |
| 549 | pub reason: BypassReason, | |
| 550 | #[serde(default)] | |
| 551 | pub comment: String, | |
| 552 | } | |
| 553 | ||
| 554 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 555 | #[serde(rename_all = "camelCase")] | |
| 556 | pub struct BypassResult { | |
| 557 | pub secret: SecretFinding, | |
| 558 | /// The request made, when delegated bypass needs approval. | |
| 559 | pub request: Option<BypassRequest>, | |
| 560 | } | |
| 561 | ||
| 562 | /// `bypass_requests`: a workspace's requests (or one repository's), newest | |
| 563 | /// first. Members see their own; reviewers see all. Returns | |
| 564 | /// `Outcome<Vec<BypassRequest>>`. | |
| 565 | #[derive(Debug, Serialize, Deserialize)] | |
| 566 | pub struct BypassRequestsArgs { | |
| 567 | pub viewer: Option<User>, | |
| 568 | pub workspace: String, | |
| 569 | #[serde(default)] | |
| 570 | pub repo: Option<RepoPath>, | |
| 571 | /// `pending`, `approved`, `denied`, `cancelled`; all when absent. | |
| 572 | #[serde(default)] | |
| 573 | pub state: Option<String>, | |
| 574 | } | |
| 575 | ||
| 576 | /// `review_bypass`: approves or denies a request (owners and the | |
| 577 | /// repository's admins), or cancels one's own. Returns | |
| 578 | /// `Outcome<BypassRequest>`. | |
| 579 | #[derive(Debug, Serialize, Deserialize)] | |
| 580 | pub struct ReviewBypassArgs { | |
| 581 | pub actor: User, | |
| 582 | pub workspace: String, | |
| 583 | pub id: String, | |
| 584 | /// `approve`, `deny` or `cancel`. | |
| 585 | pub decision: String, | |
| 586 | #[serde(default)] | |
| 587 | pub comment: String, | |
| 588 | } | |
| 589 | ||
| 590 | /// `check_validity`: asks the issuer whether a secret still works. | |
| 591 | /// Write and up; needs validity checks on for the workspace. Returns | |
| 592 | /// `Outcome<SecretFinding>`. | |
| 593 | #[derive(Debug, Serialize, Deserialize)] | |
| 594 | pub struct CheckValidityArgs { | |
| 595 | pub actor: User, | |
| 596 | pub repo: RepoPath, | |
| 597 | pub id: String, | |
| 598 | } | |
| 599 | ||
| 600 | /// `check_secret` (repos): finds the secret with `fingerprint` at | |
| 601 | /// `commit`:`path` near `line` and asks its issuer about it. The value | |
| 602 | /// never leaves the repos service except to the issuer's own API. | |
| 603 | /// Returns `SecretValidity`. | |
| 604 | #[derive(Debug, Serialize, Deserialize)] | |
| 605 | #[serde(rename_all = "camelCase")] | |
| 606 | pub struct CheckSecretArgs { | |
| 607 | pub repo_id: String, | |
| 608 | pub commit: String, | |
| 609 | pub path: String, | |
| 610 | pub line: u32, | |
| 611 | pub kind: String, | |
| 612 | pub fingerprint: String, | |
| 613 | } | |
| 614 | ||
| 615 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 616 | #[serde(rename_all = "camelCase")] | |
| 617 | pub struct SecretValidity { | |
| 618 | /// `active`, `inactive`, `unknown` or `unsupported`. | |
| 619 | pub validity: String, | |
| 620 | /// Why it is unknown, when it is. | |
| 621 | pub detail: Option<String>, | |
| 622 | } | |
| 623 | ||
| 624 | // --- Code scanning ---------------------------------------------------------- | |
| 625 | ||
| 626 | /// A code scanning alert: one problem a tool reports on the default | |
| 627 | /// branch, the same alert across analyses by its fingerprint. | |
| 628 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 629 | #[serde(rename_all = "camelCase")] | |
| 630 | pub struct CodeAlert { | |
| 631 | /// `cod_…`. | |
| 632 | pub id: String, | |
| 633 | /// Per repository, from 1. | |
| 634 | pub number: u32, | |
| 635 | pub repo_id: String, | |
| 636 | pub tool: String, | |
| 637 | pub category: String, | |
| 638 | pub rule_id: String, | |
| 639 | pub rule_name: Option<String>, | |
| 640 | pub rule_description: Option<String>, | |
| 641 | pub help: Option<String>, | |
| 642 | pub help_uri: Option<String>, | |
| 643 | pub tags: Vec<String>, | |
| 644 | /// `error`, `warning`, `note` or `none`. | |
| 645 | pub level: String, | |
| 646 | /// `critical`, `high`, `medium` or `low`, for security rules. | |
| 647 | pub security_severity: Option<String>, | |
| 648 | /// What lists sort by: the security severity, else from the level. | |
| 649 | pub severity: String, | |
| 650 | pub message: String, | |
| 651 | pub path: Option<String>, | |
| 652 | pub start_line: Option<u32>, | |
| 653 | pub end_line: Option<u32>, | |
| 654 | pub start_column: Option<u32>, | |
| 655 | pub end_column: Option<u32>, | |
| 656 | pub state: AlertState, | |
| 657 | pub fingerprint: String, | |
| 658 | pub first_commit: String, | |
| 659 | pub last_commit: String, | |
| 660 | pub created_at: String, | |
| 661 | pub updated_at: String, | |
| 662 | pub fixed_at: Option<String>, | |
| 663 | pub dismissed_by: Option<String>, | |
| 664 | pub dismissed_reason: Option<DismissReason>, | |
| 665 | pub dismissed_comment: Option<String>, | |
| 666 | pub dismissed_at: Option<String>, | |
| 667 | /// The issue g1t was put on to fix it. | |
| 668 | pub issue: Option<u32>, | |
| 669 | } | |
| 670 | ||
| 671 | /// One upload's run of one tool on one commit. | |
| 672 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 673 | #[serde(rename_all = "camelCase")] | |
| 674 | pub struct Analysis { | |
| 675 | /// `ana_…`. | |
| 676 | pub id: String, | |
| 677 | pub repo_id: String, | |
| 678 | pub sarif_id: String, | |
| 679 | pub tool: String, | |
| 680 | pub tool_version: Option<String>, | |
| 681 | pub category: String, | |
| 682 | pub commit_sha: String, | |
| 683 | /// `refs/heads/main`, `refs/pull/3/head`. | |
| 684 | pub git_ref: String, | |
| 685 | /// The pull request it is for, if any. | |
| 686 | pub pull: Option<u32>, | |
| 687 | pub results: u32, | |
| 688 | /// Alerts it opened, and fixed (on the default branch only). | |
| 689 | pub new_alerts: u32, | |
| 690 | pub fixed_alerts: u32, | |
| 691 | /// Results past the limit, not kept. | |
| 692 | pub dropped: u32, | |
| 693 | pub created_at: String, | |
| 694 | } | |
| 695 | ||
| 696 | /// `upload_sarif`: reads a SARIF upload into analyses and alerts. Write | |
| 697 | /// and up (a workflow's token can). Returns `Outcome<SarifUpload>`. | |
| 698 | #[derive(Debug, Serialize, Deserialize)] | |
| 699 | #[serde(rename_all = "camelCase")] | |
| 700 | pub struct UploadSarifArgs { | |
| 701 | pub actor: User, | |
| 702 | pub repo: RepoPath, | |
| 703 | pub commit_sha: String, | |
| 704 | /// `refs/heads/<branch>` or `refs/pull/<number>/head` (or `/merge`). | |
| 705 | pub git_ref: String, | |
| 706 | /// The SARIF document, gzipped and base64-encoded. | |
| 707 | pub sarif: String, | |
| 708 | #[serde(default)] | |
| 709 | pub tool_name: Option<String>, | |
| 710 | #[serde(default)] | |
| 711 | pub category: Option<String>, | |
| 712 | /// Where the files were checked out, to make absolute paths relative. | |
| 713 | #[serde(default)] | |
| 714 | pub checkout_uri: Option<String>, | |
| 715 | } | |
| 716 | ||
| 717 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 718 | #[serde(rename_all = "camelCase")] | |
| 719 | pub struct SarifUpload { | |
| 720 | /// `sar_…`. | |
| 721 | pub id: String, | |
| 722 | /// `complete` or `failed`. | |
| 723 | pub processing_status: String, | |
| 724 | pub analyses: Vec<String>, | |
| 725 | pub errors: Vec<String>, | |
| 726 | pub commit_sha: String, | |
| 727 | pub git_ref: String, | |
| 728 | pub created_at: String, | |
| 729 | } | |
| 730 | ||
| 731 | /// `sarif_status`: one upload. Returns `Outcome<SarifUpload>`. | |
| 732 | #[derive(Debug, Serialize, Deserialize)] | |
| 733 | pub struct SarifStatusArgs { | |
| 734 | pub viewer: Option<User>, | |
| 735 | pub repo: RepoPath, | |
| 736 | pub id: String, | |
| 737 | } | |
| 738 | ||
| 739 | /// `code_scanning`: a repository's alerts (newest 1,000) and recent | |
| 740 | /// analyses. Write and up. Returns `Outcome<CodeScanning>`. | |
| 741 | #[derive(Debug, Serialize, Deserialize)] | |
| 742 | pub struct CodeScanningArgs { | |
| 743 | pub viewer: Option<User>, | |
| 744 | pub repo: RepoPath, | |
| 745 | } | |
| 746 | ||
| 747 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 748 | #[serde(rename_all = "camelCase")] | |
| 749 | pub struct CodeScanning { | |
| 750 | pub alerts: Vec<CodeAlert>, | |
| 751 | pub analyses: Vec<Analysis>, | |
| 752 | pub entitled: bool, | |
| 753 | pub private: bool, | |
| 754 | /// Whether a starter workflow is on the default branch. | |
| 755 | pub configured: bool, | |
| 756 | } | |
| 757 | ||
| 758 | /// `code_alert`: one alert by number, with its activity and the pull | |
| 759 | /// requests that reported it. Returns `Outcome<CodeAlertDetail>`. | |
| 760 | #[derive(Debug, Serialize, Deserialize)] | |
| 761 | pub struct CodeAlertArgs { | |
| 762 | pub viewer: Option<User>, | |
| 763 | pub repo: RepoPath, | |
| 764 | pub number: u32, | |
| 765 | } | |
| 766 | ||
| 767 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 768 | #[serde(rename_all = "camelCase")] | |
| 769 | pub struct CodeAlertDetail { | |
| 770 | pub alert: CodeAlert, | |
| 771 | pub activity: Vec<AlertActivity>, | |
| 772 | /// Analyses that reported it, newest first. | |
| 773 | pub analyses: Vec<Analysis>, | |
| 774 | } | |
| 775 | ||
| 776 | /// `set_code_alert_state`: dismisses (with `false_positive`, `wont_fix` or | |
| 777 | /// `used_in_tests`) or reopens a code scanning alert. Write and up. | |
| 778 | /// Returns `Outcome<CodeAlert>`. | |
| 779 | #[derive(Debug, Serialize, Deserialize)] | |
| 780 | pub struct SetCodeAlertStateArgs { | |
| 781 | pub actor: User, | |
| 782 | pub repo: RepoPath, | |
| 783 | pub number: u32, | |
| 784 | /// `dismissed` or `open`. | |
| 785 | pub state: AlertState, | |
| 786 | #[serde(default)] | |
| 787 | pub reason: Option<DismissReason>, | |
| 788 | #[serde(default)] | |
| 789 | pub comment: String, | |
| 790 | } | |
| 791 | ||
| 792 | /// `pull_code_scanning`: what code scanning found on a pull request's head, | |
| 793 | /// for its Security results page. Returns `Outcome<PullScanning>`. | |
| 794 | #[derive(Debug, Serialize, Deserialize)] | |
| 795 | pub struct PullScanningArgs { | |
| 796 | pub viewer: Option<User>, | |
| 797 | pub repo: RepoPath, | |
| 798 | pub number: u32, | |
| 799 | } | |
| 800 | ||
| 801 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 802 | #[serde(rename_all = "camelCase")] | |
| 803 | pub struct PullScanning { | |
| 804 | pub commit: Option<String>, | |
| 805 | pub results: Vec<PullResult>, | |
| 806 | pub review: Option<DependencyReview>, | |
| 807 | /// The check's state and line, as the pull request shows it. | |
| 808 | pub code_status: Option<String>, | |
| 809 | pub code_description: Option<String>, | |
| 810 | } | |
| 811 | ||
| 812 | /// A result on a pull request's head. | |
| 813 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 814 | #[serde(rename_all = "camelCase")] | |
| 815 | pub struct PullResult { | |
| 816 | pub tool: String, | |
| 817 | pub rule_id: String, | |
| 818 | pub level: String, | |
| 819 | pub severity: String, | |
| 820 | pub security_severity: Option<String>, | |
| 821 | pub message: String, | |
| 822 | pub path: Option<String>, | |
| 823 | pub line: Option<u32>, | |
| 824 | /// Not open on the default branch: this pull request brings it. | |
| 825 | pub new: bool, | |
| 826 | /// On a line the pull request adds. | |
| 827 | pub on_changed_line: bool, | |
| 828 | /// Whether it fails the check. | |
| 829 | pub failing: bool, | |
| 830 | } | |
| 831 | ||
| 832 | /// `fix_alert`: puts g1t on an issue to fix a code scanning alert, a | |
| 833 | /// vulnerable dependency or a leaked secret (removing it from the code; | |
| 834 | /// rotating it stays with you). Write and up, and agents allowed to run. | |
| 835 | /// The agent's work is billed as agent usage. Returns | |
| 836 | /// `Outcome<AlertFix>`. | |
| 837 | #[derive(Debug, Serialize, Deserialize)] | |
| 838 | pub struct FixAlertArgs { | |
| 839 | pub actor: User, | |
| 840 | pub repo: RepoPath, | |
| 841 | /// `cod_…`, `vul_…` or `sec_…`. | |
| 842 | pub id: String, | |
| 843 | } | |
| 844 | ||
| 845 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 846 | #[serde(rename_all = "camelCase")] | |
| 847 | pub struct AlertFix { | |
| 848 | pub issue: u32, | |
| 849 | /// Whether an agent was started on it. | |
| 850 | pub started: bool, | |
| 851 | pub message: Option<String>, | |
| 852 | } | |
| 853 | ||
| 854 | // --- Supply chain ----------------------------------------------------------- | |
| 855 | ||
| 856 | /// One package in the dependency graph. | |
| 857 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 858 | #[serde(rename_all = "camelCase")] | |
| 859 | pub struct GraphDependency { | |
| 860 | /// `npm`, `crates.io`, `Go`, `PyPI`. | |
| 861 | pub ecosystem: String, | |
| 862 | pub name: String, | |
| 863 | pub version: String, | |
| 864 | pub manifest: String, | |
| 865 | /// `direct`, `transitive` or `unknown`. | |
| 866 | pub relationship: String, | |
| 867 | pub development: bool, | |
| 868 | pub license: Option<String>, | |
| 869 | pub purl: String, | |
| 870 | /// Open vulnerability alerts on it. | |
| 871 | pub vulnerabilities: u32, | |
| 872 | } | |
| 873 | ||
| 874 | /// `dependency_graph`: the packages the lockfiles on the default branch | |
| 875 | /// resolve. Write and up. Returns `Outcome<DependencyGraph>`. | |
| 876 | #[derive(Debug, Serialize, Deserialize)] | |
| 877 | pub struct DependencyGraphArgs { | |
| 878 | pub viewer: Option<User>, | |
| 879 | pub repo: RepoPath, | |
| 880 | } | |
| 881 | ||
| 882 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 883 | #[serde(rename_all = "camelCase")] | |
| 884 | pub struct DependencyGraph { | |
| 885 | pub commit: Option<String>, | |
| 886 | pub manifests: Vec<GraphManifest>, | |
| 887 | pub dependencies: Vec<GraphDependency>, | |
| 888 | } | |
| 889 | ||
| 890 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 891 | #[serde(rename_all = "camelCase")] | |
| 892 | pub struct GraphManifest { | |
| 893 | pub path: String, | |
| 894 | pub ecosystem: String, | |
| 895 | pub dependencies: u32, | |
| 896 | pub direct: u32, | |
| 897 | } | |
| 898 | ||
| 899 | /// `sbom`: the dependency graph as an SPDX 2.3 JSON document. Returns | |
| 900 | /// `Outcome<serde_json::Value>`. | |
| 901 | #[derive(Debug, Serialize, Deserialize)] | |
| 902 | pub struct SbomArgs { | |
| 903 | pub viewer: Option<User>, | |
| 904 | pub repo: RepoPath, | |
| 905 | } | |
| 906 | ||
| 907 | /// `dependency_review`: compares the dependencies at `base` and `head` | |
| 908 | /// (commits, branches or tags). Returns `Outcome<DependencyReview>`. | |
| 909 | #[derive(Debug, Serialize, Deserialize)] | |
| 910 | pub struct DependencyReviewArgs { | |
| 911 | pub viewer: Option<User>, | |
| 912 | pub repo: RepoPath, | |
| 913 | pub base: String, | |
| 914 | pub head: String, | |
| 915 | } | |
| 916 | ||
| 917 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 918 | #[serde(rename_all = "camelCase")] | |
| 919 | pub struct DependencyReview { | |
| 920 | pub base: String, | |
| 921 | pub head: String, | |
| 922 | pub changes: Vec<ReviewChange>, | |
| 923 | pub passed: bool, | |
| 924 | pub headline: String, | |
| 925 | /// The policy it was judged by. | |
| 926 | pub fail_on: String, | |
| 927 | pub deny_licenses: Vec<String>, | |
| 928 | } | |
| 929 | ||
| 930 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 931 | #[serde(rename_all = "camelCase")] | |
| 932 | pub struct ReviewChange { | |
| 933 | /// `added` or `removed`. | |
| 934 | pub change_type: String, | |
| 935 | pub manifest: String, | |
| 936 | pub ecosystem: String, | |
| 937 | pub name: String, | |
| 938 | pub version: String, | |
| 939 | pub relationship: String, | |
| 940 | pub development: bool, | |
| 941 | pub license: Option<String>, | |
| 942 | pub purl: String, | |
| 943 | pub vulnerabilities: Vec<ReviewVulnerability>, | |
| 944 | pub denied_license: bool, | |
| 945 | /// Whether it fails the review. | |
| 946 | pub failing: bool, | |
| 947 | } | |
| 948 | ||
| 949 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 950 | #[serde(rename_all = "camelCase")] | |
| 951 | pub struct ReviewVulnerability { | |
| 952 | pub advisory: String, | |
| 953 | pub osv_id: String, | |
| 954 | pub summary: String, | |
| 955 | pub severity: String, | |
| 956 | pub fixed_version: Option<String>, | |
| 957 | pub url: String, | |
| 958 | } | |
| 959 | ||
| 960 | // --- The workspace's overview ------------------------------------------------ | |
| 961 | ||
| 962 | /// `security_overview`: totals, trends, coverage and the repositories most | |
| 963 | /// in need, across a workspace. Members only; private repositories count | |
| 964 | /// only with the activation. Returns `Outcome<WorkspaceOverview>`. | |
| 965 | #[derive(Debug, Serialize, Deserialize)] | |
| 966 | pub struct WorkspaceOverviewArgs { | |
| 967 | pub viewer: Option<User>, | |
| 968 | pub workspace: String, | |
| 969 | /// Days of trend, 7 to 90. | |
| 970 | #[serde(default)] | |
| 971 | pub days: Option<u32>, | |
| 972 | } | |
| 973 | ||
| 974 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 975 | #[serde(rename_all = "camelCase")] | |
| 976 | pub struct WorkspaceOverview { | |
| 977 | pub activated: bool, | |
| 978 | /// Private repositories left out for want of the activation. | |
| 979 | pub private_hidden: u32, | |
| 980 | pub totals: Vec<TypeTotals>, | |
| 981 | pub trend: Vec<TrendPoint>, | |
| 982 | pub repos: Vec<RepoCoverage>, | |
| 983 | } | |
| 984 | ||
| 985 | /// Open alerts of one type, and how many opened and closed lately. | |
| 986 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 987 | #[serde(rename_all = "camelCase")] | |
| 988 | pub struct TypeTotals { | |
| 989 | pub alert_type: String, | |
| 990 | pub open: SeverityCounts, | |
| 991 | pub opened: u32, | |
| 992 | pub closed: u32, | |
| 993 | } | |
| 994 | ||
| 995 | /// Open alerts by type on one day. | |
| 996 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 997 | #[serde(rename_all = "camelCase")] | |
| 998 | pub struct TrendPoint { | |
| 999 | /// `YYYY-MM-DD`. | |
| 1000 | pub day: String, | |
| 1001 | pub secret_scanning: u32, | |
| 1002 | pub code_scanning: u32, | |
| 1003 | pub vulnerability: u32, | |
| 1004 | } | |
| 1005 | ||
| 1006 | /// One repository: what is on, and what is open. | |
| 1007 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 1008 | #[serde(rename_all = "camelCase")] | |
| 1009 | pub struct RepoCoverage { | |
| 1010 | pub repo_id: String, | |
| 1011 | pub name: String, | |
| 1012 | pub private: bool, | |
| 1013 | pub custom_patterns: u32, | |
| 1014 | pub validity_checks: bool, | |
| 1015 | /// When code scanning last reported, if ever. | |
| 1016 | pub code_scanning_at: Option<String>, | |
| 1017 | pub dependency_review: bool, | |
| 1018 | pub security_updates: bool, | |
| 1019 | pub lockfiles: u32, | |
| 1020 | pub secrets: SeverityCounts, | |
| 1021 | pub code: SeverityCounts, | |
| 1022 | pub vulnerabilities: SeverityCounts, | |
| 1023 | } | |
| 1024 | ||
| 1025 | /// `workspace_alerts`: alerts of one type across a workspace, for the API. | |
| 1026 | /// Returns `Outcome<Vec<WorkspaceAlert>>`. | |
| 1027 | #[derive(Debug, Serialize, Deserialize)] | |
| 1028 | #[serde(rename_all = "camelCase")] | |
| 1029 | pub struct WorkspaceAlertsArgs { | |
| 1030 | pub viewer: Option<User>, | |
| 1031 | pub workspace: String, | |
| 1032 | pub alert_type: AlertType, | |
| 1033 | } | |
| 1034 | ||
| 1035 | /// An alert of any type, with its repository. | |
| 1036 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 1037 | #[serde(rename_all = "camelCase")] | |
| 1038 | pub struct WorkspaceAlert { | |
| 1039 | pub repo: String, | |
| 1040 | #[serde(default)] | |
| 1041 | pub secret: Option<SecretFinding>, | |
| 1042 | #[serde(default)] | |
| 1043 | pub code: Option<CodeAlert>, | |
| 1044 | #[serde(default)] | |
| 1045 | pub vulnerability: Option<crate::security::Vulnerability>, | |
| 1046 | } | |
| 1047 | ||
| 1048 | // --- Events ------------------------------------------------------------------ | |
| 1049 | ||
| 1050 | /// What every security event carries (`secret_scanning_alert.created`, | |
| 1051 | /// `code_scanning_alert.fixed`, `vulnerability_alert.dismissed`, | |
| 1052 | /// `secret_scanning.bypass_requested`, …), for webhooks and the inbox. | |
| 1053 | #[derive(Clone, Debug, Default, Serialize, Deserialize)] | |
| 1054 | #[serde(rename_all = "camelCase")] | |
| 1055 | pub struct SecurityEvent { | |
| 1056 | pub repo_id: String, | |
| 1057 | pub alert_id: String, | |
| 1058 | /// `secret_scanning`, `code_scanning` or `vulnerability`. | |
| 1059 | pub alert_type: String, | |
| 1060 | /// A code scanning alert's number. | |
| 1061 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 1062 | pub alert_number: Option<u32>, | |
| 1063 | pub severity: String, | |
| 1064 | /// One line: "An AWS access key in config/prod.env". | |
| 1065 | pub title: String, | |
| 1066 | /// The alert's page, from the site's root: `/acme/rocket/security/…`. | |
| 1067 | pub link: String, | |
| 1068 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 1069 | pub path: Option<String>, | |
| 1070 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 1071 | pub line: Option<u32>, | |
| 1072 | /// `open`, `dismissed` or `fixed`. | |
| 1073 | pub state: String, | |
| 1074 | /// A dismissal's or bypass's reason. | |
| 1075 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 1076 | pub reason: Option<String>, | |
| 1077 | /// For a blocked push: who pushed it, by username. | |
| 1078 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 1079 | pub pusher: Option<String>, | |
| 1080 | /// People to tell besides the repository's security watchers: its | |
| 1081 | /// workspace's owners, for a new alert or a bypass request, and the | |
| 1082 | /// requester, for a reviewed one. Usernames. | |
| 1083 | #[serde(default, skip_serializing_if = "Vec::is_empty")] | |
| 1084 | pub notify: Vec<String>, | |
| 1085 | /// A bypass request's id. | |
| 1086 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 1087 | pub request_id: Option<String>, | |
| 1088 | } | |
| 1089 | ||
| Security tells people the way they can live with. Each scan publishes one summary event per alert type (secret_scanning_alert.summary, code_scanning_alert.summary, vulnerability_alert.summary: what it opened, by severity, what it fixed, what is open now, the worst new alert and the newest one's title, with the owners and security managers to tell) beside the per-alert events webhooks keep, so the inbox holds one thread per repository and alert type that the next scan brings up to date instead of one item per finding, and the ids are capped at 200 while the counts are not. Critical and high alerts and a refused push reach the inbox at once (and toast like a mention, as the new security notification kind); medium and low wait for each person's security digest, one inbox item and email per workspace, daily or weekly as they choose in their notification settings, at once, or never; a scan that opened nothing refreshes the thread quietly ("2 of 3 fixed") and marks it done when nothing is left open; and security alerts are emailed by default. A repository can turn alerts for secrets found in its history off while push protection stays on. Reporting arrives: security advisories (G1TSA ids, drafts published and withdrawn by Admins and security managers, visible to anyone who can see the repository once published) and private vulnerability reporting (a repository setting; anyone who can see it may report, signed out with an email on a public one, five a day each, and the owners and security managers are told at once), each with its RPC methods, REST routes under /repos/{owner}/{name}/security/advisories and /security/reports, MCP actions, scopes and reference examples. | 1090 | /// What one scan did to a repository's alerts of one type |
| 1091 | /// (`secret_scanning_alert.summary`, `code_scanning_alert.summary`, | |
| 1092 | /// `vulnerability_alert.summary`): published once per scan, beside the | |
| 1093 | /// per-alert `.created` and `.fixed` events webhooks get. The inbox reads | |
| 1094 | /// these alone, so a scan that finds forty vulnerabilities is one item, | |
| 1095 | /// not forty, and a later scan brings the same item back up to date | |
| 1096 | /// (docs.g1t.sh/guides/notifications/#security). | |
| 1097 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] | |
| 1098 | #[serde(rename_all = "camelCase")] | |
| 1099 | pub struct SecuritySummary { | |
| 1100 | pub repo_id: String, | |
| 1101 | /// `secret_scanning`, `code_scanning` or `vulnerability`. | |
| 1102 | pub alert_type: String, | |
| 1103 | /// What ran: `push`, `history`, `dependencies`, `analysis`, or | |
| 1104 | /// `change` for a dismissal, a reopening or a bypass. | |
| 1105 | pub scan: String, | |
| 1106 | /// Alerts the scan opened, newest first, at most [`MAX_SUMMARY_IDS`]; | |
| 1107 | /// `opened_count` says how many there were. | |
| 1108 | #[serde(default, skip_serializing_if = "Vec::is_empty")] | |
| 1109 | pub opened: Vec<String>, | |
| 1110 | pub opened_count: u32, | |
| 1111 | /// Those it closed as fixed (and how many), and how many were dismissed. | |
| 1112 | #[serde(default, skip_serializing_if = "Vec::is_empty")] | |
| 1113 | pub fixed: Vec<String>, | |
| 1114 | pub fixed_count: u32, | |
| 1115 | pub dismissed_count: u32, | |
| 1116 | /// The alerts opened, by severity. | |
| 1117 | pub new_by_severity: SeverityCounts, | |
| 1118 | /// What is open of this type after the scan, by severity. | |
| 1119 | pub open: SeverityCounts, | |
| 1120 | /// The worst severity among those opened: `critical`, `high`, `medium` | |
| 1121 | /// or `low`; absent when nothing opened. | |
| 1122 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 1123 | pub worst: Option<String>, | |
| 1124 | /// One line for the newest alert opened: "An AWS access key in | |
| 1125 | /// config/prod.env", "lodash 4.17.20 in package-lock.json: GHSA-…". | |
| 1126 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 1127 | pub latest: Option<String>, | |
| 1128 | /// The findings page, from the site's root: `/acme/rocket/security/…`. | |
| 1129 | pub link: String, | |
| 1130 | /// For a push that was refused: who pushed, and how many secrets | |
| 1131 | /// stopped it. They are told at once, whatever the severity. | |
| 1132 | #[serde(default, skip_serializing_if = "Option::is_none")] | |
| 1133 | pub pusher: Option<String>, | |
| 1134 | #[serde(default)] | |
| 1135 | pub blocked: u32, | |
| 1136 | /// Who hears of it besides the repository's security watchers: its | |
| 1137 | /// workspace's owners and security managers, by username. | |
| 1138 | #[serde(default, skip_serializing_if = "Vec::is_empty")] | |
| 1139 | pub notify: Vec<String>, | |
| 1140 | } | |
| 1141 | ||
| 1142 | /// The most alert ids a summary names; counts say the rest. | |
| 1143 | pub const MAX_SUMMARY_IDS: usize = 200; | |
| 1144 | ||
| 1145 | /// Whether `severity` goes to the inbox at once; the rest wait for the | |
| 1146 | /// person's security digest. | |
| 1147 | pub fn at_once(severity: &str) -> bool { | |
| 1148 | matches!(severity, "critical" | "high") | |
| 1149 | } | |
| 1150 | ||
| 1151 | impl SecuritySummary { | |
| 1152 | /// How many alerts are open of this type after the scan. | |
| 1153 | pub fn open_total(&self) -> u32 { | |
| 1154 | self.open.critical + self.open.high + self.open.medium + self.open.low + self.open.unknown | |
| 1155 | } | |
| 1156 | ||
| 1157 | /// Whether what it tells of is told at once: anything critical or high, | |
| 1158 | /// or a push that was refused. | |
| 1159 | pub fn urgent(&self) -> bool { | |
| 1160 | self.pusher.is_some() || self.worst.as_deref().is_some_and(at_once) | |
| 1161 | } | |
| 1162 | } | |
| 1163 | ||
| 1164 | impl AlertType { | |
| 1165 | /// The summary event: `vulnerability_alert.summary`. | |
| 1166 | pub fn summary_event(self) -> &'static str { | |
| 1167 | match self { | |
| 1168 | AlertType::SecretScanning => "secret_scanning_alert.summary", | |
| 1169 | AlertType::CodeScanning => "code_scanning_alert.summary", | |
| 1170 | AlertType::Vulnerability => "vulnerability_alert.summary", | |
| 1171 | } | |
| 1172 | } | |
| 1173 | ||
| 1174 | /// What people call the alerts: "secrets", "code scanning alerts", | |
| 1175 | /// "vulnerabilities". | |
| 1176 | pub fn plural(self) -> &'static str { | |
| 1177 | match self { | |
| 1178 | AlertType::SecretScanning => "secrets", | |
| 1179 | AlertType::CodeScanning => "code scanning alerts", | |
| 1180 | AlertType::Vulnerability => "vulnerabilities", | |
| 1181 | } | |
| 1182 | } | |
| 1183 | ||
| 1184 | /// Where the alerts are listed under a repository's Security page. | |
| 1185 | pub fn page(self) -> &'static str { | |
| 1186 | match self { | |
| 1187 | AlertType::SecretScanning => "secret-scanning", | |
| 1188 | AlertType::CodeScanning => "code-scanning", | |
| 1189 | AlertType::Vulnerability => "vulnerabilities", | |
| 1190 | } | |
| 1191 | } | |
| 1192 | } | |
| 1193 | ||
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1194 | /// The security event types, as webhooks list them. |
| Security tells people the way they can live with. Each scan publishes one summary event per alert type (secret_scanning_alert.summary, code_scanning_alert.summary, vulnerability_alert.summary: what it opened, by severity, what it fixed, what is open now, the worst new alert and the newest one's title, with the owners and security managers to tell) beside the per-alert events webhooks keep, so the inbox holds one thread per repository and alert type that the next scan brings up to date instead of one item per finding, and the ids are capped at 200 while the counts are not. Critical and high alerts and a refused push reach the inbox at once (and toast like a mention, as the new security notification kind); medium and low wait for each person's security digest, one inbox item and email per workspace, daily or weekly as they choose in their notification settings, at once, or never; a scan that opened nothing refreshes the thread quietly ("2 of 3 fixed") and marks it done when nothing is left open; and security alerts are emailed by default. A repository can turn alerts for secrets found in its history off while push protection stays on. Reporting arrives: security advisories (G1TSA ids, drafts published and withdrawn by Admins and security managers, visible to anyone who can see the repository once published) and private vulnerability reporting (a repository setting; anyone who can see it may report, signed out with an email on a public one, five a day each, and the owners and security managers are told at once), each with its RPC methods, REST routes under /repos/{owner}/{name}/security/advisories and /security/reports, MCP actions, scopes and reference examples. | 1195 | pub const EVENT_TYPES: [&str; 20] = [ |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1196 | "secret_scanning_alert.created", |
| 1197 | "secret_scanning_alert.fixed", | |
| 1198 | "secret_scanning_alert.dismissed", | |
| 1199 | "secret_scanning_alert.reopened", | |
| Security tells people the way they can live with. Each scan publishes one summary event per alert type (secret_scanning_alert.summary, code_scanning_alert.summary, vulnerability_alert.summary: what it opened, by severity, what it fixed, what is open now, the worst new alert and the newest one's title, with the owners and security managers to tell) beside the per-alert events webhooks keep, so the inbox holds one thread per repository and alert type that the next scan brings up to date instead of one item per finding, and the ids are capped at 200 while the counts are not. Critical and high alerts and a refused push reach the inbox at once (and toast like a mention, as the new security notification kind); medium and low wait for each person's security digest, one inbox item and email per workspace, daily or weekly as they choose in their notification settings, at once, or never; a scan that opened nothing refreshes the thread quietly ("2 of 3 fixed") and marks it done when nothing is left open; and security alerts are emailed by default. A repository can turn alerts for secrets found in its history off while push protection stays on. Reporting arrives: security advisories (G1TSA ids, drafts published and withdrawn by Admins and security managers, visible to anyone who can see the repository once published) and private vulnerability reporting (a repository setting; anyone who can see it may report, signed out with an email on a public one, five a day each, and the owners and security managers are told at once), each with its RPC methods, REST routes under /repos/{owner}/{name}/security/advisories and /security/reports, MCP actions, scopes and reference examples. | 1200 | "secret_scanning_alert.summary", |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1201 | "secret_scanning.bypass_requested", |
| 1202 | "secret_scanning.bypass_reviewed", | |
| 1203 | "code_scanning_alert.created", | |
| 1204 | "code_scanning_alert.fixed", | |
| 1205 | "code_scanning_alert.dismissed", | |
| 1206 | "code_scanning_alert.reopened", | |
| Security tells people the way they can live with. Each scan publishes one summary event per alert type (secret_scanning_alert.summary, code_scanning_alert.summary, vulnerability_alert.summary: what it opened, by severity, what it fixed, what is open now, the worst new alert and the newest one's title, with the owners and security managers to tell) beside the per-alert events webhooks keep, so the inbox holds one thread per repository and alert type that the next scan brings up to date instead of one item per finding, and the ids are capped at 200 while the counts are not. Critical and high alerts and a refused push reach the inbox at once (and toast like a mention, as the new security notification kind); medium and low wait for each person's security digest, one inbox item and email per workspace, daily or weekly as they choose in their notification settings, at once, or never; a scan that opened nothing refreshes the thread quietly ("2 of 3 fixed") and marks it done when nothing is left open; and security alerts are emailed by default. A repository can turn alerts for secrets found in its history off while push protection stays on. Reporting arrives: security advisories (G1TSA ids, drafts published and withdrawn by Admins and security managers, visible to anyone who can see the repository once published) and private vulnerability reporting (a repository setting; anyone who can see it may report, signed out with an email on a public one, five a day each, and the owners and security managers are told at once), each with its RPC methods, REST routes under /repos/{owner}/{name}/security/advisories and /security/reports, MCP actions, scopes and reference examples. | 1207 | "code_scanning_alert.summary", |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1208 | "vulnerability_alert.created", |
| 1209 | "vulnerability_alert.fixed", | |
| 1210 | "vulnerability_alert.dismissed", | |
| 1211 | "vulnerability_alert.reopened", | |
| Security tells people the way they can live with. Each scan publishes one summary event per alert type (secret_scanning_alert.summary, code_scanning_alert.summary, vulnerability_alert.summary: what it opened, by severity, what it fixed, what is open now, the worst new alert and the newest one's title, with the owners and security managers to tell) beside the per-alert events webhooks keep, so the inbox holds one thread per repository and alert type that the next scan brings up to date instead of one item per finding, and the ids are capped at 200 while the counts are not. Critical and high alerts and a refused push reach the inbox at once (and toast like a mention, as the new security notification kind); medium and low wait for each person's security digest, one inbox item and email per workspace, daily or weekly as they choose in their notification settings, at once, or never; a scan that opened nothing refreshes the thread quietly ("2 of 3 fixed") and marks it done when nothing is left open; and security alerts are emailed by default. A repository can turn alerts for secrets found in its history off while push protection stays on. Reporting arrives: security advisories (G1TSA ids, drafts published and withdrawn by Admins and security managers, visible to anyone who can see the repository once published) and private vulnerability reporting (a repository setting; anyone who can see it may report, signed out with an email on a public one, five a day each, and the owners and security managers are told at once), each with its RPC methods, REST routes under /repos/{owner}/{name}/security/advisories and /security/reports, MCP actions, scopes and reference examples. | 1212 | "vulnerability_alert.summary", |
| 1213 | "security_advisory.published", | |
| 1214 | "security_advisory.withdrawn", | |
| 1215 | "security_advisory.reported", | |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1216 | ]; |
| 1217 | ||
| 1218 | /// The checks the suite reports on pull requests, as commit statuses: | |
| 1219 | /// require them in branch protection to gate merges. | |
| 1220 | pub const CODE_SCANNING_CHECK: &str = "Code scanning"; | |
| 1221 | pub const DEPENDENCY_REVIEW_CHECK: &str = "Dependency review"; | |
| 1222 | ||
| 1223 | /// The starter workflow "Set up code scanning" opens a pull request with. | |
| 1224 | pub const STARTER_WORKFLOW_PATH: &str = ".g1t/workflows/code-scanning.yml"; | |
| 1225 | ||
| 1226 | #[cfg(test)] | |
| 1227 | mod tests { | |
| 1228 | use super::*; | |
| 1229 | ||
| 1230 | #[test] | |
| 1231 | fn alert_ids_say_their_type() { | |
| 1232 | assert_eq!(AlertType::of_id("sec_1"), Some(AlertType::SecretScanning)); | |
| 1233 | assert_eq!(AlertType::of_id("cod_1"), Some(AlertType::CodeScanning)); | |
| 1234 | assert_eq!(AlertType::of_id("vul_1"), Some(AlertType::Vulnerability)); | |
| 1235 | assert_eq!(AlertType::of_id("x"), None); | |
| 1236 | for kind in AlertType::ALL { | |
| 1237 | assert_eq!(AlertType::parse(kind.as_str()), Some(kind)); | |
| 1238 | } | |
| 1239 | } | |
| 1240 | ||
| 1241 | #[test] | |
| 1242 | fn every_alert_type_has_its_four_events() { | |
| 1243 | for kind in AlertType::ALL { | |
| 1244 | for action in ["created", "fixed", "dismissed", "reopened"] { | |
| 1245 | assert!(EVENT_TYPES.contains(&format!("{}.{action}", kind.event_prefix()).as_str())); | |
| 1246 | } | |
| 1247 | } | |
| 1248 | } | |
| 1249 | ||
| 1250 | #[test] | |
| 1251 | fn a_bypass_for_a_real_secret_leaves_the_alert_open() { | |
| 1252 | assert_eq!(BypassReason::WillFixLater.dismissal(), None); | |
| 1253 | assert_eq!(BypassReason::UsedInTests.dismissal(), Some(DismissReason::UsedInTests)); | |
| 1254 | assert_eq!(BypassReason::parse("false_positive"), Some(BypassReason::FalsePositive)); | |
| 1255 | } | |
| 1256 | ||
| 1257 | #[test] | |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 1258 | fn the_refusal_says_how_to_start_the_plan() { |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1259 | let message = needs_activation(PaidFeature::CodeScanning, "acme"); |
| Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how. | 1260 | assert!(message.starts_with("Code scanning on private repositories comes with the g1t plan")); |
| Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar | 1261 | assert!(message.contains("/acme/-/billing") && message.contains("Public repositories have it free")); |
| 1262 | } | |
| 1263 | } |