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 API | 1 | //! Scanning a repository's history for secrets once, in the background, a |
| 2 | //! page of commits at a time, metered to its workspace. | |
| 3 | ||
| 4 | use g1t_contracts::billing::{CheckLimitArgs, Limit, LimitState, NotePendingArgs}; | |
| 5 | use g1t_contracts::security::{HistoryPage, ScanHistoryArgs, SecretStatus}; | |
| 6 | use g1t_contracts::Outcome; | |
| 7 | use worker::Result; | |
| 8 | ||
| 9 | use crate::Security; | |
| 10 | use crate::store::RepoRow; | |
| 11 | ||
| 12 | /// Commits read per call to the repos service. | |
| 13 | const PAGE: u32 = 25; | |
| 14 | /// What one read of a git object is taken to cost g1t, in millionths of a | |
| 15 | /// dollar: a store call and the Worker time around it, rounded up. | |
| 16 | pub const MICROS_PER_READ: i64 = 1; | |
| 17 | ||
| 18 | impl Security { | |
| 19 | /// Whether the workspace's usage has reached its limit, which stops | |
| 20 | /// background work. Unknown counts as not. | |
| 21 | async fn over_limit(&self, workspace: &str) -> bool { | |
| 22 | let limit: Result<Outcome<Limit>> = | |
| 23 | g1t_kit::call(&self.billing, "check_limit", &CheckLimitArgs { workspace: workspace.to_owned() }).await; | |
| 24 | matches!(limit, Ok(Outcome::Ok(limit)) if limit.state == LimitState::Stopped) | |
| 25 | } | |
| 26 | ||
| 27 | /// Records what scanning cost, and tells billing the month's total so | |
| 28 | /// the workspace's limit counts it. | |
| 29 | pub async fn meter(&self, workspace: &str, reads: u32, commits: u32, osv_calls: u32, cost_micros: i64) -> Result<()> { | |
| 30 | let total = self.store.meter(workspace, reads, commits, osv_calls, cost_micros).await?; | |
| 31 | let noted: Result<bool> = g1t_kit::call( | |
| 32 | &self.billing, | |
| 33 | "note_pending", | |
| 34 | &NotePendingArgs { workspace: workspace.to_owned(), source: "security".to_owned(), cost_micros: total }, | |
| 35 | ) | |
| 36 | .await; | |
| 37 | if let Err(error) = noted { | |
| 38 | worker::console_error!("security: usage for {workspace} not noted: {error}"); | |
| 39 | } | |
| 40 | Ok(()) | |
| 41 | } | |
| 42 | ||
| 43 | /// Scans up to `pages` pages of a repository's history from where the | |
| 44 | /// last scan stopped. | |
| 45 | pub async fn advance_history(&self, repo: &RepoRow, pages: u32) -> Result<()> { | |
| 46 | if repo.history == "done" { | |
| 47 | return Ok(()); | |
| 48 | } | |
| 49 | if self.over_limit(&repo.namespace).await { | |
| 50 | return self.store.set_history(&repo.repo_id, "stopped", repo.history_cursor.as_deref(), 0).await; | |
| 51 | } | |
| 52 | let mut cursor = repo.history_cursor.clone(); | |
| 53 | for _ in 0..pages { | |
| 54 | let page: HistoryPage = g1t_kit::call( | |
| 55 | &self.repos, | |
| 56 | "scan_history", | |
| 57 | &ScanHistoryArgs { repo_id: repo.repo_id.clone(), after: cursor.clone(), limit: PAGE }, | |
| 58 | ) | |
| 59 | .await?; | |
| 60 | let fingerprints: Vec<String> = page.secrets.iter().map(|secret| secret.fingerprint.clone()).collect(); | |
| 61 | self.store.landed(&repo.repo_id, &fingerprints).await?; | |
| 62 | self.store.add_secrets(&repo.repo_id, &page.secrets, SecretStatus::Open, "history", None).await?; | |
| 63 | self.meter(&repo.namespace, page.reads, page.commits, 0, i64::from(page.reads) * MICROS_PER_READ).await?; | |
| 64 | match page.next { | |
| 65 | Some(next) => { | |
| 66 | self.store.set_history(&repo.repo_id, "running", Some(&next), page.commits).await?; | |
| 67 | cursor = Some(next); | |
| 68 | } | |
| 69 | None => return self.store.set_history(&repo.repo_id, "done", None, page.commits).await, | |
| 70 | } | |
| 71 | } | |
| 72 | Ok(()) | |
| 73 | } | |
| 74 | } |