Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

733 linesCodeBlameRaw
1//! The security service: secrets found in what is pushed and in history,
2//! vulnerable dependencies, and the upgrades g1t opens for them.
3//!
4//! The repos service reads git for it (`scan_history`, `find_lockfiles`)
5//! and asks it, during a push, which secrets have been allowed
6//! (`push_blocked`). Members of a workspace see and act on its findings;
7//! nobody else does, whether or not the repository is public.
8
9use serde::{Deserialize, Serialize};
10
11use crate::User;
12use crate::repos::RepoPath;
13
14/// Where a secret stands.
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "lowercase")]
17pub enum SecretStatus {
18 /// In the repository's history: it has to be rotated, then resolved.
19 Open,
20 /// A push carrying it was refused, so it never landed.
21 Blocked,
22 /// Someone said it is not a real secret; pushes carrying it go through.
23 Allowed,
24 /// Someone rotated or removed it.
25 Resolved,
26}
27
28impl SecretStatus {
29 pub fn as_str(self) -> &'static str {
30 match self {
31 SecretStatus::Open => "open",
32 SecretStatus::Blocked => "blocked",
33 SecretStatus::Allowed => "allowed",
34 SecretStatus::Resolved => "resolved",
35 }
36 }
37
38 pub fn parse(text: &str) -> Option<SecretStatus> {
39 Some(match text {
40 "open" => SecretStatus::Open,
41 "blocked" => SecretStatus::Blocked,
42 "allowed" => SecretStatus::Allowed,
43 "resolved" => SecretStatus::Resolved,
44 _ => return None,
45 })
46 }
47}
48
49/// Why a person dismissed an alert. The first four are for secrets, the
50/// rest for vulnerable dependencies; see [`DismissReason::for_secrets`].
51#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
52#[serde(rename_all = "snake_case")]
53pub enum DismissReason {
54 /// Not a secret at all.
55 FalsePositive,
56 /// A value made for tests or examples.
57 UsedInTests,
58 /// It was real, and has been revoked or rotated: the alert is fixed.
59 Revoked,
60 /// Real, and accepted as it is.
61 WontFix,
62 /// Someone is already upgrading it.
63 FixStarted,
64 /// Nobody can get to it now.
65 NoBandwidth,
66 /// The vulnerability does not matter for how this project uses it.
67 TolerableRisk,
68 /// The advisory is wrong about this package or version.
69 Inaccurate,
70 /// The vulnerable code is never called.
71 NotUsed,
72}
73
74impl DismissReason {
75 pub const ALL: [DismissReason; 9] = [
76 DismissReason::FalsePositive,
77 DismissReason::UsedInTests,
78 DismissReason::Revoked,
79 DismissReason::WontFix,
80 DismissReason::FixStarted,
81 DismissReason::NoBandwidth,
82 DismissReason::TolerableRisk,
83 DismissReason::Inaccurate,
84 DismissReason::NotUsed,
85 ];
86
87 pub fn as_str(self) -> &'static str {
88 match self {
89 DismissReason::FalsePositive => "false_positive",
90 DismissReason::UsedInTests => "used_in_tests",
91 DismissReason::Revoked => "revoked",
92 DismissReason::WontFix => "wont_fix",
93 DismissReason::FixStarted => "fix_started",
94 DismissReason::NoBandwidth => "no_bandwidth",
95 DismissReason::TolerableRisk => "tolerable_risk",
96 DismissReason::Inaccurate => "inaccurate",
97 DismissReason::NotUsed => "not_used",
98 }
99 }
100
101 pub fn parse(text: &str) -> Option<DismissReason> {
102 DismissReason::ALL.into_iter().find(|reason| reason.as_str() == text)
103 }
104
105 /// For people.
106 pub fn label(self) -> &'static str {
107 match self {
108 DismissReason::FalsePositive => "False positive",
109 DismissReason::UsedInTests => "Used in tests",
110 DismissReason::Revoked => "Revoked",
111 DismissReason::WontFix => "Won't fix",
112 DismissReason::FixStarted => "A fix has already been started",
113 DismissReason::NoBandwidth => "No bandwidth to fix this",
114 DismissReason::TolerableRisk => "Risk is tolerable to this project",
115 DismissReason::Inaccurate => "This alert is inaccurate or incorrect",
116 DismissReason::NotUsed => "Vulnerable code is not actually used",
117 }
118 }
119
120 /// Whether it closes a secret alert (the rest close dependency alerts).
121 pub fn for_secrets(self) -> bool {
122 matches!(
123 self,
124 DismissReason::FalsePositive | DismissReason::UsedInTests | DismissReason::Revoked | DismissReason::WontFix
125 )
126 }
127
128 /// The status a secret takes: revoked means fixed (`resolved`); the
129 /// others say it is no danger, so pushes carrying it go through
130 /// (`allowed`).
131 pub fn secret_status(self) -> SecretStatus {
132 if self == DismissReason::Revoked { SecretStatus::Resolved } else { SecretStatus::Allowed }
133 }
134}
135
136/// Where an alert stands, as the Security page's filters and the API put it.
137#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
138#[serde(rename_all = "lowercase")]
139pub enum AlertState {
140 /// Needs someone: a secret open or blocked, a dependency still vulnerable.
141 Open,
142 /// Someone said why it can stay.
143 Dismissed,
144 /// A secret revoked, or a dependency no longer vulnerable.
145 Fixed,
146}
147
148impl AlertState {
149 pub fn as_str(self) -> &'static str {
150 match self {
151 AlertState::Open => "open",
152 AlertState::Dismissed => "dismissed",
153 AlertState::Fixed => "fixed",
154 }
155 }
156
157 pub fn parse(text: &str) -> Option<AlertState> {
158 Some(match text {
159 "open" => AlertState::Open,
160 "dismissed" => AlertState::Dismissed,
161 "fixed" => AlertState::Fixed,
162 _ => return None,
163 })
164 }
165}
166
167/// A secret found in a repository. The secret itself is never kept: only
168/// a fingerprint, to know it again, and a preview a person recognises.
169#[derive(Clone, Debug, Serialize, Deserialize)]
170#[serde(rename_all = "camelCase")]
171pub struct SecretFinding {
172 pub id: String,
173 pub repo_id: String,
174 /// `aws_access_key`, `github_token`, …
175 pub kind: String,
176 /// "an AWS access key".
177 pub label: String,
178 pub path: String,
179 pub line: u32,
180 pub commit: String,
181 pub preview: String,
182 pub status: SecretStatus,
183 /// `push` or `history`.
184 pub source: String,
185 /// Who pushed it, for a push.
186 pub found_by: Option<String>,
187 /// RFC 3339.
188 pub found_at: String,
189 /// Who dismissed it (allowed or resolved it).
190 pub decided_by: Option<String>,
191 /// The comment given when it was dismissed.
192 pub reason: Option<String>,
193 pub decided_at: Option<String>,
194 /// Why it was dismissed. Absent on open alerts, and on alerts decided
195 /// before reasons were recorded.
196 #[serde(default)]
197 pub dismissed_reason: Option<DismissReason>,
198 /// Why the value looks made for tests or documentation (a documented
199 /// example key, a counting or repeating value), when it does. Such an
200 /// alert never stops a push and is never counted as critical.
201 #[serde(default)]
202 pub test_value: Option<String>,
203 /// Open, dismissed or fixed.
204 pub state: AlertState,
205 /// What its issuer said when last asked: `active`, `inactive`,
206 /// `unknown` or `unsupported`; absent when never asked.
207 #[serde(default)]
208 pub validity: Option<String>,
209 #[serde(default)]
210 pub validity_checked_at: Option<String>,
211 /// How it got past push protection, when someone bypassed it.
212 #[serde(default)]
213 pub bypass: Option<crate::security_suite::Bypass>,
214 /// The custom pattern that found it, for a `custom_pattern` finding.
215 #[serde(default)]
216 pub pattern_id: Option<String>,
217 #[serde(default)]
218 pub pattern_name: Option<String>,
219 /// How many places it was found in.
220 #[serde(default)]
221 pub locations: u32,
222}
223
224impl SecretStatus {
225 /// The alert state a secret in this status is in.
226 pub fn state(self) -> AlertState {
227 match self {
228 SecretStatus::Open | SecretStatus::Blocked => AlertState::Open,
229 SecretStatus::Allowed => AlertState::Dismissed,
230 SecretStatus::Resolved => AlertState::Fixed,
231 }
232 }
233}
234
235/// A secret as the repos service finds it, before it is stored.
236#[derive(Clone, Debug, Serialize, Deserialize)]
237#[serde(rename_all = "camelCase")]
238pub struct NewSecret {
239 pub fingerprint: String,
240 pub kind: String,
241 pub path: String,
242 pub line: u32,
243 pub commit: String,
244 pub preview: String,
245 /// Why it looks like a test value, from `g1t_scan::secrets::test_value`.
246 /// Such a secret is recorded but never stops a push.
247 #[serde(default, skip_serializing_if = "Option::is_none")]
248 pub test_value: Option<String>,
249 /// For a custom pattern's finding (`kind` `custom_pattern`): which
250 /// pattern, and its name.
251 #[serde(default, skip_serializing_if = "Option::is_none")]
252 pub pattern_id: Option<String>,
253 #[serde(default, skip_serializing_if = "Option::is_none")]
254 pub pattern_name: Option<String>,
255}
256
257/// `push_blocked`: the secrets a push would add. Those allowed before are
258/// returned; the rest are recorded as blocked. Called by the repos service.
259/// Returns `PushVerdict`.
260#[derive(Debug, Serialize, Deserialize)]
261#[serde(rename_all = "camelCase")]
262pub struct PushBlockedArgs {
263 /// The repository the findings belong to: for a pull request's fork,
264 /// the repository it was made from.
265 pub repo_id: String,
266 pub path: RepoPath,
267 pub pusher: Option<String>,
268 pub secrets: Vec<NewSecret>,
269 /// Whether the repository is private, as repos read it.
270 #[serde(default, skip_serializing_if = "Option::is_none")]
271 pub private: Option<bool>,
272}
273
274#[derive(Debug, Default, Serialize, Deserialize)]
275#[serde(rename_all = "camelCase")]
276pub struct PushVerdict {
277 /// Fingerprints that were allowed and so do not stop the push.
278 pub allowed: Vec<String>,
279 /// The finding recorded for each fingerprint that stops it.
280 pub ids: Vec<(String, String)>,
281}
282
283/// `scan_history` (repos): looks for secrets in a page of the default
284/// branch's history, newest first, each commit against its first parent.
285/// Returns `HistoryPage`.
286#[derive(Debug, Serialize, Deserialize)]
287#[serde(rename_all = "camelCase")]
288pub struct ScanHistoryArgs {
289 pub repo_id: String,
290 /// Where the last page stopped; `from`, or the head of the default
291 /// branch, when absent.
292 #[serde(default)]
293 pub after: Option<String>,
294 pub limit: u32,
295 /// For a pushed range: its newest commit, on whichever branch.
296 #[serde(default, skip_serializing_if = "Option::is_none")]
297 pub from: Option<String>,
298 /// For a pushed range: where the branch was before, which is not
299 /// scanned. The page ends there, with no next.
300 #[serde(default, skip_serializing_if = "Option::is_none")]
301 pub until: Option<String>,
302 /// Custom patterns to look for too.
303 #[serde(default, skip_serializing_if = "Vec::is_empty")]
304 pub patterns: Vec<crate::security_suite::PatternSpec>,
305}
306
307#[derive(Debug, Default, Serialize, Deserialize)]
308#[serde(rename_all = "camelCase")]
309pub struct HistoryPage {
310 pub secrets: Vec<NewSecret>,
311 pub commits: u32,
312 /// Where the next page starts; none when the history is done.
313 pub next: Option<String>,
314 /// How many objects were read from the store: what the scan cost.
315 pub reads: u32,
316}
317
318/// `find_lockfiles` (repos): the lockfiles on the default branch.
319/// Returns `Lockfiles`.
320#[derive(Debug, Serialize, Deserialize)]
321#[serde(rename_all = "camelCase")]
322pub struct FindLockfilesArgs {
323 pub repo_id: String,
324 /// A commit, branch or tag to read them at; the default branch when
325 /// absent.
326 #[serde(default, skip_serializing_if = "Option::is_none")]
327 pub git_ref: Option<String>,
328}
329
330#[derive(Debug, Default, Serialize, Deserialize)]
331#[serde(rename_all = "camelCase")]
332pub struct Lockfiles {
333 /// The commit they were read at; none for an empty repository.
334 pub commit: Option<String>,
335 pub files: Vec<LockfileText>,
336}
337
338#[derive(Debug, Serialize, Deserialize)]
339pub struct LockfileText {
340 pub path: String,
341 pub text: String,
342}
343
344/// Where a vulnerable dependency stands.
345#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
346#[serde(rename_all = "lowercase")]
347pub enum VulnStatus {
348 Open,
349 /// The version in use is no longer affected.
350 Fixed,
351 /// Someone said why it can stay. Found again, it stays dismissed until
352 /// someone reopens it.
353 Dismissed,
354}
355
356impl VulnStatus {
357 pub fn as_str(self) -> &'static str {
358 match self {
359 VulnStatus::Open => "open",
360 VulnStatus::Fixed => "fixed",
361 VulnStatus::Dismissed => "dismissed",
362 }
363 }
364
365 pub fn parse(text: &str) -> Option<VulnStatus> {
366 Some(match text {
367 "open" => VulnStatus::Open,
368 "fixed" => VulnStatus::Fixed,
369 "dismissed" => VulnStatus::Dismissed,
370 _ => return None,
371 })
372 }
373
374 pub fn state(self) -> AlertState {
375 match self {
376 VulnStatus::Open => AlertState::Open,
377 VulnStatus::Fixed => AlertState::Fixed,
378 VulnStatus::Dismissed => AlertState::Dismissed,
379 }
380 }
381}
382
383/// Where the security update for a vulnerable package stands: the pull
384/// request g1t opens itself to upgrade it, on the branch
385/// `g1t/security/<package>-<version>`.
386#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
387#[serde(rename_all = "snake_case")]
388pub enum UpdateState {
389 /// A sandbox is making the change.
390 Requested,
391 /// Its pull request is open, going through the required checks.
392 Open,
393 Merged,
394 /// Closed without merging, by a person.
395 Closed,
396 /// A newer security update replaced it, or the package is no longer
397 /// vulnerable; g1t closed it.
398 Superseded,
399 /// An earlier update g1t opened a work item for, when the version could
400 /// not be raised without changing code. New updates no longer reach it:
401 /// they fail with why, or their pull request stays open.
402 NeedsCode,
403 /// It could not be made; why is in `error`.
404 Failed,
405}
406
407impl UpdateState {
408 pub const ALL: [UpdateState; 7] = [
409 UpdateState::Requested,
410 UpdateState::Open,
411 UpdateState::Merged,
412 UpdateState::Closed,
413 UpdateState::Superseded,
414 UpdateState::NeedsCode,
415 UpdateState::Failed,
416 ];
417
418 pub fn as_str(self) -> &'static str {
419 match self {
420 UpdateState::Requested => "requested",
421 UpdateState::Open => "open",
422 UpdateState::Merged => "merged",
423 UpdateState::Closed => "closed",
424 UpdateState::Superseded => "superseded",
425 UpdateState::NeedsCode => "needs_code",
426 UpdateState::Failed => "failed",
427 }
428 }
429
430 pub fn parse(text: &str) -> Option<UpdateState> {
431 UpdateState::ALL.into_iter().find(|state| state.as_str() == text)
432 }
433
434 /// Whether g1t is still working on it.
435 pub fn in_progress(self) -> bool {
436 matches!(self, UpdateState::Requested | UpdateState::Open | UpdateState::NeedsCode)
437 }
438}
439
440/// The security update for one package.
441#[derive(Clone, Debug, Serialize, Deserialize)]
442#[serde(rename_all = "camelCase")]
443pub struct SecurityUpdate {
444 pub state: UpdateState,
445 /// The version it upgrades to.
446 pub target: String,
447 /// `g1t/security/<package>-<version>`.
448 pub branch: Option<String>,
449 /// The pull request g1t opened.
450 pub pull: Option<u32>,
451 /// The work item opened for g1t, by number in the workspace, when the
452 /// upgrade needs code changes.
453 pub item: Option<u32>,
454 /// Why it failed, when it did.
455 pub error: Option<String>,
456 /// RFC 3339.
457 pub updated_at: String,
458}
459
460/// The prefix every security update's branch starts with.
461pub const UPDATE_BRANCH_PREFIX: &str = "g1t/security/";
462
463/// The branch a security update is made on: `g1t/security/<package>-<version>`,
464/// with anything a branch name cannot hold (a scope's `@` and `/`) as `-`.
465pub fn update_branch(package: &str, version: &str) -> String {
466 let clean = |text: &str| -> String {
467 let mut out = String::new();
468 for c in text.chars() {
469 let c = if c.is_ascii_alphanumeric() || matches!(c, '.' | '_') { c } else { '-' };
470 if !(c == '-' && out.ends_with('-')) {
471 out.push(c);
472 }
473 }
474 out.trim_matches(['-', '.']).to_owned()
475 };
476 format!("{UPDATE_BRANCH_PREFIX}{}-{}", clean(package), clean(version))
477}
478
479/// One advisory against one package at the version a lockfile resolves.
480#[derive(Clone, Debug, Serialize, Deserialize)]
481#[serde(rename_all = "camelCase")]
482pub struct Vulnerability {
483 pub id: String,
484 pub repo_id: String,
485 /// `npm`, `crates.io`, `Go`, `PyPI`.
486 pub ecosystem: String,
487 pub package: String,
488 pub version: String,
489 /// The lockfile that resolves it.
490 pub manifest: String,
491 /// The id people know it by (its GHSA id when it has one).
492 pub advisory: String,
493 pub osv_id: String,
494 pub summary: String,
495 /// `critical`, `high`, `medium`, `low` or `unknown`.
496 pub severity: String,
497 pub fixed_version: Option<String>,
498 pub status: VulnStatus,
499 /// The work item opened to upgrade the package, by number in the
500 /// workspace, when g1t was put on it: the upgrade needs code changes,
501 /// or predates security updates.
502 pub item: Option<u32>,
503 /// RFC 3339.
504 pub found_at: String,
505 pub fixed_at: Option<String>,
506 /// Open, dismissed or fixed.
507 pub state: AlertState,
508 /// Who dismissed it, why, with what comment, and when.
509 #[serde(default)]
510 pub dismissed_by: Option<String>,
511 #[serde(default)]
512 pub dismissed_reason: Option<DismissReason>,
513 #[serde(default)]
514 pub dismissed_comment: Option<String>,
515 #[serde(default)]
516 pub dismissed_at: Option<String>,
517 /// The security update for its package, if g1t has started one.
518 #[serde(default)]
519 pub update: Option<SecurityUpdate>,
520}
521
522/// Open alerts by severity: vulnerabilities open and not dismissed, and
523/// secrets in the history that look real, as critical. A blocked secret
524/// never landed and a likely test value is no danger, so neither counts.
525#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
526pub struct SeverityCounts {
527 pub critical: u32,
528 pub high: u32,
529 pub medium: u32,
530 pub low: u32,
531 pub unknown: u32,
532}
533
534/// How a repository's history scan stands.
535#[derive(Clone, Debug, Default, Serialize, Deserialize)]
536#[serde(rename_all = "camelCase")]
537pub struct ScanState {
538 /// `pending`, `running`, `done` or `stopped` (over the workspace's limit).
539 pub history: String,
540 pub commits_scanned: u32,
541 /// RFC 3339.
542 pub history_finished_at: Option<String>,
543 pub dependencies_scanned_at: Option<String>,
544 /// Why the last dependency scan failed, if it did.
545 pub dependencies_error: Option<String>,
546 pub lockfiles: Vec<String>,
547}
548
549/// Secret alerts by where they stand.
550#[derive(Clone, Debug, Default, Serialize, Deserialize)]
551#[serde(rename_all = "camelCase")]
552pub struct SecretCounts {
553 /// In the history and looking real: rotate these.
554 pub open: u32,
555 /// Stopped at a push, so never landed, and looking real.
556 pub blocked: u32,
557 /// Open or blocked, but likely test values.
558 pub test_values: u32,
559 pub dismissed: u32,
560 pub fixed: u32,
561}
562
563/// One thing that happened to an alert, for its activity log.
564#[derive(Clone, Debug, Serialize, Deserialize)]
565#[serde(rename_all = "camelCase")]
566pub struct AlertActivity {
567 pub id: String,
568 /// The secret's or vulnerability's id.
569 pub alert_id: String,
570 /// `dismissed`, `reopened`, `update_requested`, `update_opened`,
571 /// `update_merged`, `update_closed`, `update_superseded`,
572 /// `update_needs_code` or `update_failed`.
573 pub action: String,
574 /// Who did it: a person's username, or `g1t`.
575 pub actor: Option<String>,
576 pub reason: Option<DismissReason>,
577 pub comment: Option<String>,
578 /// The pull request or work item it concerns.
579 pub number: Option<u32>,
580 /// RFC 3339.
581 pub at: String,
582}
583
584/// Version updates, and what the dependency update file asks of security
585/// updates: see [`crate::updates`].
586pub use crate::updates::{
587 UpdateAllow, UpdateGroup, UpdateIgnore, VersionUpdateEntry, VersionUpdatesState,
588};
589
590/// Everything the Security page shows for one repository.
591#[derive(Clone, Debug, Serialize, Deserialize)]
592#[serde(rename_all = "camelCase")]
593pub struct SecurityOverview {
594 pub repo_id: String,
595 /// Open alerts by severity; see [`SeverityCounts`].
596 pub counts: SeverityCounts,
597 pub secret_counts: SecretCounts,
598 pub secrets: Vec<SecretFinding>,
599 pub vulnerabilities: Vec<Vulnerability>,
600 /// What happened to the alerts, newest first.
601 pub activity: Vec<AlertActivity>,
602 pub scan: ScanState,
603 /// Security updates: whether g1t opens a pull request to upgrade each
604 /// vulnerable dependency that has a fix.
605 pub upkeep: bool,
606 pub version_updates: VersionUpdatesState,
607}
608
609/// `overview`: members of the workspace only. Returns
610/// `Outcome<SecurityOverview>`.
611/// Also used by `push_summary`, returning `Outcome<SeverityCounts>` for
612/// open vulnerabilities from the latest scan, without starting a scan.
613#[derive(Debug, Serialize, Deserialize)]
614pub struct OverviewArgs {
615 pub repo: RepoPath,
616 pub viewer: Option<User>,
617}
618
619/// `dismiss`: closes an alert with a reason and an optional comment. A
620/// secret takes Admin on the repository (a dismissed secret is let through
621/// push protection, unless it was revoked); a vulnerable dependency takes
622/// Write. Returns `Outcome<AlertChange>`.
623#[derive(Debug, Serialize, Deserialize)]
624pub struct DismissArgs {
625 pub actor: User,
626 pub repo: RepoPath,
627 /// A secret's id (`sec_…`) or a vulnerability's (`vul_…`).
628 pub id: String,
629 pub reason: DismissReason,
630 #[serde(default)]
631 pub comment: String,
632}
633
634/// `reopen`: opens a dismissed alert again, with the same roles as
635/// `dismiss`. Returns `Outcome<AlertChange>`.
636#[derive(Debug, Serialize, Deserialize)]
637pub struct ReopenArgs {
638 pub actor: User,
639 pub repo: RepoPath,
640 pub id: String,
641}
642
643/// The alert `dismiss` or `reopen` changed, as it is now: one of the two.
644#[derive(Clone, Debug, Default, Serialize, Deserialize)]
645#[serde(rename_all = "camelCase")]
646pub struct AlertChange {
647 pub secret: Option<SecretFinding>,
648 pub vulnerability: Option<Vulnerability>,
649}
650
651/// `rescan`: scans the dependencies again now, and the history from the
652/// start. Members only. Returns `Outcome<ScanState>`.
653#[derive(Debug, Serialize, Deserialize)]
654pub struct RescanArgs {
655 pub actor: User,
656 pub repo: RepoPath,
657}
658
659/// `set_upkeep`: security updates on or off: whether g1t opens a pull
660/// request to upgrade each vulnerable dependency that has a fix. Maintain
661/// and up. Returns `Outcome<bool>`.
662#[derive(Debug, Serialize, Deserialize)]
663pub struct SetUpkeepArgs {
664 pub actor: User,
665 pub repo: RepoPath,
666 pub enabled: bool,
667}
668
669/// `workspace`: every repository of a workspace that has findings, for
670/// its members. Returns `Outcome<Vec<RepoSecurity>>`.
671#[derive(Debug, Serialize, Deserialize)]
672pub struct WorkspaceArgs {
673 pub workspace: String,
674 pub viewer: Option<User>,
675}
676
677#[derive(Clone, Debug, Serialize, Deserialize)]
678#[serde(rename_all = "camelCase")]
679pub struct RepoSecurity {
680 pub repo_id: String,
681 pub name: String,
682 pub counts: SeverityCounts,
683 pub secrets: u32,
684 pub vulnerabilities: u32,
685 pub upkeep: bool,
686 pub dependencies_scanned_at: Option<String>,
687}
688
689/// `bump` (runner): makes a security update in a sandbox. It clones the
690/// default branch (or `base`), raises `package` to `version` in each lockfile with the
691/// ecosystem's own tool (`npm`, `cargo`, `go`, `pip`), commits that as g1t
692/// (`g1t <g1t@users.noreply.g1t.sh>`) and pushes it to `branch`, which must
693/// start with [`UPDATE_BRANCH_PREFIX`]. The push is what tells the security
694/// service to open the pull request. Returns `Outcome<bool>`: whether the
695/// sandbox started.
696#[derive(Clone, Debug, Serialize, Deserialize)]
697#[serde(rename_all = "camelCase")]
698pub struct BumpArgs {
699 pub repo: RepoPath,
700 /// OSV's name for the ecosystem: `npm`, `crates.io`, `Go` or `PyPI`.
701 pub ecosystem: String,
702 pub package: String,
703 pub version: String,
704 /// The lockfiles that resolve a vulnerable version, from the root.
705 pub lockfiles: Vec<String>,
706 pub branch: String,
707 /// The commit's message.
708 pub message: String,
709 /// `version` for a version update, whose branch is named by the
710 /// dependency update file (any branch but the default one); a security
711 /// update when absent.
712 #[serde(default, skip_serializing_if = "Option::is_none")]
713 pub kind: Option<String>,
714 /// Several packages raised in one commit, for a grouped update. When
715 /// given, `package` and `version` are its first.
716 #[serde(default, skip_serializing_if = "Vec::is_empty")]
717 pub packages: Vec<crate::updates::BumpPackage>,
718 /// How a manifest's requirement changes (`versioning-strategy`):
719 /// `increase` (the default), `increase-if-necessary`, `widen` or
720 /// `lockfile-only`.
721 #[serde(default, skip_serializing_if = "Option::is_none")]
722 pub strategy: Option<String>,
723 /// Replace the branch if it is there already: a rebase or a recreate.
724 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
725 pub force: bool,
726 /// Private registries the tools may read, with their credentials.
727 #[serde(default, skip_serializing_if = "Vec::is_empty")]
728 pub registries: Vec<crate::updates::BumpRegistry>,
729 /// The branch to start from, which its pull request merges into
730 /// (`target-branch`): the default branch when absent.
731 #[serde(default, skip_serializing_if = "Option::is_none")]
732 pub base: Option<String>,
733}