Configuration
The config file controls what gets scanned, by which tools, and how results are labeled. Generate a starter config with argus init.
Structure
Section titled “Structure”# CPET identity — scoped into every runcustomer: kisproduct: platformenvironment: productiontenant: default
# Which profile to apply (references rules/profiles/<name>.yaml)profile: standard
# Control which directories under --src are scannedprojects: 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 configurationtools: 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 settingsoutput: dir: .argus/output format: duckdbFields
Section titled “Fields”CPET Identity
Section titled “CPET Identity”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: regulated2. 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 present | auto_tier: regulated (default) | auto_tier: standard |
|---|---|---|
go.mod only | regulated | go-standard |
package.json only | js-regulated | js-standard |
pyproject.toml / requirements.txt / Pipfile only | python-regulated | python-standard |
| Multiple markers (mixed-language repo) | polyglot-regulated | polyglot-standard |
| No language markers detected | regulated (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: autoauto_tier: standard # one key, every language drops to the standard tierMixed 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: autoauto_tier: standard # baseline: standard everywhereprofile_auto: python: python-regulated # …except Python, which stays regulatedprofile_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 onlyMatching rules (same as projects.exclude):
- Exact full-path glob match
- Basename glob match
- 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 forargus 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 onminimal, a vault library that must useregulatedeven 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).
projects
Section titled “projects”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--srcare discovered automatically.exclude— Glob patterns to skip. Supports prefix matching:exclude: experimentsalso excludesexperiments/benchmarks-go,experiments/experiments-go, etc.category_map— Assignscategory_blocklabels to projects by glob pattern. Used for grouping in reports.customer_visibility— Maps customer id →{include, exclude, aliases}. Controls whatargus reports customershows and how repos are labeled. Each entry is self-contained (no inheritance). The conventionalallentry is used when--customeris omitted. Matching rules forinclude/excludeare the same asprojects.exclude(exact → basename → prefix), applied tosource_repo.aliasesis a map of raw basename → display name, applied only at customer-report render time — edit the map and re-runargus reports customer, no rescan required. Internal and dev reports always show raw names.
Per-tool scanner configuration:
| Key | Type | Description |
|---|---|---|
enabled | bool | Whether to run this scanner |
config | string | Path to tool-native config file (relative to project dir) |
rulesets | []string | Tool-specific rulesets (e.g., semgrep p/golang) |
extra | map | Additional key-value settings passed to the scanner |
concurrency | int | Cap 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. |
workers
Section titled “workers”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 parallelProfiles
Section titled “Profiles”Profiles are named, versioned bundles of rules across tools. They control which rules fire and at what severity.
Shipped Profiles
Section titled “Shipped Profiles”Go-focused:
| Profile | Inherits | Description |
|---|---|---|
minimal | — | Smoke-test: critical staticcheck + govulncheck |
go-standard | minimal | Balanced: errcheck, gosec basics, semgrep golang — non-regulated SaaS default |
strict | go-standard | Enterprise baseline: complexity checks, broader gosec, OWASP |
security-focused | go-standard | Security-heavy; aggressive, but not compliance-grade |
regulated | security-focused | Finance, 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 |
nightly | regulated | Exhaustive 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-internal | regulated | kis.ai’s own code — same regulatory bar as customer code, plus room for kis-specific semgrep rules |
JavaScript / TypeScript / React / Vue:
| Profile | Inherits | Description |
|---|---|---|
js-standard | — | Balanced 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-regulated | js-standard | Compliance-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:
| Profile | Inherits | Description |
|---|---|---|
python-standard | — | Balanced 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-regulated | python-standard | Compliance-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):
| Profile | Inherits | Description |
|---|---|---|
polyglot-standard | go-standard | Non-regulated polyglot baseline. Layers js-standard + python-standard tool sets on top of go-standard |
polyglot-regulated | regulated | Compliance-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 code | Recommended profile |
|---|---|
| Pure Go, generic SaaS | standard |
| Pure Go, enterprise internal tooling | strict |
| Pure Go, security-sensitive but not compliance-bound | security-focused |
| Pure Go handling PII, PHI, payment data, financial records | regulated |
| Pure JS/TS/React/Vue, non-regulated | js-standard |
| Pure JS/TS/React/Vue, regulated | js-regulated |
| Pure Python, non-regulated | python-standard |
| Pure Python, regulated | python-regulated |
| Mixed Go + JS + Python (full-stack monorepos), regulated | polyglot-regulated |
| Scheduled weekly deep scan (Go) | nightly |
Profile Inheritance
Section titled “Profile Inheritance”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 (profile-driven)
Section titled “Tool selection (profile-driven)”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:
- Tool-specific configuration — semgrep rulesets, nilaway concurrency, snyk auth notes, etc.
- Explicit opt-out —
tools.<x>.enabled: falseskips 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-outThe 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:
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: trueWhen 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.
Suppressing false positives
Section titled “Suppressing false positives”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):
| Tool | Syntax |
|---|---|
| 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.
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):
| Key | Meaning | Example |
|---|---|---|
rule | <tool>.<rule_id> for a specific rule, or <tool>.all for everything from a tool | bandit.B105, bun-audit.all |
file | Path glob, —src-relative. Supports *, ?, [...], and ** (any-depth segments). | apps/webapp/**, **/test_*.py |
package | SCA package name | lodash, axios |
cwe | Weakness class (CWE-N, cwe-N, or N) | CWE-327 |
reason | Required — audit-trail string | "Mitigated upstream" |
until | Time-boxed acceptance — ISO date or RFC3339 | 2026-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 reasonSELECT repo_alias, tool, rule_id, suppression_reason, suppression_untilFROM findingsWHERE run_id = ? AND suppressedORDER 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.
Custom Profiles
Section titled “Custom Profiles”Create a YAML file in rules/profiles/:
name: my-customversion: "1.0"description: Custom profile for project Xinherits: standard
rules: gosec: - "G101" - "G304" golangci-lint: - gocyclo: {threshold: 12}
severity_overrides: gosec.G104: high # specific rule bun-audit.all: critical # tool-wide wildcardOverride 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.