├── .github ├── dependabot.yml └── workflows │ ├── deps.yml │ ├── go-cross.yml │ ├── main.yml │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── LICENSE ├── README.md ├── cmd └── bidichk │ └── main.go ├── go.mod ├── go.sum ├── pkg └── bidichk │ ├── bidichk.go │ ├── bidichk_test.go │ └── version.go └── testdata └── src ├── commenting-out-lri-only └── commenting-out-lri-only.go ├── commenting-out └── commenting-out.go ├── simple └── simple.go └── stretched-string └── stretched-string.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | version: 2 4 | 5 | updates: 6 | - package-ecosystem: "gomod" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" 10 | -------------------------------------------------------------------------------- /.github/workflows/deps.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/actions/go-dependency-submission 2 | name: dependency-submission 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | main: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version: "stable" 19 | - uses: actions/go-dependency-submission@v2 20 | with: 21 | go-mod-path: go.mod 22 | -------------------------------------------------------------------------------- /.github/workflows/go-cross.yml: -------------------------------------------------------------------------------- 1 | name: Go Matrix 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | 12 | cross: 13 | name: Go 14 | runs-on: ${{ matrix.os }} 15 | env: 16 | CGO_ENABLED: 0 17 | 18 | strategy: 19 | matrix: 20 | go-version: [ "stable", "oldstable", "1.x" ] 21 | os: [ubuntu-latest, macos-latest, windows-latest] 22 | 23 | steps: 24 | - name: Set up Go ${{ matrix.go-version }} 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: ${{ matrix.go-version }} 28 | 29 | - name: Checkout code 30 | uses: actions/checkout@v4 31 | 32 | - name: Cache Go modules 33 | uses: actions/cache@v4 34 | with: 35 | # In order: 36 | # * Module download cache 37 | # * Build cache (Linux) 38 | # * Build cache (Mac) 39 | # * Build cache (Windows) 40 | path: | 41 | ~/go/pkg/mod 42 | ~/.cache/go-build 43 | ~/Library/Caches/go-build 44 | %LocalAppData%\go-build 45 | key: ${{ runner.os }}-${{ matrix.go-version }}-go-${{ hashFiles('**/go.sum') }} 46 | restore-keys: | 47 | ${{ runner.os }}-${{ matrix.go-version }}-go- 48 | 49 | - name: Test 50 | run: go test -v -cover ./... 51 | 52 | - name: Build 53 | run: go build -ldflags "-s -w" -trimpath -o bidichk ./cmd/bidichk/ 54 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | 12 | main: 13 | name: Main Process 14 | runs-on: ubuntu-latest 15 | env: 16 | GO_VERSION: "stable" 17 | GOLANGCI_LINT_VERSION: v1.64.6 18 | CGO_ENABLED: 0 19 | 20 | steps: 21 | 22 | - name: Set up Go ${{ env.GO_VERSION }} 23 | uses: actions/setup-go@v5 24 | with: 25 | go-version: ${{ env.GO_VERSION }} 26 | 27 | - name: Check out code 28 | uses: actions/checkout@v4 29 | with: 30 | fetch-depth: 0 31 | 32 | - name: Cache Go modules 33 | uses: actions/cache@v4 34 | with: 35 | path: ~/go/pkg/mod 36 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 37 | restore-keys: | 38 | ${{ runner.os }}-go- 39 | 40 | - name: Check and get dependencies 41 | run: | 42 | go mod tidy 43 | git diff --exit-code go.mod 44 | git diff --exit-code go.sum 45 | 46 | - name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }} 47 | run: | 48 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION} 49 | golangci-lint --version 50 | 51 | - name: Lint 52 | run: golangci-lint run 53 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v[0-9]+.[0-9]+.[0-9]+' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | goreleaser: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - 14 | name: Checkout 15 | uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | 19 | - 20 | name: Set up Go 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version: "stable" 24 | 25 | - 26 | name: Run GoReleaser 27 | uses: goreleaser/goreleaser-action@v6 28 | with: 29 | distribution: goreleaser 30 | version: "~> v2" 31 | args: release --clean 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | 35 | # https://github.com/marketplace/actions/go-proxy-warming 36 | pkg-go-dev-warming: 37 | name: Renew documentation 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Pull new module version 41 | uses: andrewslotin/go-proxy-pull-action@master 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | /cmd/bidichk/bidichk 8 | /bidichk 9 | 10 | # Test binary, built with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Dependency directories (remove the comment below to include it) 17 | # vendor/ 18 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | 4 | # This is an example .goreleaser.yml file with some sane defaults. 5 | # Make sure to check the documentation at http://goreleaser.com 6 | before: 7 | hooks: 8 | # You may remove this if you don't use go modules. 9 | - go mod tidy 10 | builds: 11 | - main: ./cmd/bidichk 12 | binary: bidichk 13 | env: 14 | - CGO_ENABLED=0 15 | goos: 16 | - linux 17 | - windows 18 | - darwin 19 | archives: 20 | - name_template: >- 21 | {{- .Binary }}_ 22 | {{- .Version }}_ 23 | {{- title .Os }}_ 24 | {{- if eq .Arch "amd64" }}x86_64 25 | {{- else if eq .Arch "386" }}i386 26 | {{- else }}{{ .Arch }}{{ end }} 27 | {{- if .Arm }}v{{ .Arm }}{{ end -}} 28 | snapshot: 29 | version_template: "{{ .Tag }}-next" 30 | changelog: 31 | disable: true 32 | release: 33 | github: 34 | owner: breml 35 | name: bidichk 36 | gomod: 37 | proxy: true 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Lucas Bremgartner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bidichk - checks for dangerous unicode character sequences 2 | 3 | [![Test Status](https://github.com/breml/bidichk/workflows/Go%20Matrix/badge.svg)](https://github.com/breml/bidichk/actions?query=workflow%3AGo%20Matrix) [![Go Report Card](https://goreportcard.com/badge/github.com/breml/bidichk)](https://goreportcard.com/report/github.com/breml/bidichk) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 4 | 5 | bidichk finds dangerous unicode character sequences in Go source files. 6 | 7 | ## Considered dangerous unicode characters 8 | 9 | The following unicode characters are considered dangerous: 10 | 11 | * U+202A: LEFT-TO-RIGHT-EMBEDDING 12 | * U+202B: RIGHT-TO-LEFT-EMBEDDING 13 | * U+202C: POP-DIRECTIONAL-FORMATTING 14 | * U+202D: LEFT-TO-RIGHT-OVERRIDE 15 | * U+202E: RIGHT-TO-LEFT-OVERRIDE 16 | * U+2066: LEFT-TO-RIGHT-ISOLATE 17 | * U+2067: RIGHT-TO-LEFT-ISOLATE 18 | * U+2068: FIRST-STRONG-ISOLATE 19 | * U+2069: POP-DIRECTIONAL-ISOLATE 20 | 21 | ## Installation 22 | 23 | Download `bidichk` from the [releases](https://github.com/breml/bidichk/releases) or get the latest version from source with: 24 | 25 | ```shell 26 | go install github.com/breml/bidichk/cmd/bidichk@latest 27 | ``` 28 | 29 | ## Usage 30 | 31 | ### golangci-lint 32 | 33 | [golangci-lint](https://golangci-lint.run) supports bidichk, so you can enable this linter and use it. 34 | 35 | ### Shell 36 | 37 | Check everything: 38 | 39 | ```shell 40 | bidichk ./... 41 | ``` 42 | 43 | ### Enable only required unicode runes 44 | 45 | If you run bidichk via golangci-lint look at [.golangci.example.yml](https://golangci-lint.run/usage/configuration/#config-file) for an example of the configuration. 46 | 47 | Otherwise you can run bidichk with `--disallowed-runes` flag to specify the runes you consider harmful. 48 | 49 | E.g. the following command considers only the `LEFT-TO-RIGHT-OVERRIDE` unicode rune as dangerous: 50 | 51 | ```shell 52 | bidichk --disallowed-runes LEFT-TO-RIGHT-OVERRIDE ./... 53 | ``` 54 | 55 | For the full list of supported unicode runes [see above](#considered-dangerous-unicode-characters) or use 56 | 57 | ```shell 58 | bidichk --help 59 | ``` 60 | 61 | ## Inspiration 62 | 63 | * ['Trojan Source' Bug Threatens the Security of All Code](https://krebsonsecurity.com/2021/11/trojan-source-bug-threatens-the-security-of-all-code/) 64 | * [Trojan Source - Proofs-of-Concept](https://github.com/nickboucher/trojan-source) 65 | * [Go proposal: disallow LTR/RTL characters in string literals?](https://github.com/golang/go/issues/20209) 66 | * [cockroachdb/cockroach - PR: lint: add linter for unicode directional formatting characters](https://github.com/cockroachdb/cockroach/pull/72287) 67 | * [Russ Cox - On “Trojan Source” Attacks](https://research.swtch.com/trojan) 68 | -------------------------------------------------------------------------------- /cmd/bidichk/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | "runtime/debug" 9 | 10 | "golang.org/x/tools/go/analysis/singlechecker" 11 | 12 | "github.com/breml/bidichk/pkg/bidichk" 13 | ) 14 | 15 | var ( 16 | version = "development" 17 | commit = "" 18 | date = "" 19 | ) 20 | 21 | func main() { 22 | bidichk.Version = buildVersion() 23 | 24 | singlechecker.Main(bidichk.NewAnalyzer()) 25 | } 26 | 27 | func buildVersion() string { 28 | result := fmt.Sprintf("%s version %s", filepath.Base(os.Args[0]), version) 29 | 30 | if commit != "" { 31 | result = fmt.Sprintf("%s\ncommit: %s", result, commit) 32 | } 33 | if date != "" { 34 | result = fmt.Sprintf("%s\nbuilt at: %s", result, date) 35 | } 36 | if info, ok := debug.ReadBuildInfo(); ok && info.Main.Sum != "" { 37 | result = fmt.Sprintf("%s\nmodule version: %s, checksum: %s", result, info.Main.Version, info.Main.Sum) 38 | } 39 | result = fmt.Sprintf("%s\ngoos: %s\ngoarch: %s", result, runtime.GOOS, runtime.GOARCH) 40 | 41 | return result 42 | } 43 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/breml/bidichk 2 | 3 | go 1.23.0 4 | 5 | require golang.org/x/tools v0.34.0 6 | 7 | require ( 8 | golang.org/x/mod v0.25.0 // indirect 9 | golang.org/x/sync v0.15.0 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 2 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 3 | golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= 4 | golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 5 | golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= 6 | golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 7 | golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= 8 | golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= 9 | -------------------------------------------------------------------------------- /pkg/bidichk/bidichk.go: -------------------------------------------------------------------------------- 1 | package bidichk 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "go/token" 8 | "os" 9 | "sort" 10 | "strings" 11 | "unicode/utf8" 12 | 13 | "golang.org/x/tools/go/analysis" 14 | ) 15 | 16 | const ( 17 | doc = "Checks for dangerous unicode character sequences" 18 | disallowedDoc = `comma separated list of disallowed runes (full name or short name) 19 | 20 | Supported runes 21 | 22 | LEFT-TO-RIGHT-EMBEDDING, LRE (u+202A) 23 | RIGHT-TO-LEFT-EMBEDDING, RLE (u+202B) 24 | POP-DIRECTIONAL-FORMATTING, PDF (u+202C) 25 | LEFT-TO-RIGHT-OVERRIDE, LRO (u+202D) 26 | RIGHT-TO-LEFT-OVERRIDE, RLO (u+202E) 27 | LEFT-TO-RIGHT-ISOLATE, LRI (u+2066) 28 | RIGHT-TO-LEFT-ISOLATE, RLI (u+2067) 29 | FIRST-STRONG-ISOLATE, FSI (u+2068) 30 | POP-DIRECTIONAL-ISOLATE, PDI (u+2069) 31 | ` 32 | ) 33 | 34 | type disallowedRunes map[string]rune 35 | 36 | func (m disallowedRunes) String() string { 37 | ss := make([]string, 0, len(m)) 38 | for s := range m { 39 | ss = append(ss, s) 40 | } 41 | sort.Strings(ss) 42 | return strings.Join(ss, ",") 43 | } 44 | 45 | func (m disallowedRunes) Set(s string) error { 46 | ss := strings.FieldsFunc(s, func(c rune) bool { return c == ',' }) 47 | if len(ss) == 0 { 48 | return nil 49 | } 50 | 51 | for k := range m { 52 | delete(m, k) 53 | } 54 | 55 | for _, v := range ss { 56 | switch v { 57 | case runeShortNameLRE, runeShortNameRLE, runeShortNamePDF, 58 | runeShortNameLRO, runeShortNameRLO, runeShortNameLRI, 59 | runeShortNameRLI, runeShortNameFSI, runeShortNamePDI: 60 | v = shortNameLookup[v] 61 | fallthrough 62 | case runeNameLRE, runeNameRLE, runeNamePDF, 63 | runeNameLRO, runeNameRLO, runeNameLRI, 64 | runeNameRLI, runeNameFSI, runeNamePDI: 65 | m[v] = runeLookup[v] 66 | default: 67 | return fmt.Errorf("unknown check name %q (see help for full list)", v) 68 | } 69 | } 70 | return nil 71 | } 72 | 73 | const ( 74 | runeNameLRE = "LEFT-TO-RIGHT-EMBEDDING" 75 | runeNameRLE = "RIGHT-TO-LEFT-EMBEDDING" 76 | runeNamePDF = "POP-DIRECTIONAL-FORMATTING" 77 | runeNameLRO = "LEFT-TO-RIGHT-OVERRIDE" 78 | runeNameRLO = "RIGHT-TO-LEFT-OVERRIDE" 79 | runeNameLRI = "LEFT-TO-RIGHT-ISOLATE" 80 | runeNameRLI = "RIGHT-TO-LEFT-ISOLATE" 81 | runeNameFSI = "FIRST-STRONG-ISOLATE" 82 | runeNamePDI = "POP-DIRECTIONAL-ISOLATE" 83 | 84 | runeShortNameLRE = "LRE" // LEFT-TO-RIGHT-EMBEDDING 85 | runeShortNameRLE = "RLE" // RIGHT-TO-LEFT-EMBEDDING 86 | runeShortNamePDF = "PDF" // POP-DIRECTIONAL-FORMATTING 87 | runeShortNameLRO = "LRO" // LEFT-TO-RIGHT-OVERRIDE 88 | runeShortNameRLO = "RLO" // RIGHT-TO-LEFT-OVERRIDE 89 | runeShortNameLRI = "LRI" // LEFT-TO-RIGHT-ISOLATE 90 | runeShortNameRLI = "RLI" // RIGHT-TO-LEFT-ISOLATE 91 | runeShortNameFSI = "FSI" // FIRST-STRONG-ISOLATE 92 | runeShortNamePDI = "PDI" // POP-DIRECTIONAL-ISOLATE 93 | ) 94 | 95 | var runeLookup = map[string]rune{ 96 | runeNameLRE: '\u202A', // LEFT-TO-RIGHT-EMBEDDING 97 | runeNameRLE: '\u202B', // RIGHT-TO-LEFT-EMBEDDING 98 | runeNamePDF: '\u202C', // POP-DIRECTIONAL-FORMATTING 99 | runeNameLRO: '\u202D', // LEFT-TO-RIGHT-OVERRIDE 100 | runeNameRLO: '\u202E', // RIGHT-TO-LEFT-OVERRIDE 101 | runeNameLRI: '\u2066', // LEFT-TO-RIGHT-ISOLATE 102 | runeNameRLI: '\u2067', // RIGHT-TO-LEFT-ISOLATE 103 | runeNameFSI: '\u2068', // FIRST-STRONG-ISOLATE 104 | runeNamePDI: '\u2069', // POP-DIRECTIONAL-ISOLATE 105 | } 106 | 107 | var shortNameLookup = map[string]string{ 108 | runeShortNameLRE: runeNameLRE, 109 | runeShortNameRLE: runeNameRLE, 110 | runeShortNamePDF: runeNamePDF, 111 | runeShortNameLRO: runeNameLRO, 112 | runeShortNameRLO: runeNameRLO, 113 | runeShortNameLRI: runeNameLRI, 114 | runeShortNameRLI: runeNameRLI, 115 | runeShortNameFSI: runeNameFSI, 116 | runeShortNamePDI: runeNamePDI, 117 | } 118 | 119 | type bidichk struct { 120 | disallowedRunes disallowedRunes 121 | } 122 | 123 | // NewAnalyzer return a new bidichk analyzer. 124 | func NewAnalyzer() *analysis.Analyzer { 125 | bidichk := bidichk{} 126 | bidichk.disallowedRunes = make(map[string]rune, len(runeLookup)) 127 | for k, v := range runeLookup { 128 | bidichk.disallowedRunes[k] = v 129 | } 130 | 131 | a := &analysis.Analyzer{ 132 | Name: "bidichk", 133 | Doc: doc, 134 | Run: bidichk.run, 135 | } 136 | 137 | a.Flags.Init("bidichk", flag.ExitOnError) 138 | a.Flags.Var(&bidichk.disallowedRunes, "disallowed-runes", disallowedDoc) 139 | a.Flags.Var(versionFlag{}, "V", "print version and exit") 140 | 141 | return a 142 | } 143 | 144 | func (b bidichk) run(pass *analysis.Pass) (interface{}, error) { 145 | readFile := pass.ReadFile 146 | if readFile == nil { 147 | readFile = os.ReadFile 148 | } 149 | 150 | for _, astFile := range pass.Files { 151 | f := pass.Fset.File(astFile.FileStart) 152 | if f == nil { 153 | continue 154 | } 155 | 156 | body, err := readFile(f.Name()) 157 | if err != nil { 158 | return nil, err 159 | } 160 | 161 | b.check(body, f.Pos(0), pass) 162 | } 163 | return nil, nil 164 | } 165 | 166 | func (b bidichk) check(body []byte, pos token.Pos, pass *analysis.Pass) { 167 | for name, r := range b.disallowedRunes { 168 | start := 0 169 | for { 170 | idx := bytes.IndexRune(body[start:], r) 171 | if idx == -1 { 172 | break 173 | } 174 | start += idx 175 | 176 | pass.Reportf(pos+token.Pos(start), "found dangerous unicode character sequence %s", name) 177 | 178 | start += utf8.RuneLen(r) 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /pkg/bidichk/bidichk_test.go: -------------------------------------------------------------------------------- 1 | package bidichk_test 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "golang.org/x/tools/go/analysis" 9 | "golang.org/x/tools/go/analysis/analysistest" 10 | 11 | "github.com/breml/bidichk/pkg/bidichk" 12 | ) 13 | 14 | func TestAll(t *testing.T) { 15 | tt := []struct { 16 | name string 17 | analyzerFunc func() *analysis.Analyzer 18 | testdataDir string 19 | }{ 20 | { 21 | name: "simple", 22 | analyzerFunc: bidichk.NewAnalyzer, 23 | testdataDir: "simple", 24 | }, 25 | { 26 | name: "commenting-out", 27 | analyzerFunc: bidichk.NewAnalyzer, 28 | testdataDir: "commenting-out", 29 | }, 30 | { 31 | name: "stretched-string", 32 | analyzerFunc: bidichk.NewAnalyzer, 33 | testdataDir: "stretched-string", 34 | }, 35 | { 36 | name: "commenting-out-lri-only", 37 | analyzerFunc: func() *analysis.Analyzer { 38 | a := bidichk.NewAnalyzer() 39 | _ = a.Flags.Set("disallowed-runes", "LEFT-TO-RIGHT-ISOLATE") 40 | return a 41 | }, 42 | testdataDir: "commenting-out-lri-only", 43 | }, 44 | { 45 | name: "commenting-out-lri-only short name", 46 | analyzerFunc: func() *analysis.Analyzer { 47 | a := bidichk.NewAnalyzer() 48 | _ = a.Flags.Set("disallowed-runes", "LRI") 49 | return a 50 | }, 51 | testdataDir: "commenting-out-lri-only", 52 | }, 53 | } 54 | 55 | wd, err := os.Getwd() 56 | if err != nil { 57 | t.Fatalf("Failed to get wd: %s", err) 58 | } 59 | 60 | testdata := filepath.Join(filepath.Dir(filepath.Dir(wd)), "testdata") 61 | 62 | for _, tc := range tt { 63 | t.Run(tc.name, func(t *testing.T) { 64 | analysistest.Run(t, testdata, tc.analyzerFunc(), tc.testdataDir) 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pkg/bidichk/version.go: -------------------------------------------------------------------------------- 1 | package bidichk 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | var Version = "bidichk version dev" 9 | 10 | type versionFlag struct{} 11 | 12 | func (versionFlag) IsBoolFlag() bool { return true } 13 | func (versionFlag) Get() interface{} { return nil } 14 | func (versionFlag) String() string { return "" } 15 | func (versionFlag) Set(s string) error { 16 | fmt.Println(Version) 17 | os.Exit(0) 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /testdata/src/commenting-out-lri-only/commenting-out-lri-only.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | isAdmin := false 7 | isSuperAdmin := false 8 | isAdmin = isAdmin || isSuperAdmin 9 | /*‮ } ⁦if (isAdmin)⁩ ⁦ begin admins only */ // want "found dangerous unicode character sequence LEFT-TO-RIGHT-ISOLATE" "found dangerous unicode character sequence LEFT-TO-RIGHT-ISOLATE" 10 | fmt.Println("You are an admin.") 11 | /* end admins only ‮ { ⁦*/ // want "found dangerous unicode character sequence LEFT-TO-RIGHT-ISOLATE" 12 | } 13 | -------------------------------------------------------------------------------- /testdata/src/commenting-out/commenting-out.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | isAdmin := false 7 | isSuperAdmin := false 8 | isAdmin = isAdmin || isSuperAdmin 9 | /*‮ } ⁦if (isAdmin)⁩ ⁦ begin admins only */ // want "found dangerous unicode character sequence LEFT-TO-RIGHT-ISOLATE" "found dangerous unicode character sequence RIGHT-TO-LEFT-OVERRIDE" "found dangerous unicode character sequence LEFT-TO-RIGHT-ISOLATE" "found dangerous unicode character sequence POP-DIRECTIONAL-ISOLATE" 10 | fmt.Println("You are an admin.") 11 | /* end admins only ‮ { ⁦*/ // want "found dangerous unicode character sequence LEFT-TO-RIGHT-ISOLATE" "found dangerous unicode character sequence RIGHT-TO-LEFT-OVERRIDE" 12 | } 13 | -------------------------------------------------------------------------------- /testdata/src/simple/simple.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("LEFT-TO-RIGHT-OVERRIDE: '‭'") // want "found dangerous unicode character sequence LEFT-TO-RIGHT-OVERRIDE" 7 | } 8 | -------------------------------------------------------------------------------- /testdata/src/stretched-string/stretched-string.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | accessLevel := "user" 7 | if accessLevel != "user‮ ⁦// Check if admin⁩ ⁦" { // want "found dangerous unicode character sequence POP-DIRECTIONAL-ISOLATE" "found dangerous unicode character sequence RIGHT-TO-LEFT-OVERRIDE" "found dangerous unicode character sequence LEFT-TO-RIGHT-ISOLATE" "found dangerous unicode character sequence LEFT-TO-RIGHT-ISOLATE" 8 | fmt.Println("You are an admin.") 9 | } 10 | } 11 | --------------------------------------------------------------------------------