Skip to content

Commit c14553e

Browse files
committed
Sync OSS release snapshot 2.4.3
Signed-off-by: keshavp <32313895+keshprad@users.noreply.github.com>
1 parent 8255b60 commit c14553e

10 files changed

Lines changed: 76 additions & 46 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
### 2.4.3 (Wednesday, July 22, 2026)
2+
### Features/Bug Fixes
3+
* Clarify CLI runtime model fallback in provider docs
4+
* fix(provider): align Claude fallback contract with settings isolation (#295)
5+
* fix(provider): isolate Claude settings hooks in spawned CLI (#295)
6+
* fix(suppression): match reported finding text
7+
* ci: disable optional provider test
8+
* feat: publish a public-safe changelog
9+
---
110
### 2.4.2 (Tuesday, July 21, 2026)
211
### Features/Bug Fixes
312
* fix(oss): keep internal provider references private

‎docs/SUPPRESSION.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ rules: # human-authored, glob-based, drift-tolerant
5252
reason: "Trigger-phrase breadth is a description nit, not a vuln"
5353
- id: "SSD-2"
5454
path: "example-skill/SKILL.md" # glob over the finding's file
55-
message: "*example false-positive phrase*" # glob over the finding's message
55+
message: "*example false-positive phrase*" # glob over its description or matched text
5656
reason: "False positive: benign trigger phrase, not an instruction"
5757

5858
fingerprints: # machine-generated, exact
@@ -78,7 +78,7 @@ Field reference:
7878
|-------|-----------------|-------|
7979
| `id` (or `rule_id`) | `Finding.rule_id` | glob |
8080
| `path` (or `file`) | `Finding.file` | glob; `*` crosses `/`, `**` is an alias for `*` |
81-
| `message` | `Finding.message` | glob, case-insensitive; wrap a keyword in `*` for substring |
81+
| `message` | `Finding.message`, plus the matched text shown as `finding` in reports | glob, case-insensitive; wrap a keyword in `*` for substring |
8282
| `reason` | — | required; recorded in reports and audits |
8383

8484
Glob matching uses Python's [`fnmatch`](https://docs.python.org/3/library/fnmatch.html),

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "skillspector"
7-
version = "2.4.2"
7+
version = "2.4.3"
88
description = "SkillSpector: Security scanner for AI agent skills (Claude Code, Cursor, and similar). Scans skills for vulnerabilities, malicious patterns, and security risks before installation. Supports Git repos, URLs, zips, and local directories; runs static pattern checks and optional LLM semantic analysis; outputs terminal, JSON, and Markdown reports with risk scoring."
99
readme = "README.md"
1010
license = "Apache-2.0"

‎src/skillspector/providers/anthropic/provider.py‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515

1616
"""Anthropic provider — Claude models via api.anthropic.com.
1717
18-
Reads ``ANTHROPIC_API_KEY`` for credentials and honors ``ANTHROPIC_BASE_URL``
19-
as an explicit endpoint override (e.g. a local proxy); when unset, requests
20-
go to api.anthropic.com. Constructs ``langchain_anthropic.ChatAnthropic``
21-
directly. It defaults to Opus 4.6 for analyzers and Sonnet 4.6 for
22-
``meta_analyzer`` (cheaper for the high-volume filter pass).
18+
Reads ``ANTHROPIC_API_KEY`` for credentials and constructs
19+
``langchain_anthropic.ChatAnthropic`` directly. Defaults to Opus 4.6 for
20+
analyzers and Sonnet 4.6 for ``meta_analyzer`` (cheaper for the
21+
high-volume filter pass), mirroring the policy used by
22+
``NvInferenceProvider``.
2323
"""
2424

2525
from __future__ import annotations
@@ -34,7 +34,7 @@
3434
from skillspector.providers import registry
3535
from skillspector.providers.chat_models import resolve_reasoning_effort
3636

37-
# Default endpoint; overridden by ``ANTHROPIC_BASE_URL`` when set.
37+
# Documented for completeness — ChatAnthropic defaults here when base_url=None.
3838
ANTHROPIC_BASE_URL = "https://api.anthropic.com"
3939

4040
REGISTRY_PATH = str(Path(__file__).with_name("model_registry.yaml"))
@@ -49,12 +49,11 @@ class AnthropicProvider:
4949
}
5050

5151
def resolve_credentials(self) -> tuple[str, str | None] | None:
52-
"""Return ``(api_key, base_url)`` from ``ANTHROPIC_API_KEY`` / ``ANTHROPIC_BASE_URL``."""
52+
"""Return ``(api_key, base_url)`` from ``ANTHROPIC_API_KEY``."""
5353
api_key = os.environ.get("ANTHROPIC_API_KEY", "").strip()
5454
if not api_key:
5555
return None
56-
base_url = os.environ.get("ANTHROPIC_BASE_URL", "").strip() or None
57-
return api_key, base_url
56+
return api_key, None
5857

5958
def create_chat_model(
6059
self,
@@ -68,11 +67,11 @@ def create_chat_model(
6867
if creds is None:
6968
return None
7069

71-
api_key, base_url = creds
70+
api_key, _ = creds
7271
kwargs = {
7372
"model_name": model,
7473
"api_key": SecretStr(api_key),
75-
"base_url": base_url or ANTHROPIC_BASE_URL,
74+
"base_url": ANTHROPIC_BASE_URL,
7675
"max_tokens_to_sample": max_tokens,
7776
"timeout": timeout,
7877
"stop": None,

‎src/skillspector/suppression.py‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
2121
* ``rules`` — human-authored, glob-based suppressions. A finding is suppressed
2222
when every field a rule specifies (``id``, ``path``, ``message``) glob-matches
23-
the finding. Unspecified fields match anything. This covers both global
24-
pattern suppression (e.g. ``id: "SQP-1"``) and skill/file-scoped suppression
25-
(e.g. ``id: "SSD-2"`` + ``path: "deploy-topology-execute-scripts/SKILL.md"``).
23+
the finding. ``message`` covers both the analyzer description and the matched
24+
text surfaced as ``finding`` in reports. Unspecified fields match anything.
25+
This covers both global pattern suppression (e.g. ``id: "SQP-1"``) and
26+
skill/file-scoped suppression (e.g. ``id: "SSD-2"`` +
27+
``path: "deploy-topology-execute-scripts/SKILL.md"``).
2628
2729
* ``fingerprints`` — machine-generated exact suppressions. Each entry is the
2830
stable hash of one known finding, so re-scans only surface *new* findings.
@@ -120,8 +122,14 @@ def matches(self, finding: Finding) -> bool:
120122
return False
121123
if self.path is not None and not _match_glob(finding.file or "", self.path):
122124
return False
123-
if self.message is not None and not _match_glob(finding.message or "", self.message):
124-
return False
125+
if self.message is not None:
126+
message_candidates = (
127+
finding.message or "",
128+
finding.finding or "",
129+
finding.matched_text or "",
130+
)
131+
if not any(_match_glob(candidate, self.message) for candidate in message_candidates):
132+
return False
125133
return True
126134

127135

‎tests/provider/test_provider_endpoint.py‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,11 @@ def test_openai_provider_makes_live_structured_request(
7171
assert result == ProviderResult(ok=True)
7272

7373

74-
def test_anthropic_provider_makes_live_structured_request(
75-
monkeypatch: pytest.MonkeyPatch,
76-
) -> None:
74+
def test_anthropic_provider_makes_live_structured_request() -> None:
7775
"""Anthropic provider reaches its default endpoint and returns structured output."""
7876
from skillspector.providers.anthropic import ANTHROPIC_BASE_URL, AnthropicProvider
7977

8078
_skip_without_env("ANTHROPIC_API_KEY")
81-
# This live provider check must hit Anthropic's default base URL, not a proxy.
82-
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
8379

8480
model = _model_from_env("SKILLSPECTOR_ANTHROPIC_TEST_MODEL", AnthropicProvider.DEFAULT_MODEL)
8581
llm = AnthropicProvider().create_chat_model(model, max_tokens=32, timeout=60)

‎tests/unit/test_llm_utils.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353

5454
_LLM_ENV_VARS = (
5555
"ANTHROPIC_API_KEY",
56-
"ANTHROPIC_BASE_URL",
5756
"OPENAI_API_KEY",
5857
"OPENAI_BASE_URL",
5958
"NVIDIA_INFERENCE_KEY",

‎tests/unit/test_providers.py‎

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
resolve_provider_credentials,
4646
use_provider,
4747
)
48-
from skillspector.providers.anthropic import ANTHROPIC_BASE_URL, AnthropicProvider
48+
from skillspector.providers.anthropic import AnthropicProvider
4949
from skillspector.providers.antigravity_cli import AntigravityCLIProvider
5050
from skillspector.providers.chat_models import create_openai_compatible_chat_model
5151
from skillspector.providers.claude_cli import ClaudeCLIProvider
@@ -118,7 +118,6 @@ def _clean_provider_env(monkeypatch: pytest.MonkeyPatch):
118118
monkeypatch.delenv("OPENAI_PROJECT_ID", raising=False)
119119
monkeypatch.delenv("SKILLSPECTOR_REASONING_EFFORT", raising=False)
120120
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
121-
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
122121
monkeypatch.delenv("SKILLSPECTOR_MODEL", raising=False)
123122
monkeypatch.delenv("SKILLSPECTOR_MODEL_REGISTRY", raising=False)
124123
monkeypatch.delenv("SKILLSPECTOR_PROVIDER", raising=False)
@@ -301,31 +300,14 @@ def test_resolves_anthropic_api_key_without_openai_endpoint(
301300
) -> None:
302301
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-x")
303302
creds = AnthropicProvider().resolve_credentials()
304-
assert creds == ("sk-ant-x", None) # None → ChatAnthropic uses api.anthropic.com
305-
306-
def test_honors_anthropic_base_url_override(self, monkeypatch: pytest.MonkeyPatch) -> None:
307-
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-x")
308-
monkeypatch.setenv("ANTHROPIC_BASE_URL", "http://localhost:8787")
309-
creds = AnthropicProvider().resolve_credentials()
310-
assert creds == ("sk-ant-x", "http://localhost:8787")
303+
assert creds == ("sk-ant-x", None)
311304

312305
def test_creates_native_chat_anthropic(self, monkeypatch: pytest.MonkeyPatch) -> None:
313306
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-x")
314307
llm = AnthropicProvider().create_chat_model("claude-opus-4-6", max_tokens=123)
315308
assert isinstance(llm, ChatAnthropic)
316309
assert llm.model == "claude-opus-4-6"
317310
assert llm.max_tokens == 123
318-
# No override → ChatAnthropic points at the default Anthropic endpoint.
319-
assert str(llm.anthropic_api_url).rstrip("/") == ANTHROPIC_BASE_URL.rstrip("/")
320-
321-
def test_create_chat_model_honors_base_url_override(
322-
self, monkeypatch: pytest.MonkeyPatch
323-
) -> None:
324-
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-x")
325-
monkeypatch.setenv("ANTHROPIC_BASE_URL", "http://localhost:8787")
326-
llm = AnthropicProvider().create_chat_model("claude-opus-4-6", max_tokens=123)
327-
assert isinstance(llm, ChatAnthropic)
328-
assert str(llm.anthropic_api_url).rstrip("/") == "http://localhost:8787"
329311

330312
@pytest.mark.parametrize("effort", ["provider-specific-value"])
331313
def test_reasoning_effort_passthrough(

‎tests/unit/test_suppression.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,43 @@ def test_rule_message_glob_is_case_insensitive_substring() -> None:
101101
assert not rule.matches(_finding(message="Reads environment variables"))
102102

103103

104+
def test_rule_message_glob_matches_report_finding_text() -> None:
105+
rule = SuppressionRule(
106+
path="*flow/scripts/cmd.py",
107+
message="*shell=True*",
108+
reason="Reviewed operator command",
109+
)
110+
finding = Finding(
111+
rule_id="TM1",
112+
message="Tool Parameter Abuse",
113+
severity="HIGH",
114+
file="flow/scripts/cmd.py",
115+
start_line=178,
116+
finding="subprocess.run(command, shell=True",
117+
matched_text="subprocess.run(command, shell=True",
118+
)
119+
120+
assert rule.matches(finding)
121+
122+
123+
def test_rule_message_glob_still_requires_other_selectors() -> None:
124+
rule = SuppressionRule(
125+
path="*flow/scripts/cmd.py",
126+
message="*shell=True*",
127+
reason="Reviewed operator command",
128+
)
129+
finding = Finding(
130+
rule_id="TM1",
131+
message="Tool Parameter Abuse",
132+
severity="HIGH",
133+
file="other/scripts/cmd.py",
134+
start_line=178,
135+
finding="subprocess.run(command, shell=True",
136+
)
137+
138+
assert not rule.matches(finding)
139+
140+
104141
def test_double_star_is_alias_for_star() -> None:
105142
rule = SuppressionRule(path="**/SKILL.md", reason="any skill file")
106143
assert rule.matches(_finding(file="a/b/c/SKILL.md"))

‎uv.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)