Actions: Add new models for external actions/workflows - #22263
Conversation
There was a problem hiding this comment.
Pull request overview
Adds version-aware modeling and interprocedural analysis for downloaded external reusable workflows and composite actions.
Changes:
- Adds external workflow/action extension predicates and call resolution.
- Moves downloaded files under a collision-resistant UUID directory.
- Adds documentation, models, and regression tests.
Show a summary per file
| File | Description |
|---|---|
docs/codeql/codeql-language-guides/customizing-library-models-for-actions.rst |
Documents new predicates. |
actions/ql/test/query-tests/Security/CWE-829/UntrustedCheckoutCritical.expected |
Updates expected external-workflow paths. |
actions/ql/test/query-tests/Security/CWE-829/9466014afba34ef28239871ceabf4132/TestOrg/TestRepo/ref-07cp61dt7qne3/.github/workflows/reusable.yml |
Adds reusable-workflow fixture. |
actions/ql/test/query-tests/Security/CWE-829/9466014afba34ef28239871ceabf4132/TestOrg/TestRepo/ref-07cp61dt7qne3/.github/workflows/formal.yml |
Adds external workflow fixture. |
actions/ql/test/query-tests/Security/CWE-829/9466014afba34ef28239871ceabf4132/TestOrg/TestRepo/ref-07cp61dt7qne3/.github/workflows/build.yml |
Adds checkout fixture. |
actions/ql/test/query-tests/Security/CWE-829/9466014afba34ef28239871ceabf4132/TestOrg/TestRepo/ref-07cp61dt7qne3/.github/workflows/build_nested.yml |
Adds nested workflow fixture. |
actions/ql/test/query-tests/Security/CWE-829/9466014afba34ef28239871ceabf4132/TestOrg/TestRepo/ref-07cp61dt7qne3/.github/workflows/build_nested_branching.yml |
Adds branching workflow fixture. |
actions/ql/test/query-tests/Security/CWE-094/CodeInjectionMedium.expected |
Updates medium-query results. |
actions/ql/test/query-tests/Security/CWE-094/CodeInjectionCritical.expected |
Updates critical-query results. |
actions/ql/test/query-tests/Security/CWE-094/9466014afba34ef28239871ceabf4132/ultralytics/actions/ref-07cp61dt7qne3/action.yaml |
Adds external action fixture. |
actions/ql/test/query-tests/Security/CWE-094/9466014afba34ef28239871ceabf4132/TestOrg/TestRepo/ref-3u61h4t2rfnaf/.github/workflows/publishResults.yml |
Adds code-injection fixture. |
actions/ql/test/query-tests/Security/CWE-094/9466014afba34ef28239871ceabf4132/TestOrg/TestRepo/ref-07cp61dt7qne3/.github/workflows/reusable-workflow.yml |
Adds reusable-workflow fixture. |
actions/ql/test/query-tests/Security/CWE-094/9466014afba34ef28239871ceabf4132/TestOrg/TestRepo/ref-07cp61dt7qne3/.github/actions/clone-repo/action.yaml |
Adds composite-action fixture. |
actions/ql/test/qlpack.yml |
Loads external models in tests. |
actions/ql/test/library-tests/external-reusable-workflows/external_reusable_workflows.ql |
Tests workflow call resolution. |
actions/ql/test/library-tests/external-reusable-workflows/external_reusable_workflows.expected |
Records workflow resolution results. |
actions/ql/test/library-tests/external-reusable-workflows/9466014afba34ef28239871ceabf4132/TestOrg/TestRepo/ref-3tf7yq1278i0t/.github/workflows/second.yml |
Adds v2 callee fixture. |
actions/ql/test/library-tests/external-reusable-workflows/9466014afba34ef28239871ceabf4132/TestOrg/TestRepo/ref-3tf7yq1278i0t/.github/workflows/first.yml |
Adds v2 nested caller fixture. |
actions/ql/test/library-tests/external-reusable-workflows/9466014afba34ef28239871ceabf4132/TestOrg/TestRepo/ref-0wu7t20oora8i/.github/workflows/second.yml |
Adds v1 callee fixture. |
actions/ql/test/library-tests/external-reusable-workflows/9466014afba34ef28239871ceabf4132/TestOrg/TestRepo/ref-0wu7t20oora8i/.github/workflows/first.yml |
Adds v1 nested caller fixture. |
actions/ql/test/library-tests/external-reusable-workflows/9466014afba34ef28239871ceabf4132/LegacyOrg/LegacyRepo/ref-07cp61dt7qne3/.github/workflows/legacy.yml |
Adds unmodeled workflow fixture. |
actions/ql/test/library-tests/external-reusable-workflows/.github/workflows/unmodeled_external_reusable_workflow.yml |
Tests unmodeled calls. |
actions/ql/test/library-tests/external-reusable-workflows/.github/workflows/external_reusable_workflow.yml |
Tests versioned calls. |
actions/ql/test/library-tests/external-composite-actions/external_composite_actions.ql |
Tests action call resolution. |
actions/ql/test/library-tests/external-composite-actions/external_composite_actions.expected |
Records action resolution results. |
actions/ql/test/library-tests/external-composite-actions/9466014afba34ef28239871ceabf4132/UnmodeledOrg/Actions/ref-0wu7t20oora8i/remote/action.yml |
Adds unmodeled action fixture. |
actions/ql/test/library-tests/external-composite-actions/9466014afba34ef28239871ceabf4132/TestOrg/Actions/ref-3tf7yq1278i0t/remote/action.yml |
Adds v2 external action. |
actions/ql/test/library-tests/external-composite-actions/9466014afba34ef28239871ceabf4132/TestOrg/Actions/ref-0wu7t20oora8i/remote/action.yml |
Adds v1 external action. |
actions/ql/test/library-tests/external-composite-actions/.github/workflows/caller.yml |
Adds action caller workflow. |
actions/ql/test/library-tests/external-composite-actions/.github/actions/root-v2/action.yml |
Adds local v2 root action. |
actions/ql/test/library-tests/external-composite-actions/.github/actions/root-v1/action.yml |
Adds local v1 root action. |
actions/ql/test/library-tests/external-composite-actions/.github/actions/leaf/action.yml |
Adds local leaf action. |
actions/ql/test/external-reusable-workflows.model.yml |
Defines test download models. |
actions/ql/src/change-notes/2026-07-31-external-workflow-models.md |
Adds analysis change note. |
actions/ql/lib/ext/config/workflow_runtime_data.yml |
Registers extension schemas. |
actions/ql/lib/codeql/actions/security/CodeInjectionQuery.qll |
Uses resolved workflow callers. |
actions/ql/lib/codeql/actions/Helper.qll |
Removes legacy path exclusions. |
actions/ql/lib/codeql/actions/dataflow/internal/DataFlowPrivate.qll |
Uses qualified callable names. |
actions/ql/lib/codeql/actions/config/ConfigExtensions.qll |
Declares new predicates. |
actions/ql/lib/codeql/actions/config/Config.qll |
Exposes extension data. |
actions/ql/lib/codeql/actions/ast/internal/Ast.qll |
Resolves modeled external calls. |
actions/extractor/tools/baseline-config.json |
Updates baseline extraction paths. |
actions/extractor/tools/autobuild.sh |
Updates Unix path filters. |
actions/extractor/tools/autobuild-impl.ps1 |
Updates Windows path filters. |
Review details
- Files reviewed: 35/44 changed files
- Comments generated: 2
- Review effort level: Balanced
Resolve $/ paths in downloaded reusable workflows and composite actions against their modeled repository, and cover both forms while retaining ./ compatibility.
Preserve both Actions test data extensions and regenerate the conflicting CWE-094 and CWE-829 expected results.
There was a problem hiding this comment.
CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
Rename model-binding predicates that do not return values and replace omittable exists variables with don't-care expressions.
Add QLDoc for external workflow and composite-action model bindings, classifications, and callable-name resolution.
| include:.github/workflows/*.yaml | ||
| include:.github/reusable_workflows/**/*.yml | ||
| include:.github/reusable_workflows/**/*.yaml | ||
| include:9466014afba34ef28239871ceabf4132/**/*.yml |
There was a problem hiding this comment.
This magic number approach does not sound right to me. But I lack sufficient contextual knowledge here to suggest anything.
There was a problem hiding this comment.
It is just a GUID without dashes. On one hand, especially because it becomes visible in web ui alert, it could be something human readable. On the other hand something very short such as reusable_workflow may collide with a folder in the repository. So something like downloaded_external_workflows_and_actions may work. I went with a unique GUID, but I'm open for discussions.
| externalCompositeActionDataModel(owner, repo, action_path, requested_ref, resolved_commit_sha, | ||
| local_path) and | ||
| local_path.trim() = this.getLocation().getFile().getRelativePath() |
There was a problem hiding this comment.
Quick check: for tuples in externalCompositeActionDataModel is it true that there is at most one such tuple for each local_path? I.e. does the local_path uniquely determine the rest of the values? Because otherwise this looks like a massive performance pitfall, since this joins with any Locationwith a matchinggetFile`.
There was a problem hiding this comment.
local_path is not unique: the producer intentionally lets requested refs with identical content share one materialized file while retaining separate provenance rows.
However, I asked AI to check the generated evaluator plan. The current predicate scans the model rows first and then uses keyed joins through relative path, file, location, and the action AST; it does not start by enumerating every Location. I also tested a path-bound bindingset/inline_late helper, but that changed the plan to an AST-to-model Cartesian product, increased joined tuples, and showed no runtime improvement.
| externalReusableWorkflowDataModel(owner, repo, workflow_path, requested_ref, | ||
| resolved_commit_sha, local_path) and | ||
| local_path.trim() = this.getLocation().getFile().getRelativePath() |
There was a problem hiding this comment.
Same question applies here as the pattern is the same.
There was a problem hiding this comment.
I tested this on the Grafana Actions database. All eight reusable-workflow model rows resolved to extracted workflow files, with each local_path matching the AST file’s relative path. The current plan scans externalReusableWorkflowDataModel first and uses keyed joins through file, location, and ReusableWorkflowImpl.
|
I've commented a bit, but this really needs another set of 👀 . Also, several constructions have the looks of something that might have poor join-orders, so do check performance thoroughly. |
Previously it wasn't using extension predicates, just looking for downloaded workflow files in
.github/workflows/external/. It is used only internally, so imho the accidental rename in #18563 is safe to be edited.I used a random unique name
9466014afba34ef28239871ceabf4132where the downloaded external workflows/actions are stored, but I'm open for discussion. The name is visible in alerts, but using something asreusable_workflowsmay potentially run into the naming conflict.