workflow-security/

directory
v0.90.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 5, 2026 License: MIT

README


title: Compiled workflow security model description: Executable, bounded TLA+ specification of compiled workflow trust boundaries and git authorization.

Compiled workflow security model

CompiledWorkflow.tla models a compiled agentic workflow as interacting principals, ordered steps, classified data, artifact handoffs, scoped credentials, and authorized effects. TLC explores hostile inputs, detector verdicts, missing git data, and failure schedules. Negative controls remove individual protections to check that the corresponding invariant can actually detect a violation.

This is a bounded, policy-parameterized reference model, not a proof that every compiled workflow refines it. The compiled-profile verifier checks a narrow structural connection to real YAML. It does not verify arbitrary scripts, external services, or all compiler modes. Its Boolean guard analysis is conservative, not a complete GitHub Actions expression evaluator. The initial architecture review found no confirmed exploit; the subsequent corpus trial exposed fail-open credential cleanup, now corrected with blocking cleanup and independent postcondition verification. Synthetic counterexamples must not be filed as product vulnerabilities.

State and execution

The workflow has activation, agent, detection, and safe-output jobs. Each has a status, dependencies, effective grants, and a step cursor. Start, Finish, Fail, and Skip represent job lifecycle. Host checkout/setup steps are not the sandboxed agent principal, even when GitHub Actions runs both in the same job.

Surface State / transitions Meaning
Configuration context, Activate, TrustedConfiguration Trusted activation instructions; PR-base provenance is an implementation obligation.
Untrusted data AgentRequest, requestValid, target, request.private Declarative operation, schema validity, repository selection, and private-source classification remain separate from artifact origin.
Jobs and steps status, step, started, safeOutputResult, appTokenPost, Dependencies Ordered host setup, agent execution, detection, validation, credential minting, effects, and the app-token action post step.
Artifacts artifacts, Consume, GoodOrigin Existence, producing job, run and invocation identity, naming prefix, instruction trust, secret/private labels. Current-run agent artifacts are still untrusted payloads.
Outputs and logs transfers Artifact, output, and log channels carry independently tracked secret labels. This abstracts covered redaction, not arbitrary encoded-secret detection.
Permissions grants, TokenPermissions, ValidatedEffects Repository read, issue write, content write, and inference capabilities are distinct. Workspace edits are not repository-resource writes.
Apps and secrets live, revoked, appScope, appRepos, agentSecrets Read-only checkout credentials, authorized engine credentials, and privileged installation tokens have different consumers and lifetimes.
Networking ToolCall, egress, NetworkPolicy Authorized GitHub/inference services, blocked destinations, and service-bound engine authentication.
Git checkouts, cleanup, agentBegan, privilegedCheckout, operations, errors Per-repository auth, temporary setup credentials, verified cleanup/failure, available blobs/refs, sparse/shallow state, local operations, REST, and privileged push.

The issue effect creates at most one validated issue in the main public repository. The pull-request effect prepares a full checkout in the privileged job and pushes a local change with the scoped installation token. Both reject private-source content at the public sink under this strict profile.

Detection may run after agent failure, and valid already-emitted requests may still be processed after successful detection. This follows the emitted needs.agent.result != 'skipped' policy rather than assuming all upstream jobs must succeed. Failure before request publication causes origin validation to fail. Agent failures revoke the modeled engine credential. A minted installation token remains live until the actions/create-github-app-token post step, which revokes it after either safe-output success or failure.

DetectionPolicy independently declares required, disabled, or conditional detection. DetectionEnabled selects a conditional run. Required detection cannot be bypassed merely by omitting the job. Disabled/conditionally skipped configurations retain authorization, provenance, credential, and validation invariants but do not claim detector approval. The inactive detector is represented by a skipped state-machine slot, not a real runtime job.

Evidence and invariants

Reviewed baseline: 542e937dd8447172c8c484cda3a9a5716ae99245. The authorities are Security Architecture v1.1.0, Compiler Threat Detection v1.0.42, Checkout Behavior, and the Safe Outputs specification. Line references describe that baseline; symbols are the more durable mapping. The checkout specification's 1.3.1 amendment requires verified cleanup before agent execution, including the failure paths found by the corpus trial.

Predicate Required safety condition Architecture / threat rule Compiler/runtime evidence
TypeOK Every modeled job, token, checkout, channel, and request has a valid shape. Compiler typed configuration and runtime request contracts WorkflowData, Job, safe_output_validator.
JobIsolation Agent has no repository-write grants; generated prerequisites cannot be bypassed. A:389–398 OI-01/02; CTR-001/005 validateDangerousPermissions:23–77; guardIfAgainstStatusFuncBypass:391–423.
NoCredentialPersistence No retained checkout credential is accessible once the untrusted agent begins. Trusted force-clean setup may temporarily retain credentials. K:192–205, T-CHK-018; AR1 generateCheckoutCredentialsCleanupStep; verify_git_credentials.sh.
ArtifactProvenance Accepted requests come from this run and this invocation's agent artifact. AR2; OI-01/02 generateUnifiedArtifactUpload:31–61; buildSafeOutputsDownloadSteps:246 onward. Payload validation remains necessary.
DetectionGate When declared policy requires a detector for this run, modeled approval precedes every effect. A:755–759,793–797; WTD1–3 buildSafeOutputsJobCondition:859–881; processMessages:877–963. Structural checks prove job-result gating, not threat-classification correctness.
ValidatedEffects Only configured, revalidated, authorized safe-output effects can mutate the target. OI-06/07/11; CTR-005/012/015 resolveAndValidateRepo:162–204; processSafeOutput.
AppLeastPrivilege Installation repositories and permissions are explicit and within this profile's grant. A:428–440,666–674; K:157–190 buildGitHubAppTokenMintStepWithMeta:427–461; validateAppTokenPermissions:41–100.
PrivilegedCheckoutIsolation Persisted push credentials exist only in a live privileged processor context. K:175–205; AR1–4 buildSharedPRCheckoutSteps:32–100; GenerateConfigureGitCredentialsSteps:259–382.
SecretConfinement Write credentials cannot reach the agent or any modeled publication channel; authorized inference credentials remain permitted. CTR-017; AR4; MCP scripts SN-SCOPE ComputeAWFExcludeEnvVarNames:84–182; classifyStepSecrets:27–190; StepOrderTracker:94–189.
TrustedExecution Request bytes remain data, not privileged executable code. SG-01; CTR-006/009/010 template_injection_validation; safe_output_handler_manager.
GitAuthorization Authenticated remote operations use credentials scoped to that repository and operation. K:122–130,157–205 resolveCheckoutTokenExpression:733–751; resolvePRCheckoutToken:131–191.
NoImplicitFetch Credential-free reasoning never silently fetches or pushes to compensate for missing local objects. K:212–229; checkout credential policy generateFetchStepLines:680–730; safe_outputs_push_to_pr_branch.md.
TokenLifetime Revoked tokens cannot become live again; completed successful or failed jobs retain no modeled engine or app token. Job-scoped credentials, AR1–4 Models the actions/create-github-app-token post step revoking a minted token after success or failure; skip-token-revoke disables this cleanup.
NetworkPolicy Egress follows the effective allowlist and engine auth goes only to inference. NI-01–14; CTR-011 GetAllowedDomains / GetBlockedDomains; appendEnvAndMountArgs:482–499. External firewall enforcement is assumed.
TrustedConfiguration Activation instruction authority is not inherited from attacker-controlled configuration. CTR-028/030 activationCheckoutRef:494–501; restore_base_github_folders.sh:40–83.
OutputLimit The declared maximum of one effect is not exceeded. OI validation / configured operation bounds safe_output_validator, operation-specific safe-output handlers.
PrivateSinkPolicy Private-source content is not published to this public target without an explicit policy grant. CTR-031; PPF1–4 validatePrivateToPublicFlowsPolicy:15–38; external GitHub gateway/proxy source/sink policies.

SecretConfinement, TrustedExecution, and NetworkPolicy express desired boundary obligations, not a proof of semantic prompt-injection resistance, complete secret redaction, or noninterference. Sanitization preserves a data channel; it does not transform data into trusted instructions.

Git semantics

Checkout(r) uses that entry's token during trusted setup. CheckoutMode selects transient checkout or explicit force-clean persistence. CleanCheckout either removes and verifies credentials or fails the agent job before reasoning; CheckoutComplete cannot start the engine while credentials remain. The cleanup-fail-open mutation reproduces the historical ignored-error path. main is sparse and shallow in the sparse profile; private_dependency is full. Sparse patterns, available blobs, available refs, and depth are independent fields, even though the supplied small profiles choose them together. Configured fetch: is part of authenticated setup.

diff uses local state. show-base requires a local base ref; read-blob requires a local object. A missing object/ref, credential-free fetch/push, or unauthorized cross-repository gh-read records an explicit error. It never widens/deepens the repository, consults a credential helper, or invents success. The gh-read transition is a separately authorized REST tool with command-env auth; git configuration does not authorize it.

Safe-output MCP patch/bundle preparation belongs to the credential-free principal. A privileged pull-request effect uses another checkout and may legitimately retain a push token until cleanup. Therefore the invariant is not “every checkout always has persist-credentials: false.”

The model abstracts refs to HEAD and base, paths to two locally available objects, and repositories to two identities. It does not prove Git ref/path parsing, symlink confinement, .git indirection, submodules, object graph ancestry, merge correctness, signed pushes, or concurrent checkout discovery. These require separate refinement of gitutil and findRepoCheckout.

Run the machine

Use Java 21, Python 3 (standard library only), the repository's Go toolchain, and official TLA+ Tools v1.8.0. The runner verifies this jar SHA-256 before executing it (the v1.8.0 release asset SHA-256 published by GitHub; the release notes publish SHA-1):

411ab54221cf0c9fa7ae18f07a3e0ebbdf9e5ba6254b79017e7007f1feb44e89
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s specs/workflow-security -p '*_test.py'
TLA2TOOLS_JAR=/path/to/tla2tools.jar JAVA_BIN=/path/to/java \
  python3 specs/workflow-security/check.py --results /tmp/workflow-security-new
go test ./specs/workflow-security/conformance

After make build, add --compiler ./gh-aw to the checker command to require both acceptance of the source seed and rejection of its concrete write-grant mutation. This does not dispatch either workflow.

The results directory must not already exist. Every case includes a self-contained model/config, full TLC log, and saved state data. Secure configurations exhaust the reachable graph, not a depth-constrained prefix. Ten positive configurations cover sparse/full checkout, issue/pull-request effects, force-clean lifecycle, reusable invocation naming, and declared detection modes. Twenty-one deliberately broken protections (including failure-path token retention) and eight reachability witnesses cover authenticated push, temporary host credentials, and a blocking cleanup failure.

Counterexamples include raw tlc-trace.json, normalized trace.json, and events.txt. Negative controls also generate source.md and counterexample.json. Only agent-write directly changes a supported frontmatter grant; the strict compiler must reject it. Other source seeds explicitly describe the hypothetical compiler/runtime mutation needed to realize their trace. They are not source-level exploits. Witnesses deliberately violate NoSuccessfulWrite, NoDeniedRequest, NoMissingGitData, NoCrossRepoCheckout, NoFailedJob, NoCleanupFailure, or NoTemporaryCredentials while retaining all security invariants.

The runner requires TLC exit 0 plus exhaustive-success output for secure runs, or exit 12 plus the exact expected invariant for a negative control/witness. A different safety failure, parse error, deadlock, timeout, missing trace, or wrong jar is a failure. Deadlock checking is disabled because intentional failed/stopped workflows are terminal; no liveness or fairness theorem is made.

Connect source to compiled jobs

The compile-only seed uses fictitious credentials and a fake dependency. Never dispatch it. Compile a copy into a new temporary directory:

make build
mkdir /tmp/workflow-security-source
cp specs/workflow-security/fixtures/cross-repo.md /tmp/workflow-security-source/
./gh-aw compile /tmp/workflow-security-source/cross-repo.md --approve --json
go run ./cmd/gh-aw-security-model --profile seed \
  /tmp/workflow-security-source/cross-repo.lock.yml

The verifier decodes actual YAML and rejects missing job dependencies, repository-write agent permissions, checkout credentials without immediately following fail-closed cleanup and verification, cross-run artifact downloads, missing strict detector gates, unpinned external actions, wrong dependency credentials, excessive app scopes, or disabled token revocation. Its daily profile omits seed-specific token/app assertions while still requiring detection. The compiled profile reads an independent gh-aw-manifest.threat_detection declaration emitted by the compiler. Missing or unknown policy is an error; absent jobs cannot establish an opt-out. Reports include the effective policy so a disabled detector is never presented as equivalent assurance to required detection.

Guard checks use actionlint's AST and conservative Boolean implication. An always() condition is accepted only when compiler-owned success is enforced; OR branches, negation, and status-function calls cannot simply bypass the check. Symbolic artifact names must match across producer/consumer and refer to the trusted activation prefix helper, not arbitrary caller-selected prefixes. The prefix helper hashes inputs and run attempt; identical inputs in the same attempt intentionally share a prefix. Caller-provided invocation uniqueness and hash collision resistance remain assumptions, not guarantees proved here. Manifest provenance, payload validation, redaction coverage, external network enforcement, and token expiry remain obligations, not facts extracted by this structural check.

Validate the entire existing compiled corpus together with TLC:

go build -o /tmp/gh-aw-security-model ./cmd/gh-aw-security-model
TLA2TOOLS_JAR=/path/to/tla2tools.jar JAVA_BIN=/path/to/java \
  python3 specs/workflow-security/check.py --results /tmp/workflow-security-corpus \
  --verifier /tmp/gh-aw-security-model --compiled-workflows .github/workflows

compiled-workflows.json records every lock's hash, policy, outcome, and violations. Any structural or tooling failure fails the runner. Legacy locks without the explicit policy must be recompiled; they are not silently accepted.

examples.json records representative action traces generated by the pinned checker. Regenerate it only from a successful full run:

python3 specs/workflow-security/check.py --results /tmp/workflow-security-new \
  --write-examples

Refinement backlog and architecture exceptions

The architecture review identified contract distinctions, not confirmed exploitable bugs. Preserve them rather than strengthening the model's assumptions until real behavior disappears:

Boundary not fully modeled Evidence / required refinement
Pre-activation actor authorization, bots, replay A:608–660,966–1012; CTR-027/029; model trusted event identity and separate membership/checkout gates.
Compiler expressions and freshness CTR-010/016/018; model expression classification, manifest approvals, frontmatter/body hashes, legacy fallbacks, and status-function augmentation.
Detection warning handling WTD1–3; disabled/conditional modes are modeled, but warning-mode annotated publication, push-to-PR conversion, and abort-only operations still need operation-specific refinement.
Authorized custom steps/tools and augmented app grants CTR-017; agent-job host secrets and explicit tool grants are legitimate. Do not assume all job secrets or permission augmentation are forbidden.
Redaction coverage and exceptions Ordering checks do not guarantee successful redaction of every artifact. Binary/unscanned exceptions and fallback always() uploads need explicit coverage/lifecycle states.
Cache integrity and publication CTR-019; model policy/integrity namespaces, restoration, detection-gated publication, and detection-disabled post-action saving.
Checkout merging, primary target, and directory identity Entry identity is (repository,path,wiki); current does not change cwd. Model token/app alternatives, unioned fetch/sparse patterns, and deepest history selection.
Safe-output token precedence Checkout specification K:175–190 differs from resolvePRCheckoutToken:131–191, which supports operation PAT, checkout safe-output app, shared app/PAT, and defaults. Preserve this as a conformance observation, not a vulnerability claim.
Filesystem and platform enforcement Artifact service origin, immutable event metadata, AWF/gateway isolation, labels, action pin integrity, post-job cleanup, and revocation are environmental assumptions at this layer.

Daily investigation

daily-workflow-security-model.md runs daily with read-only repository permissions and mediated safe outputs. It prepares checksum-pinned Java/TLC and a native compiled-profile verifier, checks the machine, rotates one trust boundary, and proposes validated model refinements through a restricted draft PR.

New credible defects or architectural gaps are deduplicated and filed with the exact label security critical. That label is a triage marker, not proof of severity. Intentional mutations, reachability witnesses, rejected sources, and tooling failures are not reported as confirmed vulnerabilities. New findings must include a minimized trace, benign source, compiled-step mapping, source evidence, assumptions, and reproducible commands; no live attack or workflow dispatch is permitted.

Directories

Path Synopsis
Package conformance checks a narrow compiled-workflow security profile.
Package conformance checks a narrow compiled-workflow security profile.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL