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 package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "contract-guard",
"displayName": "contract-guard",
"description": "A VS Code extension that finds security issues in code, configs, queries, Dockerfiles, and secrets.",
"version": "1.2.1",
"version": "1.3.0",
"publisher": "BlackplaneSystems",
"license": "Apache-2.0",
"icon": "media/icon.png",
Expand Down Expand Up @@ -164,7 +164,7 @@
},
"scripts": {
"build": "tsc -p ./tsconfig.json",
"package": "node -e \"require('fs').mkdirSync('dist-vsix',{recursive:true})\" && vsce package --out dist-vsix/contractguard-1.2.1.vsix",
"package": "node -e \"require('fs').mkdirSync('dist-vsix',{recursive:true})\" && vsce package --out dist-vsix/contractguard-1.3.0.vsix",
"prepackage": "npm run build"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "contractguard"
version = "1.2.0"
version = "1.3.0"
description = "ContractGuard security analysis core for VS Code and CI workflows."
readme = "README.md"
license = {text = "Apache-2.0"}
Expand Down
2 changes: 1 addition & 1 deletion src/contractguard/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""ContractGuard core package."""

__version__ = "1.2.0"
__version__ = "1.3.0"
25 changes: 25 additions & 0 deletions src/contractguard/analyzers/file_filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from __future__ import annotations

from pathlib import Path

_SKIP_DIRS = {
".git",
".hg",
".svn",
".tox",
".venv",
"venv",
"node_modules",
"dist",
"dist-vsix",
"build",
"out",
".pytest_cache",
"__pycache__",
".mypy_cache",
".ruff_cache",
}


def should_skip_path(path: Path) -> bool:
return any(part in _SKIP_DIRS for part in path.parts)
Comment on lines +22 to +25
18 changes: 16 additions & 2 deletions src/contractguard/analyzers/pii_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@

from __future__ import annotations

import ipaddress
import json
import re
from pathlib import Path
from typing import Any

from contractguard.engine import Finding, Severity, load_rules_for_analyzer, run_rules
from contractguard.analyzers.file_filters import should_skip_path

_PII_PATTERNS: list[tuple[str, re.Pattern, str]] = [
("ssn", re.compile(r"\b\d{3}-\d{2}-\d{4}\b"), "Social Security Number"),
Expand Down Expand Up @@ -65,8 +67,10 @@ def extract_facts(content: str, filename: str = "") -> dict[str, Any]:
for line_num, line in enumerate(content.splitlines(), 1):
for pii_name, regex, desc in _PII_PATTERNS:
for match in regex.finditer(line):
facts["pii_count"] += 1
matched = match.group(0)
if pii_name == "ip_address" and _is_non_personal_ip(matched):
continue
facts["pii_count"] += 1
Comment on lines 70 to +73
Comment on lines 70 to +73
if len(matched) > 8:
preview = matched[:3] + "***" + matched[-2:]
else:
Expand Down Expand Up @@ -106,19 +110,29 @@ def load_files(path: str | Path) -> list[tuple[str, str]]:

if path.is_dir():
for f in sorted(path.rglob("*")):
if f.is_file() and f.suffix.lower() not in _skip:
if f.is_file() and f.suffix.lower() not in _skip and not should_skip_path(f):
try:
Comment on lines 111 to 114
Comment on lines 111 to 114
files.append((str(f), f.read_text(encoding="utf-8", errors="replace")))
except Exception:
continue
elif path.is_file():
if should_skip_path(path):
return files
try:
files.append((str(path), path.read_text(encoding="utf-8", errors="replace")))
except Exception:
pass
return files


def _is_non_personal_ip(value: str) -> bool:
try:
ip_value = ipaddress.ip_address(value)
except ValueError:
return False
return ip_value.is_loopback or ip_value.is_unspecified or ip_value.is_reserved


def analyze(path: str | Path, rules_dir: str | Path) -> list[Finding]:
"""Run PII detection on files at *path*."""
files = load_files(path)
Expand Down
5 changes: 4 additions & 1 deletion src/contractguard/analyzers/secrets_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from typing import Any

from contractguard.engine import Finding, Severity, load_rules_for_analyzer, run_rules
from contractguard.analyzers.file_filters import should_skip_path

_SECRET_PATTERNS: list[tuple[str, re.Pattern, str]] = [
("aws_access_key", re.compile(r"(?:^|[^A-Za-z0-9/+=])(?:AKIA[0-9A-Z]{16})(?:[^A-Za-z0-9/+=]|$)"), "block"),
Expand Down Expand Up @@ -115,13 +116,15 @@ def load_files(path: str | Path) -> list[tuple[str, str]]:

if path.is_dir():
for f in sorted(path.rglob("*")):
if f.is_file() and f.suffix.lower() not in _SKIP_EXTENSIONS:
if f.is_file() and f.suffix.lower() not in _SKIP_EXTENSIONS and not should_skip_path(f):
try:
Comment on lines 117 to 120
Comment on lines 117 to 120
content = f.read_text(encoding="utf-8", errors="replace")
files.append((str(f), content))
except Exception:
continue
elif path.is_file():
if should_skip_path(path):
return files
try:
content = path.read_text(encoding="utf-8", errors="replace")
files.append((str(path), content))
Expand Down
Loading