# How to register secrets - as plugin developers, and users?

**URL:** <https://forum.ansible.com/t/how-to-register-secrets-as-plugin-developers-and-users/46448>\
**Category:** Project Discussions\
**Tags:** security, ansible-devel\
**Created:** [October 11, 2026, 9:03pm UTC](https://forum.ansible.com/t/how-to-register-secrets-as-plugin-developers-and-users/46448 "2026-10-11T21:03:31Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![felixfontein](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/felixfontein/32/7530_2.png) [@felixfontein](https://forum.ansible.com/u/felixfontein)\
**Post date:** [October 11, 2026, 9:03pm UTC](https://forum.ansible.com/t/how-to-register-secrets-as-plugin-developers-and-users/46448/1 "2026-10-11T21:03:31Z")

</div>

This is [another discussion](https://forum.ansible.com/t/46388) about the new [Secret masking feature of ansible-core 2.22](https://forum.ansible.com/t/46291). I started working on some PRs in community.general to make lookup plugins that retrieve secrets automatically register sensitive values they look up as secrets ([PR 1](https://github.com/ansible-collections/community.general/pull/12727), [PR 2](https://github.com/ansible-collections/community.general/pull/12738), [PR 3](https://github.com/ansible-collections/community.general/pull/12739), [PR 4](https://github.com/ansible-collections/community.general/pull/12740), [PR 5](https://github.com/ansible-collections/community.general/pull/12741), [PR 6](https://github.com/ansible-collections/community.general/pull/12742)). While working on them, I started having doubts on whether this is actually a good idea (for [some plugins in community.sops](https://github.com/ansible-collections/community.sops/pull/304), I changed the default to [“do not register anything as secrets, unless the user explicitly asks for it”](https://github.com/ansible-collections/community.sops/pull/304/changes/32e4df74db4700a81108577a0a78c0196d65880c)). This is more critical now that ansible-core 2.22 will likely also [register and mask secrets that are less than 4 letters long, and mask 4 and 5 letter long secrets also inside words and not just at word boundaries](https://github.com/ansible/ansible/pull/87648): accidentally registering non-secret values can cause big pain, especially for short and generic texts (like `0`, `5`, `test`, `user`, `localhost`, …). Think of a random playbook output where all these texts (even the mentioned individual digits!) are replaced by `$REDACTED$`.

Right now, I only merged code that automatically registers secrets (I explicitly exclude values to options that are marked with `secret: true` here to restrict scope of the discussion) in case the values are obviously secrets, such as [GitHub tokens](https://github.com/ansible-collections/community.general/pull/12884) or [Docker Swarm tokens](https://github.com/ansible-collections/community.docker/pull/1312/changes#diff-d4ae45e6fd5dc4d3bae7335d2871cb28074d6f550246834c41b2a6ade0f8a918). Everything that retrieves values created by humans, such as passwords stored in password/secret/vault managers (see most of the PRs linked above) IMO have a certain chance of containing non-sensitive, trivial strings (see examples above) that are only stored in such places for convenience reasons, and would be dangerous to register.

I think it would be better (instead of registering such values) to make it easier for users to register output of plugins as secrets. Right now, there’s the [ansible.builtin.register\_secret](https://docs.ansible.com/projects/ansible/devel/collections/ansible/builtin/register_secret_filter.html) filter which registers individual strings. If you have a data structure where some parts are secrets, it’s pretty painful to register these secrets, especially if you want to do it very close to the lookup to prevent secrets to show up in logs (think of verbose output which prints the results of a task).

IMO it would be best if you can directly register **parts** of the output of a lookup / filter plugin or a module as secrets.

Think of a lookup / filter which returns a data structure:

```auto
- foo: this is a secret # this is sensitive
  bar: hello
- foo: another secret # this is sensitive
  bar: world
- foo: true # if you feed this to ansible.builtin.register_secret, it will barf
  members:
    - user: foo
      password: bar # this is sensitive
    - user: baz
      token: asdlkjfa98321329 # this is sensitive

```

Try to come up with some Jinja2 that takes this result - as a first step, assume it has already been stored in a variable - and registers all the secrets in it with `ansible.builtin.register_secret`, without accidentally passing `true` to it. That’s definitely not fun. (I’d likely use [community.general.json\_query](https://docs.ansible.com/projects/ansible/latest/collections/community/general/json_query_filter.html) as a building block.)

And then, as the next level, try to do this as a list of filters directly after `lookup(...)` / `query(...)` / `| filter` so that you actually get the unmodified values, while at the same time registering the secrets. (I don’t think this is possible without creating a new filter plugin.) Otherwise, if you do:

```yaml
- name: Retrieve data
  ansible.builtin.set_fact:
    my_value: '{{ lookup("foo.bar.lookup", whatever) }}'-
- name: Register secrets:
  ansible.builtin.set_fact:
    _secrets: >-
      {{ my_value | community.general.json_query(...) | filter(...) | map("ansible.builtin.register_secret") }}

```

and run the playbook with `-v`, the secret values are printed in clear during the first task. So you really want to write:

```auto
- name: Retrieve data and register secrets
  ansible.builtin.set_fact:
    my_value: '{{ lookup("foo.bar.lookup", whatever) | ...magic happens here... }}'-

```

so that only the secrets are registered, but you end up with the same (complex) data structure as `my_value`.

I think we need some more flexible filter plugins that allow to register specific parts of their input as secrets, while ignoring things that shouldn’t be registered as secrets (like booleans, and maybe also too short strings or specific strings), while returning the original output.

For module results, this is likely more complex. What you could do is use register projections together with such filters to also register the secrets before the task is actually done; I haven’t used register projections yet (since I had to support older ansible-core versions as well) so I’m not sure how exactly they interact with callbacks etc., and whether this can actually be used to properly remove secret values returned by modules (that don’t themselves register these values as secrets)…

I’m starting this thread so we can collect ideas of how to do this - what kind of filters / other kind of plugins would be needed/userful, how to write tasks/… where secrets can appear in that should be hidden, etc.

---

<div class="post-metadata">

**Author:** ![jborean](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/jborean/32/1525_2.png) [@jborean](https://forum.ansible.com/u/jborean)\
**Post date:** [October 12, 2026, 1:08am UTC](https://forum.ansible.com/t/how-to-register-secrets-as-plugin-developers-and-users/46448/2 "2026-10-12T01:08:35Z")

</div>

Having Jinja deal with lists of dict values and selecting/transforming/filtering on specific attributes/values has always been confusing and a mess to me. On the flip side what constitutes a secret is very much a content/user driven choice so having a generic filter to deal with this is hard to get right. That’s why we have restricted the builtin filter to accept a single value and leave it up to the caller to redact. I think register projections make things simpler but I can respect that some people need to support older Ansible versions before this. The example we have in [Masking secrets in Ansible output — Ansible Community Documentation](https://docs.ansible.com/projects/ansible/devel/playbook_guide/playbooks_secret_masking.html#registering-your-own-secrets) I think shows a nice way for a module return result to register a value based on a content driven choice. Maybe having more examples here to show how to deal with different structure formats like selecting a specific attribute in a list would be nice to show here. I know we still need to update the docs to reflect the new length rules and behaviour in [Remove secret masking floor and ceiling by jborean93 · Pull Request #87648 · ansible/ansible · GitHub](https://github.com/ansible/ansible/pull/87648) but the general patterns would probably stay the same.

I’m sorry I don’t have a good suggestion here, I’m just not sure how this can be done generically without overcomplicating the implementation or UX of such a plugin.

While I do lament we are ending up moving away from the floor and word boundary checks I think in the end Ansible cannot make this decision for people and the alternative ends up being constant CVE reports and a lack of confidence in the mechanism if their short secrets end up being exposed in the output. We didn’t even get to the first beta before we had our first report 🙂 There’s always a chance we will revisit this decision and at least put the floor at 2 or 3 but only time will tell if these short secrets become an actual problem. Ultimately caution won out over what we deemed to be practical.

---

<div class="post-metadata">

**Author:** ![felixfontein](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/felixfontein/32/7530_2.png) [@felixfontein](https://forum.ansible.com/u/felixfontein)\
**Post date:** [October 12, 2026, 9:23pm UTC](https://forum.ansible.com/t/how-to-register-secrets-as-plugin-developers-and-users/46448/3 "2026-10-12T21:23:48Z")

</div>

> [@jborean](#):
>
> I’m sorry I don’t have a good suggestion here, I’m just not sure how this can be done generically without overcomplicating the implementation or UX of such a plugin.

I don’t think there is a generic way to handle this for _all_ cases, but I think that a (small) set of more generic filters could cover most cases.

A filter which accepts input (that it returns unchanged, unless some `eat=true` parameter is passed) and a sequence of inclusion/exclusion [JSON query paths](https://jmespath.org/examples.html) (as used by community.general.json\_query) / [jq expressions](https://jqlang.org/manual/#basic-filters) ([used in other contexts](https://forum.ansible.com/t/45595)) / something similar to select secret values (or reject non-secret values), would likely already cover a lot of cases.

Being able to define schemas for this (maybe stored as ansible vars, or also in other ways?) and reusing them in multiple places would likely also be useful, if you need to process similar lookup/query results in different places.

---

<div class="post-metadata">

**Author:** ![jborean](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/jborean/32/1525_2.png) [@jborean](https://forum.ansible.com/u/jborean)\
**Post date:** [October 12, 2026, 10:16pm UTC](https://forum.ansible.com/t/how-to-register-secrets-as-plugin-developers-and-users/46448/4 "2026-10-12T22:16:10Z")

</div>

That’s a good suggestion, having some sort of jquery syntax to specify such things makes it a lot more versatile and fits into an existing language that someone may already know. I believe the `json_query` had some issues with dependencies way back when and I don’t know if the same is true for jquery. The last time I checked [jq](https://pypi.org/project/jq/) (not `jquery` on PyPI) could be an option as it ships the actual jq engine as part of some wheels so it’s simple enough to install.

---

<div class="post-metadata">

**Author:** ![kks](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/kks/32/10352_2.png) [@kks](https://forum.ansible.com/u/kks)\
**Post date:** [October 13, 2026, 6:24am UTC](https://forum.ansible.com/t/how-to-register-secrets-as-plugin-developers-and-users/46448/5 "2026-10-13T06:24:44Z")

</div>

> [@felixfontein](#):
>
> but I think that a (small) set of more generic filters could cover most cases.

Looks like you would like to mark all the secrets in one go, but you do not have to. E.g. for your example:

```yaml
my_value:
- foo: this is a secret # this is sensitive
  bar: hello
- foo: another secret # this is sensitive
  bar: world
- foo: true # if you feed this to ansible.builtin.register_secret, it will barf
  members:
    - user: foo
      password: bar # this is sensitive
    - user: baz
      token: asdlkjfa98321329 # this is sensitive

```

```yaml
- name: Register secrets:
  ansible.builtin.set_fact:
    __register_secret1: >-
      {{ my_value | selectattr('foo', defined) | selectattr('bar', defined) | 
 map(attribute='foo') | map("ansible.builtin.register_secret") }}
    __register_secret2: >-
      {{ my_value | selectattr('foo', defined) | selectattr('members', defined) | 
 map(attribute='members') | flatten | selectattr('password', defined) | map(attribute='password') | map("ansible.builtin.register_secret") }}
    __register_secret3: >-
      {{ my_value | selectattr('foo', defined) | selectattr('members', defined) | 
 map(attribute='members') | flatten | selectattr('token', defined) | map(attribute='token') | map("ansible.builtin.register_secret") }}

```

So all the pieces needed are present in ansible (for majority of use cases). Here the fact that all the variables in set\_fact will be evaluated comes in handy.

PS not quite sure that jinjas are correct, hope you get the idea.

---

<div class="post-metadata">

**Author:** ![felixfontein](https://sea1.discourse-cdn.com/ansible/user_avatar/forum.ansible.com/felixfontein/32/7530_2.png) [@felixfontein](https://forum.ansible.com/u/felixfontein)\
**Post date:** [October 13, 2026, 9:33am UTC](https://forum.ansible.com/t/how-to-register-secrets-as-plugin-developers-and-users/46448/6 "2026-10-13T09:33:33Z")

</div>

> [@kks](#):
>
> ooks like you would like to mark all the secrets in one go, but you do not have to.

It’s definitely true that you _can_ do it without new plugins, in the way you showed in your example. But there are some UX problems with it:

1. Registering secrets is a separate task to retrieving the value. This forces you to use a general `no_log: true` for the task that actually retrieves the secrets (to avoid leakage), which makes debugging it in case it fails very hard (you won’t even see the error message, just see that the task failed). Especially if you want to use the retrieved value to pass it into a module: you now need three tasks (retrieve infos, register secrets, call module) instead of one (call module with retrieved infos where secrets are registered through a filter).
2. Having to use pure Jinja results in a lot of boilerplate that’s harder to read _and_ write.
3. You end up with having new variables (`__register_secret1`, …) that contain secrets that are just lying around. This is also a general problem when using register projections to register secrets; having a filter which replaces its input by a constant (like `null`) would at least avoid having the secret values lying around, but you still have an extra amount of variables now being there.

It’s definitely OK for now and for some secrets, but having some easier to use mechanisms would definitely be helpful 🙂 Think of being able to just write `my_value | mark_as_secrets(include='[].foo', include='[].members[].password', include='[].members[].token')` for only registering what you want or instead `my_value | mark_as_secrets(exclude='[].bar', exclude='[].members[].user')` for defense in depth (if new fields are added, they are automatically registered as well). Obviously, these examples aren’t working Jinja since the same keyword argument is used more than once in the filter, and I’m not sure whether the selection syntax is valid in any of the JSON query syntaxes (I’ve never really used any of them), but it should show the general idea 🙂
