Hey all!
The Problem
I love the flexibility of the ActiveSupport::ParameterFilter
. It’s useful to be able to specify filters as regexes or strings. I also appreciate the ability to target specific hash subkeys using dot notation.
However, I’m finding it difficult to target only top-level keys. For example:
{
data: "filter me"
nested: {
data: "keep me"
}
}
I can initialize ActiveSupport::ParameterFilter
with:
-
'data'
,/data/
, or even/^data$/
to redact both “keep me” and “filter me”, or -
'nested.data'
to redact only “keep me”,
but in order to only redact “filter me,” I need to circumvent the “deep filter” inference by pretending I’m using dot notation:
/^data\.{0}$/
The \.{0}
in the above regex is designed to match the “includes \.
” rule in the code linked above, and does nothing else. This feels hacky.
Possible Solutions?
I would love suggestions for how the interface might be changed or augmented to accommodate slightly more general subkey matching that is both backwards-compatible and solves the problem described above. Here’s what I’ve vaguely imagined so far:
A) Add an explicit deep_filters:
keyword argument to the ActiveSupport::ParameterFilter
initializer.
This would allow callers to specify the initial deep filters directly, thereby bypassing inference during compilation for those filters. Callers still passing dot-notation filters through the filters
positional argument could continue doing so; we’d tack on any inferred deep filters to the explicit ones. (We’d still compile the supplied deep filter strings and regexes.)
Example usage:
params = {
data: "filter me"
nested: {
data: "keep me"
}
}
ActiveSupport::ParameterFilters.new(deep_filters: ['data']).filter(params)
# => {data: "[FILTERED]", nested: {data: "keep me"}}
B) Prepend a root symbol to the path string.
Currently, as we traverse the parameter tree, we maintain a path segment stack, then join each segment with .
to match the full path against deep filters, like so:
{
data: "filter me" # 'data'
nested: { # 'nested'
data: "keep me" # 'nested.data'
}
}
What if we prepended a symbol to indicate the root, à la JSONPath? Using $
, for example:
{
data: "filter me" # '$.data'
nested: { # '$.nested'
data: "keep me" # '$.nested.data'
}
}
Then, to redact:
-
only “filter me”, pass
'$.data'
-
only “keep me”, pass
'$.nested.data'
-
both “keep me” and “filter me,” pass
data
,/data/
, or similar, as before
This approach seems a bit more complicated, as we’d have to tweak deep string filter compilation (probably by prepending $.
at some point), and we’d have to think hard about how this would affect existing matching behavior against keys with dots or dollar signs, e.g.:
{
"$$": "dollars" # '$.$$'
"$.$": "dollars" # '$.$.$'
".nested.": { # '$..nested.'
"$$": "dollars" # '$..nested.$$'
}
}
So…
Any thoughts?