Skip to main content

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 validate shows policy: 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".

Trust tickets expire

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​

KeyDefaultEffect
policy_version1Required, 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/binDirectories from which tools may be launched. Replaces the default list completely.
enforcement.tools.allow_project_tool_pathsfalsetrue 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.metricsemptyMetrics baseline (same shape as rules.metrics) that the project may only tighten
enforcement.limits.max_waiver_days90Maximum duration of a waiver (at most 7 for critical)
enforcement.execution_environment.declaration_path/Library/Application Support/CodeGuard/execution-environment.jsonPath of the CI attestation
enforcement.execution_environment.require_for_untrusted_buildstrueMust stay true, otherwise builds under --ci are impossible
enforcement.limits.max_iterations, max_fix_files, max_fix_changed_lines, max_approval_ttl2, 5, 100, 5mLimit autofix and approval values, which have no effect in 0.3.5
enforcement.network.project_may_enable, enforcement.ci.project_may_enable_autofixfalseNo 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 root and 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 doctor shows unavailable.
  • Without the XcodeDefault.xctoolchain entry, CodeGuard does not find swift-format and swift of 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, doctor shows the capability unauthorized-network-destination: active.
Disabling is the exception

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 the codeguard-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.
Severity floors and legacy code

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.warning may be 120 or lower, not higher. line_length.error is free, because the baseline does not set it.
  • ignores_urls may be set to false, not back to true.
  • severity may only be raised (error → critical).
  • cyclomatic_complexity.warning must be exactly 10.
  • A project that writes a line_length object needs its own threshold in it, such as line_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:

  1. 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.)

  2. enforcement.execution_environment.require_for_untrusted_builds is true (default). With false, builds under --ci are impossible.

  3. 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.

  4. The file at declaration_path is a regular file (not a symlink), is owned by root, has no group/world write permissions and no special bits.

  5. The content is exactly this document, line by line, UTF-8, at most 4 KiB; a trailing newline is allowed:

    version: 1
    runner_class: ephemeral
    ephemeral: true
    filesystem_isolated: true
    network_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.

The attestation is an assurance

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
note

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.

See also​