audit-labs/gh-attest
GitHub Audit Evidence Extractor
clone: git clone https://gitbay.org/audit-labs/gh-attest.git
14e147a8d4f000507bceaecd373fad153274aa09
verified · cmc
author: Christian Cleberg <hello@cleberg.net> · 2026-07-20T15:16:52Z
src/auth.ts | 6 ++++-- src/crypto-utils.ts | 2 +- src/exporter.ts | 2 +- src/github-app.ts | 4 ++-- src/index.ts | 6 +++--- 5 files changed, 11 insertions(+), 9 deletions(-) @@ -94,11 +94,13 @@ export async function fetchUserInstallationIds(userAccessToken: string): Promise } function base64UrlEncode(input: string): string { - return btoa(input).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); + // Padding is bounded to two characters, so the quantifier is too — an + // unbounded `=+$` backtracks super-linearly. + return btoa(input).replaceAll("+", "-").replaceAll("/", "_").replace(/={1,2}$/, ""); } function base64UrlDecode(input: string): string { - const padded = input.replace(/-/g, "+").replace(/_/g, "/"); + const padded = input.replaceAll("-", "+").replaceAll("_", "/"); const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - (padded.length % 4)); return atob(padded + padding); } @@ -1,7 +1,7 @@ export function hexToBytes(hex: string): Uint8Array { const bytes = new Uint8Array(hex.length / 2); for (let i = 0; i < bytes.length; i++) { - bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16); + bytes[i] = Number.parseInt(hex.substring(i * 2, i * 2 + 2), 16); } return bytes; } @@ -89,7 +89,7 @@ export function renderCsv(rows: EvidenceRow[]): string { function csvEscape(value: string | number): string { const str = String(value); if (/[",\r\n]/.test(str)) { - return `"${str.replace(/"/g, '""')}"`; + return `"${str.replaceAll('"', '""')}"`; } return str; } @@ -9,7 +9,7 @@ const GITHUB_API = "https://api.github.com"; // key they downloaded. function pkcs1PemToPkcs8Pem(pem: string): string { const base64 = pem.replace(/-----(BEGIN|END) RSA PRIVATE KEY-----/g, "").replace(/\s/g, ""); - const pkcs1 = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); + const pkcs1 = Uint8Array.from(atob(base64), (c) => c.codePointAt(0) ?? 0); const derLength = (length: number): number[] => { if (length < 0x80) return [length]; @@ -30,7 +30,7 @@ function pkcs1PemToPkcs8Pem(pem: string): string { ]); let binary = ""; - for (const byte of pkcs8) binary += String.fromCharCode(byte); + for (const byte of pkcs8) binary += String.fromCodePoint(byte); return `-----BEGIN PRIVATE KEY-----\n${btoa(binary)}\n-----END PRIVATE KEY-----`; } @@ -385,8 +385,8 @@ interface CleanupResult { // a no-op most of the time. async function runRetentionCleanup(env: Env): Promise<CleanupResult> { const now = Date.now(); - const snapshotCutoff = new Date(now - SNAPSHOT_RETENTION_DAYS * 86400_000).toISOString(); - const exportCutoff = new Date(now - EXPORT_RETENTION_DAYS * 86400_000).toISOString(); + const snapshotCutoff = new Date(now - SNAPSHOT_RETENTION_DAYS * 86_400_000).toISOString(); + const exportCutoff = new Date(now - EXPORT_RETENTION_DAYS * 86_400_000).toISOString(); // Delete expired export R2 objects first (their keys live in the rows). const { results: expiredExports } = await env.DB.prepare( @@ -567,7 +567,7 @@ async function handleAccessReview(request: Request, env: Env): Promise<Response> const since = requested && !Number.isNaN(requested.getTime()) ? requested.toISOString() - : new Date(Date.now() - 30 * 86400_000).toISOString(); + : new Date(Date.now() - 30 * 86_400_000).toISOString(); const [diff, orgRow, installations] = await Promise.all([ buildAccessDiff(env.DB, session.installationId, since),