Enforce ruff lint / format in CI + pre-commit, clear the baseline, fix CodeQL findings - #346
Draft
lelia wants to merge 12 commits into
Draft
Enforce ruff lint / format in CI + pre-commit, clear the baseline, fix CodeQL findings#346lelia wants to merge 12 commits into
lelia wants to merge 12 commits into
Conversation
Ruff already ran in CI, but only as a job inside the Unit Tests workflow,
so it inherited that workflow's path filter and never saw .hooks/,
benchmarks/, or tests/e2e/. Move it to its own unconditional Lint
workflow, which also keeps it usable as a required status check.
Add ruff to pre-commit so violations surface before CI. The hook runs
ruff out of the project environment rather than the upstream mirror, so
the version stays pinned in one place; Dependabot has no pre-commit
ecosystem and would never update a mirror's rev.
Expand the rule set beyond E/F/I to cover bug classes that matter for a
CLI other people run in their pipelines, and fix every resulting
violation so the baseline is clean rather than suppressed.
Behaviour changes worth calling out:
- Package.created_at used str.strip(" (Coordinated Universal Time)"),
which treats its argument as a set of characters, not a suffix. It ate
a leading "T" from "Tue ..." and a trailing "T" from timestamps that
carried no suffix at all. Now uses removesuffix.
- Every requests call in the plugins and the GitLab client now passes an
explicit timeout. requests blocks forever by default, so a hung
notification could wedge the pipeline the CLI reports into.
- Two asserts became real checks. assert is stripped under python -O, so
neither guard survived an optimised interpreter.
- config.py logs through the socketcli logger instead of the root
logger, so its messages honour the configured level and format.
- A stray debug print in the SBOM artifact loop became a log.debug call;
it was writing to stdout, which carries machine-readable output.
- Closures defined inside loops in alert_selection and messages were
hoisted and now take their inputs explicitly.
Complexity is bounded by C901 (max 12) and PLR0913 (max 8). The 20
functions over the limit today carry an explicit noqa; RUF100 fails the
build once a suppression goes stale, so the list can only shrink.
E501 and W291/W293 are left to ruff format rather than duplicated in the
linter: everything the formatter cannot reflow is a string literal, and
the PR-comment markup depends on trailing double-spaces as Markdown
hard line breaks.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ruff format` normalises string quotes to double, so `__version__` in socketsecurity/__init__.py went from single to double quotes. Five places parsed or rewrote that line assuming single quotes: - version-check.yml stripped only `'`, so it read the version as `"2.7.2"` (quotes included) and failed to parse it. This is what broke on the PR. - build_container.sh and build_container_flexible.sh would have produced a Docker tag containing literal quote characters. - deploy-test-pypi.sh both read the version and rewrote it with a sed that matched single quotes only, so the rewrite would silently no-op. - .hooks/sync_version.py read either quote style but always wrote single quotes, so it and the formatter would have rewritten the same line back and forth on every commit. Readers now strip both quote characters and the hook writes double quotes to match the formatter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lelia
marked this pull request as draft
September 7, 2026 18:27
CodeQL flagged `"github.com" in diff_url` as incomplete URL substring sanitization. Looking at what diff_url actually holds makes the finding more interesting than a sanitization gap. diff_url is always a Socket dashboard link, built in Core as `https://socket.dev/dashboard/org/{org_slug}/diff/...` (or the equivalent sbom URL). Its host is always socket.dev and it carries no SCM information -- which is exactly what the comment three lines below the check already said. The only variable part is the org slug, so the sniff could only ever fire when a Socket org slug happened to contain "github", "gitlab" or "bitbucket". Such an org got a link to a repository host it may not use; everyone else fell through to the Socket file view. The branch was also almost unreachable: CliConfig declares `scm` with a default of "api", so `hasattr(config, "scm")` is true for every real config and the elif never runs. It was observable only for a config object carrying `repo` but no `scm`, since the URL builders all require a truthy config -- with `config=None` the sniffed value was computed and then discarded. Replaced with `getattr(config, "scm", None) or "api"`, which handles a missing config, a config without the attribute, and an empty value. Adds tests for get_manifest_file_url, which had none: GitHub, GitHub Enterprise, GitLab, self-hosted GitLab, Bitbucket, the Socket fallback, build-agent prefix stripping, and multi-manifest paths. The three org-slug cases are regression guards, confirmed to fail against the old implementation. Removing the dead branch drops the function under the complexity limit, so RUF100 required its `# noqa: C901` be removed. The backlog is now 19. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…it-mccabe # Conflicts: # CHANGELOG.md # socketsecurity/__init__.py
CodeQL reported py/clear-text-logging-sensitive-data at output.py:127, the `logger.info(json.dumps(...))` in output_console_json. That line is a false positive -- neither build_json_report nor build_fossa_report_payload copies a credential into the payload; both pick named fields. CodeQL taints anything derived from self.config because CliConfig declares api_token. Chasing it did turn up three real leaks: - socketcli logged `config.to_dict()` at debug level, and to_dict() is asdict(), so `--debug` printed the Socket API token in clear text. In CI that lands in the job log, which is retained, pasted into support tickets and world-readable for public repositories. - The Slack plugin logged the full webhook URL twice, once unconditionally at debug level. A webhook URL is a bearer credential -- anyone holding it can post into the customer's channel. - output.py logged the configured webhook URL in its Slack debug block. The Slack sites matter more than the token one: they run inside the StreamingLogs context, whose upload handler has no level filter and whose loggers are forced to DEBUG, so those records are shipped to Socket. The config line runs before streaming attaches, so it stayed local. Adds socketsecurity/redaction.py: redact_mapping masks values whose field name looks credential-bearing, matching on the name so a field added later is covered without anyone remembering. redact_url keeps a webhook's scheme and host -- which is what the debug line was for -- and drops the secret path and any userinfo. Unset values are left alone, since "no token configured" is useful and is not a secret. CliConfig.to_dict() still returns real values; to_redacted_dict() is the logging view. A serialiser that silently dropped the token would be its own bug. The six actions/cache-poisoning alerts are inert: nothing in this repository uses actions/cache or a `cache:` input, so there is no cache to poison. They are not fixed, they are documented -- pr-preview.yml's build job now says why caching must never be added there, since under workflow_dispatch it executes untrusted PR code in the default branch's cache scope. Recommend dismissing them rather than leaving them open. TRY400/TRY401 move from "not selected" into `ignore` so the decision survives a future family-wide selection. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ch pins Brings in #348 and #349, which move httpx2 and httpcore2 to 2.12.0 and close five Dependabot advisories (GHSA-8xx6-hgc6-gc2m and GHSA-7mj9-2mp8-4m2p high, three medium). No CLI code change is needed for these, and the reason is worth recording: httpx2 is not a runtime dependency. It arrives transitively through `hatch` in the dev extra, so it is absent from the published wheel's Requires-Dist and never reaches anyone installing socketsecurity from PyPI. Nothing under socketsecurity/ imports httpx or httpcore, and the only hatch commands this repository runs are `hatch version` and `hatch build`, neither of which performs HTTP. The advisories describe build-tooling exposure on developer machines and CI runners, not shipped surface. Verified against the release path rather than assumed: hatch version, hatch build, twine check, and a no-deps wheel install with a bytecode compile all pass on 2.12.0, as do the 568 unit and core tests. Separately, the same investigation turned up three different pins for the same build tooling: build-system.requires wanted hatchling 1.32.0, the dev extra pulled 1.28.0 via hatch 1.18.0, and .github/actions/setup-hatch installed hatchling 1.27.0 with hatch 1.14.0 -- so CI ran an older hatch than local development. The artifact was never affected, because `hatch build` resolves the backend in an isolated environment from build-system.requires and the wheel records `Generator: hatchling 1.32.0`. The composite action now matches the other two, verified by building in a clean virtualenv with the new pins. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#331 added this workflow to its own paths filter so an edit to the check gets exercised by the PR making it. That had the side effect of demanding a version bump from CI-only PRs, and now that 2.8.0 is published it fails outright: a PR touching only version-check.yml sits at the published version with nothing to bump, and is told to bump it. The bump is now enforced only when the PR touches socketsecurity/, pyproject.toml or uv.lock. The comparison still runs and reports its verdict either way, so the check stays exercised without dictating a release. Matches the gating already on socket-sdk-python. This branch predates #331, so the file is taken from main -- picking up the PyPI-only floor and the pyproject/__init__ consistency assertion -- with this branch's own tweak reapplied on top: the version literal is stripped of both quote styles, since ruff format rewrites __init__.py to double quotes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
main shipped 2.8.0 (#325 monorepo scan diagnostics and API exit codes, #331 Slack severity normalization and the socketdev 3.6.0 bump) while this branch was open, so the 2.7.3 it targeted is now behind the published release. Conflict resolution, in every case keeping main's shipped behaviour and this branch's formatting and fixes: - slack.py: kept the "middle" -> "medium" normalization from #331, restyled to double quotes. Losing it would have silently reintroduced the wrong Slack severity counts. - socketcli.py: kept #325's _log_scan_mode_fallback diagnostics and the force_api_mode branch; this branch's side was the older logic reformatted. - core/__init__.py: kept #325's _log_scan_configuration helper and its call sites, and the switch from sys.exit to raise so API failures reach the configured infrastructure exit code. Kept this branch's contextlib.suppress cleanup and modern annotations, and modernized the helper's own signature since this branch drops the typing.List/Optional imports. - version-check.yml: already assembled from main plus the enforcement gating and this branch's both-quote version strip. - CHANGELOG: this branch's section is now 2.8.1, above the shipped 2.8.0. uv.lock regenerated from the merged pyproject with the pinned uv 0.12.8. ruff check and ruff format are both clean; 584 passed, 2 skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lelia
marked this pull request as ready for review
September 11, 2026 15:42
lelia
marked this pull request as draft
September 11, 2026 15:54
The changelog entry and the module and test docstrings spelled out the failure mode in enough detail to serve as a lookup guide, on a public repository, before the fix has shipped. Reworded to describe the behaviour -- configuration values and integration URLs are redacted in log output -- without the specifics. No functional change; redaction behaviour and tests are untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ruff already ran in CI, but as a job inside the Unit Tests workflow, so it inherited that workflow's
paths:filter and never saw.hooks/,benchmarks/ortests/e2e/. This moves it to its own unconditionalLintworkflow, adds a pre-commit hook, bounds function complexity, and clears every resulting violation so the baseline is clean rather than suppressed.Enforcement
Lintworkflow, unconditional on every PR. Not path-filtered: a filtered workflow reports "not run" rather than "passed", which blocks any PR that doesn't touch the filtered paths once it is a required check.astral-sh/ruff-pre-commitmirror. Dependabot has no pre-commit ecosystem, so a mirror would drift from the pinnedruff==0.16.5and fail in the worst way — clean locally, red on the PR.make lintmirrors CI exactly.C90112 andPLR09138. The 18 functions currently over the limit carry an explicit# noqarather than a blanket per-file ignore, andRUF100fails the build once a suppression goes stale, so the backlog can only shrink.Behavior changes
These are fixes, not style:
Package.created_attruncated timestamps.str.strip(" (Coordinated Universal Time)")treats its argument as a set of characters rather than a suffix, so a timestamp beginningTuelost its leadingT. Now usesremovesuffix.requestsblocks forever by default. All now use an explicit 30s timeout.--scmalone.get_manifest_file_urlhad no test coverage; it does now, including regression guards for the org-slug cases.python -O, which stripsassert. One is now an explicit check that raises; the other was redundant and removed.config.pylogged through the root logger, so its warnings ignored the CLI's configured level and format.Log redaction
Adds
socketsecurity/redaction.pyand routes debug logging through it, so configuration values and integration URLs are masked in log output rather than written verbatim.Masking is by field-name pattern, so fields added later are covered by default — a test asserts that by iterating
CliConfig's fields.to_dict()is unchanged and still returns real values;to_redacted_dict()is the logging view, because a serialiser that silently dropped values would be its own bug.Prompted by a CodeQL alert on
output.py, which is a false positive on the reported call (it selects named fields).Open decision: 6 ×
actions/cache-poisoningNot fixed, because there is nothing to fix — no
actions/cachestep and nocache:input exists anywhere in this repository, so there is no cache to poison. The hazard is real but latent, and I've documented the invariant that keeps it safe inpr-preview.ymlat the point someone would violate it. Dismissing these six is a security-posture call, so I left it to you; I'd dismiss as "won't fix / no cache in use".Rule decisions
TRY400/TRY401,E501,W291/W293,N815/N818,SIM108,PERF401,S603andS607sit inignorewith a one-line reason each inpyproject.tomlrather than being merely unselected, so the decision survives a future family-wide selection instead of silently reverting.Two are worth calling out because they are not preference:
W291/W293— the trailing double-spaces in the PR-comment markup are Markdown hard line breaks. Stripping them collapses the❗️ Cautionbanner into the body text. Whitespace inside a string is content.N815/N818— those names are bound directly to Socket API JSON keys on both read and write, and the exception names are part of the public import surface. Renaming either is a breaking change to a documented contract, not a rename. Happy to do it as its own PR with deprecation aliases if you want the convention.Formatting, and the move to 2.8.1
ruff formatis enforced from this PR onward. The formatting pass shares a commit with the lint fixes because they touch the same lines and cannot be cleanly separated after the fact, so.git-blame-ignore-revsis added with instructions but no SHA — blame-ignoring this commit would also hide the real fixes above.mainshipped 2.8.0 (#325, #331) while this branch was open, so the release is now 2.8.1. Conflicts were resolved keeping main's shipped behaviour together with this branch's formatting and fixes — #331's Slackmiddle→mediumnormalization, #325's scan diagnostics, and thesys.exit→raisethat routes API failures to the configured infrastructure exit code.ruff checkandruff format --checkare both clean. 584 passed, 2 skipped.Ref: CE-451
Note
Medium Risk
Touches CI required checks, broad lint/format churn, and several CLI/runtime paths (HTTP notifications, config exit/logging, package timestamps) that affect customer pipelines.
Overview
Release 2.7.2 ships alongside a full Ruff enforcement story: linting moves out of the path-filtered Unit Tests workflow into a dedicated, unconditional
Lintworkflow (ruff check+ruff format --check), with matching pre-commit hooks andmake lint/make hookstargets.pyproject.tomlexpands the rule set (bugbear, bandit, complexityC901/PLR0913,RUF100, etc.), documents intentional ignores, and clears the baseline across the repo.Runtime fixes (not just style):
Package.created_atnow usesremovesuffixinstead ofstripso timestamps are not mangled; outbound Slack/Teams/Jira/webhook/GitLab calls get a 30srequeststimeout so CI cannot hang forever;config.pylogs via thesocketclilogger andsys.exit; duplicate SBOM packages log at debug instead of stdout; and guards that relied onassertare replaced or removed sopython -Ostill behaves correctly.Docs (
CONTRIBUTING.md,CHANGELOG.md) and.git-blame-ignore-revs(placeholder for future format-only SHAs) support the new workflow.Reviewed by Cursor Bugbot for commit ea36905. Configure here.