flagon-io/g1t

public

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

g1t/crates/contracts/src/security.rs

713 lines24,455 bytesCodeBlame

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.

Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API1//! 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
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily49/// 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
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API167/// 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,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily189 /// Who dismissed it (allowed or resolved it).
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API190 pub decided_by: Option<String>,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily191 /// The comment given when it was dismissed.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API192 pub reason: Option<String>,
193 pub decided_at: Option<String>,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily194 /// 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}
206
207impl SecretStatus {
208 /// The alert state a secret in this status is in.
209 pub fn state(self) -> AlertState {
210 match self {
211 SecretStatus::Open | SecretStatus::Blocked => AlertState::Open,
212 SecretStatus::Allowed => AlertState::Dismissed,
213 SecretStatus::Resolved => AlertState::Fixed,
214 }
215 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API216}
217
218/// A secret as the repos service finds it, before it is stored.
219#[derive(Clone, Debug, Serialize, Deserialize)]
220#[serde(rename_all = "camelCase")]
221pub struct NewSecret {
222 pub fingerprint: String,
223 pub kind: String,
224 pub path: String,
225 pub line: u32,
226 pub commit: String,
227 pub preview: String,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily228 /// Why it looks like a test value, from `g1t_scan::secrets::test_value`.
229 /// Such a secret is recorded but never stops a push.
230 #[serde(default, skip_serializing_if = "Option::is_none")]
231 pub test_value: Option<String>,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API232}
233
234/// `push_blocked`: the secrets a push would add. Those allowed before are
235/// returned; the rest are recorded as blocked. Called by the repos service.
236/// Returns `PushVerdict`.
237#[derive(Debug, Serialize, Deserialize)]
238#[serde(rename_all = "camelCase")]
239pub struct PushBlockedArgs {
240 /// The repository the findings belong to: for a pull request's fork,
241 /// the repository it was made from.
242 pub repo_id: String,
243 pub path: RepoPath,
244 pub pusher: Option<String>,
245 pub secrets: Vec<NewSecret>,
246}
247
248#[derive(Debug, Default, Serialize, Deserialize)]
249#[serde(rename_all = "camelCase")]
250pub struct PushVerdict {
251 /// Fingerprints that were allowed and so do not stop the push.
252 pub allowed: Vec<String>,
253 /// The finding recorded for each fingerprint that stops it.
254 pub ids: Vec<(String, String)>,
255}
256
257/// `scan_history` (repos): looks for secrets in a page of the default
258/// branch's history, newest first, each commit against its first parent.
259/// Returns `HistoryPage`.
260#[derive(Debug, Serialize, Deserialize)]
261#[serde(rename_all = "camelCase")]
262pub struct ScanHistoryArgs {
263 pub repo_id: String,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily264 /// Where the last page stopped; `from`, or the head of the default
265 /// branch, when absent.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API266 #[serde(default)]
267 pub after: Option<String>,
268 pub limit: u32,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily269 /// For a pushed range: its newest commit, on whichever branch.
270 #[serde(default, skip_serializing_if = "Option::is_none")]
271 pub from: Option<String>,
272 /// For a pushed range: where the branch was before, which is not
273 /// scanned. The page ends there, with no next.
274 #[serde(default, skip_serializing_if = "Option::is_none")]
275 pub until: Option<String>,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API276}
277
278#[derive(Debug, Default, Serialize, Deserialize)]
279#[serde(rename_all = "camelCase")]
280pub struct HistoryPage {
281 pub secrets: Vec<NewSecret>,
282 pub commits: u32,
283 /// Where the next page starts; none when the history is done.
284 pub next: Option<String>,
285 /// How many objects were read from the store: what the scan cost.
286 pub reads: u32,
287}
288
289/// `find_lockfiles` (repos): the lockfiles on the default branch.
290/// Returns `Lockfiles`.
291#[derive(Debug, Serialize, Deserialize)]
292#[serde(rename_all = "camelCase")]
293pub struct FindLockfilesArgs {
294 pub repo_id: String,
295}
296
297#[derive(Debug, Default, Serialize, Deserialize)]
298#[serde(rename_all = "camelCase")]
299pub struct Lockfiles {
300 /// The commit they were read at; none for an empty repository.
301 pub commit: Option<String>,
302 pub files: Vec<LockfileText>,
303}
304
305#[derive(Debug, Serialize, Deserialize)]
306pub struct LockfileText {
307 pub path: String,
308 pub text: String,
309}
310
311/// Where a vulnerable dependency stands.
312#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
313#[serde(rename_all = "lowercase")]
314pub enum VulnStatus {
315 Open,
316 /// The version in use is no longer affected.
317 Fixed,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily318 /// Someone said why it can stay. Found again, it stays dismissed until
319 /// someone reopens it.
320 Dismissed,
321}
322
323impl VulnStatus {
324 pub fn as_str(self) -> &'static str {
325 match self {
326 VulnStatus::Open => "open",
327 VulnStatus::Fixed => "fixed",
328 VulnStatus::Dismissed => "dismissed",
329 }
330 }
331
332 pub fn parse(text: &str) -> Option<VulnStatus> {
333 Some(match text {
334 "open" => VulnStatus::Open,
335 "fixed" => VulnStatus::Fixed,
336 "dismissed" => VulnStatus::Dismissed,
337 _ => return None,
338 })
339 }
340
341 pub fn state(self) -> AlertState {
342 match self {
343 VulnStatus::Open => AlertState::Open,
344 VulnStatus::Fixed => AlertState::Fixed,
345 VulnStatus::Dismissed => AlertState::Dismissed,
346 }
347 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API348}
349
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily350/// Where the security update for a vulnerable package stands: the pull
351/// request g1t opens itself to upgrade it, on the branch
352/// `g1t/security/<package>-<version>`.
353#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
354#[serde(rename_all = "snake_case")]
355pub enum UpdateState {
356 /// A sandbox is making the change.
357 Requested,
358 /// Its pull request is open, going through the required checks.
359 Open,
360 Merged,
361 /// Closed without merging, by a person.
362 Closed,
363 /// A newer security update replaced it, or the package is no longer
364 /// vulnerable; g1t closed it.
365 Superseded,
366 /// The version could not be raised without changing code: an issue
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent367 /// was opened for g1t instead.
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily368 NeedsCode,
369 /// It could not be made; why is in `error`.
370 Failed,
371}
372
373impl UpdateState {
374 pub const ALL: [UpdateState; 7] = [
375 UpdateState::Requested,
376 UpdateState::Open,
377 UpdateState::Merged,
378 UpdateState::Closed,
379 UpdateState::Superseded,
380 UpdateState::NeedsCode,
381 UpdateState::Failed,
382 ];
383
384 pub fn as_str(self) -> &'static str {
385 match self {
386 UpdateState::Requested => "requested",
387 UpdateState::Open => "open",
388 UpdateState::Merged => "merged",
389 UpdateState::Closed => "closed",
390 UpdateState::Superseded => "superseded",
391 UpdateState::NeedsCode => "needs_code",
392 UpdateState::Failed => "failed",
393 }
394 }
395
396 pub fn parse(text: &str) -> Option<UpdateState> {
397 UpdateState::ALL.into_iter().find(|state| state.as_str() == text)
398 }
399
400 /// Whether g1t is still working on it.
401 pub fn in_progress(self) -> bool {
402 matches!(self, UpdateState::Requested | UpdateState::Open | UpdateState::NeedsCode)
403 }
404}
405
406/// The security update for one package.
407#[derive(Clone, Debug, Serialize, Deserialize)]
408#[serde(rename_all = "camelCase")]
409pub struct SecurityUpdate {
410 pub state: UpdateState,
411 /// The version it upgrades to.
412 pub target: String,
413 /// `g1t/security/<package>-<version>`.
414 pub branch: Option<String>,
415 /// The pull request g1t opened.
416 pub pull: Option<u32>,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent417 /// The issue opened for g1t, when the upgrade needs code changes.
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily418 pub issue: Option<u32>,
419 /// Why it failed, when it did.
420 pub error: Option<String>,
421 /// RFC 3339.
422 pub updated_at: String,
423}
424
425/// The prefix every security update's branch starts with.
426pub const UPDATE_BRANCH_PREFIX: &str = "g1t/security/";
427
428/// The branch a security update is made on: `g1t/security/<package>-<version>`,
429/// with anything a branch name cannot hold (a scope's `@` and `/`) as `-`.
430pub fn update_branch(package: &str, version: &str) -> String {
431 let clean = |text: &str| -> String {
432 let mut out = String::new();
433 for c in text.chars() {
434 let c = if c.is_ascii_alphanumeric() || matches!(c, '.' | '_') { c } else { '-' };
435 if !(c == '-' && out.ends_with('-')) {
436 out.push(c);
437 }
438 }
439 out.trim_matches(['-', '.']).to_owned()
440 };
441 format!("{UPDATE_BRANCH_PREFIX}{}-{}", clean(package), clean(version))
442}
443
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API444/// One advisory against one package at the version a lockfile resolves.
445#[derive(Clone, Debug, Serialize, Deserialize)]
446#[serde(rename_all = "camelCase")]
447pub struct Vulnerability {
448 pub id: String,
449 pub repo_id: String,
450 /// `npm`, `crates.io`, `Go`, `PyPI`.
451 pub ecosystem: String,
452 pub package: String,
453 pub version: String,
454 /// The lockfile that resolves it.
455 pub manifest: String,
456 /// The id people know it by (its GHSA id when it has one).
457 pub advisory: String,
458 pub osv_id: String,
459 pub summary: String,
460 /// `critical`, `high`, `medium`, `low` or `unknown`.
461 pub severity: String,
462 pub fixed_version: Option<String>,
463 pub status: VulnStatus,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent464 /// The issue opened to upgrade the package, when g1t was put on
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily465 /// it: the upgrade needs code changes, or predates security updates.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API466 pub issue: Option<u32>,
467 /// RFC 3339.
468 pub found_at: String,
469 pub fixed_at: Option<String>,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily470 /// Open, dismissed or fixed.
471 pub state: AlertState,
472 /// Who dismissed it, why, with what comment, and when.
473 #[serde(default)]
474 pub dismissed_by: Option<String>,
475 #[serde(default)]
476 pub dismissed_reason: Option<DismissReason>,
477 #[serde(default)]
478 pub dismissed_comment: Option<String>,
479 #[serde(default)]
480 pub dismissed_at: Option<String>,
481 /// The security update for its package, if g1t has started one.
482 #[serde(default)]
483 pub update: Option<SecurityUpdate>,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API484}
485
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily486/// Open alerts by severity: vulnerabilities open and not dismissed, and
487/// secrets in the history that look real, as critical. A blocked secret
488/// never landed and a likely test value is no danger, so neither counts.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API489#[derive(Clone, Debug, Default, Serialize, Deserialize)]
490pub struct SeverityCounts {
491 pub critical: u32,
492 pub high: u32,
493 pub medium: u32,
494 pub low: u32,
495 pub unknown: u32,
496}
497
498/// How a repository's history scan stands.
499#[derive(Clone, Debug, Default, Serialize, Deserialize)]
500#[serde(rename_all = "camelCase")]
501pub struct ScanState {
502 /// `pending`, `running`, `done` or `stopped` (over the workspace's limit).
503 pub history: String,
504 pub commits_scanned: u32,
505 /// RFC 3339.
506 pub history_finished_at: Option<String>,
507 pub dependencies_scanned_at: Option<String>,
508 /// Why the last dependency scan failed, if it did.
509 pub dependencies_error: Option<String>,
510 pub lockfiles: Vec<String>,
511}
512
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily513/// Secret alerts by where they stand.
514#[derive(Clone, Debug, Default, Serialize, Deserialize)]
515#[serde(rename_all = "camelCase")]
516pub struct SecretCounts {
517 /// In the history and looking real: rotate these.
518 pub open: u32,
519 /// Stopped at a push, so never landed, and looking real.
520 pub blocked: u32,
521 /// Open or blocked, but likely test values.
522 pub test_values: u32,
523 pub dismissed: u32,
524 pub fixed: u32,
525}
526
527/// One thing that happened to an alert, for its activity log.
528#[derive(Clone, Debug, Serialize, Deserialize)]
529#[serde(rename_all = "camelCase")]
530pub struct AlertActivity {
531 pub id: String,
532 /// The secret's or vulnerability's id.
533 pub alert_id: String,
534 /// `dismissed`, `reopened`, `update_requested`, `update_opened`,
535 /// `update_merged`, `update_closed`, `update_superseded`,
536 /// `update_needs_code` or `update_failed`.
537 pub action: String,
538 /// Who did it: a person's username, or `g1t`.
539 pub actor: Option<String>,
540 pub reason: Option<DismissReason>,
541 pub comment: Option<String>,
542 /// The pull request or issue it concerns.
543 pub number: Option<u32>,
544 /// RFC 3339.
545 pub at: String,
546}
547
548/// What `.g1t/dependencies.yml` asks for: version updates, which keep
549/// dependencies current whether or not they are vulnerable.
550#[derive(Clone, Debug, Default, Serialize, Deserialize)]
551#[serde(rename_all = "camelCase")]
552pub struct VersionUpdatesState {
553 /// Whether the file is on the default branch.
554 pub found: bool,
555 /// What is wrong with it, if anything.
556 pub error: Option<String>,
557 /// Each entry under `updates`, as read.
558 pub updates: Vec<VersionUpdateEntry>,
559 /// When it was last read, RFC 3339.
560 pub read_at: Option<String>,
561}
562
563/// One entry of `.g1t/dependencies.yml`'s `updates`.
564#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
565#[serde(rename_all = "camelCase")]
566pub struct VersionUpdateEntry {
567 /// `npm`, `cargo`, `go` or `pip`.
568 pub ecosystem: String,
569 /// Where its manifest is, from the repository's root: `/`, `/web`.
570 pub directory: String,
571 /// `daily`, `weekly` or `monthly`.
572 pub interval: String,
573 /// Groups, each a name and the package patterns it gathers.
574 pub groups: Vec<UpdateGroup>,
575 /// Packages never updated, or not to these versions.
576 pub ignore: Vec<UpdateIgnore>,
577 /// Version update pull requests open at once, at most.
578 pub open_pull_requests_limit: u32,
579}
580
581#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
582pub struct UpdateGroup {
583 pub name: String,
584 /// Package names, with `*` for any run of characters.
585 pub patterns: Vec<String>,
586}
587
588#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
589pub struct UpdateIgnore {
590 /// A package name, with `*` for any run of characters.
591 pub dependency: String,
592 /// Version requirements to skip, such as `>=5`; all when empty.
593 pub versions: Vec<String>,
594}
595
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API596/// Everything the Security page shows for one repository.
597#[derive(Clone, Debug, Serialize, Deserialize)]
598#[serde(rename_all = "camelCase")]
599pub struct SecurityOverview {
600 pub repo_id: String,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily601 /// Open alerts by severity; see [`SeverityCounts`].
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API602 pub counts: SeverityCounts,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily603 pub secret_counts: SecretCounts,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API604 pub secrets: Vec<SecretFinding>,
605 pub vulnerabilities: Vec<Vulnerability>,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily606 /// What happened to the alerts, newest first.
607 pub activity: Vec<AlertActivity>,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API608 pub scan: ScanState,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily609 /// Security updates: whether g1t opens a pull request to upgrade each
610 /// vulnerable dependency that has a fix.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API611 pub upkeep: bool,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily612 pub version_updates: VersionUpdatesState,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API613}
614
615/// `overview`: members of the workspace only. Returns
616/// `Outcome<SecurityOverview>`.
617#[derive(Debug, Serialize, Deserialize)]
618pub struct OverviewArgs {
619 pub repo: RepoPath,
620 pub viewer: Option<User>,
621}
622
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily623/// `dismiss`: closes an alert with a reason and an optional comment. A
624/// secret takes Admin on the repository (a dismissed secret is let through
625/// push protection, unless it was revoked); a vulnerable dependency takes
626/// Write. Returns `Outcome<AlertChange>`.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API627#[derive(Debug, Serialize, Deserialize)]
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily628pub struct DismissArgs {
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API629 pub actor: User,
630 pub repo: RepoPath,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily631 /// A secret's id (`sec_…`) or a vulnerability's (`vul_…`).
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API632 pub id: String,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily633 pub reason: DismissReason,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API634 #[serde(default)]
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily635 pub comment: String,
636}
637
638/// `reopen`: opens a dismissed alert again, with the same roles as
639/// `dismiss`. Returns `Outcome<AlertChange>`.
640#[derive(Debug, Serialize, Deserialize)]
641pub struct ReopenArgs {
642 pub actor: User,
643 pub repo: RepoPath,
644 pub id: String,
645}
646
647/// The alert `dismiss` or `reopen` changed, as it is now: one of the two.
648#[derive(Clone, Debug, Default, Serialize, Deserialize)]
649#[serde(rename_all = "camelCase")]
650pub struct AlertChange {
651 pub secret: Option<SecretFinding>,
652 pub vulnerability: Option<Vulnerability>,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API653}
654
655/// `rescan`: scans the dependencies again now, and the history from the
656/// start. Members only. Returns `Outcome<ScanState>`.
657#[derive(Debug, Serialize, Deserialize)]
658pub struct RescanArgs {
659 pub actor: User,
660 pub repo: RepoPath,
661}
662
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily663/// `set_upkeep`: security updates on or off: whether g1t opens a pull
664/// request to upgrade each vulnerable dependency that has a fix. Maintain
665/// and up. Returns `Outcome<bool>`.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API666#[derive(Debug, Serialize, Deserialize)]
667pub struct SetUpkeepArgs {
668 pub actor: User,
669 pub repo: RepoPath,
670 pub enabled: bool,
671}
672
673/// `workspace`: every repository of a workspace that has findings, for
674/// its members. Returns `Outcome<Vec<RepoSecurity>>`.
675#[derive(Debug, Serialize, Deserialize)]
676pub struct WorkspaceArgs {
677 pub workspace: String,
678 pub viewer: Option<User>,
679}
680
681#[derive(Clone, Debug, Serialize, Deserialize)]
682#[serde(rename_all = "camelCase")]
683pub struct RepoSecurity {
684 pub repo_id: String,
685 pub name: String,
686 pub counts: SeverityCounts,
687 pub secrets: u32,
688 pub vulnerabilities: u32,
689 pub upkeep: bool,
690 pub dependencies_scanned_at: Option<String>,
691}
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily692
693/// `bump` (runner): makes a security update in a sandbox. It clones the
694/// default branch, raises `package` to `version` in each lockfile with the
695/// ecosystem's own tool (`npm`, `cargo`, `go`, `pip`), commits that as g1t
696/// (`g1t <g1t@users.noreply.g1t.sh>`) and pushes it to `branch`, which must
697/// start with [`UPDATE_BRANCH_PREFIX`]. The push is what tells the security
698/// service to open the pull request. Returns `Outcome<bool>`: whether the
699/// sandbox started.
700#[derive(Clone, Debug, Serialize, Deserialize)]
701#[serde(rename_all = "camelCase")]
702pub struct BumpArgs {
703 pub repo: RepoPath,
704 /// OSV's name for the ecosystem: `npm`, `crates.io`, `Go` or `PyPI`.
705 pub ecosystem: String,
706 pub package: String,
707 pub version: String,
708 /// The lockfiles that resolve a vulnerable version, from the root.
709 pub lockfiles: Vec<String>,
710 pub branch: String,
711 /// The commit's message.
712 pub message: String,
713}