The CLI now displays scan results in a clean, box-drawn dashboard format:
┌─────────────────────────────────────────────────────────────┐
│ 📊 SCAN SUMMARY │
├─────────────────────────────────────────────────────────────┤
│ Total Findings: 15 │
│ Filtered (Non-Exploitable): 3 │
│ │
│ 🔴 CRITICAL: 2 │
│ 🟠 HIGH: 5 │
│ 🟡 MEDIUM: 6 │
│ 🔵 LOW: 2 │
└─────────────────────────────────────────────────────────────┘
The CLI shows detailed progress during scans:
- Creating a compressed archive... - While packaging your code
- Uploading archive to Armis Cloud... - During upload
- Analyzing code for vulnerabilities... - While scanning
- Fetching scan results... - When retrieving findings
Each stage includes an elapsed timer showing real-time progress.
Group findings by different criteria using the --group-by flag:
# Group by CWE (Common Weakness Enumeration)
armis-cli scan repo . --group-by cwe
# Group by severity level
armis-cli scan repo . --group-by severity
# Group by file
armis-cli scan repo . --group-by file
# No grouping (default)
armis-cli scan repo . --group-by noneExample grouped output:
┌─────────────────────────────────────────────────────────────┐
│ CWE: CWE-89 │
│ Count: 3 │
└─────────────────────────────────────────────────────────────┘
🔴 CRITICAL
SQL Injection vulnerability detected...
When scanning a git repository, the CLI automatically shows who introduced each vulnerability:
Location: internal/api/client.go:45
Git Blame: John Doe <j***@e***.com> (2024-11-15, abc1234)
Features:
- Automatically detects if directory is a git repository
- Shows author, partially masked email, date, and commit SHA
- Gracefully handles non-git directories
- Caches results to avoid redundant git calls
Armis CLI can enforce a minimum package release age on the packages your builds install, defending against supply-chain attacks (typosquatting, compromised maintainer accounts, dependency confusion) where a malicious version is published and pulled in before anyone notices. The default policy withholds any release younger than 72 hours.
Enforcement is wired into your package managers by armis-cli supply-chain init,
which installs thin shell wrappers. From then on, npm install, pip install,
etc. transparently run through the age policy.
The mechanism differs by package manager, because they resolve dependencies differently:
| Mode | Package managers | How it works |
|---|---|---|
| Proxy | npm, npx, pnpm, bun, yarn, pip, uv, uvx |
A local, in-process HTTP proxy intercepts every registry metadata request — direct and transitive — strips versions younger than the policy, and repoints dist-tags.latest to the newest older version. |
| Pre-install block | poetry, pipenv, pdm, mvn, gradle |
The lockfile is audited up front; the build is hard-blocked before it runs if any package is too new. |
The proxy is a stateless per-request filter — it has no model of your dependency graph. This creates a real, bounded limitation worth understanding:
If
expressrequiresdebug@^4.4.0, anddebug@4.4.0was published 12 hours ago, the proxy removes4.4.0(too new) and repointslatestto4.3.9. But4.3.9does not satisfy^4.4.0, so npm rejects it and the install fails. The block is correct — a brand-new version really was withheld — but the failure can look opaque.
When this happens, Armis names the culprit at block time. On the npm family
(npm/pnpm/bun/yarn), a one-hop constraint check reports exactly which
dependency became unsatisfiable and which package required it:
[armis supply-chain] the install did not complete. This tool withheld brand-new
releases on purpose — a common supply-chain attack vector...
→ scheduler has no version older than the 3-day policy that satisfies ^0.24.0
(required by react-dom) — this is the likely cause.
For pip/uv, the PyPI Simple API does not expose per-package dependency
ranges, so the one-hop attribution is not available there — Armis names the
blocked package and points you at uv tree / pipdeptree to find the requiring
package. See Out of scope below.
When a young package is blocking you, four knobs unblock the build. They are ordered here least-permissive first — reach for the lower-numbered, more reviewable option before the blunt instruments:
-
Allow one package, this invocation/environment — exempts a single package:
ARMIS_SUPPLY_CHAIN_SKIP=scheduler npm install
⚠️ This persists in whatever environment you set it in and exempts all future versions of that package, including potentially-malicious ones (skip-list rot). Prefer a reviewed exception (below) for anything permanent. -
Permanent, reviewed team exception — add the package to
exclusions:in.armis-supply-chain.yaml(committed, reviewed, team-wide):exclusions: - scheduler - "@myorg/*"
-
Relax the policy window for all packages — edit
min-age:in.armis-supply-chain.yaml:min-age: 24h # weakens the check for EVERY package
Note: the
wrappath reads policy only from.armis-supply-chain.yaml. There is no--min-ageflag on the wrapped install — that flag exists only onarmis-cli supply-chain check. -
Emergency kill switch — disable enforcement entirely for one command:
ARMIS_SUPPLY_CHAIN=off npm install
This turns the control off completely. Use it only as a last resort.
Rather than failing a build that a young transitive dependency would break, you
can let young transitive dependencies through with a warning while still
hard-blocking young direct dependencies. This is off by default (the
secure posture is block).
# .armis-supply-chain.yaml
transitive-policy: warn # default: blockOr per-invocation for the wrapped path (which can't take flags):
ARMIS_SUPPLY_CHAIN_TRANSITIVE=warn npm installUnder warn:
- A young transitive dependency (one not declared in your root manifest) is allowed through. The build succeeds; each allowed-through package is printed as a warning and marked in the compliance report.
- A young direct dependency (declared in your
package.jsondependencies/devDependencies/peerDependencies/optionalDependencies) is still blocked — that is where you have control and where typosquat/ dependency-confusion risk concentrates. - If the direct-dependency set cannot be determined (e.g. no readable
package.json, or a non-npm ecosystem), Armis fails safe: every package is treated as direct and young versions are blocked, exactly as underblock.
Residual risk (read before enabling): warn permits a freshly-published
transitive package into your build. A malicious indirect dependency could
therefore land before its release has aged. The control still blocks direct
deps, fails safe on an undeterminable direct set, and records every
warned-through package in the --report audit so security teams can review what
entered the build. Direct/transitive classification is npm-family only; pip/
uv and the pre-install ecosystems cannot determine a direct set and therefore
never warn-through (they stay at block).
To prove no young package entered a build, emit a machine-readable JSON report:
# Wrapped install (wrap can't take flags — use the env var; "-" writes to stderr):
ARMIS_SUPPLY_CHAIN_REPORT=supply-chain-report.json npm install
# The check subcommand parses flags, so a flag is fine there:
armis-cli supply-chain check --report supply-chain-report.jsonThe report carries the effective policy, the enforcement mode, and the
checked / blocked / resolved / warned_through / conflicts sets plus an
install_status. CI can gate on it with jq:
jq -e '.install_status == "ok" and (.warned_through | length) == 0' supply-chain-report.jsonBy default, age checks query the public registries (npm, PyPI). If your org
routes installs through a private artifactory (Nexus, JFrog Artifactory), point
Armis at it so age checks — and the wrap/check age enforcement — run against
the registry you actually use, not the public one:
# .armis-supply-chain.yaml
registries:
npm: https://nexus.corp/repository/npm-group/
pypi: https://nexus.corp/repository/pypi-group/simple/ # must expose the PEP 503 Simple API
registry-enforcement: warn # optional: warn when an install resolves off the approved registry
registry-ca-bundle: /etc/armis/nexus-ca.pem # optional: trust a private/corporate CAregistries.<npm|pypi>— the approved registry URL for that ecosystem. Must behttps://with no embedded credentials and no loopback/private/ link-local host (the committed config is a trust boundary — validated at load time, not a best-effort parse). The PyPI URL must end in/simpleor/simple/(the PEP 503 Simple API path); Maven/Gradle are audit-path only and not routable to a custom registry in this version.registry-enforcement: warn— when set, a package that resolves from a host other than the approved registry is flagged with a warning (currently npm-family only). Onlywarnis supported;blockis rejected at config load so it can never be silently downgraded.registry-ca-bundle— path to a PEM file to trust a private CA for the registry connection, e.g. a self-signed or corporate-CA-issued artifactory certificate. Override per-run withARMIS_REGISTRY_CA_BUNDLE=<path>. A bad or unreadable bundle is a hard error, never a silent fallback to unverified TLS.
Credentials are read from your existing package-manager config, never from
the committed policy file: npm-family reads the .npmrc _authToken (host- or
host+path-scoped); pip/uv read Basic-auth userinfo embedded in
PIP_INDEX_URL/UV_INDEX_URL. If a credential is configured but unusable
(e.g. an .npmrc referencing an unset ${VAR}), that's a hard error rather
than a silent unauthenticated request.
Check your setup before relying on it:
armis-cli supply-chain wrap --dry-run npmThis resolves and prints the approved registry, whether a credential was found, the enforcement posture, and the CA bundle in use — without running the package manager.
- One-hop, npm-family only. The constraint conflict check is a single hop (a dependent's declared range vs. the dependency's surviving versions) on metadata the proxy already fetched — it is not a full resolver and does not backtrack multi-hop chains. It runs for the npm family only; PyPI's Simple API lacks per-package dependency ranges.
- pip/uv get culprit-naming but no attribution. Run
uv treeorpipdeptreeto find which package requires a blocked dependency. - Maven
pom.xmlcovers direct dependencies only. Maven resolves transitives at build time, so they are not audited. For full coverage, generate a lockfile (e.g.mvn dependency:tree, or a lockfile plugin) — Armis ships no Maven transitive parser.
Generate industry-standard Software Bill of Materials (SBOM) and Vulnerability Exploitability eXchange (VEX) documents alongside your security scans.
-
SBOM (Software Bill of Materials): A comprehensive inventory of all software components, dependencies, and libraries in your project. Essential for supply chain security, license compliance, and vulnerability management.
-
VEX (Vulnerability Exploitability eXchange): A document that communicates the exploitability status of vulnerabilities in your specific context. Helps reduce alert fatigue by indicating which vulnerabilities actually affect your deployment.
Both documents are generated in CycloneDX format, an OWASP standard widely supported by security tools.
# ARMIS_CLIENT_ID and ARMIS_CLIENT_SECRET set via env (JWT auto-extracts the tenant ID)
# Generate SBOM for a repository scan
armis-cli scan repo . --sbom
# Generate both SBOM and VEX
armis-cli scan repo . --sbom --vex
# Specify custom output paths
armis-cli scan repo . \
--sbom --sbom-output ./reports/sbom.json \
--vex --vex-output ./reports/vex.json
# Generate SBOM for container image scan
armis-cli scan image nginx:latest --sbom --vex| Flag | Description | Default |
|---|---|---|
--sbom |
Generate Software Bill of Materials | false |
--vex |
Generate VEX document | false |
--sbom-output |
Custom output path for SBOM | .armis/<artifact>-sbom.json |
--vex-output |
Custom output path for VEX | .armis/<artifact>-vex.json |
By default, SBOM and VEX files are saved to the .armis/ directory:
.armis/
├── my-project-sbom.json
└── my-project-vex.json
Generate SBOM and VEX as part of your CI pipeline:
# GitHub Actions example
- name: Security Scan with SBOM
env:
ARMIS_CLIENT_ID: ${{ secrets.ARMIS_CLIENT_ID }}
ARMIS_CLIENT_SECRET: ${{ secrets.ARMIS_CLIENT_SECRET }}
run: |
armis-cli scan repo . \
--sbom --vex \
--sbom-output ./artifacts/sbom.json \
--vex-output ./artifacts/vex.json
- name: Upload SBOM Artifact
uses: actions/upload-artifact@v4
with:
name: sbom-vex
path: ./artifacts/- Compliance: Many regulations (Executive Order 14028, EU Cyber Resilience Act) require SBOM generation
- Supply Chain Security: Track all dependencies and detect supply chain attacks
- License Management: Identify all licenses in your software stack
- Vulnerability Prioritization: Use VEX to focus on actually exploitable vulnerabilities
- Incident Response: Quickly identify if vulnerable components are in your software
- SBOM/VEX generation is performed server-side by Armis Cloud
- Download failures are logged as warnings but do not fail the scan
- Files are protected against path traversal attacks
- Maximum download size is 100MB per file
Exclude files and directories from scans using .armisignore files.
Create a .armisignore file in your repository root:
# Exclude build outputs
dist/
build/
*.o
# Exclude dependencies
node_modules/
vendor/
# Exclude logs
*.log
# Include specific files (negation)
!important.log
- Gitignore-compatible syntax - Uses the same pattern matching as
.gitignore - Nested support - Place
.armisignorefiles in subdirectories - Glob patterns - Support for wildcards (
*.log,test_*.py) - Directory exclusions - Exclude entire directories (
node_modules/) - Negation patterns - Include files that would otherwise be excluded (
!important.log) - Comments - Lines starting with
#are ignored
Files matching .armisignore patterns are excluded before creating the upload archive, reducing:
- Upload time
- Scan time
- Bandwidth usage
- False positives from generated code
All CLI flags now include detailed descriptions and examples:
armis-cli scan repo --helpFlags include:
- Clear descriptions of what each flag does
- Default values
- Valid options and ranges
- Environment variable alternatives
All new features include comprehensive unit tests:
# Run all tests
go test ./...
# Run specific test suites
go test ./internal/scan/repo/...
go test ./internal/output/...- Uses simple ASCII box characters (┌─┐│└┘) for maximum compatibility
- Works across all operating systems and CI/CD environments
- Gracefully handles terminals without emoji support
- Respects
NO_COLORandTERM=dumbenvironment variables
armis-cli scan repo . \
--group-by cwe \
--format human# Create .armisignore
echo "test/" > .armisignore
echo "*.generated.go" >> .armisignore
# Run scan
armis-cli scan repo .# Fail on HIGH or CRITICAL findings
armis-cli scan repo . \
--fail-on HIGH,CRITICAL \
--no-progress \
--format sarif > results.sarif# Include test files and non-exploitable findings
armis-cli scan repo . \
--include-tests \
--include-non-exploitable \
--group-by fileAuthentication:
JWT authentication is recommended. Obtain JWT credentials from the VIPR external API screen in the Armis platform.
| Variable | Description |
|---|---|
ARMIS_CLIENT_ID |
Client ID for JWT authentication (recommended) |
ARMIS_CLIENT_SECRET |
Client secret for JWT authentication (recommended) |
ARMIS_API_TOKEN |
API token for Basic authentication (legacy) |
ARMIS_TENANT_ID |
Tenant identifier (legacy, not needed with JWT) |
ARMIS_API_URL |
Override base URL for Armis API and authentication (advanced) |
ARMIS_REGION |
Authentication region override (advanced; corresponds to --region flag) |
General:
| Variable | Description |
|---|---|
ARMIS_FORMAT |
Default output format |
ARMIS_PAGE_LIMIT |
Results pagination size |
Supply Chain Enforcement:
| Variable | Description |
|---|---|
ARMIS_SUPPLY_CHAIN |
Set to off to disable enforcement for one command (kill switch) |
ARMIS_SUPPLY_CHAIN_SKIP |
Comma/space-separated package names to exempt from the age check (persists in the env; exempts future versions) |
ARMIS_SUPPLY_CHAIN_TRANSITIVE |
Set to warn to let young transitive deps through with a warning (direct deps still blocked); default block |
ARMIS_SUPPLY_CHAIN_REPORT |
Path to write the JSON compliance report for a wrapped install (- for stderr) |
ARMIS_REGISTRY_CA_BUNDLE |
Path to a PEM CA bundle to trust for the configured custom registry connection; overrides registry-ca-bundle in .armis-supply-chain.yaml |
- Test files are excluded by default (use
--include-teststo include) - Non-exploitable findings are filtered by default (use
--include-non-exploitableto include) - Progress indicators are enabled by default (use
--no-progressto disable) - Grouping is disabled by default (use
--group-byto enable)
- Use .armisignore - Exclude unnecessary files to speed up uploads
- Adjust timeout - Increase
--timeoutfor large repositories - Optimize page limit - Adjust
--page-limitbased on finding count - Disable progress - Use
--no-progressin CI/CD for cleaner logs
- See
.armisignore.examplefor a comprehensive ignore file template - Check
docs/ci-examples/for CI/CD integration examples - Run
armis-cli --helpfor full command reference