Skip to content
729 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 an issue 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 issue opened for g1t, when the upgrade needs code changes.
452 pub issue: Option<u32>,
453 /// Why it failed, when it did.
454 pub error: Option<String>,
455 /// RFC 3339.
456 pub updated_at: String,
457}
458
459/// The prefix every security update's branch starts with.
460pub const UPDATE_BRANCH_PREFIX: &str = "g1t/security/";
461
462/// The branch a security update is made on: `g1t/security/<package>-<version>`,
463/// with anything a branch name cannot hold (a scope's `@` and `/`) as `-`.
464pub fn update_branch(package: &str, version: &str) -> String {
465 let clean = |text: &str| -> String {
466 let mut out = String::new();
467 for c in text.chars() {
468 let c = if c.is_ascii_alphanumeric() || matches!(c, '.' | '_') { c } else { '-' };
469 if !(c == '-' && out.ends_with('-')) {
470 out.push(c);
471 }
472 }
473 out.trim_matches(['-', '.']).to_owned()
474 };
475 format!("{UPDATE_BRANCH_PREFIX}{}-{}", clean(package), clean(version))
476}
477
478/// One advisory against one package at the version a lockfile resolves.
479#[derive(Clone, Debug, Serialize, Deserialize)]
480#[serde(rename_all = "camelCase")]
481pub struct Vulnerability {
482 pub id: String,
483 pub repo_id: String,
484 /// `npm`, `crates.io`, `Go`, `PyPI`.
485 pub ecosystem: String,
486 pub package: String,
487 pub version: String,
488 /// The lockfile that resolves it.
489 pub manifest: String,
490 /// The id people know it by (its GHSA id when it has one).
491 pub advisory: String,
492 pub osv_id: String,
493 pub summary: String,
494 /// `critical`, `high`, `medium`, `low` or `unknown`.
495 pub severity: String,
496 pub fixed_version: Option<String>,
497 pub status: VulnStatus,
498 /// The issue opened to upgrade the package, when g1t was put on
499 /// it: the upgrade needs code changes, or predates security updates.
500 pub issue: Option<u32>,
501 /// RFC 3339.
502 pub found_at: String,
503 pub fixed_at: Option<String>,
504 /// Open, dismissed or fixed.
505 pub state: AlertState,
506 /// Who dismissed it, why, with what comment, and when.
507 #[serde(default)]
508 pub dismissed_by: Option<String>,
509 #[serde(default)]
510 pub dismissed_reason: Option<DismissReason>,
511 #[serde(default)]
512 pub dismissed_comment: Option<String>,
513 #[serde(default)]
514 pub dismissed_at: Option<String>,
515 /// The security update for its package, if g1t has started one.
516 #[serde(default)]
517 pub update: Option<SecurityUpdate>,
518}
519
520/// Open alerts by severity: vulnerabilities open and not dismissed, and
521/// secrets in the history that look real, as critical. A blocked secret
522/// never landed and a likely test value is no danger, so neither counts.
523#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
524pub struct SeverityCounts {
525 pub critical: u32,
526 pub high: u32,
527 pub medium: u32,
528 pub low: u32,
529 pub unknown: u32,
530}
531
532/// How a repository's history scan stands.
533#[derive(Clone, Debug, Default, Serialize, Deserialize)]
534#[serde(rename_all = "camelCase")]
535pub struct ScanState {
536 /// `pending`, `running`, `done` or `stopped` (over the workspace's limit).
537 pub history: String,
538 pub commits_scanned: u32,
539 /// RFC 3339.
540 pub history_finished_at: Option<String>,
541 pub dependencies_scanned_at: Option<String>,
542 /// Why the last dependency scan failed, if it did.
543 pub dependencies_error: Option<String>,
544 pub lockfiles: Vec<String>,
545}
546
547/// Secret alerts by where they stand.
548#[derive(Clone, Debug, Default, Serialize, Deserialize)]
549#[serde(rename_all = "camelCase")]
550pub struct SecretCounts {
551 /// In the history and looking real: rotate these.
552 pub open: u32,
553 /// Stopped at a push, so never landed, and looking real.
554 pub blocked: u32,
555 /// Open or blocked, but likely test values.
556 pub test_values: u32,
557 pub dismissed: u32,
558 pub fixed: u32,
559}
560
561/// One thing that happened to an alert, for its activity log.
562#[derive(Clone, Debug, Serialize, Deserialize)]
563#[serde(rename_all = "camelCase")]
564pub struct AlertActivity {
565 pub id: String,
566 /// The secret's or vulnerability's id.
567 pub alert_id: String,
568 /// `dismissed`, `reopened`, `update_requested`, `update_opened`,
569 /// `update_merged`, `update_closed`, `update_superseded`,
570 /// `update_needs_code` or `update_failed`.
571 pub action: String,
572 /// Who did it: a person's username, or `g1t`.
573 pub actor: Option<String>,
574 pub reason: Option<DismissReason>,
575 pub comment: Option<String>,
576 /// The pull request or issue it concerns.
577 pub number: Option<u32>,
578 /// RFC 3339.
579 pub at: String,
580}
581
582/// Version updates, and what the dependency update file asks of security
583/// updates: see [`crate::updates`].
584pub use crate::updates::{
585 UpdateAllow, UpdateGroup, UpdateIgnore, VersionUpdateEntry, VersionUpdatesState,
586};
587
588/// Everything the Security page shows for one repository.
589#[derive(Clone, Debug, Serialize, Deserialize)]
590#[serde(rename_all = "camelCase")]
591pub struct SecurityOverview {
592 pub repo_id: String,
593 /// Open alerts by severity; see [`SeverityCounts`].
594 pub counts: SeverityCounts,
595 pub secret_counts: SecretCounts,
596 pub secrets: Vec<SecretFinding>,
597 pub vulnerabilities: Vec<Vulnerability>,
598 /// What happened to the alerts, newest first.
599 pub activity: Vec<AlertActivity>,
600 pub scan: ScanState,
601 /// Security updates: whether g1t opens a pull request to upgrade each
602 /// vulnerable dependency that has a fix.
603 pub upkeep: bool,
604 pub version_updates: VersionUpdatesState,
605}
606
607/// `overview`: members of the workspace only. Returns
608/// `Outcome<SecurityOverview>`.
609#[derive(Debug, Serialize, Deserialize)]
610pub struct OverviewArgs {
611 pub repo: RepoPath,
612 pub viewer: Option<User>,
613}
614
615/// `dismiss`: closes an alert with a reason and an optional comment. A
616/// secret takes Admin on the repository (a dismissed secret is let through
617/// push protection, unless it was revoked); a vulnerable dependency takes
618/// Write. Returns `Outcome<AlertChange>`.
619#[derive(Debug, Serialize, Deserialize)]
620pub struct DismissArgs {
621 pub actor: User,
622 pub repo: RepoPath,
623 /// A secret's id (`sec_…`) or a vulnerability's (`vul_…`).
624 pub id: String,
625 pub reason: DismissReason,
626 #[serde(default)]
627 pub comment: String,
628}
629
630/// `reopen`: opens a dismissed alert again, with the same roles as
631/// `dismiss`. Returns `Outcome<AlertChange>`.
632#[derive(Debug, Serialize, Deserialize)]
633pub struct ReopenArgs {
634 pub actor: User,
635 pub repo: RepoPath,
636 pub id: String,
637}
638
639/// The alert `dismiss` or `reopen` changed, as it is now: one of the two.
640#[derive(Clone, Debug, Default, Serialize, Deserialize)]
641#[serde(rename_all = "camelCase")]
642pub struct AlertChange {
643 pub secret: Option<SecretFinding>,
644 pub vulnerability: Option<Vulnerability>,
645}
646
647/// `rescan`: scans the dependencies again now, and the history from the
648/// start. Members only. Returns `Outcome<ScanState>`.
649#[derive(Debug, Serialize, Deserialize)]
650pub struct RescanArgs {
651 pub actor: User,
652 pub repo: RepoPath,
653}
654
655/// `set_upkeep`: security updates on or off: whether g1t opens a pull
656/// request to upgrade each vulnerable dependency that has a fix. Maintain
657/// and up. Returns `Outcome<bool>`.
658#[derive(Debug, Serialize, Deserialize)]
659pub struct SetUpkeepArgs {
660 pub actor: User,
661 pub repo: RepoPath,
662 pub enabled: bool,
663}
664
665/// `workspace`: every repository of a workspace that has findings, for
666/// its members. Returns `Outcome<Vec<RepoSecurity>>`.
667#[derive(Debug, Serialize, Deserialize)]
668pub struct WorkspaceArgs {
669 pub workspace: String,
670 pub viewer: Option<User>,
671}
672
673#[derive(Clone, Debug, Serialize, Deserialize)]
674#[serde(rename_all = "camelCase")]
675pub struct RepoSecurity {
676 pub repo_id: String,
677 pub name: String,
678 pub counts: SeverityCounts,
679 pub secrets: u32,
680 pub vulnerabilities: u32,
681 pub upkeep: bool,
682 pub dependencies_scanned_at: Option<String>,
683}
684
685/// `bump` (runner): makes a security update in a sandbox. It clones the
686/// default branch (or `base`), raises `package` to `version` in each lockfile with the
687/// ecosystem's own tool (`npm`, `cargo`, `go`, `pip`), commits that as g1t
688/// (`g1t <g1t@users.noreply.g1t.sh>`) and pushes it to `branch`, which must
689/// start with [`UPDATE_BRANCH_PREFIX`]. The push is what tells the security
690/// service to open the pull request. Returns `Outcome<bool>`: whether the
691/// sandbox started.
692#[derive(Clone, Debug, Serialize, Deserialize)]
693#[serde(rename_all = "camelCase")]
694pub struct BumpArgs {
695 pub repo: RepoPath,
696 /// OSV's name for the ecosystem: `npm`, `crates.io`, `Go` or `PyPI`.
697 pub ecosystem: String,
698 pub package: String,
699 pub version: String,
700 /// The lockfiles that resolve a vulnerable version, from the root.
701 pub lockfiles: Vec<String>,
702 pub branch: String,
703 /// The commit's message.
704 pub message: String,
705 /// `version` for a version update, whose branch is named by the
706 /// dependency update file (any branch but the default one); a security
707 /// update when absent.
708 #[serde(default, skip_serializing_if = "Option::is_none")]
709 pub kind: Option<String>,
710 /// Several packages raised in one commit, for a grouped update. When
711 /// given, `package` and `version` are its first.
712 #[serde(default, skip_serializing_if = "Vec::is_empty")]
713 pub packages: Vec<crate::updates::BumpPackage>,
714 /// How a manifest's requirement changes (`versioning-strategy`):
715 /// `increase` (the default), `increase-if-necessary`, `widen` or
716 /// `lockfile-only`.
717 #[serde(default, skip_serializing_if = "Option::is_none")]
718 pub strategy: Option<String>,
719 /// Replace the branch if it is there already: a rebase or a recreate.
720 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
721 pub force: bool,
722 /// Private registries the tools may read, with their credentials.
723 #[serde(default, skip_serializing_if = "Vec::is_empty")]
724 pub registries: Vec<crate::updates::BumpRegistry>,
725 /// The branch to start from, which its pull request merges into
726 /// (`target-branch`): the default branch when absent.
727 #[serde(default, skip_serializing_if = "Option::is_none")]
728 pub base: Option<String>,
729}