Skip to content

Latest commit

 

History

History
708 lines (561 loc) · 30.4 KB

File metadata and controls

708 lines (561 loc) · 30.4 KB

GitHub Identity Management Investigation for AgentBox

NETWORG internal — July 2025

How to handle GitHub authentication, Copilot licensing, and identity split for ephemeral AI coding containers.


1. Executive Summary

Core finding: GitHub Copilot CLI supports credential isolation via COPILOT_GITHUB_TOKEN — enabling a split where Copilot authenticates as one identity and git operates as another. However, GitHub's Terms of Service prohibit sharing a single Copilot seat across multiple users, making the "shared account" approach non-viable as designed.

Context: NETWORG uses GitHub Team plan (not Enterprise) with github-organization-management for custom Entra ID → GitHub sync — specifically to avoid the cost of GitHub Enterprise. EMU/SCIM are not available. Copilot Business ($19/user/month) IS available on the Team plan.

Key recommendations:

  1. Licensed developers → Standard OAuth flow via the AgentBox GitHub App. User's own ghu_* token powers both Copilot CLI and git. This is already designed in identity.md.

  2. Unlicensed developersPurchase a Copilot Business seat for each developer who will use AgentBox, even if they're "light-use". At $19/user/month, this is the only ToS-compliant approach. Alternatively, designate unlicensed users as Copilot-free and provide them with an AgentBox that has all other tools but no Copilot CLI.

  3. Copilot license detection → Use GET /orgs/{org}/members/{username}/copilot to check entitlement at spawn time. The existing github-organization-management app already has the GitHub App infrastructure and Entra ID ↔ GitHub identity mapping needed.

  4. Identity mapping → Already solved. The github-organization-management repo stores GitHub user IDs in Entra ID directory extension attributes. Extend this to drive AgentBox's spawn-time license checks.


2. Copilot License API

2.1 Available Endpoints

Endpoint Method Purpose
/orgs/{org}/copilot/billing GET Org-level Copilot subscription summary + feature policies
/orgs/{org}/copilot/billing/seats GET List all assigned Copilot seats (paginated)
/orgs/{org}/members/{username}/copilot GET Check if specific user has a Copilot seat
/orgs/{org}/copilot/billing/selected_users POST Add Copilot seats for specific users
/orgs/{org}/copilot/billing/selected_users DELETE Remove Copilot seats
/orgs/{org}/copilot/billing/selected_teams POST Add Copilot seats for entire teams
/orgs/{org}/copilot/billing/selected_teams DELETE Remove team Copilot access

Note: All endpoints are in public preview and subject to change.

2.2 Check Individual User Entitlement

This is the key endpoint for AgentBox's spawn flow:

curl -L \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer <TOKEN>" \
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/orgs/NETWORG/members/octocat/copilot

Response (200 — user has seat):

{
  "assignee": {
    "login": "octocat",
    "id": 1,
    "type": "User"
  },
  "assigning_team": {
    "name": "Justice League",
    "slug": "justice-league"
  },
  "pending_cancellation_date": null,
  "last_activity_at": "2024-01-15T10:30:00Z",
  "last_activity_editor": "vscode/1.77.3/copilot/1.86.82",
  "last_authenticated_at": "2024-01-15T10:30:00Z",
  "created_at": "2024-01-01T00:00:00Z",
  "plan_type": "business"
}

Response (404 — user does NOT have seat):

{
  "message": "Not Found"
}

Response (422 — Copilot not enabled for org):

{
  "message": "Copilot Business or Enterprise is not enabled for this organization"
}

2.3 Org Billing Summary

curl -L \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer <TOKEN>" \
  https://api.github.com/orgs/NETWORG/copilot/billing

Response:

{
  "seat_breakdown": {
    "total": 50,
    "added_this_cycle": 10,
    "pending_cancellation": 2,
    "pending_invitation": 3,
    "active_this_cycle": 45,
    "inactive_this_cycle": 5
  },
  "public_code_suggestions": "allow",
  "ide_chat": "enabled",
  "platform_chat": "enabled",
  "cli": "enabled",
  "seat_management_setting": "assign_selected",
  "plan_type": "business"
}

2.4 Required Permissions

Auth Method Required Scope Access Level
Classic PAT manage_billing:copilot or read:org Read-only endpoints
Classic PAT manage_billing:copilot or admin:org Write endpoints (add/remove seats)
GitHub App organization_copilot_seat_management: read Read-only
GitHub App organization_copilot_seat_management: write Full management

Important: Only organization owners can query these endpoints. The token must belong to (or act on behalf of) an org owner.

2.5 GitHub App Support

✅ GitHub Apps can access Copilot billing endpoints with the organization_copilot_seat_management permission. This is an organization-level permission set during app registration.

For AgentBox, the recommended approach is to add this permission to the existing github-organization-management GitHub App (which already has org-level access), or to the AgentBox GitHub App if it has org owner authorization.

2.6 Programmatic Seat Management

The API also supports adding/removing seats programmatically:

# Add a seat for a user on-demand
curl -L -X POST \
  -H "Authorization: Bearer <TOKEN>" \
  https://api.github.com/orgs/NETWORG/copilot/billing/selected_users \
  -d '{ "selected_usernames": ["newuser"] }'
# Response: { "seats_created": 1 }

# Remove a seat
curl -L -X DELETE \
  -H "Authorization: Bearer <TOKEN>" \
  https://api.github.com/orgs/NETWORG/copilot/billing/selected_users \
  -d '{ "selected_usernames": ["olduser"] }'
# Response: { "seats_cancelled": 1 }

This opens up a possible just-in-time seat provisioning model (see Section 6).


3. Identity Linking (Entra ID ↔ GitHub)

3.1 Current State: Already Solved by github-organization-management

The NETWORG/github-organization-management repository already implements Entra ID ↔ GitHub identity mapping. Key findings:

Architecture: ASP.NET Core 8.0 web app using:

  • Microsoft.Identity.Web for Entra ID authentication
  • Microsoft.Graph (v5.93) for Azure AD user management
  • Octokit (v14) for GitHub API

Identity mapping mechanism:

  • GitHub numeric user ID stored as long? in an Entra ID directory schema extension attribute on each User object
  • Extension attribute name configured via AzureAd:ExtensionAttributeName (e.g., extension_<appid>_githubId)
  • Mapping persisted in Entra ID — no separate database needed
  • Data model: GraphUserDto { Id: string (Entra object ID), GitHubId: long? (nullable GitHub user ID), Upn: string }
  • The GraphUserDto.From(User) factory reads User.AdditionalData[ExtensionAttributeName] and parses to long

Account linking flow:

  1. User signs in with Entra ID (OpenID Connect)
  2. User clicks "Link GitHub Account" → redirected to GitHub OAuth (read:user, user:email scopes)
  3. App extracts GitHub numeric user ID from OAuth response
  4. GitHub ID written to Entra ID user extension attribute via Microsoft Graph PATCH (User.ReadWrite.All application permission)
  5. Mapping is immediately available to all systems reading from Entra ID

Sync process (/api/sync):

  • Gets all GitHub App installations across orgs
  • For each org: reads team descriptions (format: Entra: <group-id>) to map teams → Entra ID groups
  • Resolves GitHub IDs from Entra ID extension attributes
  • Invites missing users, removes departed users, syncs team memberships
  • Idempotent, stateless, containerized

3.2 Options Analysis

Approach Status Pros Cons
Entra ID extension attribute (current) ✅ Already implemented No extra database, single source of truth, works with Graph API Requires user to self-link once
GitHub EMU (Enterprise Managed Users) ❌ Not applicable Full SSO, SCIM provisioning Requires GitHub Enterprise Cloud ($$$). NETWORG uses GitHub Team plan specifically to avoid Enterprise costs. EMU accounts also can't interact with public repos.
SCIM provisioning ❌ Not available Automatic user lifecycle Only available with GitHub Enterprise Cloud EMU, which NETWORG doesn't have
Manual mapping table in API ❌ Redundant Simple Duplicates what Entra extension already does
GitHub API email lookup ⚠️ Unreliable No user action needed Email must be public on GitHub; many users hide it

3.3 Recommendation: Build on Existing Infrastructure

Do not build a new identity mapping system. The github-organization-management app already:

  1. ✅ Links Entra ID users to GitHub accounts
  2. ✅ Stores the mapping in Entra ID (single source of truth)
  3. ✅ Has a GitHub App with org-level access
  4. ✅ Has Microsoft Graph integration for reading user attributes

AgentBox should:

  • Read the same Entra ID extension attribute at spawn time to resolve GitHub username
  • Use the resolved GitHub username to check Copilot entitlement via the API
  • Require users to link their GitHub account (via the existing linking UI) before spawning an AgentBox

4. Split Identity Analysis

4.1 Technical Feasibility: ✅ Possible

GitHub Copilot CLI uses a priority-based token lookup that is completely independent from git credentials:

Priority Source Used For
1 (highest) COPILOT_GITHUB_TOKEN env var Copilot CLI only
2 GH_TOKEN env var Copilot CLI + gh CLI
3 GITHUB_TOKEN env var Copilot CLI + gh CLI
4 OAuth token from keychain (copilot login) Copilot CLI only
5 (lowest) gh auth token fallback Copilot CLI via gh

Git credentials are entirely separate:

  • Git uses credential.helper, GIT_ASKPASS, or inline URL tokens
  • GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL control commit identity
  • git push authentication uses whatever credential helper or token is configured

This means you CAN do:

# Copilot CLI authenticates as shared-account (for license)
export COPILOT_GITHUB_TOKEN=ghu_shared_account_token

# Git authenticates as the real developer
export GIT_AUTHOR_NAME="Real Developer"
export GIT_AUTHOR_EMAIL="real@networg.com"
export GIT_COMMITTER_NAME="Real Developer"
export GIT_COMMITTER_EMAIL="real@networg.com"

# Git push uses a different credential
git config credential.helper '!f() { echo "username=x-access-token"; echo "password=ghu_real_user_token"; }; f'

4.2 Token Types for Copilot CLI

Token Type Prefix Works with Copilot CLI? Notes
User access token (OAuth) ghu_* ✅ Yes Preferred method
OAuth device flow token gho_* ✅ Yes Via copilot login
Fine-grained PAT github_pat_* ✅ Yes Must have "Copilot" permission
Classic PAT ghp_* ❌ No Silently ignored
Installation token ghs_* ❌ No Cannot use Copilot

4.3 The Shared Account Problem

For unlicensed users, the split identity approach requires:

  1. Copilot CLI → authenticated as a shared/licensed GitHub account
  2. Git operations → authenticated as the real developer

Scenario A: Real user HAS a GitHub account but NO Copilot license

COPILOT_GITHUB_TOKEN = shared_account_ghu_token  (Copilot)
GH_TOKEN = real_user_ghu_token                    (git push, gh CLI)
GIT_AUTHOR_EMAIL = real@networg.com               (commit identity)

✅ Technically works. Git push uses real user's token. Copilot uses shared account.

Scenario B: Real user has NO GitHub account at all

COPILOT_GITHUB_TOKEN = shared_account_ghu_token  (Copilot)
# No git push token available — who pushes?
# Option 1: GitHub App installation token (ghs_*) — acts as the app
# Option 2: Shared account pushes — but commits attributed to shared account

⚠️ Possible but messy. Commits can be attributed via --author but push identity is the shared account or app.

4.4 Terms of Service Analysis: 🚫 Shared Seat is Prohibited

GitHub Copilot is licensed per-user, non-transferable.

From GitHub's Copilot Product Specific Terms and general Terms of Service:

  • Each Copilot license is tied to a specific GitHub user account
  • "Account" means a personal account: one person = one account
  • Sharing account credentials with multiple people violates GitHub ToS Section A.1
  • Using a shared/bot account to proxy Copilot access to multiple developers = seat sharing

Risks of the shared account approach:

  1. Account suspension — GitHub may disable the shared account for ToS violation
  2. License revocation — Copilot entitlement can be revoked
  3. Audit trail corruption — Copilot usage telemetry attributed to wrong user
  4. No accountability — if AI generates problematic code, whose Copilot session was it?

Verdict: The shared Copilot account approach is a ToS violation regardless of how cleanly we split git identity.

4.5 The git commit --author Approach

Even if we used a shared account for everything, git allows overriding commit authorship:

git commit --author="Real Developer <real@networg.com>" -m "feat: add feature"
# or via environment variables:
export GIT_AUTHOR_NAME="Real Developer"
export GIT_AUTHOR_EMAIL="real@networg.com"

Limitations:

  • Author ≠ Committer: git log shows both. The committer would still be the shared account
  • GPG signing: can't sign as the real developer if using shared account's token
  • GitHub attribution: PRs pushed by shared account show shared account as contributor
  • No verified badge: commits won't show as "verified" for the real developer

4.6 Credential Helper Routing

For scenarios where different repos need different credentials, git supports per-URL credential routing:

# In container's .gitconfig
[credential "https://github.com"]
    helper = !f() { echo "username=x-access-token"; echo "password=${REAL_USER_TOKEN}"; }; f

# Copilot uses a completely separate env var
# COPILOT_GITHUB_TOKEN=<shared_token>

This works because:

  • Git credential helpers only affect git clone/push/fetch
  • Copilot CLI reads COPILOT_GITHUB_TOKEN independently
  • The two credential stores never interact

5. GitHub App Token Architecture

5.1 Token Types Available to AgentBox

Token How Obtained Lifespan Copilot? Git Push? Billing API?
User access token (ghu_*) OAuth web/device flow 8hr (refreshable) Only if user is org owner
Installation token (ghs_*) JWT exchange by app 1 hour
Fine-grained PAT (github_pat_*) Manual creation Configurable ✅ (with scope) ✅ (with scope)
Classic PAT (ghp_*) Manual creation Long-lived

5.2 Licensed User Flow (Recommended — No Changes Needed)

The current design in identity.md is correct:

User signs in → Entra ID
  ↓
AgentBox API checks Entra ID extension attribute → gets GitHub username
  ↓
API checks: GET /orgs/NETWORG/members/{username}/copilot → 200 OK
  ↓
Standard GitHub App OAuth flow → user authorizes → ghu_* token
  ↓
Container spawns with:
  GH_TOKEN=ghu_user_token     ← powers Copilot CLI + git + gh CLI
  (user.name/user.email from Entra ID profile)

5.3 Unlicensed User — Option Analysis

Option Feasibility ToS Compliant? UX
A. Buy each user a Copilot seat ✅ Simple ✅ Yes User gets full Copilot
B. Just-in-time seat provisioning ✅ Possible ✅ Yes Auto-assign on spawn, auto-revoke on destroy
C. Shared Copilot account ✅ Technical 🚫 No Split credentials work, but violates ToS
D. No Copilot for unlicensed users ✅ Simple ✅ Yes Users get all tools except Copilot CLI
E. Copilot Free tier ⚠️ Limited ✅ Yes 2,000 completions/month, limited features

Option B (Just-in-Time Provisioning) is particularly interesting:

Spawn request → check Copilot entitlement
  ↓ (no seat)
API calls: POST /orgs/NETWORG/copilot/billing/selected_users
  { "selected_usernames": ["the-user"] }
  ↓
Seat assigned → user can now use Copilot
  ↓
Container destroyed → optionally remove seat:
  DELETE /orgs/NETWORG/copilot/billing/selected_users
  { "selected_usernames": ["the-user"] }

Caveat: Copilot billing is per-seat per-month. Removing a seat mid-cycle still bills for the full month. So JIT only saves money if the user won't need Copilot again that billing cycle.

5.4 Installation Token for Git-Only Operations

For users who don't need Copilot but do need git access:

AgentBox GitHub App → JWT signed with private key
  ↓
POST /app/installations/{id}/access_tokens
  { "repositories": ["target-repo"], "permissions": { "contents": "write", "pull_requests": "write" } }
  ↓
Returns ghs_* token (1-hour expiry)
  ↓
Container uses ghs_* for git clone/push
  (commits show as "AgentBox[bot]" unless GIT_AUTHOR overridden)

6. Recommended Architecture

6.1 Spawn Flow Decision Tree

User requests AgentBox spawn
  │
  ├── Entra ID authentication (existing)
  │
  ├── Read GitHub username from Entra ID extension attribute
  │   └── Not linked? → Redirect to github-organization-management linking UI
  │
  ├── Check Copilot entitlement:
  │   GET /orgs/NETWORG/members/{username}/copilot
  │
  ├── 200 OK (has seat) ─────────────────────────────┐
  │   │                                               │
  │   ├── GitHub App OAuth flow → ghu_* token         │
  │   │                                               │
  │   └── Spawn container:                            │
  │       GH_TOKEN=ghu_*            (all operations)  │
  │       git user.name=<from Entra>                  │
  │       git user.email=<from Entra>                 │
  │       → Full Copilot CLI access ✅                │
  │                                                   │
  ├── 404 (no seat) ─────────────────────────────────┐
  │   │                                               │
  │   ├── Option A: Show "No Copilot license" warning │
  │   │   └── Spawn without Copilot CLI               │
  │   │       (all other tools work: gh, az, etc.)    │
  │   │                                               │
  │   ├── Option B: JIT seat provisioning             │
  │   │   └── POST to assign seat → then OAuth flow   │
  │   │       → spawn with full Copilot               │
  │   │                                               │
  │   └── Option C: Copilot Free tier                 │
  │       └── User's GitHub Free plan includes        │
  │           limited Copilot — may be sufficient     │
  │                                                   │
  └── 422 (no org Copilot subscription) ──────────────┘
      └── Copilot not available for this org

6.2 Container Configuration by User Class

Licensed user container:

# entrypoint.sh additions
export GH_TOKEN="${USER_GH_TOKEN}"           # ghu_* from OAuth
export GITHUB_TOKEN="${USER_GH_TOKEN}"
export COPILOT_GITHUB_TOKEN="${USER_GH_TOKEN}"

# Git identity from Entra ID profile
git config --global user.name "${ENTRA_DISPLAY_NAME}"
git config --global user.email "${ENTRA_EMAIL}"

# Login to gh CLI
echo "${USER_GH_TOKEN}" | gh auth login --with-token

Unlicensed user container (no Copilot):

# Only git operations via GitHub App installation token or user OAuth
export GH_TOKEN="${USER_GH_TOKEN}"  # or ghs_* installation token

# Git identity from Entra ID profile
git config --global user.name "${ENTRA_DISPLAY_NAME}"
git config --global user.email "${ENTRA_EMAIL}"

# Copilot CLI disabled — no COPILOT_GITHUB_TOKEN set
# Optional: alias copilot to a message explaining why it's unavailable
alias copilot='echo "Copilot CLI requires a Copilot Business license. Contact your admin."'

6.3 API Backend License Check (Pseudocode)

// In the AgentBox API (ASP.NET Core)
public async Task<SpawnResult> SpawnAgentBox(SpawnRequest request)
{
    // 1. Get GitHub user ID from Entra ID extension attribute
    //    (same extension attribute used by github-organization-management)
    var entraUser = await _graph.Users[request.EntraUserId]
        .GetAsync(r => r.QueryParameters.Select = new[] { "id", "displayName", "mail", extensionAttr });

    // Extension stores numeric GitHub user ID as string — same as GraphUserDto.GitHubId
    var githubIdStr = entraUser.AdditionalData.GetValueOrDefault(extensionAttr)?.ToString();
    if (string.IsNullOrEmpty(githubIdStr) || !long.TryParse(githubIdStr, out var githubId))
        return SpawnResult.Error("GitHub account not linked. Please link at github-org-mgmt URL.");

    // 2. Resolve GitHub username from numeric ID (Octokit)
    var githubUser = await _github.User.Get(githubId);
    var githubLogin = githubUser.Login;  // e.g. "octocat"

    // 3. Check Copilot entitlement
    bool hasCopilot = false;
    try
    {
        var seat = await _github.Organization.Copilot
            .GetSeatDetails("NETWORG", githubLogin);
        hasCopilot = seat.PendingCancellationDate == null;
    }
    catch (NotFoundException)
    {
        hasCopilot = false;
    }

    // 4. Get user's OAuth token (ghu_*) via stored refresh token
    var ghToken = await _tokenStore.GetOrRefreshToken(request.EntraUserId);

    // 5. Spawn container with appropriate config
    return await SpawnContainer(new ContainerConfig
    {
        GhToken = ghToken,
        CopilotEnabled = hasCopilot,
        GitAuthorName = entraUser.DisplayName,
        GitAuthorEmail = entraUser.Mail,
        GitHubLogin = githubLogin,
    });
}

7. Integration with github-organization-management

7.1 What to Reuse

Component How AgentBox Uses It
Entra ID extension attribute Read githubId (numeric) at spawn time → Octokit User.Get(id) → get login
GraphUserDto model Same pattern: { Id, GitHubId: long?, Upn } — AgentBox reads the same attribute
Account linking UI Redirect users who haven't linked yet before they can spawn
GitHub App Add organization_copilot_seat_management permission for license checks
Microsoft Graph integration Already configured with User.ReadWrite.All; AgentBox needs only User.Read.All
Sync process Ensures org membership is current; AgentBox relies on users being org members

7.2 Changes Needed to github-organization-management

  1. Add Copilot billing permission to the GitHub App registration:

    • Permission: organization_copilot_seat_managementread (or write for JIT provisioning)
    • Org owners will be prompted to approve the new permission
  2. Optional: Expose an API endpoint for Copilot license checks:

    GET /api/copilot-status/{entra-user-id}
    → { "github_username": "octocat", "has_copilot": true, "plan_type": "business" }
    

    This avoids AgentBox needing its own GitHub App with billing permissions.

  3. Optional: Add JIT seat management endpoint:

    POST /api/copilot-seat/{entra-user-id}/assign
    DELETE /api/copilot-seat/{entra-user-id}/revoke
    

7.3 Alternative: AgentBox GitHub App Does Everything

If you prefer not to modify github-organization-management, the AgentBox GitHub App can:

  • Request organization_copilot_seat_management: read permission
  • Read the Entra ID extension attribute directly via Microsoft Graph
  • Check Copilot entitlement via the GitHub API

This requires the AgentBox App to be installed at the org level with billing permissions. The trade-off is duplication of GitHub App infrastructure.

7.4 Recommended Integration Model

┌───────────────────────────┐     ┌──────────────────────────────────┐
│  github-org-management    │     │  AgentBox API                    │
│                           │     │                                  │
│  - Account linking UI     │     │  - Entra ID auth                 │
│  - Entra ↔ GitHub mapping │     │  - Spawn form                    │
│  - Team/org sync          │     │  - License check                 │
│  - [NEW] Copilot status   │◄────│  - Container provisioning        │
│    API endpoint            │     │  - Token management              │
│                           │     │                                  │
│  GitHub App:              │     │  GitHub App:                     │
│  - org management perms   │     │  - contents, PRs, issues         │
│  - [NEW] copilot billing  │     │  - copilot (user access)         │
│    read                   │     │  - OAuth device flow             │
│  - members read/write     │     │                                  │
└───────────────────────────┘     └──────────────────────────────────┘
         │                                     │
         │ Microsoft Graph                     │ Microsoft Graph
         ▼                                     ▼
┌─────────────────────────────────────────────────────┐
│              Entra ID (networg.com)                  │
│                                                      │
│  User objects with extension attribute:              │
│  extension_<app-id>_githubId = "12345678"           │
│                                                      │
└─────────────────────────────────────────────────────┘

8. Open Questions

8.1 Business Decisions Needed

Question Options Impact
What to do with unlicensed users? A) Buy seats, B) JIT provisioning, C) No Copilot, D) Copilot Free Cost vs. feature parity
JIT seat provisioning cost model? Assign on spawn / revoke on destroy vs. keep seat assigned Billing is per-month regardless of mid-cycle changes
Who pays for extra seats? Org budget vs. project budget vs. per-user chargeback Finance decision
Copilot Free tier sufficient? 2,000 completions/month, limited models May be enough for light-use developers

8.2 Technical Questions

Question Status Notes
What's the current Copilot seat count and utilization? Check via API GET /orgs/NETWORG/copilot/billing shows seat_breakdown
Can the GitHub App permission be upgraded without reinstalling? Yes — org owners approve GitHub prompts approval for new permissions
Does COPILOT_GITHUB_TOKEN work with @github/copilot npm package? Needs testing The npm package (copilot CLI in our Dockerfile) may have different auth paths than gh copilot
Copilot Business availability on GitHub Team plan? ✅ Confirmed available Copilot Business is for "organizations on GitHub Free or GitHub Team plan" per GitHub docs

8.3 Testing Needed

  1. Verify COPILOT_GITHUB_TOKEN isolation in an actual AgentBox container:

    # In container:
    export COPILOT_GITHUB_TOKEN=ghu_user_a_token
    export GH_TOKEN=ghu_user_b_token
    copilot  # Should authenticate as user_a
    gh api user  # Should authenticate as user_b
    git push  # Should authenticate as user_b
  2. Verify Copilot license check API with the existing GitHub App:

    # Get installation token, then:
    curl -H "Authorization: token ghs_xxx" \
      https://api.github.com/orgs/NETWORG/members/USERNAME/copilot
  3. Test the @github/copilot npm package auth behavior — the Dockerfile installs @github/copilot globally. Verify it respects the same env var priority as gh copilot.

  4. Verify Copilot Free tier works without org seat assignment — if a user has GitHub Free, they get limited Copilot. Does this work in the CLI context without an org seat?


Appendix A: Token Priority Reference

Copilot CLI Token Resolution Order

1. COPILOT_GITHUB_TOKEN     ← Highest priority (Copilot-specific)
2. GH_TOKEN                 ← Shared with gh CLI
3. GITHUB_TOKEN             ← Shared with gh CLI, CI/CD
4. System keychain           ← From `copilot login` / `gh auth login`
5. gh auth token fallback   ← Lowest priority

Git Credential Resolution Order

1. URL-embedded token       ← https://x-access-token:TOKEN@github.com/...
2. GIT_ASKPASS              ← Script that outputs credentials
3. credential.helper        ← Configured helper (store, cache, osxkeychain)
4. SSH key                  ← For git@github.com:... URLs

Identity (Commit Author/Committer) Resolution

1. GIT_AUTHOR_NAME/EMAIL + GIT_COMMITTER_NAME/EMAIL  ← Environment vars
2. git config user.name/email                          ← .gitconfig
3. --author="Name <email>" flag                        ← Per-commit override

Appendix B: Copilot API Quick Reference

# Check if a user has Copilot (the key endpoint for AgentBox)
GET /orgs/{org}/members/{username}/copilot
# 200 = has seat, 404 = no seat

# List all seats (paginated)
GET /orgs/{org}/copilot/billing/seats?per_page=100&page=1

# Org billing summary
GET /orgs/{org}/copilot/billing

# Add seat for user
POST /orgs/{org}/copilot/billing/selected_users
Body: { "selected_usernames": ["user1"] }

# Remove seat
DELETE /orgs/{org}/copilot/billing/selected_users
Body: { "selected_usernames": ["user1"] }

# Required headers for all:
Accept: application/vnd.github+json
Authorization: Bearer <token>
X-GitHub-Api-Version: 2022-11-28