Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
29995d5
Bump commons-io:commons-io from 2.10.0 to 2.14.0 in /tests
dependabot[bot] Nov 19, 2024
bbf1bf4
Bump requests from 2.12.1 to 2.32.2
dependabot[bot] Nov 19, 2024
a2b5958
Merge pull request #1 from cognitivegears/dependabot/maven/tests/comm…
cognitivegears Nov 19, 2024
3fe4f10
Merge pull request #2 from cognitivegears/dependabot/pip/requests-2.32.2
cognitivegears Nov 19, 2024
109788d
Fix multiline string formatting in main function
cognitivegears Nov 19, 2024
4a89de0
Handle 404 status code and improve error handling in recv_pkg_info fu…
cognitivegears Nov 19, 2024
ccff951
Add PyPI scanner and update requirements for requirements-parser
cognitivegears Nov 19, 2024
0f9b594
Refactor file path handling in Maven and NPM scanners to use os.path.…
cognitivegears Nov 19, 2024
1fdb1fc
Fix argument parsing and improve error handling in package functions
cognitivegears Nov 20, 2024
8ddbc6a
Refactor package manager handling and error codes; introduce constant…
cognitivegears Nov 20, 2024
a81e6c9
Add logging functionality and improve error handling across package r…
cognitivegears Nov 20, 2024
9a82db0
Add recursive scanning option for package managers and enhance error …
cognitivegears Nov 20, 2024
8d100be
Remove duplicate entries in dependency lists across Maven, NPM, and P…
cognitivegears Nov 20, 2024
bebd71f
Changed packages not exist to a warning
cognitivegears Nov 20, 2024
98aa05d
Add error handling for warnings and new exit code for package not found
cognitivegears Nov 20, 2024
3455502
Update project configuration and dependencies
cognitivegears Nov 20, 2024
05a0c23
Enhance package analysis with detailed docstrings and logging improve…
cognitivegears Nov 20, 2024
b277998
Add request timeout handling and constant for HTTP requests
cognitivegears Nov 20, 2024
11460d6
Moved argument parsing for Combobulator
cognitivegears Nov 20, 2024
f8a39b2
Remove currently unused GitHub token argument
cognitivegears Nov 20, 2024
60dc060
Update README to include 'pypi' as a supported package manager type a…
cognitivegears Nov 20, 2024
5a52358
Possible fix/workaround for old scan issue
cognitivegears Nov 22, 2024
6bdbfcb
Version count conditional backwards
cognitivegears Nov 22, 2024
5593b01
Added rate limiting, added additional npm info for heuristics, and ad…
cognitivegears Nov 22, 2024
2976da0
Remove unused imports from combobulator.py
cognitivegears Nov 22, 2024
ae5c7d7
Add JSON export functionality and update README with new argument
cognitivegears Nov 22, 2024
e12bfa2
Refactor heuristics scoring logic to use default thresholds from Defa…
cognitivegears Nov 22, 2024
e665ccb
Add risk assessment properties and update heuristics logic for packag…
cognitivegears Nov 22, 2024
68ec38d
Add risk assessment check and update export functions to include risk…
cognitivegears Nov 22, 2024
f9a2ea9
Update risk handling in combobulator.py to log identified risks and a…
cognitivegears Nov 22, 2024
65e6d1d
Add quiet mode option to suppress console output and adjust logging c…
cognitivegears Nov 22, 2024
d220fbd
Added CONTRIBUTERS.md file
cognitivegears Nov 24, 2024
676c0a5
Bump requests from 2.32.2 to 2.32.4
dependabot[bot] Jun 10, 2025
e61e19d
Bump org.apache.commons:commons-lang3 from 3.10 to 3.18.0 in /tests
dependabot[bot] Jul 12, 2025
6a8b963
Merge pull request #3 from cognitivegears/dependabot/pip/requests-2.32.4
cognitivegears Sep 3, 2025
37b7f70
Merge pull request #4 from cognitivegears/dependabot/maven/tests/org.…
cognitivegears Sep 3, 2025
ea0a833
Moved to uv
cognitivegears Sep 3, 2025
da2cf7c
Renamed to depgate
cognitivegears Sep 3, 2025
cf8709e
Updates for release
cognitivegears Sep 3, 2025
2d3c6a1
Updated README
cognitivegears Sep 4, 2025
19b3bc3
Bump actions/checkout from 4 to 5
dependabot[bot] Sep 4, 2025
f8e04b1
Update requests requirement from <2.32.5,>=2.32.4 to >=2.32.4,<2.32.6
dependabot[bot] Sep 4, 2025
904f163
Bump actions/download-artifact from 4 to 5
dependabot[bot] Sep 4, 2025
b6729ea
Merge pull request #5 from cognitivegears/dependabot/github_actions/a…
cognitivegears Sep 4, 2025
5e3ebaa
Merge pull request #6 from cognitivegears/dependabot/pip/requests-gte…
cognitivegears Sep 4, 2025
eb81f98
Merge pull request #7 from cognitivegears/dependabot/github_actions/a…
cognitivegears Sep 4, 2025
5a63e28
Small visual improvements
cognitivegears Sep 4, 2025
15e569a
Fixed some pylint warnings
cognitivegears Sep 7, 2025
1c6dd78
Added e2e tests
cognitivegears Sep 7, 2025
993168a
Added github action
cognitivegears Sep 7, 2025
70f259d
refactor(cli): extract helpers to reduce branches; keep lazy imports …
cognitivegears Sep 8, 2025
065f1d1
lint: add targeted pylint disables for data-holder classes; document …
cognitivegears Sep 8, 2025
bbac403
Changes for gitignore
cognitivegears Sep 8, 2025
38ff25d
Initial version of source code repository integration
cognitivegears Sep 8, 2025
346cb27
Modified to reduce duplicate code
cognitivegears Sep 8, 2025
f16666c
Extracted common code and moved
cognitivegears Sep 8, 2025
0b93293
Bump actions/setup-python from 5 to 6
dependabot[bot] Sep 8, 2025
e91dbe8
Bump actions/checkout from 4 to 5
dependabot[bot] Sep 8, 2025
be2b4a1
Added logging
cognitivegears Sep 9, 2025
9e56fc3
Added debug logging
cognitivegears Sep 9, 2025
73acbf2
Fixed lookup of npm repo information
cognitivegears Sep 9, 2025
bf9f95d
Matched version checking fixed
cognitivegears Sep 9, 2025
0d582af
Small change to wording
cognitivegears Sep 9, 2025
12e4657
Setting version information
cognitivegears Sep 9, 2025
95ae5c8
Fixed version lookup
cognitivegears Sep 9, 2025
6d27239
Fixed npm resolution for latest
cognitivegears Sep 9, 2025
abd8b82
Improved release and tag comparisons
cognitivegears Sep 10, 2025
3dffc03
Added tests for recent changes
cognitivegears Sep 10, 2025
ac013f3
General cleanup
cognitivegears Sep 10, 2025
4545380
Enhanced config file
cognitivegears Sep 10, 2025
c9d36a4
Added http rate limiting and retry support
cognitivegears Sep 10, 2025
7bec27f
updated example
cognitivegears Sep 10, 2025
ffadbf1
Initial version of policy based scans
cognitivegears Sep 10, 2025
6d6086e
Changed command line arguments
cognitivegears Sep 10, 2025
25a352d
Fixed pypi license checking
cognitivegears Sep 11, 2025
affbe69
Fixed npm license checking
cognitivegears Sep 11, 2025
02ffa8f
Fixed small bug with maven lookup
cognitivegears Sep 11, 2025
a5abc74
Added depsdev for further enrichment
cognitivegears Sep 11, 2025
5cd1532
Fixes scanning lock files
cognitivegears Sep 11, 2025
7f7735f
Added dev and test dep detection, transitive and direct
cognitivegears Sep 11, 2025
d49ca6f
Fixed regression in npm
cognitivegears Sep 11, 2025
851f8b4
Fixed bug with npm naming
cognitivegears Sep 12, 2025
0ac25b6
Refactoring
cognitivegears Sep 12, 2025
fd8eb39
Added new linked mode to validate package linkage
cognitivegears Sep 12, 2025
a916748
Updated to use scan semantics
cognitivegears Sep 12, 2025
4bdbe42
Updated version
cognitivegears Sep 12, 2025
c5a94fe
Added linked policy type
cognitivegears Sep 12, 2025
93a23b1
Added partial match support
cognitivegears Sep 16, 2025
1ba0c19
Merge pull request #8 from cognitivegears/dependabot/github_actions/a…
cognitivegears Sep 16, 2025
5815d8b
Merge pull request #9 from cognitivegears/dependabot/github_actions/a…
cognitivegears Sep 16, 2025
362e6b5
Initial version of MCP
cognitivegears Oct 18, 2025
878d5f1
Fixed pylint issues
cognitivegears Oct 18, 2025
41a188b
Bump actions/upload-artifact from 4 to 5
dependabot[bot] Oct 27, 2025
3c6742b
Bump actions/download-artifact from 5 to 6
dependabot[bot] Oct 27, 2025
f6813b3
Landed MCP support for Depgate
cognitivegears Nov 5, 2025
beeb516
Merge pull request #10 from cognitivegears/dependabot/github_actions/…
cognitivegears Nov 5, 2025
81b8f14
Merge pull request #11 from cognitivegears/dependabot/github_actions/…
cognitivegears Nov 5, 2025
675e2f9
Code review changes
cognitivegears Nov 5, 2025
9881774
Additional code improvements
cognitivegears Nov 5, 2025
ec9e64c
Additional code cleanup
cognitivegears Nov 5, 2025
7700f1e
Merge pull request #12 from cognitivegears/feature/mcp
cognitivegears Nov 5, 2025
007b190
Added fix for hanging
cognitivegears Nov 6, 2025
a082ed9
Changes to make warnings more obvious
cognitivegears Nov 6, 2025
b0c1d46
Additional code review changes
cognitivegears Nov 6, 2025
88f1f8f
Code review security changes
cognitivegears Nov 6, 2025
f73cb2a
Code review changes
cognitivegears Nov 6, 2025
dd92f25
Merge pull request #13 from cognitivegears/bugfix/mcp_hanging
cognitivegears Nov 6, 2025
21ec675
Bug fix for warnings
cognitivegears Nov 6, 2025
822f110
Merge pull request #14 from cognitivegears/bugfix/version_warning
cognitivegears Nov 6, 2025
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
Prev Previous commit
Next Next commit
Added partial match support
  • Loading branch information
cognitivegears committed Sep 16, 2025
commit 93a23b1c253af8908e6f9476fadf146f2d5507cc
6 changes: 6 additions & 0 deletions docs/depgate.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ policy:
# allowed_providers:
# - github
# - gitlab
# # Repository name matching (optional):
# # off -> do not enforce name match (default)
# # exact -> package_name must equal repo name (case-insensitive)
# # partial -> package_name and repo name must share a substring of at least N characters
# name_match: off # off | exact | partial
# name_match_min_len: 3 # minimum overlap length for partial
# # Optional overrides:
# # repo: "org/name" # If not derivable from metadata
# # branch: "main" # If refs should be validated against a specific branch
Expand Down
98 changes: 98 additions & 0 deletions src/analysis/policy_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,9 @@ def evaluate(self, facts: Dict[str, Any], config: Dict[str, Any]) -> Dict[str, A
require_ver = bool(config.get("require_version_in_source", False))
patterns = config.get("version_tag_patterns") or ["v{version}", "{version}"]
allowed_providers = config.get("allowed_providers") or []
# New: repository name matching
name_match_mode = str(config.get("name_match", "off")).lower()
name_match_min_len = int(config.get("name_match_min_len", 3))

violations: List[str] = []
evaluated: Dict[str, Any] = {}
Expand All @@ -282,6 +285,22 @@ def evaluate(self, facts: Dict[str, Any], config: Dict[str, Any]) -> Dict[str, A
exists = facts.get("source_repo_exists")
version_found = facts.get("version_found_in_source")
version = facts.get("resolved_version")
pkg_name = facts.get("package_name")

# Debug: configuration and facts snapshot for this evaluation
try:
logger.debug(
"[linked] config: enabled=%s require_src=%s require_ver=%s allowed_providers=%s name_match=%s name_match_min_len=%s",
str(enabled), str(require_src), str(require_ver),
list(allowed_providers) if isinstance(allowed_providers, list) else allowed_providers,
name_match_mode, str(name_match_min_len)
)
logger.debug(
"[linked] facts: package=%s repo_url=%s host=%s resolved=%s exists=%s version=%s version_found=%s",
str(pkg_name), str(repo_url), str(host), str(resolved), str(exists), str(version), str(version_found)
)
except Exception:
pass

evaluated.update({
"source_repo": repo_url,
Expand Down Expand Up @@ -326,7 +345,86 @@ def evaluate(self, facts: Dict[str, Any], config: Dict[str, Any]) -> Dict[str, A
f"linked: version not found in SCM (repo={rstr}, version={vstr}, patterns=[{pstr}])"
)

# Repository name matching (off | exact | partial)
def _extract_repo_name(url: Any) -> Optional[str]:
try:
s = str(url).rstrip("/")
seg = s.split("/")[-1] if "/" in s else s
if seg.endswith(".git"):
seg = seg[:-4]
return seg or None
except Exception:
return None

def _sanitize(s: str) -> str:
try:
return "".join(ch for ch in s.lower() if ch.isalnum())
except Exception:
return ""

def _has_min_len_substring(a: str, b: str, min_len: int) -> bool:
a_s = _sanitize(a)
b_s = _sanitize(b)
if not a_s or not b_s or min_len <= 0:
return False
# Ensure we iterate over the shorter string
if len(a_s) > len(b_s):
a_s, b_s = b_s, a_s
if len(a_s) < min_len:
return False
# Check any substring of length == min_len (sufficient for >= min_len)
for i in range(0, len(a_s) - min_len + 1):
sub = a_s[i : i + min_len]
if sub in b_s:
return True
return False

if name_match_mode in ("exact", "partial"):
if not repo_url:
violations.append("linked: repository name match requested but no repository URL is available")
else:
repo_name = _extract_repo_name(repo_url)
try:
logger.debug(
"[linked] name_match start: mode=%s min_len=%s pkg=%s repo_url=%s repo_name=%s",
name_match_mode, str(name_match_min_len), str(pkg_name), str(repo_url), str(repo_name)
)
except Exception:
pass
if not repo_name:
violations.append("linked: repository name could not be parsed from URL for name match validation")
else:
if name_match_mode == "exact":
match_ok = bool(isinstance(pkg_name, str) and str(pkg_name).lower() == str(repo_name).lower())
try:
logger.debug("[linked] name_match exact: pkg=%s repo=%s ok=%s",
str(pkg_name), str(repo_name), str(match_ok))
except Exception:
pass
if not match_ok:
violations.append(
f"linked: repository name '{repo_name}' does not match package name '{pkg_name}' (mode=exact)"
)
else: # partial
match_ok = bool(isinstance(pkg_name, str) and _has_min_len_substring(repo_name, pkg_name, name_match_min_len))
try:
logger.debug("[linked] name_match partial: pkg=%s repo=%s min_len=%s ok=%s",
str(pkg_name), str(repo_name), str(name_match_min_len), str(match_ok))
except Exception:
pass
if not match_ok:
violations.append(
f"linked: package name '{pkg_name}' does not overlap repository name '{repo_name}' with min length {name_match_min_len} (mode=partial)"
)

decision = "allow" if not violations else "deny"
try:
logger.debug(
"[linked] decision=%s violations=%d details=%s",
decision, len(violations), "; ".join(violations) if violations else "none"
)
except Exception:
pass
return {
"decision": decision,
"violated_rules": violations,
Expand Down
24 changes: 22 additions & 2 deletions src/analysis/policy_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import json
import logging
from typing import Sequence
from constants import Constants


def run_policy_analysis(args, instances: Sequence[object]) -> None:
Expand All @@ -31,6 +32,7 @@ def run_policy_analysis(args, instances: Sequence[object]) -> None:
from analysis import heuristics as _heur # pylint: disable=import-outside-toplevel

logger = logging.getLogger(__name__)
STG = f"{Constants.ANALYSIS} "

# Step 1: Build facts for all packages
fact_builder = FactBuilder()
Expand Down Expand Up @@ -199,10 +201,28 @@ def _deep_merge(dest, src):
pkg.policy_decision = decision.decision
pkg.policy_violated_rules = decision.violated_rules
pkg.policy_evaluated_metrics = decision.evaluated_metrics
# Log results
# Debug-level details
try:
logger.debug(
"[policy] evaluated package=%s decision=%s violations=%d details=%s",
pname, decision.decision, len(decision.violated_rules or []),
"; ".join(decision.violated_rules or [])
)
except Exception:
pass
# Single ANALYSIS outcome log (INFO)
try:
logger.info(
"%sPolicy outcome: %s for %s (%d violations).",
STG, decision.decision.upper(), pname, len(decision.violated_rules or [])
)
except Exception:
pass
# Existing result logs
if decision.decision == "deny":
logger.warning("Policy DENY for %s: %s", pname, ", ".join(decision.violated_rules))
else:
logger.info("Policy ALLOW for %s", pname)
# Demote non-ANALYSIS outcome to debug to avoid duplicate INFO logs
logger.debug("Policy ALLOW for %s", pname)
except Exception as exc: # pylint: disable=broad-exception-caught
logger.error("Policy evaluation error for %s: %s", pname, exc)
55 changes: 55 additions & 0 deletions tests/policy/test_linked_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,58 @@ def test_factbuilder_maps_version_found_flag(self):
facts2 = FactBuilder().build_facts(mp)
# When "matched" is False, evaluator should see False (not None)
assert facts2.get("version_found_in_source") is False


class TestLinkedPolicyNameMatch:
def test_name_match_exact_pass(self):
mp = _make_mp_with_repo(name="lodash")
mp.repo_url_normalized = "https://github.com/acme/lodash"
facts = FactBuilder().build_facts(mp)

policy = {"rules": [{"type": "linked", "enabled": True, "name_match": "exact"}]}
engine = create_policy_engine()
decision = engine.evaluate_policy(facts, policy)
assert decision.decision == "allow"

def test_name_match_exact_fail(self):
mp = _make_mp_with_repo(name="lodash-es")
mp.repo_url_normalized = "https://github.com/acme/lodash"
facts = FactBuilder().build_facts(mp)

policy = {"rules": [{"type": "linked", "enabled": True, "name_match": "exact"}]}
engine = create_policy_engine()
decision = engine.evaluate_policy(facts, policy)
assert decision.decision == "deny"
assert any("mode=exact" in v for v in decision.violated_rules)

def test_name_match_partial_pass(self):
mp = _make_mp_with_repo(name="lodash-es")
mp.repo_url_normalized = "https://github.com/acme/lodash"
facts = FactBuilder().build_facts(mp)

policy = {"rules": [{"type": "linked", "enabled": True, "name_match": "partial", "name_match_min_len": 3}]}
engine = create_policy_engine()
decision = engine.evaluate_policy(facts, policy)
assert decision.decision == "allow"

def test_name_match_partial_fail_short_overlap(self):
mp = _make_mp_with_repo(name="ab")
mp.repo_url_normalized = "https://github.com/acme/abc"
facts = FactBuilder().build_facts(mp)

policy = {"rules": [{"type": "linked", "enabled": True, "name_match": "partial", "name_match_min_len": 3}]}
engine = create_policy_engine()
decision = engine.evaluate_policy(facts, policy)
assert decision.decision == "deny"
assert any("mode=partial" in v for v in decision.violated_rules)

def test_name_match_requested_but_no_repo_url_fails(self):
mp = _make_mp_with_repo(name="mypkg")
mp.repo_url_normalized = None
facts = FactBuilder().build_facts(mp)

policy = {"rules": [{"type": "linked", "enabled": True, "name_match": "exact"}]}
engine = create_policy_engine()
decision = engine.evaluate_policy(facts, policy)
assert decision.decision == "deny"
assert any("name match requested" in v.lower() for v in decision.violated_rules)