Purpose
This guide explains how to generate a GitHub Personal Access Token and use it in Logsign USO to ingest GitHub organization audit log events (and a periodic inventory of members without two-factor authentication) for security analytics and forensic purposes, as part of the shipped GitHub Organization Audit Log API integration.
Important scope note: this integration calls GitHub's organization-level audit log endpoint (GET /orgs/{organization}/audit-log) plus the organization members endpoint (GET /orgs/{organization}/members?filter=2fa_disabled), both at the fixed public host api.github.com. It covers a single GitHub organization per configured source. GitHub's separate enterprise-level audit log (/enterprises/{enterprise}/audit-log, which aggregates every organization under a GitHub Enterprise account) is not called by this poller; if you need enterprise-wide coverage, a source must be configured per organization. Because the host is hardcoded to api.github.com, GitHub Enterprise Server (self-hosted GitHub) is not supported: there is no field to point the integration at a different host.
Prerequisites
Your GitHub organization must be on the GitHub Enterprise Cloud plan. The audit log endpoint this poller calls is restricted to that plan (GitHub's API marks it githubCloudOnly); on Free, Team, or a plan without Enterprise Cloud, the endpoint is not available and requests return 403/404.
The token used must be a classic Personal Access Token with the read:audit_log scope, and the GitHub account generating it must hold the organization owner role. This is a GitHub platform restriction, not a Logsign choice: GitHub does not offer a scoped "audit-log-only" role for this endpoint: the token is tied to a full org-owner account, all or nothing. If your organization can designate a dedicated bot/service-account owner for this token rather than a personal owner account, that is recommended so the token is not attached to an individual's account. Fine-grained personal access tokens are not confirmed to support this endpoint as of this writing; use a classic PAT.
Step 1: Generate a Personal Access Token
- Sign in to GitHub with an account that has the organization owner role in the target organization.
- Go to Settings > Developer settings > Personal access tokens > Tokens (classic).
- Click Generate new token (classic).
- Give it a name (for example, "Logsign USO integration") and choose an expiration. GitHub does not require a permanent token; set a realistic expiration and plan to rotate it, since an expired token stops collection with repeated
401errors until it is regenerated and updated in Logsign. - Under scopes, select
read:audit_log. No other scope is required for this integration. - Click Generate token and copy the value immediately. GitHub will not show it again.
Note down the organization's login/slug as well: it is the segment right after github.com/ in your organization's URL (for example, the org at https://github.com/my-org has the slug my-org).
Step 2: Configure the Integration in Logsign USO
Log in to Logsign USO and add a new GitHub (Organization Audit Log API) data source. You will be asked for:
| Field | Value |
|---|---|
| organization | The organization slug from Step 1 (for example my-org). Enter the slug only, not the full URL. Correct: my-org. Incorrect: https://github.com/my-org or github.com/my-org/; either form will make the request fail. |
| access_token | The classic PAT value from Step 1. Logsign sends it as Authorization: token <value>; enter only the token itself. |
| snapshot_interval_hours | Optional, default 24. Controls how often the members-without-2FA inventory (see below) is re-collected. Set to 0 to collect it on every poll. |
There is no host/region field: the integration always calls https://api.github.com, GitHub's single public API host. GitHub Enterprise Cloud does not use per-tenant hostnames for the REST API the way some SaaS products do.
What Gets Collected
The integration runs two independent collection modes on every poll:
- Audit log events (incremental): calls
GET /orgs/{organization}/audit-logwithinclude=all(so repository, Git, and workflow-level events are included, not just web UI actions) and pages forward using GitHub's cursor (Linkheader,rel="next") until caught up. Position is tracked by each event'screated_attimestamp, so a restart resumes from the last event actually processed rather than re-sending history. - Members without 2FA (periodic snapshot, not incremental): calls
GET /orgs/{organization}/members?filter=2fa_disabled. This endpoint has no time cursor: it always returns the organization's current full list of members without two-factor authentication, so the poller only runs it once persnapshot_interval_hours(default once a day) and re-sends the complete current list each time, rather than only "new" members. Expect the same member(s) to reappear in every snapshot for as long as their 2FA stays disabled; this is expected behavior, not duplicate data.
Use Case Coverage
| Use case | How it's covered |
|---|---|
| A member is promoted to organization Owner | Audit log action org.update_member where the new permission is admin/owner. |
| An Owner's role is downgraded to a regular member | Audit log action org.update_member where the new permission is member. Note this is a different action from a member being removed from the organization entirely (org.remove_member): a role downgrade and a full removal are two separate events and are reported separately. |
| A user outside the organization's domain is invited | Audit log actions org.invite_member (organization-level invite) and org.add_outside_collaborator (repository-level outside collaborator). |
| An account is created/kept without MFA enabled | GitHub's audit log has no single event for "account created without MFA". MFA status is a per-account attribute, not an auditable action. This use case is covered instead by the periodic members-without-2FA snapshot described above, which is not real-time: a newly created account without 2FA is reported on the next scheduled snapshot (at most snapshot_interval_hours later), not the moment it is created. |
Field mapping highlights: action → Event.Action, actor/actor_id → Source.UserName/Source.ID, actor_ip → Source.IP, user (the affected member) → TargetUser.Name, org_id → Organization.ID, repo/repository → Object.Name, and for the members snapshot, membership without 2FA is reported via Account.MultiFactorAuth.
Notes and Limits
- Enterprise Cloud only: the audit-log endpoint requires the GitHub Enterprise Cloud plan; other plans return
403/404regardless of credentials. - Single organization per source: one configured source covers one
organizationvalue. An enterprise account with multiple organizations needs one source per organization. - Rate limit: the audit-log endpoint has its own dedicated limit, separate from GitHub's general 5,000/hour PAT limit. Per GitHub's own August 2023 changelog announcement, this was set at 1,750 requests/hour per user+IP+organization; GitHub stated an intent to later tighten this to 15 requests/minute, but that later change was not independently reconfirmed as of this writing; treat the exact current figure as informational. Either way, the poller stays well under both bounds: at most 50 page requests (100 records/page) per polling cycle, with a 1-second pause between pages.
- Pagination: GitHub's standard
Link-header cursor (RFC 5988); the poller followsrel="next"until the response has no further page. - Default lookback: on first run there is no prior event history, so the poller starts from GitHub's
phrase=created:>=<now>equivalent going forward. Historical events from before the source was configured are not backfilled. - Unclassified event types: because
include=allis used, repository/Git/workflow-level audit actions outside the four use cases above also arrive through this source. Any action not explicitly mapped falls back to a genericIdentity / Other / Infoclassification rather than being dropped.
Troubleshooting / Error Codes
| Code | Likely cause | What to check |
|---|---|---|
| 401 | The PAT is invalid, expired, or was revoked. | Regenerate the classic PAT in GitHub and update the access_token field in Logsign. |
| 403 | (a) the PAT is missing the read:audit_log scope, (b) the token's account is not an organization owner, or (c) the organization is not on the GitHub Enterprise Cloud plan. | Check the token's scopes and the account's role in GitHub organization settings, and confirm the org's plan tier. |
| 404 | The organization value is wrong, or the audit-log endpoint is not exposed for this plan. | Confirm the organization slug matches the URL segment after github.com/, and confirm the Enterprise Cloud plan. |
| 429 | The endpoint's dedicated rate limit was hit. | No action needed: the poller logs the hit and picks up remaining records on the next scheduled poll; it does not currently implement an in-cycle retry/backoff. |