Inspiration

GitLab shows you who has access to a group or project, but it never shows you which access nobody is using. Over time, membership drifts from real work: people change teams, finish a project, or were over-provisioned on day one. That dormant, over-broad access is a textbook least-privilege violation, and every unused-but-live credential is one more layer an attacker can pivot through. Today, catching it means a human manually cross-referencing a members list against activity in a spreadsheet.

I also scanned the AI Catalog before building. Blast-radius, onboarding, vuln-tracing, and reviewer-routing agents were already saturated. Least-privilege / dormant-access had zero competitors, and it taps a dimension of Orbit nobody was using: access joined with activity.

What it does

Access Auditor is a published GitLab Duo agent that runs a least-privilege review automatically. Point it at a group or project and it:

  1. Uses okay. GitLab Orbit (the Knowledge Graph) to pull access (User -MEMBER_OF-> Group/Project).
  2. Pulls activity (AUTHORED work items, notes, MRs, plus MR APPROVED), scoped to that same group or project.
  3. Computes the gap and sorts every member into cohorts: recently active, stale, dormant (never active), new-to-verify, and service/bot accounts.
  4. Files one confidential access-review issue with a severity-ranked findings table, a Mermaid access-vs-activity map, and a remediation checklist, every finding backed by graph evidence. It is recency-aware, so a new hire who hasn't contributed yet is flagged to verify, not accused of a violation. It audits service and bot accounts separately (an idle bot token is its own attack vector). It annotates instance-admin and external-collaborator risk flags. And, it is privacy-safe: the report is filed confidential in a restricted review project, and every audited username is rendered in backticks so the audit never notifies the people it flags. That is a real action, not a chat answer.

How we built it

The custom agent is defined in one import-ready agents/agent.yml: the system prompt with verified Orbit query shapes, the built-in action tools (create_issue, create_issue_note, run_command), and the Orbit MCP tools (query_graph, get_graph_schema, list_commands, invoke_command).

Challenges we ran into

This was the hard part, and most of it was platform-level.

  • Orbit tools intermittently not reaching the published agent: I traced it to a two-flag mismatch: one feature flag governs the tool allow-list (legacy names); another governs what Orbit advertises at discovery (command names), and the runtime keeps only the intersection. When they disagree, the intersection is empty and no Orbit tools reach the model. I worked around it by granting both the legacy and command tool sets.
  • Duplicated members: a membership traversal came back as one user repeated up to the limit. Fixed by requesting format: "raw" and using aggregations.
  • Everyone looked dormant: Group activity queried via IN_GROUP returned almost nothing because the work is IN_PROJECT. Fixed with the CONTAINS rollup.
  • Roles are invisible in the graph: access_level is not queryable and the OWNER edge was unpopulated, so I pivoted to scope-based severity plus the two risk flags that are filterable (is_admin, is_external).
  • Reliability of the agent's own output: Early runs dropped whole cohorts, omitted usernames, or confabulated fake members when tools were briefly absent. I hardened the prompt with hard rules: cohorts must partition the full member set, never omit a username, and never fabricate data if the Orbit tools are missing.

Accomplishments that we're proud of

  • A working, public, published catalog agent that performs a real action against live Orbit data, not a demo that only runs locally.
  • It fills a genuine gap. Least-privilege / dormant-access had no competing entry in the catalog.
  • It is trustworthy by design: recency-aware so new hires are never falsely flagged, privacy-safe so the audit never pings the people it names, and every finding is auditable from a collapsible query-and-math trail in the issue.

What we learned

  • Orbit is far more than a code graph. The people and SDLC dimension (MEMBER_OF, AUTHORED, ASSIGNED) is there and is exactly what an access review needs.
  • The graph returns a minimal projection. Dates and roles are filterable even when they are not readable, which shaped the whole query strategy.
  • Making an LLM agent reliable at set math means not asking it to do set math in its head. Offloading to a tool was the difference between stalling and finishing.
  • Security tooling has to be safe to run. Where you file the report and how you render names are as important as the findings.

What's next for GitLab Access Auditor Agent

  • Role-based severity by pairing in the Members API, so an Owner with dormant access becomes Critical.
  • Scheduled, recurring audits via triggers (or flows), with re-audit of the new-member watchlist after the grace window.
  • Actionable remediation: recommend membership expires_at, and eventually a guarded one-click revoke or downgrade.
  • Broader signals and scope: more activity edges, and org-wide rollups across many groups in one run.

Built With

Share this project:

Updates