Skip to content

Repo sync #39316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions data/release-notes/enterprise-server/3-13/16.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ sections:
Users of GitHub Actions could not view or manage Actions artifacts and logs if the global AWS STS endpoint was unavailable, because Actions did not use the configured regional STS endpoint.
- |
If an Enterprise Managed User (EMU) pushed to their personal repository with both secret scanning and push protection enabled, the custom patterns defined at enterprise level were not being applied during the push protection scan.
- |
In some situations, the kafka-lite service could cause client timeouts when processing consumer group membership sessions and expirations. [Updated: 2025-07-14]
changes:
- |
Site administrators can now set rate limits for the WebSockets controller used for live updates, with `ghe-config app.github.web-sockets-rate-limit`. For more information, see [Controlling the rate for the live update service](/admin/configuring-settings/configuring-user-applications-for-your-enterprise/configuring-rate-limits#controlling-the-rate-for-the-live-update-service).
Expand Down
2 changes: 2 additions & 0 deletions data/release-notes/enterprise-server/3-14/13.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ sections:
Organization owners had no audit log events to track organization announcements displayed on banners in the UI.
- |
If an Enterprise Managed User (EMU) pushed to their personal repository with both secret scanning and push protection enabled, the custom patterns defined at enterprise level were not being applied during the push protection scan.
- |
In some situations, the kafka-lite service could cause client timeouts when processing consumer group membership sessions and expirations. [Updated: 2025-07-14]
changes:
- |
Site administrators can now set rate limits for the WebSockets controller used for live updates, with `ghe-config app.github.web-sockets-rate-limit`. For more information, see [Controlling the rate for the live update service](/admin/configuring-settings/configuring-user-applications-for-your-enterprise/configuring-rate-limits#controlling-the-rate-for-the-live-update-service).
Expand Down
2 changes: 2 additions & 0 deletions data/release-notes/enterprise-server/3-15/8.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ sections:
Organization owners had no audit log events to track organization announcements displayed on banners in the UI.
- |
If an Enterprise Managed User (EMU) pushed to their personal repository with both secret scanning and push protection enabled, the custom patterns defined at enterprise level were not being applied during the push protection scan.
- |
In some situations, the kafka-lite service could cause client timeouts when processing consumer group membership sessions and expirations. [Updated: 2025-07-14]
changes:
- |
To ensure critical integrations and automated systems have uninterrupted access, the `/repositories/:repository_id/collaborators` endpoints now honor the higher rate limits for exempt users set with `ghe-config app.github.rate-limiting-exempt-users "<USER>"`.
Expand Down
2 changes: 2 additions & 0 deletions data/release-notes/enterprise-server/3-16/4.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ sections:
When an administrator suspended a user from the site admin dashboard, the form required them to complete Digital Services Act (DSA) fields that are not relevant on GitHub Enterprise Server.
- |
After an appliance reboot, code scanning did not always trigger or process analyses.
- |
In some situations, the kafka-lite service could cause client timeouts when processing consumer group membership sessions and expirations. [Updated: 2025-07-14]
changes:
- |
Site administrators who test the Prometheus endpoint can now use 127.0.0.1 as a trusted IP address. Previously, only specific IPs were allowed for testing.
Expand Down
2 changes: 2 additions & 0 deletions data/release-notes/enterprise-server/3-17/1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ sections:
On instances with dangling commit graph lock files, recompute checksum operations were unexpectedly triggered.
- |
After an appliance reboot, code scanning did not always trigger or process analyses.
- |
In some situations, the kafka-lite service could cause client timeouts when processing consumer group membership sessions and expirations. [Updated: 2025-07-14]
changes:
- |
Site administrators who test the Prometheus endpoint can now use 127.0.0.1 as a trusted IP address. Previously, only specific IPs were allowed for testing.
Expand Down
1 change: 1 addition & 0 deletions data/ui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ parameter_table:
audit_logs:
action: Action
description: Description
reference: Reference
graphql:
reference:
implements: <code>{{ GraphQLItemTitle }}</code> Implements
Expand Down
24 changes: 24 additions & 0 deletions src/audit-logs/components/GroupedEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,40 @@ export default function GroupedEvents({ auditLogEvents, category }: Props) {
<tr>
<th scope="col">{t('action')}</th>
<th scope="col">{t('description')}</th>
<th scope="col">{t('reference')}</th>
</tr>
</thead>
<tbody>
{auditLogEvents.map((event) => {
const renderReferenceLinks = () => {
if (!event.docs_reference_links || event.docs_reference_links === 'N/A') {
return null
}

const links = event.docs_reference_links
.split(/[,\s]+/)
.map((link) => link.trim())
.filter((link) => link && link !== 'N/A')

const titles = event.docs_reference_titles
? event.docs_reference_titles.split(', ')
: links

return links.map((link, index) => (
<span key={link}>
<a href={link}>{titles[index] || link}</a>
{index < links.length - 1 && ', '}
</span>
))
}

return (
<tr key={event.action}>
<td>
<code>{event.action}</code>
</td>
<td>{event.description}</td>
<td>{renderReferenceLinks()}</td>
</tr>
)
})}
Expand Down
137 changes: 117 additions & 20 deletions src/audit-logs/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import path from 'path'

import { readCompressedJsonFileFallback } from '@/frame/lib/read-json-file'
import { getOpenApiVersion } from '@/versions/lib/all-versions'
import findPage from '@/frame/lib/find-page'
import type {
AuditLogEventT,
CategorizedEvents,
Expand All @@ -20,6 +21,61 @@ type PipelineConfig = {
appendedDescriptions: Record<string, string>
}

type TitleResolutionContext = {
pages: Record<string, any>
redirects: Record<string, string>
}

// Resolves docs_reference_links URLs to page titles
async function resolveReferenceLinksToTitles(
docsReferenceLinks: string,
context: TitleResolutionContext,
): Promise<string> {
if (!docsReferenceLinks || docsReferenceLinks === 'N/A') {
return ''
}

// Handle multiple comma-separated or space-separated links
const links = docsReferenceLinks
.split(/[,\s]+/)
.map((link) => link.trim())
.filter((link) => link && link !== 'N/A')

const titles = []
for (const link of links) {
try {
const page = findPage(link, context.pages, context.redirects)
if (page) {
// Create a minimal context for rendering the title
const renderContext = {
currentLanguage: 'en',
currentVersion: 'free-pro-team@latest',
pages: context.pages,
redirects: context.redirects,
}
const title = await page.renderProp('title', renderContext, { textOnly: true })
titles.push(title)
} else {
// If we can't resolve the link, use the original URL
titles.push(link)
}
} catch (error) {
// If resolution fails, use the original URL
console.warn(
`Failed to resolve title for link: ${link}`,
error instanceof Error
? error instanceof Error
? error.message
: String(error)
: String(error),
)
titles.push(link)
}
}

return titles.join(', ')
}

// get audit log event data for the requested page and version
//
// returns an array of event objects that look like this:
Expand Down Expand Up @@ -87,17 +143,19 @@ export function getCategorizedAuditLogEvents(page: string, version: string) {
}

// Filters audit log events based on allowlist values.
//
// * eventsToCheck: events to consider
// * allowListvalues: allowlist values to filter by
// * currentEvents: events already collected
// * pipelineConfig: audit log pipeline config data
export function filterByAllowlistValues(
eventsToCheck: RawAuditLogEventT[],
allowListValues: string | string[],
currentEvents: AuditLogEventT[],
pipelineConfig: PipelineConfig,
) {
export async function filterByAllowlistValues({
eventsToCheck,
allowListValues,
currentEvents = [],
pipelineConfig,
titleContext,
}: {
eventsToCheck: RawAuditLogEventT[]
allowListValues: string | string[]
currentEvents?: AuditLogEventT[]
pipelineConfig: PipelineConfig
titleContext?: TitleResolutionContext
}) {
if (!Array.isArray(allowListValues)) allowListValues = [allowListValues]
if (!currentEvents) currentEvents = []

Expand All @@ -112,12 +170,27 @@ export function filterByAllowlistValues(
if (seen.has(event.action)) continue
seen.add(event.action)

const minimal = {
const minimal: AuditLogEventT = {
action: event.action,
description: processAndGetEventDescription(event, eventAllowlists, pipelineConfig),
docs_reference_links: event.docs_reference_links,
}

// Resolve reference link titles if context is provided
if (titleContext && event.docs_reference_links && event.docs_reference_links !== 'N/A') {
try {
minimal.docs_reference_titles = await resolveReferenceLinksToTitles(
event.docs_reference_links,
titleContext,
)
} catch (error) {
console.warn(
`Failed to resolve titles for event ${event.action}:`,
error instanceof Error ? error.message : String(error),
)
}
}

minimalEvents.push(minimal)
}
}
Expand All @@ -132,6 +205,7 @@ export function filterByAllowlistValues(
// * currentEvents: events already collected
// * pipelineConfig: audit log pipeline config data
// * auditLogPage: the audit log page the event belongs to
// * titleContext: optional context for resolving reference link titles
//
// Mutates `currentGhesEvents` and updates it with any new filtered for audit
// log events, the object maps GHES versions to page events for that version e.g.:
Expand All @@ -148,13 +222,21 @@ export function filterByAllowlistValues(
// user: [...],
// },
// }
export function filterAndUpdateGhesDataByAllowlistValues(
eventsToCheck: RawAuditLogEventT[],
allowListValue: string,
currentGhesEvents: VersionedAuditLogData,
pipelineConfig: PipelineConfig,
auditLogPage: string,
) {
export async function filterAndUpdateGhesDataByAllowlistValues({
eventsToCheck,
allowListValue,
currentGhesEvents,
pipelineConfig,
auditLogPage,
titleContext,
}: {
eventsToCheck: RawAuditLogEventT[]
allowListValue: string
currentGhesEvents: VersionedAuditLogData
pipelineConfig: PipelineConfig
auditLogPage: string
titleContext?: TitleResolutionContext
}) {
if (!currentGhesEvents) currentGhesEvents = {}

const seenByGhesVersion = new Map()
Expand All @@ -173,12 +255,27 @@ export function filterAndUpdateGhesDataByAllowlistValues(
if (seenByGhesVersion.get(fullGhesVersion)?.has(event.action)) continue

if (ghesVersionAllowlists.includes(allowListValue)) {
const minimal = {
const minimal: AuditLogEventT = {
action: event.action,
description: processAndGetEventDescription(event, ghesVersionAllowlists, pipelineConfig),
docs_reference_links: event.docs_reference_links,
}

// Resolve reference link titles if context is provided
if (titleContext && event.docs_reference_links && event.docs_reference_links !== 'N/A') {
try {
minimal.docs_reference_titles = await resolveReferenceLinksToTitles(
event.docs_reference_links,
titleContext,
)
} catch (error) {
console.warn(
`Failed to resolve titles for event ${event.action}:`,
error instanceof Error ? error.message : String(error),
)
}
}

// we need to initialize as we go to build up the `minimalEvents`
// object that we'll return which will contain the GHES events for
// each versions + page type combos e.g. when processing GHES events
Expand Down
60 changes: 40 additions & 20 deletions src/audit-logs/scripts/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import path from 'path'
import { filterByAllowlistValues, filterAndUpdateGhesDataByAllowlistValues } from '../lib/index'
import { getContents, getCommitSha } from '@/workflows/git-utils'
import { latest, latestStable, releaseCandidate } from '@/versions/lib/enterprise-server-releases'
import { loadPages, loadPageMap } from '@/frame/lib/page-data'
import loadRedirects from '@/redirects/lib/precompile'
import type { AuditLogEventT, VersionedAuditLogData } from '../types'

if (!process.env.GITHUB_TOKEN) {
Expand Down Expand Up @@ -53,6 +55,13 @@ async function main() {
pipelineConfig.sha = mainSha
await writeFile(configFilepath, JSON.stringify(pipelineConfig, null, 2))

// Load pages and redirects for title resolution
console.log('Loading pages and redirects for title resolution...')
const pageList = await loadPages(undefined, ['en'])
const pages = await loadPageMap(pageList)
const redirects = await loadRedirects(pageList)
const titleContext = { pages, redirects }

// store an array of audit log event data keyed by version and audit log page,
// will look like this (depends on supported GHES versions):
//
Expand All @@ -75,32 +84,39 @@ async function main() {
// Wrapper around filterByAllowlistValues() because we always need all the
// schema events and pipeline config data.
const filter = (allowListValues: string | string[], currentEvents: AuditLogEventT[] = []) =>
filterByAllowlistValues(schemaEvents, allowListValues, currentEvents, pipelineConfig)
filterByAllowlistValues({
eventsToCheck: schemaEvents,
allowListValues,
currentEvents,
pipelineConfig,
titleContext,
})
// Wrapper around filterGhesByAllowlistValues() because we always need all the
// schema events and pipeline config data.
const filterAndUpdateGhes = (
allowListValues: string,
allowListValue: string,
auditLogPage: string,
currentEvents: VersionedAuditLogData,
currentGhesEvents: VersionedAuditLogData,
) =>
filterAndUpdateGhesDataByAllowlistValues(
schemaEvents,
allowListValues,
currentEvents,
filterAndUpdateGhesDataByAllowlistValues({
eventsToCheck: schemaEvents,
allowListValue,
currentGhesEvents,
pipelineConfig,
auditLogPage,
)
titleContext,
})

auditLogData.fpt = {}
auditLogData.fpt.user = filter('user')
auditLogData.fpt.organization = filter(['organization', 'org_api_only'])
auditLogData.fpt.user = await filter('user')
auditLogData.fpt.organization = await filter(['organization', 'org_api_only'])

auditLogData.ghec = {}
auditLogData.ghec.user = filter('user')
auditLogData.ghec.organization = filter('organization')
auditLogData.ghec.organization = filter('org_api_only', auditLogData.ghec.organization)
auditLogData.ghec.enterprise = filter('business')
auditLogData.ghec.enterprise = filter('business_api_only', auditLogData.ghec.enterprise)
auditLogData.ghec.user = await filter('user')
auditLogData.ghec.organization = await filter('organization')
auditLogData.ghec.organization = await filter('org_api_only', auditLogData.ghec.organization)
auditLogData.ghec.enterprise = await filter('business')
auditLogData.ghec.enterprise = await filter('business_api_only', auditLogData.ghec.enterprise)

// GHES versions are numbered (i.e. "3.9", "3.10", etc.) and filterGhes()
// gives us back an object of GHES versions to page events for each version
Expand All @@ -114,11 +130,15 @@ async function main() {
// so there's no single auditLogData.ghes like the other versions.
const ghesVersionsAuditLogData = {}

filterAndUpdateGhes('business', AUDIT_LOG_PAGES.ENTERPRISE, ghesVersionsAuditLogData)
filterAndUpdateGhes('business_api_only', AUDIT_LOG_PAGES.ENTERPRISE, ghesVersionsAuditLogData)
filterAndUpdateGhes('user', AUDIT_LOG_PAGES.USER, ghesVersionsAuditLogData)
filterAndUpdateGhes('organization', AUDIT_LOG_PAGES.ORGANIZATION, ghesVersionsAuditLogData)
filterAndUpdateGhes('org_api_only', AUDIT_LOG_PAGES.ORGANIZATION, ghesVersionsAuditLogData)
await filterAndUpdateGhes('business', AUDIT_LOG_PAGES.ENTERPRISE, ghesVersionsAuditLogData)
await filterAndUpdateGhes(
'business_api_only',
AUDIT_LOG_PAGES.ENTERPRISE,
ghesVersionsAuditLogData,
)
await filterAndUpdateGhes('user', AUDIT_LOG_PAGES.USER, ghesVersionsAuditLogData)
await filterAndUpdateGhes('organization', AUDIT_LOG_PAGES.ORGANIZATION, ghesVersionsAuditLogData)
await filterAndUpdateGhes('org_api_only', AUDIT_LOG_PAGES.ORGANIZATION, ghesVersionsAuditLogData)
Object.assign(auditLogData, ghesVersionsAuditLogData)

// We don't maintain the order of events as we process them so after filtering
Expand Down
Loading
Loading