mirror of
https://github.com/zizmorcore/zizmor.git
synced 2025-12-23 08:47:33 +00:00
feat: stabilize the auto-fix mode (#1232)
This commit is contained in:
parent
7ad9afb239
commit
d9c2d957d6
4 changed files with 53 additions and 71 deletions
|
|
@ -13,24 +13,12 @@ use crate::{
|
|||
registry::{FindingRegistry, input::InputKey, input::InputRegistry},
|
||||
};
|
||||
|
||||
const FIX_MODE_WARNING: &str = "
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! IMPORTANT WARNING !!
|
||||
!! !!
|
||||
!! Fix mode is EXPERIMENTAL! !!
|
||||
!! You will encounter bugs; please report them. !!
|
||||
!! !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
";
|
||||
|
||||
/// Apply all fixes associated with findings, filtered by the specified fix mode.
|
||||
pub fn apply_fixes(
|
||||
fix_mode: FixMode,
|
||||
results: &FindingRegistry,
|
||||
registry: &InputRegistry,
|
||||
) -> Result<()> {
|
||||
anstream::eprintln!("{}", FIX_MODE_WARNING.red().bold());
|
||||
|
||||
let mut fixes_by_input: HashMap<&InputKey, Vec<(&Fix, &Finding)>> = HashMap::new();
|
||||
let mut total_fixes = 0;
|
||||
for finding in results.fixable_findings() {
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ See each audit's section for its scope, behavior, and other information.
|
|||
|
||||
Legend:
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|------------------|---------------|----------------|--------------------|--------------|
|
||||
| The kind of audit ("Workflow" or "Action") | Links to vulnerable examples | Added to `zizmor` in this version | The audit works with `--offline` | The audit needs to be explicitly enabled via configuration or an API token | The audit supports custom configuration |
|
||||
| Workflow, Action, Dependabot | Links to vulnerable examples | Added to `zizmor` in this version | The audit works with `--offline` | The audit supports auto-fixes when used in the `--fix` mode | The audit supports custom configuration |
|
||||
|
||||
## `anonymous-definition`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|-----------------|------------------|---------------|----------------|--------------------|--------------|
|
||||
| Workflow, Action | N/A | v1.10.0 | ✅ | ❌ | ❌ |
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ Add a `name:` field to your workflow or action.
|
|||
|
||||
## `artipacked`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|------------------|---------------|----------------|--------------------| -------------|
|
||||
| Workflow | [artipacked.yml] | v0.1.0 | ✅ | ✅ | ❌ |
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ with `#!yaml persist-credentials: true`.
|
|||
|
||||
## `bot-conditions`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Workflow | [bot-conditions.yml] | v1.2.0 | ✅ | ✅ | ❌ |
|
||||
|
||||
|
|
@ -197,7 +197,7 @@ not using `pull_request_target` for auto-merge workflows.
|
|||
|
||||
## `cache-poisoning`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Workflow | [cache-poisoning.yml] | v0.10.0 | ✅ | ✅ | ❌ |
|
||||
|
||||
|
|
@ -248,9 +248,9 @@ intended to publish build artifacts:
|
|||
|
||||
## `dangerous-triggers`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|---------------------------|---------------|----------------|--------------------|--------------|
|
||||
| Workflow | [pull-request-target.yml] | v0.1.0 | ✅ | ✅ | ❌ |
|
||||
| Workflow | [pull-request-target.yml] | v0.1.0 | ✅ | ❌ | ❌ |
|
||||
|
||||
[pull-request-target.yml]: https://github.com/woodruffw/gha-hazmat/blob/main/.github/workflows/pull-request-target.yml
|
||||
|
||||
|
|
@ -329,9 +329,9 @@ Some general pointers:
|
|||
|
||||
## `dependabot-cooldown`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Dependabot | [dependabot-cooldown/] | v1.15.0 | ✅ | ✅ | ❌ |
|
||||
| Dependabot | [dependabot-cooldown/] | v1.15.0 | ✅ | ❌ | ❌ |
|
||||
|
||||
[dependabot-cooldown/]: https://github.com/zizmorcore/zizmor/blob/main/crates/zizmor/tests/integration/test-data/dependabot-cooldown/
|
||||
|
||||
|
|
@ -395,9 +395,9 @@ enforces the following minimums:
|
|||
|
||||
## `dependabot-execution`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Dependabot | [dependabot-execution/] | v1.15.0 | ✅ | ✅ | ❌ |
|
||||
| Dependabot | [dependabot-execution/] | v1.15.0 | ✅ | ❌ | ❌ |
|
||||
|
||||
[dependabot-execution/]: https://github.com/zizmorcore/zizmor/blob/main/crates/zizmor/tests/integration/test-data/dependabot-execution/
|
||||
|
||||
|
|
@ -460,9 +460,9 @@ In practice, this means that users should set
|
|||
|
||||
## `excessive-permissions`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-----------------------------|---------------|----------------|--------------------|---------------|
|
||||
| Workflow | [excessive-permissions.yml] | v0.1.0 | ✅ | ✅ | ❌ |
|
||||
| Workflow | [excessive-permissions.yml] | v0.1.0 | ✅ | ❌ | ❌ |
|
||||
|
||||
[excessive-permissions.yml]: https://github.com/woodruffw/gha-hazmat/blob/main/.github/workflows/excessive-permissions.yml
|
||||
|
||||
|
|
@ -569,7 +569,7 @@ by default, and then set specific job-level permissions as needed.
|
|||
|
||||
## `forbidden-uses`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Workflow, Action | N/A | v1.6.0 | ✅ | ❌ | ✅ |
|
||||
|
||||
|
|
@ -656,9 +656,9 @@ your [configuration](#forbidden-uses-configuration).
|
|||
|
||||
## `github-env`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|--------------------|---------------|----------------|--------------------| --------------|
|
||||
| Workflow, Action | [github-env.yml] | v0.6.0 | ✅ | ✅ | ❌ |
|
||||
| Workflow, Action | [github-env.yml] | v0.6.0 | ✅ | ❌ | ❌ |
|
||||
|
||||
[github-env.yml]: https://github.com/woodruffw/gha-hazmat/blob/main/.github/workflows/github-env.yml
|
||||
|
||||
|
|
@ -695,9 +695,9 @@ If you need to pass state between steps, consider using `GITHUB_OUTPUT` instead.
|
|||
|
||||
## `hardcoded-container-credentials`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-----------------------------|---------------|----------------|--------------------|---------------|
|
||||
| Workflow | [hardcoded-credentials.yml] | v0.1.0 | ✅ | ✅ | ❌ |
|
||||
| Workflow | [hardcoded-credentials.yml] | v0.1.0 | ✅ | ❌ | ❌ |
|
||||
|
||||
[hardcoded-credentials.yml]: https://github.com/woodruffw/gha-hazmat/blob/main/.github/workflows/hardcoded-credentials.yml
|
||||
|
||||
|
|
@ -765,7 +765,7 @@ Use [encrypted secrets] instead of hardcoded credentials.
|
|||
|
||||
## `impostor-commit`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-----------------------|---------------|----------------|--------------------|---------------|
|
||||
| Workflow, Action | [impostor-commit.yml] | v0.1.0 | ❌ | ✅ | ❌ |
|
||||
|
||||
|
|
@ -805,7 +805,7 @@ within an authentic commit (or an authentic tag/branch reference).
|
|||
|
||||
## `insecure-commands`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Workflow, Action | [insecure-commands.yml] | v0.5.0 | ✅ | ✅ | ❌ |
|
||||
|
||||
|
|
@ -853,7 +853,7 @@ In general, users should use [GitHub Actions environment files]
|
|||
|
||||
## `known-vulnerable-actions`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|------------------|--------------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Workflow, Action | [known-vulnerable-actions.yml] | v0.1.0 | ❌ | ✅ | ❌ |
|
||||
|
||||
|
|
@ -877,7 +877,7 @@ the action if one is available, or remove the action's usage entirely.
|
|||
|
||||
## `obfuscation`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Workflow, Action | N/A | v1.7.0 | ✅ | ✅ | ❌ |
|
||||
|
||||
|
|
@ -930,9 +930,9 @@ Address the source of obfuscation by simplifying the expression,
|
|||
|
||||
## `overprovisioned-secrets`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Workflow, Action | [overprovisioned-secrets.yml] | v1.3.0 | ✅ | ✅ | ❌ |
|
||||
| Workflow, Action | [overprovisioned-secrets.yml] | v1.3.0 | ✅ | ❌ | ❌ |
|
||||
|
||||
[overprovisioned-secrets.yml]: https://github.com/woodruffw/gha-hazmat/blob/main/.github/workflows/overprovisioned-secrets.yml
|
||||
|
||||
|
|
@ -994,9 +994,9 @@ Secrets should be accessed individually by name.
|
|||
|
||||
## `ref-confusion`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|------------------|---------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Workflow, Action | [ref-confusion.yml] | v0.1.0 | ❌ | ✅ | ❌ |
|
||||
| Workflow, Action | [ref-confusion.yml] | v0.1.0 | ❌ | ❌ | ❌ |
|
||||
|
||||
|
||||
[ref-confusion.yml]: https://github.com/woodruffw/gha-hazmat/blob/main/.github/workflows/ref-confusion.yml
|
||||
|
|
@ -1020,7 +1020,7 @@ Switch to hash-pinned actions.
|
|||
|
||||
## `ref-version-mismatch`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Workflow, Action | [ref-version-mismatch.yml] | v1.14.0 | ✅ | ✅ | ❌ |
|
||||
|
||||
|
|
@ -1064,9 +1064,9 @@ Update the tag comment to match the pinned commit. Tools like
|
|||
|
||||
## `secrets-inherit`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Workflow | [secrets-inherit.yml] | v1.1.0 | ✅ | ✅ | ❌ |
|
||||
| Workflow | [secrets-inherit.yml] | v1.1.0 | ✅ | ❌ | ❌ |
|
||||
|
||||
[secrets-inherit.yml]: https://github.com/woodruffw/gha-hazmat/blob/main/.github/workflows/secrets-inherit.yml
|
||||
|
||||
|
|
@ -1108,7 +1108,7 @@ that explicitly forwards each secret actually needed by the reusable workflow.
|
|||
|
||||
## `self-hosted-runner`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|---------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Workflow | [self-hosted.yml] | v0.1.0 | ✅ | ❌ | ❌ |
|
||||
|
||||
|
|
@ -1155,9 +1155,9 @@ there are steps you can take to minimize their risk:
|
|||
|
||||
## `stale-action-refs`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-------------------------|---------------|----------------|--------------------|--------------|
|
||||
| Workflow, Action | N/A | v1.7.0 | ❌ | ✅ | ❌ |
|
||||
| Workflow, Action | N/A | v1.7.0 | ❌ | ❌ | ❌ |
|
||||
|
||||
Checks for `#!yaml uses:` clauses which pin an action using a SHA reference,
|
||||
but where that reference does not point to a Git tag.
|
||||
|
|
@ -1187,7 +1187,7 @@ which points to a Git tag.
|
|||
|
||||
## `template-injection`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|--------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Workflow, Action | [template-injection.yml] | v0.1.0 | ✅ | ✅ | ❌ |
|
||||
|
||||
|
|
@ -1277,9 +1277,9 @@ shell quoting/expansion rules.
|
|||
|
||||
## `undocumented-permissions`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|------------------|---------------|----------------|--------------------|--------------|
|
||||
| Workflow | [undocumented-permissions.yml] | v1.13.0 | ✅ | ❌ | ❌ |
|
||||
| Workflow | [undocumented-permissions.yml] | v1.13.0 | ✅ | ❌ | ❌ |
|
||||
|
||||
[undocumented-permissions.yml]: https://github.com/zizmorcore/zizmor/blob/main/crates/zizmor/tests/integration/test-data/undocumented-permissions.yml
|
||||
|
||||
|
|
@ -1322,9 +1322,9 @@ Add inline comments explaining why each permission is needed:
|
|||
|
||||
## `unpinned-images`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-------------------------|---------------|----------------|--------------------|--------------|
|
||||
| Workflow, Action | [unpinned-images.yml] | v1.7.0 | ✅ | ✅ | ❌ |
|
||||
| Workflow, Action | [unpinned-images.yml] | v1.7.0 | ✅ | ❌ | ❌ |
|
||||
|
||||
[unpinned-images.yml]: https://github.com/woodruffw/gha-hazmat/blob/main/.github/workflows/unpinned-images.yml
|
||||
|
||||
|
|
@ -1412,9 +1412,9 @@ by running `#!bash docker inspect redis:7.4.3 --format='{{.RepoDigests}}'`.
|
|||
|
||||
## `unpinned-uses`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|------------------|------------------|---------------|----------------|--------------------|--------------|
|
||||
| Workflow, Action | [unpinned.yml] | v0.4.0 | ✅ | ✅ | ✅ |
|
||||
| Workflow, Action | [unpinned.yml] | v0.4.0 | ✅ | ❌ | ✅ |
|
||||
|
||||
[unpinned.yml]: https://github.com/woodruffw/gha-hazmat/blob/main/.github/workflows/unpinned.yml
|
||||
|
||||
|
|
@ -1604,9 +1604,9 @@ For Docker actions (like `docker://ubuntu`): add an appropriate
|
|||
|
||||
## `unredacted-secrets`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Workflow, Action | [unredacted-secrets.yml] | v1.4.0 | ✅ | ✅ | ❌ |
|
||||
| Workflow, Action | [unredacted-secrets.yml] | v1.4.0 | ✅ | ❌ | ❌ |
|
||||
|
||||
[unredacted-secrets.yml]: https://github.com/woodruffw/gha-hazmat/blob/main/.github/workflows/unredacted-secrets.yml
|
||||
|
||||
|
|
@ -1674,7 +1674,7 @@ individual fields as separate secrets.
|
|||
|
||||
## `unsound-condition`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Workflow, Action | [unsound-condition.yml] | v1.12.0 | ✅ | ✅ | ❌ |
|
||||
|
||||
|
|
@ -1766,9 +1766,9 @@ There are two ways to remediate this:
|
|||
|
||||
## `unsound-contains`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|-------------------------------------|---------------|---------------|--------------------|--------------|
|
||||
| Workflow | [unsound-contains.yml] | v1.7.0 | ✅ | ✅ | ❌ |
|
||||
| Workflow | [unsound-contains.yml] | v1.7.0 | ✅ | ❌ | ❌ |
|
||||
|
||||
[unsound-contains.yml]: https://github.com/woodruffw/gha-hazmat/blob/main/.github/workflows/unsound-contains.yml
|
||||
|
||||
|
|
@ -1844,9 +1844,9 @@ Other resources:
|
|||
|
||||
## `use-trusted-publishing`
|
||||
|
||||
| Type | Examples | Introduced in | Works offline | Enabled by default | Configurable |
|
||||
| Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable |
|
||||
|----------|------------------------------|---------------|----------------|--------------------| ---------------|
|
||||
| Workflow | [pypi-manual-credential.yml] | v0.1.0 | ✅ | ✅ | ❌ |
|
||||
| Workflow | [pypi-manual-credential.yml] | v0.1.0 | ✅ | ❌ | ❌ |
|
||||
|
||||
[pypi-manual-credential.yml]: https://github.com/woodruffw/gha-hazmat/blob/main/.github/workflows/pypi-manual-credential.yml
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ To complement this new functionality, this release comes with two new audits:
|
|||
* `--collect=dependabot` is now supported as a collection option,
|
||||
allowing users to audit only Dependabot configuration files (#1215)
|
||||
|
||||
* The `--fix` mode (introduced with v1.10.0) is now considered
|
||||
**stable** and no longer experimental (#1232)
|
||||
|
||||
### Bug Fixes 🐛
|
||||
|
||||
* Fixed a bug where `zizmor` would fail instead of analyzing single-file
|
||||
|
|
|
|||
|
|
@ -508,21 +508,12 @@ sensitive `zizmor`'s analyses are:
|
|||
1 finding: 0 informational, 0 low, 1 medium, 0 high
|
||||
```
|
||||
|
||||
## Auto-fixing results *​*{.chip .chip-experimental} { #auto-fixing-results }
|
||||
|
||||
!!! warning
|
||||
|
||||
`zizmor`'s auto-fix mode is currently **experimental** and subject to
|
||||
breaking changes.
|
||||
|
||||
You **will** encounter bugs while experimenting with it;
|
||||
please [file them]!
|
||||
|
||||
[file them]: https://github.com/zizmorcore/zizmor/issues/new?template=bug-report.yml
|
||||
## Auto-fixing results { #auto-fixing-results }
|
||||
|
||||
!!! tip
|
||||
|
||||
`--fix=[MODE]` is available in `v1.10.0` and later.
|
||||
`--fix=[MODE]` is available in `v1.10.0` and later, and is
|
||||
considered stable as of `v1.15.0`.
|
||||
|
||||
Starting with `v1.10.0`, `zizmor` can automatically fix a subset of its findings.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue