AnsibleXXVII · Collections, Galaxy and Dependency TrustCollections and dependency trust
Reading a collection before you run it
What you'll learn
- Run a repeatable evaluation of a third-party collection before it touches a host
- Read a collection manifest, changelog and source tree for the signals that predict maintenance
- Determine what privilege a collection needs and what it does with the credentials it is given
- Produce a written verdict a colleague can disagree with
Prerequisites
Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11
You would not install a .deb from a stranger’s website onto a
production server. You would ask who built it, whether the repository
is signed, what it depends on, and what it does when it runs.
ansible-galaxy collection install is that install, with a wider blast
radius. The modules go to every host in the play and run under
become. The lookup, filter, action and inventory plugins run on the
controller — the machine holding SSH keys to the entire fleet and the
vault password that decrypts your secrets. There is no sandbox at
either end.
The good news is that a collection is a small, readable, fully local artefact. Everything below is answerable in about twenty minutes from files already on your disk.
The seven questions
- Who publishes it, and does the namespace match?
- Is it maintained, and by how many people?
- Where is the source, and can you reach it?
- What does it drag in?
- What privilege does it need, and what does it do with credentials?
- Does it support check mode and diff, honestly?
- What is the licence, and does it fit?
Take them in order against a real collection.
1. Who publishes it
$ python3 -c "
import json
m = json.load(open('collections/ansible_collections/community/docker/MANIFEST.json'))
ci = m['collection_info']
for k in ('namespace','name','version','license','authors','repository','issues','dependencies','tags'):
print(k, '=', ci.get(k))
"namespace = community
name = docker
version = 4.7.0
license = ['GPL-3.0-or-later', 'Apache-2.0']
authors = ['Ansible Docker Working Group']
repository = https://github.com/ansible-collections/community.docker
issues = https://github.com/ansible-collections/community.docker/issues
dependencies = {'community.library_inventory_filtering_v1': '>=1.0.0'}
tags = ['docker']That block is the publisher’s own statement about itself, and it is
where a typosquat gives itself away. The check is not “does it look
plausible” but does the repository URL belong to the organisation the
namespace claims? community.* collections live under
github.com/ansible-collections/. A community.something whose
repository points at a personal account is either a mistake or a
name nobody should have been able to claim.
2. Is it maintained
Every well-run collection ships its changelog inside the artefact, so this is a local file:
$ grep '^## v' collections/ansible_collections/community/docker/CHANGELOG.md | head -16## v4\.7\.0
## v4\.6\.2
## v4\.6\.1
## v4\.6\.0
## v4\.5\.2
## v4\.5\.1
## v4\.5\.0
## v4\.4\.0
## v4\.3\.1
## v4\.3\.0
## v4\.2\.0
## v4\.1\.0
## v4\.0\.1
## v4\.0\.0
## v3\.13\.1
## v3\.13\.0The backslashes are real. This changelog is generated from reStructuredText, where the dots in a version number are escaped; the file on disk contains them.
What you are reading for:
- Cadence. Regular minor releases with patch releases between them is a project responding to reports. Four years of silence followed by one release is a project somebody dusted off.
- Do patch releases exist at all? A collection that only ever
publishes
x.y.0is not fixing bugs between features, which tells you what happens when you report one. - What the entries say. Bug fixes, security fixes and deprecation notices in the text are the signal. Nothing but “add new module” is a project that is growing rather than being maintained.
- How breaking changes are handled. Look at a major bump and see
whether the removals were deprecated first, with a
removal_version, in an earlier release.
The one thing the artefact cannot tell you is how many humans are behind it, so this is the point at which you open the repository.
3. Where is the source
The repository and issues URLs from the manifest are the ones to
follow, not a search result. What to look at, in ten minutes:
- Open issues and their ages. Not the count — the distribution. Fifty open issues with recent maintainer replies is healthy. Twelve open issues, all unanswered, oldest three years, is not.
- Commits in the last year, and by how many distinct people. A collection with one contributor is one bus away from unmaintained, regardless of how good that contributor is.
- Is CI green, and does it test what you use? Collections usually
publish a test matrix. If it tests against
ansible-core2.14–2.17 and you run 2.21, you are the test matrix. - Security contact. A
SECURITY.md, or membership of a programme with one. Its absence tells you how a vulnerability report would go.
4. What does it drag in
You have already seen this one:
$ ansible-galaxy collection list -p ./collections# /home/opsuser/project/collections/ansible_collections
Collection Version
---------------------------------------- -------
community.docker 4.7.0
community.library_inventory_filtering_v1 1.1.5Every collection in that list needs its own answers to questions 1 through 7. A transitive dependency has exactly the same access as a direct one; the only difference is that you did not choose it and probably have not read it.
The requires_ansible line is worth pulling out at the same time,
because it bounds compatibility:
$ grep requires_ansible collections/ansible_collections/community/docker/meta/runtime.ymlrequires_ansible: '>=2.15.0'That is a floor, not a ceiling, and it is a claim rather than a
guarantee — >=2.15.0 does not mean anyone has tested it against
2.21. Cross-check against the repository’s CI matrix.
5. Privilege and credentials
Now open the source. This is the part people skip and it is the part that matters.
$ grep -rl 'run_command' collections/ansible_collections/community/docker/plugins/modules/collections/ansible_collections/community/docker/plugins/modules/docker_stack_task_info.py
collections/ansible_collections/community/docker/plugins/modules/docker_stack_info.pyTwo out of thirty-eight modules shell out; the rest talk to the Docker API. That is a good ratio and a meaningful one, because a module that shells out inherits every quoting and injection hazard the Linux course’s shell material covers, on the managed host, as root.
Then look at how credentials are handled. The single most informative
grep in a collection is for no_log:
$ grep -n -A 6 'argument_spec = dict' collections/ansible_collections/community/docker/plugins/modules/docker_login.py argument_spec = dict(
registry_url=dict(type='str', default=DEFAULT_DOCKER_REGISTRY, aliases=['registry', 'url']),
username=dict(type='str'),
password=dict(type='str', no_log=True),
reauthorize=dict(type='bool', default=False, aliases=['reauth']),
state=dict(type='str', default='present', choices=['present', 'absent']),
config_path=dict(type='path', default='~/.docker/config.json', aliases=['dockercfg_path']),
)no_log=True on password is what you want to see. Without it, the
value appears in -vvv output, in the JSON the module returns, in
callback plugin output and in any CI log that captured the run — the
leak paths Part XXII catalogues.
The questions to answer for any module you will hand a credential to:
- Is every secret parameter marked
no_log=True? Grep the argument spec. One unmarked parameter is a finding. - Where does the credential end up? Read the code path.
docker_loginwrites to~/.docker/config.jsonon the target — base64-encoded, not encrypted, persisting after the play ends. That may be exactly what you want, and you should know it either way. - Does it phone anywhere you did not name? Grep for
urlopen,requests,httpin the plugin tree and check every hit resolves to an endpoint you configured. - Does it need root, or does it need root because nobody tried otherwise? Many modules work fine as an unprivileged user with the right group membership. Part XXI’s least-privilege material applies unchanged.
6. Check mode and diff, honestly
$ ansible-doc -t module community.docker.docker_container | sed -n '/ATTRIBUTES/,/NOTES/p'ATTRIBUTES:
check_mode:
description: Can run in `check_mode' and return changed status prediction without
modifying target.
details:
- When trying to pull an image, the module assumes this is never changed in check
mode except when the image is not present on the Docker daemon.
- This behavior can be configured with `pull_check_mode_behavior'.
support: partial
diff_mode:
description: Will return details on what has changed (or possibly needs changing in
`check_mode'), when in diff mode.
support: full
idempotent:
description:
- When run twice in a row outside check mode, with the same arguments, the second
invocation indicates no change.
details:
- If `recreate=true' or `restart=true' the module is not idempotent.
support: partialsupport: partial with the specific caveat spelled out is a
well-documented module. support: none is honest. A missing
attributes block entirely is the finding — it means nobody stated a
position, and a --check run that includes that module proves less
than it appears to.
Part IX owns what check mode does and does not prove overall. Here the
point is narrower: this is intake evidence. A collection whose
modules all claim check_mode: full with no caveats, for operations
that obviously cannot be predicted without performing them, is making a
claim you should test rather than accept.
7. Licence
license = ['GPL-3.0-or-later', 'Apache-2.0'] above. Two things to
check: that the licence exists at all, and that it is compatible with
whatever you are doing. For internal infrastructure automation, almost
anything is fine. For a collection you intend to vendor into a product
you ship, GPL-3.0 is a decision for somebody other than you.
A collection with license_file: '' and an empty license list is a
collection whose author did not think about it, which is a small signal
about everything else.
The verdict
Write it down. Four lines is enough:
community.docker 4.7.0 - approved for the container estate, 2026-08-11
Publisher: Ansible Docker Working Group, github.com/ansible-collections
Maintenance: regular minor + patch releases; changelog current; CI matrix
covers 2.21
Drags in: community.library_inventory_filtering_v1 (pinned 1.1.5 by us,
declared >=1.0.0 upstream)
Privilege: docker group on target; 2 of 38 modules shell out; password
parameters carry no_log
Check mode: partial on docker_container, documented caveat on image pull
Licence: GPL-3.0-or-later / Apache-2.0 - fine, internal use only
Reviewed by: <name> Re-review on major version bump.The point of writing it is not the document. It is that a verdict somebody can disagree with is a verdict, and “we’ve always used it” is not. When the next major version lands, this file tells the reviewer what changed since somebody last thought about it.
Knowledge check
Knowledge check · 4 questions
Q1. Reviewing a collection, which part of the plugins/ tree deserves the closest reading and typically gets the least?
Q2. Which of these are genuine signals when judging whether a collection is maintained? Select all that apply.
Q3. A module takes a password parameter and its argument spec reads password=dict(type='str'). What is the finding?
Q4. A collection installed as a transitive dependency needs the same intake review as one you named in requirements.yml.
Passing score: 75%. Answers are checked in this browser.