Escape all CSDL attribute values on emit; decode entity references on parse (ARN-237) - #409
Open
NALLSUR wants to merge 2 commits into
Conversation
`attr_str` returned `Attribute::value` verbatim, which is the raw, still-escaped byte range. Any CSDL containing `&`, `"` or a numeric character reference in an attribute therefore parsed into the typed model as the literal escape text rather than the character it denotes. This is load-bearing for the emitter escaping that follows: with the emitter escaping correctly and the parser not unescaping, every parse/emit/parse cycle would add a layer (`&` -> `&` -> `&amp;`). The two changes are only correct together. No existing behaviour changes for the current corpus — no CSDL fixture in the repository contains an entity reference — so this commit is a no-op for the suite and is separated purely to keep the shared read-path change reviewable on its own. Refs ARN-237. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`emit_csdl_xml` applied `xml_escape` to only 4 of roughly 30
interpolation sites. Names, types, namespaces, DefaultValue,
navigation-binding Path/Target, action/function references and
annotation terms were all written into attributes verbatim. Those values
are agent- and user-influenced, so a `"` in an identifier closed the
attribute and let the remainder of the value inject arbitrary markup —
the same defect class as ARN-172. A property named
Name"/><Property Name="Smuggled
produced a second, unintended property element, and a smuggled
`HasStream="true"` altered the typed model on re-parse.
Every interpolated string value now passes through `xml_escape`. The
remaining unescaped interpolations are numeric (`i64`, `u32`, `f64`,
`bool`) and cannot produce a metacharacter.
`xml_escape` additionally escapes tab, newline and carriage return as
character references. XML attribute-value normalisation replaces literal
occurrences with spaces, so without this a multi-line DefaultValue or
Description came back altered even when the quoting was correct. The
function is also now a single pass rather than five chained `replace`
calls.
Tests cover both properties: `adversarial_identifiers_do_not_inject_markup`
asserts that no live markup is emitted and that the typed model survives
a round trip unchanged, across entity types, keys, properties,
annotations, containers, entity sets, navigation bindings and
action/function imports; `whitespace_in_attribute_values_round_trips`
pins the normalisation behaviour.
This is the emitter half of ARN-237. The strict-parsing half — rejecting
truncated schemas, mandatory attributes, unknown-element rejection — is
deliberately deferred: it increases the set of persisted schemas that
fail to load, which interacts directly with ARN-190, where one corrupt
tenant CSDL aborts the entire registry restore.
Refs ARN-237.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NALLSUR
force-pushed
the
surendranalla/arn-237-bug-csdl-parser-accepts-truncated-or-defaulted-schemas-and
branch
from
July 22, 2026 21:57
9e40ee1 to
a5e8053
Compare
NALLSUR
marked this pull request as ready for review
July 23, 2026 19:18
| .flatten() | ||
| .find(|attribute| std::str::from_utf8(attribute.key.as_ref()).unwrap_or("") == name) | ||
| .and_then(|attribute| String::from_utf8(attribute.value.to_vec()).ok()) | ||
| .and_then(|attribute| attribute.unescape_value().ok()) |
There was a problem hiding this comment.
Malformed entities erase optional attributes
When an optional attribute contains a malformed entity reference, converting unescape_value() errors to None makes the parser treat the present attribute as absent, causing annotations to be dropped or values such as nullability, defaults, paths, and targets to silently revert to fallback values.
Context Used: CLAUDE.md (source)
Knowledge Base Used: Temper spec format, codegen, and verification
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/temper-spec/src/csdl/parser/xml.rs
Line: 51
Comment:
**Malformed entities erase optional attributes**
When an optional attribute contains a malformed entity reference, converting `unescape_value()` errors to `None` makes the parser treat the present attribute as absent, causing annotations to be dropped or values such as nullability, defaults, paths, and targets to silently revert to fallback values.
**Context Used:** CLAUDE.md ([source](https://github.com/nerdsane/temper/blob/main/CLAUDE.md))
**Knowledge Base Used:** [Temper spec format, codegen, and verification](https://app.greptile.com/arni-labs/-/custom-context/knowledge-base/nerdsane/temper/-/docs/temper-spec.md)
How can I resolve this? If you propose a fix, please make it concise.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Emitter half of ARN-237: CSDL metadata emitted by
emit_csdl_xmlapplied XML escaping to only 4 of ~30 attribute interpolation sites, so agent- or user-influenced identifiers (entity/property names, namespaces, DefaultValue, navigation-binding Path/Target, annotation terms, action/function references) could close their attribute and inject arbitrary markup — the same defect class as ARN-172. A property namedName"/><Property Name="Smuggledproduced a second property element; a smuggledHasStream="true"altered the typed model on re-parse.Two commits, each independently green (bisect-safe):
attr_str(crates/temper-spec/src/csdl/parser/xml.rs) —attr_strreturned the raw, still-escaped attribute bytes, so&/"/
surfaced as literal escape text in the typed model. This is load-bearing for commit 2, not incidental: the old emitter and parser bugs were compensating, and fixing emitter escaping alone would double-escape on every parse/emit/parse cycle (&→&→&amp;). No fixture in the repo contains an entity reference, so this commit is a behavioral no-op for the existing corpus; it is separated purely to keep the shared read-path change reviewable on its own.crates/temper-spec/src/csdl/emit.rs) — every interpolated string now routes throughxml_escape; the only remaining raw interpolations are numeric types (i64,u32,f64,bool), which cannot produce a metacharacter.xml_escapeadditionally escapes tab/LF/CR as character references (XML attribute-value normalization otherwise silently rewrites them to spaces on re-parse) and is now a single pass instead of five chainedreplacecalls.Tests
adversarial_identifiers_do_not_inject_markup— builds a document whose namespace, entity-type name, key, property name, DefaultValue, annotation term, container name, entity-set name/type, navigation-binding path/target, and action/function imports all carry"><Injected/>-style payloads; asserts no live markup is emitted, the output re-parses, and the typed model survives the round trip byte-identical (including that the smuggledHasStream="true"does not take effect). Satisfies the ARN-237 acceptance line "parse/emit/parse round trips preserve the typed model for adversarial XML characters".whitespace_in_attribute_values_round_trips— pins the tab/LF/CR character-reference behavior.Verification
cargo test -p temper-spec: 269 + 3 integration, green; verified each commit passes independentlycargo test -p temper-server(primary CSDL consumer, ~20 test targets incl. DST suites): ~750 passed, 0 new failures. The 2 failures inspec_validate_endpointreproduce on unmodifiedmain— root-caused toobservefeature gating and filed as ARN-281cargo fmt --checkandcargo clippy --all-targetscleanDeliberately deferred — strict-parser half of ARN-237
Rejecting truncated schemas (EOF-as-success), mandatory attributes, exact numeric parsing, unknown-structure rejection, and reference validation are not in this PR. Strictness strictly increases the set of persisted schemas that fail to load, which interacts directly with ARN-190 (one corrupt tenant CSDL aborts the entire registry restore). Sequencing it after ARN-190 avoids turning currently-booting servers into non-booting ones. One reviewer finding rides with it:
unescape_value().ok()silently drops an optional attribute whose value contains a malformed entity reference; mandatory attributes still fail loudly viarequired_attr. The proper fix (attr_str → Result) is the strict-parser work.Tradeoff, plainly: until the strict half lands, malformed CSDL still parses permissively — this PR only guarantees that what we emit is well-formed and injection-free, and that legal escaped content round-trips faithfully.
🤖 Generated with Claude Code
Greptile Summary
The PR hardens CSDL XML round trips by:
Confidence Score: 3/5
The parser's silent handling of malformed entity references needs to be fixed before merging because it can erase optional CSDL data.
The new attribute decoding path converts unescape errors to absence, so malformed values silently select defaults or drop annotations instead of preserving or reporting the input error.
crates/temper-spec/src/csdl/parser/xml.rs
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart LR A[CsdlDocument] --> B[emit_csdl_xml] B --> C[xml_escape all string attributes] C --> D[Well-formed CSDL XML] D --> E[parse_csdl] E --> F[attr_str unescapes references] F --> G[Round-tripped CsdlDocument]Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "Escape every attribute value emitted in ..." | Re-trigger Greptile
Context used: