diff --git a/.github/workflows/images.yml b/.github/workflows/images.yml index 6d5e08d..4fef0ca 100644 --- a/.github/workflows/images.yml +++ b/.github/workflows/images.yml @@ -16,6 +16,18 @@ env: jobs: sandbox: runs-on: ubuntu-latest + strategy: + matrix: + include: + - profile: sandbox-default + tag_prefix: sandbox + platforms: linux/amd64,linux/arm64 + - profile: sandbox-stackrox-ci + tag_prefix: sandbox-stackrox-ci + platforms: linux/amd64 + env: + IMAGE_PROFILE: ${{ matrix.profile }} + IMAGE_TAG_PREFIX: ${{ matrix.tag_prefix }} steps: - uses: actions/checkout@v7 with: @@ -27,7 +39,7 @@ jobs: run: | if [[ "$GITHUB_REF_TYPE" == tag || -z "$BASE_SHA" || "$BASE_SHA" =~ ^0+$ ]]; then changed=true - elif git diff --quiet "$BASE_SHA" HEAD -- profiles/stackrox/image/sandbox-default .github/workflows/images.yml; then + elif git diff --quiet "$BASE_SHA" HEAD -- "profiles/stackrox/image/${IMAGE_PROFILE}" .github/workflows/images.yml; then changed=false else changed=true @@ -53,15 +65,15 @@ jobs: with: images: ${{ env.IMAGE_BASE }} tags: | - type=semver,pattern=sandbox-v{{version}} - type=raw,value=sandbox-${{ steps.version.outputs.version }},enable=${{ github.ref_type != 'tag' }} + type=semver,pattern=${{ env.IMAGE_TAG_PREFIX }}-v{{version}} + type=raw,value=${{ env.IMAGE_TAG_PREFIX }}-${{ steps.version.outputs.version }},enable=${{ github.ref_type != 'tag' }} - uses: docker/build-push-action@v6 if: steps.changes.outputs.changed == 'true' with: - context: profiles/stackrox/image/sandbox-default - platforms: linux/amd64,linux/arm64 + context: profiles/stackrox/image/${{ matrix.profile }} + platforms: ${{ matrix.platforms }} push: ${{ github.event_name == 'push' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - cache-from: type=registry,ref=${{ env.IMAGE_BASE }}:sandbox-cache - cache-to: ${{ github.event_name == 'push' && format('type=registry,ref={0}:sandbox-cache,mode=max', env.IMAGE_BASE) || '' }} + cache-from: type=registry,ref=${{ env.IMAGE_BASE }}:${{ env.IMAGE_TAG_PREFIX }}-cache + cache-to: ${{ github.event_name == 'push' && format('type=registry,ref={0}:{1}-cache,mode=max', env.IMAGE_BASE, env.IMAGE_TAG_PREFIX) || '' }} diff --git a/profiles/stackrox/README.md b/profiles/stackrox/README.md index 66cf11a..871cf59 100644 --- a/profiles/stackrox/README.md +++ b/profiles/stackrox/README.md @@ -1,24 +1,45 @@ -# StackRox sandbox image +# StackRox sandbox images -`image/sandbox-default` is the optional StackRox agent image. It extends the -NVIDIA OpenShell community base with integrations shared by StackRox workflows, -including Atlassian MCP and Google Workspace tooling. +StackRox profiles are optional OpenShell sandbox images for workflows that need +repository-specific tools. Providers, credentials, skills supplied by a +workflow, and task-specific policy remain outside the image. +The image does not create or attach providers; a workflow must name providers +that are already provisioned and attach them through `sandbox.providers` before +provider credentials or inference routes are available. -It also includes the ACS triage toolchain: Go, `gopls` (the Go-analysis MCP -server), and `ajv-cli` for JSON Schema validation. `gcloud` is intentionally -not included; OpenShell provider credentials and inference routing replace the -runner-side service-account setup used by the original GitHub Actions workflow. +## Profiles -Generic Harness workflows use the NVIDIA base image directly. Select the -published StackRox image only when a workflow needs one of these additions; -providers, credentials, skills, and task-specific policy remain outside the -image. The image does not create or attach providers; a workflow must name -providers that are already provisioned and attach them through -`sandbox.providers` before provider credentials or inference routes are -available. +### `sandbox-default` + +The general StackRox image, based on the NVIDIA OpenShell community image. It +adds the integrations shared by StackRox workflows, including Atlassian MCP, +Google Workspace, and the ACS triage toolchain (`gopls` and `ajv-cli`). Build it locally with: ```bash make dev-sandbox ``` + +### `sandbox-stackrox-ci` + +An opt-in image based on the StackRox `rox-ci-image` build image +`quay.io/stackrox-io/apollo-ci:stackrox-build-0.5.14-1-g9bed4c4911`. It keeps +the StackRox CI toolchain (Go, compilers, make, and scanner build tools) and +adds the OpenShell sandbox contract, coding agents, `gh`, `uv`, `ajv-cli`, the +GitHub skill, Atlassian MCP, Google Workspace CLI, and the `gopls` MCP server. +Go module and build caches stay below `/sandbox`. It deliberately does not +install `gcloud` or copy service-account keys; OpenShell providers own those +credentials and inference routes. + +The `rox-ci-image` build currently provides an amd64 toolchain, so this profile +is published for `linux/amd64` only. It is an experimental alternative to +`sandbox-default`, not a replacement for it. + +Build it locally with: + +```bash +docker build --platform linux/amd64 \ + -t quay.io/rcochran/openshell:sandbox-stackrox-ci \ + profiles/stackrox/image/sandbox-stackrox-ci +``` diff --git a/profiles/stackrox/image/sandbox-stackrox-ci/CLAUDE.md b/profiles/stackrox/image/sandbox-stackrox-ci/CLAUDE.md new file mode 100644 index 0000000..95abc7f --- /dev/null +++ b/profiles/stackrox/image/sandbox-stackrox-ci/CLAUDE.md @@ -0,0 +1,24 @@ +# Sandbox Environment + +You are running inside an OpenShell sandbox based on the StackRox +`rox-ci-image` build 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 `rox-ci-image` 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-stackrox-ci/Dockerfile b/profiles/stackrox/image/sandbox-stackrox-ci/Dockerfile new file mode 100644 index 0000000..94bfbf0 --- /dev/null +++ b/profiles/stackrox/image/sandbox-stackrox-ci/Dockerfile @@ -0,0 +1,187 @@ +# syntax=docker/dockerfile:1.4 + +# StackRox CI sandbox image for OpenShell repository workflows. +# +# This profile deliberately starts from the StackRox rox-ci-image build so that +# repository build tools (Go, compilers, make, and the scanner toolchain) are +# available to the agent. It adds the OpenShell sandbox contract and the +# coding-agent tools plus selected vendored assets from the NVIDIA community +# base image. +# +# The rox-ci-image build currently publishes an amd64 toolchain. Keep this +# profile amd64-only until the upstream CI image provides a multi-architecture +# build. +# +# Base image source: +# https://github.com/stackrox/rox-ci-image/blob/main/images/scanner-build.Dockerfile +# OpenShell base contract: +# https://github.com/NVIDIA/OpenShell-Community/tree/main/sandboxes/base + +# The tag is stackrox-build-0.5.14-1-g9bed4c4911; pin its immutable digest +# so a retag cannot silently change the toolchain. +ARG BASE_IMAGE=quay.io/stackrox-io/apollo-ci@sha256:fe0e38d8d7792fbe61b593d9fca98906985404a083d1e761f344a8c7e49b873b +FROM ${BASE_IMAGE} + +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +USER root + +# Runtime tools used by the OpenShell sandbox contract and by agent skills. +# rox-ci-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 (rox-ci-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 +# rox-ci-image system Python is 3.9, while current MCP integrations require +# 3.10+. +# This profile is amd64-only; pin the matching uv image manifest for +# reproducible builds. +COPY --from=ghcr.io/astral-sh/uv@sha256:f99c19c9683591761e0dc9d80db421b17d8c004adf4ac4031cac1fc92777f091 /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. +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 \ + "@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 \ + && rm -rf /tmp/npm-global + +# 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. rox-ci-image supplies the Go +# toolchain; gopls is the only additional Go binary needed here. +ARG GOPLS_VERSION=0.20.0 +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. +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 +# UBI glibc version supplied by rox-ci-image. +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-stackrox-ci/claude.json b/profiles/stackrox/image/sandbox-stackrox-ci/claude.json new file mode 100644 index 0000000..5af008a --- /dev/null +++ b/profiles/stackrox/image/sandbox-stackrox-ci/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-stackrox-ci/mcp.json b/profiles/stackrox/image/sandbox-stackrox-ci/mcp.json new file mode 100644 index 0000000..f37b1d4 --- /dev/null +++ b/profiles/stackrox/image/sandbox-stackrox-ci/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-stackrox-ci/opencode.json b/profiles/stackrox/image/sandbox-stackrox-ci/opencode.json new file mode 100644 index 0000000..33680c1 --- /dev/null +++ b/profiles/stackrox/image/sandbox-stackrox-ci/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-stackrox-ci/openshell/.bashrc b/profiles/stackrox/image/sandbox-stackrox-ci/openshell/.bashrc new file mode 100644 index 0000000..559fbbb --- /dev/null +++ b/profiles/stackrox/image/sandbox-stackrox-ci/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-stackrox-ci/openshell/.profile b/profiles/stackrox/image/sandbox-stackrox-ci/openshell/.profile new file mode 100644 index 0000000..91502a9 --- /dev/null +++ b/profiles/stackrox/image/sandbox-stackrox-ci/openshell/.profile @@ -0,0 +1 @@ +[ -f ~/.bashrc ] && . ~/.bashrc diff --git a/profiles/stackrox/image/sandbox-stackrox-ci/openshell/README.md b/profiles/stackrox/image/sandbox-stackrox-ci/openshell/README.md new file mode 100644 index 0000000..808437f --- /dev/null +++ b/profiles/stackrox/image/sandbox-stackrox-ci/openshell/README.md @@ -0,0 +1,16 @@ +# Vendored OpenShell base 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-stackrox-ci/openshell/skills/github/SKILL.md b/profiles/stackrox/image/sandbox-stackrox-ci/openshell/skills/github/SKILL.md new file mode 100644 index 0000000..5d025fc --- /dev/null +++ b/profiles/stackrox/image/sandbox-stackrox-ci/openshell/skills/github/SKILL.md @@ -0,0 +1,372 @@ +--- +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 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) + +Create pull requests with the REST 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-stackrox-ci/policy.yaml b/profiles/stackrox/image/sandbox-stackrox-ci/policy.yaml new file mode 100644 index 0000000..395a8b1 --- /dev/null +++ b/profiles/stackrox/image/sandbox-stackrox-ci/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-stackrox-ci/settings.json b/profiles/stackrox/image/sandbox-stackrox-ci/settings.json new file mode 100644 index 0000000..3e1e207 --- /dev/null +++ b/profiles/stackrox/image/sandbox-stackrox-ci/settings.json @@ -0,0 +1,53 @@ +{ + "permissions": { + "defaultMode": "acceptEdits", + "allow": [ + "Bash(cd *)", + "Bash(curl *)", + "Bash(for *)", + "Bash(gh *)", + "Bash(git *)", + "Bash(go list *)", + "Bash(go mod *)", + "Bash(go version *)", + "Bash(if *)", + "Bash(jq *)", + "Bash(mkdir *)", + "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" + } +}