Scans your Github Actions for security issues.
Run: npm run start -- [OPTIONS]
.
Github Actions Scanner
Options:
-e, --env <path> .env file path. (default: ".env")
-r, --recurse Recurse into referenced actions
-m, --max-depth <depth> Max Recursion Depth (default: 5)
-s, --scan-rules <rule1,rule2,...> Comma separated list of rules to use, by ID (default: "")
--output <path> Output file path.
-f, --format <format> Output format (choices: "json", "text", default: "text")
-h, --help display help for command
Commands:
list-rules List all available rules
scan-repo [options] Scan a single repo
scan-org [options] Scan all repos in an org
scan-actions [options] Scan a list of standalone actions from a file
clone [options] Pseudo-fork a repo for testing
ldpreload-poc [options] Create a PoC to exploit subsequent steps after command injection with LD_PRELOAD
help [command] display help for command
List all available rules
Options:
-h, --help display help for command
Scan a single repo
Options:
-u, --url <string> Github repository URL.
-h, --help display help for command
Scan all repos in an org
Options:
-o, --org <name> Github org name.
-h, --help display help for command
Scan a list of standalone actions from a file
Options:
-a, --actions-yaml [actions-yaml-path] Analyze actions from yaml. (default: "./github-action-repos.yml")
-h, --help display help for command
Pseudo-fork a repo for testing
Options:
-u, --url <string> Github repository URL.
-h, --help display help for command
Create a PoC to exploit subsequent steps after command injection with LD_PRELOAD
Options:
-c, --command <command> Command to run from the LD_PRELOAD
-b, --base64 Encode the code for injection
-h, --help display help for command
Please provide a GITHUB_TOKEN
either via .env
file or env var.
Description: Dynamic values inserted into a run
(or similar) item using ${{ }}
are not escaped and, if controlled by an attacker, may result in command execution inside the step.
Mitigation:
- Pass the expressions to an action as an argument in the
with
clause - Pass the expressions to a
run
directive using an intermediate environmental variable. For more details, please check Good practices for mitigating script injection attacks.
Description: Similar to CMD_EXEC
, when using the actions/github-script
action, values inserted using ${{ }}
are not escaped and, if controlled by an attacker, may result in command execution inside the step.
Mitigation: Pass the expressions to the action as an argument in the with
clause.
Description: An action triggered by pull_request_target
which additionally performs a checkout of the pull request branch may lead to compromise of specific steps or secrets. pull_request_target
can be triggered by untrusted external attackers.
Mitigation: If accepting actions triggered by unknown third parties, ensure that the pull request branch is not checked out and acted on unsafely (e.g via the use of tooling which acts on the repository, such as npm install
). If it is necessary to check out the third party code, be very careful to ensure that no steps treat the repository as trusted.
Description: Potentially attacker controlled input is passed by value to a step using with
. Depending on the contents of the step involved, if these values are not handled with care this may result in further compromise (such as command or code execution in the step).
Mitigation: Ensure that all values which may be attacker control are handled with care to ensure that they do not result in further compromise.
Description: The identified action is triggered by another action, and performs a checkout of the branch which triggered the original action. This branch may be attacker controlled and therefore this action chain should be reviewed.
Mitigation: Ensure that subsequently triggered actions treat untrusted data sources as untrusted, especially when the origin is masked by indirection of the original action.
Description: The identified referenced action may be repojackable. Either the organisation has been renamed or does not exist at all.
Mitigation: Ensure that all uses
items reference present and up-to-date repositories to ensure that they cannot be repojacked if renamed or deleted.
Description: The identified action is used (i.e via uses:
) with a branch or tag reference, rather than a fixed commit. Should the target action repository be compromised this action may therefore be at risk.
Mitigation: Ensure that all actions are referenced by a fixed and validated commit hash.
To write your own rules, create a new .mjs
file in the rules
directory. This file should be written in Javascript and implement a single rule. Inside this file, implement and export a class which includes the following methods and attributes:
class MyRule {
static id = "MY_RULE";
static documentation = "https://github.com/snyk/github-actions-scanner/blob/main/README.md#MY_RULE"
static async description(finding) {
// takes a single Finding instance, as defined in finding.mjs. Will always be from those returned by `scan`
// allows for full context creation of a single line to provide a description for a single finding instance
return "";
}
static async prereport(finding) {
// Optional
// Perform final modification to a finding of this type once all scans have been completed
// Useful for looking at other actions in the same repository
}
static async scan(action) {
// Takes a single Action instance (defined in actions.mjs) and returns an array of Finding instances (defined in finding.mjs)
return [
new Finding(MyRule, action, job_name, step_name_or_id, {"optional": "data"});
]
}
}
export { MyRule as default }
The stepMatches
and evaluteStepRule
functions are provided to assist with creating rules.
stepMatches
takes an array of individual 'rules' and an action step and returns an array of those rules which match. The rules are objects which can define either full match key:values, match based on regex on the value for a key, and keys not present. Examples to show this in action are as follows:
The following example shows a match for the inclusion of any (denoted by the wildcard '*') secrets in the env
object for the provided step. The resulting matches
array wil contain the single matching rule.
const RULES = [
{ env: { "*": new RegExp("\\${{\\s*secrets[.]") } }
]
const step = {
name: 'Secrets test',
uses: 'nick-invision/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd',
env: {
GITHUB_AUTH_TOKEN: '${{ secrets.GITHUB_TOKEN }}',
GITLAB_AUTH_TOKEN: '${{ secrets.GITLAB_TOKEN }}'
},
with: {
command: 'omitted'
}
}
const matches = stepMatches(RULES, step);
The following rule attempts to ensure that the env.DOESNTMATCH
key is not present. In this case the resulting matches
array will be empty.
const RULES = [
{ env: { "DOESNTMATCH": undefined } },
]
const step = {
name: 'DOESNTMATCH test',
uses: 'nick-invision/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd',
env: {
DOESNTMATCH: "true"
},
with: {
command: 'omitted'
}
}
const matches = stepMatches(RULES, step);
The additional function evaluateStepRule
aids in the further processing of such a match. This function takes a single rule and a single step, and returns the step object only where the rule is specified.
For example, the following rule will attempt to find environmental variables which match the .INDME
regex. The resultant evaluation will only contain those elements that match the rule, in the same format as the rule itself.
const RULE = { env: { "*": new RegExp(".INDME") } };
const step = {
foo: "bar",
env: {
"abc": "FINDME",
"def": "notme",
"ghi": "FINDME"
}
}
const evaluated = evaluateStepRule(RULE, step)
expect(evaluated).toEqual({
"env": {
"abc": "FINDME",
"ghi": "FINDME",
}
})
A third, optional, parameter can be passed to evaluateStepRule. This parameter is an object which is a subset of the rule object, and can be used to specify the regex match groups to extract in place of the full field. The example below shows extracting the suffix in place of the full environmental variable value. If no value is provided for a specific regex in the object, the full value will be returned. This is useful for displaying the specific line where an injection occurs, or extracting the interpolated variable inside a ${{ }}
item.
const RULE = { env: { "*": new RegExp("FINDME(?<suffix>...)") } };
const step = {
foo: "bar",
env: {
"abc": "FINDMEabc",
"def": "notme",
"ghi": "FINDMEdef"
}
}
const evaluated = evaluateStepRule(RULE, step, { env: { "*": "suffix" } })
expect(evaluated).toEqual({
"env": {
"abc": "abc",
"ghi": "def",
}
})