chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
package astquery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
)
|
||||
|
||||
// userRuleFile is the TOML schema for a pluggable domain-extractor
|
||||
// rule file:
|
||||
//
|
||||
// [[rule]]
|
||||
// name = "feature-flag-register"
|
||||
// description = "calls to flags.Register"
|
||||
// domain = "feature_flag"
|
||||
// severity = "info"
|
||||
// tags = ["flags"]
|
||||
// [rule.query]
|
||||
// go = '(call_expression function: (selector_expression ...)) @match'
|
||||
type userRuleFile struct {
|
||||
Rule []userRule `toml:"rule"`
|
||||
}
|
||||
|
||||
// userRule is one TOML-defined domain-extractor rule. Each becomes an
|
||||
// astquery Detector registered under Category "domain", so the whole
|
||||
// rule-execution and `analyze kind=domain` surface is reused.
|
||||
type userRule struct {
|
||||
Name string `toml:"name"`
|
||||
Description string `toml:"description"`
|
||||
// Domain labels the rule's purpose — http_route / cli_command /
|
||||
// feature_flag / i18n / event_bus / custom. It becomes a tag so
|
||||
// `analyze kind=domain tag:<domain>` filters to it.
|
||||
Domain string `toml:"domain"`
|
||||
Severity string `toml:"severity"`
|
||||
Tags []string `toml:"tags"`
|
||||
// Query maps a language ("go", "python", …) to a tree-sitter
|
||||
// S-expression. A capture named `match` anchors the row.
|
||||
Query map[string]string `toml:"query"`
|
||||
}
|
||||
|
||||
// LoadUserRulesData parses TOML domain-extractor rule definitions and
|
||||
// registers each as an astquery Detector under Category "domain".
|
||||
// Returns the number of rules registered. A parse error or an invalid
|
||||
// rule aborts the load — already-registered rules from the same file
|
||||
// stay registered.
|
||||
func LoadUserRulesData(data []byte) (int, error) {
|
||||
var f userRuleFile
|
||||
if err := toml.Unmarshal(data, &f); err != nil {
|
||||
return 0, fmt.Errorf("astquery: parse user rules: %w", err)
|
||||
}
|
||||
n := 0
|
||||
for _, r := range f.Rule {
|
||||
d, err := r.toDetector()
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
RegisterDetector(d)
|
||||
n++
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// LoadUserRulesFile reads a TOML rule file and registers its rules.
|
||||
func LoadUserRulesFile(path string) (int, error) {
|
||||
data, err := os.ReadFile(path) //nolint:gosec // path is operator-supplied config
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("astquery: read user rules %s: %w", path, err)
|
||||
}
|
||||
return LoadUserRulesData(data)
|
||||
}
|
||||
|
||||
// toDetector converts a TOML rule into a registrable Detector. The
|
||||
// detector name is prefixed `user:` so a user rule can never clobber a
|
||||
// bundled SAST / hygiene rule.
|
||||
func (r userRule) toDetector() (*Detector, error) {
|
||||
name := strings.TrimSpace(r.Name)
|
||||
if name == "" {
|
||||
return nil, fmt.Errorf("astquery: user rule missing name")
|
||||
}
|
||||
langs := map[string]string{}
|
||||
for lang, q := range r.Query {
|
||||
lang = strings.ToLower(strings.TrimSpace(lang))
|
||||
q = strings.TrimSpace(q)
|
||||
if lang != "" && q != "" {
|
||||
langs[lang] = q
|
||||
}
|
||||
}
|
||||
if len(langs) == 0 {
|
||||
return nil, fmt.Errorf("astquery: user rule %q has no query patterns", name)
|
||||
}
|
||||
domain := strings.TrimSpace(r.Domain)
|
||||
if domain == "" {
|
||||
domain = "custom"
|
||||
}
|
||||
severity := strings.TrimSpace(r.Severity)
|
||||
if severity == "" {
|
||||
severity = "info"
|
||||
}
|
||||
desc := strings.TrimSpace(r.Description)
|
||||
if desc == "" {
|
||||
desc = "user-defined " + domain + " rule"
|
||||
}
|
||||
tags := append([]string{"user-rule", domain}, r.Tags...)
|
||||
return &Detector{
|
||||
Name: "user:" + name,
|
||||
Description: desc,
|
||||
Severity: severity,
|
||||
Category: "domain",
|
||||
Tags: tags,
|
||||
Languages: langs,
|
||||
ExcludeTests: false,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user