From hawkscan-ci
Use when the user wants to WIRE HawkScan into a CI/CD pipeline config file — triggers on "set up hawkscan in CI", "add stackhawk to my pipeline", "scan in CI", "configure github actions / gitlab / jenkins / circleci for hawkscan", "wire hawkscan into ci/cd", or any provider-named variant. Provider-agnostic: detects the CI system from repo files, edits the pipeline file in place to add a HawkScan job, prompts for HAWK_API_KEY storage in the CI's native secrets store (or an approved external manager), and wires commit-SHA + branch traceability. If no local stackhawk.yml exists yet, still trigger and route the local-config work to the hawkscan skill. Do NOT trigger for: documentation-only changes (e.g. editing a README about CI scanning); informational or research questions ("what CI providers does HawkScan support?"); or running / performing a scan — a local scan is the hawkscan skill, whereas this skill only edits CI pipeline config. Explicit trigger only; no autonomous code-change hook.
How this skill is triggered — by the user, by Claude, or both
Slash command
/hawkscan-ci:hawkscan-ciThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill graduates a working *local* HawkScan setup into a CI/CD
This skill graduates a working local HawkScan setup into a CI/CD
pipeline. It is a wrapper around the local-scan path the hawkscan
skill owns — it never generates stackhawk.yml, never picks auth
recipes, never parses findings. Its scope is the pipeline file alone.
Hand-off rule: if stackhawk.yml doesn't exist or doesn't validate,
the agent must invoke the hawkscan skill first. This skill does not
onboard apps from scratch.
Before doing anything CI-specific, confirm the local scan path is in place. Run:
test -f stackhawk.yml || echo "MISSING_CONFIG"
timeout 30 hawk validate config stackhawk.yml 2>&1 | head -20
If stackhawk.yml is missing, validation fails, or applicationId is
still a placeholder (${APP_ID} with no real value, or literal
your-app-id):
"I need a working local scan first. Run the hawkscan skill (or invoke it explicitly) to generate and validate
stackhawk.yml, then come back here."
Stop. Do not proceed.
Glob the repo for known CI config locations:
ls .github/workflows/*.yml .github/workflows/*.yaml 2>/dev/null
ls .gitlab-ci.yml 2>/dev/null
ls Jenkinsfile* 2>/dev/null
ls .circleci/config.yml 2>/dev/null
ls azure-pipelines*.yml 2>/dev/null
ls bitbucket-pipelines.yml 2>/dev/null
ls .buildkite/pipeline.yml 2>/dev/null
ls .travis.yml 2>/dev/null
ls -d bamboo-specs 2>/dev/null
ls concourse/*.yml 2>/dev/null
ls -d harness 2>/dev/null
ls appspec.yml buildspec.yml 2>/dev/null
| Match | Provider |
|---|---|
.github/workflows/*.yml, *.yaml | GitHub Actions |
.gitlab-ci.yml | GitLab CI/CD |
Jenkinsfile, Jenkinsfile.* | Jenkins |
.circleci/config.yml | CircleCI |
azure-pipelines*.yml | Azure Pipelines |
bitbucket-pipelines.yml | Bitbucket Pipelines |
.buildkite/pipeline.yml | Buildkite |
.travis.yml | Travis CI |
bamboo-specs/ | Bamboo |
appspec.yml, buildspec.yml | AWS CodeBuild / CodeDeploy |
concourse/*.yml (heuristic — confirm with user) | Concourse |
harness/ | Harness |
Three planning questions. Use the defaults if the user just says "sounds good".
2a. Trigger — when should the scan run?
"On pull requests, on pushes to main, on a daily schedule, or all three? Default: PRs + push to main."
2b. Blocking behavior — what happens on findings?
"Should findings above the configured threshold fail the pipeline (exit code 42 → build failure), or warn-only (log the result, exit 0)? Default: fail the pipeline."
2c. Job placement — where does the scan fit?
"Standalone parallel job (scans an ephemeral env or already-deployed preview), or chained after build (starts the app in the same job and scans it)? Default: chained-after-build."
These answers parameterize Step 5.
The skill never sets the secret itself. Prompt the user and tell them exactly where to put it.
Ask once:
"Where should
HAWK_API_KEYcome from at runtime? Most teams use their CI provider's built-in secret store (recommended). If your org requires an external secrets manager (Vault, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, 1Password, Doppler), tell me which one and I'll generate the workflow to fetch from it instead."
Print the exact command/UI step the user needs to run:
| Provider | Command / UI |
|---|---|
| GitHub Actions | gh secret set HAWK_API_KEY (paste interactively), or Settings → Secrets and variables → Actions |
| GitLab CI/CD | glab variable set HAWK_API_ID <id> --masked --protected AND glab variable set HAWK_API_SECRET <secret> --masked --protected — mandatory key split (GitLab's masker can't handle the . separators in hawk.<id>.<secret>). Reassemble in the job: API_KEY="hawk.${HAWK_API_ID}.${HAWK_API_SECRET}" |
| Jenkins | Credentials Store → Add credential → "Secret text", ID HAWK_API_KEY (UI only) |
| CircleCI | circleci context store-secret <context-name> HAWK_API_KEY (org-scoped Contexts), or Project Settings → Environment Variables (UI) |
| Azure Pipelines | az pipelines variable create --name HAWK_API_KEY --secret true, or variable group |
| Bitbucket | Repository Settings → Repository variables → add HAWK_API_KEY, check "Secured" |
| Others | See references/execution-shapes.md |
If the user picked Vault / AWS SM / GCP SM / Azure KV / 1Password / Doppler / etc., the workflow you write in Step 5 includes a fetch step before the scan step. Fetch patterns:
vault kv get -field=hawk_api_key secret/ci/stackhawkaws secretsmanager get-secret-value --secret-id stackhawk/hawk-api-key --query SecretString --output textgcloud secrets versions access latest --secret=hawk-api-keyaz keyvault secret show --vault-name <vault> --name hawk-api-key --query value -o tsv1password/load-secrets-action@v2 with op://<vault>/<item>/<field> referencedoppler secrets get HAWK_API_KEY --plainThe user is responsible for the manager-side setup (storing the secret, granting the runner read access). Call those out as out-of-band actions in Step 6.
Reminder regardless of path: the secret must be set before the first pipeline run, or the scan will fail with a 401.
Three tiers, prefer highest the provider supports:
| Tier | Mechanism | When to use |
|---|---|---|
| 1. Native action | stackhawk/hawkscan-action@v2 | GitHub Actions only |
| 2. Docker image | stackhawk/hawkscan:latest | Anywhere with Docker (GitLab, Jenkins, CircleCI, Azure, Bitbucket, Buildkite, AWS CodeBuild) |
| 3. Binary download | hawk.manifest.json → download.stackhawk.com/hawk/<version>/<platform>/hawk (self-contained binary) | Bare shell runners (Travis without sudo, some Spinnaker stages) |
Track the latest stable scanner:
stackhawk/hawkscan:latest — HawkScan is a security scanner, so CI should run the newest stable build for the freshest checks.@v2) — auto-receives the newest action within v2.download.stackhawk.com/hawkdocs/hawk.manifest.json → .latest.version and asset .url); it's a single self-contained binary (no Java).stackhawk/hawkscan:<X.Y.Z>, @vX.Y.Z, or an image digest) instead — see references/execution-shapes.md.See references/execution-shapes.md for the full per-provider mapping.
Build the workflow block using the choices from Steps 2-4. The block must do these things, in this order:
Use the provider's standard checkout at the commit under test.
Detect the start pattern from the repo:
ls docker-compose.yml docker-compose.yaml compose.yml compose.yaml 2>/dev/null
test -f package.json && jq -r '.scripts | keys[]' package.json 2>/dev/null | grep -E '^(start|dev|serve)$'
test -f Makefile && grep -E '^(start|run|serve):' Makefile 2>/dev/null
ls pom.xml build.gradle build.gradle.kts 2>/dev/null
docker compose up -d + wait-for-it on the health endpoint. Make sure the scanned image is built from the commit under test — build in-job (--build / a build: override) or build→push→pull; a stale or upstream image: tag gates code that isn't in this PR. See references/app-startup-patterns.md Pattern 1.npm install && npm start & + wait-for-it../mvnw spring-boot:run & or ./gradlew bootRun & + wait-for-it.App-startup pattern menu lives in references/app-startup-patterns.md.
If the scan authenticates, the credential should come from the scan itself, not
the pipeline. HawkScan authenticates from the authentication: block in
stackhawk.yml — including app.authentication.script for multi-step
login/token-mint flows — owned by the hawkscan skill, so the same scan
authenticates identically on a laptop and in CI. Confirm that's in place rather
than replicating it in the workflow:
stackhawk.yml already has a validated authentication: block → add nothing;
the scan handles auth itself.hawkscan skill to add/repair the recipe, and to
stackhawk-data-seed if the app needs seed data before authenticated routes
return anything.data-seed/ replay over bespoke scripting.Don't replicate a token-mint step in the workflow — it belongs in the scan's
authentication.script. Full priority order + ownership table:
references/app-startup-patterns.md Pattern 8.
COMMIT_SHA = provider's commit SHA expression
BRANCH_NAME = provider's branch expression
Provider expression cheat sheet:
| Provider | COMMIT_SHA | BRANCH_NAME |
|---|---|---|
| GitHub Actions | ${{ github.sha }} | ${{ github.head_ref || github.ref_name }} |
| GitLab CI | ${CI_COMMIT_SHA} | ${CI_COMMIT_REF_NAME} |
| Jenkins | ${env.GIT_COMMIT} | ${env.BRANCH_NAME} (multibranch) |
| CircleCI | ${CIRCLE_SHA1} | ${CIRCLE_BRANCH} |
| Azure Pipelines | $(Build.SourceVersion) | $(Build.SourceBranchName) |
| Bitbucket | ${BITBUCKET_COMMIT} | ${BITBUCKET_BRANCH} |
| Buildkite | ${BUILDKITE_COMMIT} | ${BUILDKITE_BRANCH} |
| Others | see references/execution-shapes.md | see references/execution-shapes.md |
Do not set HAWK_AGENT. That variable attributes scans to the AI
agent that triggered the work; a CI pipeline isn't an agent. The
${HAWK_AGENT:none} default already in stackhawk.yml resolves to
none for CI runs, which is the correct attribution. Leave it alone.
If the user picked an external secrets manager in Step 3, insert the
provider-appropriate fetch step before the scan step, then export the
result as HAWK_API_KEY for the scan step.
The scan step itself. Pass HAWK_API_KEY from the chosen secret
source via the provider's native secret reference (e.g.,
${{ secrets.HAWK_API_KEY }} for GHA, $HAWK_API_KEY exported from
the previous step for everyone else).
Per the Step 2b choice:
set +e
hawk scan
HAWK_EXIT=$?
case $HAWK_EXIT in
0) ;;
42) echo "::warning::HawkScan found findings — review at the platform URL above." ;;
*) exit $HAWK_EXIT ;; # exit 1 = config error / unreachable / auth — never swallow
esac
GitHub Actions has a native form (continue-on-error: true). Full per-provider warn-only patterns: references/failure-semantics.md.Per provider:
| Provider | Mechanism |
|---|---|
| GitHub Actions | actions/upload-artifact@v4 |
| GitLab | artifacts: block in the job |
| Jenkins | archiveArtifacts artifacts: 'hawkscan/*' |
| CircleCI | store_artifacts step |
| Azure Pipelines | PublishBuildArtifacts@1 task |
| Bitbucket | artifacts: block |
Path: the location the invocation shape produces (typically
hawkscan-report/ for the action, or $(pwd)/hawkscan/ for Docker).
Full per-provider workflow examples live in
references/execution-shapes.md.
Pick the example matching the detected provider, parameterize per
Steps 2-4, and write the result.
After writing/patching the workflow file:
stackhawk.yml applicationId and host match the environment the pipeline will scan (preview URL vs ephemeral env vs local-in-runner)"First pipeline run will be slower — HawkScan creates the App and Env in the platform if they don't exist, and the initial full scan has no prior baseline to diff against. Subsequent runs reuse the App and Env and are faster."
stackhawk.yml. That's the hawkscan skill's surface. If the config is missing, hand off.HAWK_AGENT. CI pipelines aren't agents; the ${HAWK_AGENT:none} default resolves correctly without intervention.stackhawk/hawkscan:latest for the scanner image. It's a security scanner — CI should run the newest stable build for the best detection. Pin an explicit version only when an org reproducibility mandate requires it.HAWK_API_KEY in the form hawk.<id>.<secret> triggers GitLab's variable-masking bug. Split into HAWK_API_ID + HAWK_API_SECRET and reassemble in the job._STACKHAWK_GIT_* tags from this skill. Those live in stackhawk.yml; if they're missing, hand off to the hawkscan skill for the edit. The CI skill only exports the env vars the tags interpolate from.image: tag built elsewhere gates code that isn't in this PR. Building in-job (--build / a build: override) and build→push→pull both work; scanning a stale or upstream tag is the bug. See references/app-startup-patterns.md Pattern 1.stackhawk.yml's authentication: block (including authentication.script for multi-step login/token-mint flows), owned by the hawkscan skill, so it's portable local↔CI. Confirm it's configured and route to hawkscan if not; add a pipeline step only for a credential that genuinely can't live in the scan. See Pattern 8.Guides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.
Dispatches multiple subagents concurrently for independent tasks without shared state. Use when facing 2+ unrelated failures or subsystems that can be investigated in parallel.
npx claudepluginhub stackhawk/agent-skills --plugin hawkscan-ci