This project is not covered by Drupal’s security advisory policy.
Note that extensive parts of this module, most of the React app and some of the backend was generating via AI coding agents under supervision.
PHP-backed skills for coding agents (Claude Code, Codex, Gemini CLI, Kiro, Opencode) to triage and maintain GitLab issues. Everything runs inside a ddev two-container sandbox: your GitLab tokens stay in the trusted web container and the agent never sees them.
It is written specifically for the Drupal project, but the architecture is generic and could be adapted to other GitLab-hosted projects.
Quick start
-
Copy and configure:
cp config/config.example.php config/config.php # Edit config/config.php — add a GitLab token for each project you want to triage. -
Start the sandbox (builds the Alpine agent image on first run and generates the shared bridge secret automatically):
ddev start -
Enter a coding agent inside the sandbox:
ddev claude # Claude Code (Anthropic) ddev codex # Codex (OpenAI) ddev gemini # Gemini CLI (Google) ddev opencode # Opencode (sst) ddev kiro # Kiro (AWS — requires a paid plan)Or open a plain shell in the agent container (all five CLIs and the
triage/suggest/demo/test-instructionswrappers are onPATH):ddev agent-shell # interactive shell in the agent sandbox(
ddev ssh -s agentdoes the same thing if you prefer the built-in.) -
Run a skill from inside the agent, e.g.:
/triage-issue https://git.drupalcode.org/project/ai/-/work_items/3577170
Requirements
- ddev — the sandbox orchestrator. See https://ddev.readthedocs.io/
- A GitLab access token (scope
api) for each project you want to triage. - No PHP installation needed on the host — PHP 8.3 runs inside the
webcontainer.
Security
The two-container boundary
The sandbox has two containers:
web(trusted): holdsconfig/config.php(your GitLab tokens),lib/gitlab.php, theskills/*/scripts/*.phpCLI scripts, andserver/bridge.php. It has internet access and is the only place that ever makes GitLab API calls.agent(untrusted Alpine): holds the five coding CLIs, the bridge client (node /opt/bridge/bridge-client.js), and the thin wrapper commands (triage,suggest,demo,test-instructions). The project root is not mounted here — the agent container never hasconfig/config.phpon its filesystem.
When the agent runs a skill wrapper (e.g. triage show <url>), the wrapper POSTs {skill, cmd, args, flags} as JSON to http://web/server/bridge.php with a shared-secret header (X-Bridge-Secret). The bridge validates the request against the allowlist in server/registry.php, runs the PHP script via proc_open with an argument array (no shell injection), and returns {stdout, stderr, exit}. The GitLab token never leaves the web container.
What the bridge enforces
Before any subprocess starts the bridge re-applies the same guards the scripts use:
- Skill + command allowlist (
server/registry.php): only listed skill keys and subcommands are accepted; everything else is rejected with HTTP 400. - Flag allowlist (
BRIDGE_ALLOWED_FLAGS): only--force,--create,--remove. - Prompt-injection scan + slash-strip (
lib/gitlab.php): issue text is scanned before it is printed into the agent context; leading slashes on comment lines are neutralised so no comment can trigger a GitLab quick action. - Shared-secret header: the bridge refuses any request without the correct
X-Bridge-Secret(generated once byddev startand stored in.ddev/.env).
This substantially reduces the attack surface, but no automation that reads public issue text can be proven completely safe against every future prompt-injection phrasing. To reduce blast radius:
- Use project access tokens, not personal access tokens.
- Use the least-privileged token that can perform the required issue and label operations.
- Run inside the ddev sandbox — the agent container does not have access to your SSH keys or local filesystem.
- Always use
/skillcommands so the harness enforcesdisallowed-tools. - If prompt injection bypasses guardrails, scripted actions are limited to unwanted comments (without slash commands), label changes, and weight/state changes on configured projects only.
Configuration
All commands run only against projects listed in config/config.php, and only when that project has a non-empty token. Anything else is hard-refused before any request is made.
cp config/config.example.php config/config.phpEdit config/config.php and add a project key (its web URL) and a token for each project:
$config = [
'https://git.drupalcode.org/project/ai' => [
'token' => 'glpat-xxxxxxxxxxxxxxxxxxxx',
// Version suggest-solution fetches when an issue names none of its own:
'default_version' => '1.x',
'developers_to_tag' => ['Marcus_Johansson', 'a.dmitriiev'],
'project_managers_to_tag' => ['arianraeesi'],
],
];config/config.php is git-ignored so your tokens never get committed; only config/config.example.php is tracked.
Available skills
See: https://git.drupalcode.org/project/ai_maintenance_skills/-/blob/1.0.x/RE...
Project information
Minimally maintained
Maintainers monitor issues, but fast responses are not guaranteed.- Created by marcus_johansson on , updated
This project is not covered by the security advisory policy.
Use at your own risk! It may have publicly disclosed vulnerabilities.
