Skip to content

Tags: SocketDev/socket-basics

Tags

v3.4.0

Toggle v3.4.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
chore(release): 3.4.0 (#120)

* chore(release): 3.4.0

Stamps [Unreleased] as [3.4.0], bumps the version files and uv.lock, and
synchronizes the current-release references across README.md and docs/**.

Minor rather than patch: #119 changes the snippet, detailed report, dataflow
trace and description that every consumer of a finding reads, and adds the
`redact` rule-metadata key. Also records the socketdev 3.5.0 -> 3.6.0 lockfile
bump from #117, which merged without a changelog entry.

* fix(redaction): treat plain-text-password as logic, not a credential

The rule that fragment names matches password *handling* -- assigning request
input to a password field, or comparing against one -- so its match is an
expression rather than a literal. Running the literal pass on it reduced
`user.password = request.form.get('password')` to a row of asterisks, which is
the rule's main pattern and leaves nothing to act on. It belongs with
`hardcoded-ip` and the password-policy rules, which the same comment already
excludes for the same reason. A comparison against a hardcoded value is the one
shape it covers that carries a credential, and that is what the `hardcoded-*`
rules are for.

Also folds a duplicated TestRedactMessage class into one. The second definition
shadowed the first, so two message tests never ran.

* fix(redaction): serve both shapes of the password-logic rule

Dropping plain-text-password from the credential fragments fixed the
over-masking but opened a hole: one of that rule's patterns is a comparison
against a hardcoded string, and no hardcoded-* rule matches that shape, so
`if user.password == "hunter2"` went into the facts file verbatim. Confirmed
by scanning a file with exactly that line.

The fragment goes back, and the over-masking is fixed where it belongs. An
assigned value that calls something is an expression, not a bare credential, so
it skips the unquoted fallback and the literal pass masks just the quoted parts.
`user.password = request.form.get('password')` keeps its expression, the
comparison value is masked, and a bare value with a trailing comment is still
masked whole so a short credential cannot be partly revealed.

* fix(redaction): bind the right operator and mask trailing comments

Three defects in the unquoted-assignment fallback, found by working through the
shapes the credential rules actually produce.

A call matched anywhere in the value skipped masking entirely, so
`password: hunter2  # see get_secret()` kept the credential. The check is now
anchored: only a value that opens with a call is treated as an expression.

The first operator on the line bound, so a type annotation won over the
assignment after it and `password: str = "..."` was starred out whole rather
than reaching the literal pass -- ordinary Python and TypeScript. The last
operator now binds, and operators covered by a string literal are skipped so
the `:` in `url = "https://..."` cannot bind either. That needs the literal
spans, which one regex cannot express, so _split_assignment walks the matches.

Masking the value left a comment beside it holding the plaintext, as in
`password = get_secret()  # real value is hunter2`. Text after an unquoted
comment marker is now masked too.

Also stops measuring an unquoted value together with whatever follows it.
`password: hunter2 # plain comment` is long enough for a partial reveal even
though `hunter2` is not, and it was rendering as `password: hunt...ment`.

* fix(redaction): take the comment off first and mask per statement

Two ways a credential stayed in the part of the line the value search never
looked at.

A comment can hold an operator later in the line than the real one. Because the
comment was masked after the operator was chosen, that one bound and the value
in front of it was left in the head: `password = hunter2  # see x = y` kept
hunter2. The comment now comes off before anything else reads the line.

A line can also carry more than one statement, and only one operator binds per
statement, so `a = hunter2; password = x` masked the second value and left the
first. Masking now runs per statement, split on separators outside string
literals.

* fix(redaction): measure literal spans over the snippet, not per line

A literal can open on one line and close on another. Spans were computed per
line, so a marker on a literal's second line read as a comment and the rest of
that line was starred -- dropping the closing quote, after which the literal
pass no longer matched and the opening line's value survived.

Spans are now measured once over the whole snippet and consulted by absolute
position. Two things fall out of that.

A snippet is a slice of a file, so a literal can also never close. The quoted
value branch deferred to the literal pass, which never matches an unterminated
literal, so `password = "hunter2` was left untouched. It now defers only when
the quote opens a span the pass can find, and masks the value whole otherwise.

A multi-line literal body was measured as one value, so the head-and-tail
reveal exposed the start of its first line. Each line of such a body is now
masked whole, with the line breaks kept so the snippet still shows where the
literal begins and ends.

* fix(redaction): recognize string prefixes and interpolated bodies

A prefixed opener such as r""" or f""" was not read as opening a literal, so
the opening line was starred and its quotes were removed. Later lines were
still measured against the original spans, which said they were inside a
literal, so nothing masked them and the final literal pass no longer matched.
The prefix is now part of the opener check.

That alone left a partial reveal: literal text around an interpolation inflates
the body past the reveal threshold, so f"{b}_SuperSecret123!" showed 123!. An
interpolated body is masked whole, on the same reasoning as a multi-line one --
it is a block of content, not a single opaque value.

* fix(redaction): do not defer on a spurious empty-literal match

An unterminated triple-quoted value still produces a literal match: the engine
backtracks past the triple alternative and reads the first two quotes as an
empty string. That match was enough to send the value to the masking pass,
which then covered only those two quotes, so the credential stayed in the
snippet. Affects bare and prefixed openers alike.

Deferring now requires a span that starts at the quote and holds both
delimiters, which an empty match cannot satisfy. _STRING_LITERAL also gained
triple-quoted alternatives so a terminated block matches once with its real
body rather than as an empty string followed by a second literal.

* fix(redaction): fail safe when string state is lost, and fuzz the invariant

A generated-snippet sweep found three gaps the hand-written cases did not,
all of them the same thing: masking depends on knowing where string literals
start and end, and a truncated snippet can make that unknowable.

An unterminated literal is not reached by the assignment fallback when the
value sits in a comparison or a call argument, so it went to the masking pass,
which cannot match it. A quote outside every matched span now marks the rest of
the line as literal content.

An unclosed triple-quoted block does not simply fail to match -- its first two
quotes match as an empty string and the third pairs with any stray quote later,
producing one long span that hides a real assignment on a later line. An odd
count of triple delimiters now masks from the opener to the end.

A " or ' literal cannot hold a raw newline in any language these rules cover,
so a match that does is the same pairing artifact rather than a literal. Those
spans are discarded; backticks and triple quotes keep theirs.

tests/test_secret_redaction_fuzz.py generates the combinations rather than
listing them, and asserts no credential survives, none is partly revealed, and
non-credential snippets come through unchanged. 2,000,000 generated cases pass;
20,000 run in CI in about a second.

* fix(redaction): mask to the end of a snippet once string state is lost

Bugbot found that a credential on a continuation line survived, and extending
the fuzzer to put the secret after the line break -- it had only ever put it
before -- found a second case immediately.

Both are the same thing: masking that stops at the opening line. Where a
literal opens and its end is unknowable, everything after is inside it as far
as any reader can tell, so masking now runs to the end of the snippet rather
than the end of the line. The two ways state is lost -- a quote no surviving
span covers, and an odd number of triple delimiters -- are handled together
instead of separately.

The second case was the opposite failure. Masking an unquoted value whole
destroyed the opening quote of a literal that continued past the line, so the
snippet-wide pass afterwards no longer matched and the rest of the literal was
left alone. Such a value is now masked only up to the opener, and the pass
takes the literal itself.

Both generators put the secret on either side of a line break, so the shape is
covered from here on. 1,000,000 generated cases pass.

* fix(redaction): fail closed on ambiguous credential syntax

v3.3.0

Toggle v3.3.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
chore(release): 3.3.0 (#116)

Release prep for 3.3.0, bundling #114 and #115. #115 adds the
`--scan-all` / `--no-scan-all` CLI flags, so this is a minor bump
rather than a patch.

Bumps pyproject.toml, socket_basics/version.py, socket_basics/__init__.py,
action.yml and uv.lock to 3.3.0, synchronizes 83 current-release references
across README.md and docs/**, and stamps [Unreleased] as [3.3.0] - 2026-09-15.

Also pins the Socket Python CLI to 2.9.0 in Dockerfile.heavy and
app_tests/Dockerfile, ahead of that release publishing to PyPI.

v3.2.0

Toggle v3.2.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
chore(release): 3.2.0 (#113)

Release prep for 3.2.0: bump every version-bearing file, refresh uv.lock,
synchronize current-release references in README and docs/**, and stamp the
[Unreleased] changelog section as [3.2.0] - 2026-09-10.

Bundles #110 (TruffleHog verification and fail-closed scan errors), #111
(CLI/action input parity plus the documentation consistency pass) and #112
(Java SAST rule rewrite, and the Socket Python CLI 2.8.0 bump in the heavy
and app-tests images, which landed with that PR).

The changelog section was condensed and reorganized around a new "Upgrade
notes" block, because four of these changes alter which findings a scan
produces and the per-PR entries buried that. Two consequences of #110 were
missing from the changelog entirely and are now stated: on the default path
(trufflehog_show_unverified off) verified secrets become critical and
blocking where previously no secret could block a run, and verification is a
live check that sends candidates to third-party validation endpoints.

The Java volume reduction is quoted as the benchmark doc's own -92% headline
with its scoping caveat (~26% of the drop is new test and example path
exclusions, not rule logic), rather than the -94.6% unique-findings-in-
mature-libraries-only subset the per-PR entry used.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

v3.1.0

Toggle v3.1.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
chore(release): 3.1.0 (#109)

* docs(changelog): draft 3.1.0 entry for the bundled release

Covers #97, #98, #104, #105 and #106. Internal-only changes (#104, #106)
are collapsed into a short section; customer-facing changes keep the
detail needed to plan an upgrade, including the behavioral change where
an unresolvable changed_files scope now fails instead of scanning.

* chore(deps): refresh Socket-owned tool pins

Socket npm CLI 1.1.154 -> 1.1.165 across all three images, matching the
current npm release. Socket Python CLI 2.6.3 -> 2.7.0 in the heavy and
app-tests images.

2.7.0 is NOT published to PyPI yet (latest is 2.6.11), so this is
scaffolding: the heavy and app-tests image builds and core-tool-watch
both fail until it lands. Keep this commit separate so it can be dropped
or held if the CLI release slips.

The socketdev Python SDK is already current at 3.5.0, so no change.

* chore(release): 3.1.0

Version metadata, uv.lock, CHANGELOG date stamp, and 74 current-release
documentation references, via scripts/prep_release.py --version 3.1.0.

* fix(app-tests): refresh socketsecurity index metadata on install

This install pins an exact version, so a stale cached uv index response
makes a freshly published release look like it does not exist. Use
--refresh-package for just this package rather than --no-cache, which
would discard the cache mount's benefit for bandit and built wheels.

Dockerfile.heavy already passes --no-cache-dir on its pip equivalent.

* test: assert the socketsecurity pin, not the RUN's formatting

The assertion matched an exact literal, so it broke when the install
gained a --refresh-package flag and a line continuation even though the
version pin it guards was unchanged. Collapse continuations and match the
ARG-pinned spec after 'uv tool install' instead.

Verified the guard still fails for an unpinned spec and for a hardcoded
version that bypasses the ARG.

v3.0.0

Toggle v3.0.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
chore(release): 3.0.0 (#102)

* chore(release): 3.0.0

Version refs (version.py, __init__.py, pyproject.toml, action.yml image tag)
and CHANGELOG entry only, per the release process.

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* chore(release): regenerate uv.lock for 3.0.0

uv.lock records the project's own version; uv sync --frozen fails on the
pyproject mismatch without the regen.

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* chore(release): bump Socket CLI to 2.6.0 in the heavy image

Version ref bump folded into the release PR (was briefly #103).

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* chore(release): bump socketdev SDK to 3.5.0

Constraint + lock only; the core-tool-watch typed-params migration remains
in #99, which rebases on this.

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* chore(release): bump Socket CLI to 2.6.3 in the heavy image

Adopts the post-outage CLI release bundling the final pending PRs, so 3.0.0
ships a current pin without needing a back-to-back Basics release.

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

---------

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

v2.2.1

Toggle v2.2.1's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
fix: support TruffleHog exclude patterns (#94)

* fix: handle trufflehog exclude paths

* fix: use absolute trufflehog scan targets

* fix: complete trufflehog exclude handling

* docs: clarify trufflehog exclude fix

* fix: support trufflehog exclude globs

* fix: complete trufflehog exclusions for v2.2.1

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* fix(trufflehog): Address review feedback

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

---------

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

v2.2.0

Toggle v2.2.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
ci: publish multi-arch Docker image variants (#85)

* ci: publish multi-arch Docker images

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* ci: publish socket-basics heavy image

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* docs(changelog): add 2.1.0 release notes

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* fix(ci): harden Docker release publishing

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* fix(ci): address Docker publish review findings

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* fix(ci): harden Docker manifest publishing

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* fix(ci): publish heavy variant as tag suffix in the shared repo

All image variants now publish to the single socket-basics repository per
registry, distinguished by tag suffix (2.1.0 vs 2.1.0-heavy) instead of a
separate socket-basics-heavy repository. This follows the standard Docker
variant convention (like :slim/:alpine), requires no new Docker Hub repo,
token rescoping, or GHCR package visibility changes, and makes retiring
the POC variant trivial.

- _docker-pipeline.yml: new push_name input decouples the registry repo
  from the local build/artifact name
- publish-docker.yml: merge-manifests iterates variants with a tag_suffix,
  tags via metadata-action flavor suffix, and inspects both suffixed tags

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: move unreleased changelog entries out of the shipped 2.1.0 section

v2.1.0 was released from main with different content; this PR's entries
now sit under [Unreleased] and get stamped as 2.2.0 at release time.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: correct 2.1.0 changelog date to actual release date (2026-07-22)

The 2026-06-02 date reflected when the bundled commits were authored,
not when v2.1.0 was actually tagged and released.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(release): prep v2.2.0 — stamp changelog, bump version files and action image ref

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* ci: gate publishing on version files matching the release tag

Restores the guarantee lost when .hooks/version-check.py was removed in
#46: resolve-version now fails fast if version.py, pyproject.toml, or
the action.yml image tag disagree with the tag being published.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(scripts): add prep_release.py for mechanical release-prep PRs

One command bumps version.py, pyproject.toml, action.yml, refreshes
uv.lock, and stamps the [Unreleased] changelog section — so the final
release PR is a five-file diff that always satisfies the publish
workflow's version gate. Validates everything before writing anything;
a failure leaves the tree untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(release): sync __init__.py version and derive bumps from pyproject

The sync_release_version.py check from main caught socket_basics/
__init__.py still at 2.0.3 — a duplicate version field prep_release.py
didn't know about. prep_release.py now bumps only pyproject.toml (the
canonical source) and delegates derived files to sync_release_version.py
so the two scripts can never disagree. The publish version gate also
checks __init__.py now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(core-tool-watch): opt into fail-closed purl batch semantics

The batch purl endpoints default to fail-open: inputs with pending or
failed resolution are silently omitted unless the caller opts in. Fresh
pins (socketdev 3.3.0) fell into that omission path and tripped the
unverified-pin guard with a misleading message.

- purl.post now sends poll=true + timeoutSec=120 + alerts=true (extra
  kwargs pass through as query params on SDK 3.0.29 and 3.3.0)
- client timeout raised 60->180s so the bounded server poll can finish
- synthetic pendingScan/notFound rows are mapped to a status field
  before severity classification (never through MALWARE_ALERT_TYPES /
  CRITICAL_SEVERITIES) and fail closed with distinct, precise messages
- OpenGrep's pkg:github coverage-gap exemption carries over: its pin
  now returns a notFound row instead of being omitted, and stays exempt
- log the endpoint choice + org slug for forensics

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: changelog entry for core-tool-watch fail-closed purl fix

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(core-tool-watch): calibrate alert thresholds for the full alert set

alerts=true exposed the complete informational alert firehose for the
first time (the old fail-open responses carried no alert data, so the
malware gate never actually saw alerts). Calibrated against real batch
data from run 30504424787:

- drop capability/heuristic signals from MALWARE_ALERT_TYPES:
  shellAccess fires on all four tools (security CLIs spawn
  subprocesses), gptMalware/gptSecurity/obfuscatedFile fire on the
  OpenGrep repo artifact (SAST engines bundle malicious-looking test
  fixtures by design)
- hard-fail severity gate is critical-only; high-severity rows (cve on
  trivy, gpt heuristics) stay visible in the report for human review

Verified: replaying the failing run's report through the new rules
yields green while keeping true compromise signals fail-worthy.

---------

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

v2.1.0

Toggle v2.1.0's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
chore(deps): bundle Dependabot updates + harden dependency review wor…

…kflows (#78)

* chore(deps): bundle dependency updates + harden supply-chain review

Bundles 8 open Dependabot PRs into one verified change and hardens the
Dependabot config + dependency-review workflows, mirroring the work in
socket-sdk-python#84 and socket-python-cli#207/#217. Adds a supply-chain
watch for the four core OSS tools Dependabot cannot cleanly track.

- uv.lock: idna 3.10->3.18 (CVE-2026-45409), pygments 2.19.2->2.20.0,
  pytest 8.4.2->9.0.3, urllib3 2.6.3->2.7.0
- _docker-pipeline.yml: bump 4 docker/* actions (setup-buildx, login,
  metadata, build-push)
- dependabot.yml: add uv ecosystem, group every ecosystem into
  minor/patch + major bundles, scan composite actions
- dependency-review.yml (was dependabot-review.yml): runs on every PR;
  free/enterprise sfw split; report artifacts; app_tests docker smoke
- core-tool-watch.yml + scripts/check_core_tools.py: discover latest
  versions of opengrep/trufflehog/trivy/socketdev and score them through
  the Socket API (socketdev SDK purl.post); drift issue + report artifact
- python-tests.yml: uv.lock drift guard

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ci): drop socket-firewall environment gate, add required coverage gate

Mirroring the Python CLI/SDK used `environment: socket-firewall` to scope the
SFW token, but that environment can carry a required-reviewers approval gate.
Because the enterprise SFW check can't be a required status check (it would
block Dependabot/fork PRs that only run the free edition), maintainers could
merge without approving the deployment -- the meaningful check silently never
ran, and approvers could rubber-stamp their own PRs. On the scheduled
core-tool-watch job an approval gate would hang the cron run outright.

- Remove `environment:` from python-sfw-smoke-enterprise and core-tool-watch;
  use a plain repo/org SOCKET_SFW_API_TOKEN (zizmor secrets-outside-env is
  already disabled here, so no lint cost). Job split still isolates the token
  to the enterprise job only.
- Add always-on `dependency-review-gate` job: pass when no python deps changed,
  else require the free (Dependabot/fork) or enterprise (maintainer) smoke job
  to have succeeded. Mark THIS as the single required status check -- safe on
  every PR, no manual gate, no bypass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ci): scope SFW token via environment (no approval rule), harden gate

Adopt the socket-python-cli#224 pattern uniformly. The environment was never
the problem -- the required-reviewers approval RULE on it was. Keep the
environment for secret scoping; forbid the rule.

- Restore `environment: socket-firewall` on python-sfw-smoke-enterprise and the
  core-tool-watch analyze job so SOCKET_SFW_API_TOKEN is scoped to those jobs.
  Header documents that the environment must have NO reviewers rule, with the
  gh api command to enforce it (reviewers: null).
- dependency-review-gate (Pattern 2 aggregator): now also needs
  docker-smoke-app-tests; fails on any failure/cancelled result (success and
  skipped pass) AND requires the trust-appropriate SFW edition to have
  succeeded when Python deps changed. Runs if: always() so the required context
  is always created -- no Pattern 1 bypass twin needed. Must land on main before
  being added to branch protection.

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* fix(ci): degrade SFW enterprise to free when token absent; upload JSON report

Live CI exposed two things on the now-enabled Actions:
- socketdev/action firewall-enterprise HARD-ERRORS on an empty token (no
  silent fallback), so a trusted dep PR opened before the SOCKET_SFW_API_TOKEN
  secret exists fails and the required gate blocks merge. setup-sfw now resolves
  the effective mode and falls back to firewall-free when enterprise is
  requested without a token -- still a real supply-chain check, ships green
  today, auto-upgrades to enterprise the moment the secret is added. Token is
  read via env, never interpolated into the script.
- socketdev/action writes a structured report to $SFW_JSON_REPORT_PATH; both
  smoke jobs now capture it and upload it alongside the tee'd log.

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* feat(core-tool-watch): add Semgrep upstream proxy for OpenGrep Socket scoring

OpenGrep ships as a GitHub-release binary that Socket has no data for under
its pkg:github coordinate, so the watcher reported 'no data' for it. OpenGrep
is a hard fork of Semgrep, so fall back to scoring the upstream Semgrep
lineage (pkg:pypi/semgrep) as a project-health proxy.

The proxy is report-only and never build-failing: it does not analyze
OpenGrep's own release artifacts, so a Semgrep alert must not block an
OpenGrep build. The pinned/latest verdicts show the proxy result labeled
'(via semgrep upstream proxy)' when the primary coordinate has no data, and
the JSON report records it under a separate 'proxy' key.

The npm 'opengrep' package is a single-version squat (not the official
distribution) and is deliberately not used as a coordinate.

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* chore(deps): bundle 3 newly-filed Dependabot PRs (#81, #86, #87)

- requests >=2.31.0 -> >=2.33.0 (#87); targeted 'uv lock --upgrade-package
  requests' resolves 2.34.2 (newer than Dependabot's 2.33.0) and pulls
  light-s3-client 0.0.40 -- the only two packages Dependabot's own PR touched.
- actions/setup-python 6.2.0 -> 6.3.0 in python-tests.yml (#86, SHA verified
  against the v6.3.0 tag). The group's setup-buildx 4.1.0 bump is already in
  this branch.
- docker/metadata-action 6.1.0 (#81) is already applied here (identical SHA) --
  #81 is fully superseded, no code change.

All three PRs to be closed manually as superseded by #78.

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* fix(core-tool-watch): address Cursor Bugbot findings on #78

Five hardening fixes flagged by Bugbot:

1. Critical alerts now fail-on-malware (was malware-only). Track any_critical
   separately and exit non-zero on malware OR critical, matching the documented
   intent; add a 'critical' GitHub output.
2. Pins are read from BOTH Dockerfiles. app_tests/Dockerfile pins the same core
   tools (trufflehog/trivy/opengrep) independently; the reader only saw the root
   Dockerfile, so a divergent app_tests bump went unscored. Tool.pinned is now a
   list of every distinct pinned version, all of which are scored.
3. Watch mode no longer fails on latest. Only PINNED (in-use) versions are
   fail-worthy; the discovered latest is scored for drift reporting only, so a
   scheduled watch can't go red on an upstream release we haven't adopted.
4. dependency-review-gate fails closed when inspect fails. A failed inspect left
   DEPS_CHANGED/IS_TRUSTED empty, so the coverage rules silently passed and a PR
   with dep changes could merge with no Socket Firewall run. Added Rule 0.
5. Socket API errors fail closed in build mode. A swallowed analyze_purls
   exception let --fail-on-malware exit 0 with pinned versions unverified; now
   a scoring error (token present) fails the run.

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* fix(ci): address 2 more Bugbot findings on #78

1. Drift issue never opened (High). gh issue list --jq '.[0].number' prints
   the literal string 'null' when no open core-tool-drift issue exists; 'null'
   is non-empty in bash, so the first scheduled drift run would call
   'gh issue edit null' instead of creating the issue. Use '// empty' so an
   absent issue yields an empty string and the create branch runs.

2. Tests ignored the lockfile (Medium). python-tests installed deps via
   'pip install -e .[dev]' (a fresh resolution) while only asserting the lock
   separately, so tests could run against different versions than uv.lock.
   Switch to 'uv sync --locked --extra dev' + 'uv run --no-sync pytest' so
   tests run against exactly the locked set.

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* fix(ci): restore bare LICENSE to unblock CI (mirrors #88)

Temporary: pull #88's fix into this branch so CI's merge-with-main ref builds.
PR #79 renamed the license to LICENSE.md and deleted the empty LICENSE, but
pyproject.toml and the Dockerfile still reference LICENSE, so hatchling and the
Docker build fail against current main. This restores the PolyForm content into
bare LICENSE (rename LICENSE.md -> LICENSE) and drops the now-moot !LICENSE.md
.gitignore exception.

Revert this commit and pull latest main once #88 lands there (main will then
carry the identical fix).

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>

* fix(core-tool-watch): per-event concurrency group + document Dependabot secret gap

- Include github.event_name in the concurrency group so a merge to main
  can't cancel the in-flight weekly watch run (or vice versa).
- Document that Dependabot-triggered pull_request runs never receive the
  environment-scoped SOCKET_SFW_API_TOKEN (only Dependabot secrets), so
  build-mode scoring silently degrades to drift-only there; note the
  'gh secret set --app dependabot' mirror needed for pre-merge coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(deps-review): route Dependabot through sfw-enterprise; isolate core-tool-watch scan env

Dependabot-triggered runs can read a *Dependabot-store* secret (set via
'gh secret set SOCKET_SFW_API_TOKEN --app dependabot'), so the free-tier
routing for Dependabot -- a workaround for the assumption that its runs
could never hold a token -- is gone:

- dependency-review.yml: trusted == any in-repo (non-fork) PR, now
  including Dependabot. Its dep bumps get full org-policy (enterprise)
  enforcement; forks stay on the anonymous free edition. setup-sfw's
  existing empty-token fallback covers the window until the Dependabot
  secret mirror exists.
- core-tool-watch.yml: sync the scan's Python env from the DEFAULT
  BRANCH lockfile (second checkout at .scan-env) so the token-holding
  step never imports packages bumped by the PR under review -- it only
  reads the PR's pins. Makes the Dependabot token mirror safe here too.
- dependency-review.yml: import smokes use 'uv run --no-sync' so the
  post-firewall step can't re-sync outside sfw (Bugbot finding).
- check_core_tools.py: docstring/help now honestly describe the strict
  fail thresholds (curated malware-class list + high/critical), which
  are intentional (Bugbot finding).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(core-tool-watch): sync scan env with --no-install-project

The scan env checkout of main fails to build the socket-basics package
editable while main carries the LICENSE.md rename breakage (#79, fix
pending in #88). The scan only imports the dependencies (socketdev SDK),
never socket_basics itself, so skip installing the project entirely --
also insulates this guard from any future main-side packaging breakage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(core-tool-watch): org-scoped purl endpoint + fail closed on empty API result

Two related hardenings from external review of purl.post() usage:

- Pass org_slug (resolved via client.org.get) when the installed SDK
  supports it: socketdev >= 3.1 (socket-sdk-python#76) deprecates the
  legacy POST /v0/purl in favor of POST /v0/orgs/{slug}/purl, and a
  future major may drop the legacy route. The pinned 3.0.29 predates
  the parameter, so it is signature-gated -- activates automatically
  when the scan env's lockfile bumps the SDK.

- Raise on an empty purl.post result: the SDK swallows ANY non-200
  (expired token, dropped endpoint, outage) into [], which previously
  flowed through as 'no data' verdicts and exit 0 -- fail-open. Every
  run scores coordinates Socket definitely has data for, so empty is
  an API failure; raising routes it into the existing scoring_error
  fail-closed path under --fail-on-malware.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(core-tool-watch): resolve org slug only when unambiguous

Match socket-python-cli's get_org_id_slug() semantics: pass org_slug to
purl.post only when the token maps to exactly one org; multi-org tokens
fall back to the legacy endpoint with a notice rather than guessing and
scoring under the wrong org's policies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(core-tool-watch): fail closed on unverified pinned coordinates

Bugbot: a non-empty but incomplete Socket batch (or a pinned coordinate
that never matches a returned analysis row) previously passed as 'no
data' with exit 0 -- the guard could green-light a build without
verifying every pin it exists to gate.

Tools now declare socket_coverage (default True); with a token and a
successful scoring pass, any covered pinned coordinate missing from the
results is collected as unverified and fails a --fail-on-malware run,
listing the exact coordinates. OpenGrep sets socket_coverage=False:
its pkg:github coordinate is the documented no-data case with the
report-only semgrep proxy, and must not perma-fail the guard. The
unverified list is also surfaced in the JSON report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

Verified

This tag was signed with the committer’s verified signature.

v2.0.2

Toggle v2.0.2's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
chore: fix release and updater script (#57)

* chore: bump versions to v2.0.2, clean changelog, fix update script

Signed-off-by: lelia <lelia@socket.dev>

* chore: handle existing release edge case

Signed-off-by: lelia <lelia@socket.dev>

* chore: update PR template to remove temp instrux

Signed-off-by: lelia <lelia@socket.dev>

* chore: further template tweaks

Signed-off-by: lelia <lelia@socket.dev>

* fix: remove duplicate line from workflow

Signed-off-by: lelia <lelia@socket.dev>

---------

Signed-off-by: lelia <lelia@socket.dev>