Skip to content

fix: Substring symbol names cross-tainting - #90

Merged
martinnaj merged 1 commit into
masterfrom
mnaj-fix-substring-symbol-names-cross-tainting
Aug 7, 2026
Merged

fix: Substring symbol names cross-tainting#90
martinnaj merged 1 commit into
masterfrom
mnaj-fix-substring-symbol-names-cross-tainting

Conversation

@martinnaj

@martinnaj martinnaj commented Aug 7, 2026

Copy link
Copy Markdown
Member

Risk: low

Summary by CodeRabbit

  • Bug Fixes

    • Improved intra-file taint and dependency matching to recognize complete identifiers.
    • Prevented false matches where one symbol name is only part of another.
    • Preserved wildcard and namespace matching behavior.
  • Chores

    • Updated the project version to 0.25.4.
    • Added release notes for the improvements.

@martinnaj
martinnaj enabled auto-merge August 7, 2026 17:20
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The analyzer now matches plain identifiers by whole-token boundaries across intra-file dependency and taint-propagation paths. Non-identifier names retain substring matching. The project version and changelog now record release 0.25.4.

Changes

Identifier matching

Layer / File(s) Summary
Identifier matching helpers
internal/analyzer/astdiff.go
Added boundary-aware matching for plain identifiers. Non-identifier names retain substring matching.
Propagation path integration
internal/analyzer/analyzer.go, internal/analyzer/astdiff.go, VERSION, CHANGELOG.md
Updated all listed intra-file propagation and usage-detection paths. Recorded version 0.25.4 and its changelog entry.

Estimated code review effort: 2 (Simple) | ~10 minutes

Poem

I hop through tokens, neat and bright,
No partial names escape my sight.
Wildcards still may roam free,
Exact symbols guide the tree.
Version four brings cleaner flow.
— A rabbit

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preventing cross-tainting caused by substring symbol-name matching.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@martinnaj
martinnaj merged commit 4442e29 into master Aug 7, 2026
6 of 7 checks passed
@martinnaj
martinnaj deleted the mnaj-fix-substring-symbol-names-cross-tainting branch August 7, 2026 17:21

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/analyzer/astdiff.go`:
- Around line 576-587: Update containsIdentifier in internal/analyzer/astdiff.go
to use TypeScript tokenization or an equivalent grammar-aware scanner for
identifier matching, preventing matches inside Unicode or escaped identifiers
such as foo\u0062ar, fooé, or éx. Preserve plain substring behavior for
non-identifiers only, and add regression tests covering these boundary cases.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 72fb6634-dcee-47eb-9aa2-cad414603a24

📥 Commits

Reviewing files that changed from the base of the PR and between b11b5d2 and 3750020.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • VERSION
  • internal/analyzer/analyzer.go
  • internal/analyzer/astdiff.go

Comment on lines +576 to +587
if !isPlainIdentifier(name) {
return strings.Contains(text, name)
}
for from := 0; ; {
i := strings.Index(text[from:], name)
if i < 0 {
return false
}
start := from + i
end := start + len(name)
beforeOK := start == 0 || !isIdentByte(text[start-1])
afterOK := end == len(text) || !isIdentByte(text[end])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n 'containsIdentifier|isPlainIdentifier|isIdentByte' internal/analyzer --glob '*.go'
rg -nP '\\u[0-9A-Fa-f]{4}|[^\x00-\x7F]' internal/analyzer --glob '*_test.go' || true

Repository: gooddata/gooddata-goodchanges

Length of output: 1317


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- target implementation ---'
sed -n '130,185p;540,620p' internal/analyzer/astdiff.go

printf '%s\n' '--- parser and identifier-related code ---'
rg -n 'Parse|parser|Identifier|identifier|bodyText|containsIdentifier' internal/analyzer go.mod go.sum --glob '*.go' --glob 'go.mod' --glob 'go.sum' | head -240

printf '%s\n' '--- relevant tests ---'
fd -i '.*_test\.go$' internal/analyzer -x sh -c 'rg -n -C 3 "containsIdentifier|unicode|escaped|identifier|Rename|rename" "$1"' sh {} || true

Repository: gooddata/gooddata-goodchanges

Length of output: 7240


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- repository files and module dependencies ---'
git ls-files | rg '(^|/)(tsparse|parser|.*_test\.go$)|go\.mod$|go\.sum$' | head -240
sed -n '1,180p' go.mod

printf '%s\n' '--- symbol extraction and identifier handling ---'
sed -n '220,300p' internal/analyzer/astdiff.go
rg -n -C 4 'type Symbol|struct .*Symbol|Symbols =|Name:' --glob '*.go' .

printf '%s\n' '--- parser package API references ---'
rg -n -C 3 'package tsparse|func .*Parse|func .*Extract|IsIdentifier|IdentifierName|Unicode|escaped' --glob '*.go' .

Repository: gooddata/gooddata-goodchanges

Length of output: 25678


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- tsparse analysis construction ---'
sed -n '1,115p;300,435p' internal/tsparse/tsparse.go

printf '%s\n' '--- vendor scanner and source-file APIs ---'
fd -i '.*\.go$' _vendor/typescript-go | head -80
rg -n -C 3 'func .*Scan|type Scanner|Scanner|Token.*Identifier|Identifier.*Text|Unescape|UnicodeEscape|Text\(\)' _vendor/typescript-go --glob '*.go' | head -320

printf '%s\n' '--- analyzer tests and test helpers ---'
git ls-files internal/analyzer | sort
rg -n -C 5 'FindAffected|affected|repointed|intra-file|taint|astdiff' internal/analyzer --glob '*_test.go' || true

Repository: gooddata/gooddata-goodchanges

Length of output: 7547


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- module layout and submodules ---'
git submodule status || true
git ls-tree HEAD _vendor/typescript-go
cat .gitmodules 2>/dev/null || true
find . -maxdepth 3 -type d -name '*typescript*' -o -name '_vendor'

printf '%s\n' '--- scanner usage in tracked source ---'
rg -n -C 5 'scanner\.' internal --glob '*.go'
sed -n '1,110p' internal/tsparse/tsparse.go
sed -n '400,440p' internal/tsparse/tsparse.go

printf '%s\n' '--- tests in the analyzer package ---'
git ls-files internal/analyzer | sort
git ls-files '*_test.go' | wc -l

Repository: gooddata/gooddata-goodchanges

Length of output: 8719


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
def is_ident_byte(b):
    return b in (ord('_'), ord('$')) or 65 <= b <= 90 or 97 <= b <= 122 or 48 <= b <= 57

def is_plain_identifier(s):
    return bool(s) and all(is_ident_byte(b) for b in s.encode())

def contains_identifier(text, name):
    if not name:
        return False
    if not is_plain_identifier(name):
        return name in text
    start = 0
    while True:
        i = text.find(name, start)
        if i < 0:
            return False
        end = i + len(name)
        before_ok = i == 0 or not is_ident_byte(text.encode()[i - 1])
        after_ok = end == len(text) or not is_ident_byte(text.encode()[end])
        if before_ok and after_ok:
            return True
        start = i + 1

cases = [
    ("const fooé = 1", "foo"),
    (r"const foo\u0062ar = 1", "foo"),
    ("const é = 1", "é"),
    ("const éx = 1", "é"),
]
for text, name in cases:
    print(repr(text), repr(name), contains_identifier(text, name))
PY

printf '%s\n' '--- exact call sites and absence of tests ---'
rg -n -C 3 'containsIdentifier\(' internal/analyzer --glob '*.go'
git ls-files '*_test.go' | sort

Repository: gooddata/gooddata-goodchanges

Length of output: 4155


Use parser-aware identifier boundaries.

containsIdentifier can match part of a Unicode or escaped identifier, such as foo in foo\u0062ar or fooé. Non-ASCII names use raw substring matching and can match inside éx. This creates false taint links. Add regression tests and use TypeScript tokenization or an equivalent grammar-aware scanner.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/analyzer/astdiff.go` around lines 576 - 587, Update
containsIdentifier in internal/analyzer/astdiff.go to use TypeScript
tokenization or an equivalent grammar-aware scanner for identifier matching,
preventing matches inside Unicode or escaped identifiers such as foo\u0062ar,
fooé, or éx. Preserve plain substring behavior for non-identifiers only, and add
regression tests covering these boundary cases.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants