fix(redact): catch external targets missing TargetMode - #238
Conversation
Greptile SummaryThis PR expands relationship redaction to classify targets as external when they have an RFC 3986 scheme or are protocol-relative, even without
Confidence Score: 3/5This PR should not merge until raw backslash UNC relationship targets are also classified and redacted. The new fallback closes several missing-TargetMode leaks but still preserves sensitive server and share paths for reachable raw UNC targets. Files Needing Attention: crates/ooxml-redact/src/xml.rs, crates/ooxml-redact/src/tests.rs
|
| Filename | Overview |
|---|---|
| crates/ooxml-redact/src/xml.rs | Adds fallback external-target classification, but misses raw backslash UNC targets and can leave sensitive paths unredacted. |
| crates/ooxml-redact/src/tests.rs | Adds useful scheme and fragment coverage, but the UNC case is scheme-prefixed and does not cover the remaining raw UNC leak. |
| .changeset/redact-external-links.md | Adds an appropriate patch release note for the expanded relationship redaction. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Relationship Target] --> B{TargetMode External?}
B -- Yes --> E[Rewrite to safe placeholder]
B -- No --> C{Starts with // or URI scheme?}
C -- Yes --> E
C -- No --> I[Keep as internal target]
U[Raw backslash UNC path] --> C
C -. Misclassified .-> I
Reviews (1): Last reviewed commit: "fix(redact): catch external targets miss..." | Re-trigger Greptile
| fn external_target(target: &str) -> bool { | ||
| let lower = target.trim().to_ascii_lowercase(); | ||
| lower.starts_with("//") | ||
| || lower | ||
| .split_once(':') | ||
| .is_some_and(|(scheme, _)| is_uri_scheme(scheme)) | ||
| } |
There was a problem hiding this comment.
Raw UNC targets remain unredacted
When a relationship uses a raw backslash UNC target such as \\server\share\x.xlsx without TargetMode="External", external_target rejects both branches and preserves the sensitive server/share path in the redacted package. How this was verified: Raw UNC targets have neither the checked // prefix nor a URI-scheme colon, while only targets passing this predicate are rewritten.
| fn external_target(target: &str) -> bool { | |
| let lower = target.trim().to_ascii_lowercase(); | |
| lower.starts_with("//") | |
| || lower | |
| .split_once(':') | |
| .is_some_and(|(scheme, _)| is_uri_scheme(scheme)) | |
| } | |
| fn external_target(target: &str) -> bool { | |
| let lower = target.trim().to_ascii_lowercase(); | |
| lower.starts_with("//") | |
| || lower.starts_with(r"\\") | |
| || lower | |
| .split_once(':') | |
| .is_some_and(|(scheme, _)| is_uri_scheme(scheme)) | |
| } |
Knowledge Base Used:
|
All contributors have signed the CLA — thank you! ✍️ ✅ Posted by the CLA bot. |
1530170 to
1e68276
Compare
Relationship targets that name a UNC share or climb above the package root are now redacted alongside scheme-bearing ones, and a relationship whose external mode was inferred gets TargetMode="External" written out so the placeholder is not read back as an internal part reference. Co-Authored-By: codex <codex@openai.com>
Relationship handling now applies only to `.rels` parts and unqualified OPC attributes, a target that climbs above the package root is external only when the clamped path names no part, and a relationship the target proves external always carries TargetMode="External" so the placeholder and the declared mode agree. Co-Authored-By: codex <codex@openai.com>
A target that climbs above the package root is no longer classified as external. The part index it was checked against compared raw lowercased names, so percent-encoded, dot-segment and backslash spellings of parts the package really contains were rewritten to the placeholder and the document lost the part. Scheme, protocol-relative and UNC detection, TargetMode normalization and the inferred TargetMode declaration stay. Co-Authored-By: codex <codex@openai.com>
884f341 to
6f050ae
Compare
Scoping relationship handling to .rels parts also dropped main's rule that an explicit TargetMode="External" rewrites the target wherever it appears. Target inspection stays scoped to .rels; a declared external mode is honoured again everywhere, and the mode is written back under its canonical name so a lowercase targetmode spelling no longer leaves the redacted package with a placeholder URI the parsers read as internal. Co-Authored-By: codex <codex@openai.com>
Two well-formed inputs came out worse than they went in. A relationship carrying both Target and a case-variant target let the secondary spelling decide the mode, so the authoritative internal target was replaced and the slide went missing; the shape is now read from the exact-case Target that consumers resolve. A relationship carrying two case-variant mode attributes emitted two canonical TargetMode attributes, which no longer parses; the canonical mode is now written once. Co-Authored-By: codex <codex@openai.com>
TL;DR:
Summary:
TargetMode=External(whitespace- and case-tolerant), or when the target carries an RFC 3986 scheme, is protocol-relative (//host/share), or names a UNC share (\\host\share,\\?\UNC\...)TargetMode="External"— synthesized when absent, normalized when the producer spelled it oddly, corrected when the producer declaredInternal— so the placeholder target and the declared mode never disagree and the redacted package still parses.relsparts and to unqualified OPC attributes, so foreign namespaced attributes andRelationship-shaped markup in ordinary parts are left alonepart.xml#ref=https://...), package-root targets (/word/media/x.png) and relative targets stay internal and byte-identicalTest plan:
cargo test -p betteroffice-redactgreencargo test --workspace,cargo clippy --workspace --all-targets --all-features -- -D warningsandcargo fmt --all -- --checkgreenTargetModeUNC hyperlink and confirm the host does not survive in the output zipdocx-parse/xlsx-parse/pptx-parse./and backslash spellings