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
11 changes: 11 additions & 0 deletions CAPABILITIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
- SARIF 2.1.0 export
- Scan history storage
- Machine-readable bridge for editor integration
- Confidence-aware filtering for fixtures, tests, docs, and generated-looking data
- Per-analyzer runtime isolation so one failed analyzer does not abort the whole scan

## VS Code integration

Expand All @@ -19,9 +21,18 @@
- Scan-on-save
- SARIF export command
- Disabled-rule filtering in the client
- Minimum-confidence filtering in the client

## Dependency coverage

- Python requirements files
- Python `pyproject.toml` dependencies
- npm `package-lock.json` dependencies
- Static local advisory database for offline use

## Non-goals in this repository

- No web upload UI
- No CSV analyzer
- No duplicate extension and CLI logic paths
- No live package-advisory network lookup
3 changes: 3 additions & 0 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ The extension is packaged from the repository root with `vsce`. The package incl
- `src/contractguard/` Python engine
- `rules/` bundled rule files
- `media/` extension assets
- `python-requirements.txt` runtime dependency list

## Runtime model

ContractGuard runs its analyzers out of process through `python -m contractguard.bridge`. The extension sets `PYTHONPATH` to its bundled `src/` directory so the engine can run without a separate package install step inside the extension host.

The 2.0 scanner filters low-confidence fixture findings by default and reports analyzer runtime failures as findings instead of aborting an entire workspace scan.

## Publish checklist

1. Build the extension with `tsc`.
Expand Down
2 changes: 2 additions & 0 deletions INSTRUCTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
```powershell
.\.venv\Scripts\python.exe -m contractguard.cli analyze --type all --path . --score
.\.venv\Scripts\python.exe -m contractguard.cli analyze --type secrets --path . --report-sarif contractguard.sarif
.\.venv\Scripts\python.exe -m contractguard.cli analyze --type all --path . --min-confidence low --score
```
Comment on lines 5 to 9

## Bridge

```powershell
$env:PYTHONPATH = (Resolve-Path .\src).Path
.\.venv\Scripts\python.exe -m contractguard.bridge scan --path . --analyzer all --include-sarif
.\.venv\Scripts\python.exe -m contractguard.bridge scan --path . --analyzer all --min-confidence medium
```

## VS Code
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
- Publish inline diagnostics in the editor
- Export SARIF for external security workflows
- Show an overall security score in the status bar
- Filter low-confidence fixture/doc/test findings by default
- Continue scans when one analyzer has a runtime problem

## What it checks

Expand All @@ -21,6 +23,8 @@
- Insecure configuration
- Dockerfile issues
- Dependency vulnerabilities
- Python `requirements.txt` and `pyproject.toml`
- npm `package-lock.json` advisories from the bundled local database

## Commands

Expand All @@ -43,12 +47,15 @@ If the Python runtime dependencies are missing, run:
- `contractguard.pythonPath`
- `contractguard.scanOnSave`
- `contractguard.scanDebounceMs`
- `contractguard.scanOnSaveScope`
- `contractguard.enabledAnalyzers`
- `contractguard.disabledRules`
- `contractguard.minimumConfidence`
- `contractguard.rulesDirectory`
- `contractguard.sqlExplainDatabase`

## Notes

- The extension runs analysis locally.
- The default minimum confidence is `medium`; use `low` for audit mode when you want sample/test fixtures included.
Comment on lines 54 to +60
- SARIF export is available for CI and external security tooling.
6 changes: 3 additions & 3 deletions package-lock.json

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

20 changes: 16 additions & 4 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.3.1",
"version": "2.1.0",
"publisher": "BlackplaneSystems",
"license": "Apache-2.0",
"icon": "media/icon.png",
Expand Down Expand Up @@ -38,7 +38,8 @@
"main": "./dist/extension.js",
"files": [
"dist/**",
"src/contractguard/**",
"src/contractguard/*.py",
"src/contractguard/analyzers/*.py",
"rules/**",
"media/**",
"python-requirements.txt",
Expand Down Expand Up @@ -120,10 +121,21 @@
},
"contractguard.scanOnSaveScope": {
"type": "string",
"default": "workspace",
"default": "currentFile",
"enum": ["workspace", "currentFile"],
"description": "What to scan after saving a file: the whole workspace or just the active file."
},
"contractguard.minimumConfidence": {
"type": "string",
"default": "medium",
"enum": ["low", "medium", "high"],
"description": "Minimum finding confidence shown by the VS Code client and requested from the scanner."
},
"contractguard.includeFixtures": {
"type": "boolean",
"default": false,
"description": "Include findings from fixture-like paths (tests, samples, docs)."
},
"contractguard.enabledAnalyzers": {
"type": "array",
"default": [
Expand Down Expand Up @@ -164,7 +176,7 @@
},
"scripts": {
"build": "tsc -p ./tsconfig.json",
"package": "node -e \"require('fs').mkdirSync('dist-vsix',{recursive:true})\" && vsce package --out dist-vsix/contractguard-1.3.1.vsix",
"package": "node -e \"require('fs').mkdirSync('dist-vsix',{recursive:true})\" && vsce package --out dist-vsix/contractguard-2.1.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.3.1"
version = "2.1.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.3.1"
__version__ = "2.1.0"
28 changes: 26 additions & 2 deletions src/contractguard/analyzers/config_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
import yaml

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

_SKIP_CONFIG_NAMES = {
"package-lock.json",
"package.json",
"pnpm-lock.yaml",
"poetry.lock",
"yarn.lock",
}


def extract_facts(content: str, filename: str = "") -> dict[str, Any]:
Expand Down Expand Up @@ -63,15 +72,24 @@ def extract_facts(content: str, filename: str = "") -> dict[str, Any]:
facts["insecure_secret_key"] = True
facts["dangerous_settings_count"] += 1

if re.search(r'(?:password|passwd|pwd)\s*[=:]\s*[\'"]?(?:admin|password|123456|root|default|test)[\'"]?', content, re.I):
if re.search(r'(?:password|passwd|pwd)\s*[=:]\s*[\'"]?(?:admin|password|123456|root|default|test)[\'"]?', content, re.I) or \
re.search(r'\$\{[^}]*?(?:password|passwd|pwd)[^}]*:-(?:admin|password|postgres|root|default|test)', content, re.I):
facts["default_password"] = True
facts["dangerous_settings_count"] += 1

if re.search(r'(?:host|bind|listen)\s*[=:]\s*[\'"]?0\.0\.0\.0', content, re.I):
facts["exposed_admin_port"] = True
facts["dangerous_settings_count"] += 1

if re.search(r'(?:ssl|tls|https)[_\w]*\s*[=:]\s*(?:false|0|off|no|disabled)', content, re.I):
ssl_disabled_lines = [
line for line in lines
if re.search(r'(?:ssl|tls|https)[_\w]*\s*[=:]\s*(?:false|0|off|no|disabled)', line, re.I)
]
ssl_disabled_lines = [
line for line in ssl_disabled_lines
if not ("smtp" in line.lower() and re.search(r'smtp_use_starttls\s*[=:]\s*(?:true|1|yes|on)', content, re.I))
]
if ssl_disabled_lines:
facts["ssl_disabled"] = True
facts["dangerous_settings_count"] += 1

Expand Down Expand Up @@ -106,12 +124,16 @@ def load_config_files(path: str | Path) -> list[tuple[str, str]]:

if path.is_dir():
for f in sorted(path.rglob("*")):
if should_skip_path(f) or should_skip_large_file(f) or f.name.casefold() in _SKIP_CONFIG_NAMES:
continue
if f.is_file() and (f.suffix.lower() in config_exts or f.stem.lower() in config_names):
try:
files.append((str(f), f.read_text(encoding="utf-8", errors="replace")))
except Exception:
continue
elif path.is_file():
if should_skip_path(path) or should_skip_large_file(path) or path.name.casefold() in _SKIP_CONFIG_NAMES:
return files
try:
files.append((str(path), path.read_text(encoding="utf-8", errors="replace")))
except Exception:
Expand All @@ -131,6 +153,8 @@ def analyze(path: str | Path, rules_dir: str | Path) -> list[Finding]:
for f in findings:
f.location = source
f.context = f"Config file: {Path(source).name}"
if is_fixture_path(source):
f.confidence = "low"
all_findings.extend(findings)

return all_findings
Loading
Loading