Skip to main content

Trust and security

compile and tests run code from your project: the package manifest, build tool plugins, build settings and the tests themselves. That is why CodeGuard only starts them with explicit approval. This page explains how this approval works.

Two ways to approve​

ProfileApprovalIn the report
local (without --ci)Trust ticket from codeguard trust grantrun.build_authorization: "local_trust"
CI (--ci)Organization attestation of an ephemeral, isolated environmentrun.build_authorization: "operator_attested"

The two ways are mutually exclusive. Under --ci, a local ticket never applies, and an attestation never applies locally. No environment variable grants or revokes approval.

Without approval, all static checks run, compile and tests end up under Skipped, and the report contains:

1. ERROR
Rule: codeguard-build-execution-denied
Build and test execution was not authorized: project trust is not-trusted; grant trust before running builds or tests.
Grant project trust, or run inside an attested isolated environment.

Without other errors, the run ends with exit 6. If the static checks also produce blocking findings, the exit code is 1 and policy_blocked appears under secondary_failures.

check-file never needs approval because it never builds or tests.

Grant a trust ticket​

codeguard trust grant --duration 8h
  1. CodeGuard shows the execution surfaces (see below) on stderr.
  2. It asks Grant trust for 8h? Type 'yes' to continue:.
  3. Only yes grants the ticket. Anything else ends with exit 6 (project trust was not granted).

A ticket is valid for at most 24 hours. trust grant requires an interactive terminal and is forbidden under --ci (exit 6 in both cases). It automatically binds the .codeguard.yml in the project root directory, the same one a check-* run without parameters loads.

Show the status and revoke the ticket:

codeguard trust status
codeguard trust revoke

What a ticket binds​

A ticket is only valid as long as all of the following remain unchanged:

  • canonical project root
  • current user
  • configuration_hash of the effective configuration
  • policy_hash of the organization policy
  • fingerprints of all execution surfaces in the project
KindFiles (at any directory depth)
manifestPackage.swift, Package@swift-*.swift, Package.resolved
pluginPlugins/*.swift
build-script*.sh, *.command, build.swift
xcode-project*.xcodeproj/project.pbxproj, *.xcworkspace/contents.xcworkspacedata, *.xcconfig, *.xctestplan
scheme*.xcscheme

.git, .build, DerivedData, xcuserdata and *.xcresult are not scanned. So a local swift package resolve or a clean does not invalidate a ticket.

If a bound item changes, trust status reports invalid and builds are refused. Real example after switching the configuration file:

Build and test execution was not authorized: project trust is invalid; grant trust before running builds or tests.

A ticket therefore becomes invalid if

  • you change .codeguard.yml, Package.swift, the Xcode project, a scheme or an .xcconfig,
  • you use --config or --no-project-config differently than when granting,
  • the organization policy changes,
  • CodeGuard is updated and default values change in the process (for example when moving to 0.3.4).

Then simply run codeguard trust grant again.

Keychain​

The trust store is protected with a key from the keychain (service com.codeguard.control-state, account hmac-key-v1). version and schema do not need it, but doctor, trust and check-* do.

Hanging run after a rebuild

If CodeGuard was rebuilt, the binary is signed differently. If the access rule of the existing keychain entry no longer matches, macOS waits indefinitely for confirmation during a run without a graphical session (for example over SSH). To fix this:

  1. Start CodeGuard once in a graphical session and confirm the prompt with "Always Allow", or
  2. delete the entry: security delete-generic-password -s com.codeguard.control-state -a hmac-key-v1. This invalidates all local trust tickets.

Audit log​

CodeGuard writes security-relevant events to a device-local audit log. It cannot be turned off.

EventWhen
trust-grantedafter trust grant
trust-checkedwhen the result of a trust check changes (not on every check)
security-decisionon a critical finding, with rule, file and fingerprint, without the value found
simulator-device-created, simulator-device-deleted, simulator-cleanup-failed, simulator-orphan-removedlifecycle of the disposable simulators

Trust events carry the project configuration mode (auto, explicit, disabled, none) and, if applicable, its sha256, never a path. The log holds at most 4096 entries and does not rotate. A write error changes neither the result nor the exit code. Under --ci, trust status reads neither the keychain nor the trust store and writes no event.

What builds touch on your machine​

  • Xcode builds run on a copy of the project in a private working directory (without .git, .build, DerivedData, xcuserdata, *.xcresult), with their own HOME and TMPDIR inside this directory. The copy is limited to 50,000 entries, 2 GiB and a directory depth of 100. Symlinks pointing outside the project abort the run (exit 6).
  • SwiftPM builds read the project in place and write build and cache directories to the working directory, with an empty environment.
  • SwiftPM still writes its manifest cache to the real user directory (~/Library/Caches/org.swift.swiftpm).
  • CodeGuard passes no signing or team settings (CODE_SIGN…, DEVELOPMENT_TEAM) and no -allowProvisioningUpdates to xcodebuild.
  • The working directory is removed at the end of every run, even on errors and cancellation. On SIGTERM or SIGKILL, it remains in the temp directory (codeguard-run-<run-id>-…).

Disposable simulators​

For tests on iOS, watchOS, tvOS and visionOS:

  1. CodeGuard creates a device codeguard-<run-id>-<family> in the user's default device set. It is visible in the Simulator app during the run.
  2. Before booting, it writes a lease (proof of ownership).
  3. After the test, it shuts the device down, deletes it and removes the lease, even on test failures, timeout and cancellation.
  4. At most one CodeGuard device is booted at a time.

Only what clearly belongs to CodeGuard is cleaned up: CodeGuard never deletes a device without a lease. If a device with a lease is left behind after a hard abort (SIGTERM/SIGKILL), the next run with simulator tests cleans it up. doctor shows such devices under Verwaiste CodeGuard-Geräte and never deletes anything.

Traces that remain after deletion: logs under ~/Library/Logs/CoreSimulator/<UDID>/, CoreSimulator.log, usage keys in com.apple.CoreSimulator.plist, empty directories under ~/Library/Caches/com.apple.dt.Xcode/TestReport/ and entries in DiagnosticReports.

The simulator is not a sandbox

Test code runs in the simulator with your user permissions. The trust ticket or the CI attestation covers this. The organization can turn off simulator tests with checks.tests.simulator: false in locked_values.

What is redacted in the report​

Tool output passes through redaction before it reaches the report. Private paths and detected secrets appear as tokens with a truncated, device-bound HMAC, for example:

Build input file cannot be found: '<redacted:private-path:hmac-sha256:98cea2427df843611deab4a243996ee2cc23605f0f1fd88a0bd93ad6030c83f5>'.

redactions_applied: true in the JSON report shows that redaction took place. Simulator UDIDs and device set paths never appear in the report.

See also​