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: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,10 +311,10 @@ record to prove it could is not worth the finding.
| **TypeScript/JavaScript** (Next.js, Express, tRPC, GraphQL) | Yes | Yes | Yes (npm) | Yes |
| **Python** (Django, FastAPI, Flask) | Yes | Yes | Yes (pip) | Yes |
| **Java/Kotlin** (Spring Boot) | Yes | Yes | Yes (Maven, Gradle) | Yes |
| **Go** (net/http, Gin, Echo, chi, gorilla) | Yes | Yes | No | Yes |
| **Go** (net/http, Gin, Echo, chi, gorilla) | Yes | Yes | Yes (go.mod) | Yes |
| **Ruby, Rust, PHP, etc.** | No | No | No | Yes (DAST works against any HTTP API) |

DAST scanners test live HTTP endpoints regardless of backend language. SAST route mapping, auth detection, and dependency scanning are language-specific. Go auth detection is per-route (router/group middleware, in-handler checks) and the gopls LSP refines it — following a cross-file auth helper via go-to-definition to confirm a guard or suppress a false "missing auth". Dependency (go.mod) scanning is the one Go gap.
DAST scanners test live HTTP endpoints regardless of backend language. SAST route mapping, auth detection, and dependency scanning are language-specific. Go auth detection is per-route (router/group middleware, in-handler checks) and the gopls LSP refines it — following a cross-file auth helper via go-to-definition to confirm a guard or suppress a false "missing auth". Go module dependencies (`go.mod`, direct and indirect) are checked against the OSV.dev vulnerability database, like npm/PyPI/Maven.

## Output Formats

Expand Down
47 changes: 46 additions & 1 deletion isitsecure/engine/code_analysis/osv_dependency_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class OSVDependencyScanner:
"""Scans all dependency files against the OSV.dev vulnerability database.

Unified scanner for npm (package.json), PyPI (requirements.txt,
pyproject.toml), and Maven/Gradle (pom.xml, build.gradle).
pyproject.toml), Maven/Gradle (pom.xml, build.gradle), and Go (go.mod).
"""

SCANNER_NAME = "osv_dependency_scanner"
Expand Down Expand Up @@ -117,9 +117,54 @@ def _extract_all_dependencies(self, repo: RepoSnapshot) -> list[ParsedDependency
deps.extend(self._extract_maven(file_path, content))
elif name in ("build.gradle", "build.gradle.kts"):
deps.extend(self._extract_gradle(file_path, content))
elif name == "go.mod":
deps.extend(self._extract_go(file_path, content))

return deps

def _extract_go(self, file_path: str, content: str) -> list[ParsedDependency]:
"""Extract Go modules from go.mod (direct and indirect ``require``s).

Handles both the block form::

require (
github.com/gin-gonic/gin v1.9.1
golang.org/x/crypto v0.14.0 // indirect
)

and the single-line form ``require github.com/x/y v1.2.3``. The
``module``/``go``/``toolchain``/``replace``/``exclude``/``retract``
directives are ignored — only versioned requires reach OSV. The leading
``v`` is stripped to the semver OSV's Go ecosystem compares against
(``+incompatible`` and pseudo-versions are preserved).
"""
deps: list[ParsedDependency] = []
in_require = False
for line_num, raw in enumerate(content.splitlines(), 1):
code = raw.split("//", 1)[0].strip() # drop `// indirect` etc.
if not code:
continue
if code.startswith("require (") or code == "require(":
in_require = True
continue
if in_require:
if code == ")":
in_require = False
continue
match = re.match(r"^(\S+)\s+(v\S+)$", code)
elif code.startswith("require "):
match = re.match(r"^require\s+(\S+)\s+(v\S+)$", code)
else:
match = None
if match:
deps.append(ParsedDependency(
name=match.group(1),
version=re.sub(r"^v", "", match.group(2)),
ecosystem="Go",
file_path=file_path, line_number=line_num, raw_line=raw.strip(),
))
return deps

def _extract_npm(self, file_path: str, content: str) -> list[ParsedDependency]:
"""Extract from package.json."""
import json
Expand Down
2 changes: 2 additions & 0 deletions isitsecure/engine/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,8 @@ class RepoIngestionConfig:
"build.gradle.kts",
"application.properties",
"application.yml",
# Go projects — go.mod feeds OSV dependency scanning
"go.mod",
)

# Directories to skip during indexing
Expand Down
36 changes: 35 additions & 1 deletion tests/engine/test_osv_dependency_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,44 @@ def test_multi_ecosystem(self, scanner):
"package.json": '{"dependencies":{"next":"13.4.0"}}',
"requirements.txt": "django==3.2.0\n",
"pom.xml": "<project><dependencies><dependency><groupId>g</groupId><artifactId>a</artifactId><version>1.0</version></dependency></dependencies></project>",
"go.mod": "module x\nrequire github.com/gin-gonic/gin v1.9.1\n",
})
deps = scanner._extract_all_dependencies(snapshot)
ecosystems = {d.ecosystem for d in deps}
assert ecosystems == {"npm", "PyPI", "Maven"}
assert ecosystems == {"npm", "PyPI", "Maven", "Go"}

def test_extracts_go_block_and_singleline(self, scanner):
snapshot = _make_snapshot({
"go.mod": (
"module example.com/app\n\ngo 1.21\n\n"
"require (\n"
"\tgithub.com/gin-gonic/gin v1.9.1\n"
"\tgolang.org/x/crypto v0.14.0 // indirect\n"
")\n\n"
"require github.com/dgrijalva/jwt-go v3.2.0+incompatible\n\n"
"replace github.com/foo/bar => github.com/foo/bar v1.0.1\n"
)
})
deps = {d.name: d for d in scanner._extract_all_dependencies(snapshot)}
assert set(deps) == {
"github.com/gin-gonic/gin",
"golang.org/x/crypto",
"github.com/dgrijalva/jwt-go",
}
assert all(d.ecosystem == "Go" for d in deps.values())
# leading v stripped; +incompatible preserved; indirect included
assert deps["github.com/gin-gonic/gin"].version == "1.9.1"
assert deps["github.com/dgrijalva/jwt-go"].version == "3.2.0+incompatible"

def test_go_skips_directives(self, scanner):
"""module/go/toolchain/replace/exclude are not dependencies."""
snapshot = _make_snapshot({
"go.mod": (
"module example.com/app\ngo 1.21\ntoolchain go1.21.5\n"
"exclude github.com/bad/pkg v1.0.0\n"
)
})
assert scanner._extract_all_dependencies(snapshot) == []


class TestSeverityExtraction:
Expand Down
Loading