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
42 changes: 42 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Lint

env:
PYTHON_VERSION: "3.12"

# Report on every PR, including changes outside the Python test paths.
# Required checks need a result even when a PR changes no Python files.
on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: lint-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
ruff:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
- name: 🐍 setup python
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: 🛠️ install deps
run: |
python -m pip install --upgrade pip
pip install uv
uv sync --locked --extra dev
- name: 🧹 ruff check
run: uv run --locked --extra dev ruff check
- name: 🎨 ruff format
run: uv run --locked --extra dev ruff format --check
18 changes: 18 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
repos:
- repo: local
hooks:

# Use the project's locked Ruff version, shared with CI and make lint.
- id: ruff-check
name: ruff check
entry: uv run --locked --extra dev ruff check --force-exclude --fix
language: system
types_or: [python, pyi]
require_serial: true

- id: ruff-format
name: ruff format
entry: uv run --locked --extra dev ruff format --force-exclude
language: system
types_or: [python, pyi]
require_serial: true
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
[Network access for Tier 1](docs/parameters.md#network-access-for-tier-1).

### Changed
- Ruff replaces Black and Flake8 for development. Linting and formatting are
enforced on every pull request and push to `main`, with matching pre-commit
hooks and `make lint` / `make format` commands using the locked tool version.
- Functions are limited to complexity 12 and eight arguments. Existing exceptions
are marked individually and obsolete suppressions fail lint checks. Vulnerable
scanner fixtures and the generated CWE catalog are excluded from lint cleanup.
- Release-validation scripts and tests also support Python 3.10 through the
existing TOML compatibility dependency.
- Notification formatters use timezone-aware UTC timestamps while preserving
their trailing-`Z` JSON representation.
- The images set `NODE_USE_ENV_PROXY=1`, so Socket Tier 1 honors `HTTPS_PROXY`
and `NO_PROXY`. The Socket npm CLI does not apply `HTTPS_PROXY` to its own
requests, and Tier 1 behind a proxy failed with `getaddrinfo EAI_AGAIN
Expand Down
20 changes: 20 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.PHONY: lint lint-fix format format-check hooks test

lint:
uv run --locked --extra dev ruff check
uv run --locked --extra dev ruff format --check

lint-fix:
uv run --locked --extra dev ruff check --fix

format:
uv run --locked --extra dev ruff format

format-check:
uv run --locked --extra dev ruff format --check

hooks:
uv run --locked --extra dev pre-commit install

test:
uv run --locked --extra dev pytest tests/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ socket-basics --workspace app_tests/pygoat \
import pytest
from socket_basics.module import new_function


def test_new_feature():
result = new_function(input_data)
assert result == expected_output
Expand Down
21 changes: 13 additions & 8 deletions docs/github-pr-comment-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -556,32 +556,37 @@ Socket Basics is designed to support **any security scanner**. To add enhancemen
```python
from socket_basics.core.notification import github_pr_helpers as helpers


def format_notifications(data, config=None):
# 1. Get feature flags (handles all config sources automatically)
flags = helpers.get_feature_flags(config)

# 2. Use shared utilities to build your content
file_link = helpers.format_file_location_link(
filepath, line_start=line_num, repository=flags['repository'],
commit_hash=flags['commit_hash'], enable_links=flags['enable_links']
filepath,
line_start=line_num,
repository=flags["repository"],
commit_hash=flags["commit_hash"],
enable_links=flags["enable_links"],
)

code_block = helpers.format_code_block(
code_snippet, filepath=filepath,
enable_fencing=flags['enable_code_fencing']
code_snippet, filepath=filepath, enable_fencing=flags["enable_code_fencing"]
)

collapsible = helpers.create_collapsible_section(
title, content, severity_counts,
auto_expand=(critical and not flags['collapse_non_critical'])
title,
content,
severity_counts,
auto_expand=(critical and not flags["collapse_non_critical"]),
)

# 3. Wrap in the standard PR comment section (logo, scan link, markers)
wrapped = helpers.wrap_pr_comment_section(
'my-scanner', title, body_content, flags['full_scan_url']
"my-scanner", title, body_content, flags["full_scan_url"]
)

return [{'title': title, 'content': wrapped}]
return [{"title": title, "content": wrapped}]
```

**That's it!** Your tool instantly gets:
Expand Down
180 changes: 174 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ dependencies = [
dev = [
"pytest>=9.1.1",
"pytest-cov>=7.1.0",
"black>=26.5.1",
"flake8>=5.0.0",
"ruff==0.17.4",
"pre-commit==4.6.2",
]

[project.urls]
Expand Down Expand Up @@ -69,12 +69,180 @@ include = [
[dependency-groups]
dev = []

[tool.black]
line-length = 100
target-version = ["py39"]

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]

[tool.ruff]
# The formatter owns physical layout. E501 and W291/W293 are unselected:
# whitespace inside help text, log messages, and Markdown strings is content.
target-version = "py310"
line-length = 100
# Scanner targets intentionally contain vulnerable code; the CWE table is generated.
extend-exclude = ["app_tests", "tests/fixtures", "socket_basics/core/connector/opengrep/cwe_catalog.py"]
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".ipynb_checkpoints",
".mypy_cache",
".nox",
".pants.d",
".pyenv",
".pytest_cache",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
".vscode",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"site-packages",
"venv",
]

[tool.ruff.lint]
select = [
# --- Baseline correctness -------------------------------------------
"E4", # pycodestyle: import formatting
"E7", # pycodestyle: statement-level errors
"E9", # pycodestyle: syntax/IO errors
"W605", # invalid escape sequence in a string literal
"F", # Pyflakes: undefined names, unused imports, star imports
"I", # isort: deterministic import order
"PLE", # Pylint errors: unambiguous bugs, never style

# --- Complexity guardrails ------------------------------------------
"C901", # McCabe cyclomatic complexity
"PLR0913", # too many arguments in a function definition

# --- Bug classes that matter for a CLI shipped to other people -------
"ASYNC", # blocking calls inside async functions
"B", # bugbear: loop-variable capture, missing `raise ... from`
"C4", # comprehension misuse
"DTZ", # naive datetimes: scan timestamps must be tz-aware
"EXE", # shebang / executable-bit mismatches in a packaged wheel
"FLY", # static str.join() that should be an f-string
"FURB", # modern-Python refactors
"ICN", # conventional import aliases
"INT", # gettext f-string traps
"LOG", # logging misuse
"PERF", # avoidable per-iteration work
"PIE", # dead code and redundant constructs
"PLW", # Pylint warnings: unchecked subprocess, loop-var rebinding
"RET", # unreachable or redundant return flow
"RSE", # raise Exception() -- not raise Exception
"RUF", # Ruff-native checks (incl. RUF100, see below)
"S", # bandit: request timeouts, unsafe subprocess, weak randomness
"SIM", # simplifiable branches and context managers
"SLOT", # __slots__ on subclasses of str/tuple/namedtuple
"T10", # leftover breakpoint()/pdb.set_trace() reaching a release
"T20", # stray print(): stdout is a contract for a CLI, not a debug log
"TID", # relative imports
"UP", # pyupgrade: the floor is Python 3.11
"YTT", # incorrect sys.version comparisons

# Individually selected because their siblings are style opinions
# (PLR2004 magic values, PLC0415 deliberate lazy imports, TRY003
# message length) that would cost more than they catch here.
"PLR1714", # repeated equality comparison -> `in`
"PLR1722", # `exit()` -> `sys.exit()`: `exit` only exists when `site` ran
"PLR1730", # if-statement that should be min()/max()
"PLR5501", # else-if that should be elif
"TRY201", # `raise` instead of re-raising the bound name

# --- Keeping the suppressions honest --------------------------------
"PGH", # no blanket `# noqa` / `# type: ignore` -- codes required
"RUF100", # unused `# noqa` -- makes the grandfather list self-cleaning
]

ignore = [
# Keep expected failures readable without requiring tracebacks.
"TRY400",
"TRY401",
"SIM108", # retain comments on if/else branches
"PERF401", # retain readable loops for multi-line dictionary construction
"S603", # external tools use argument lists; shell=True remains checked
"S607", # external tools intentionally resolve through PATH
"TID252", # package-local relative imports are intentional
"S110", # optional finding metadata may be malformed; skip without logging payloads
"S112", # skip unsupported or malformed findings during best-effort parsing
"PERF203", # recover from individual finding parse errors inside scanning loops
]

# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []

# The information glyph is deliberate UI in the PR-comment markup.
allowed-confusables = ["ℹ"]

# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"

[tool.ruff.lint.mccabe]
# Existing over-limit functions have explicit suppressions. Split new functions
# that exceed the limit; RUF100 rejects suppressions that become unnecessary.
max-complexity = 12

[tool.ruff.lint.pylint]
# Allow ordinary CLI constructors while bounding argument-heavy interfaces.
max-args = 8

[tool.ruff.lint.per-file-ignores]
# Tests use assertions, placeholder credentials, and fixed fixture paths.
"tests/**" = [
"S101", # asserts are the point
"S105", # throwaway credentials in fixtures
"S106",
"S108", # fixed temp paths in fixtures
"T201", # diagnostic prints; pytest captures stdout anyway
"RUF012", # ClassVar annotations on test-local constants are noise
"S311", # deterministic fuzz seeds are not credentials
]

# Developer tooling, not shipped in the wheel. stdout is their entire interface.
".hooks/**" = ["T201", "S310"] # fetches a hardcoded PyPI JSON endpoint
"scripts/**" = ["T201"]
"socket_basics/core/notification/console.py" = ["T201"]
"socket_basics/core/notification/json_notifier.py" = ["T201"]

[tool.ruff.lint.isort]
known-first-party = ["socket_basics"]

[tool.ruff.format]
# Like Black, use double quotes for strings.
quote-style = "double"

# Like Black, indent with spaces, rather than tabs.
indent-style = "space"

# Like Black, respect magic trailing commas.
skip-magic-trailing-comma = false

# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"

# Enable auto-formatting of code examples in docstrings. Markdown,
# reStructuredText code/literal blocks and doctests are all supported.
#
# This is currently disabled by default, but it is planned for this
# to be opt-out in the future.
docstring-code-format = false

# Set the line length limit used when formatting code snippets in
# docstrings.
#
# This only has an effect when the `docstring-code-format` setting is
# enabled.
docstring-code-line-length = "dynamic"
Loading
Loading