mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 18:58:00 +00:00
Merge pull request #1639 from DeusData/fix/yaml-interior-anchor-chars
fix(yaml): treat interior `*` and `&` as text, not as alias/anchor indicators
This commit is contained in:
2 files changed
+72
-1
No files matched your search
@@ -1254,23 +1254,54 @@ static int yaml_find_comment(const char *data, size_t start, size_t end, size_t
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* `&` and `*` are anchor/alias indicators only where a NODE begins. Once a
|
||||
* plain scalar has started, they are ordinary characters.
|
||||
*
|
||||
* This used to reject them anywhere in the range, so a value containing prose
|
||||
* asterisks — `use *emphasis* here`, a kaomoji, a glob in a description — was
|
||||
* refused as if it were an alias. Reported against a real 16 KB Hermes config
|
||||
* where this was one of four constructs that made the file permanently
|
||||
* un-editable by us (#1631, root-caused by @rg6304 with an isolated repro).
|
||||
*
|
||||
* The test is positional and deliberately conservative: an indicator is only
|
||||
* honoured as one when nothing has appeared before it in the value. `key: *a`
|
||||
* is still an alias and still refused; `key: 2 * 3` and `key: a*b` are text.
|
||||
* `{`/`}` keep their existing treatment — empty flow mappings are a separate
|
||||
* change with its own semantics. */
|
||||
static int yaml_range_has_unsupported(const char *data, size_t start, size_t end) {
|
||||
char quote = '\0';
|
||||
/* Last significant character seen, so an indicator can be judged by what
|
||||
* PRECEDES it. The scanned range covers a whole line including its key, so
|
||||
* "first character in the range" is not the same as "start of a node" —
|
||||
* in `command: &shared` the `&` is an anchor even though `command:` came
|
||||
* first. A node begins at the range start, after a mapping colon, or after
|
||||
* a block-sequence dash. */
|
||||
char previous = '\0';
|
||||
for (size_t i = start; i < end; i++) {
|
||||
bool is_plain = false;
|
||||
if (yaml_scan_quote(data, end, &i, "e, &is_plain) != 0) {
|
||||
return YAML_MATCH;
|
||||
}
|
||||
if (!is_plain) {
|
||||
previous = 'q';
|
||||
continue;
|
||||
}
|
||||
char c = data[i];
|
||||
if (c == '#' && (i == start || data[i - YAML_UNIT] == ' ')) {
|
||||
break;
|
||||
}
|
||||
if (c == '{' || c == '}' || c == '&' || c == '*') {
|
||||
if (c == '{' || c == '}') {
|
||||
return YAML_MATCH;
|
||||
}
|
||||
if (c == '&' || c == '*') {
|
||||
bool begins_node = previous == '\0' || previous == ':' || previous == '-';
|
||||
if (begins_node) {
|
||||
return YAML_MATCH;
|
||||
}
|
||||
}
|
||||
if (c != ' ' && c != '\t') {
|
||||
previous = c;
|
||||
}
|
||||
if (c == '<' && i + YAML_ENTRY_INDENT < end && data[i + YAML_UNIT] == '<' &&
|
||||
data[i + YAML_ENTRY_INDENT] == ':') {
|
||||
return YAML_MATCH;
|
||||
|
||||
@@ -1501,6 +1501,44 @@ TEST(config_yaml_edit_nested_sequence_ambiguity_fails_byte_identically) {
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
/* #1631: an interior `*` in a written value is ordinary text, not an alias.
|
||||
* A real 16 KB Hermes config was permanently un-editable because prose
|
||||
* asterisks in a personality string were read as aliases (root-caused by
|
||||
* @rg6304 with an isolated repro).
|
||||
*
|
||||
* The asterisk is in the ENTRY BLOCK deliberately: that is the range the
|
||||
* editor validates. An earlier version of this test put it in an untouched
|
||||
* foreign section, which is never scanned - so it passed with the fix
|
||||
* reverted and proved nothing. */
|
||||
TEST(config_yaml_edit_accepts_interior_asterisk_in_plain_scalar_issue1631) {
|
||||
const char *original = "existing:\n keep: 1\n";
|
||||
yaml_fixture_t fixture;
|
||||
ASSERT_EQ(yaml_fixture_init(&fixture, original), 0);
|
||||
ASSERT_EQ(cbm_yaml_upsert_mapping_entry(fixture.path, "hooks", "cbm",
|
||||
" value: use *emphasis* and 2 * 3\n"),
|
||||
0);
|
||||
char *after = yaml_read_alloc(fixture.path);
|
||||
ASSERT_NOT_NULL(after);
|
||||
ASSERT_NOT_NULL(strstr(after, "value: use *emphasis* and 2 * 3"));
|
||||
free(after);
|
||||
ASSERT_EQ(cbm_unlink(fixture.path), 0);
|
||||
th_cleanup(fixture.dir);
|
||||
PASS();
|
||||
}
|
||||
TEST(config_yaml_edit_still_refuses_leading_alias_issue1631) {
|
||||
const char *original = "existing:\n keep: 1\n";
|
||||
yaml_fixture_t fixture;
|
||||
ASSERT_EQ(yaml_fixture_init(&fixture, original), 0);
|
||||
ASSERT(cbm_yaml_upsert_mapping_entry(fixture.path, "hooks", "cbm", " value: *alias\n") != 0);
|
||||
char *after = yaml_read_alloc(fixture.path);
|
||||
ASSERT_NOT_NULL(after);
|
||||
ASSERT_STR_EQ(after, original);
|
||||
free(after);
|
||||
ASSERT_EQ(cbm_unlink(fixture.path), 0);
|
||||
th_cleanup(fixture.dir);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(config_yaml_edit_nested_sequence_rejects_symlink_byte_identically) {
|
||||
const char *original = "hooks:\n pre_llm_call:\n - id: \"other\"\n";
|
||||
yaml_fixture_t fixture;
|
||||
@@ -1573,6 +1611,8 @@ SUITE(config_yaml_edit) {
|
||||
RUN_TEST(config_yaml_edit_nested_sequence_removes_only_exact_canonical_item);
|
||||
RUN_TEST(config_yaml_edit_nested_sequence_ambiguity_fails_byte_identically);
|
||||
#ifndef _WIN32
|
||||
RUN_TEST(config_yaml_edit_accepts_interior_asterisk_in_plain_scalar_issue1631);
|
||||
RUN_TEST(config_yaml_edit_still_refuses_leading_alias_issue1631);
|
||||
RUN_TEST(config_yaml_edit_nested_sequence_rejects_symlink_byte_identically);
|
||||
#endif
|
||||
}
|
||||
Reference in new issue
Block a user