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:
- 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. - Content rules (
security): Swift constructs, secrets, sensitive data in logs, network destinations, custom rules. - Platform checks (
platform): static analysis of Xcode targets and package targets with a privacy manifest. - External static tools:
format,swiftlintandswiftlint_metrics. Their processes start in parallel, and the report always lists them in this order. compile: build for each platform, one after another.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
| Check | check-file | check-diff | check-project | Default | Can the project disable it? |
|---|---|---|---|---|---|
security | yes | yes | yes | on, required | no |
platform | yes | yes | yes | on, not required | yes |
format | yes | yes | yes | on, required | only via scopes |
swiftlint | yes | yes | yes | on, required | only via scopes |
swiftlint_metrics | yes | yes | yes | off until metric rules are configured | via rules.metrics |
compile | never | yes | yes | on, required | only via scopes |
tests | never | yes | yes | on, required | only 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 ID | Severity | Trigger | Exit |
|---|---|---|---|
path-outside-allowed-scope | error | File is outside paths.include or inside paths.exclude | 6 (scope_violation) |
protected-file-change | critical | A changed file matches protected_files | 6 (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/**
check-diffIf 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.
| ID | Default | What is detected |
|---|---|---|
forbidden-fatal-error | error, off in test paths | Call to fatalError( or Swift.fatalError( |
forbidden-force-try | error, off in test paths | try! |
forbidden-force-cast | error, off in test paths | as! |
sensitive-data-in-log | critical, also in tests | Logger call with a sensitive identifier in its arguments |
hardcoded-secret | critical or warning | Secret formats, password in a URL, sensitive assignment |
unauthorized-network-destination | error, warning in test paths | Network 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
#ifbranches are checked, including inactive ones. Macro expansions are not visible. x.fatalError(with a different receiver does not count. If the file itself declaresfunc fatalError, the finding is onlyprobable.try !xis not a match forforbidden-force-try.- Unbalanced brackets in a
.swiftfile result incodeguard-content-scan-incompleteinstead 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 fromlogger_symbols.methods(default:debug,info,notice,log,trace,warning,error,fault,critical). A method only counts if the last member of the receiver chain containslog(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 mostwarning.
- Identifier equals an entry (case-insensitive):
privacy: .privatedoes not make a call safe.- The report never contains the argument text, only the category:
Possible password is written to a log.
hardcoded-secret
| Stage | Files | Match | Severity |
|---|---|---|---|
| Format | all text files | 15 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 Key | critical; Google API Key warning |
| URL userinfo | all text files | scheme://user:password@host with a non-empty password | critical |
| Sensitive assignment | Swift/ObjC, Plist, JSON, YAML, .xcconfig, .env | sensitive identifier with a literal of at least 8 characters that is not a placeholder | warning |
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).
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_destinationsis not empty. Only the organization policy can set this list (see Organization policy). - Entry format:
scheme://host[:port], alsoscheme://*.host[:port]for subdomains of any depth (not the domain itself). Without a port, the default port applies. - Checked are string literals in
.swiftand string values in Plist and JSON that start withhttp://,https://,ws://orwss://. - 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)
| Engine | Behavior |
|---|---|
regex | The 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. |
file | Every matched file is a finding (including binary files). pattern is not allowed. |
dependency, architecture | Not 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
| Scope | Files |
|---|---|
check-file | the requested files |
check-diff | added, modified, renamed, copied and (with --include-untracked) untracked files, each in full |
check-project | all 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:
| Value | Meaning | Blocks? |
|---|---|---|
introduced | Finding in an added line (new and untracked files: always) | yes, if the rule blocks |
touched | Finding in a changed hunk without an added start line | yes, if the rule blocks |
pre_existing | Finding in unchanged code of a changed file | only 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 kind | Detection | Rules |
|---|---|---|
| App-like | Application, Watch app, App Clip, App Extension, ExtensionKit Extension | all |
| Framework | Framework target | privacy only |
| Swift package target | Folder Sources/<Target>/ with PrivacyInfo.xcprivacy | privacy only, never privacy-manifest-missing |
| everything else | CLI tool, test bundle, static library, package target without manifest | none |
| Rule ID | Default | Trigger |
|---|---|---|
privacy-manifest-invalid | error | .xcprivacy not parsable, wrong type or incomplete entry in NSPrivacyAccessedAPITypes |
privacy-manifest-missing | error | App or framework target uses a required-reason API but has no PrivacyInfo.xcprivacy |
privacy-required-reason-undeclared | error | API category used but not declared in NSPrivacyAccessedAPITypes |
privacy-reason-code-invalid | error | Reason code is not allowed for the category |
privacy-tracking-domains-missing | warning | NSPrivacyTracking: true without NSPrivacyTrackingDomains |
purpose-string-missing | error | Protected API used, key missing in Info.plist and INFOPLIST_KEY_* (in at least one configuration) |
purpose-string-empty | error | Key present but empty |
ats-arbitrary-loads | warning | NSAllowsArbitraryLoads, …InWebContent or …ForMedia is true |
ats-insecure-exception-domain | warning | Exception domain with NSExceptionAllowsInsecureHTTPLoads: true or TLS below 1.2 |
entitlements-file-missing | error | CODE_SIGN_ENTITLEMENTS points to a missing file |
entitlement-weakens-hardened-runtime | warning | e.g. com.apple.security.cs.disable-library-validation: true |
entitlement-get-task-allow-release | warning | get-task-allow: true in a configuration named Release |
macos-app-sandbox-missing | warning | App-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, alwayserrorand 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-formatin 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.
rules mapIf 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:errorblocks,warningdoes not. - At most
<project root>/.swiftlint.ymlis respected. Rejected (exit 6) areparent_config,child_config,plugins, remote URLs in these keys as well as absolute paths and..inincluded/excluded. included/excludedin the.swiftlint.ymlhave no effect under CodeGuard, because SwiftLint runs on a private copy of the files. Control the scope with CodeGuard'spaths.include/paths.exclude.// swiftlint:disablestill 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.metrics | Rule ID |
|---|---|
line_length | codeguard-metric-line-length |
file_length | codeguard-metric-file-length |
type_body_length | codeguard-metric-type-body-length |
function_body_length | codeguard-metric-function-body-length |
closure_body_length | codeguard-metric-closure-body-length |
cyclomatic_complexity | codeguard-metric-cyclomatic-complexity |
nesting | codeguard-metric-nesting |
function_parameter_count | codeguard-metric-function-parameter-count |
large_tuple | codeguard-metric-large-tuple |
enum_case_associated_values_count | codeguard-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 type | macOS | iOS, watchOS, tvOS, visionOS |
|---|---|---|
| Swift package | swift build | xcodebuild build on a copy of the package with the package scheme |
| Xcode project | xcodebuild build | xcodebuild build with a generic simulator destination |
- CodeGuard detects the platforms itself (Xcode:
-showdestinations; packages:platformsin 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 1or--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) orxcodebuild-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 testorxcodebuild 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_FAMILYcontains 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.
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 ID | Severity | Meaning |
|---|---|---|
codeguard-build-execution-denied | error | Build/test not authorized (no trust ticket or no CI attestation). Results in exit 6. |
codeguard-tool-output-unparseable | error | Tool output cannot be read reliably. Results in exit 4, never in a pass. |
codeguard-dependency-unavailable | error/warning | Tool missing, or SwiftPM could not fetch remote dependencies (offline) |
codeguard-workspace-copy-failed | error | The project copy for Xcode exceeded a fixed limit (exit 4) |
codeguard-content-scan-incomplete | warning | A file could not be evaluated reliably (size, encoding, lexer, time budget, unparsable JSON/Plist …). With checks.security.required: true, exit 3. |
codeguard-custom-rule-unsupported | warning | Custom rule with engine dependency or architecture |
codeguard-tool-configuration-rejected | warning | Project .swift-format or .swiftlint.yml rejected |
codeguard-project-config-ignored | warning | --no-project-config although a .codeguard.yml exists |
codeguard-platform-check-incomplete | warning | Platform check could not evaluate a target reliably |
codeguard-platform-unavailable | warning | Detected platform without a local SDK skipped |
codeguard-platform-not-built | info | Platform in the manifest that CodeGuard does not build (e.g. maccatalyst, linux) |
codeguard-platform-resolution-failed | error | Platform resolution ends the run before the first build |
codeguard-simulator-unavailable | warning | No matching runtime/device type or no simctl |
codeguard-simulator-selection-failed | error | Forced destination cannot be satisfied or no test destination is runnable |
codeguard-simulator-failed | error | A simctl step failed (exit 4/5) |
codeguard-simulator-cleanup-failed | warning | A simulator could not be cleaned up. Does not change the exit code. |
codeguard-simulator-sweep-incomplete | warning | Orphaned devices could not be cleaned up completely |