diff --git a/actions/ql/lib/change-notes/2025-01-07-trusted-owner-ext.md b/actions/ql/lib/change-notes/2025-01-07-trusted-owner-ext.md new file mode 100644 index 000000000000..ecffb9cf1315 --- /dev/null +++ b/actions/ql/lib/change-notes/2025-01-07-trusted-owner-ext.md @@ -0,0 +1,4 @@ +--- +category: feature +--- +* The "Unpinned tag for a non-immutable Action in workflow" query (`actions/unpinned-tag`) now supports expanding the trusted action owner list using data extensions (`extensible: trustedActionsOwnerDataModel`). If you trust an Action publisher, you can include the owner name/organization in a model pack to add it to the allow list for this query. This addition will prevent security alerts when using unpinned tags for Actions published by that owner. For more information on creating a model pack, see [Creating a CodeQL Model Pack](https://docs.github.com/en/code-security/codeql-cli/using-the-advanced-functionality-of-the-codeql-cli/creating-and-working-with-codeql-packs#creating-a-codeql-model-pack). \ No newline at end of file diff --git a/actions/ql/lib/codeql/actions/config/Config.qll b/actions/ql/lib/codeql/actions/config/Config.qll index 265d4bd820f8..08bc7e860c67 100644 --- a/actions/ql/lib/codeql/actions/config/Config.qll +++ b/actions/ql/lib/codeql/actions/config/Config.qll @@ -126,6 +126,15 @@ predicate vulnerableActionsDataModel( */ predicate immutableActionsDataModel(string action) { Extensions::immutableActionsDataModel(action) } +/** + * MaD models for trusted actions owners + * Fields: + * - owner: owner name + */ +predicate trustedActionsOwnerDataModel(string owner) { + Extensions::trustedActionsOwnerDataModel(owner) +} + /** * MaD models for untrusted git commands * Fields: diff --git a/actions/ql/lib/codeql/actions/config/ConfigExtensions.qll b/actions/ql/lib/codeql/actions/config/ConfigExtensions.qll index 99ad7eb8df1b..68685f5874bb 100644 --- a/actions/ql/lib/codeql/actions/config/ConfigExtensions.qll +++ b/actions/ql/lib/codeql/actions/config/ConfigExtensions.qll @@ -63,6 +63,11 @@ extensible predicate vulnerableActionsDataModel( */ extensible predicate immutableActionsDataModel(string action); +/** + * Holds for trusted Actions owners. + */ +extensible predicate trustedActionsOwnerDataModel(string owner); + /** * Holds for git commands that may introduce untrusted data when called on an attacker controlled branch. */ diff --git a/actions/ql/lib/ext/config/trusted_actions_owner.yml b/actions/ql/lib/ext/config/trusted_actions_owner.yml new file mode 100644 index 000000000000..c90b1afee769 --- /dev/null +++ b/actions/ql/lib/ext/config/trusted_actions_owner.yml @@ -0,0 +1,8 @@ +extensions: + - addsTo: + pack: codeql/actions-all + extensible: trustedActionsOwnerDataModel + data: + - ["actions"] + - ["github"] + - ["advanced-security"] \ No newline at end of file diff --git a/actions/ql/src/Security/CWE-829/UnpinnedActionsTag.md b/actions/ql/src/Security/CWE-829/UnpinnedActionsTag.md index d7c114f0404e..f8ea2fdc82fe 100644 --- a/actions/ql/src/Security/CWE-829/UnpinnedActionsTag.md +++ b/actions/ql/src/Security/CWE-829/UnpinnedActionsTag.md @@ -24,4 +24,4 @@ Pinning an action to a full length commit SHA is currently the only way to use a ## References -- [Using third-party actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-third-party-actions) +- [Using third-party actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-third-party-actions) \ No newline at end of file diff --git a/actions/ql/src/Security/CWE-829/UnpinnedActionsTag.ql b/actions/ql/src/Security/CWE-829/UnpinnedActionsTag.ql index de8d3c2078a8..6bb2345fc26e 100644 --- a/actions/ql/src/Security/CWE-829/UnpinnedActionsTag.ql +++ b/actions/ql/src/Security/CWE-829/UnpinnedActionsTag.ql @@ -17,14 +17,15 @@ import codeql.actions.security.UseOfUnversionedImmutableAction bindingset[version] private predicate isPinnedCommit(string version) { version.regexpMatch("^[A-Fa-f0-9]{40}$") } -bindingset[repo] -private predicate isTrustedOrg(string repo) { - repo.matches(["actions", "github", "advanced-security"] + "/%") +bindingset[nwo] +private predicate isTrustedOwner(string nwo) { + // Gets the segment before the first '/' in the name with owner(nwo) string + trustedActionsOwnerDataModel(nwo.substring(0, nwo.indexOf("/"))) } -from UsesStep uses, string repo, string version, Workflow workflow, string name +from UsesStep uses, string nwo, string version, Workflow workflow, string name where - uses.getCallee() = repo and + uses.getCallee() = nwo and uses.getEnclosingWorkflow() = workflow and ( workflow.getName() = name @@ -32,9 +33,9 @@ where not exists(workflow.getName()) and workflow.getLocation().getFile().getBaseName() = name ) and uses.getVersion() = version and - not isTrustedOrg(repo) and + not isTrustedOwner(nwo) and not isPinnedCommit(version) and - not isImmutableAction(uses, repo) + not isImmutableAction(uses, nwo) select uses.getCalleeNode(), - "Unpinned 3rd party Action '" + name + "' step $@ uses '" + repo + "' with ref '" + version + + "Unpinned 3rd party Action '" + name + "' step $@ uses '" + nwo + "' with ref '" + version + "', not a pinned commit hash", uses, uses.toString()