Thanks to visit codestin.com
Credit goes to github.com

Skip to content

๐Ÿ›ก๏ธ Sentinel: [MEDIUM] HTML BiDi ํ…์ŠคํŠธ ์กฐ์ž‘ ์šฐํšŒ ๋ฐฉ์ง€ - #682

Draft
seonghobae wants to merge 10 commits into
masterfrom
sentinel-bidi-fix-12923153437295710098
Draft

seonghobae wants to merge 10 commits into
masterfrom
sentinel-bidi-fix-12923153437295710098

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Scope

BiDi display isolation for user-controlled directory/file names in generated HTML. Protected target: master@728f0f33323e43573d6664209891099502827d5d. Current exact head: 36510f48d859e8fa41a235e9130f4e6f64142087.

Predecessor #672์˜ ์œ ํšจ source/test delta๋Š” ์ด branch๊ฐ€ ์ „๋ถ€ ์Šน๊ณ„ํ–ˆ์Œ์„ live patch๋กœ ๋‹ค์‹œ ํ™•์ธํ–ˆ๊ณ , #672๋Š” verified-successor ์กฐ๊ฑด์œผ๋กœ unmerged Closeํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด PR์ด ํ˜„์žฌ canonical implementation/security acceptance owner์ž…๋‹ˆ๋‹ค.

Causal RED and repair lineage

์ดˆ๊ธฐ ๊ตฌํ˜„์€ <title> / link title์„ FSI + escaped filename + PDI๋กœ ๊ฐ์‹ธ๊ณ  visible name์— dir="auto"๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. HTML escaping๋งŒ์œผ๋กœ Unicode directional formatting control์€ ์‚ฌ๋ผ์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ณต๊ฒฉ์ž๊ฐ€ filename์— U+2069 PDI๋ฅผ ๋„ฃ์œผ๋ฉด outer isolate๋ฅผ filename ์ค‘๊ฐ„์—์„œ ๋๋‚ผ ์ˆ˜ ์žˆ๊ณ , ๋’ค์ด์€ U+202E RLO ๊ฐ™์€ override๊ฐ€ wrapper ๋ฐ–์—์„œ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Test-first commit 3cf2442193eb7a31267c1cdace463e244703d81c๊ฐ€ hostile filename regression์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. Production์€ display/title context์—์„œ U+061C, U+200E/U+200F, U+202A..U+202E, U+2066..U+2069๋ฅผ visible \\uXXXX text๋กœ neutralizeํ•˜๋ฉด์„œ ์‹ค์ œ filesystem name์€ percent-encoded href generation์— ๊ทธ๋Œ€๋กœ ๋ณด์กดํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜ Arabic/Hebrew/CJK/LTR ๋ฌธ์ž๋Š” rewriteํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

24f5b0ee799019be39d3f57086ea5445b4927f6f์—์„œ generated .jules/sentinel.md doctrine์„ ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค. CI๊ฐ€ ์ƒˆ directional-control branches์˜ coverage gap์„ ์žก์€ ๋’ค current 36510f48d859e8fa41a235e9130f4e6f64142087์—์„œ ๋ชจ๋“  supported neutralized code point๋ฅผ exact visible \\uXXXX๋กœ ๊ฒ€์ฆํ•˜๋Š” focused coverage contract๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. Coverage threshold๋ฅผ ๋‚ฎ์ถ”๊ฑฐ๋‚˜ branch๋ฅผ ์ œ์™ธํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

Unicode UAX #9๊ฐ€ isolate matching ๋ฐ explicit directional formatting์˜ normative rationale์ž…๋‹ˆ๋‹ค. U+206A..U+206F๋Š” ๋ณ„๋„ deprecated format characters์ด๋ฉฐ ์ด contract์—์„œ current directional-isolate control๋กœ ์žฌ๋ถ„๋ฅ˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Exact-head verification

36510f48d859e8fa41a235e9130f4e6f64142087์—์„œ:

  • CI 34657409996: GREEN
  • Security Scan 34657410033: GREEN
  • SAST Semgrep 34657410003: GREEN
  • CodeQL PR 34657409995: FAIL โ€” central settlement ordering
    • Actions compatibility consumer: 2026-09-11 23:40:50Z ์‹œ์ž‘, 23:43:28Z terminal failure
    • Java/Kotlin compatibility consumer: 23:41:06Z ์‹œ์ž‘, 23:43:40Z terminal failure
    • authoritative dispatch๋Š” ๋‘ consumer failure ๋’ค์ธ 23:44:51Z์— ์‹œ์ž‘ํ•ด 23:44:58Z success

๋”ฐ๋ผ์„œ CodeQL failure๋ฅผ leaf source finding์œผ๋กœ ์˜ค๋ถ„๋ฅ˜ํ•˜์ง€ ์•Š์ง€๋งŒ, authoritative current-head settlement๊ฐ€ ์™„๋ฃŒ๋˜๊ธฐ ์ „ GREEN์œผ๋กœ๋„ ์ทจ๊ธ‰ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. No-op retrigger, synthetic status, gate weakening์€ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Delivery Gate

  • Intentionality: PASS โ€” hostile display-direction controls๋งŒ ์ œํ•œํ•˜๊ณ  actual path identity๋Š” ๋ณด์กดํ•ฉ๋‹ˆ๋‹ค.
  • Functional completeness: PARTIAL โ€” production repair์™€ code-point regression์€ exact-head CI GREEN์ด์ง€๋งŒ real-browser acceptance๊ฐ€ ๋‚จ์•˜์Šต๋‹ˆ๋‹ค.
  • Content fitness: PASS โ€” generated parallel doctrine์„ ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค.
  • Resilience: PARTIAL โ€” hostile control coverage๋Š” ์žˆ์œผ๋‚˜ mixed-direction/wrapping/focus browser evidence๊ฐ€ ๋‚จ์•˜์Šต๋‹ˆ๋‹ค.
  • Evidence: FAIL โ€” CodeQL central settlement, browser/accessibility-tree evidence, independent current-head review๊ฐ€ ํ•œ generation์—์„œ ์™„๊ฒฐ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.
  • Distinctiveness: N/A.

Acceptance before Ready

  1. BidiIsolationSecurityTest, all-code-point neutralization contract์™€ ๊ธฐ์กด generation/security tests๊ฐ€ repository 100% coverage๋ฅผ ์œ ์ง€ํ•˜๋ฉฐ GREEN์ผ ๊ฒƒ.
  2. mixed Arabic/Hebrew + Latin, ordinary LTR/CJK, long/wrapping names, page title, tooltip/title attribute, visible label, keyboard/focus ๋ฐ accessibility-tree behavior๋ฅผ real browser์—์„œ ๊ฒ€์ฆํ•  ๊ฒƒ.
  3. hostile filename ์ž์ฒด๋Š” href์—์„œ percent-encoded identity๋กœ ๋ณด์กด๋˜๊ณ  display/title์—๋Š” contract ๋Œ€์ƒ raw directional control์ด ์—†์„ ๊ฒƒ.
  4. CodeQL์„ ํฌํ•จํ•œ required gates์™€ independent current-head review๊ฐ€ suppression/no-op retrigger/gate weakening ์—†์ด settleํ•  ๊ฒƒ.

Draft ์œ ์ง€์ž…๋‹ˆ๋‹ค. Browser/a11y ๋ฐ central CodeQL acceptance๊ฐ€ ์—†์œผ๋ฏ€๋กœ ๋ณด์•ˆ ์™„๋ฃŒ๋‚˜ release-ready๋ฅผ ์ฃผ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

@google-labs-jules

Copy link
Copy Markdown

๐Ÿ‘‹ Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a ๐Ÿ‘€ emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

โค๏ธ Share

Comment @coderabbitai help to get the list of available commands.

@cwl-noema-review cwl-noema-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noema LLM review

The BiDi mitigation can be bypassed because escapeHtml() does not encode Unicode directional isolates U+2068/U+2069. A filename containing a literal U+2069 prematurely closes the FSI wrap, exposing surrounding text to malicious directionality and allowing UI spoofing. The fix needs to sanitize or reject directional isolates before approval.

Reviewed changed lines

  • .jules/sentinel.md:102 (RIGHT): Documentation entry describes FSI/PDI mitigation; no code impact.
  • .jules/sentinel.md:103 (RIGHT): Documentation entry describes FSI/PDI mitigation; no code impact.
  • .jules/sentinel.md:104 (RIGHT): Documentation entry describes FSI/PDI mitigation; no code impact.
  • .jules/sentinel.md:105 (RIGHT): Documentation entry describes FSI/PDI mitigation; no code impact.
  • .jules/sentinel.md:106 (RIGHT): Documentation entry describes FSI/PDI mitigation; no code impact.
  • src/main/kotlin/html4tree/main.kt:424 (RIGHT): FSI/PDI wraps escaped directory name in title; wrapping can be broken by literal U+2068/U+2069 because escapeHtml leaves them unencoded.
  • src/main/kotlin/html4tree/main.kt:429 (RIGHT): dir="auto" on h1 handles normal LTR names correctly.
  • src/main/kotlin/html4tree/main.kt:460 (RIGHT): ariaLabel retains escapeHtml before FSI/PDI; still affected by literal directional-isolate bypass.
  • src/main/kotlin/html4tree/main.kt:463 (RIGHT): Title attribute uses same FSI/PDI wrapping and has same bypass risk for literal U+2068/U+2069.
  • src/test/kotlin/html4tree/MainTest.kt:342 (RIGHT): Updated assertion matches current output; does not cover malicious directional isolates.
  • src/test/kotlin/html4tree/MainTest.kt:344 (RIGHT): Updated assertion matches current output; does not cover malicious directional isolates.
  • src/test/kotlin/html4tree/MainTest.kt:945 (RIGHT): Assertion matches escaped title; does not cover malicious directional isolates.
  • src/test/kotlin/html4tree/MainTest.kt:946 (RIGHT): Assertion matches h1 dir attribute; no adversarial case.

Adversarial validation

  • src/main/kotlin/html4tree/main.kt:424 (RIGHT) confirmed: A directory name containing a literal U+2069 will prematurely close the FSI isolate, breaking the intended BiDi separation. โ€” escapeHtml tests cover only &, <, >, ", ', `; U+2068/U+2069 are not among encoded characters and pass through unchanged.
  • src/main/kotlin/html4tree/main.kt:429 (RIGHT) falsified: Adding dir="auto" to the h1 could mis-render normal LTR directory names. โ€” Existing rendering test at MainTest.kt line 946 asserts '

    Root

    ' and passes; no LTR regression observed.
  • Residual risk: Other places that emit user-controlled text with FSI/PDI wrapping retain the same bypass until directional isolates are encoded or rejected.

Findings

  • [medium] src/main/kotlin/html4tree/main.kt:424 (RIGHT): FSI/PDI isolation can be bypassed by filenames containing literal U+2068/U+2069 because escapeHtml() does not encode these characters. A literal U+2069 in the name closes the isolate early, exposing surrounding text to malicious directionality and enabling UI spoofing.
  • Result: REQUEST_CHANGES
  • Head SHA: 8a70f133349e0055e13291516430ff5f4e4a386d
  • Reviewer credential: noema-review-github-app-refresh
  • Actor: cwl-noema-review[bot]

@seonghobae
seonghobae marked this pull request as draft September 11, 2026 22:03

Copy link
Copy Markdown
Collaborator Author

@jules Fresh causal security finding on current BiDi hardening lane. I added test-first commit 3cf2442193eb7a31267c1cdace463e244703d81c with BidiIsolationSecurityTest: an attacker-controlled filename containing U+2069 PDI followed by U+202E RLO must not appear as raw directional controls in generated visible/title text, while the href must still address the exact underlying path. Unicode UAX #9 BD9 says the first PDI that decrements the isolate counter to zero is the matching PDI, so the current FSI + raw filename + PDI wrapper can be terminated by a filename-supplied PDI; a following override can then escape the intended text isolation. W3C also recommends markup/isolation for user-generated unknown-direction text rather than relying on embedded controls alone.

Please repair by ordinary descendant, preserving the exact path in URL encoding but neutralizing user-supplied bidirectional formatting controls for display/title contexts before the outer isolation is applied. Cover at least U+061C, U+200E/U+200F, U+202A..U+202E and U+2066..U+2069. Prefer rendering those controls visibly (for example \\u2069) rather than silently changing the filesystem path. Keep ordinary Arabic/Hebrew/CJK/LTR text unchanged. Do not weaken escaping, URL encoding, CSP, symlink checks, or the new regression; remove the generic .jules/sentinel.md doctrine delta rather than treating it as security evidence. GREEN is the focused regression plus existing suite on one exact descendant; browser/a11y evidence is still required before Ready.

@google-labs-jules

Copy link
Copy Markdown

@jules Fresh causal security finding on current BiDi hardening lane. I added test-first commit 3cf2442193eb7a31267c1cdace463e244703d81c with BidiIsolationSecurityTest: an attacker-controlled filename containing U+2069 PDI followed by U+202E RLO must not appear as raw directional controls in generated visible/title text, while the href must still address the exact underlying path. Unicode UAX #9 BD9 says the first PDI that decrements the isolate counter to zero is the matching PDI, so the current FSI + raw filename + PDI wrapper can be terminated by a filename-supplied PDI; a following override can then escape the intended text isolation. W3C also recommends markup/isolation for user-generated unknown-direction text rather than relying on embedded controls alone.

Please repair by ordinary descendant, preserving the exact path in URL encoding but neutralizing user-supplied bidirectional formatting controls for display/title contexts before the outer isolation is applied. Cover at least U+061C, U+200E/U+200F, U+202A..U+202E and U+2066..U+2069. Prefer rendering those controls visibly (for example \\u2069) rather than silently changing the filesystem path. Keep ordinary Arabic/Hebrew/CJK/LTR text unchanged. Do not weaken escaping, URL encoding, CSP, symlink checks, or the new regression; remove the generic .jules/sentinel.md doctrine delta rather than treating it as security evidence. GREEN is the focused regression plus existing suite on one exact descendant; browser/a11y evidence is still required before Ready.

Acknowledged. I've updated the escapeHtml function to neutralize user-supplied bidirectional formatting controls (ALM, LRM, RLM, LRE, RLE, PDF, LRO, RLO, LRI, RLI, FSI, PDI) by rendering them visibly as their unicode escape sequence representations (e.g., \u2069). This prevents these raw controls from terminating the intended isolation while maintaining the correct URL encoding for the actual underlying path link. I've successfully verified this fix passes the embeddedBidiControlsCannotTerminateGeneratedIsolation test and the rest of the existing suite.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working priority: high High-priority or P1 work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant