From c0b60c8a579dd0b1adb666f63e71664548afab64 Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Sat, 12 Sep 2026 15:12:50 -0700 Subject: [PATCH 1/9] feat: add collector-builder StackRox sandbox profile --- .../image/sandbox-collector-builder/CLAUDE.md | 24 ++ .../sandbox-collector-builder/Dockerfile | 192 +++++++++ .../sandbox-collector-builder/claude.json | 14 + .../image/sandbox-collector-builder/mcp.json | 17 + .../sandbox-collector-builder/opencode.json | 18 + .../openshell/.bashrc | 7 + .../openshell/.profile | 1 + .../openshell/README.md | 16 + .../openshell/skills/github/SKILL.md | 380 ++++++++++++++++++ .../sandbox-collector-builder/policy.yaml | 42 ++ .../sandbox-collector-builder/settings.json | 55 +++ 11 files changed, 766 insertions(+) create mode 100644 profiles/stackrox/image/sandbox-collector-builder/CLAUDE.md create mode 100644 profiles/stackrox/image/sandbox-collector-builder/Dockerfile create mode 100644 profiles/stackrox/image/sandbox-collector-builder/claude.json create mode 100644 profiles/stackrox/image/sandbox-collector-builder/mcp.json create mode 100644 profiles/stackrox/image/sandbox-collector-builder/opencode.json create mode 100644 profiles/stackrox/image/sandbox-collector-builder/openshell/.bashrc create mode 100644 profiles/stackrox/image/sandbox-collector-builder/openshell/.profile create mode 100644 profiles/stackrox/image/sandbox-collector-builder/openshell/README.md create mode 100644 profiles/stackrox/image/sandbox-collector-builder/openshell/skills/github/SKILL.md create mode 100644 profiles/stackrox/image/sandbox-collector-builder/policy.yaml create mode 100644 profiles/stackrox/image/sandbox-collector-builder/settings.json diff --git a/profiles/stackrox/image/sandbox-collector-builder/CLAUDE.md b/profiles/stackrox/image/sandbox-collector-builder/CLAUDE.md new file mode 100644 index 0000000..3cb3a4c --- /dev/null +++ b/profiles/stackrox/image/sandbox-collector-builder/CLAUDE.md @@ -0,0 +1,24 @@ +# Sandbox Environment + +You are running inside an OpenShell sandbox based on the StackRox Collector +`collector-builder` image. Credentials are injected by OpenShell providers and +are not part of the image. + +## Environment + +- Working directory: `/sandbox` +- Writable paths: `/sandbox`, `/tmp` +- Inference routes through the gateway proxy at `inference.local` +- Repository build tools from the `collector-builder` build are available, including + Go, compilers, make, git, jq, and the StackRox CI toolchain. + +## Tools + +- `gh` — GitHub CLI. Use the bundled GitHub skill for REST-only API access. +- `gws` — Google Workspace CLI when the provider is attached. +- `python3`, `uv`, `node`, `npm`, `go`, `gopls`, `ajv`, `git`, `curl` +- `claude`, `opencode`, `codex`, and `copilot` coding agents +- Atlassian and Go-analysis MCP servers through `.mcp.json` when configured + +The OpenShell Vertex provider supplies model access and credentials. The image +does not install `gcloud` or copy service-account keys into the sandbox. diff --git a/profiles/stackrox/image/sandbox-collector-builder/Dockerfile b/profiles/stackrox/image/sandbox-collector-builder/Dockerfile new file mode 100644 index 0000000..43beb43 --- /dev/null +++ b/profiles/stackrox/image/sandbox-collector-builder/Dockerfile @@ -0,0 +1,192 @@ +# syntax=docker/dockerfile:1.4 + +# StackRox collector-builder sandbox image for OpenShell repository workflows. +# +# This profile starts from the latest StackRox Collector builder image so +# collector build tools (Go, compilers, make, and the collector toolchain) are +# available to the agent. It adds the OpenShell sandbox contract, coding-agent +# tools, and selected vendored OpenShell assets. +# +# The collector builder is multi-architecture, but the bundled Claude and +# Google Workspace binaries are amd64-only. Keep this profile amd64-only until +# those downloads are made architecture-aware. +# +# Base image source: +# https://github.com/stackrox/collector/tree/master/builder +# OpenShell base contract: +# https://github.com/NVIDIA/OpenShell-Community/tree/main/sandboxes/base + +# The tag is master; pin the current multi-architecture manifest digest +# so a retag cannot silently change the toolchain. +ARG BASE_IMAGE=quay.io/stackrox-io/collector-builder@sha256:52a3cca5253abb0cd5c3606769d5fd5b8be4cdb4671396f38a792695f07d7696 +FROM ${BASE_IMAGE} + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +USER root + +# Runtime tools used by the OpenShell sandbox contract and by agent skills. +# collector-builder image already supplies the compiler toolchain, Go, Node, npm, git, jq, +# and curl; install only the missing runtime pieces here. +RUN dnf install -y --setopt=install_weak_deps=False \ + ca-certificates \ + findutils \ + gzip \ + iproute \ + iputils \ + nano \ + net-tools \ + nmap-ncat \ + openssh-clients \ + perl-Digest-SHA \ + procps-ng \ + shadow-utils \ + tar \ + vim-minimal \ + wget \ + which \ + && dnf clean all \ + && rm -rf /var/cache/dnf + +# OpenShell expects an unprivileged sandbox user with a writable home and a +# supervisor account available for images that need privileged setup. +RUN groupadd -r supervisor \ + && useradd -r -g supervisor -s /sbin/nologin supervisor \ + && groupadd -r sandbox \ + && useradd -r -g sandbox -d /sandbox -s /bin/bash sandbox \ + && mkdir -p /sandbox \ + && chown sandbox:sandbox /sandbox + +# GitHub CLI (collector-builder image does not include it). Pin the release and verify it +# before installing so the image remains reproducible. +ARG GH_VERSION=2.100.0 +ARG GH_SHA256_AMD64=e4d4bb4498e8d007abe545b6568926793ace1b6447da598294a610018cb164be +RUN curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" -o /tmp/gh.tgz \ + && echo "${GH_SHA256_AMD64} /tmp/gh.tgz" | sha256sum -c - \ + && mkdir -p /tmp/gh-extract \ + && tar -xzf /tmp/gh.tgz --strip-components=2 -C /tmp/gh-extract "gh_${GH_VERSION}_linux_amd64/bin/gh" \ + && test -f /tmp/gh-extract/gh \ + && install -m 0755 /tmp/gh-extract/gh /usr/bin/gh \ + && rm -rf /tmp/gh-extract /tmp/gh.tgz + +# Use the same uv-managed Python contract as the community base image. The +# collector-builder image system Python is 3.9, while current MCP integrations require +# 3.10+. +COPY --from=ghcr.io/astral-sh/uv:0.10.8 /uv /usr/local/bin/uv +ARG PYTHON_VERSION=3.14.3 +ENV UV_PYTHON_INSTALL_DIR=/sandbox/.uv/python +RUN uv python install "${PYTHON_VERSION}" \ + && uv cache clean + +# Coding agents and the JSON-schema utility used by StackRox workflows. These +# versions mirror the current StackRox sandbox profile where applicable. +# tar is a Node library dependency; npm leaves the system GNU tar in /usr/bin +# unchanged. +ARG NPM_TAR_VERSION=7.5.11 +ARG NPM_HONO_VERSION=1.19.11 +ARG NPM_OPENCODE_VERSION=1.18.30 +ARG NPM_CODEX_VERSION=0.117.0 +ARG NPM_COPILOT_VERSION=1.0.16 +ARG NPM_AJV_VERSION=5.0.0 +RUN mkdir -p /tmp/npm-global \ + && chown sandbox:sandbox /tmp/npm-global +USER sandbox +RUN NPM_CONFIG_PREFIX=/tmp/npm-global npm install -g \ + "tar@${NPM_TAR_VERSION}" \ + "@hono/node-server@${NPM_HONO_VERSION}" \ + "opencode-ai@${NPM_OPENCODE_VERSION}" \ + "@openai/codex@${NPM_CODEX_VERSION}" \ + "@github/copilot@${NPM_COPILOT_VERSION}" \ + "ajv-cli@${NPM_AJV_VERSION}" \ + && npm cache clean --force +USER root +RUN cp -a /tmp/npm-global/lib/node_modules/. /usr/lib/node_modules/ \ + && for name in opencode codex copilot ajv; do \ + test -e "/tmp/npm-global/bin/${name}"; \ + cp -a "/tmp/npm-global/bin/${name}" "/usr/bin/${name}"; \ + chown -h root:root "/usr/bin/${name}"; \ + done \ + && chown -R root:root /usr/lib/node_modules \ + && tar --version | grep -q 'GNU tar' \ + && rm -rf /tmp/npm-global + +# Keep the profile's command path stable even though npm puts the executable +# in /usr/bin on this UBI-based image. +RUN ln -sf /usr/bin/opencode /usr/local/bin/opencode + +# Claude Code's native binary. Pin both the release URL and its release +# manifest checksum instead of executing a downloaded installer as root. +ARG CLAUDE_VERSION=2.1.269 +ARG CLAUDE_SHA256_AMD64=25e44883f54419569a3d739f38cbbdaebe83b09895da0f343e1b003710a4775b +RUN curl -fsSL "https://downloads.claude.ai/claude-code-releases/${CLAUDE_VERSION}/linux-x64/claude" -o /tmp/claude \ + && echo "${CLAUDE_SHA256_AMD64} /tmp/claude" | sha256sum -c - \ + && install -m 0755 /tmp/claude /usr/local/bin/claude \ + && rm -f /tmp/claude + +# Go-language MCP support for repository analysis. collector-builder image supplies the Go +# toolchain; gopls is the only additional Go binary needed here. +ARG GOPLS_VERSION=0.20.0 +RUN GOBIN=/usr/local/bin /usr/local/go/bin/go install "golang.org/x/tools/gopls@v${GOPLS_VERSION}" \ + && rm -rf /root/go /root/.cache/go-build + +# Atlassian MCP is part of the StackRox workflow image contract. The gateway +# still owns credentials; no credential values are baked into this image. +RUN uv venv --python "${PYTHON_VERSION}" --seed /sandbox/.venv \ + && uv pip install --python /sandbox/.venv/bin/python \ + cloudpickle==3.1.2 \ + mcp-atlassian==0.21.1 \ + && uv cache clean + +# Google Workspace CLI is used by StackRox workflows when the corresponding +# OpenShell provider is attached. Use the static musl build so it runs on the +# CentOS glibc version supplied by collector-builder. +ARG GWS_VERSION=0.22.5 +ARG GWS_SHA256_AMD64=4db473dde4b1ab872e4ff35d769b0d4af1f1a6441a605e79d5cf8ada9c87e920 +RUN curl -fsSL "https://github.com/googleworkspace/cli/releases/download/v${GWS_VERSION}/google-workspace-cli-x86_64-unknown-linux-musl.tar.gz" -o /tmp/gws.tgz \ + && echo "${GWS_SHA256_AMD64} /tmp/gws.tgz" | sha256sum -c - \ + && mkdir -p /tmp/gws-extract \ + && tar xzf /tmp/gws.tgz --no-same-owner --no-same-permissions -C /tmp/gws-extract ./gws \ + && test -f /tmp/gws-extract/gws \ + && install -m 0755 /tmp/gws-extract/gws /usr/local/bin/gws \ + && rm -rf /tmp/gws-extract \ + && rm -f /tmp/gws.tgz + +ENV PATH="/sandbox/.venv/bin:/usr/local/go/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" \ + VIRTUAL_ENV=/sandbox/.venv \ + GOPATH=/sandbox/.cache/go \ + GOCACHE=/sandbox/.cache/go-build \ + GOMODCACHE=/sandbox/.cache/go-mod + +# Vendor only the small OpenShell assets this profile uses. The upstream +# network policy is intentionally not inherited; this profile owns its small +# StackRox policy and provider profiles supply integration egress at runtime. +COPY openshell/skills/ /sandbox/.agents/skills/ +COPY openshell/.bashrc /sandbox/.bashrc +COPY openshell/.profile /sandbox/.profile +COPY policy.yaml /etc/openshell/policy.yaml + +# Default agent instructions and configuration. Workflows can override these +# files with payloads for repository-specific skills and instructions. +COPY CLAUDE.md /sandbox/.claude/CLAUDE.md +COPY settings.json /sandbox/.claude/settings.json +COPY claude.json /sandbox/.claude.json +COPY mcp.json /sandbox/.mcp.json +COPY opencode.json /sandbox/opencode.json + +RUN mkdir -p /sandbox/.claude/skills /sandbox/.config/openshell \ + && chmod 0644 /etc/openshell/policy.yaml \ + /sandbox/.claude/CLAUDE.md \ + /sandbox/.claude/settings.json \ + /sandbox/.claude.json \ + /sandbox/.mcp.json \ + /sandbox/opencode.json \ + && for skill in /sandbox/.agents/skills/*/; do \ + [ -d "${skill}" ] || continue; \ + ln -sf "${skill}" "/sandbox/.claude/skills/$(basename "${skill}")"; \ + done \ + && chown -R sandbox:sandbox /sandbox + +WORKDIR /sandbox +USER sandbox + +ENTRYPOINT ["/bin/bash"] diff --git a/profiles/stackrox/image/sandbox-collector-builder/claude.json b/profiles/stackrox/image/sandbox-collector-builder/claude.json new file mode 100644 index 0000000..5af008a --- /dev/null +++ b/profiles/stackrox/image/sandbox-collector-builder/claude.json @@ -0,0 +1,14 @@ +{ + "hasCompletedOnboarding": true, + "numStartups": 1, + "autoUpdates": false, + "customApiKeyResponses": { + "approved": ["nshell-proxy-managed"] + }, + "projects": { + "/sandbox": { + "hasTrustDialogAccepted": true, + "allowedTools": [] + } + } +} diff --git a/profiles/stackrox/image/sandbox-collector-builder/mcp.json b/profiles/stackrox/image/sandbox-collector-builder/mcp.json new file mode 100644 index 0000000..f37b1d4 --- /dev/null +++ b/profiles/stackrox/image/sandbox-collector-builder/mcp.json @@ -0,0 +1,17 @@ +{ + "mcpServers": { + "atlassian": { + "type": "stdio", + "command": "/sandbox/.venv/bin/mcp-atlassian", + "args": [], + "env": { + "READ_ONLY_MODE": "true" + } + }, + "gopls-mcp": { + "type": "stdio", + "command": "/usr/local/bin/gopls", + "args": ["mcp"] + } + } +} diff --git a/profiles/stackrox/image/sandbox-collector-builder/opencode.json b/profiles/stackrox/image/sandbox-collector-builder/opencode.json new file mode 100644 index 0000000..33680c1 --- /dev/null +++ b/profiles/stackrox/image/sandbox-collector-builder/opencode.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "atlassian": { + "type": "local", + "command": ["/sandbox/.venv/bin/mcp-atlassian"], + "enabled": true, + "environment": { + "READ_ONLY_MODE": "true" + } + }, + "gopls-mcp": { + "type": "local", + "command": ["/usr/local/bin/gopls", "mcp"], + "enabled": true + } + } +} diff --git a/profiles/stackrox/image/sandbox-collector-builder/openshell/.bashrc b/profiles/stackrox/image/sandbox-collector-builder/openshell/.bashrc new file mode 100644 index 0000000..559fbbb --- /dev/null +++ b/profiles/stackrox/image/sandbox-collector-builder/openshell/.bashrc @@ -0,0 +1,7 @@ +export PATH="/sandbox/.venv/bin:/usr/local/go/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" +export VIRTUAL_ENV="/sandbox/.venv" +export UV_PYTHON_INSTALL_DIR="/sandbox/.uv/python" +export GOPATH="/sandbox/.cache/go" +export GOCACHE="/sandbox/.cache/go-build" +export GOMODCACHE="/sandbox/.cache/go-mod" +export PS1="\u@\h:\w\$ " diff --git a/profiles/stackrox/image/sandbox-collector-builder/openshell/.profile b/profiles/stackrox/image/sandbox-collector-builder/openshell/.profile new file mode 100644 index 0000000..91502a9 --- /dev/null +++ b/profiles/stackrox/image/sandbox-collector-builder/openshell/.profile @@ -0,0 +1 @@ +[ -f ~/.bashrc ] && . ~/.bashrc diff --git a/profiles/stackrox/image/sandbox-collector-builder/openshell/README.md b/profiles/stackrox/image/sandbox-collector-builder/openshell/README.md new file mode 100644 index 0000000..7255b7d --- /dev/null +++ b/profiles/stackrox/image/sandbox-collector-builder/openshell/README.md @@ -0,0 +1,16 @@ +# Vendored OpenShell assets + +This directory contains the small set of files copied from the NVIDIA +OpenShell Community base sandbox that this profile uses at runtime: + +- the GitHub REST-only agent skill; +- shell initialization adapted from the upstream base image for the StackRox + Go toolchain and writable caches. + +Source: `NVIDIA/OpenShell-Community/sandboxes/base` at commit +`fffb6b2248ff6ba585f50517f3711b08122089f2`. + +The source repository is Apache-2.0 licensed. Keep this copy synchronized with +the pinned source commit when the upstream base contract changes. The upstream +network policy is intentionally not copied; this profile owns its minimal +policy and provider profiles supply integration egress at runtime. diff --git a/profiles/stackrox/image/sandbox-collector-builder/openshell/skills/github/SKILL.md b/profiles/stackrox/image/sandbox-collector-builder/openshell/skills/github/SKILL.md new file mode 100644 index 0000000..55fe8af --- /dev/null +++ b/profiles/stackrox/image/sandbox-collector-builder/openshell/skills/github/SKILL.md @@ -0,0 +1,380 @@ +--- +name: github +description: Interact with GitHub using the gh CLI restricted to REST API only (no GraphQL). Use when the user wants to work with GitHub issues, pull requests, repos, releases, or Actions — especially in sandboxed environments where the GraphQL endpoint may be blocked. Trigger keywords - github, gh, pull request, PR, github issue, github actions, workflow, release, gh api. +--- + +Always prefer using the github cli. do not use git for any operations except cloning. + +# GitHub via REST API + +Work with GitHub using `gh api` and REST-only subcommands. Many `gh` subcommands (`gh pr list`, `gh issue list`, `gh issue view`, `gh pr view`, etc.) use GraphQL internally and **will fail** if the sandbox blocks `api.github.com/graphql`. Always prefer `gh api` hitting REST endpoints. + +## Shell Permissions + +Use `required_permissions: ["full_network"]` for all `gh` commands (they need to reach `api.github.com`). + +## Key Constraint + +| Endpoint | Status | +| --------------------------------- | ---------------------- | +| `api.github.com/graphql` | **Blocked** in sandbox | +| `api.github.com/repos/...` (REST) | Allowed | + +**Rule:** Never use `gh` subcommands that call GraphQL. When in doubt, use `gh api` with an explicit REST path. + +### Commands known to use GraphQL (avoid these) + +- `gh pr list`, `gh pr view`, `gh pr status`, `gh pr checks` +- `gh issue list`, `gh issue view`, `gh issue status` +- `gh project` (all subcommands) +- `gh search` (all subcommands) +- `gh label list` + +### Commands that are REST-safe + +- `gh api ` — always REST when given a path (not `graphql`) +- `gh pr create` — uses REST +- `gh pr merge` — uses REST +- `gh release *` — uses REST +- `gh run *` — uses REST +- `gh workflow *` — uses REST +- `gh auth status` — local/REST +- `gh repo clone` / `gh repo view --json` — may use GraphQL; prefer `gh api` + +## Authentication Check + +```bash +gh auth status +``` + +If not authenticated, instruct the user to run `gh auth login`. + +## Repositories + +### Get repo info + +```bash +gh api repos/{owner}/{repo} +``` + +### List repos for a user or org + +```bash +gh api "users/{username}/repos?per_page=30&sort=updated" +gh api "orgs/{org}/repos?per_page=30&sort=updated" +``` + +## Issues + +### List issues + +```bash +gh api "repos/{owner}/{repo}/issues?state=open&per_page=30" \ + | jq '.[] | {number, title, state, user: .user.login, labels: [.labels[].name]}' +``` + +Filter options (query params): `state` (open/closed/all), `labels` (comma-separated), `assignee`, `creator`, `milestone`, `sort` (created/updated/comments), `direction` (asc/desc), `since` (ISO 8601), `per_page`, `page`. + +### Get a single issue + +```bash +gh api repos/{owner}/{repo}/issues/{number} +``` + +### Create an issue + +```bash +gh api repos/{owner}/{repo}/issues \ + -f title="Issue title" \ + -f body="Issue description" \ + -f "labels[]=bug" \ + -f "assignees[]={username}" +``` + +### Comment on an issue + +```bash +gh api repos/{owner}/{repo}/issues/{number}/comments \ + -f body="Comment text" +``` + +### Close an issue + +```bash +gh api repos/{owner}/{repo}/issues/{number} -X PATCH -f state=closed +``` + +### Reopen an issue + +```bash +gh api repos/{owner}/{repo}/issues/{number} -X PATCH -f state=open +``` + +### Add labels + +```bash +gh api repos/{owner}/{repo}/issues/{number}/labels \ + -f "labels[]=bug" -f "labels[]=priority-high" +``` + +## Pull Requests + +### List PRs + +```bash +gh api "repos/{owner}/{repo}/pulls?state=open&per_page=30" \ + | jq '.[] | {number, title, state, user: .user.login, head: .head.ref, base: .base.ref}' +``` + +Filter options: `state` (open/closed/all), `head` (filter by head user/org and branch: `user:ref-name`), `base` (filter by base branch), `sort` (created/updated/popularity/long-running), `direction`, `per_page`, `page`. + +### Get a single PR + +```bash +gh api repos/{owner}/{repo}/pulls/{number} \ + | jq '{number, title, state, body, user: .user.login, head: .head.ref, base: .base.ref, mergeable: .mergeable, merged: .merged}' +``` + +### Get PR diff + +```bash +gh api repos/{owner}/{repo}/pulls/{number} \ + -H "Accept: application/vnd.github.diff" +``` + +### Get PR files changed + +```bash +gh api "repos/{owner}/{repo}/pulls/{number}/files?per_page=100" \ + | jq '.[] | {filename, status, additions, deletions, changes}' +``` + +### Create a PR (REST-safe alternative) + +`gh pr create` is REST-safe and the easiest way: + +```bash +gh pr create --title "PR title" --body "PR description" --base main --head feature-branch +``` + +Or via `gh api`: + +```bash +gh api repos/{owner}/{repo}/pulls \ + -f title="PR title" \ + -f body="PR description" \ + -f head="feature-branch" \ + -f base="main" +``` + +### Merge a PR + +```bash +gh pr merge {number} --squash # REST-safe +``` + +Or via `gh api`: + +```bash +gh api repos/{owner}/{repo}/pulls/{number}/merge \ + -X PUT -f merge_method=squash +``` + +### Request reviewers + +```bash +gh api repos/{owner}/{repo}/pulls/{number}/requested_reviewers \ + -f "reviewers[]={username}" +``` + +### List PR reviews + +```bash +gh api "repos/{owner}/{repo}/pulls/{number}/reviews" \ + | jq '.[] | {user: .user.login, state, body}' +``` + +### List PR review comments + +```bash +gh api "repos/{owner}/{repo}/pulls/{number}/comments" \ + | jq '.[] | {user: .user.login, path, body, line}' +``` + +### Comment on a PR + +PRs use the issues comments endpoint: + +```bash +gh api repos/{owner}/{repo}/issues/{number}/comments \ + -f body="Comment text" +``` + +## Commits & Branches + +### List branches + +```bash +gh api "repos/{owner}/{repo}/branches?per_page=30" \ + | jq '.[].name' +``` + +### Get a commit + +```bash +gh api repos/{owner}/{repo}/commits/{sha} \ + | jq '{sha, message: .commit.message, author: .commit.author.name, date: .commit.author.date}' +``` + +### Compare two refs + +```bash +gh api "repos/{owner}/{repo}/compare/{base}...{head}" \ + | jq '{ahead_by, behind_by, total_commits, files: [.files[] | {filename, status, additions, deletions}]}' +``` + +### List commits on a branch + +```bash +gh api "repos/{owner}/{repo}/commits?sha={branch}&per_page=20" \ + | jq '.[] | {sha: .sha[:8], message: (.commit.message | split("\n")[0]), date: .commit.author.date}' +``` + +## GitHub Actions + +### List workflow runs + +```bash +gh run list --limit 10 # REST-safe +``` + +Or via `gh api`: + +```bash +gh api "repos/{owner}/{repo}/actions/runs?per_page=10" \ + | jq '.workflow_runs[] | {id, name, status, conclusion, head_branch, created_at}' +``` + +### Get a specific run + +```bash +gh api repos/{owner}/{repo}/actions/runs/{run_id} \ + | jq '{id, name, status, conclusion, head_branch, html_url}' +``` + +### List jobs for a run + +```bash +gh api "repos/{owner}/{repo}/actions/runs/{run_id}/jobs" \ + | jq '.jobs[] | {id, name, status, conclusion, started_at, completed_at}' +``` + +### Download job logs + +```bash +gh run view {run_id} --log # REST-safe +``` + +### Re-run a workflow + +```bash +gh api repos/{owner}/{repo}/actions/runs/{run_id}/rerun -X POST +``` + +### Re-run failed jobs only + +```bash +gh api repos/{owner}/{repo}/actions/runs/{run_id}/rerun-failed-jobs -X POST +``` + +### List workflows + +```bash +gh api "repos/{owner}/{repo}/actions/workflows" \ + | jq '.workflows[] | {id, name, state, path}' +``` + +### Trigger a workflow dispatch + +```bash +gh api repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches \ + -f ref=main -f "inputs[key]=value" +``` + +## Releases + +### List releases + +```bash +gh api "repos/{owner}/{repo}/releases?per_page=10" \ + | jq '.[] | {tag_name, name, draft, prerelease, published_at}' +``` + +### Get latest release + +```bash +gh api repos/{owner}/{repo}/releases/latest \ + | jq '{tag_name, name, body, published_at, assets: [.assets[] | {name, download_count, browser_download_url}]}' +``` + +### Create a release + +```bash +gh api repos/{owner}/{repo}/releases \ + -f tag_name="v1.0.0" \ + -f name="Release v1.0.0" \ + -f body="Release notes here" \ + -F draft=false \ + -F prerelease=false +``` + +## Check Runs & Statuses (CI) + +### List check runs for a ref + +```bash +gh api "repos/{owner}/{repo}/commits/{ref}/check-runs" \ + | jq '.check_runs[] | {name, status, conclusion, html_url}' +``` + +### Get combined status for a ref + +```bash +gh api "repos/{owner}/{repo}/commits/{ref}/status" \ + | jq '{state, total_count, statuses: [.statuses[] | {context, state, description}]}' +``` + +## Pagination + +GitHub REST API returns at most 100 items per page. Use `per_page` and `page` query params: + +```bash +gh api "repos/{owner}/{repo}/issues?state=all&per_page=100&page=1" +gh api "repos/{owner}/{repo}/issues?state=all&per_page=100&page=2" +``` + +Or use `--paginate` to auto-follow `Link` headers (returns all pages concatenated): + +```bash +gh api "repos/{owner}/{repo}/issues?state=all&per_page=100" --paginate \ + | jq '.[] | {number, title}' +``` + +## Resolving Owner/Repo + +If the user doesn't specify a repo, infer from the current git remote: + +```bash +gh api repos/:owner/:repo +``` + +`gh api` resolves `:owner` and `:repo` from the current git remote automatically. + +## Error Handling + +- **401 Unauthorized** — run `gh auth status`; may need `gh auth login` +- **403 Forbidden** — rate limit or insufficient permissions; check `gh api rate_limit` +- **404 Not Found** — wrong owner/repo/number or private repo without access +- **422 Unprocessable** — invalid payload; check field names and types +- **Network error / timeout** — if you see connection refused on the graphql endpoint, you're hitting the sandbox restriction; switch to a REST endpoint + diff --git a/profiles/stackrox/image/sandbox-collector-builder/policy.yaml b/profiles/stackrox/image/sandbox-collector-builder/policy.yaml new file mode 100644 index 0000000..395a8b1 --- /dev/null +++ b/profiles/stackrox/image/sandbox-collector-builder/policy.yaml @@ -0,0 +1,42 @@ +version: 1 + +# Keep the image policy small. Provider profiles and workflow policy overlays +# supply integration egress (GitHub, Atlassian, Google Workspace, and model +# providers) at run time. No provider credentials or broad upstream policy is +# baked into this image. +filesystem_policy: + include_workdir: true + read_only: + - /usr + - /lib + - /proc + - /dev/urandom + - /app + - /etc + - /var/log + read_write: + - /sandbox + - /tmp + - /dev/null + +landlock: + compatibility: best_effort + +process: + run_as_user: sandbox + run_as_group: sandbox + +# gopls may resolve missing modules while analyzing a checked-out repository. +# Keep its cache under /sandbox so this remains compatible with the filesystem +# policy above. Other network access must come from a provider or an explicit +# workflow overlay. +network_policies: + go_modules: + name: go-modules + endpoints: + - { host: proxy.golang.org, port: 443 } + - { host: sum.golang.org, port: 443 } + - { host: go.dev, port: 443 } + binaries: + - { path: /usr/local/go/bin/go } + - { path: /usr/local/bin/gopls } diff --git a/profiles/stackrox/image/sandbox-collector-builder/settings.json b/profiles/stackrox/image/sandbox-collector-builder/settings.json new file mode 100644 index 0000000..1bb7091 --- /dev/null +++ b/profiles/stackrox/image/sandbox-collector-builder/settings.json @@ -0,0 +1,55 @@ +{ + "permissions": { + "defaultMode": "acceptEdits", + "allow": [ + "Bash(cd *)", + "Bash(cp *)", + "Bash(curl *)", + "Bash(for *)", + "Bash(gh *)", + "Bash(git *)", + "Bash(go list *)", + "Bash(go mod *)", + "Bash(go version *)", + "Bash(if *)", + "Bash(jq *)", + "Bash(mkdir *)", + "Bash(python3 *)", + "Bash(test *)", + "Bash(timeout *)", + "Bash(while *)", + "Read", + "Write(artifacts/*)", + "Edit(artifacts/*)", + "Glob", + "Grep", + "Agent", + "SendMessage", + "TaskCreate", + "TaskUpdate", + "TaskList", + "TaskGet", + "mcp__*" + ], + "deny": [ + "Bash(rm *)", + "Bash(chmod *)", + "Bash(chown *)", + "Bash(apt *)", + "Bash(pip *)", + "Bash(npm *)", + "Bash(git push *)", + "Read(**/.env*)", + "Read(**/.ssh/*)", + "Read(**/.gnupg/*)", + "Read(**/.config/gh/*)", + "Read(**/.claude.json)" + ] + }, + "enableAllProjectMcpServers": true, + "trustedMcpServers": ["atlassian"], + "skipDangerousModePermissionPrompt": true, + "env": { + "CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY": "1" + } +} From fb6b747e05b6c52bd65bddf12a2285e4c91e05c7 Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Sat, 12 Sep 2026 15:13:10 -0700 Subject: [PATCH 2/9] ci: publish collector-builder sandbox profile --- .github/workflows/images.yml | 3 +++ profiles/stackrox/README.md | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/.github/workflows/images.yml b/.github/workflows/images.yml index 4fef0ca..576546c 100644 --- a/.github/workflows/images.yml +++ b/.github/workflows/images.yml @@ -25,6 +25,9 @@ jobs: - profile: sandbox-stackrox-ci tag_prefix: sandbox-stackrox-ci platforms: linux/amd64 + - profile: sandbox-collector-builder + tag_prefix: sandbox-collector-builder + platforms: linux/amd64 env: IMAGE_PROFILE: ${{ matrix.profile }} IMAGE_TAG_PREFIX: ${{ matrix.tag_prefix }} diff --git a/profiles/stackrox/README.md b/profiles/stackrox/README.md index 871cf59..b895c97 100644 --- a/profiles/stackrox/README.md +++ b/profiles/stackrox/README.md @@ -43,3 +43,26 @@ docker build --platform linux/amd64 \ -t quay.io/rcochran/openshell:sandbox-stackrox-ci \ profiles/stackrox/image/sandbox-stackrox-ci ``` + +### `sandbox-collector-builder` + +An amd64 image based on the StackRox Collector builder image. The `master` +builder manifest is pinned to +`sha256:52a3cca5253abb0cd5c3606769d5fd5b8be4cdb4671396f38a792695f07d7696`. +It keeps the Collector compiler and build toolchain and adds the same +OpenShell contract, coding agents, GitHub skill, Atlassian MCP, Google +Workspace CLI, and `gopls` support as the StackRox CI profile. It is separate +from `sandbox-stackrox-ci` so workflows can choose the Collector-specific +toolchain without changing the Apollo/rox-ci-image profile. + +The base image is multi-architecture, but several bundled third-party +binaries are currently amd64-only, so this profile is published for +`linux/amd64` only. + +Build it locally with: + +```bash +docker build --platform linux/amd64 \ + -t quay.io/rcochran/openshell:sandbox-collector-builder \ + profiles/stackrox/image/sandbox-collector-builder +``` From 43db256379c71018a6c39e109057efca57578d2a Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Sat, 12 Sep 2026 15:13:28 -0700 Subject: [PATCH 3/9] style: normalize vendored GitHub skill --- .../sandbox-collector-builder/openshell/skills/github/SKILL.md | 1 - 1 file changed, 1 deletion(-) diff --git a/profiles/stackrox/image/sandbox-collector-builder/openshell/skills/github/SKILL.md b/profiles/stackrox/image/sandbox-collector-builder/openshell/skills/github/SKILL.md index 55fe8af..b3c9137 100644 --- a/profiles/stackrox/image/sandbox-collector-builder/openshell/skills/github/SKILL.md +++ b/profiles/stackrox/image/sandbox-collector-builder/openshell/skills/github/SKILL.md @@ -377,4 +377,3 @@ gh api repos/:owner/:repo - **404 Not Found** — wrong owner/repo/number or private repo without access - **422 Unprocessable** — invalid payload; check field names and types - **Network error / timeout** — if you see connection refused on the graphql endpoint, you're hitting the sandbox restriction; switch to a REST endpoint - From e7219b84344c498d2c626115eba25e3919a4e732 Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Sat, 12 Sep 2026 15:16:38 -0700 Subject: [PATCH 4/9] fix: install Node tooling in collector-builder image --- .../stackrox/image/sandbox-collector-builder/Dockerfile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/profiles/stackrox/image/sandbox-collector-builder/Dockerfile b/profiles/stackrox/image/sandbox-collector-builder/Dockerfile index 43beb43..1e5af7a 100644 --- a/profiles/stackrox/image/sandbox-collector-builder/Dockerfile +++ b/profiles/stackrox/image/sandbox-collector-builder/Dockerfile @@ -26,8 +26,9 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] USER root # Runtime tools used by the OpenShell sandbox contract and by agent skills. -# collector-builder image already supplies the compiler toolchain, Go, Node, npm, git, jq, -# and curl; install only the missing runtime pieces here. +# collector-builder image already supplies the compiler toolchain, Go, git, jq, +# and curl. Add Node/npm for the coding-agent CLIs, plus the missing runtime +# pieces needed by the OpenShell sandbox contract. RUN dnf install -y --setopt=install_weak_deps=False \ ca-certificates \ findutils \ @@ -37,6 +38,8 @@ RUN dnf install -y --setopt=install_weak_deps=False \ nano \ net-tools \ nmap-ncat \ + nodejs \ + npm \ openssh-clients \ perl-Digest-SHA \ procps-ng \ From a79329cf8bedde434634399d7d8c5d4d0e6d53df Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Sat, 12 Sep 2026 15:19:13 -0700 Subject: [PATCH 5/9] fix: add Go toolchain to collector-builder image --- .../image/sandbox-collector-builder/Dockerfile | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/profiles/stackrox/image/sandbox-collector-builder/Dockerfile b/profiles/stackrox/image/sandbox-collector-builder/Dockerfile index 1e5af7a..b12b37f 100644 --- a/profiles/stackrox/image/sandbox-collector-builder/Dockerfile +++ b/profiles/stackrox/image/sandbox-collector-builder/Dockerfile @@ -126,8 +126,16 @@ RUN curl -fsSL "https://downloads.claude.ai/claude-code-releases/${CLAUDE_VERSIO && install -m 0755 /tmp/claude /usr/local/bin/claude \ && rm -f /tmp/claude -# Go-language MCP support for repository analysis. collector-builder image supplies the Go -# toolchain; gopls is the only additional Go binary needed here. +# Go-language MCP support for repository analysis. The Collector builder image +# supplies C/C++ build tooling but not Go, so install the pinned Go toolchain +# before building gopls. +ARG GO_VERSION=1.24.2 +ARG GO_SHA256_AMD64=68097bd680839cbc9d464a0edce4f7c333975e27a90246890e9f1078c7e702ad +RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" -o /tmp/go.tgz \ + && echo "${GO_SHA256_AMD64} /tmp/go.tgz" | sha256sum -c - \ + && tar -C /usr/local -xzf /tmp/go.tgz \ + && rm -f /tmp/go.tgz + ARG GOPLS_VERSION=0.20.0 RUN GOBIN=/usr/local/bin /usr/local/go/bin/go install "golang.org/x/tools/gopls@v${GOPLS_VERSION}" \ && rm -rf /root/go /root/.cache/go-build From 3f5c31eb5d2ca7f2c96504dbb01a05a871a02758 Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Sat, 12 Sep 2026 15:24:20 -0700 Subject: [PATCH 6/9] simplify collector-builder npm tool installation --- .../image/sandbox-collector-builder/Dockerfile | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/profiles/stackrox/image/sandbox-collector-builder/Dockerfile b/profiles/stackrox/image/sandbox-collector-builder/Dockerfile index b12b37f..03f61c9 100644 --- a/profiles/stackrox/image/sandbox-collector-builder/Dockerfile +++ b/profiles/stackrox/image/sandbox-collector-builder/Dockerfile @@ -83,9 +83,6 @@ RUN uv python install "${PYTHON_VERSION}" \ # Coding agents and the JSON-schema utility used by StackRox workflows. These # versions mirror the current StackRox sandbox profile where applicable. -# tar is a Node library dependency; npm leaves the system GNU tar in /usr/bin -# unchanged. -ARG NPM_TAR_VERSION=7.5.11 ARG NPM_HONO_VERSION=1.19.11 ARG NPM_OPENCODE_VERSION=1.18.30 ARG NPM_CODEX_VERSION=0.117.0 @@ -95,7 +92,6 @@ RUN mkdir -p /tmp/npm-global \ && chown sandbox:sandbox /tmp/npm-global USER sandbox RUN NPM_CONFIG_PREFIX=/tmp/npm-global npm install -g \ - "tar@${NPM_TAR_VERSION}" \ "@hono/node-server@${NPM_HONO_VERSION}" \ "opencode-ai@${NPM_OPENCODE_VERSION}" \ "@openai/codex@${NPM_CODEX_VERSION}" \ @@ -106,17 +102,13 @@ USER root RUN cp -a /tmp/npm-global/lib/node_modules/. /usr/lib/node_modules/ \ && for name in opencode codex copilot ajv; do \ test -e "/tmp/npm-global/bin/${name}"; \ - cp -a "/tmp/npm-global/bin/${name}" "/usr/bin/${name}"; \ - chown -h root:root "/usr/bin/${name}"; \ + cp -a "/tmp/npm-global/bin/${name}" "/usr/local/bin/${name}"; \ + chown -h root:root "/usr/local/bin/${name}"; \ done \ && chown -R root:root /usr/lib/node_modules \ && tar --version | grep -q 'GNU tar' \ && rm -rf /tmp/npm-global -# Keep the profile's command path stable even though npm puts the executable -# in /usr/bin on this UBI-based image. -RUN ln -sf /usr/bin/opencode /usr/local/bin/opencode - # Claude Code's native binary. Pin both the release URL and its release # manifest checksum instead of executing a downloaded installer as root. ARG CLAUDE_VERSION=2.1.269 From c672fbdb0effc3aa5a80fade3d217981088d5ed6 Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Sat, 12 Sep 2026 15:39:51 -0700 Subject: [PATCH 7/9] simplify collector-builder npm cleanup --- profiles/stackrox/image/sandbox-collector-builder/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/profiles/stackrox/image/sandbox-collector-builder/Dockerfile b/profiles/stackrox/image/sandbox-collector-builder/Dockerfile index 03f61c9..cb03113 100644 --- a/profiles/stackrox/image/sandbox-collector-builder/Dockerfile +++ b/profiles/stackrox/image/sandbox-collector-builder/Dockerfile @@ -106,7 +106,6 @@ RUN cp -a /tmp/npm-global/lib/node_modules/. /usr/lib/node_modules/ \ chown -h root:root "/usr/local/bin/${name}"; \ done \ && chown -R root:root /usr/lib/node_modules \ - && tar --version | grep -q 'GNU tar' \ && rm -rf /tmp/npm-global # Claude Code's native binary. Pin both the release URL and its release From 78b90a68e12ffaae6585ddcf824d795889bf4b7d Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Sat, 12 Sep 2026 15:48:08 -0700 Subject: [PATCH 8/9] fix: isolate collector-builder Go install cache --- .../stackrox/image/sandbox-collector-builder/Dockerfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/profiles/stackrox/image/sandbox-collector-builder/Dockerfile b/profiles/stackrox/image/sandbox-collector-builder/Dockerfile index cb03113..68d00f2 100644 --- a/profiles/stackrox/image/sandbox-collector-builder/Dockerfile +++ b/profiles/stackrox/image/sandbox-collector-builder/Dockerfile @@ -102,8 +102,8 @@ USER root RUN cp -a /tmp/npm-global/lib/node_modules/. /usr/lib/node_modules/ \ && for name in opencode codex copilot ajv; do \ test -e "/tmp/npm-global/bin/${name}"; \ - cp -a "/tmp/npm-global/bin/${name}" "/usr/local/bin/${name}"; \ - chown -h root:root "/usr/local/bin/${name}"; \ + cp -a "/tmp/npm-global/bin/${name}" "/usr/bin/${name}"; \ + chown -h root:root "/usr/bin/${name}"; \ done \ && chown -R root:root /usr/lib/node_modules \ && rm -rf /tmp/npm-global @@ -128,8 +128,9 @@ RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" -o /tmp/go && rm -f /tmp/go.tgz ARG GOPLS_VERSION=0.20.0 -RUN GOBIN=/usr/local/bin /usr/local/go/bin/go install "golang.org/x/tools/gopls@v${GOPLS_VERSION}" \ - && rm -rf /root/go /root/.cache/go-build +RUN GOPATH=/tmp/gopath GOCACHE=/tmp/gocache GOBIN=/usr/local/bin \ + /usr/local/go/bin/go install "golang.org/x/tools/gopls@v${GOPLS_VERSION}" \ + && rm -rf /tmp/gopath /tmp/gocache # Atlassian MCP is part of the StackRox workflow image contract. The gateway # still owns credentials; no credential values are baked into this image. From 82fa6e57da3ebfba4775aae69f7f273479b549fc Mon Sep 17 00:00:00 2001 From: Robby Cochran Date: Sat, 12 Sep 2026 15:58:22 -0700 Subject: [PATCH 9/9] docs: clarify collector-builder Go tooling --- profiles/stackrox/image/sandbox-collector-builder/CLAUDE.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/profiles/stackrox/image/sandbox-collector-builder/CLAUDE.md b/profiles/stackrox/image/sandbox-collector-builder/CLAUDE.md index 3cb3a4c..edeb265 100644 --- a/profiles/stackrox/image/sandbox-collector-builder/CLAUDE.md +++ b/profiles/stackrox/image/sandbox-collector-builder/CLAUDE.md @@ -9,8 +9,9 @@ are not part of the image. - Working directory: `/sandbox` - Writable paths: `/sandbox`, `/tmp` - Inference routes through the gateway proxy at `inference.local` -- Repository build tools from the `collector-builder` build are available, including - Go, compilers, make, git, jq, and the StackRox CI toolchain. +- Repository build tools from the `collector-builder` build are available, + including compilers, make, git, jq, and the StackRox CI toolchain. This + profile adds the pinned Go toolchain and `gopls` for Go repository analysis. ## Tools