Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ markdown_security_temp.md
.DS_Store
*.pyc
test.py

# Note: requirements.txt is no longer needed - using pyproject.toml + uv.lock instead
# Version files are auto-managed by .hooks/version-check.py
*.cpython-312.pyc`
file_generator.py
.env
*.md
test_results
test_results
local_tests
55 changes: 55 additions & 0 deletions .hooks/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""
Setup script to install pre-commit hooks for version management.
"""
import pathlib
import subprocess
import sys

def setup_pre_commit_hook():
"""Set up the pre-commit hook for version checking."""
git_hooks_dir = pathlib.Path(".git/hooks")
pre_commit_hook = git_hooks_dir / "pre-commit"

if not git_hooks_dir.exists():
print("❌ .git/hooks directory not found. Are you in a git repository?")
sys.exit(1)

hook_content = '''#!/bin/bash
# Version check pre-commit hook
python3 .hooks/version-check.py
'''

# Create or update the pre-commit hook
if pre_commit_hook.exists():
print("⚠️ Pre-commit hook already exists.")
response = input("Do you want to overwrite it? (y/N): ")
if response.lower() != 'y':
print("❌ Aborted.")
sys.exit(1)

pre_commit_hook.write_text(hook_content)
pre_commit_hook.chmod(0o755)

print("✅ Pre-commit hook installed successfully!")
print("Now version changes will be automatically checked on each commit.")
print("")
print("Usage:")
print(" Normal commit: Will auto-bump patch version if unchanged")
print(" Dev mode: python3 .hooks/version-check.py --dev")

def main():
if "--install-hook" in sys.argv:
setup_pre_commit_hook()
else:
print("Version management setup script")
print("")
print("Options:")
print(" --install-hook Install pre-commit hook for version checking")
print("")
print("Manual usage:")
print(" python3 .hooks/version-check.py # Check and auto-bump if needed")
print(" python3 .hooks/version-check.py --dev # Use dev versioning")

if __name__ == "__main__":
main()
128 changes: 128 additions & 0 deletions .hooks/version-check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
import subprocess
import pathlib
import re
import sys
import urllib.request
import json

VERSION_FILE = pathlib.Path("src/version.py")
PYPROJECT_FILE = pathlib.Path("pyproject.toml")

VERSION_PATTERN = re.compile(r"__version__\s*=\s*['\"]([^'\"]+)['\"]")
PYPROJECT_PATTERN = re.compile(r'^version\s*=\s*"([^"]+)"$', re.MULTILINE)
# Update this URL to match your actual PyPI package if you publish it
PYPI_API = "https://pypi.org/pypi/security-wrapper/json"

def read_version_from_version_file(path: pathlib.Path) -> str:
if not path.exists():
print(f"❌ Version file {path} does not exist")
sys.exit(1)
content = path.read_text()
match = VERSION_PATTERN.search(content)
if not match:
print(f"❌ Could not find __version__ in {path}")
sys.exit(1)
return match.group(1)

def read_version_from_pyproject(path: pathlib.Path) -> str:
if not path.exists():
print(f"❌ pyproject.toml file {path} does not exist")
sys.exit(1)
content = path.read_text()
match = PYPROJECT_PATTERN.search(content)
if not match:
print(f"❌ Could not find version in {path}")
sys.exit(1)
return match.group(1)

def read_version_from_git(path: str) -> str:
try:
output = subprocess.check_output(["git", "show", f"HEAD:{path}"], text=True)
match = VERSION_PATTERN.search(output)
if not match:
return None
return match.group(1)
except subprocess.CalledProcessError:
return None

def bump_patch_version(version: str) -> str:
if ".dev" in version:
version = version.split(".dev")[0]
parts = version.split(".")
parts[-1] = str(int(parts[-1]) + 1)
return ".".join(parts)

def fetch_existing_versions() -> set:
try:
with urllib.request.urlopen(PYPI_API) as response:
data = json.load(response)
return set(data.get("releases", {}).keys())
except Exception as e:
print(f"⚠️ Warning: Failed to fetch existing versions from PyPI: {e}")
return set()

def find_next_available_dev_version(base_version: str) -> str:
existing_versions = fetch_existing_versions()
for i in range(1, 100):
candidate = f"{base_version}.dev{i}"
if candidate not in existing_versions:
return candidate
print("❌ Could not find available .devN slot after 100 attempts.")
sys.exit(1)

def inject_version(version: str):
print(f"🔁 Updating version to: {version}")

# Update version.py
VERSION_FILE.write_text(f'__version__ = "{version}"\n')

# Update pyproject.toml
pyproject = PYPROJECT_FILE.read_text()
if PYPROJECT_PATTERN.search(pyproject):
new_pyproject = PYPROJECT_PATTERN.sub(f'version = "{version}"', pyproject)
PYPROJECT_FILE.write_text(new_pyproject)
print(f"✅ Updated {PYPROJECT_FILE}")
else:
print(f"⚠️ Could not find version field in {PYPROJECT_FILE}")

def check_version_sync():
"""Ensure version.py and pyproject.toml are in sync"""
version_py = read_version_from_version_file(VERSION_FILE)
version_toml = read_version_from_pyproject(PYPROJECT_FILE)

if version_py != version_toml:
print(f"❌ Version mismatch: {VERSION_FILE} has {version_py}, {PYPROJECT_FILE} has {version_toml}")
print("🔁 Syncing versions...")
inject_version(version_toml) # Use pyproject.toml as source of truth
return version_toml

return version_py

def main():
dev_mode = "--dev" in sys.argv

# Ensure versions are synced
current_version = check_version_sync()
previous_version = read_version_from_git("src/version.py")

print(f"Current: {current_version}, Previous: {previous_version}")

if current_version == previous_version:
if dev_mode:
base_version = current_version.split(".dev")[0] if ".dev" in current_version else current_version
new_version = find_next_available_dev_version(base_version)
inject_version(new_version)
print("⚠️ Version was unchanged — auto-bumped. Please git add + commit again.")
sys.exit(0)
else:
new_version = bump_patch_version(current_version)
inject_version(new_version)
print("⚠️ Version was unchanged — auto-bumped. Please git add + commit again.")
sys.exit(1)
else:
print("✅ Version already bumped — proceeding.")
sys.exit(0)

if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.9
29 changes: 19 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# Use the official Python image as a base
FROM python:3.9
FROM python:3.12
COPY src/socket_external_tools_runner.py /
COPY src/core /core
COPY entrypoint.sh /
ENV PATH=$PATH:/usr/local/go/bin

# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

# Setup Golang
RUN curl -sfL https://go.dev/dl/go1.23.2.linux-amd64.tar.gz > go1.23.2.linux-amd64.tar.gz
RUN rm -rf /usr/local/go && tar -C /usr/local -xzf go1.23.2.linux-amd64.tar.gz
Expand All @@ -17,26 +20,32 @@ RUN curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh
# Install Trivy
RUN curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin v0.18.3

#Install Trufflehog
# Install trufflehog
# Install Trufflehog
RUN curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh | sh -s -- -b /usr/local/bin

# Install Bandit
RUN pip install bandit
# Install Bandit using uv as a tool
RUN uv tool install bandit
ENV PATH="/root/.local/bin:$PATH"

# Install eslint
RUN apt-get update && apt-get install -y curl && \
curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \
curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
apt-get install -y nodejs && \
npm install -g eslint eslint-plugin-security @typescript-eslint/parser @typescript-eslint/eslint-plugin
npm install -g eslint eslint-plugin-security @typescript-eslint/parser @typescript-eslint/eslint-plugin socket

# Install Socket CLI tools
RUN npm install -g socket
RUN uv tool install socketsecurity

# Copy the entrypoint script and make it executable
RUN chmod +x /entrypoint.sh


COPY requirements.txt /scripts/requirements.txt
# Install Python dependencies from requirements.txt
RUN pip install -r /scripts/requirements.txt
COPY pyproject.toml uv.lock /scripts/
# Install Python dependencies using uv
WORKDIR /scripts
RUN uv sync --frozen
ENV PATH="/scripts/.venv/bin:$PATH"

# Define entrypoint
ENTRYPOINT ["/entrypoint.sh"]
74 changes: 72 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
# Security Tools Scanning

The purpose of this action is to run various security tools, process their output, and then comment the results on a PR. It is expected to only run this on PRs
The purpose of this action is to run various security tools, process their output, and then comment the results on a PR. It is expected to only run this on PRs.

## New: Consolidated Socket Facts Format

Starting with version 2.0.0, all security tool results are consolidated into a unified `.socket.facts.json` format. This provides:

- **Unified Processing**: All security findings in a single, consistent format
- **Enhanced Integration**: Easier integration with Socket's dependency analysis
- **Custom Components**: Support for organization-specific component types
- **Backward Compatibility**: Existing workflows continue to work unchanged

The consolidated format extends Socket's dependency data with external security findings from SAST scanners, secret scanners, and container scanners.

## Supported Security Tools

- **Bandit** - Python SAST analysis
- **Gosec** - Golang SAST analysis
- **ESLint** - JavaScript/TypeScript SAST analysis
- **Trivy** - Container image and Dockerfile vulnerability scanning
- **Trufflehog** - Secret scanning
- **Socket** - Dependency reachability analysis and supply chain risk scanning
- Uses `socket scan reach` to identify which vulnerable code paths are actually reachable in your application
- Includes stack trace information for reachable vulnerabilities to help with remediation
- Note: This is different from Socket SCA scanning which analyzes all dependencies

## Example Usage

Expand Down Expand Up @@ -33,11 +56,16 @@ jobs:
dockerfile_enabled: true
image_enabled: true
secret_scanning_enabled: true
socket_scanning_enabled: true

# Trivy Configuration
docker_images: "image:latest,test/image2:latest"
dockerfiles: "Dockerfile,relative/path/Dockerfile"

# Socket Configuration
socket_org: "your-socket-org" # Required when socket_scanning_enabled is true
socket_api_key: ${{ secrets.SOCKET_API_KEY }}

# Exclusion settings
trufflehog_exclude_dir: "node_modules/*,vendor,.git/*,.idea"
trufflehog_show_unverified: False
Expand Down Expand Up @@ -77,6 +105,26 @@ jobs:

You can run the security-wrapper locally using Docker. This is useful for testing changes or scanning code outside of GitHub Actions.

### Prerequisites

This project uses [uv](https://docs.astral.sh/uv/) for Python package management. Install it with:

```sh
curl -LsSf https://astral.sh/uv/install.sh | sh
```

### Local Python Development

For local Python development without Docker:

```sh
# Install dependencies
uv sync

# Run the security wrapper directly
uv run python src/socket_external_tools_runner.py
```

### Build the Docker Image

```sh
Expand Down Expand Up @@ -106,13 +154,35 @@ docker run --rm --name security-wrapper \
-e "INPUT_PYTHON_SAST_ENABLED=true" \
-e "PYTHONUNBUFFERED=1" \
-e "INPUT_SECRET_SCANNING_ENABLED=true" \
-e "INPUT_SOCKET_SCANNING_ENABLED=true" \
-e "INPUT_SOCKET_ORG=your-socket-org" \ # Required when socket_scanning_enabled is true
-e "INPUT_SOCKET_API_KEY=your-socket-api-key" \
-e "SOCKET_SCM_DISABLED=true" \
-e "INPUT_SOCKET_CONSOLE_MODE=json" \
socketdev/security-wrapper
```

## Version Management

This project uses automated version management with uv and pyproject.toml:

- **Version Source**: `pyproject.toml` is the source of truth for version numbers
- **Runtime Version**: `src/version.py` is auto-synced and imported by the application
- **Pre-commit Hooks**: Automatic version checking and bumping via `.hooks/version-check.py`

### Setup Version Management

```sh
# Install the pre-commit hook
python3 .hooks/setup.py --install-hook

# Manual version checking
python3 .hooks/version-check.py # Auto-bump patch version if unchanged
python3 .hooks/version-check.py --dev # Create dev versions (1.0.18.dev1, etc.)
```

**Notes:**
- You can adjust the environment variables to enable/disable specific scanners.
- For image scanning, Docker-in-Docker must be enabled, and you may need to add a `docker pull` step before running.
- Results will be printed to the console or output as JSON, depending on `INPUT_SOCKET_CONSOLE_MODE`.
- You can also run the wrapper directly with Bash and Python for rapid local development (see `entrypoint.sh`).
- You can also run the wrapper directly with Bash and Python/uv for rapid local development (see `entrypoint.sh`).
Loading