Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ Sight is a Hawk support engine. Keep the dependency edge one-way:
- do not import removed legacy path `hawk/shared/types`; use `hawk-core-contracts/types`
- do not import other engines (`eyrie`, `yaad`, `tok`, `trace`, `inspect`) — engines are peers, not dependencies

> **Note:** `memory_bridge.go` is an intentional boundary exception — it calls
> `yaad` directly for automated memory persistence after reviews. This is documented
> as a known trade-off; the canonical integration path is via hawk.

## Features

- **Diff-aware analysis** - Reviews only changed code with full context
Expand Down
10 changes: 10 additions & 0 deletions memory_bridge.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
package sight

// Package-level architectural note:
// memory_bridge.go is an intentional, scoped exception to the "engines are peers"
// rule. Sight calls Yaad directly here for the memory integration feature, which
// requires tight coupling between review findings and memory storage. This is a
// known trade-off: hawk composes sight+yaad results in most cases, but the
// memory_bridge provides a direct integration path for automated memory
// persistence after reviews. If the boundary needs to be restored, move the
// integration logic to hawk/internal/bridge/ and call it from there instead.
// See: hawk-eco/hawk/docs/architecture/hawk-current-vs-proposed.md

import (
"context"
"fmt"
Expand Down
877 changes: 22 additions & 855 deletions static_rules_defaults.go

Large diffs are not rendered by default.

189 changes: 189 additions & 0 deletions static_rules_go.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package sight

import "regexp"

// goSecurityRules returns built-in static analysis rules for Go security.
func goSecurityRules() []StaticRule {
return []StaticRule{
// =====================================================================
// SECURITY - Go
// =====================================================================
{
ID: "SEC-GO-001",
Name: "SQL Injection",
Description: "String formatting used in SQL query construction; use parameterized queries instead",
Language: "go",
Pattern: regexp.MustCompile(`fmt\.Sprintf\s*\(\s*"[^"]*(?:SELECT|INSERT|UPDATE|DELETE|DROP)[^"]*%[sv]`),
Antipattern: nil,
Severity: "critical",
Category: "security",
CWE: "CWE-89",
Fix: "Use parameterized queries with ? or $1 placeholders instead of fmt.Sprintf for SQL",
},
{
ID: "SEC-GO-002",
Name: "Command Injection",
Description: "Potentially unsanitized input passed to exec.Command",
Language: "go",
Pattern: regexp.MustCompile(`exec\.Command\s*\([^"` + "`" + `][^,)]*\)`),
Antipattern: regexp.MustCompile(`exec\.Command\s*\(\s*"[^"]+"\s*\)`),
Severity: "critical",
Category: "security",
CWE: "CWE-78",
Fix: "Validate and sanitize all input passed to exec.Command; use an allowlist of permitted commands",
},
{
ID: "SEC-GO-003",
Name: "Path Traversal",
Description: "User-controlled path used in file operations without filepath.Clean or validation",
Language: "go",
Pattern: regexp.MustCompile(`(?:os\.(?:Open|ReadFile|Create|WriteFile)|filepath\.Join)\s*\(.*(?:req\.|r\.|input|param|query|user)`),
Antipattern: regexp.MustCompile(`filepath\.Clean`),
Severity: "high",
Category: "security",
CWE: "CWE-22",
Fix: "Use filepath.Clean() and validate the resolved path is within the expected directory",
},
{
ID: "SEC-GO-004",
Name: "Hardcoded Secret",
Description: "Hardcoded password or secret string detected",
Language: "go",
Pattern: regexp.MustCompile(`(?i)(?:password|secret|api_?key|token|private_?key)\s*(?::?=|=)\s*"[^"]{4,}`),
Antipattern: regexp.MustCompile(`(?i)(?:test|example|placeholder|TODO|CHANGE|xxx|dummy)`),
Severity: "critical",
Category: "security",
CWE: "CWE-798",
Fix: "Use environment variables or a secrets manager instead of hardcoded credentials",
},
{
ID: "SEC-GO-005",
Name: "Insecure TLS",
Description: "TLS certificate verification is disabled",
Language: "go",
Pattern: regexp.MustCompile(`InsecureSkipVerify\s*:\s*true`),
Antipattern: nil,
Severity: "high",
Category: "security",
CWE: "CWE-295",
Fix: "Remove InsecureSkipVerify: true; configure proper TLS certificate validation",
},
{
ID: "SEC-GO-006",
Name: "Weak Crypto (MD5)",
Description: "MD5 is cryptographically broken and should not be used for security purposes",
Language: "go",
Pattern: regexp.MustCompile(`md5\.(?:New|Sum)`),
Antipattern: regexp.MustCompile(`(?i)(?:checksum|fingerprint|cache|etag|non.?security)`),
Severity: "medium",
Category: "security",
CWE: "CWE-328",
Fix: "Use crypto/sha256 or crypto/sha512 instead of MD5 for security-sensitive operations",
},
{
ID: "SEC-GO-007",
Name: "Weak Crypto (SHA1)",
Description: "SHA-1 is deprecated for security use; collisions are practical",
Language: "go",
Pattern: regexp.MustCompile(`sha1\.(?:New|Sum)`),
Antipattern: regexp.MustCompile(`(?i)(?:git|fingerprint|cache|etag|non.?security)`),
Severity: "medium",
Category: "security",
CWE: "CWE-328",
Fix: "Use crypto/sha256 or crypto/sha512 instead of SHA-1 for security-sensitive operations",
},
{
ID: "SEC-GO-008",
Name: "Unvalidated Redirect",
Description: "HTTP redirect using user-controlled input without validation",
Language: "go",
Pattern: regexp.MustCompile(`http\.Redirect\s*\(.*(?:r\.(?:URL|Form|Query)|req\.|param|input)`),
Antipattern: nil,
Severity: "medium",
Category: "security",
CWE: "CWE-601",
Fix: "Validate redirect URLs against an allowlist of permitted destinations",
},
{
ID: "SEC-GO-009",
Name: "Sensitive Data in Log",
Description: "Potentially logging sensitive data (passwords, tokens, secrets)",
Language: "go",
Pattern: regexp.MustCompile(`(?:log\.|slog\.|logger\.)(?:Print|Info|Debug|Warn|Error|Fatal).*(?i)(?:password|token|secret|key|credential)`),
Antipattern: regexp.MustCompile(`(?i)(?:redact|mask|\*\*\*|<hidden>)`),
Severity: "medium",
Category: "security",
CWE: "CWE-532",
Fix: "Redact sensitive values before logging; never log passwords, tokens, or API keys",
},
{
ID: "SEC-GO-010",
Name: "Defer in Loop",
Description: "defer inside a loop can accumulate resources until the function returns",
Language: "go",
Pattern: regexp.MustCompile(`^\s*defer\s+`),
Antipattern: nil,
Severity: "medium",
Category: "correctness",
CWE: "",
Fix: "Move the deferred call into a helper function or handle resource cleanup explicitly in the loop",
},
}
}

// goCorrectnessRules returns built-in correctness rules for Go.
func goCorrectnessRules() []StaticRule {
return []StaticRule{
// =====================================================================
// CORRECTNESS - Go
// =====================================================================
{
ID: "COR-GO-001",
Name: "Unchecked Error",
Description: "Function return value that likely includes an error is discarded",
Language: "go",
Pattern: regexp.MustCompile(`^\s*[a-zA-Z_][a-zA-Z0-9_.]*\s*\(.*\)\s*$`),
Antipattern: regexp.MustCompile(`(?:^//|^\s*//|defer|go\s+|fmt\.Print|log\.|println|print\()`),
Severity: "medium",
Category: "correctness",
CWE: "CWE-252",
Fix: "Capture and handle the returned error: if err != nil { return err }",
},
{
ID: "COR-GO-002",
Name: "Goroutine Leak Risk",
Description: "Goroutine launched without visible context cancellation or done channel",
Language: "go",
Pattern: regexp.MustCompile(`\bgo\s+func\s*\(`),
Antipattern: regexp.MustCompile(`(?:ctx|context|done|cancel|Done\(\)|quit|stop|timer|ticker)`),
Severity: "medium",
Category: "correctness",
CWE: "",
Fix: "Pass a context.Context or done channel to goroutines to enable graceful shutdown",
},
{
ID: "COR-GO-003",
Name: "Race Condition Risk",
Description: "Shared variable access in goroutine without synchronization",
Language: "go",
Pattern: regexp.MustCompile(`go\s+func\s*\([^)]*\)\s*\{[^}]*(?:[a-z]+\s*(?:\+\+|--|(?:\+|-|\*|/)=))`),
Antipattern: regexp.MustCompile(`(?:mutex|Mutex|sync\.|atomic\.|Lock\(\)|chan\s)`),
Severity: "high",
Category: "correctness",
CWE: "CWE-362",
Fix: "Use sync.Mutex, sync/atomic, or channels to synchronize shared state access",
},
{
ID: "COR-GO-004",
Name: "nil Map Write",
Description: "Writing to a potentially nil map causes a runtime panic",
Language: "go",
Pattern: regexp.MustCompile(`var\s+\w+\s+map\[`),
Antipattern: regexp.MustCompile(`=\s*(?:make|map\[)`),
Severity: "high",
Category: "correctness",
CWE: "",
Fix: "Initialize the map with make() before writing to it",
},
}
}
72 changes: 72 additions & 0 deletions static_rules_java.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package sight

import "regexp"

// javaSecurityRules returns built-in static analysis rules for Java.
func javaSecurityRules() []StaticRule {
return []StaticRule{
// =====================================================================
// SECURITY - Java
// =====================================================================
{
ID: "SEC-JV-001",
Name: "SQL Injection (Java)",
Description: "String concatenation used in SQL statement execution; use PreparedStatement instead",
Language: "java",
Pattern: regexp.MustCompile(`(?:(?:execute|executeQuery|executeUpdate)\s*\(.*\+)|(?:(?:Statement|createStatement).*\+)`),
Antipattern: regexp.MustCompile(`PreparedStatement|setParameter|bindValue`),
Severity: "critical",
Category: "security",
CWE: "CWE-89",
Fix: "Use PreparedStatement with parameter placeholders (?) instead of string concatenation",
},
{
ID: "SEC-JV-002",
Name: "Insecure Deserialization (Java)",
Description: "ObjectInputStream.readObject() can execute arbitrary code during deserialization",
Language: "java",
Pattern: regexp.MustCompile(`ObjectInputStream.*\.readObject\s*\(`),
Antipattern: regexp.MustCompile(`(?i)(?:ValidatingObjectInputStream|ObjectInputFilter|whiteList|allowList)`),
Severity: "critical",
Category: "security",
CWE: "CWE-502",
Fix: "Use ValidatingObjectInputStream or an ObjectInputFilter with a strict allowlist",
},
{
ID: "SEC-JV-003",
Name: "SSRF (Java)",
Description: "new URL() with user-controlled input may lead to Server-Side Request Forgery",
Language: "java",
Pattern: regexp.MustCompile(`new\s+URL\s*\(.*(?:req\.|request\.|param|input|query|user)`),
Antipattern: regexp.MustCompile(`(?i)(?:allowList|whitelist|validate.*url|UriUtils)`),
Severity: "high",
Category: "security",
CWE: "CWE-918",
Fix: "Validate URLs against an allowlist of permitted domains and schemes",
},
{
ID: "SEC-JV-004",
Name: "Hardcoded Secret (Java)",
Description: "Hardcoded password or secret string detected",
Language: "java",
Pattern: regexp.MustCompile(`(?i)(?:password|secret|api_?key|token|private_?key)\s*=\s*"[^"]{4,}"`),
Antipattern: regexp.MustCompile(`(?i)(?:test|example|placeholder|TODO|CHANGE|xxx|dummy|fake|System\.getenv)`),
Severity: "critical",
Category: "security",
CWE: "CWE-798",
Fix: "Use environment variables, a secrets manager, or a vault instead of hardcoded credentials",
},
{
ID: "SEC-JV-005",
Name: "Path Traversal (Java)",
Description: "new File() with user-controlled input may allow path traversal attacks",
Language: "java",
Pattern: regexp.MustCompile(`new\s+File\s*\(.*(?:req\.|request\.|param|input|query|user|\+)`),
Antipattern: regexp.MustCompile(`(?i)(?:canonicalPath|normalize|sanitizePath|getCanonicalPath)`),
Severity: "high",
Category: "security",
CWE: "CWE-22",
Fix: "Validate and normalize paths; ensure the resolved path is within the expected directory",
},
}
}
72 changes: 72 additions & 0 deletions static_rules_performance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package sight

import "regexp"

// performanceRules returns built-in performance rules across all languages.
func performanceRules() []StaticRule {
return []StaticRule{
// =====================================================================
// PERFORMANCE
// =====================================================================
{
ID: "PERF-GO-001",
Name: "String Concat in Loop",
Description: "String concatenation with += in a loop causes quadratic allocation",
Language: "go",
Pattern: regexp.MustCompile(`\w+\s*\+=\s*(?:"|\w+)`),
Antipattern: regexp.MustCompile(`(?:int|float|byte|rune|uint|count|total|sum|num|idx|index|offset|i\s*\+)`),
Severity: "low",
Category: "performance",
CWE: "",
Fix: "Use strings.Builder for string concatenation in loops",
},
{
ID: "PERF-GO-002",
Name: "Unbounded Allocation",
Description: "Slice allocation with user-controlled size without bounds check",
Language: "go",
Pattern: regexp.MustCompile(`make\s*\(\s*\[\]\w+\s*,\s*(?:req\.|r\.|input|param|query|size|length|count|n\b)`),
Antipattern: regexp.MustCompile(`(?:maxSize|maxLen|cap|min\(|max\(|limit|<=|>=|<\s*\d|>\s*\d)`),
Severity: "medium",
Category: "performance",
CWE: "CWE-789",
Fix: "Validate and cap allocation sizes to prevent denial of service via memory exhaustion",
},
{
ID: "PERF-GO-003",
Name: "N+1 Query Pattern",
Description: "Database query inside a loop suggests N+1 query problem",
Language: "go",
Pattern: regexp.MustCompile(`(?:\.Query|\.Exec|\.Get|\.Find|\.First|\.Select|\.Where)\s*\(`),
Antipattern: regexp.MustCompile(`(?:batch|bulk|IN\s*\(|ids|Preload|Join)`),
Severity: "medium",
Category: "performance",
CWE: "",
Fix: "Batch database queries outside the loop; use IN clauses or JOINs",
},
{
ID: "PERF-PY-001",
Name: "N+1 Query Pattern (Python)",
Description: "Database query inside a loop suggests N+1 query problem",
Language: "python",
Pattern: regexp.MustCompile(`(?:\.execute|\.query|\.filter|\.get|session\.)\s*\(`),
Antipattern: regexp.MustCompile(`(?:bulk|batch|in_|prefetch|select_related|join)`),
Severity: "medium",
Category: "performance",
CWE: "",
Fix: "Use select_related/prefetch_related (Django) or eager loading (SQLAlchemy) to batch queries",
},
{
ID: "PERF-ANY-001",
Name: "Synchronous Sleep",
Description: "Hardcoded sleep/delay found; consider exponential backoff or event-driven approach",
Language: "any",
Pattern: regexp.MustCompile(`(?:time\.Sleep|sleep\(|setTimeout.*\d{4,}|Thread\.sleep)`),
Antipattern: regexp.MustCompile(`(?i)(?:test|spec|mock|backoff|retry)`),
Severity: "low",
Category: "performance",
CWE: "",
Fix: "Use exponential backoff for retries or event-driven signaling instead of fixed sleeps",
},
}
}
Loading
Loading