Skip to content
1,263 linesCodeBlameRaw

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 bar1//! 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 bar14
15use serde::{Deserialize, Serialize};
16
17use crate::User;
18use crate::repos::RepoPath;
19use 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")]
25pub enum PaidFeature {
26 CustomPatterns,
27 ValidityChecks,
28 DelegatedBypass,
29 CodeScanning,
30 DependencyReview,
31 SecurityOverview,
32}
33
34impl 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 bar69pub 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 bar73 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")]
80pub enum AlertType {
81 SecretScanning,
82 CodeScanning,
83 Vulnerability,
84}
85
86impl 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")]
126pub 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
152fn yes() -> bool {
153 true
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar154}
155
156impl 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 bar166 }
167 }
168}
169
170/// A workspace's security settings.
171#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
172#[serde(rename_all = "camelCase")]
173pub 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)]
185pub struct SecuritySettingsArgs {
186 pub viewer: Option<User>,
187 pub repo: RepoPath,
188}
189
190#[derive(Clone, Debug, Serialize, Deserialize)]
191#[serde(rename_all = "camelCase")]
192pub 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)]
206pub 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)]
215pub struct WorkspaceSecuritySettingsArgs {
216 pub viewer: Option<User>,
217 pub workspace: String,
218}
219
220#[derive(Clone, Debug, Serialize, Deserialize)]
221#[serde(rename_all = "camelCase")]
222pub 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)]
230pub 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")]
241pub 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")]
254pub 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)]
282pub 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")]
291pub 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")]
301pub 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")]
323pub 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)]
332pub 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")]
345pub 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")]
362pub struct DryRun {
363 pub repos: Vec<DryRunRepo>,
364}
365
366#[derive(Clone, Debug, Default, Serialize, Deserialize)]
367#[serde(rename_all = "camelCase")]
368pub 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")]
378pub 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")]
387pub 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")]
398pub 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")]
410pub 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")]
422pub 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
431impl 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")]
467pub 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")]
480pub 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")]
492pub 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)]
519pub 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")]
527pub 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)]
545pub 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")]
556pub 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)]
566pub 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)]
580pub 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)]
594pub 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")]
606pub 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")]
617pub 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")]
630pub 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")]
674pub 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")]
700pub 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")]
719pub 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)]
733pub 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)]
742pub struct CodeScanningArgs {
743 pub viewer: Option<User>,
744 pub repo: RepoPath,
745}
746
747#[derive(Clone, Debug, Serialize, Deserialize)]
748#[serde(rename_all = "camelCase")]
749pub 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)]
761pub 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")]
769pub 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)]
780pub 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)]
795pub 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")]
803pub 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")]
815pub 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)]
838pub 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")]
847pub 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")]
859pub 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)]
877pub 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")]
884pub 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")]
892pub 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)]
902pub 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)]
910pub 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")]
919pub 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")]
932pub 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")]
951pub 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)]
966pub 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")]
976pub 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")]
988pub 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")]
998pub 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")]
1009pub 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")]
1029pub 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")]
1038pub 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")]
1055pub 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")]
1099pub 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.
1143pub 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.
1147pub fn at_once(severity: &str) -> bool {
1148 matches!(severity, "critical" | "high")
1149}
1150
1151impl 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
1164impl 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 bar1194/// 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.1195pub const EVENT_TYPES: [&str; 20] = [
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1196 "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 bar1201 "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 bar1208 "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 bar1216];
1217
1218/// The checks the suite reports on pull requests, as commit statuses:
1219/// require them in branch protection to gate merges.
1220pub const CODE_SCANNING_CHECK: &str = "Code scanning";
1221pub const DEPENDENCY_REVIEW_CHECK: &str = "Dependency review";
1222
1223/// The starter workflow "Set up code scanning" opens a pull request with.
1224pub const STARTER_WORKFLOW_PATH: &str = ".g1t/workflows/code-scanning.yml";
1225
1226#[cfg(test)]
1227mod 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 bar1259 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 bar1261 assert!(message.contains("/acme/-/billing") && message.contains("Public repositories have it free"));
1262 }
1263}