SHEET 01 / WORKFLOW GRAPH
Daggler parses GitHub Actions workflows into a typed graph, validates them in five layers and scores their security. Below is an example release workflow with common mistakes, and what daggler lint actually reported on it, pinned to the job where it was found.
| No. | Code | Where | Finding |
|---|---|---|---|
| 1 | POL007 | lint · 14:9 | Action ref uses a branch (error) |
| 2 | POL008 | test · 21:9 | Shell injection from untrusted input (error) |
| 3 | POL002 | build · 29:9 | Third-party action not SHA-pinned (error) |
| 4 | POL003 | publish · 54:5 | Privileged token on an untrusted event (error) |
| 5 | POL004 | publish · 59:9 | Secret reachable from untrusted event (error) |
| 6 | POL004 | deploy · 68:9 | Secret reachable from untrusted event (error) |
| 7 | POL001 | workflow · permissions | No top-level permissions (warning) |
| 8 | POL006 | publish · 51:3 | Deploy without environment gate (warning) |
| 9 | POL009 | publish · 51:3 | OIDC permission without a cloud step (warning) |
| 10 | POL006 | deploy · 63:3 | Deploy without environment gate (warning) |
n error n warning job with findings
SHEET 02 / REPORT
This is the unedited output of npx daggler-cli@0.1.2 lint --no-color on the workflow above (examples/insecure-release.yml in the repo). Every line is addressed: file, line, column, and what to do about it. The exit code is 1 whenever there is an error, so it can gate CI.
──────────────────────────────────────────────────────────────────────── examples/insecure-release.yml Security F 0/100 6 errors · 4 warnings · 0 info ⚑ Unpinned third-party actions ⚑ Untrusted input reaches privileged context ⚑ Shell injection risk ● POL007 examples/insecure-release.yml:14:9 Action ref uses a branch └─ 'actions/checkout@main' tracks a moving branch — each run may execute different code. Pin to a release tag or a full commit SHA. ● POL008 examples/insecure-release.yml:21:9 Shell injection from untrusted input └─ Untrusted 'github.event.pull_request.title' is interpolated into a shell script — an attacker can inject commands. Pass it via an env var and quote it instead. ● POL002 examples/insecure-release.yml:29:9 Third-party action not SHA-pinned └─ 'docker/build-push-action@v5' is pinned to a mutable tag — an upstream push can silently change what runs. Pin to a full commit SHA instead. ● POL003 examples/insecure-release.yml:54:5 Privileged token on an untrusted event └─ Job 'publish' runs on pull_request_target (an untrusted event) and grants contents permission — attacker-controlled code can exfiltrate secrets or modify the repository. ● POL004 examples/insecure-release.yml:59:9 Secret reachable from untrusted event └─ Secret 'NPM_TOKEN' is exposed to a workflow that runs on an untrusted event and may execute attacker-controlled code. ● POL004 examples/insecure-release.yml:68:9 Secret reachable from untrusted event └─ Secret 'DEPLOY_KEY' is exposed to a workflow that runs on an untrusted event and may execute attacker-controlled code. ▲ POL001 examples/insecure-release.yml:workflow:permissions No top-level permissions └─ No top-level 'permissions:' block — the workflow inherits broad default token scopes; add an explicit least-privilege block. ▲ POL006 examples/insecure-release.yml:51:3 Deploy without environment gate └─ Deploy-like job 'publish' has no `environment:` protection gate — add one to require reviewer approval before deploying. ▲ POL009 examples/insecure-release.yml:51:3 OIDC permission without a cloud step └─ Job 'publish' grants id-token:write but none of its steps use a recognized cloud-auth (OIDC) action — remove the permission or add the appropriate cloud-auth action. ▲ POL006 examples/insecure-release.yml:63:3 Deploy without environment gate └─ Deploy-like job 'deploy' has no `environment:` protection gate — add one to require reviewer approval before deploying. ════════════════════════════════════════════════════════════════════════ Summary 6 errors · 4 warnings · 0 info in 1 file · worst security grade F════════════════════════════════════════════════════════════════════════
SHEET 03 / SECTION A-A
Workflow YAML becomes a typed IR with a source map, then a job graph. Each layer runs over the same parse and adds diagnostics; the policy engine sits on top and scores the result from A to F. The whole pipeline is TypeScript and runs in the browser.
syntaxYAML syntax errors and warnings, with line and column.SCHEMA001-005Required fields, empty step lists, missing runs-on, no jobs, no triggers.EXPR001-004Context availability inside ${{ }}: matrix, needs, steps, inputs.SEM001-003, 005needs cycles, dangling references, unreachable jobs, oversized matrices.ACT001-005uses: steps checked against a curated catalog: inputs, outputs, deprecated majors.SHEET 04 / SCHEDULE OF RULES
POL001 to POL010 cover permissions, pinning, untrusted triggers and injection. AGENT001 to AGENT003 cover AI-agent workflows: prompt injection, over-permitted agents and executed model output. Rules are grouped into six packs.
| Code | Severity | Rule | Packs |
|---|---|---|---|
| POL001 | warning | No top-level permissions block | oss-maintainer, enterprise-least-privilege |
| POL002 | error | Third-party action not SHA-pinned | oss-maintainer, release-hardening, enterprise-least-privilege |
| POL003 | error | Privileged token on an untrusted event | oss-maintainer, enterprise-least-privilege |
| POL004 | error | Secret reachable from untrusted event | oss-maintainer |
| POL005 | warning | Broad contents:write on PR workflow | oss-maintainer |
| POL006 | warning | Deploy without environment gate | release-hardening, cloud-deploy |
| POL007 | error | Action ref uses a branch | release-hardening, enterprise-least-privilege |
| POL008 | error | Shell injection from untrusted input | oss-maintainer, enterprise-least-privilege |
| POL009 | warning | OIDC permission without a cloud step | cloud-deploy, enterprise-least-privilege |
| POL010 | warning | Workflow modifies workflow files | enterprise-least-privilege |
| AGENT001 | error | Untrusted input flows into an AI agent | ai-agent-safety |
| AGENT002 | warning | Over-permitted AI agent | ai-agent-safety |
| AGENT003 | error | Agent output executed | ai-agent-safety |
SHEET 05 / DETAIL VIEWS
Captures of the real editor from a checkout, using the real engine and bundled sample workflows. The hosted editor runs the same engine in your browser: graph, diagnostics, event simulation and policy checks. Running workflows needs the CLI or a local build.



SHEET 06 / GENERAL NOTES
Run the CLI once, Node 20 or newer. With no arguments lint scans .github/workflows/.
npx daggler-cli lint
Or install it. The package is daggler-cli; the binary is daggler.
npm install -g daggler-cli daggler lint
Open the hosted editor, no install: alliecatowo.github.io/daggler/app/editor. Or build the full editor from source (Node 20+, pnpm 10+).
git clone https://github.com/alliecatowo/daggler cd daggler && pnpm install pnpm --filter @daggler/web dev # http://localhost:3737