mirror of
https://github.com/zizmorcore/zizmor.git
synced 2025-12-23 08:47:33 +00:00
feat: dependabot-execution audit (#1220)
This commit is contained in:
parent
fbd86b5955
commit
62655cb7c1
9 changed files with 224 additions and 6 deletions
52
crates/zizmor/src/audit/dependabot_execution.rs
Normal file
52
crates/zizmor/src/audit/dependabot_execution.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
use github_actions_models::dependabot::v2::AllowDeny;
|
||||
|
||||
use crate::{
|
||||
audit::{Audit, audit_meta},
|
||||
finding::location::Locatable as _,
|
||||
};
|
||||
|
||||
audit_meta!(
|
||||
DependabotExecution,
|
||||
"dependabot-execution",
|
||||
"external code execution in Dependabot updates"
|
||||
);
|
||||
|
||||
pub(crate) struct DependabotExecution;
|
||||
|
||||
impl Audit for DependabotExecution {
|
||||
fn new(_state: &crate::state::AuditState) -> Result<Self, super::AuditLoadError>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
fn audit_dependabot<'doc>(
|
||||
&self,
|
||||
dependabot: &'doc crate::models::dependabot::Dependabot,
|
||||
_config: &crate::config::Config,
|
||||
) -> anyhow::Result<Vec<crate::finding::Finding<'doc>>> {
|
||||
let mut findings = vec![];
|
||||
|
||||
for update in dependabot.updates() {
|
||||
if matches!(update.insecure_external_code_execution, AllowDeny::Allow) {
|
||||
findings.push(
|
||||
Self::finding()
|
||||
.confidence(crate::finding::Confidence::High)
|
||||
.severity(crate::finding::Severity::High)
|
||||
.add_location(
|
||||
update
|
||||
.location()
|
||||
.with_keys(["insecure-external-code-execution".into()])
|
||||
.primary()
|
||||
.annotated("enabled here"),
|
||||
)
|
||||
.add_location(update.location_with_name())
|
||||
.build(dependabot)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(findings)
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ pub(crate) mod artipacked;
|
|||
pub(crate) mod bot_conditions;
|
||||
pub(crate) mod cache_poisoning;
|
||||
pub(crate) mod dangerous_triggers;
|
||||
pub(crate) mod dependabot_execution;
|
||||
pub(crate) mod excessive_permissions;
|
||||
pub(crate) mod forbidden_uses;
|
||||
pub(crate) mod github_env;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use github_actions_models::dependabot;
|
|||
use terminal_link::Link;
|
||||
|
||||
use crate::{
|
||||
finding::location::{SymbolicFeature, SymbolicLocation},
|
||||
finding::location::{Locatable, SymbolicFeature, SymbolicLocation},
|
||||
models::AsDocument,
|
||||
registry::input::{CollectionError, InputKey},
|
||||
utils::{DEPENDABOT_VALIDATOR, from_str_with_validation},
|
||||
|
|
@ -76,4 +76,64 @@ impl Dependabot {
|
|||
kind: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn updates(&self) -> Updates<'_> {
|
||||
Updates::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Updates<'doc> {
|
||||
parent: &'doc Dependabot,
|
||||
inner:
|
||||
std::iter::Enumerate<std::slice::Iter<'doc, github_actions_models::dependabot::v2::Update>>,
|
||||
}
|
||||
|
||||
impl<'doc> Updates<'doc> {
|
||||
fn new(parent: &'doc Dependabot) -> Self {
|
||||
Self {
|
||||
parent,
|
||||
inner: parent.inner.updates.iter().enumerate(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'doc> Iterator for Updates<'doc> {
|
||||
type Item = Update<'doc>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(|(idx, update)| Update {
|
||||
parent: self.parent,
|
||||
index: idx,
|
||||
inner: update,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Update<'doc> {
|
||||
parent: &'doc Dependabot,
|
||||
index: usize,
|
||||
inner: &'doc github_actions_models::dependabot::v2::Update,
|
||||
}
|
||||
|
||||
impl<'doc> std::ops::Deref for Update<'doc> {
|
||||
type Target = github_actions_models::dependabot::v2::Update;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<'doc> Locatable<'doc> for Update<'doc> {
|
||||
fn location(&self) -> SymbolicLocation<'doc> {
|
||||
self.parent
|
||||
.location()
|
||||
.with_keys(["updates".into(), self.index.into()])
|
||||
.annotated("this update rule")
|
||||
}
|
||||
|
||||
fn location_with_name(&self) -> SymbolicLocation<'doc> {
|
||||
self.location()
|
||||
.with_keys(["package-ecosystem".into()])
|
||||
.annotated("this ecosystem")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ impl AuditRegistry {
|
|||
register_audit!(audit::anonymous_definition::AnonymousDefinition);
|
||||
register_audit!(audit::unsound_condition::UnsoundCondition);
|
||||
register_audit!(audit::ref_version_mismatch::RefVersionMismatch);
|
||||
register_audit!(audit::dependabot_execution::DependabotExecution);
|
||||
|
||||
Ok(registry)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -948,3 +948,16 @@ fn unsound_condition() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dependabot_execution() -> Result<()> {
|
||||
insta::assert_snapshot!(
|
||||
zizmor()
|
||||
.input(input_under_test(
|
||||
"dependabot-execution/basic/dependabot.yml"
|
||||
))
|
||||
.run()?
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
source: crates/zizmor/tests/integration/snapshot.rs
|
||||
expression: "zizmor().input(input_under_test(\"dependabot-execution/basic/dependabot.yml\")).run()?"
|
||||
---
|
||||
error[dependabot-execution]: external code execution in Dependabot updates
|
||||
--> @@INPUT@@:8:5
|
||||
|
|
||||
4 | - package-ecosystem: pip
|
||||
| ---------------------- this ecosystem
|
||||
...
|
||||
8 | insecure-external-code-execution: allow
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ enabled here
|
||||
|
|
||||
= note: audit confidence → High
|
||||
|
||||
1 finding: 0 informational, 0 low, 0 medium, 1 high
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: pip
|
||||
directory: /
|
||||
schedule:
|
||||
interval: daily
|
||||
insecure-external-code-execution: allow
|
||||
|
|
@ -327,6 +327,71 @@ Some general pointers:
|
|||
|
||||
[reusable workflow]: https://docs.github.com/en/actions/sharing-automations/reusing-workflows
|
||||
|
||||
## `dependabot-execution`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
|----------|-------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Dependabot | [dependabot.yml] | v1.15.0 | ✅ | ✅ | ❌ |
|
||||
|
||||
[dependabot.yml]: https://github.com/zizmorcore/zizmor/blob/main/crates/zizmor/tests/integration/test-data/dependabot-execution/basic/dependabot.yml
|
||||
|
||||
Detects usages of `insecure-external-code-execution` in Dependabot configuration
|
||||
files.
|
||||
|
||||
By default, Dependabot does not execution code from dependency manifests
|
||||
during updates. However, users can opt in to this behavior by setting
|
||||
`#!yaml insecure-external-code-execution: allow` in their Dependabot
|
||||
configuration.
|
||||
|
||||
Some ecosystems (including but not limited to Python, Ruby, and JavaScript)
|
||||
depend partially on code execution during dependency resolution.
|
||||
|
||||
In these ecosystems fully avoiding build-time code execution is impossible.
|
||||
However, build-time code execution *should* be avoided in automated dependency
|
||||
update contexts like Dependabot, since a compromised dependency may be able
|
||||
to obtain credentials or private source access automatically through
|
||||
a Dependabot job.
|
||||
|
||||
Other resources:
|
||||
|
||||
* [`insecure-external-code-execution` documentation](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#insecure-external-code-execution--)
|
||||
* [Dependabot: Allowing external code execution](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/configuring-access-to-private-registries-for-dependabot#allowing-external-code-execution)
|
||||
|
||||
### Remediation
|
||||
|
||||
In general, automatic dependency updates should be limited to only updates
|
||||
that do not require code execution at resolution time.
|
||||
|
||||
In practice, this means that users should set
|
||||
`#!yaml insecure-external-code-execution: deny` **or** omit the field entirely
|
||||
(and rely on the default of `deny`).
|
||||
|
||||
!!! example
|
||||
|
||||
=== "Before :warning:"
|
||||
|
||||
```yaml title="dependabot.yml" hl_lines="7"
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
insecure-external-code-execution: allow
|
||||
```
|
||||
|
||||
=== "After :white_check_mark:"
|
||||
|
||||
```yaml title="dependabot.yml" hl_lines="7"
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
insecure-external-code-execution: deny
|
||||
```
|
||||
|
||||
## `excessive-permissions`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
|
|
|
|||
|
|
@ -49,9 +49,8 @@ sources can be mixed and matched:
|
|||
zizmor ../example.yml ../other-repo/ example/example
|
||||
```
|
||||
|
||||
When auditing local and/or remote repositories, `zizmor` will collect both
|
||||
workflows (e.g. `.github/workflows/ci.yml`) **and** action definitions
|
||||
(e.g. `custom-action/foo.yml`) by default. To configure collection behavior,
|
||||
When auditing local and/or remote repositories, `zizmor` will collect
|
||||
all known input kinds by default. To configure collection behavior,
|
||||
you can use the `--collect=...` option.
|
||||
|
||||
```bash
|
||||
|
|
@ -66,6 +65,9 @@ zizmor --collect=workflows-only example/example
|
|||
|
||||
# collect only actions
|
||||
zizmor --collect=actions-only example/example
|
||||
|
||||
# collect only Dependabot configs
|
||||
zizmor --collect=dependabot-only example/example
|
||||
```
|
||||
|
||||
!!! tip
|
||||
|
|
@ -81,8 +83,8 @@ zizmor --collect=actions-only example/example
|
|||
*will* audit `workflow.yml`, since it was passed explicitly and not
|
||||
collected indirectly.
|
||||
|
||||
By default, `zizmor` will warn (but not fail) if it fails to parse a
|
||||
workflow or action definition. To turn these warnings into failures,
|
||||
By default, `zizmor` will warn (but not fail) if it fails to parse an
|
||||
input file. To turn these warnings into failures,
|
||||
you can use the `--strict-collection` option:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue