Generated banner for Gitlab
Generated logo for Gitlab

Gitlab

MicrosoftOfficialPublished by Microsoft

  • Skill
  • Tool integration
  • Software development
  • MIT
  • Low risk
  • Has scripts

From the author

Manage GitLab merge requests and pipelines with a Python CLI
— the description by Microsoft

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The GitLab skill provides a repository-local Python interface for interacting with GitLab.com or self-managed GitLab instances. It enables users to perform common development tasks such as listing and updating merge requests, triggering pipelines, and inspecting job logs directly from the command line. The tool supports both OAuth and legacy personal access token authentication modes and includes optional audit logging for tracking API operations.

What it does

  • Authenticates via OAuth or legacy personal access tokens
  • Lists, creates, and updates merge requests
  • Retrieves merge request notes and comments
  • Triggers pipelines for specific branches or tags
  • Lists pipeline jobs and retrieves raw job logs
  • Provides structured audit logging for API requests

When to use it

  • When you need to automate GitLab interactions within a local development workflow
  • When you prefer a command-line interface for managing merge requests and pipelines
  • When you need to extract specific data fields from GitLab resources for reporting
  • When you require an auditable trail of API operations for internal tracking

Advantages

  • Supports concise output filtering using the --fields parameter
  • Integrates with existing git repositories via automatic project detection
  • Offers secure OAuth authentication with PKCE support
  • Provides a structured JSON Lines audit trail for operations

Usage examples

“List all open merge requests for the current project.”
The CLI outputs a list of open merge requests, displaying the IID, title, and author name for each.
“Get the job logs for pipeline 12345.”
The CLI retrieves and prints the raw log output for the specified pipeline job.
“Create a new merge request from a JSON payload.”
The CLI submits the provided JSON data to create a new merge request on the configured GitLab project.
“Add a comment to merge request 42.”
The CLI posts the provided text as a comment on the specified merge request.

Install

Documentation by Microsoft

GitLab Skill

Overview

Use this skill to inspect and update GitLab merge requests, notes, pipelines, and job logs against GitLab.com or self-managed GitLab instances.

This skill is the repository-local Python workflow for GitLab tasks. It is not the official GitLab MCP server integration surface.

This first hve-core implementation is Python-only. Run the CLI through python scripts/gitlab.py and prefer --fields for read operations to keep output concise.

Prerequisites

The skill requires Python 3.11 or later.

Set these environment variables before running any command:

VariableRequiredExamplePurpose
GITLAB_URLYeshttps://gitlab.comGitLab instance URL
GITLAB_AUTH_MODEOptionaloauthoauth or explicit legacy-token compatibility mode
GITLAB_OAUTH_CLIENT_IDOAuthApplication IDNon-confidential OAuth application ID; no client secret
GITLAB_TOKENLegacy modeglpat-...PAT accepted only with GITLAB_AUTH_MODE=legacy-token
GITLAB_PROFILENodefaultNamed OAuth profile
GITLAB_TOKEN_STORENopathOAuth store override; parent must be owner-only on POSIX
GITLAB_ALLOW_INSECURENo1Permit explicit loopback HTTP development endpoints only
XDG_DATA_HOMENopathPOSIX data root used for the default OAuth store
LOCALAPPDATANopathReserved Windows data root; OAuth persistence currently fails closed on Windows
GITLAB_PROJECTNogroup/projectProject path or numeric project ID

If GITLAB_PROJECT is not set, the script attempts to detect the project from git remote get-url origin. Set the variable explicitly when you are not in a git repository or when you want to target a different project.

Operational Variables
VariableRequiredPurpose
GITLAB_AUDIT_LOGNoPath to a JSON Lines audit log. When set, every request is audited (see Audit Logging).
GITLAB_AUDIT_ACTORNoOverrides the recorded actor identity (for example, a CI service principal).
GITLAB_DEBUGNoSet to 1 to print a redacted traceback on failure. Never disables redaction.
Audit Logging

When GITLAB_AUDIT_LOG is set, the script writes a structured JSON Lines audit trail for every REST API and OAuth form request. Auditing is fail-closed and write-ahead:

  • An attempt record is written before the request is sent. If the audit log cannot be written, the operation is aborted and nothing is sent to GitLab.

  • An outcome record (success or error, with HTTP status on failure) is written after the request completes.

REST records include a UTC timestamp, actor, auth mode, normalized origin, operation, HTTP method, and event. OAuth records use bounded operation and failure kinds without actor, origin, client, profile, code, body, or free-form error fields. Resource paths, queries, profile names, and profile contents are excluded. The optional audit sink is operationally sensitive because origins can identify tenants or internal hosts. Place it at an access-controlled path and retain it only as long as operations require. Tokens and authorization headers are never written. Audit failures after the request emit a warning without altering the result.

Credential Rotation

OAuth is the default. Register a non-confidential user-, group-, or self-managed instance-owned application with redirect URI http://127.0.0.1:8766/callback and the api scope. Set only GITLAB_URL and GITLAB_OAUTH_CLIENT_ID, then run auth login or the human-assisted auth device-login. Browser login requires a rotating refresh token; access tokens refresh before expiry and replacements are stored atomically. Device login can return an access-token-only profile and then requires another device login after expiry. A provider/local crash window can still require re-login.

The default store is $XDG_DATA_HOME/hve-core/gitlab/gitlab-token.json on POSIX, falling back to ~/.local/share/hve-core/gitlab/gitlab-token.json. POSIX store and lock files use mode 0600 inside a dedicated owner-only directory. OAuth profile persistence fails closed on Windows because Python 3.11 mode bits cannot enforce an owner-only DACL. Use explicit legacy-token mode on Windows until a protected OAuth backend is available.

PAT authentication remains available through GITLAB_AUTH_MODE=legacy-token. Device authorization requires user approval on a browser-capable device and is not unattended workload identity.

Quick Start

Export your environment variables, then run a read command with --fields.

export GITLAB_URL="https://gitlab.com"
export GITLAB_OAUTH_CLIENT_ID="your-application-id"
export GITLAB_PROJECT="group/project"

python scripts/gitlab.py auth login
python scripts/gitlab.py mr-list opened --fields iid,title,author.name

Read pipeline jobs for a known pipeline:

python scripts/gitlab.py pipeline-jobs 12345 --fields id,name,status,stage

Parameters Reference

Common Option
ParameterApplies ToExampleDescription
--fieldsmr-list, mr-get, mr-notes, pipeline-get, pipeline-jobs--fields iid,title,stateExtract specific fields with dot notation and print concise tabular or key-value output
Commands
CommandArgumentsDescription
auth loginNoneAuthenticate with Authorization Code and PKCE
auth device-loginNoneAuthenticate through human-assisted device authorization
auth statusNonePrint secret-free local profile status
auth logoutNoneDelete one local profile without revoking server authorization
mr-list[state] [max]List merge requests, defaulting to all states and 20 results
mr-get<mr-iid>Get one merge request by project-scoped IID
mr-create<json> or stdinCreate a merge request from a JSON payload
mr-update<mr-iid> <json> or stdinUpdate merge request fields from a JSON payload
mr-comment<mr-iid> <body> or stdinAdd a comment to a merge request
mr-notes<mr-iid> [max]List merge request notes, excluding system notes when using --fields
pipeline-get<pipeline-id>Get one pipeline by numeric ID
pipeline-run<branch-or-tag>Trigger a pipeline for a branch or tag
pipeline-jobs<pipeline-id>List jobs for a pipeline
job-log<job-id>Print raw log output for a job

Script Reference

List recent open merge requests:

python scripts/gitlab.py mr-list opened --fields iid,title,author.name,user_notes_count

Get one merge request:

python scripts/gitlab.py mr-get 42 --fields iid,title,state,source_branch,target_branch

Create a merge request from inline JSON:

python scripts/gitlab.py mr-create '{
  "source_branch": "feature/add-auth",
  "target_branch": "main",
  "title": "feat(auth): add OAuth login"
}'

Add a merge request comment from standard input:

echo "CI passed. Ready for review." | python scripts/gitlab.py mr-comment 42

Inspect a failed pipeline:

python scripts/gitlab.py pipeline-get 12345 --fields id,status,web_url
python scripts/gitlab.py pipeline-jobs 12345 --fields id,name,status,stage
python scripts/gitlab.py job-log 67890

Troubleshooting

SymptomCauseResolution
GITLAB_URL is not setRequired environment variable missingExport GITLAB_URL before running the script
GITLAB_TOKEN is not set for legacy-token modeExplicit legacy mode has no PATExport a PAT only with GITLAB_AUTH_MODE=legacy-token
GITLAB_TOKEN must not be set in oauth modeLegacy PAT leaked into the OAuth configurationRemove GITLAB_TOKEN or explicitly select legacy-token mode
OAuth persistence is unavailable on WindowsNo protected Windows profile-store backendUse explicit legacy-token mode or run OAuth from POSIX
GITLAB_OAUTH_CLIENT_ID is not setOAuth application is not configuredSet the public client ID, then run auth login
Callback listener cannot bindPort 8766 is unavailable or not forwardedFree the port or use auth device-login
timed out waiting for the GitLab OAuth token store lockAnother process holds the profile-store lockWait for the other command to finish, then retry
GitLab device response has invalid verification_uriThe verification URI is unsafe to display or does not match GITLAB_URLConfirm GITLAB_URL matches the instance that serves device verification
cannot parse git remote URLProject autodetection failedSet GITLAB_PROJECT explicitly
HTTP 401 or HTTP 403Credential is invalid or lacks accessRe-login for OAuth; rotate the PAT in explicit legacy mode
HTTP 404Wrong project, MR IID, pipeline ID, or job IDVerify GITLAB_PROJECT and confirm the numeric identifiers
expected numeric IDNon-numeric value passed to an ID argumentUse project MR IID values and numeric pipeline or job IDs
python3 is required or syntax errors on launchUnsupported interpreterRun the script with Python 3.11 or later

GitLab uses MR IIDs such as !42 inside a project. This skill expects the numeric IID, not the global merge request ID.

Files

SECURITY.md


title: GitLab Skill Security Model description: STRIDE threat model for the GitLab skill covering API egress, credentials, git project resolution, caller input, the OAuth callback listener, and profile storage author: microsoft/hve-core ms.date: 2026-08-18 ms.topic: reference estimated_reading_time: 11 keywords:

  • security

  • STRIDE

  • gitlab

  • rest cli

  • threat model


<!-- markdownlint-disable-file -->

GitLab Skill Security Model

This document records the STRIDE threat model for the GitLab skill (scripts/gitlab.py). The model is organized by trust bucket: CLI → GitLab API (B1), Credential configuration (B2), Git remote subprocess / project resolution (B3), CLI caller process (B4), Browser → fixed loopback callback (B5), and OAuth profile store and lock (B6). Each bucket enumerates all six STRIDE categories with the in-code mitigations that address them. Assets and adversaries are enumerated first. Acknowledged enterprise readiness gaps are listed at the end.

The skill is a standard-library-only CLI with provider-local OAuth and credential modules. OAuth mode runs a bounded loopback listener for PKCE or a human-assisted device flow and persists profile-bound tokens in a POSIX mode-0600 store. OAuth persistence fails closed on Windows until a protected backend exists. Explicit legacy mode reads a PAT from the environment. The CLI also spawns one read-only git remote get-url origin subprocess when GITLAB_PROJECT is unset.

See also: repo-wide STRIDE model. This skill participates in the repository-wide threat model at docs/security/security-model.md and is registered in its Skill Security Models section.

Executive Summary

The GitLab skill uses public-client OAuth with PKCE or human-assisted device authorization by default. Short-lived access tokens refresh through rotating refresh tokens stored in an instance/client-bound POSIX mode-0600 profile store. Explicit legacy-token mode retains PAT support. All OAuth and API egress uses TLS and the hardened no-redirect opener. Residual risks include the provider/local refresh-commit crash window, same-user access to the token store, and server-side authorization that local logout does not revoke.

Security Posture Overview
DimensionValue
Runtime surfaceREST CLI (stdlib only); OAuth loopback/device flow; token store; read-only git subprocess
Trust bucketsB1 API, B2 config, B3 git remote, B4 caller, B5 callback, B6 profile store
CredentialsOAuth Bearer access/refresh tokens by default; explicit PAT legacy mode
Network egressHTTPS to GitLab OAuth/API endpoints (no-redirect); fixed HTTP loopback callback
Open residual gaps8 (EoP-Med: skill cannot revoke a leaked token)

Contents

  • System Description

  • Trust Boundaries

  • Assets

  • Adversaries

  • Bucket B1: CLI → GitLab API

  • Bucket B2: Credential profiles and environment

  • Bucket B3: Git remote subprocess / project resolution

  • Bucket B4: CLI caller process

  • Bucket B5: Browser → fixed loopback OAuth callback

  • Bucket B6: OAuth profile store and lock

  • Enterprise Readiness Gaps

  • References

System Description

Components
  1. scripts/gitlab.py — CLI entry point, API transport, command dispatch, and audit logging.

  2. scripts/_gitlab_oauth.py — public-client PKCE, device authorization, token parsing, and refresh exchange.

  3. scripts/_gitlab_credentials.py — profile validation, POSIX mode-0600 persistence, Windows fail-close, and cross-process locking.

  4. Hardened opener (_OPENER / _NoRedirect) — enforces TLS, refuses 30x redirects, and caps response bodies.

  5. git remote get-url origin subprocess — read-only project resolution when GITLAB_PROJECT is unset.

Data Flow
flowchart TD
    subgraph HOST["Operator Workstation / Runner (trust zone)"]
        CLI["gitlab.py CLI"]
        ENVCRED["URL / auth mode / public client ID<br/>or explicit legacy PAT"]
        GIT["git remote get-url origin<br/>(argv, no shell)"]
        LOOP["Fixed callback<br/>127.0.0.1:8766/callback"]
        STORE["OAuth profile store + lock<br/>(0700 dir, 0600 files)"]
        OUT["JSON / redacted job traces / audit log"]
    end
    subgraph BROWSER["User browser (authorization boundary)"]
        CONSENT["GitLab consent page"]
    end
    subgraph GL["GitLab Instance (network boundary)"]
        OAUTH["OAuth authorize / device / token endpoints"]
        API["GitLab REST API + CI job traces"]
    end
    CLI -->|"reads per invocation"| ENVCRED
    CLI -->|"resolve project on cache miss"| GIT
    CLI -->|"open authorize URL + PKCE challenge"| CONSENT
    CONSENT -->|"code + state over HTTP loopback"| LOOP
    LOOP -->|"validated code + state"| CLI
    CLI -->|"token exchange / device poll / refresh<br/>(HTTPS, no-redirect)"| OAUTH
    OAUTH -->|"short-lived access + rotating refresh token"| CLI
    CLI -->|"atomic profile commit"| STORE
    STORE -->|"locked bound profile read"| CLI
    CLI -->|"Bearer or explicit PRIVATE-TOKEN<br/>(HTTPS, no-redirect)"| API
    API -->|"MR/pipeline payloads + CI trace (untrusted)"| CLI
    CLI -->|"writes (redacted, truncated)"| OUT

Trust Boundaries

Boundary Diagram
┌──────────────────────────────────────────────────┐
│ TRUST BOUNDARY: Operator Workstation / Runner                 │
│  ┌──────────┐  ┌───────────┐  ┌──────────┐  ┌─────────┐  │
│  │ gitlab   │  │ Env config│  │ git remote│  │ output   │  │
│  │ CLI      │  │ / PAT     │  │ subprocess│  │          │  │
│  └────┬─────┘  └───────────┘  └──────────┘  └─────────┘  │
│       ├── 127.0.0.1:8766 callback                          │
│       └── POSIX owner-only OAuth profile store + lock      │
└────────────────────────┬──────────────────────────┘
       open browser      │ HTTPS (TLS, no-redirect)
   ┌───────────────┐  ┌────▼──────────────────────────────┐
   │ User browser  │  │ BOUNDARY: GitLab Instance        │
   │ consent page  │  │ OAuth endpoints + REST API       │
   └───────────────┘  └───────────────────────────────────┘
Boundary Descriptions
BoundaryAssets ProtectedControls Enforced
Operator Workstation / RunnerPAT, output, local git configPer-invocation env resolution; redaction; sanitized remote URL; argv (no shell)
User browser and callbackCode, state, PKCE verifierFixed loopback address/path; Host and state checks; bounded wait; PKCE
OAuth profile store and lockAccess/refresh tokens, bindingPOSIX owner-only directory; 0600; O_NOFOLLOW; descriptor validation; lock; atomic replacement; Windows fail-close
GitLab InstanceRequest/response integrity, tokenTLS (system trust store); _NoRedirect; origin-only base URL; capped JSON parser; CI trace redacted/truncated

Assets

IdAssetLifetimeNotes
A1GitLab OAuth access tokenShort-livedStored in a bound profile and sent as Authorization: Bearer.
A2Rotating OAuth refresh tokenProvider-managedExchanged only against the trusted issuer/client binding and stored in the owner-only profile store.
A3Explicit legacy PATOperator-managedRead from GITLAB_TOKEN only when GITLAB_AUTH_MODE=legacy-token.
A4OAuth code, device code, state, verifierOne flowTemporary capabilities; the PKCE verifier and state remain process-local.
A5Profile store and lock statePersistentSchema, issuer/client binding, usability, expiry, scopes, and cross-process coordination state.
A6GITLAB_URL and public client IDOperator-managedTrusted issuer/client identity used to bind profiles and egress.
A7Git remote URLCommand lifetimeMay embed credentials; sanitized before diagnostics.
A8API payloads, CI traces, diagnosticsCommand lifetimeUntrusted server content and optional operational audit data; never contains intentional credential output.

Adversaries

IdAdversaryIn-scope mitigations
ADV-aSame-uid malware on the operator workstationNot defended. A process running as the operator can read the environment and git config directly. Workstation hygiene is the controlling defense.
ADV-bNetwork attacker on the CLI ↔ GitLab channelTLS with stdlib certificate validation; HTTP redirects refused (_NoRedirect); HTTPS required for non-loopback hosts; capped, content-type-checked response parser.
ADV-cHostile or malformed GitLab server / responseNo-redirect opener; response size cap (MAX_BODY_BYTES); JSON content-type fail-closed; error and non-JSON bodies redacted and size-previewed before display.
ADV-dHostile local git remoteSubprocess uses an arg list (no shell) with an explicit timeout; embedded credentials stripped for logging (_sanitize_remote_url); resolved path validated (_validate_project_path).
ADV-eHostile caller process controlling argv / stdin / envInputs validated/encoded; GITLAB_URL canonicalized to origin-only; state/ref/numeric IDs validated; stdin/JSON size-capped before parse.

Bucket B1: CLI → GitLab API

All REST calls target the configured GITLAB_URL over urllib.request through a hardened opener.

Spoofing
  • TLS certificate validation is enforced by the stdlib default SSLContext (system trust store).

  • GITLAB_URL is reduced to an origin-only URL by _normalize_base_url, which rejects embedded userinfo so a crafted value cannot impersonate a host with inline credentials.

Tampering
  • TLS protects request and response bodies in transit.

  • The shared opener _OPENER is built with _NoRedirect, which raises on any 30x so a redirect cannot silently retarget the request.

  • Interpolated values are validated/encoded: the project path via urllib.parse.quote(safe=""), merge-request state via validate_state, pipeline ref via validate_ref, and numeric IDs via validate_numeric_id / validate_positive_int (which reject 0 and enforce upper bounds).

Repudiation
  • Commands emit deterministic exit codes (EXIT_SUCCESS/EXIT_FAILURE/EXIT_USAGE).

  • The attempt audit record is write-ahead and fail-closed. The outcome record is best-effort; see Enterprise Readiness Gaps.

Information Disclosure
  • OAuth uses Authorization: Bearer; explicit legacy mode uses PRIVATE-TOKEN. Both are sent only over TLS and are never logged.

  • The token-bearing opener blocks 30x, preventing a hostile redirect from forwarding the token to a non-GitLab origin (_NoRedirect).

  • The transport requires a JSON content type when JSON is expected and reads through _read_capped; a missing or non-JSON content type fails closed.

  • HTTP error bodies are parsed first, then redacted for presentation; a dict error without message/error produces a single structured redacted summary (_request_bytes). Non-JSON bodies that the CLI prints for usability are passed through _redact and capped via _preview_text.

  • _redact scrubs the Bearer/Basic scheme-plus-value pair first, then PRIVATE-TOKEN/X-API-Key/Authorization/Proxy-Authorization/Cookie/Set-Cookie/token/password/secret and query-string secrets. It additionally applies _REDACT_PATTERNS, which mask every _REDACT_KEYS entry in JSON shape ("key": "value"), bare form shape (key=value, with no leading ? or & required), and Azure Blob SAS query strings. code_challenge is deliberately excluded because a PKCE challenge is public by design and masking it would corrupt an authorization URL.

  • Transport failures raise GitLabAPIError, whose __str__ renders only the status, method, query-stripped resource, redacted summary, and a character-validated request ID. The raw response body, the query-bearing request URL, and the header dictionary are never part of the rendered string (_scrub_url, _response_request_id).

  • The resolved credential lives only on the frozen AuthContext, whose token field carries repr=False. There is no module-level token global, so repr(globals()), an accidental repr(auth_context), and traceback frame dumps cannot surface the PAT. A source-contract test asserts the global stays absent.

Denial of Service
  • Response bodies are read through _read_capped with a MAX_BODY_BYTES cap.

  • Requests use REQUEST_TIMEOUT so a stalled server cannot hang the CLI.

Elevation of Privilege
  • The skill issues only the operations exposed by its explicit subcommands; request URLs are built only from validated, encoded segments.

TLS posture

Every GitLab call uses the stdlib opener with no custom SSLContext, CA-bundle flag, or pinning. Operators inherit Python's default HTTPS behavior: validation uses the system trust store; internal CAs require SSL_CERT_FILE/SSL_CERT_DIR; there is no pinning or mTLS (G-TLS-1). HTTPS is required for non-loopback hosts; plaintext http:// is refused for non-loopback hosts even when GITLAB_ALLOW_INSECURE=1 is set, and is permitted only for loopback hosts when GITLAB_ALLOW_INSECURE=1 is explicitly set — an opt-in gate mirroring the jira skill. cmd_job_log continues to emit redacted, untrusted CI trace content with truncation.

Risk Rating
ThreatLikelihoodImpactResidual RiskStatus
TLS MITM / hostile redirect retargetingLowHighLowMitigated (TLS + _NoRedirect)
Plaintext HTTP to a non-loopback hostLowHighLowMitigated (refused)
Oversized-response memory exhaustionLowLowLowMitigated (cap + timeout)

Bucket B2: Credential profiles and environment

The instance origin and mode are read from the environment. OAuth mode loads an instance/client-bound local profile containing short-lived access and rotating refresh tokens. Legacy mode reads a PAT from GITLAB_TOKEN.

Spoofing
  • GITLAB_URL is parsed with urllib.parse.urlsplit, must use http/https, and HTTPS is enforced for non-loopback hosts (_is_loopback).

Tampering
  • _normalize_base_url rejects control characters, embedded userinfo, query, fragment, and non-root paths, reducing the value to an origin-only URL before any request is built.

Repudiation
  • Missing, malformed, mixed, or mismatched credentials fail fast with a usage exit code via die.

Information Disclosure
  • OAuth profiles use atomic mode-0600 writes in a dedicated POSIX owner-only directory and never appear in status output. Windows OAuth persistence fails closed. PAT values remain environment-only. Neither credential form is logged.

Denial of Service
  • Profile reads and writes are serialized with an advisory lock whose cross-process acquisition is bounded by a finite timeout.

Elevation of Privilege
  • The token's effective permissions are governed entirely by GitLab; the skill adds no privilege.

Risk Rating
ThreatLikelihoodImpactResidual RiskStatus
Base-URL host impersonation (embedded userinfo)LowMedLowMitigated (origin-only)
OAuth refresh token at restMedHighMedPartially mitigated (0600 + binding)
PAT at rest in operator environmentLowHighMedNot defended (workstation hygiene)

Bucket B3: Git remote subprocess / project resolution

When GITLAB_PROJECT is unset, the skill resolves the project from the local git remote (project()).

Spoofing
  • The remote is read from the local git configuration, which is operator-controlled; a hostile local remote is modeled as ADV-d and constrained by path validation below.

Tampering
  • The subprocess is invoked with an argument list (["git", "remote", "get-url", "origin"]) and shell is never used, so remote values cannot inject shell commands.

  • The resolved path is validated by _validate_project_path, which rejects %, backslashes, and empty/./.. segments before it is URL-encoded — blocking path traversal and encoded-separator escapes.

Repudiation
  • Resolution failures (CalledProcessError, FileNotFoundError, TimeoutExpired) map to explicit exit codes with messages that reference only the sanitized remote URL.

Information Disclosure
  • _sanitize_remote_url strips embedded credentials from the remote URL before it appears in any error message, so a remote like https://user:pass@host/group/project.git never leaks its secret to stderr.

Denial of Service
  • subprocess.check_output is bounded by timeout=REQUEST_TIMEOUT; a stalled or blocked git invocation surfaces as a typed timeout error rather than hanging the CLI.

Elevation of Privilege
  • The resolved project path becomes an API path component, but it is validated and encoded; GITLAB_PROJECT can be set explicitly to bypass remote resolution entirely for privileged or destructive operations.

Risk Rating
ThreatLikelihoodImpactResidual RiskStatus
Shell injection via hostile remote URLLowHighLowMitigated (argv, no shell)
Path traversal via resolved project pathLowMedLowMitigated (_validate_project_path)
Credential leak from remote URL in logsLowHighLowMitigated (_sanitize_remote_url)
Stalled git subprocess hangLowLowLowMitigated (timeout)

Bucket B4: CLI caller process

The caller controls argv, environment, stdin, stdout, and stderr; the CLI treats that process as operator-controlled.

Spoofing
  • API commands have no listener or attach surface. The bounded auth login listener is modeled separately in B5.

Tampering
  • Arguments are parsed locally; handlers validate identifiers, state, ref, and numeric IDs, and encode interpolated segments before issuing requests.

  • JSON payloads are parsed through load_json_payload with a size cap enforced before json.loads.

Repudiation
  • Validation, authentication, and runtime failures map to distinct exit codes for attribution by a calling step.

Information Disclosure
  • Command output is JSON-encoded GitLab payloads; tokens never appear in normal output.

  • Diagnostic and raw-text writes use _emit, _emit_stdout, or _emit_debug_traceback and apply _redact. Successful structured JSON and TSV are sanitized by sensitive key before _emit_structured_stdout, preserving syntax and field arity. Arbitrary credentials copied into non-sensitive successful free text remain a residual gap.

  • Job traces are redacted via _redact and truncated at MAX_LOG_BYTES before printing (cmd_job_log), so a token echoed into a CI trace is masked and an oversized trace is truncated rather than hard-failing.

  • Credentialed traffic has two explicit owners: REST requests use gitlab._request_bytes, and OAuth forms use _gitlab_oauth.post_form. A recursive production-module source contract rejects direct egress elsewhere, while behavioral tests prove write-ahead audit attempts and bounded outcomes for both owners.

  • GITLAB_DEBUG=1 enables a traceback on the failure path; the formatted traceback is redacted before it is written. LOGGER.exception is banned by a source-contract test because it would emit an unredacted traceback.

  • GitLab-authored text returned in output must be treated as untrusted by downstream automation.

Denial of Service
  • HTTP bodies are read through _read_capped; job logs are truncated at MAX_LOG_BYTES; stdin/JSON payloads are size-capped; pagination is bounded by validate_positive_int.

Elevation of Privilege
  • No command path bypasses input validation or constructs an unencoded request URL from caller input.

Risk Rating
ThreatLikelihoodImpactResidual RiskStatus
Token echoed into a CI job traceMedHighLowMitigated (_redact + truncate)
Untrusted GitLab / CI text consumed downstreamMedMedMedBy design (consumer responsibility)
Oversized stdin / job-log payloadLowLowLowMitigated (caps / truncation)
Leaked token not revocable by the skillLowHighMedAccepted upstream (G-EOP-1)

Bucket B5: Browser → fixed loopback OAuth callback

auth login binds 127.0.0.1:8766 before opening the authorization URL and accepts a callback only at /callback.

Spoofing
  • The handler validates the exact Host header and callback path, then compares UTF-8 state bytes with secrets.compare_digest before accepting completion.

  • PKCE S256 binds a captured authorization code to the verifier held by the CLI process.

Tampering
  • The listener binds only to IPv4 loopback, sets allow_reuse_address = False, and rejects wrong hosts, paths, missing state, and incomplete callback parameters.

Repudiation
  • Authentication commands use distinct operation names and deterministic exit codes. Authorization URL output contains no secret verifier or token.

Information Disclosure
  • The callback response is minimal text and contains no token, authorization code, or profile content.

  • Token exchange uses the HTTPS no-redirect opener only after state validation.

  • Device verification instructions are validated before display. Values carrying Unicode control characters, and verification URIs with embedded credentials, raw spaces, backslashes, unparseable components, or an origin that differs from the configured issuer, are rejected instead of printed. The user code itself is preserved unchanged because its format belongs to the authorization server. This is local defense-in-depth and deliberately narrows support for deployments that publish device verification on a different host.

Denial of Service
  • Bind failure is converted to a typed error with a device-login recovery path. Invalid local requests do not terminate the serve loop, each accepted peer has a five-second read timeout, and one shared deadline bounds listener service and caller waiting.

Elevation of Privilege
  • The handler accepts only a valid code or provider error paired with the expected state. Authorization remains subject to the user-visible GitLab consent page and registered application scopes.

Risk Rating
ThreatLikelihoodImpactResidual RiskStatus
Forged callback or code interceptionLowHighLowMitigated (state + PKCE)
Local callback-port denialLowLowLowMitigated (typed device flow)
Callback wait exhaustionLowLowLowMitigated (shared deadline)
Hostile device-instruction outputLowMedLowMitigated (validated before display)

Bucket B6: OAuth profile store and lock

On POSIX, OAuth profiles are stored beneath a dedicated owner-only directory with a sibling advisory lock. The store contains access and refresh tokens plus issuer/client binding and usability state. Windows persistence fails closed before filesystem access.

Spoofing
  • Every stored issuer is validated as an origin-only HTTPS URL (or explicit loopback HTTP), and each locked reread must match the already trusted issuer and client ID before refresh or API use.

Tampering
  • Store and lock opens use O_NOFOLLOW; opened descriptors must be regular files owned by the effective user without group or world permissions.

  • Writes validate the schema, use mode 0600, fsync file content, atomically replace the store, and fsync the parent directory. Cross-process updates require an advisory lock.

Repudiation
  • Profiles record obtained and expiry timestamps, scopes, issuer, client ID, and usability. The store is not a signed audit record and must not be treated as one.

Information Disclosure
  • The dedicated POSIX directory is owner-only and store/lock files use mode 0600. Status output excludes access and refresh tokens.

  • Same-uid malware remains outside the defended boundary (G-INF-3).

Denial of Service
  • Missing locking primitives fail closed. Corrupt schemas, unsafe permissions, and symlinks fail before credential use. Windows OAuth persistence is unavailable until a protected backend exists.

  • Cross-process lock acquisition is non-blocking with a finite deadline, so a stalled lock holder surfaces a typed timeout instead of hanging the CLI. Only lock-contention errors are retried; other locking failures propagate unchanged. The bound covers acquisition, not in-process waiting or work performed while the lock is held.

  • A provider/local refresh-commit uncertainty can require re-login (G-DOS-1).

Elevation of Privilege
  • Profiles cannot change the trusted issuer/client binding during a locked refresh. GitLab remains the authority for granted scopes; local storage cannot broaden them.

Risk Rating
ThreatLikelihoodImpactResidual RiskStatus
Cross-user POSIX store disclosureLowHighLowMitigated (owner-only directory and files)
Unsupported Windows persistenceLowHighLowMitigated (fails closed before store access)
Symlink or replacement tamperingLowHighLowMitigated (no-follow + descriptor checks)
Same-uid credential accessMedHighMedAccepted boundary (G-INF-3)
Refresh commit uncertaintyLowMedMedFail closed; re-login (G-DOS-1)

Enterprise Readiness Gaps

The following are known limitations recorded so operators can make informed deployment decisions. Severity ratings are the project's own assessment and are not equivalent to a CVSS score.

IdGapSeverityStatus
G-REP-1The write-ahead attempt is fail-closed, but the outcome is best-effort and the operator-supplied file is not signed or append-only.Repudiation-MedBy design; integrate with host telemetry for tamper-evident logging.
G-INF-1The CLI prints redacted non-JSON response bodies to stdout/stderr for usability; redaction is a broad regex backstop that may over- or under-cover novel secret formats.InfoDisc-LowAccepted; monitor and extend _redact as new secret formats appear.
G-INF-2_REDACT_KEYS enumerates known credential field names; a novel or vendor-specific field name is masked only if it also matches the header, Bearer/Basic, or query-string rules.InfoDisc-LowAccepted; extend _REDACT_KEYS as new credential shapes appear. tests/test_gitlab_helpers.py pins the set.
G-INF-3Same-uid processes can read the OAuth store despite owner-only filesystem permissions.InfoDisc-MedAccepted workstation boundary; evaluate an OS keyring separately where policy requires it.
G-DOS-1A crash or malformed successful response between provider refresh rotation and durable local commit can invalidate the retained refresh token and require re-login.DoS-MedFail closed by marking uncertain profiles unusable; re-run login.
G-EOP-1Local logout cannot revoke OAuth authorization, and the skill cannot revoke a leaked OAuth access token, refresh token, or legacy PAT.EoP-MedUpstream control; rotate or revoke at the GitLab instance on suspicion of compromise.
G-SUP-1Python dependencies are declared in pyproject.toml but transitive hashes are not pinned and no SBOM is published; transport/subprocess/redaction fuzz coverage is partial.SupplyChain-MedTracked at the repository level.
G-TLS-1No certificate pinning for the GitLab origin; TLS validation depends entirely on the system trust store.InfoDisc-LowOperator-acceptable for a managed GitLab endpoint; documented for customers whose policy mandates pinning.

For an active issue tracker entry covering these gaps, see microsoft/hve-core#2225.

References

🤖 Crafted with precision by ✨Copilot following brilliant human instruction, then carefully refined by our team of discerning human reviewers.

Requirements and compatibility

Written for: GitHub Copilot

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an MCP server, Needs a command-line tool, Needs Python

The author says: Requires Python 3.11+. GitLab OAuth public-client credentials or explicit legacy-token mode.

Security

What our scanner noticed· Low risk

  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
Microsoft
Publisher
Microsoft (Official)
Source
microsoft/hve-core · .github/skills/project-planning/gitlab · commit f425e94
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Microsoft or Microsoft.

Get it from the source

8 of this skill’s files are not mirrored here, so there is no ZIP. Get the complete skill from its source: microsoft/hve-core