Compare commits

...

2 Commits

Author SHA1 Message Date
Leonardo Grasso
68ef3cc864 docs(userspace): document backslash escaping to -o key-path parser
Signed-off-by: Leonardo Grasso <me@leonardograsso.com>
2026-03-24 12:55:17 +01:00
Leonardo Grasso
cfb5227062 fix(userspace): add backslash escaping to -o key-path parser
Signed-off-by: Leonardo Grasso <me@leonardograsso.com>
2026-03-24 12:50:53 +01:00
3 changed files with 88 additions and 3 deletions

View File

@@ -102,6 +102,58 @@ TEST(Configuration, modify_yaml_fields) {
ASSERT_EQ(conf.get_scalar<bool>(key, false), true);
}
static std::string escaped_keys_yaml =
"annotations:\n"
" kubernetes.io/role: master\n"
" example[0]: bracket_val\n"
"nested:\n"
" level1.with.dots:\n"
" inner: value\n"
"escape_test:\n"
" back\\slash: bslash_val\n";
TEST(Configuration, escaped_keys_read) {
yaml_helper conf;
conf.load_from_string(escaped_keys_yaml);
/* Key containing literal dots */
ASSERT_STREQ(conf.get_scalar<std::string>("annotations.kubernetes\\.io/role", "none").c_str(),
"master");
/* Key containing literal bracket */
ASSERT_STREQ(conf.get_scalar<std::string>("annotations.example\\[0]", "none").c_str(),
"bracket_val");
/* Multiple escaped dots */
ASSERT_STREQ(conf.get_scalar<std::string>("nested.level1\\.with\\.dots.inner", "none").c_str(),
"value");
/* Key containing literal backslash */
ASSERT_STREQ(conf.get_scalar<std::string>("escape_test.back\\\\slash", "none").c_str(),
"bslash_val");
}
TEST(Configuration, escaped_keys_write) {
yaml_helper conf;
conf.load_from_string(escaped_keys_yaml);
/* Modify a value accessed via escaped key */
conf.set_scalar<std::string>("annotations.kubernetes\\.io/role", "worker");
ASSERT_STREQ(conf.get_scalar<std::string>("annotations.kubernetes\\.io/role", "none").c_str(),
"worker");
}
TEST(Configuration, escaped_keys_errors) {
yaml_helper conf;
conf.load_from_string(escaped_keys_yaml);
/* Trailing backslash */
EXPECT_ANY_THROW(conf.get_scalar<std::string>("annotations\\", "none"));
/* Invalid escape sequence */
EXPECT_ANY_THROW(conf.get_scalar<std::string>("annotations\\x", "none"));
}
TEST(Configuration, configuration_webserver_ip) {
falco_configuration falco_config;

View File

@@ -369,17 +369,23 @@ private:
* nested nodes, with arbitrary depth. The key string follows
* this regular language:
*
* Key := NodeKey ('.' NodeKey)*
* NodeKey := (any)+ ('[' (integer)+? ']')*
* Key := NodeKey ('.' NodeKey)*
* NodeKey := (Char | EscSeq)+ ('[' (integer)+? ']')*
* EscSeq := '\' ('.' | '[' | '\')
*
* If can_append is true, an empty NodeKey will append a new entry
* to the sequence, it is rejected otherwise.
*
* Backslash escaping allows literal dots, brackets, and backslashes
* in key names, following the Helm --set conventions, e.g.:
* falco -o 'annotations.kubernetes\.io/role=master'
*
* Some examples of accepted key strings:
* - NodeName
* - ListValue[3].subvalue
* - MatrixValue[1][3]
* - value1.subvalue2.subvalue3
* - annotations.kubernetes\.io/role
*/
void get_node(YAML::Node& ret, const std::string& key, bool can_append = false) const {
try {
@@ -387,6 +393,33 @@ private:
ret.reset(m_root);
for(std::string::size_type i = 0; i < key.size(); ++i) {
char c = key[i];
// Handle backslash escaping
if(c == '\\') {
if(i + 1 >= key.size()) {
throw std::runtime_error("Parsing error: trailing backslash at pos " +
std::to_string(i));
}
char next = key[i + 1];
if(next != '.' && next != '[' && next != '\\') {
throw std::runtime_error("Parsing error: invalid escape sequence '\\" +
std::string(1, next) + "' at pos " +
std::to_string(i));
}
nodeKey += next;
++i;
// If this escaped char is the last in the key, shift now
if(i == key.size() - 1) {
if(nodeKey.empty()) {
throw std::runtime_error("Parsing error: unexpected character at pos " +
std::to_string(i));
}
ret.reset(ret[nodeKey]);
nodeKey.clear();
}
continue;
}
bool should_shift = c == '.' || c == '[' || i == key.size() - 1;
if(c != '.' && c != '[') {

View File

@@ -155,7 +155,7 @@ void options::define(cxxopts::Options& opts)
("markdown", "DEPRECATED: use --format markdown instead. Print output in Markdown format when used in conjunction with --list or --list-events options. It has no effect when used with other options.", cxxopts::value<bool>(markdown))
("format", "Print output in the specified <format> when used in conjunction with --list or --list-events options. Valid values are 'text', 'markdown', or 'json'. It has no effect when used with other options. Cannot be used together with --markdown.", cxxopts::value(format), "<format>")
("N", "Only print field names when used in conjunction with the --list option. It has no effect when used with other options.", cxxopts::value(names_only)->default_value("false"))
("o,option", "Set the value of option <opt> to <val>. Overrides values in the configuration file. <opt> can be identified using its location in the configuration file using dot notation. Elements of list entries can be accessed via square brackets [].\n E.g. base.id = val\n base.subvalue.subvalue2 = val\n base.list[1]=val", cxxopts::value(cmdline_config_options), "<opt>=<val>")
("o,option", "Set the value of option <opt> to <val>. Overrides values in the configuration file. <opt> can be identified using its location in the configuration file using dot notation. Elements of list entries can be accessed via square brackets []. Use backslash (\\) to escape literal dots or brackets in key names.\n E.g. base.id=val\n base.subvalue.subvalue2=val\n base.list[1]=val\n base.dotted\\.key=val", cxxopts::value(cmdline_config_options), "<opt>=<val>")
("plugin-info", "Print info for the plugin specified by <plugin_name> and exit.\nThis includes all descriptive information like name and author, along with the\nschema format for the init configuration and a list of suggested open parameters.\n<plugin_name> can be the plugin's name or its configured 'library_path'.", cxxopts::value(print_plugin_info), "<plugin_name>")
("p,print", "DEPRECATED: use -o append_output... instead. Print additional information in the rule's output.\nUse -pc or -pcontainer to append container details to syscall events.\nUse -pk or -pkubernetes to add both container and Kubernetes details to syscall events.\nThe details will be directly appended to the rule's output.\nAlternatively, use -p <output_format> for a custom format. In this case, the given <output_format> will be appended to the rule's output without any replacement to all events, including plugin events.", cxxopts::value(print_additional), "<output_format>")
("P,pidfile", "Write PID to specified <pid_file> path. By default, no PID file is created.", cxxopts::value(pidfilename)->default_value(""), "<pid_file>")