Skip to main content

Checks and rules

This page explains which checks run in which command, which rules exist and when a finding blocks the run.

How a check run works​

The phases run in this order:

  1. Path rules (security): Is every requested or changed file inside the allowed scope? Is a protected file affected? A violation ends the run immediately with exit 6. Nothing runs after that.
  2. Content rules (security): Swift constructs, secrets, sensitive data in logs, network destinations, custom rules.
  3. Platform checks (platform): static analysis of Xcode targets and package targets with a privacy manifest.
  4. External static tools: format, swiftlint and swiftlint_metrics. Their processes start in parallel, and the report always lists them in this order.
  5. compile: build for each platform, one after another.
  6. tests: only if the build succeeded on all built platforms.

A blocking finding from steps 2 to 6 results in exit 1. The later steps still run, with one exception: tests does not start after a failed build.

Which check runs in which command​

Checkcheck-filecheck-diffcheck-projectDefaultCan the project disable it?
securityyesyesyeson, requiredno
platformyesyesyeson, not requiredyes
formatyesyesyeson, requiredonly via scopes
swiftlintyesyesyeson, requiredonly via scopes
swiftlint_metricsyesyesyesoff until metric rules are configuredvia rules.metrics
compileneveryesyeson, requiredonly via scopes
testsneveryesyeson, requiredonly via scopes

"Required" (required: true) means: if the tool is missing or the check stays incomplete, the run ends with exit 3. A check that is not required only produces a warning in this case. To configure this, see Project configuration.

compile and tests are only requested if the project root contains a Package.swift or an .xcodeproj/.xcworkspace.

Path rules​

Rule IDSeverityTriggerExit
path-outside-allowed-scopeerrorFile is outside paths.include or inside paths.exclude6 (scope_violation)
protected-file-changecriticalA changed file matches protected_files6 (protected_file)

Both can never be waived: the waiver would live in the project's .codeguard.yml, which the same diff could change.

Protected by default:

.codeguard.yml, .codeguard.yaml, Package.swift, Package.resolved,
**/*.entitlements, **/*.xcodeproj/project.pbxproj, .github/workflows/**,
.gitlab-ci.yml, **/*.xcconfig, .claude/settings.json, .claude/settings.local.json,
.claude/hooks/**, .opencode/package.json, .opencode/plugins/**, opencode.json,
opencode.jsonc, Tools/CodeGuard/**, .codeguard/baselines/**
Protected files in check-diff

If a diff changes a protected file, such as Package.swift, the .codeguard.yml or a CI file, check-diff ends with exit 6. This also applies in merge requests. Such changes deliberately require human review.

Real output after a change to Package.swift:

CODEGUARD_BLOCKED

1 blocking violations found in 2 changed files.
Validation: file (complete)
Exit: 6 (protected_file)
Requested: security
Completed: security
Skipped:
Konfiguration: .codeguard.yml (auto, sha256 5967f32a…)
Run: cg_3cd7b976-4cc7-498d-b1fe-da50c5fab091

1. CRITICAL Package.swift
Rule: protected-file-change
The operation targets a protected project path.
Do not change protected files without an explicit policy review.

Next action: Request human review for the reported findings.

Content rules​

The content rules run in the security phase. They read each file exactly once, start no process and need no trust ticket. The Swift rules work on lexer tokens: comments and string contents do not count as code, but an interpolation \(…) does. There is no suppression via comments and no autofix.

IDDefaultWhat is detected
forbidden-fatal-errorerror, off in test pathsCall to fatalError( or Swift.fatalError(
forbidden-force-tryerror, off in test pathstry!
forbidden-force-casterror, off in test pathsas!
sensitive-data-in-logcritical, also in testsLogger call with a sensitive identifier in its arguments
hardcoded-secretcritical or warningSecret formats, password in a URL, sensitive assignment
unauthorized-network-destinationerror, warning in test pathsNetwork destination outside the allowlist (only active if an allowlist is set)

Test paths are Tests/**, **/*Tests/** and **/*UITests/**. They are stored as default path_overrides and can be overridden.

forbidden-fatal-error, forbidden-force-try, forbidden-force-cast​

  • All #if branches are checked, including inactive ones. Macro expansions are not visible.
  • x.fatalError( with a different receiver does not count. If the file itself declares func fatalError, the finding is only probable.
  • try !x is not a match for forbidden-force-try.
  • Unbalanced brackets in a .swift file result in codeguard-content-scan-incomplete instead of a silent pass.

sensitive-data-in-log​

  • Loggers are free functions from logger_symbols.functions (default: print, debugPrint, dump, NSLog, os_log) or methods from logger_symbols.methods (default: debug, info, notice, log, trace, warning, error, fault, critical). A method only counts if the last member of the receiver chain contains log (e.g. logger.info(…)).
  • Sensitive identifiers (sensitive_identifiers, default: token, accessToken, refreshToken, password, passwd, secret, apiKey, authorization, cookie, sessionID, privateKey):
    • Identifier equals an entry (case-insensitive): certain, severity as configured.
    • Only one camelCase component matches (e.g. userPasswordHash): possible, at most warning.
  • privacy: .private does not make a call safe.
  • The report never contains the argument text, only the category: Possible password is written to a log.

hardcoded-secret​

StageFilesMatchSeverity
Formatall text files15 detectors: AWS Access Key ID, GitHub tokens (classic, fine-grained), GitLab PAT, Slack, Stripe Secret Key, OpenAI (two formats), Anthropic, Twilio API Key, SendGrid, npm, PEM private key header, JWT, Google API Keycritical; Google API Key warning
URL userinfoall text filesscheme://user:password@host with a non-empty passwordcritical
Sensitive assignmentSwift/ObjC, Plist, JSON, YAML, .xcconfig, .envsensitive identifier with a literal of at least 8 characters that is not a placeholderwarning

Not a match: placeholders (<…>, YOUR_…, xxx…, changeme, $(…), ${…}, %@, $VAR), UUIDs, hex digests, official example values (e.g. the AWS example key) and Swift enum raw values. In Package.resolved, *.lock and *.xcassets/**/Contents.json only the format stage runs.

The detected value never appears in the report, the audit log or the fingerprint. The message is always A hard-coded credential was detected. To permanently allow a known, harmless value, use rules.hardcoded-secret.allowlist_sha256 (see Project configuration).

PEM headers in documentation

Even the header of a PEM private key in docs, a comment or a string is a critical finding.

unauthorized-network-destination​

  • Only active if security.network.allowed_destinations is not empty. Only the organization policy can set this list (see Organization policy).
  • Entry format: scheme://host[:port], also scheme://*.host[:port] for subdomains of any depth (not the domain itself). Without a port, the default port applies.
  • Checked are string literals in .swift and string values in Plist and JSON that start with http://, https://, ws:// or wss://.
  • Destination outside the list: error. Interpolation in the scheme or host part, or an unparsable URL: warning/possible.
  • Ignored are reserved names (example.com/.org/.net, *.example, *.test, *.invalid, localhost, *.localhost) and Apple's Plist DTD URL.

Custom rules (custom_rules)​

EngineBehavior
regexThe pattern runs line by line on the raw text, one finding per line. Comments and strings are searched too. Lines over 4 KiB are skipped.
fileEvery matched file is a finding (including binary files). pattern is not allowed.
dependency, architectureNot implemented. Produces the warning codeguard-custom-rule-unsupported.

Custom rules can never be waived, but you can control them via their paths. For configuration, see Project configuration.

What the content rules scan​

ScopeFiles
check-filethe requested files
check-diffadded, modified, renamed, copied and (with --include-untracked) untracked files, each in full
check-projectall text files that are not excluded

.git/, .build/, DerivedData/ and *.xcresult are never scanned, at any depth.

When a content finding blocks in check-diff​

Every finding gets a scope_relation:

ValueMeaningBlocks?
introducedFinding in an added line (new and untracked files: always)yes, if the rule blocks
touchedFinding in a changed hunk without an added start lineyes, if the rule blocks
pre_existingFinding in unchanged code of a changed fileonly for critical

This way, legacy code in a touched file does not block, except for critical findings such as secrets.

Platform checks​

The platform phase checks purely statically, without xcodebuild and without a trust ticket.

Target kindDetectionRules
App-likeApplication, Watch app, App Clip, App Extension, ExtensionKit Extensionall
FrameworkFramework targetprivacy only
Swift package targetFolder Sources/<Target>/ with PrivacyInfo.xcprivacyprivacy only, never privacy-manifest-missing
everything elseCLI tool, test bundle, static library, package target without manifestnone
Rule IDDefaultTrigger
privacy-manifest-invaliderror.xcprivacy not parsable, wrong type or incomplete entry in NSPrivacyAccessedAPITypes
privacy-manifest-missingerrorApp or framework target uses a required-reason API but has no PrivacyInfo.xcprivacy
privacy-required-reason-undeclarederrorAPI category used but not declared in NSPrivacyAccessedAPITypes
privacy-reason-code-invaliderrorReason code is not allowed for the category
privacy-tracking-domains-missingwarningNSPrivacyTracking: true without NSPrivacyTrackingDomains
purpose-string-missingerrorProtected API used, key missing in Info.plist and INFOPLIST_KEY_* (in at least one configuration)
purpose-string-emptyerrorKey present but empty
ats-arbitrary-loadswarningNSAllowsArbitraryLoads, …InWebContent or …ForMedia is true
ats-insecure-exception-domainwarningException domain with NSExceptionAllowsInsecureHTTPLoads: true or TLS below 1.2
entitlements-file-missingerrorCODE_SIGN_ENTITLEMENTS points to a missing file
entitlement-weakens-hardened-runtimewarninge.g. com.apple.security.cs.disable-library-validation: true
entitlement-get-task-allow-releasewarningget-task-allow: true in a configuration named Release
macos-app-sandbox-missingwarningApp-like macOS target without com.apple.security.app-sandbox: true

Every target is checked for all of its build configurations. For this, the analysis may read files outside paths.include (project file, Info.plist, entitlements), but paths.exclude still applies. If a setting or file cannot be resolved reliably, the result is codeguard-platform-check-incomplete instead of a guessed result.

Real excerpt from a macOS project without App Sandbox:

2. WARNING App.entitlements
Rule: entitlement-weakens-hardened-runtime
Entitlement com.apple.security.cs.disable-library-validation is true, weakening the hardened runtime.
Remove com.apple.security.cs.disable-library-validation unless the target has a specific, documented need for it.

3. WARNING App.entitlements
Rule: macos-app-sandbox-missing
Target "App" configuration "Debug" does not set com.apple.security.app-sandbox = true.
Set com.apple.security.app-sandbox to true, or waive this rule for non-App-Store distribution.

Format: swift-format​

  • Rule ID swift-format, always error and blocking. The swift-format category appears in square brackets before the message, e.g. [Indentation] unindent by 2 spaces.
  • The whole file is always checked.
  • Without a project file, the swift-format defaults apply (among others, 2-space indentation and 100-character line length).
  • A .swift-format in the project root is respected. It must be a regular file (not a symlink) of at most 64 KiB and may only contain keys known to the swift-format shipped with Xcode 27. Unknown keys, wrong types or empty nested objects result in exit 2, a non-regular file in exit 6.
Partial rules map

If rules in the .swift-format contains only some of the rules, swift-format disables all rules not listed. To keep the defaults for the others, list them explicitly (swift-format dump-configuration shows all of them).

Example of a .swift-format used to check the sample projects in this help:

{
"version": 1,
"indentation": { "spaces": 4 },
"lineLength": 120
}

Lint: swiftlint​

  • Rule IDs swiftlint.<rule>, e.g. swiftlint.force_try. The SwiftLint severity is kept: error blocks, warning does not.
  • At most <project root>/.swiftlint.yml is respected. Rejected (exit 6) are parent_config, child_config, plugins, remote URLs in these keys as well as absolute paths and .. in included/excluded.
  • included/excluded in the .swiftlint.yml have no effect under CodeGuard, because SwiftLint runs on a private copy of the files. Control the scope with CodeGuard's paths.include/paths.exclude.
  • // swiftlint:disable still works, including for the managed metric rules.

Content rules and SwiftLint can report the same location, for example forbidden-force-try and swiftlint.force_try. These are two separate findings.

Metrics: swiftlint_metrics​

A second swiftlint run generated by CodeGuard enforces the metric rules from rules.metrics, independently of the project's .swiftlint.yml. It only runs if checks.swiftlint is active and at least one metric rule has a threshold. There are no built-in default thresholds.

Key in rules.metricsRule ID
line_lengthcodeguard-metric-line-length
file_lengthcodeguard-metric-file-length
type_body_lengthcodeguard-metric-type-body-length
function_body_lengthcodeguard-metric-function-body-length
closure_body_lengthcodeguard-metric-closure-body-length
cyclomatic_complexitycodeguard-metric-cyclomatic-complexity
nestingcodeguard-metric-nesting
function_parameter_countcodeguard-metric-function-parameter-count
large_tuplecodeguard-metric-large-tuple
enum_case_associated_values_countcodeguard-metric-enum-case-associated-values-count

If code exceeds the error threshold, the finding gets the severity from rules.metrics.severity (error or critical) and blocks. Exceeding a warning threshold does not block. For parameters, see Project configuration.

Build: compile​

Project typemacOSiOS, watchOS, tvOS, visionOS
Swift packageswift buildxcodebuild build on a copy of the package with the package scheme
Xcode projectxcodebuild buildxcodebuild build with a generic simulator destination
  • CodeGuard detects the platforms itself (Xcode: -showdestinations; packages: platforms in the manifest, macOS only if not specified) and builds the intersection of detected, configured (project.platforms) and locally available (-showsdks).
  • The platforms are built one after another. A compile error on one platform does not stop the others. A timeout, cancellation or tool failure stops the matrix.
  • Builds run offline, without code signing, with one job (-jobs 1 or --jobs 1), in a working directory outside the project. Xcode builds run on a copy of the project (at most 50,000 entries, 2 GiB, depth 100; without .git, .build, DerivedData, xcuserdata, *.xcresult).
  • Rule IDs: swift-compiler-error, swift-compiler-warning, swift-compiler-note (SwiftPM) or xcodebuild-compiler-error, xcodebuild-compiler-warning (Xcode).

Real compile error from a Swift package:

1. ERROR Sources/SwiftPMClean/SwiftPMClean.swift:2:17
Rule: swift-compiler-error
[swift-compiler-error] cannot find 'missingValue' in scope

Tests: tests​

  • macOS is tested natively (swift test or xcodebuild test).
  • For iOS, watchOS, tvOS and visionOS, CodeGuard creates throwaway simulators, boots them, runs the tests and deletes them again. Destinations: ios-phone, ios-pad (only for an iPad-capable Xcode target, TARGETED_DEVICE_FAMILY contains 2), watchos, tvos, visionos. Packages get no iPad run.
  • Runtime: the newest available one with a version ≥ the deployment target. Device type: the first matching entry from a fixed preference list (newest iPhone, iPad Pro 13", largest Watch, Apple TV 4K, Vision Pro). You can override both with project.test_destinations.
  • Rule ID swift-test-failure. If the same test fails on several destinations, it is one finding listing all destinations.

Real finding from an iOS app with iPad support:

1. [iOS/iPhone, iOS/iPad] ERROR Tests/AppTests.swift:8:1
Rule: swift-test-failure
Expectation failed: 1 == 2

If a runtime is missing for a detected platform, the destination is skipped (simulatorUnavailable) and you get a warning. If this leaves no runnable test destination and macOS is not among the built platforms, the run ends with exit 3, because tests is required. Real output of a watchOS app on a machine without a watchOS runtime:

1. ERROR
Rule: codeguard-simulator-selection-failed
checks.tests.required is set, but no test target can run on this host; no test ran.
Install a simulator runtime for a detected platform, or set checks.tests.required: false.

2. WARNING
Rule: codeguard-simulator-unavailable
No usable watchOS simulator runtime or device type with deployment target 10.0; its tests were skipped. There are no installed watchOS simulator runtimes.
note

Only the organization policy can set checks.tests.required: false. The project can only exclude tests via scopes: []. See Project configuration.

Run diagnostics​

Run diagnostics describe the state of the run, not a code rule. They are not waivable, and severity_floors do not apply to them.

Rule IDSeverityMeaning
codeguard-build-execution-deniederrorBuild/test not authorized (no trust ticket or no CI attestation). Results in exit 6.
codeguard-tool-output-unparseableerrorTool output cannot be read reliably. Results in exit 4, never in a pass.
codeguard-dependency-unavailableerror/warningTool missing, or SwiftPM could not fetch remote dependencies (offline)
codeguard-workspace-copy-failederrorThe project copy for Xcode exceeded a fixed limit (exit 4)
codeguard-content-scan-incompletewarningA file could not be evaluated reliably (size, encoding, lexer, time budget, unparsable JSON/Plist …). With checks.security.required: true, exit 3.
codeguard-custom-rule-unsupportedwarningCustom rule with engine dependency or architecture
codeguard-tool-configuration-rejectedwarningProject .swift-format or .swiftlint.yml rejected
codeguard-project-config-ignoredwarning--no-project-config although a .codeguard.yml exists
codeguard-platform-check-incompletewarningPlatform check could not evaluate a target reliably
codeguard-platform-unavailablewarningDetected platform without a local SDK skipped
codeguard-platform-not-builtinfoPlatform in the manifest that CodeGuard does not build (e.g. maccatalyst, linux)
codeguard-platform-resolution-failederrorPlatform resolution ends the run before the first build
codeguard-simulator-unavailablewarningNo matching runtime/device type or no simctl
codeguard-simulator-selection-failederrorForced destination cannot be satisfied or no test destination is runnable
codeguard-simulator-failederrorA simctl step failed (exit 4/5)
codeguard-simulator-cleanup-failedwarningA simulator could not be cleaned up. Does not change the exit code.
codeguard-simulator-sweep-incompletewarningOrphaned devices could not be cleaned up completely

See also​