Skip to content
728 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.

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,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar205 /// 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,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily222}
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 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API233}
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,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily245 /// 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>,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar249 /// 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>,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API255}
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>,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar269 /// Whether the repository is private, as repos read it.
270 #[serde(default, skip_serializing_if = "Option::is_none")]
271 pub private: Option<bool>,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API272}
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,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily290 /// Where the last page stopped; `from`, or the head of the default
291 /// branch, when absent.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API292 #[serde(default)]
293 pub after: Option<String>,
294 pub limit: u32,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily295 /// 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>,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar302 /// Custom patterns to look for too.
303 #[serde(default, skip_serializing_if = "Vec::is_empty")]
304 pub patterns: Vec<crate::security_suite::PatternSpec>,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API305}
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,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar324 /// 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>,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API328}
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,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily351 /// 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 }
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API381}
382
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily383/// 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 /// 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-agent400 /// was opened for g1t instead.
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily401 NeedsCode,
402 /// It could not be made; why is in `error`.
403 Failed,
404}
405
406impl UpdateState {
407 pub const ALL: [UpdateState; 7] = [
408 UpdateState::Requested,
409 UpdateState::Open,
410 UpdateState::Merged,
411 UpdateState::Closed,
412 UpdateState::Superseded,
413 UpdateState::NeedsCode,
414 UpdateState::Failed,
415 ];
416
417 pub fn as_str(self) -> &'static str {
418 match self {
419 UpdateState::Requested => "requested",
420 UpdateState::Open => "open",
421 UpdateState::Merged => "merged",
422 UpdateState::Closed => "closed",
423 UpdateState::Superseded => "superseded",
424 UpdateState::NeedsCode => "needs_code",
425 UpdateState::Failed => "failed",
426 }
427 }
428
429 pub fn parse(text: &str) -> Option<UpdateState> {
430 UpdateState::ALL.into_iter().find(|state| state.as_str() == text)
431 }
432
433 /// Whether g1t is still working on it.
434 pub fn in_progress(self) -> bool {
435 matches!(self, UpdateState::Requested | UpdateState::Open | UpdateState::NeedsCode)
436 }
437}
438
439/// The security update for one package.
440#[derive(Clone, Debug, Serialize, Deserialize)]
441#[serde(rename_all = "camelCase")]
442pub struct SecurityUpdate {
443 pub state: UpdateState,
444 /// The version it upgrades to.
445 pub target: String,
446 /// `g1t/security/<package>-<version>`.
447 pub branch: Option<String>,
448 /// The pull request g1t opened.
449 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-agent450 /// 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 daily451 pub issue: Option<u32>,
452 /// Why it failed, when it did.
453 pub error: Option<String>,
454 /// RFC 3339.
455 pub updated_at: String,
456}
457
458/// The prefix every security update's branch starts with.
459pub const UPDATE_BRANCH_PREFIX: &str = "g1t/security/";
460
461/// The branch a security update is made on: `g1t/security/<package>-<version>`,
462/// with anything a branch name cannot hold (a scope's `@` and `/`) as `-`.
463pub fn update_branch(package: &str, version: &str) -> String {
464 let clean = |text: &str| -> String {
465 let mut out = String::new();
466 for c in text.chars() {
467 let c = if c.is_ascii_alphanumeric() || matches!(c, '.' | '_') { c } else { '-' };
468 if !(c == '-' && out.ends_with('-')) {
469 out.push(c);
470 }
471 }
472 out.trim_matches(['-', '.']).to_owned()
473 };
474 format!("{UPDATE_BRANCH_PREFIX}{}-{}", clean(package), clean(version))
475}
476
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API477/// One advisory against one package at the version a lockfile resolves.
478#[derive(Clone, Debug, Serialize, Deserialize)]
479#[serde(rename_all = "camelCase")]
480pub struct Vulnerability {
481 pub id: String,
482 pub repo_id: String,
483 /// `npm`, `crates.io`, `Go`, `PyPI`.
484 pub ecosystem: String,
485 pub package: String,
486 pub version: String,
487 /// The lockfile that resolves it.
488 pub manifest: String,
489 /// The id people know it by (its GHSA id when it has one).
490 pub advisory: String,
491 pub osv_id: String,
492 pub summary: String,
493 /// `critical`, `high`, `medium`, `low` or `unknown`.
494 pub severity: String,
495 pub fixed_version: Option<String>,
496 pub status: VulnStatus,
g1t is one name: its agent's work, commits and comments show as @g1t, and nobody can claim g1t or g1t-agent497 /// 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 daily498 /// 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 API499 pub issue: Option<u32>,
500 /// RFC 3339.
501 pub found_at: String,
502 pub fixed_at: Option<String>,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily503 /// Open, dismissed or fixed.
504 pub state: AlertState,
505 /// Who dismissed it, why, with what comment, and when.
506 #[serde(default)]
507 pub dismissed_by: Option<String>,
508 #[serde(default)]
509 pub dismissed_reason: Option<DismissReason>,
510 #[serde(default)]
511 pub dismissed_comment: Option<String>,
512 #[serde(default)]
513 pub dismissed_at: Option<String>,
514 /// The security update for its package, if g1t has started one.
515 #[serde(default)]
516 pub update: Option<SecurityUpdate>,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API517}
518
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily519/// Open alerts by severity: vulnerabilities open and not dismissed, and
520/// secrets in the history that look real, as critical. A blocked secret
521/// never landed and a likely test value is no danger, so neither counts.
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.522#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API523pub struct SeverityCounts {
524 pub critical: u32,
525 pub high: u32,
526 pub medium: u32,
527 pub low: u32,
528 pub unknown: u32,
529}
530
531/// How a repository's history scan stands.
532#[derive(Clone, Debug, Default, Serialize, Deserialize)]
533#[serde(rename_all = "camelCase")]
534pub struct ScanState {
535 /// `pending`, `running`, `done` or `stopped` (over the workspace's limit).
536 pub history: String,
537 pub commits_scanned: u32,
538 /// RFC 3339.
539 pub history_finished_at: Option<String>,
540 pub dependencies_scanned_at: Option<String>,
541 /// Why the last dependency scan failed, if it did.
542 pub dependencies_error: Option<String>,
543 pub lockfiles: Vec<String>,
544}
545
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily546/// Secret alerts by where they stand.
547#[derive(Clone, Debug, Default, Serialize, Deserialize)]
548#[serde(rename_all = "camelCase")]
549pub struct SecretCounts {
550 /// In the history and looking real: rotate these.
551 pub open: u32,
552 /// Stopped at a push, so never landed, and looking real.
553 pub blocked: u32,
554 /// Open or blocked, but likely test values.
555 pub test_values: u32,
556 pub dismissed: u32,
557 pub fixed: u32,
558}
559
560/// One thing that happened to an alert, for its activity log.
561#[derive(Clone, Debug, Serialize, Deserialize)]
562#[serde(rename_all = "camelCase")]
563pub struct AlertActivity {
564 pub id: String,
565 /// The secret's or vulnerability's id.
566 pub alert_id: String,
567 /// `dismissed`, `reopened`, `update_requested`, `update_opened`,
568 /// `update_merged`, `update_closed`, `update_superseded`,
569 /// `update_needs_code` or `update_failed`.
570 pub action: String,
571 /// Who did it: a person's username, or `g1t`.
572 pub actor: Option<String>,
573 pub reason: Option<DismissReason>,
574 pub comment: Option<String>,
575 /// The pull request or issue it concerns.
576 pub number: Option<u32>,
577 /// RFC 3339.
578 pub at: String,
579}
580
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar581/// Version updates, and what the dependency update file asks of security
582/// updates: see [`crate::updates`].
583pub use crate::updates::{
584 UpdateAllow, UpdateGroup, UpdateIgnore, VersionUpdateEntry, VersionUpdatesState,
585};
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily586
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API587/// Everything the Security page shows for one repository.
588#[derive(Clone, Debug, Serialize, Deserialize)]
589#[serde(rename_all = "camelCase")]
590pub struct SecurityOverview {
591 pub repo_id: String,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily592 /// 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 API593 pub counts: SeverityCounts,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily594 pub secret_counts: SecretCounts,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API595 pub secrets: Vec<SecretFinding>,
596 pub vulnerabilities: Vec<Vulnerability>,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily597 /// What happened to the alerts, newest first.
598 pub activity: Vec<AlertActivity>,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API599 pub scan: ScanState,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily600 /// Security updates: whether g1t opens a pull request to upgrade each
601 /// 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 API602 pub upkeep: bool,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily603 pub version_updates: VersionUpdatesState,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API604}
605
606/// `overview`: members of the workspace only. Returns
607/// `Outcome<SecurityOverview>`.
608#[derive(Debug, Serialize, Deserialize)]
609pub struct OverviewArgs {
610 pub repo: RepoPath,
611 pub viewer: Option<User>,
612}
613
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily614/// `dismiss`: closes an alert with a reason and an optional comment. A
615/// secret takes Admin on the repository (a dismissed secret is let through
616/// push protection, unless it was revoked); a vulnerable dependency takes
617/// Write. Returns `Outcome<AlertChange>`.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API618#[derive(Debug, Serialize, Deserialize)]
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily619pub struct DismissArgs {
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API620 pub actor: User,
621 pub repo: RepoPath,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily622 /// 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 API623 pub id: String,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily624 pub reason: DismissReason,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API625 #[serde(default)]
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily626 pub comment: String,
627}
628
629/// `reopen`: opens a dismissed alert again, with the same roles as
630/// `dismiss`. Returns `Outcome<AlertChange>`.
631#[derive(Debug, Serialize, Deserialize)]
632pub struct ReopenArgs {
633 pub actor: User,
634 pub repo: RepoPath,
635 pub id: String,
636}
637
638/// The alert `dismiss` or `reopen` changed, as it is now: one of the two.
639#[derive(Clone, Debug, Default, Serialize, Deserialize)]
640#[serde(rename_all = "camelCase")]
641pub struct AlertChange {
642 pub secret: Option<SecretFinding>,
643 pub vulnerability: Option<Vulnerability>,
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API644}
645
646/// `rescan`: scans the dependencies again now, and the history from the
647/// start. Members only. Returns `Outcome<ScanState>`.
648#[derive(Debug, Serialize, Deserialize)]
649pub struct RescanArgs {
650 pub actor: User,
651 pub repo: RepoPath,
652}
653
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily654/// `set_upkeep`: security updates on or off: whether g1t opens a pull
655/// request to upgrade each vulnerable dependency that has a fix. Maintain
656/// and up. Returns `Outcome<bool>`.
Agents get guardrails, run credentials, an audit log, a context hub, repository instructions and mentions; security upkeep; snake_case API657#[derive(Debug, Serialize, Deserialize)]
658pub struct SetUpkeepArgs {
659 pub actor: User,
660 pub repo: RepoPath,
661 pub enabled: bool,
662}
663
664/// `workspace`: every repository of a workspace that has findings, for
665/// its members. Returns `Outcome<Vec<RepoSecurity>>`.
666#[derive(Debug, Serialize, Deserialize)]
667pub struct WorkspaceArgs {
668 pub workspace: String,
669 pub viewer: Option<User>,
670}
671
672#[derive(Clone, Debug, Serialize, Deserialize)]
673#[serde(rename_all = "camelCase")]
674pub struct RepoSecurity {
675 pub repo_id: String,
676 pub name: String,
677 pub counts: SeverityCounts,
678 pub secrets: u32,
679 pub vulnerabilities: u32,
680 pub upkeep: bool,
681 pub dependencies_scanned_at: Option<String>,
682}
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily683
684/// `bump` (runner): makes a security update in a sandbox. It clones the
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar685/// default branch (or `base`), raises `package` to `version` in each lockfile with the
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily686/// ecosystem's own tool (`npm`, `cargo`, `go`, `pip`), commits that as g1t
687/// (`g1t <g1t@users.noreply.g1t.sh>`) and pushes it to `branch`, which must
688/// start with [`UPDATE_BRANCH_PREFIX`]. The push is what tells the security
689/// service to open the pull request. Returns `Outcome<bool>`: whether the
690/// sandbox started.
691#[derive(Clone, Debug, Serialize, Deserialize)]
692#[serde(rename_all = "camelCase")]
693pub struct BumpArgs {
694 pub repo: RepoPath,
695 /// OSV's name for the ecosystem: `npm`, `crates.io`, `Go` or `PyPI`.
696 pub ecosystem: String,
697 pub package: String,
698 pub version: String,
699 /// The lockfiles that resolve a vulnerable version, from the root.
700 pub lockfiles: Vec<String>,
701 pub branch: String,
702 /// The commit's message.
703 pub message: String,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar704 /// `version` for a version update, whose branch is named by the
705 /// dependency update file (any branch but the default one); a security
706 /// update when absent.
707 #[serde(default, skip_serializing_if = "Option::is_none")]
708 pub kind: Option<String>,
709 /// Several packages raised in one commit, for a grouped update. When
710 /// given, `package` and `version` are its first.
711 #[serde(default, skip_serializing_if = "Vec::is_empty")]
712 pub packages: Vec<crate::updates::BumpPackage>,
713 /// How a manifest's requirement changes (`versioning-strategy`):
714 /// `increase` (the default), `increase-if-necessary`, `widen` or
715 /// `lockfile-only`.
716 #[serde(default, skip_serializing_if = "Option::is_none")]
717 pub strategy: Option<String>,
718 /// Replace the branch if it is there already: a rebase or a recreate.
719 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
720 pub force: bool,
721 /// Private registries the tools may read, with their credentials.
722 #[serde(default, skip_serializing_if = "Vec::is_empty")]
723 pub registries: Vec<crate::updates::BumpRegistry>,
724 /// The branch to start from, which its pull request merges into
725 /// (`target-branch`): the default branch when absent.
726 #[serde(default, skip_serializing_if = "Option::is_none")]
727 pub base: Option<String>,
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily728}