Skip to content

chore(ci): gocyclo's filesystem walk in ci.yml scans frontend/node_modules when deps are installed #177

Description

@cristim

Summary

ci.yml's complexity check walks the filesystem:

# .github/workflows/ci.yml (lint job)
COMPLEXITY_ISSUES=$(gocyclo -over 10 -ignore "_test\.go" . 2>&1 || true)

frontend/node_modules contains vendored Go source — e.g. frontend/node_modules/flatted/golang/pkg/flatted/flatted.go ships Stringify at complexity 32 and Parse at 16. Any run of that command in a workspace where npm dependencies are installed reports those as findings.

CI is NOT currently affected — established, not assumed

I checked both jobs that run gocyclo before filing:

workflow / job gocyclo invocation npm install in the same job? affected
ci.yml / lint gocyclo -over 10 -ignore "_test\.go" . (filesystem walk) no (no setup-node, no npm ci) no — node_modules never exists in that workspace
pre-commit.yml / pre-commit via the gocyclo hook: gocyclo -over 10 $(git ls-files "*.go" | grep -v _test.go | grep -v vendor/) yes (npm ci at line 233, before pre-commit run --all-files at line 279) no — git ls-files only lists tracked files, and frontend/node_modules/ is gitignored (.gitignore:97)

So the two invocations are safe today for opposite reasons: one has no node_modules, the other cannot see it.

Why file it anyway

1. It is a live trap for local runs and agents. The documented, copy-pasteable command is the ci.yml one, and anyone who runs it after npm ci in the same checkout gets findings that belong to a third-party npm package. I hit this during review of PR LeanerCloud/cloud-commitments-cli#1758 and initially attributed 2 findings to the PR that were my own — which is exactly how it would present to anyone else, including a CI-reproduction agent trying to match the lint job locally.

2. The two invocations disagree about scope, and only one of them is robust. ci.yml's . walk is safe purely because that job happens not to install npm dependencies. Adding setup-node + npm ci to the lint job — or a cache restore that materialises node_modules — breaks it immediately, with a failure pointing at a vendored file nobody in this repo owns. The pre-commit form is structurally immune because it derives its file list from git.

Note ci.yml's form also lacks the vendor/ exclusion the pre-commit form has. That costs nothing today (git ls-files "*/vendor/*.go" "vendor/*.go" returns 0 — no tracked vendor tree), but it is the same latent gap.

Suggested fix

Make ci.yml derive its file list the same way the pre-commit hook does, so the two checks agree and neither depends on what happens to be on disk:

COMPLEXITY_ISSUES=$(gocyclo -over 10 $(git ls-files "*.go" | grep -v _test.go | grep -v vendor/) 2>&1 || true)

Or, minimally, extend the ignore pattern: -ignore "_test\.go|node_modules|vendor/".

Reproduction

cd frontend && npm ci && cd ..
gocyclo -over 10 -ignore "_test\.go" .
  32 flatted Stringify frontend/node_modules/flatted/golang/pkg/flatted/flatted.go:16:1
  16 flatted Parse     frontend/node_modules/flatted/golang/pkg/flatted/flatted.go:145:1

gocyclo -over 10 -ignore "_test\.go|node_modules" .
  (no output, exit 0)

Found during independent adversarial review of PR LeanerCloud/cloud-commitments-cli#1758.

Findings from the 2026-09-02 codebase audit

Added by an automated audit of 3c0f8ac94048a2c36fce5ccddee54e6c4849a5cd (tip of origin/main). Each item below was reported by one reviewer and independently confirmed by a second that did not write it. Full report: docs/audits/codebase-audit-2026-09-02.md.

A15-003 (medium)

The table in this issue clears pre-commit.yml on the grounds that its gocyclo hook enumerates via git ls-files and so cannot see node_modules. That holds for gocyclo but not for the Go hooks beside it. .pre-commit-config.yaml:16-21 defines the go-vet hook as bash -c 'go vet ./...', and Go's ./... skips only testdata and paths beginning with . or _, never node_modules. Reproduced in a clean worktree: after npm ci, go list ./... emits github.com/LeanerCloud/CUDly/frontend/node_modules/flatted/golang/pkg/flatted, because frontend/node_modules/flatted/golang/pkg/flatted/flatted.go has no go.mod of its own and is absorbed into the CUDly module. In .github/workflows/pre-commit.yml the "Install frontend deps" step at :235 runs before "Run pre-commit" at :241, so this fires in CI. go vet happens to pass on that package today, so nothing is broken; the exposure is that a future flatted release reddens this repo's own vet, test and govulncheck gates on code it does not own. Same fix shape as the gocyclo one: have the Go ./... invocations enumerate real packages, e.g. go vet $(go list ./... | grep -v /node_modules/). Audit finding A15-003.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions