Skip to main content

Project configuration

With a .codeguard.yml you adapt CodeGuard to your project: paths, platforms, rules, metrics, custom rules and waivers. This page describes every key.

Where the file lives and how it is found​

InvocationWhat is loadedIn the report (project_source.mode)
no option, file present<project root>/.codeguard.yml or .codeguard.yamlauto
no option, no filedefaultsfield missing
--config Xonly Xexplicit
--no-project-configdefaults, warning codeguard-project-config-ignored if a file existsdisabled
--config and --no-project-confignothingexit 2
  • The project root is --project-root or the current directory. CodeGuard does not search parent directories or the Git root.
  • Only these exact file names count, even on case-insensitive volumes. .CodeGuard.yml is not loaded.
  • If both .codeguard.yml and .codeguard.yaml are in the root directory, that is exit 2 (ambiguous).
  • A broken file is exit 2. CodeGuard never silently falls back to defaults.
  • .codeguard.yml and .codeguard.yaml are protected files by default. A diff that changes them ends with exit 6.

The smallest valid file:

version: 1

codeguard schema --kind configuration returns the full schema. You can validate the file at any time with:

codeguard config validate

Levels and precedence​

The effective configuration is always the result of three sources, in this order:

  1. Built-in defaults
  2. Organization policy (/Library/Application Support/CodeGuard/policy.yml): metric baseline, mandatory rules, severity floors, locked values, trusted tool directories. See Organization policy.
  3. Project configuration (.codeguard.yml)

The policy then enforces its locked values (locked_values). A project value that contradicts a locked value is exit 2.

codeguard 0.3.5 doesn't load user or CI configuration files. --ci doesn't load an additional file either.

Lists are replaced, not merged

If the project configuration sets a list (e.g. paths.exclude or protected_files), it replaces the default list. If you want to keep the default entries, include them in your list.

What the project level may do​

The project level may tighten settings, but not loosen any security guarantee. These limits were verified with codeguard 0.3.5:

Project sets …Result
project.*, paths.*, protected_files, rules.*, custom_rules, waivers, diff.*allowed
checks.*.scopes, checks.platform.*, checks.tests.simulator, checks.tests.ipadallowed
checks.<format/swiftlint/compile/tests/security>.enabled: false or .required: falseexit 2: required checks cannot be disabled by a lower-precedence source
checks.compile.enabled: falseexit 2: tests require compile
execution.analysis_limits.*allowed
execution.timeouts.*exit 2, unless the value repeats the default
execution.max_workers and other execution.*exit 2, unless the value repeats the default
security.* (including security.network.allowed_destinations)exit 2, unless the value repeats the default
tools.*exit 2 (configuration source is not allowed to set this field), unless the policy allows it with allow_project_tool_paths: true
logging.level other than infoexit 2: project configuration may only repeat the safe logging default
logging.audit.enabled: false, logging.audit.include_tool_raw_output: trueexit 2
a value the organization has locked, set differentlyexit 2

Whatever the project level may not do, the organization policy can set via enforcement.locked_values.

version​

KeyTypeDefaultRequired
versioninteger, only 11yes

Keys starting with x- are allowed as extensions at every level and are ignored. Any other unknown key is exit 2.

project​

Describes how CodeGuard builds and tests the project.

KeyValuesDefaultEffect
project.kindauto, swift-package, xcodeautoChoice of build tool (see below)
project.package_pathpath or nullnullNames the manifest. Only the Package.swift in the root directory is accepted; a package in a subfolder is exit 2.
project.xcode.workspacepath to .xcworkspace or nullnullContainer for Xcode builds
project.xcode.projectpath to .xcodeproj or nullnullContainer for Xcode builds
project.xcode.schemename or nullnullScheme. If not set, CodeGuard uses the project's shared scheme; without a shared scheme, exit 2.
project.xcode.configurationnameDebugBuild configuration for xcodebuild
project.xcode.destinationplatform=macOS or platform=macOS,arch=<host architecture>nullDeprecated. Now only a safeguard for macOS; any other value is exit 2. Use project.platforms.
project.platformslist of macos, ios, watchos, tvos, visionosnot set (automatic)Platforms for compile/tests
project.test_destinationsobject (see below)not setOverride simulator device and runtime per destination

project.kind:

  • auto: If there is a Package.swift in the root directory, SwiftPM builds, even if an Xcode project exists as well. Otherwise an .xcodeproj/.xcworkspace is built with xcodebuild (workspace before project). If neither exists, compile and tests don't run.
  • swift-package: always SwiftPM, never Xcode.
  • xcode: always Xcode.

project.platforms:

  • Not set: CodeGuard builds every platform it detects and whose SDK is installed locally.
  • Set: intersection of the list and the detected platforms.
    • For Xcode projects, a platform the scheme doesn't support is exit 2.
    • A Swift package may name any platform, even one its manifest doesn't list under platforms:. This way [macos, ios] also builds an iOS package on macOS.
    • If the SDK of a listed platform is missing locally, the run ends with exit 3 before the first build.
  • The list must not be empty and must not contain duplicates.

project.test_destinations: allowed keys ios, ipad, watchos, tvos, visionos. Each entry has:

FieldValuesMeaning
device_typename or identifier of the device type, not emptyreplaces the automatically chosen device type
runtimeX.Y or latestX.Y selects exactly this version (18.0 doesn't match 18.2)

ios only applies to the iPhone destination, ipad only to the iPad destination. If a destination listed here can't be satisfied (runtime missing, runtime below the deployment target), the run ends with exit 3 before a device is created. An empty object, an unknown key or an invalid version format is exit 2.

project:
platforms: [ios]
test_destinations:
ios: { device_type: "iPhone 18 Pro", runtime: "27.0" }
ipad: { runtime: latest }

paths​

KeyDefaultEffect
paths.include["Sources/**", "Tests/**", "Package.swift"]Only these files may be checked. Others result in path-outside-allowed-scope (exit 6) in check-file/check-diff.
paths.exclude[".build/**", "DerivedData/**", "Vendor/**", ".git/**"]These files are excluded, also from the content rules and from reading by the platform checks
paths.generated[".codeguard-artifacts/**"]Validated, but has no effect on the run in 0.3.5

Globs are relative to the project: ** (any number of levels), *, ?, [...]. Not allowed are a leading /, .. and the characters ! ~ ; | & < > ( ) { } $ as well as backtick and backslash.

Xcode projects with other folder names

The default only covers Sources/, Tests/ and Package.swift. If your sources live in, say, MyApp/ and MyAppTests/, you have to adjust paths.include; otherwise format, swiftlint and the content rules don't check these files. The platform checks read the project file, Info.plist and entitlements even outside paths.include.

To exclude a CocoaPods project, add Pods/** to paths.exclude. If an exclude covers a project's project.pbxproj, the platform checks silently skip that project.

protected_files​

A list of globs. If a diff changes a matching file, the run ends with protected-file-change and exit 6. The default list is under Checks and rules. The organization policy can enforce entries with mandatory_protected_files that the project can't remove.

protected_files:
- .codeguard.yml
- .codeguard.yaml
- Package.swift
- Package.resolved
- "**/*.entitlements"
- "**/*.xcodeproj/project.pbxproj"
- .github/workflows/**
- .gitlab-ci.yml
- "**/*.xcconfig"
- Config/Secrets.plist # custom entry

checks​

Each check has three fields:

FieldMeaning
enabledCheck is enabled
requiredmandatory: a missing tool or an incomplete check results in exit 3 instead of a warning
scopesIn which commands the check runs: file (check-file), diff (check-diff), project (check-project), operation
Checkenabledrequiredscopes (default)
checks.securitytruetrue[file, diff, project, operation]
checks.platformtruefalse[file, diff, project]
checks.formattruetrue[file, diff, project]
checks.swiftlinttruetrue[file, diff, project]
checks.compiletruetrue[diff, project]
checks.teststruetrue[diff, project]

Additionally for checks.tests:

FieldDefaultEffect
simulatortruefalse skips all simulator test destinations (simulatorDisabled)
ipadtruefalse prevents the additional iPad run

What the project can do with checks:

  • Restricting scopes is allowed, even to []. This removes a check from every run without disabling it. compile and tests never run in file anyway.
  • checks.platform may be disabled entirely by the project (enabled: false) or made mandatory (required: true).
  • enabled: false or required: false for format, swiftlint, compile, tests and security is exit 2. Only the organization policy may do this via locked_values.

Example: static checks only, no build and no tests:

version: 1
checks:
compile: { scopes: [] }
tests: { scopes: [] }

rules​

Every built-in rule under rules.<id> supports these fields:

FieldValuesMeaning
enabledtrue/falseRule on or off
severityinfo, warning, error, criticalSeverity. error and critical block.
scopesfile, diff, project, operationin which commands the rule runs
path_overrideslist of { paths, enabled?, severity? }different settings for specific paths

Rule IDs this applies to:

  • Content rules: forbidden-fatal-error, forbidden-force-try, forbidden-force-cast, sensitive-data-in-log, hardcoded-secret, unauthorized-network-destination
  • Platform rules: privacy-manifest-invalid, privacy-manifest-missing, privacy-required-reason-undeclared, privacy-reason-code-invalid, privacy-tracking-domains-missing, purpose-string-missing, purpose-string-empty, ats-arbitrary-loads, ats-insecure-exception-domain, entitlements-file-missing, entitlement-weakens-hardened-runtime, entitlement-get-task-allow-release, macos-app-sandbox-missing

The defaults are listed under Checks and rules. The keys rules.public-api-change and rules.dependency-policy exist in the schema but have no effect in 0.3.5.

Additional fields:

KeyDefaultMeaning
rules.sensitive-data-in-log.logger_symbols.functions[print, debugPrint, dump, NSLog, os_log]free logger functions
rules.sensitive-data-in-log.logger_symbols.methods[debug, info, notice, log, trace, warning, error, fault, critical]logger methods (the receiver must contain log)
rules.sensitive-data-in-log.sensitive_identifiers[token, accessToken, refreshToken, password, passwd, secret, apiKey, authorization, cookie, sessionID, privateKey]sensitive identifiers (also used by the assignment heuristic of hardcoded-secret)
rules.hardcoded-secret.allowlist_sha256[]allowlist specific values as sha256:<64 hex> (format tier and URL userinfo only; for URLs, the hash of the password)

The project may not go below a minimum severity that the organization sets via severity_floors (exit 2). It may not disable mandatory rules (mandatory_rules).

Example:

rules:
forbidden-fatal-error:
severity: critical
path_overrides:
- paths: ["Tests/**", "Sources/Previews/**"]
enabled: false
sensitive-data-in-log:
sensitive_identifiers: [token, password, iban]
hardcoded-secret:
path_overrides:
- paths: ["Tests/Fixtures/**"]
enabled: false
ats-arbitrary-loads:
severity: error
note

For forbidden-*, a path_overrides entry replaces the default overrides for test paths, because lists are replaced. Add Tests/**, **/*Tests/** and **/*UITests/** again if the rule should stay off there.

rules.metrics​

Configures the managed SwiftLint metric rules. Without rules.metrics, there is no metrics run. Keys here are snake_case.

KeyParameters
severityerror (default) or critical: severity of a finding above the error threshold
line_lengthwarning, error, ignores_urls, ignores_function_declarations, ignores_comments, ignores_interpolated_strings
file_lengthwarning, error, ignore_comment_only_lines
type_body_lengthwarning, error
function_body_lengthwarning, error
closure_body_lengthwarning, error
cyclomatic_complexitywarning, error, ignores_case_statements
nestingtype_level.warning, type_level.error, function_level.warning, function_level.error
function_parameter_countwarning, error, ignores_default_parameters
large_tuplewarning, error
enum_case_associated_values_countwarning, error

Rules for the values:

  • Thresholds are integers ≥ 1, and warning ≤ error.

  • Every rule in the project needs at least one threshold. A switch alone is exit 2:

    configuration validation failed: project:/rules/metrics/line_length: metric rule requires at least one warning or error threshold
  • Only configured thresholds apply. If only error is set, warning = error. An unset nesting level is disabled.

  • If the organization sets a baseline (enforcement.metrics), the project may only lower thresholds, only change ignores_* from true to false and only raise severity.

rules:
metrics:
severity: error
line_length: { warning: 120, error: 160, ignores_urls: true }
function_body_length: { warning: 40, error: 80 }
cyclomatic_complexity: { warning: 10, error: 20 }
nesting: { type_level: { warning: 2 }, function_level: { warning: 3 } }

config validate then shows every effective threshold with its source:

metrics: rules.metrics.severity = error (built-in, locked: false)
metrics: rules.metrics.line_length.warning = 120 (project, locked: false)
metrics: rules.metrics.line_length.error = 160 (project, locked: false)
metrics: rules.metrics.line_length.ignores_urls = true (project, locked: false)

custom_rules​

Your own rules. In 0.3.5, all fields except pattern and suggestion must be specified; otherwise config validate reports configuration value has the wrong type.

FieldValuesRequired
id^[a-z][a-z0-9-]{2,63}$, not codeguard-…, not a built-in IDyes
engineregex or file (dependency, architecture are not implemented)yes
severityinfo, warning, error, criticalyes
scopeslist of file, diff, project, operationyes
autofixonly falseyes
paths{ include: [...], exclude: [...] }, specify both listsyes
patternregular expression, at most 4096 characters; forbidden with engine: filewith regex
messageat most 500 charactersyes
suggestionat most 1000 charactersno
custom_rules:
- id: no-debug-print
engine: regex
severity: warning
scopes: [file, diff, project]
autofix: false
paths: { include: ["Sources/**"], exclude: [] }
pattern: "debugPrint\\("
message: "debugPrint gehört nicht in Produktionscode."
suggestion: "Logger verwenden."
- id: no-env-files
engine: file
severity: error
scopes: [diff, project]
autofix: false
paths: { include: ["**/*.env"], exclude: [] }
message: ".env-Dateien nicht einchecken."

Custom rules can never be waived. The matched text doesn't appear in the report.

waivers​

A waiver accepts one specific finding as an accepted risk. The finding stays in the report but no longer blocks (disposition: accepted_risk, counted under summary.accepted_risk).

FieldMeaning
idunique ID
rulerule ID of the finding (built-in rules and codeguard-metric-* only)
fingerprintsha256:<64 hex> from the finding's fingerprint field in the JSON report
pathsglobs the waiver is restricted to (not empty)
reasonjustification (not empty)
ownerresponsible person or group (not empty)
created_at, expires_attimestamps, e.g. 2026-10-01T00:00:00Z; expires_at must be after created_at, and the waiver must be currently valid

Maximum duration: 90 days, 7 days for critical. The organization can lower this further with limits.max_waiver_days. Not waivable are protected-file-change, path-outside-allowed-scope, all run diagnostics (codeguard-… except codeguard-metric-…), custom rules, swift-format, swiftlint.* and rules the organization lists in non_waivable_rules.

This is how you get the fingerprint:

codeguard --format json check-project | jq '.violations[] | {rule, file, fingerprint}'
waivers:
- id: W-001
rule: macos-app-sandbox-missing
fingerprint: "sha256:3f766684cfc82777b3ca10fb481c1154bed31576d0f9a3e45301457d7a741496"
paths: ["App.entitlements"]
reason: "Debug-Build außerhalb des App Store, Ticket APP-123"
owner: "team-mac"
created_at: "2026-10-01T00:00:00Z"
expires_at: "2026-12-01T00:00:00Z"

A critical waiver lasting more than 7 days fails:

configuration validation failed: project:/waivers/0/expires_at: waiver duration exceeds the severity maximum
When a waiver still matches

The fingerprint contains no line number. A waiver therefore still matches when code above it moves. It no longer matches when the affected line itself changes. For platform rules, it is tied to target, file, key and configuration.

execution​

The project level may only set analysis_limits here. All other values can only be changed by the organization via locked_values.

execution.analysis_limits (project may set):

KeyDefaultMaximumApplies to
max_plist_bytes1 MiB64 MiBplist, .entitlements, .xcprivacy
max_pbxproj_bytes16 MiB256 MiBproject.pbxproj
max_source_bytes2 MiB64 MiBSwift/ObjC/C sources, .xcconfig, contents.xcworkspacedata
max_text_bytes2 MiB64 MiBother text files
max_files20 0001 000 000number of files in a run (if exceeded, the content check evaluates no file)
max_include_depth1664depth of #include in .xcconfig chains

The minimum is 1 in each case. Specify values in bytes, e.g. max_text_bytes: 4194304.

execution.timeouts (organization only): duration as a number plus s, m, h or d.

KeyDefaultApplies to
preflight10squeries before the build (dump-package, -showsdks, simctl list, -showBuildSettings) as a shared budget
format_file30sswift-format per file
format_batch2mswift-format per run
swiftlint5mSwiftLint runs
static_analysis2mcontent rules and platform checks
compile20mevery build in the matrix, including -showdestinations and -list
tests30mevery test run
simulator_boot3mcreating and booting a simulator
simulator_cleanup1mshutting down and deleting a simulator
termination_grace5swait time after terminating a tool
project_total60mnot an overall limit for the run; only used to detect orphaned simulators
guard_operation, report, check_file_total5s, 30s, 5mno effect in 0.3.5

Other values (organization only):

KeyDefaultEffect
max_workers4 (1–4)parallel evaluation of the content rules
output_limit_mib_per_stream10 (1–10)upper limit per output stream of a tool
concurrency, reserve_logical_cpus, cache.enabled, cache.locationauto, 2, true, user-cacheno effect in 0.3.5

security​

Only the organization can change anything here.

KeyDefaultEffect
security.network.allowed_destinations[]Allowlist for unauthorized-network-destination. Format scheme://host[:port] or scheme://*.host[:port]. The old format host[:port] is exit 2. Empty: rule inactive.
security.fail_closed, security.offline, security.redact.enabledtruealways true, can't be changed
security.build_scripts, security.shell.*, security.network.default_decision, security.approvals.*, security.redact.replacementsee schemano effect in 0.3.5

tools​

Paths and version constraints of the tools. By default, only the organization can set them.

KeyDefaultEffect
tools.swift_format.executable / .version_constraintnullexplicit path or version constraint (e.g. >=6.3.0,<6.4.0)
tools.swiftlint.executable / .version_constraintnullsame as above
tools.swift.executable / .version_constraintnullsame as above
tools.xcodebuild.executable / .version_constraint/usr/bin/xcodebuild / default range >=26.0.0,<28.0.0same as above
tools.resolutiontrusted-pathsonly value

An explicit path must be under a trusted directory of the policy. A version_constraint set for swift-format excludes the unversioned Xcode 27 build (main).

diff​

KeyDefaultEffect
diff.detect_renamestruedetect renames and copies (git diff -M -C --find-copies-harder)
diff.include_untrackedtrueno effect in 0.3.5. Untracked files are controlled only by check-diff --include-untracked.
diff.static_findings.fail_on, diff.baseline[introduced, touched], nullno effect in 0.3.5

Keys without effect in 0.3.5​

These sections exist in the schema and are validated, but don't affect the run (they only go into the configuration_hash):

  • autofix.* (there is no autofix; autofix.max_iterations only appears as run.max_iterations in the report)
  • reports.* (format and destination come from --format and --output)
  • logging.level, logging.audit.retention_days, logging.audit.include_tool_raw_output; the audit log is always on
  • ci.*
Every change invalidates trust tickets

The configuration_hash is part of every trust ticket. Any change to the effective configuration, even to a key without effect, invalidates existing tickets. After that, you need to run codeguard trust grant again.

Complete example​

version: 1

project:
kind: auto
platforms: [macos, ios]

paths:
include: ["Sources/**", "Tests/**", "Package.swift"]
exclude: [".build/**", "DerivedData/**", "Vendor/**", ".git/**", "Tests/Fixtures/**"]

checks:
platform: { enabled: true, required: true, scopes: [file, diff, project] }
tests: { simulator: true, ipad: true }

rules:
forbidden-fatal-error:
severity: error
sensitive-data-in-log:
sensitive_identifiers: [token, accessToken, refreshToken, password, secret, apiKey, iban]
metrics:
line_length: { warning: 120, error: 160, ignores_urls: true }
function_body_length: { warning: 40, error: 80 }

custom_rules:
- id: no-debug-print
engine: regex
severity: warning
scopes: [file, diff, project]
autofix: false
paths: { include: ["Sources/**"], exclude: [] }
pattern: "debugPrint\\("
message: "debugPrint gehört nicht in Produktionscode."

execution:
analysis_limits:
max_text_bytes: 4194304

See also​