This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Armis CLI is an enterprise-grade security scanning tool written in Go that integrates with Armis Cloud. It scans repositories and container images for security vulnerabilities, secrets, and license risks.
Prerequisites: Go 1.25+, golangci-lint v2.0+, Make
make build # Build binary to bin/armis-cli
make install # Install binary to /usr/local/bin (or PREFIX)
make test # Run all tests with verbose output (uses gotestsum if installed)
make lint # Run golangci-lint
make clean # Remove build artifacts
make release # Build for all platforms (linux/darwin/windows, amd64/arm64)
make scan # Run security scan on this repository (requires built binary)
make tools # Install dev tools (gotestsum)Run a single test:
go test -v ./internal/api -run TestClientStartIngest
go test -v ./internal/output/... -run TestHumanFormattercmd/armis-cli/main.go- Entry point. Sets version info, initializes colors viacli.InitColors(), callscmd.Execute().internal/cmd/- Cobra command definitions:root.go- Root command with global flags.PersistentPreRunEinitializes color mode, syncs output styles, and starts background update check.getAuthProvider()delegates toauth.NewAuthProvider().scan.go- Parent scan command with shared flags. ItsPersistentPreRunEmanually chains torootCmd.PersistentPreRunE(Cobra does not auto-chain when a child also definesPersistentPreRunE).scan_repo.go- Repository scanning subcommandscan_image.go- Container image scanning subcommand (--tarballflag for pre-exported images)auth.go- Standaloneauthcommand for testing JWT authentication (prints raw token)context.go- Signal handling:NewSignalContext()creates context canceled on SIGINT/SIGTERM
internal/auth/- Authentication provider supporting two modes. JWT (priority): client credentials exchange at/api/v1/auth/token, auto-refresh 5min before expiry, tenant ID extracted fromcustomer_idJWT claim. Basic (fallback): static token + explicit tenant ID. ImplementsAuthHeaderProviderinterface used by the API client.internal/api/- API client for Armis Cloud. Two HTTP clients: one for general calls (60s timeout), one for uploads (streaming, no timeout, no retry). Functional options pattern (WithHTTPClient(),WithUploadHTTPClient(),WithAllowLocalURLs()). Upload usesio.Pipestreaming to avoid OOM on large files. Enforces HTTPS, validates presigned S3 URLs against SSRF.internal/model/- Data structures:Finding(23 fields),ScanResult,Summary,Fix,FindingValidation(with taint/reachability analysis), API response types (NormalizedFinding, pagination).internal/output/- Output formatters (human, json, sarif, junit) implementing theFormatterinterface.styles.godefines ~50 lipgloss styles using Tailwind CSS color palette.icons.godefines Unicode constants (severity dots, box-drawing chars).SyncColors()switches between full-color and plain styles based oncli.ColorsEnabled().internal/cli/- Centralized color state management.InitColors(mode)resolves--colorflag (auto/always/never) withNO_COLORenv,TERM=dumb, and TTY detection on stderr.PrintError()/PrintWarning()parse JSON{"detail":"..."}from API errors for clean display.internal/scan/repo/- Repository scanner: creates tar.gz (with.armisignoresupport via go-git gitignore matcher), uploads, polls, fetches paginated results. Builder pattern withWithPollInterval(),WithIncludeFiles(),WithSBOMVEXOptions().internal/scan/image/- Image scanner: validates image names viadistribution/reference, uses docker/podman to export, then uploads. Also supports direct tarball scanning.internal/scan/- Shared scan utilities:status.go(status formatting, severity mapping),finding_type.go(classifies findings as VULNERABILITY/SCA/SECRET/MISCONFIG/LICENSE),sbom_vex.go(downloads SBOM/VEX from presigned S3 URLs).internal/progress/- Braille-dot spinner with timer display, lipgloss styling, cursor hiding, CI detection (auto-disables animation). Context-aware with configurable timeout (default 30min). Upload progress viaNewReader()/NewWriter()wrappers.internal/update/- Background version checker against GitHub Releases API with file-based caching (~/.cache/armis-cli/, 24h TTL). Semver comparison. Skipped in CI, dev builds.internal/httpclient/- HTTP client with exponential backoff retry (cenkalti/backoff). Retries on 5xx errors.internal/util/- Path sanitization (SanitizePath,SafeJoinPathfor traversal prevention), secret masking (12 regex patterns), category formatting.
output.Formatter-Format()andFormatWithOptions()for all output formattersapi.AuthHeaderProvider-GetAuthorizationHeader(ctx)decouples auth from API client
- Scanner creates compressed archive (repo tar.gz with
.armisignorefiltering) or exports image to tarball via docker/podman - For user-supplied tarballs (
scan image --tarball), client validates extension + magic bytes before upload (internal/scan/validate.go) - API client runs the split-flow ingest (PPSC-894/895):
POST /api/v1/ingest/presigned-url— reserves ascan_id, returns a pre-signed S3 POST whose policy carries acontent-length-rangecap up toMAX_UPLOAD_SIZE_MB.- Multipart-POST the tarball directly to S3 using the returned
presigned_url+fields. The CLI never sets an Authorization header on the S3 request — the signedpolicyfield is the credential. POST /api/v1/ingest/scan— the API head_objects the upload, transitions PENDING_UPLOAD → INITIATED, and dispatches to Prefect or SQS.
- Client polls
/api/v1/ingest/status/with spinner status updates until scan completes - Client fetches paginated results from
/api/v1/ingest/normalized-results(cursor-based) NormalizedFindingconverted to internalmodel.Finding(type classification, secret masking, code location extraction)- Results formatted for output; SBOM/VEX downloaded from presigned S3 URLs if requested
ExitIfNeeded()checks findings against--fail-onseverity levels
- Max repository size: 2GB (
repo.MaxRepoSize) - Max image size: 5GB (
image.MaxImageSize) - Default scan timeout: 60 minutes
- Default upload timeout: 10 minutes
- Page limit range: 1-1000 (default 500)
Authentication:
ARMIS_CLIENT_ID- Client ID for JWT authentication (recommended)ARMIS_CLIENT_SECRET- Client secret for JWT authenticationARMIS_API_TOKEN- API token for Basic authentication (fallback)ARMIS_TENANT_ID- Tenant identifier (required only with Basic auth; JWT extracts it from token)
API Configuration:
ARMIS_API_URL- Override base URL for Armis API (advanced; defaults based on --dev flag)ARMIS_REGION- Override Armis cloud region (equivalent to--region; used for region-aware authentication)ARMIS_LOCAL_S3_ENDPOINT- Comma-separated list of host:port entries for mock S3 services in local development (e.g.,awsmock-dev:4566,localstack:4566). SECURITY: Only enabled whenARMIS_API_URLis localhost or RFC 1918 private IP. Allows HTTP access to configured hosts for SBOM/VEX downloads. Blocked for all remote/cloud endpoints.
Output Configuration:
ARMIS_FORMAT- Default output formatARMIS_PAGE_LIMIT- Results pagination sizeARMIS_THEME- Terminal background theme: auto, dark, light (default: auto)
Other:
ARMIS_NO_UPDATE_CHECK- Disable automatic version update checking
When both JWT and Basic credentials are configured, JWT takes precedence.
Terminal output uses lipgloss with a centralized two-phase initialization:
main.gocallscli.InitColors(auto)early for error displayroot.PersistentPreRunEre-initializes with the--colorflag value, applies--themeoverride, then callsoutput.SyncColors()to set the active style set (DefaultStylesorNoColorStyles)
All styles are defined in internal/output/styles.go using lipgloss.AdaptiveColor for automatic light/dark theme adaptation. Colors use Tailwind CSS palette with separate light/dark variants (e.g., gray-600 on light, gray-500 on dark). The --theme flag (auto/dark/light) overrides auto-detection via lipgloss.SetHasDarkBackground(). The lipgloss renderer targets stderr. Terminal width is detected from stderr with fallback=68, min=60, max=120.
Tests use table-driven patterns. Mock HTTP responses with internal/testutil/httptest.go. The test/ directory contains a mock server and sample repository for integration testing.
- Real S3 requires
Content-Lengthon POST.internal/api/client.go::buildMultipartEnvelopeprecomputes the total length so the HTTP client can set it; do not add a streaming-body variant without preserving this. Real S3 returns 411 otherwise. Thetestutil.AssertValidS3Uploadhelper asserts the contract on every fake-S3 handler. - Race detector runs only on Linux (
.github/workflows/ci.yml::matrix). The orchestrator readsPresignedUploadResponsefields after kicking off the multipart writer; today everything is read-only post-hand-off. If anyone caches the response across goroutines or mutatesFields, ensure the race build still passes — better yet, add a-racejob to the macOS/Windows matrix. - Per-leg timing is currently coarse.
StartIngestreports total elapsed; the three legs (/presigned-url, S3 multipart POST,/scan) are not measured separately. If you add upload-time telemetry, instrument each leg so a regression in any one is attributable.
Uses golangci-lint v2 config (.golangci.yml) with: errcheck, govet, ineffassign, staticcheck, unused, gosec, goconst, misspell.
If make lint reports issues from ../*/ paths (sibling git worktrees), the goconst cache has bled across worktrees sharing this module path. Run golangci-lint cache clean then re-run, or use the make lint-clean target which does both.
- Error wrapping: always use
fmt.Errorf("context: %w", err) - Commit messages: conventional commits (
feat,fix,docs,test,refactor,chore) - All output (spinners, styled text) writes to stderr; only scan results go to stdout