Organization policy
The organization policy defines what a project must not relax: trusted tool directories, locked values, mandatory rules, minimum severities and the conditions for builds in CI. It applies to every run on the machine.
Location and permissions
The policy is read only from this fixed path:
/Library/Application Support/CodeGuard/policy.yml
There is no parameter and no environment variable to change the path. --ci loads it just like a local run. It applies to config validate, doctor, trust … and all check-* commands.
- File missing: The built-in defaults apply.
config validateshowspolicy: built-in (sha256:…). - File present: It must be a regular file (not a symlink), owned by
root, must be neither group- nor world-writable and must not have an ACL entry that allows writing, deleting or changing permissions. The same applies to every directory from/to/Library/Application Support/CodeGuard.
Any violation, an unreadable file, invalid YAML, a wrong policy_version, an unknown field or an unsafe entry in trusted_roots ends every command with exit 2 before any check starts. There is no fallback to the defaults.
Installation:
sudo mkdir -p "/Library/Application Support/CodeGuard"
sudo install -o root -g wheel -m 0644 policy.yml "/Library/Application Support/CodeGuard/policy.yml"
codeguard config validate
codeguard doctor
doctor --format json shows the source as "policySource": {"hash": "sha256:…", "kind": "file", "path": "…"} or "kind": "built-in".
Every change to the policy changes the policy_hash. All existing trust tickets no longer match afterwards. Developers have to run codeguard trust grant once again.
Structure
policy_version: 1
configuration_versions: [1]
enforcement:
tools:
trusted_roots: [...]
allow_project_tool_paths: false
locked_values: {...}
mandatory_rules: [...]
severity_floors: {...}
non_waivable_rules: [...]
mandatory_protected_files: [...]
metrics: {...}
limits: {...}
execution_environment:
declaration_path: ...
require_for_untrusted_builds: true
Fields you omit get their default values. A policy that sets only enforcement.tools.trusted_roots is valid.
All keys
| Key | Default | Effect |
|---|---|---|
policy_version | 1 | Required, only 1 |
configuration_versions | [1] | Allowed configuration versions |
enforcement.tools.trusted_roots | /usr/bin, /usr/local/bin, /opt/homebrew/bin, /Applications/Xcode.app/Contents/Developer/usr/bin | Directories from which tools may be launched. Replaces the default list completely. |
enforcement.tools.allow_project_tool_paths | false | true allows the project to set tools.* (the path must still be under trusted_roots) |
enforcement.locked_values | {} | Locked values, see below |
enforcement.mandatory_rules | [] | Rules that are always on and cannot be disabled by the project |
enforcement.severity_floors | {} | Minimum severity per rule ID |
enforcement.non_waivable_rules | [] | Rules for which waivers have no effect |
enforcement.mandatory_protected_files | [] | Globs that are always added to protected_files |
enforcement.metrics | empty | Metrics baseline (same shape as rules.metrics) that the project may only tighten |
enforcement.limits.max_waiver_days | 90 | Maximum duration of a waiver (at most 7 for critical) |
enforcement.execution_environment.declaration_path | /Library/Application Support/CodeGuard/execution-environment.json | Path of the CI attestation |
enforcement.execution_environment.require_for_untrusted_builds | true | Must stay true, otherwise builds under --ci are impossible |
enforcement.limits.max_iterations, max_fix_files, max_fix_changed_lines, max_approval_ttl | 2, 5, 100, 5m | Limit autofix and approval values, which have no effect in 0.3.5 |
enforcement.network.project_may_enable, enforcement.ci.project_may_enable_autofix | false | No effect on the run in 0.3.5 |
Trusted directories
enforcement:
tools:
trusted_roots:
- /usr/bin
- /usr/local/bin
- /Applications/Xcode.app/Contents/Developer/usr/bin
- /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
- Every entry must be a safe absolute path (no
//, no.., no$, no backslash, no backtick). - Every directory must be owned by
rootand must not be writable by anyone else. CodeGuard does not check this itself. - No Homebrew paths (
/opt/homebrew/bin,/opt/homebrew/Cellar/…). Put a root-owned copy of SwiftLint in/usr/local/bin(see Getting started). - A tool must not be reachable through two entries (e.g. a symlink in one, the target in the other). This counts as ambiguous, and
doctorshowsunavailable. - Without the
XcodeDefault.xctoolchainentry, CodeGuard does not findswift-formatandswiftof a standard Xcode installation.
Locked values
locked_values pins a configuration value. The key is the dotted path, the value is the fixed value. A conflicting project value results in exit 2 (configuration value conflicts with a locked organization value).
This lets you achieve everything the project level is not allowed to do:
enforcement:
locked_values:
# Disable SwiftLint organization-wide
checks.swiftlint.enabled: false
checks.swiftlint.required: false
# Make tests optional (e.g. for platforms without a simulator runtime)
checks.tests.required: false
# Disable simulator tests organization-wide
checks.tests.simulator: false
# Content scan: incomplete files only as a warning
checks.security.required: false
# Enable the network destination rule
security.network.allowed_destinations:
- "https://api.example.org"
- "https://*.cdn.example.org:8443"
# Timeouts
execution.timeouts.preflight: 30s
execution.timeouts.compile: 40m
execution.timeouts.simulator_boot: 5m
# Fix the platforms
project.platforms: [macos, ios]
Rules for lock paths:
- Only canonical dotted paths (e.g.
checks.tests.required), no/. - Under
rules.metrics.*, you can lock individual thresholds and switches, e.g.rules.metrics.cyclomatic_complexity.warning: 10. - You cannot lock an entire platform rule as an object (
rules.ats-arbitrary-loads), only individual fields (rules.ats-arbitrary-loads.severity). - With a locked
security.network.allowed_destinations,doctorshows the capabilityunauthorized-network-destination: active.
Locked values that disable checks apply to every project on the machine. Check whether a project-level solution is enough, such as scopes: [] for compile/tests.
Mandatory rules, severity floors, non-waivable rules
enforcement:
mandatory_rules:
- hardcoded-secret
- sensitive-data-in-log
- codeguard-metric-line-length
severity_floors:
hardcoded-secret: critical
forbidden-force-try: critical
codeguard-metric-cyclomatic-complexity: critical
non_waivable_rules:
- hardcoded-secret
- codeguard-metric-line-length
mandatory_rules: The project cannot disable these rules (exit 2). For metric rules, use thecodeguard-metric-*IDs here. If a mandatory metric rule has no threshold after merging, the configuration is invalid (exit 2), because there are no default thresholds.severity_floors: Raises the severity at runtime for every finding with this rule ID. The project must not set a lower severity (exit 2). Run diagnostics (codeguard-platform-*,codeguard-simulator-*,codeguard-content-*,codeguard-custom-rule-*,codeguard-tool-configuration-*,codeguard-project-config-*) are exempt.non_waivable_rules: Waivers for these rules have no effect.
For content rules in check-diff, the scope_relation still decides after raising: a finding in unchanged code (pre_existing) blocks only at critical. A floor of error therefore does not make legacy code blocking.
Metrics baseline
enforcement:
metrics:
severity: error
line_length: { warning: 120, ignores_urls: true }
nesting: { function_level: { warning: 3 } }
locked_values:
rules.metrics.cyclomatic_complexity.warning: 10
mandatory_rules:
- codeguard-metric-line-length
This means for projects:
rules.metrics.line_length.warningmay be 120 or lower, not higher.line_length.erroris free, because the baseline does not set it.ignores_urlsmay be set tofalse, not back totrue.severitymay only be raised (error→critical).cyclomatic_complexity.warningmust be exactly10.- A project that writes a
line_lengthobject needs its own threshold in it, such asline_length: { warning: 120, ignores_urls: false }.
If the project sets only one threshold of a pair and the baseline sets the other, so that warning > error would result (baseline warning: 120, project error: 100), warning is lowered to the error value.
Making metrics hard to bypass
Thresholds alone do not enforce a metrics run. A project could narrow the scopes of swiftlint, exclude files or waive findings. To enforce metrics, also lock:
enforcement:
locked_values:
checks.swiftlint.enabled: true
checks.swiftlint.required: true
checks.swiftlint.scopes: [file, diff, project]
non_waivable_rules:
- codeguard-metric-line-length
- codeguard-metric-cyclomatic-complexity
If paths.exclude must not be extended either, lock the whole list. A project then can no longer add its own excludes.
CI attestation
Under --ci, local trust tickets do not apply. Builds and tests start only if all conditions are met:
-
An organization policy is installed and differs from the built-in defaults. (A policy that is byte-for-byte identical to the defaults counts as not present.)
-
enforcement.execution_environment.require_for_untrusted_buildsistrue(default). Withfalse, builds under--ciare impossible. -
Every directory above the attestation file is a real directory (not a symlink), is owned by
root(or the organization owner) and is not group- or world-writable. -
The file at
declaration_pathis a regular file (not a symlink), is owned byroot, has no group/world write permissions and no special bits. -
The content is exactly this document, line by line, UTF-8, at most 4 KiB; a trailing newline is allowed:
version: 1runner_class: ephemeralephemeral: truefilesystem_isolated: truenetwork_isolated: true
The default path ends in .json, but the content is still the YAML document above. To avoid this, set declaration_path to a .yml path.
With this file, the operator declares that the runner is ephemeral and isolated from the file system and the network. CodeGuard does not verify this, but relies on it to run project code without human approval. Only place the file on runners where this is actually true.
No environment variable can replace or disable the attestation. The report then shows run.build_authorization: "operator_attested". Setting it up in the runner image is described in CI with GitHub Actions and CI with GitLab.
Complete example
policy_version: 1
configuration_versions: [1]
enforcement:
tools:
trusted_roots:
- /usr/bin
- /usr/local/bin
- /Applications/Xcode.app/Contents/Developer/usr/bin
- /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
mandatory_rules:
- hardcoded-secret
- sensitive-data-in-log
severity_floors:
hardcoded-secret: critical
sensitive-data-in-log: critical
non_waivable_rules:
- hardcoded-secret
mandatory_protected_files:
- .codeguard.yml
- .gitlab-ci.yml
- .github/workflows/**
- "**/*.entitlements"
metrics:
line_length: { warning: 120, error: 200 }
limits:
max_waiver_days: 30
execution_environment:
declaration_path: /Library/Application Support/CodeGuard/execution-environment.yml
require_for_untrusted_builds: true
The policy examples on this page follow the structure from the CodeGuard repository (docs/help/policy.example.yml and test fixtures). Because the policy is loaded only from the fixed system path, they were not individually installed and run for this help. Always check a new policy with codeguard config validate after installing it.