Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

226 lines
7.4 KiB
Go

package audit
import (
"regexp"
"strings"
)
// tokenKind classifies a backticked token as path, symbol, or other (ignored).
type tokenKind int
const (
tokenOther tokenKind = iota
tokenSymbol
tokenPath
)
// extractBackticked returns all `backticked` spans on a line, without the
// enclosing backticks. Triple-backticks (code fences) are skipped.
func extractBackticked(line string) []string {
// Skip fenced code block markers.
trim := strings.TrimSpace(line)
if strings.HasPrefix(trim, "```") {
return nil
}
var out []string
for {
start := strings.Index(line, "`")
if start < 0 {
return out
}
// Don't match triple backticks mid-line.
if strings.HasPrefix(line[start:], "```") {
end := strings.Index(line[start+3:], "```")
if end < 0 {
return out
}
line = line[start+3+end+3:]
continue
}
rest := line[start+1:]
end := strings.Index(rest, "`")
if end < 0 {
return out
}
tok := strings.TrimSpace(rest[:end])
if tok != "" {
out = append(out, tok)
}
line = rest[end+1:]
}
}
// Path-shaped: contains a path separator OR starts with a dot and has a dot
// further on (e.g. `.gortex.yaml`). URLs are excluded.
var (
pathExts = map[string]bool{
".go": true, ".ts": true, ".tsx": true, ".js": true, ".jsx": true,
".py": true, ".rs": true, ".java": true, ".kt": true, ".rb": true,
".cs": true, ".cpp": true, ".c": true, ".h": true, ".hpp": true,
".md": true, ".yaml": true, ".yml": true, ".json": true, ".toml": true,
".sh": true, ".ps1": true, ".lua": true, ".dart": true, ".swift": true,
".html": true, ".css": true, ".sql": true, ".proto": true, ".xml": true,
}
// Identifier-shaped: CamelCase / PascalCase / snake_case / dotted / `()` suffix.
// Require either a capital letter somewhere OR an explicit `()` suffix so we
// don't flag every plain english word.
identRe = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*(?:[.:][A-Za-z_][A-Za-z0-9_]*)*(?:\(\))?$`)
// Common shell/tool names and markdown-ish terms that get backticked but
// are not symbol references.
skipTokens = map[string]bool{
"true": true, "false": true, "nil": true, "null": true, "None": true,
"TODO": true, "FIXME": true, "NOTE": true, "XXX": true,
"grep": true, "ls": true, "cd": true, "rm": true, "mv": true, "cp": true,
"cat": true, "echo": true, "sed": true, "awk": true, "find": true,
"git": true, "go": true, "npm": true, "yarn": true, "make": true,
"docker": true, "curl": true, "wget": true, "ssh": true,
}
)
func classifyToken(tok string) tokenKind {
if tok == "" || len(tok) > 200 {
return tokenOther
}
// Strip common markdown decorations that sometimes end up inside backticks.
tok = strings.TrimSpace(tok)
// URL? Ignore.
if strings.Contains(tok, "://") {
return tokenOther
}
// Whitespace inside a token (e.g. `POST /mcp`, `git status`) — the
// content is a snippet, not an identifier. Skip before path
// classification so a slash + space combination doesn't get
// mistakenly read as a filesystem path.
if strings.ContainsAny(tok, " \t") {
return tokenOther
}
// Placeholder syntax inside docs (e.g. `<exact-name>`, `<kind>`) —
// templated examples, not real identifiers.
if strings.ContainsAny(tok, "<>") {
return tokenOther
}
// Tokens carrying the Go-style `::` qualifier are always symbol
// candidates even when they also contain a `/`. Without this gate
// `pkg/foo.go::Bar` would land in tokenPath first and look up a
// non-existent file rather than a non-existent symbol.
if !strings.Contains(tok, "::") && isPathLike(tok) {
return tokenPath
}
// Strip trailing `()` for ident classification.
bare := strings.TrimSuffix(tok, "()")
if skipTokens[bare] {
return tokenOther
}
if !identRe.MatchString(tok) {
return tokenOther
}
// SCREAMING_SNAKE / SCREAMING-DASH tokens (env var names, build-tag
// constants, JSON-schema-style ALL_CAPS keys) are not Go symbols
// in any graph we'd audit — agent configs cite them constantly
// (ANTHROPIC_API_KEY, AWS_ACCESS_KEY_ID, NODE_TLS_REJECT_UNAUTHORIZED)
// and the audit must not flag them as stale.
if isScreamingSnake(bare) {
return tokenOther
}
// Require a strong signal that this is a code symbol the graph
// would carry: an uppercase letter (Go/Java/TS public ident),
// the Go-style `::` qualifier, or an explicit `()` call suffix.
// Pure-lowercase tokens with only `_` or `.` (e.g. `search_symbols`,
// `meta.signature`, `older_than`) are MCP tool names, framework
// option keys, and Python-style attribute paths — agent configs
// reference them constantly but they are NOT Go symbols, so they
// would otherwise dominate stale-ref reports as false positives.
hasSignal := strings.HasSuffix(tok, "()") ||
strings.Contains(bare, "::") ||
hasUppercase(bare)
if !hasSignal {
return tokenOther
}
// A bare lowercase-first identifier (e.g. `generateContent`,
// `responseSchema`, `additionalProperties`) is ambiguous — could
// be a Go unexported method we own, could be a Google/AWS/JSON-
// schema API name an agent config legitimately cites. Require a
// stronger signal: a qualifier (`pkg.Sym` / `pkg::Sym`), an
// explicit call form (`fn()`), or a capitalized first letter
// (exported Go / Java / TS, where the docs-author convention is
// to mean "this exported symbol"). Bare `handleFoo` no longer
// qualifies — false positives from docs vocabulary used to drown
// out the real signal.
firstUpper := bare[0] >= 'A' && bare[0] <= 'Z'
if !firstUpper && !strings.ContainsAny(bare, ".:") && !strings.HasSuffix(tok, "()") {
return tokenOther
}
// Must be at least 3 chars to reduce false positives on 1-2 letter vars.
if len(bare) < 3 {
return tokenOther
}
return tokenSymbol
}
// isPathLike detects tokens that look like filesystem paths rather than
// symbols. Returns true only when the token carries a positive path
// signal — a known file extension, a leading `./` / `~` / `/`, or a
// recognised directory leaf. Bare slash-separated identifiers (e.g.
// `notifications/tools/list_changed`, `pkg/foo`) are NOT paths because
// the audit can't pathExists() them without a stat against the repo
// root, and slash-separated identifiers are common in MCP method names,
// JSON pointer fragments, and HTTP routes that the audit should leave
// alone.
func isPathLike(tok string) bool {
if tok == "" {
return false
}
// Explicit-path prefixes — `./relative`, `/absolute`, `~/home`.
if strings.HasPrefix(tok, "./") || strings.HasPrefix(tok, "/") || strings.HasPrefix(tok, "~/") || strings.HasPrefix(tok, "~") && len(tok) > 1 && tok[1] == '/' {
return true
}
if strings.Contains(tok, "\\") {
return true
}
// Files with a known extension (e.g. `.gortex.yaml`, `README.md`,
// `internal/foo.go`).
if idx := strings.LastIndex(tok, "."); idx > 0 {
ext := strings.ToLower(tok[idx:])
if pathExts[ext] {
return true
}
}
return false
}
// isScreamingSnake reports whether tok is composed entirely of upper-
// case letters, digits, underscores, and dashes — the env-var /
// constant shape that agent configs cite but our symbol graphs don't
// carry as nodes.
func isScreamingSnake(tok string) bool {
if tok == "" {
return false
}
hasLetter := false
for _, r := range tok {
switch {
case r >= 'A' && r <= 'Z':
hasLetter = true
case r >= '0' && r <= '9':
case r == '_' || r == '-':
default:
return false
}
}
return hasLetter
}
func hasUppercase(s string) bool {
for _, r := range s {
if r >= 'A' && r <= 'Z' {
return true
}
}
return false
}