Skip to content
Open
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
56 changes: 54 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@
</div>

```bash
pip install cognis-depgraph
pip install "git+https://github.com/cognis-digital/depgraph.git"
depgraph scan . # → prioritized findings in seconds
```

<!-- cognis:layman:start -->
## What is this?

depgraph checks your project's dependency list for known security problems before they cause you harm. It reads a standard requirements file, flags any packages with known vulnerabilities or suspicious names that look like typos of real libraries (a common attack), and gives each package a simple A–F safety grade. The tool runs entirely on your own machine with no internet connection required, making it fast and safe to use in automated build pipelines. It is aimed at software developers and security teams who want a quick, offline way to catch supply-chain risks in Python and JavaScript projects.
<!-- cognis:layman:end -->

## Contents

- [Why depgraph?](#why) · [Features](#features) · [Quick start](#quick-start) · [Example](#example) · [Architecture](#architecture) · [AI stack](#ai-stack) · [How it compares](#how-it-compares) · [Integrations](#integrations) · [Install anywhere](#install-anywhere) · [Related](#related) · [Contributing](#contributing)
Expand All @@ -46,10 +52,56 @@ Dependency risk visualizer — Scorecard + OSV + typosquat + maintainer signals
<div align="right"><a href="#top">↑ back to top</a></div>

<a name="quick-start"></a>
<!-- cognis:domains:start -->
## Domains

**Primary domain:** Cloud & DevTools · **JTF MERIDIAN division:** ATHENA-PRIME · COGNI-2

**Topics:** `cognis` `devtools` `cloud` `developer-tools`

Part of the **Cognis Neural Suite** — 300+ source-available tools organized across 12 domains under the JTF MERIDIAN command structure. See the [suite on GitHub](https://github.com/cognis-digital) and [jtf-meridian](https://github.com/cognis-digital/jtf-meridian) for how the pieces fit together.
<!-- cognis:domains:end -->

<!-- cognis:install:start -->
## Install

`depgraph` is source-available (not published to PyPI) — every method below installs
straight from GitHub. Pick whichever you prefer; the one-line scripts auto-detect
the best tool available on your machine.

**One-liner (Linux / macOS):**
```sh
curl -fsSL https://raw.githubusercontent.com/cognis-digital/depgraph/HEAD/install.sh | sh
```

**One-liner (Windows PowerShell):**
```powershell
irm https://raw.githubusercontent.com/cognis-digital/depgraph/HEAD/install.ps1 | iex
```

**Or install manually — any one of:**
```sh
pipx install "git+https://github.com/cognis-digital/depgraph.git" # isolated (recommended)
uv tool install "git+https://github.com/cognis-digital/depgraph.git" # uv
pip install "git+https://github.com/cognis-digital/depgraph.git" # pip
```

**From source:**
```sh
git clone https://github.com/cognis-digital/depgraph.git
cd depgraph && pip install .
```

Then run:
```sh
depgraph --help
```
<!-- cognis:install:end -->

## Quick start

```bash
pip install cognis-depgraph
pip install "git+https://github.com/cognis-digital/depgraph.git"
depgraph --version
depgraph scan . # scan current project
depgraph scan . --format json # machine-readable
Expand Down
10 changes: 8 additions & 2 deletions depgraph/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@


def _read_stdin() -> str:
return sys.stdin.read()
try:
return sys.stdin.read()
except UnicodeDecodeError as exc:
raise ValueError(f"stdin contains non-text data: {exc}") from exc


_GRADE_GLYPH = {"A": "A", "B": "B", "C": "C", "D": "D", "F": "F"}
Expand Down Expand Up @@ -173,9 +176,12 @@ def main(argv: Sequence[str] | None = None) -> int:
result = results[0] if len(results) == 1 else _merge_results(results)
else:
result = audit_text(_read_stdin(), filename="<stdin>")
except OSError as exc:
except (OSError, ValueError) as exc:
print(f"error: {exc}", file=sys.stderr)
return 2
except Exception as exc: # noqa: BLE001
print(f"error: unexpected failure — {exc}", file=sys.stderr)
return 2

if args.format == "json":
_emit_json(result.as_dict())
Expand Down
52 changes: 39 additions & 13 deletions depgraph/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import json
import re
from dataclasses import dataclass, field
from typing import Iterable

# ---------------------------------------------------------------------------
# Bundled data: popular package names (for typosquat heuristics)
Expand Down Expand Up @@ -342,7 +341,16 @@ def max_severity(self) -> str:
order = ["", "LOW", "MEDIUM", "HIGH", "CRITICAL"]
worst = ""
for f in self.findings:
if order.index(f.severity) > order.index(worst):
# Guard against unexpected severity values not in the order list.
try:
f_idx = order.index(f.severity)
except ValueError:
f_idx = 0
try:
w_idx = order.index(worst)
except ValueError:
w_idx = 0
if f_idx > w_idx:
worst = f.severity
return worst or "NONE"

Expand Down Expand Up @@ -455,16 +463,20 @@ def _parse_pipfile(text: str) -> list[Dependency]:
if "=" not in line or line.startswith("#"):
continue
name, _, rhs = line.partition("=")
name = name.strip().strip('"')
name = name.strip().strip('"').strip("'")
if not name:
continue
rhs = rhs.strip().strip('"').strip("'")
version = None
m = re.search(r"(\d[\w.]*)", rhs)
if rhs.startswith("==") or (m and rhs.lstrip("=").startswith(m.group(1))):
version = m.group(1) if m else None
if name:
deps.append(
Dependency(name=name, version=version, ecosystem="pypi", scope=scope)
)
try:
m = re.search(r"(\d[\w.]*)", rhs)
if rhs.startswith("==") or (m and rhs.lstrip("=").startswith(m.group(1))):
version = m.group(1) if m else None
except (TypeError, AttributeError):
version = None
deps.append(
Dependency(name=name, version=version, ecosystem="pypi", scope=scope)
)
return deps


Expand All @@ -481,15 +493,22 @@ def _clean_npm_version(spec: str) -> str | None:

def _parse_package_json(text: str) -> list[Dependency]:
deps: list[Dependency] = []
if not text or not text.strip():
return deps
try:
data = json.loads(text)
except json.JSONDecodeError:
return deps
except json.JSONDecodeError as exc:
raise ValueError(f"package.json is not valid JSON: {exc}") from exc
if not isinstance(data, dict):
raise ValueError("package.json must be a JSON object at the top level")
for key, scope in (("dependencies", "runtime"), ("devDependencies", "dev")):
block = data.get(key) or {}
if not isinstance(block, dict):
continue
for name, spec in block.items():
name = str(name).strip()
if not name:
continue
deps.append(
Dependency(
name=name,
Expand All @@ -502,7 +521,14 @@ def _parse_package_json(text: str) -> list[Dependency]:


def parse_manifest(text: str, filename: str = "") -> list[Dependency]:
"""Dispatch to the right parser based on filename / content sniffing."""
"""Dispatch to the right parser based on filename / content sniffing.

Raises ``ValueError`` for manifests that are structurally invalid (e.g.
malformed JSON in a package.json). Returns an empty list for empty or
comment-only manifests — that is not an error.
"""
if text is None:
text = ""
fn = (filename or "").lower()
if fn.endswith("package.json") or (text.lstrip().startswith("{") and '"dependencies"' in text):
return _parse_package_json(text)
Expand Down
29 changes: 29 additions & 0 deletions install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Comprehensive installer for cognis-digital/depgraph (Windows PowerShell).
# Tries: pipx -> uv -> pip (git+https) -> from source.
# depgraph is source-available and not on PyPI; all paths install from GitHub.
$ErrorActionPreference = "Stop"
$Repo = "depgraph"
$Url = "git+https://github.com/cognis-digital/depgraph.git"
$Git = "https://github.com/cognis-digital/depgraph.git"
function Say($m) { Write-Host "[$Repo] $m" -ForegroundColor Magenta }
function Have($c) { [bool](Get-Command $c -ErrorAction SilentlyContinue) }

if (-not (Have python) -and -not (Have py)) {
Say "Python 3.9+ is required but was not found. Install Python first."; exit 1
}
if (Have pipx) {
Say "Installing with pipx (isolated, recommended)..."
pipx install $Url; if ($LASTEXITCODE -eq 0) { Say "Done. Run: depgraph"; exit 0 }
}
if (Have uv) {
Say "Installing with uv..."
uv tool install $Url; if ($LASTEXITCODE -eq 0) { Say "Done. Run: depgraph"; exit 0 }
}
if (Have pip) {
Say "Installing with pip (user site)..."
pip install --user $Url; if ($LASTEXITCODE -eq 0) { Say "Done. Run: depgraph"; exit 0 }
}
Say "No packaging tool worked; falling back to a source clone."
$Tmp = Join-Path $env:TEMP "$Repo-src"
git clone --depth 1 $Git $Tmp
Say "Cloned to $Tmp - run: cd $Tmp; python -m pip install ."
44 changes: 34 additions & 10 deletions install.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,34 @@
#!/usr/bin/env sh
# Universal installer for depgraph. Prefers uv > pipx > pip; installs from the repo.
set -e
SRC="git+https://github.com/cognis-digital/depgraph.git"
echo "Installing depgraph ..."
if command -v uv >/dev/null 2>&1; then uv tool install "$SRC"
elif command -v pipx >/dev/null 2>&1; then pipx install "$SRC"
elif command -v python3 >/dev/null 2>&1; then python3 -m pip install --user "$SRC"
else echo "Need uv, pipx, or python3+pip"; exit 1; fi
echo "Done. Run: depgraph --help"
#!/usr/bin/env sh
# Comprehensive installer for cognis-digital/depgraph (Linux / macOS).
# Tries the best available method: pipx -> uv -> pip (git+https) -> from source.
# depgraph is source-available and not on PyPI; all paths install from GitHub.
set -eu

REPO="depgraph"
URL="git+https://github.com/cognis-digital/depgraph.git"
GITURL="https://github.com/cognis-digital/depgraph.git"

say() { printf '\033[1;35m[%s]\033[0m %s\n' "$REPO" "$1"; }
have() { command -v "$1" >/dev/null 2>&1; }

if ! have python3 && ! have python; then
say "Python 3.9+ is required but was not found. Install Python first."; exit 1
fi

if have pipx; then
say "Installing with pipx (isolated, recommended)..."
pipx install "$URL" && { say "Done. Run: depgraph"; exit 0; }
fi
if have uv; then
say "Installing with uv..."
uv tool install "$URL" && { say "Done. Run: depgraph"; exit 0; }
fi
if have pip3 || have pip; then
PIP="$(command -v pip3 || command -v pip)"
say "Installing with pip (user site)..."
"$PIP" install --user "$URL" && { say "Done. Run: depgraph"; exit 0; }
fi

say "No packaging tool worked; falling back to a source clone."
TMP="$(mktemp -d)"; git clone --depth 1 "$GITURL" "$TMP/$REPO"
say "Cloned to $TMP/$REPO — run: cd $TMP/$REPO && python3 -m pip install ."
2 changes: 1 addition & 1 deletion integrations/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Usage: <tool> scan . --format json | python integrations/webhook.py --url URL
"""
from __future__ import annotations
import argparse, json, sys, urllib.request
import argparse, sys, urllib.request

def main() -> int:
ap = argparse.ArgumentParser()
Expand Down
1 change: 1 addition & 0 deletions layman.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
depgraph checks your project's dependency list for known security problems before they cause you harm. It reads a standard requirements file, flags any packages with known vulnerabilities or suspicious names that look like typos of real libraries (a common attack), and gives each package a simple A–F safety grade. The tool runs entirely on your own machine with no internet connection required, making it fast and safe to use in automated build pipelines. It is aimed at software developers and security teams who want a quick, offline way to catch supply-chain risks in Python and JavaScript projects.
Loading
Loading