Skip to content
Talk to our solutions team

Configuration

The config file controls what gets scanned, by which tools, and how results are labeled. Generate a starter config with argus init.

# CPET identity — scoped into every run
customer: kis
product: platform
environment: production
tenant: default
# Which profile to apply (references rules/profiles/<name>.yaml)
profile: standard
# Control which directories under --src are scanned
projects:
include:
- "lib/*"
- "ai/*"
exclude:
- "tools/*"
- "ui/*"
category_map:
"lib/*": "libraries"
"ai/*": "ai-services"
# customer_visibility scopes what `argus reports customer` can show.
# Each entry is self-contained (no inheritance from `all`). --customer
# picks the entry; omitting it uses `all`. Aliases map raw basenames
# to customer-facing display names at render time — internal/dev
# reports ignore aliases entirely and always show the raw name.
customer_visibility:
all:
include: ["lib/*", "ai/shared/*"]
exclude: ["lib/lib-experimental"]
aliases:
lib-datastore: "lib-data"
lib-chassis: "Service Framework"
acme:
include: ["lib/*", "baas/acme-*"]
exclude: []
aliases:
lib-datastore: "Acme Data Core"
# Scanner configuration
tools:
golangci-lint:
enabled: true
config: .golangci.yml # relative to each project dir
gosec:
enabled: true
govulncheck:
enabled: true
semgrep:
enabled: true
rulesets:
- "p/golang"
- "p/security-audit"
# Output settings
output:
dir: .argus/output
format: duckdb

Every run is scoped by customer, product, environment, and tenant. These are stored in the runs table and used for multi-tenant isolation. Environment and tenant default to production and default.

profile, profile_auto, and profile_overrides

Section titled “profile, profile_auto, and profile_overrides”

The profile: key sets the default profile used for every discovered repo.

Three forms are supported:

1. Single profile name (the simplest case for a single-language repo):

profile: regulated

2. profile: auto (recommended for polyglot repos and monorepos) — Code Quality inspects each project’s marker files and substitutes a concrete language-tier profile per repo. Two single-key flips control the baseline tier; per-language overrides layer on top.

auto_tier: baseline (default regulated):

Marker presentauto_tier: regulated (default)auto_tier: standard
go.mod onlyregulatedgo-standard
package.json onlyjs-regulatedjs-standard
pyproject.toml / requirements.txt / Pipfile onlypython-regulatedpython-standard
Multiple markers (mixed-language repo)polyglot-regulatedpolyglot-standard
No language markers detectedregulated (fallback)go-standard (fallback)

The default is the regulated tier because Code Quality’s primary audience is finance/healthcare customers. For non-regulated codepaths, flip with auto_tier: standard:

profile: auto
auto_tier: standard # one key, every language drops to the standard tier

Mixed compliance scopes — e.g. JS/TS internal tooling on standard but Python touches PHI so it stays regulated — combine the tier flip with per-language overrides in profile_auto::

profile: auto
auto_tier: standard # baseline: standard everywhere
profile_auto:
python: python-regulated # …except Python, which stays regulated

profile_auto: accepts the same fields as the auto map (go, js, python, polyglot, default); any field set wins over the baseline.

When profile: auto is in effect, the run log records the per-project resolution and reason (“auto: ui/web-ui → js-regulated (package.json present)”) and the Overrides line in the run summary shows the distribution: Overrides: python-regulated × 1, js-regulated × 1.

3. profile_overrides: (advanced, mixed-criticality codebases) — assigns different profiles to specific repos or glob patterns. Entries are evaluated top-to-bottom; first match wins. Overrides take precedence over profile: auto detection — a matching glob pins the repo regardless of detected language. If no override matches, auto-detection runs (when enabled), then the default profile: applies.

profile: auto # auto-detect for everything else
profile_overrides:
"lib/lib-payments": regulated # pinned regardless of language
"lib/lib-vault": regulated
"baas/core-*": strict # all core-* services
"experimental/*": minimal # smoke-test only

Matching rules (same as projects.exclude):

  1. Exact full-path glob match
  2. Basename glob match
  3. Prefix match (pattern/ prefix of the repo path)

When to use which:

  • Single profile — single-language single-tier project, no edge cases.
  • profile: auto — polyglot monorepo where most repos should use their language’s regulated tier. The default for argus init-generated configs.
  • profile: auto + profile_overrides — polyglot monorepo with a few special repos that need a specific profile (e.g. an experimental directory that should run on minimal, a vault library that must use regulated even though its markers suggest otherwise).
  • Single profile + profile_overrides — non-polyglot project where most repos use one tier and a few exceptions need another.

Each repo’s actual profile is recorded in run_repos.profile_name, and every profile used in a run is written to resolved_profile so reports can show the distribution. runs.profile_name reflects the default profile (or, when profile: auto, the alphabetically-first concrete profile picked).

Controls which git repositories under --src are scanned. Code Quality discovers projects by looking for directories containing a .git subdirectory (up to 3 levels deep).

  • include — Glob patterns (relative to --src). Only matching directories are scanned. If a match is not itself a git repo, Code Quality searches within it for repos. If empty, all git repos under --src are discovered automatically.
  • exclude — Glob patterns to skip. Supports prefix matching: exclude: experiments also excludes experiments/benchmarks-go, experiments/experiments-go, etc.
  • category_map — Assigns category_block labels to projects by glob pattern. Used for grouping in reports.
  • customer_visibility — Maps customer id → {include, exclude, aliases}. Controls what argus reports customer shows and how repos are labeled. Each entry is self-contained (no inheritance). The conventional all entry is used when --customer is omitted. Matching rules for include/exclude are the same as projects.exclude (exact → basename → prefix), applied to source_repo. aliases is a map of raw basename → display name, applied only at customer-report render time — edit the map and re-run argus reports customer, no rescan required. Internal and dev reports always show raw names.

Per-tool scanner configuration:

KeyTypeDescription
enabledboolWhether to run this scanner
configstringPath to tool-native config file (relative to project dir)
rulesets[]stringTool-specific rulesets (e.g., semgrep p/golang)
extramapAdditional key-value settings passed to the scanner
concurrencyintCap on concurrent instances of this tool across the whole scan. 0 = use the shipped default; -1 = unlimited; 1 = serialize. Defaults: nilaway = 1 (can exceed 10 GB per instance), semgrep = 2, everything else = unlimited. Independent of --workers — e.g. --workers 8 with nilaway.concurrency: 1 runs 8 repos in parallel but queues nilaway through a single slot.

Concurrent scan workers. Each worker processes one repo at a time; tools within a repo run serially. Defaults to min(NumCPU/2, 12) when omitted or set to 0. The --workers N CLI flag overrides this.

workers: 10 # 10 repos scanned in parallel

Profiles are named, versioned bundles of rules across tools. They control which rules fire and at what severity.

Go-focused:

ProfileInheritsDescription
minimalSmoke-test: critical staticcheck + govulncheck
go-standardminimalBalanced: errcheck, gosec basics, semgrep golang — non-regulated SaaS default
strictgo-standardEnterprise baseline: complexity checks, broader gosec, OWASP
security-focusedgo-standardSecurity-heavy; aggressive, but not compliance-grade
regulatedsecurity-focusedFinance, healthcare, PCI/HIPAA/SOX. Zero-tolerance: every gosec rule, full semgrep coverage (CWE Top 25 + secrets + supply-chain), tightest complexity thresholds (gocyclo 6 / gocognit 8), severity escalation (crypto/injection → critical, unchecked errors → high), full govulncheck
nightlyregulatedExhaustive scheduled-scan profile. Adds Snyk (if licensed) and broader semgrep rulesets (trailofbits, gitleaks, command-injection, sql-injection, xss, jwt). Intended for weekly runs, not PR gating
kis-internalregulatedkis.ai’s own code — same regulatory bar as customer code, plus room for kis-specific semgrep rules

JavaScript / TypeScript / React / Vue:

ProfileInheritsDescription
js-standardBalanced JS/TS profile: eslint security plugins (project must bring its own config), npm-audit + pnpm-audit + retire-js + osv-scanner for SCA, semgrep with p/javascript, p/typescript, p/react, p/nodejs, p/owasp-top-ten, p/cwe-top-25
js-regulatedjs-standardCompliance-grade JS/TS. Full semgrep OWASP / CWE / secrets / supply-chain / xss / jwt / sql-injection packs. SCA findings escalated to critical. Use for finance/healthcare web frontends and Node services

Python:

ProfileInheritsDescription
python-standardBalanced Python profile: bandit + ruff (security rules), osv-scanner SCA, semgrep with p/python, p/django, p/flask, p/fastapi, p/owasp-top-ten, p/cwe-top-25
python-regulatedpython-standardCompliance-grade Python. Severity escalation matches Go’s regulated: code injection (B102/B307), command injection (B602), SQL injection (B608), insecure deserialization (B301/B506), hardcoded credentials (B105/B106/B107) all → critical. Weak crypto, TLS misconfig, XXE, XSS → high

Polyglot (mixed-language repos):

ProfileInheritsDescription
polyglot-standardgo-standardNon-regulated polyglot baseline. Layers js-standard + python-standard tool sets on top of go-standard
polyglot-regulatedregulatedCompliance-grade for monorepos and full-stack services that mix Go + JS/TS + Python. Inherits Go’s regulated and layers on the JS and Python tool sets. Each adapter’s Applicable() ensures per-repo cost is bounded — JS tools skip pure-Go repos and vice versa. For single-language repos, prefer the language-specific profile

Profile selection by vertical and language

Section titled “Profile selection by vertical and language”
Your codeRecommended profile
Pure Go, generic SaaSstandard
Pure Go, enterprise internal toolingstrict
Pure Go, security-sensitive but not compliance-boundsecurity-focused
Pure Go handling PII, PHI, payment data, financial recordsregulated
Pure JS/TS/React/Vue, non-regulatedjs-standard
Pure JS/TS/React/Vue, regulatedjs-regulated
Pure Python, non-regulatedpython-standard
Pure Python, regulatedpython-regulated
Mixed Go + JS + Python (full-stack monorepos), regulatedpolyglot-regulated
Scheduled weekly deep scan (Go)nightly

Profiles can inherit from a parent via inherits:. Child profiles additively merge rules and can override severities. The resolved profile at scan time is written into the DuckDB artifact so historical runs are self-describing.

Tool selection is profile-driven. Every tool the active profile lists runs by default — you don’t need to enable each tool individually in tools:. The tools: block is for two things:

  1. Tool-specific configuration — semgrep rulesets, nilaway concurrency, snyk auth notes, etc.
  2. Explicit opt-outtools.<x>.enabled: false skips a tool even when the profile lists it. Takes precedence over the profile.
profile: js-regulated
tools:
semgrep:
rulesets: # tool config
- "p/javascript"
- "p/owasp-top-ten"
snyk:
enabled: false # explicit opt-out

The tools.<x>.enabled: true legacy form continues to work but is now redundant — Code Quality emits a one-time deprecation log per scan listing entries that can be cleaned up. To genuinely opt-out, write enabled: false.

Inspect what will run. Profile-driven semantics make it less obvious which tools fire on which projects. Use:

Terminal window
argus tools resolve --config config.yaml --src . [--project apps/webapp]

Output groups by project:

── apps/webapp ──
profile: js-regulated (auto: package.json present)
will run (4): [bun-audit eslint osv-scanner retire-js]
opted out (1): [snyk]
missing on PATH (0): []

This answers “what tools would run if I scanned right now?” without invoking any scanner.

Strict mode for audit-grade scans. SOC2 / SOX environments where every tool’s enablement must live in version control can opt into strict mode:

tools_strict: true

When set, Code Quality refuses to start if any profile-listed tool has neither enabled: true nor enabled: false in tools:. Catches config drift loud rather than silently picking up new scanners after an Code Quality upgrade. Default off.

Some findings are false positives — a password = "password" form-field constant flagged by gosec G101, an SCA finding for a transitive dep already mitigated upstream, a # nosec-grade pattern in test fixtures. Code Quality has two muting layers, one inline-per-source-line and one config-driven.

Layer 1: inline scanner suppressions (works today via the underlying tools, no Code Quality support needed):

ToolSyntax
gosec// #nosec G101 end-of-line
golangci-lint / staticcheck//nolint:linter1,linter2
bandit# nosec B105 (whole line)
ruff# noqa: S105
eslint// eslint-disable-next-line no-eval or /* eslint-disable */ block
semgrep// nosemgrep: rule-id

Surgical, version-controlled with the code, doesn’t need Code Quality configuration. Limitation: only works for SAST findings on source code — you can’t put a comment in package.json or pnpm-lock.yaml to suppress a CVE.

Layer 2: Argus-level suppressions (.argus-suppressions.yaml):

For SCA findings, recurring patterns, and time-boxed acceptances. The file lives in the repo being scanned alongside config.yaml so suppressions are version-controlled with the code and travel with it. Default location: <config-dir>/.argus-suppressions.yaml. Override via suppressions_file: in config.yaml or --suppressions on the CLI.

.argus-suppressions.yaml
suppressions:
# SCA — accepted risk on a transitive dep, mitigated upstream
- rule: bun-audit.GHSA-w9j2-pvgh-6h63
package: axios
reason: "Mitigated via request-validation middleware in apps/webapp/src/lib/http"
until: 2026-12-31
# SAST pattern — every test file's hardcoded fixture passwords
- rule: bandit.B105
file: "**/test_*.py"
reason: "Test fixtures use deliberately-fake credentials"
- rule: ruff.S105
file: "**/test_*.py"
reason: "Test fixtures (ruff side)"
# CWE-based — covers any tool that flags this weakness class for a
# specific area
- cwe: CWE-327
file: "lib/lib-cache/**"
reason: "MD5 used as cache-key hash, not for security"
# Whole tool muted in a directory (rare; usually wrong)
- tool: retire-js
file: "vendor/**"
reason: "We bundle these libs intentionally; tracked in DEPS.md"

Match keys (any combination — AND within an entry):

KeyMeaningExample
rule<tool>.<rule_id> for a specific rule, or <tool>.all for everything from a toolbandit.B105, bun-audit.all
filePath glob, —src-relative. Supports *, ?, [...], and ** (any-depth segments).apps/webapp/**, **/test_*.py
packageSCA package namelodash, axios
cweWeakness class (CWE-N, cwe-N, or N)CWE-327
reasonRequired — audit-trail string"Mitigated upstream"
untilTime-boxed acceptance — ISO date or RFC33392026-12-31

A finding matches a suppression when every non-empty key matches. Entries are evaluated top-to-bottom; first match wins. List specific entries before broader patterns.

When a suppression matches, the finding is recorded in DuckDB with suppressed = true, the reason in suppression_reason, and any expiry in suppression_until. Reports default to excluding suppressed findings from headline counts (severity bars, tool counts, repo summaries) — but they’re queryable for audit:

-- Every currently-suppressed finding with its reason
SELECT repo_alias, tool, rule_id, suppression_reason, suppression_until
FROM findings
WHERE run_id = ? AND suppressed
ORDER BY repo_alias, rule_id;

The run-summary line at the end of argus run reflects the split:

Findings: 866 (12 suppressed, 854 open)

Time-boxed acceptance. Set until: 2026-12-31 on entries that accept a known FP for a specific window. Once the date passes, Code Quality stops applying that suppression on future runs and the finding reverts to open — forcing a re-review rather than letting the acceptance become permanent debt. Use ISO date or RFC3339 timestamp.

When to use which. Inline # nosec is right for one-offs at a specific call site. The suppressions file is right for:

  • SCA findings (the only path that works for lockfile-level CVEs)
  • Repeated patterns across files (**/test_*.py)
  • Time-boxed acceptance you want to revisit
  • Cross-tool suppression by CWE rather than per-tool rule_id
  • Audit-trail requirements where reasons must live in version control

The two coexist — Code Quality respects scanner-native inline suppressions (the scanner doesn’t emit the finding) and applies file-level suppressions on top of whatever the scanners did emit.

Create a YAML file in rules/profiles/:

name: my-custom
version: "1.0"
description: Custom profile for project X
inherits: standard
rules:
gosec:
- "G101"
- "G304"
golangci-lint:
- gocyclo: {threshold: 12}
severity_overrides:
gosec.G104: high # specific rule
bun-audit.all: critical # tool-wide wildcard

Override key syntax: <tool>.<rule_id> for a specific rule, or <tool>.all for every finding from that tool. When both forms are present and would apply to the same finding, the specific rule wins. Wildcards are useful for SCA adapters (npm-audit, pnpm-audit, bun-audit, osv-scanner) whose rule_ids are CVE / GHSA identifiers that can’t be enumerated ahead of time — <tool>.all: critical ensures every advisory escalates to compliance-grade severity.