diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c9190..5c84d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Use GPT-6 Luna with high reasoning for both bundled ClawHub judges and forward SkillSpector model/reasoning and optional A.I.G reasoning settings into scanner sandboxes. + ## 0.2.0 - 2026-09-22 **Highlights:** Preserve scanner evidence, bound benchmark input memory, and identify worker-owned containers for cleanup. diff --git a/README.md b/README.md index 4d0ffde..c2f10b7 100644 --- a/README.md +++ b/README.md @@ -156,11 +156,11 @@ clawscan scanners skillspector | ID | Name | Repo | Description | Required env vars | Local dependency setup | | --- | --- | --- | --- | --- | --- | | `agentverus` | AgentVerus | [repo](https://github.com/agentverus/agentverus-scanner) | Local file or directory scanner invoked through agentverus-scanner. | none | `npm install --save-dev agentverus-scanner` | -| `aig` | Tencent AI-Infra-Guard | [repo](https://github.com/Tencent/AI-Infra-Guard/tree/main/skill-scan) | Tencent Zhuque Lab's local directory scanner invoked through `aig-skill-scan`. Produces SARIF 2.1.0 with SkillTrustBench T01-T09 evidence. | `LLM_API_KEY` or `OPENAI_API_KEY`
Optional configDEFAULT_MODEL, DEFAULT_BASE_URL, DEFAULT_MODEL_CONTEXT_WINDOW, LOG_LEVEL.
| `pip install aig-skill-scan` | +| `aig` | Tencent AI-Infra-Guard | [repo](https://github.com/Tencent/AI-Infra-Guard/tree/main/skill-scan) | Tencent Zhuque Lab's local directory scanner invoked through `aig-skill-scan`. Produces SARIF 2.1.0 with SkillTrustBench T01-T09 evidence. | `LLM_API_KEY` or `OPENAI_API_KEY`
Optional configDEFAULT_MODEL, REASONING_EFFORT (requires upstream support), DEFAULT_BASE_URL, DEFAULT_MODEL_CONTEXT_WINDOW, LOG_LEVEL.
| `pip install aig-skill-scan` | | `cisco` | Cisco AI Defense skill-scanner | [repo](https://github.com/cisco-ai-defense/skill-scanner) | Local file or directory scanner invoked through `skill-scanner` with JSON report output. Optional upstream env vars enable LLM, VirusTotal, and Cisco AI Defense analyzers. | none
Optional configSKILL_SCANNER_LLM_API_KEY, SKILL_SCANNER_LLM_PROVIDER, SKILL_SCANNER_LLM_MODEL, SKILL_SCANNER_LLM_BASE_URL, SKILL_SCANNER_LLM_USER, SKILL_SCANNER_LLM_API_VERSION, SKILL_SCANNER_LLM_FORCE_JSON_OBJECT, SKILL_SCANNER_META_LLM_API_KEY, SKILL_SCANNER_META_LLM_MODEL, SKILL_SCANNER_META_LLM_BASE_URL, SKILL_SCANNER_META_LLM_API_VERSION, AWS_PROFILE, AWS_REGION, GOOGLE_APPLICATION_CREDENTIALS, VIRUSTOTAL_API_KEY, AI_DEFENSE_API_KEY, AI_DEFENSE_API_URL.
| `uv pip install cisco-ai-skill-scanner` | | `clawscan-static` | ClawScan Static | [repo](https://github.com/openclaw/clawscan) | Built-in deterministic scanner for high-signal risky skill and OpenClaw plugin patterns; packaged Python bytecode and NUL-obfuscated text are flagged and inspected, while opaque binary omissions remain visible as low-severity evidence. | none | skipped; built in | | `relyable` | Relyable | [repo](https://github.com/veriker/relyable) | Functional re-derivation evidence: does the skill still do what its docs claim, recomputed? Emits the strongest grade that applies. `exogenous`: a declared `rederive.json` property manifest (idempotence / round-trip), with both sides of the relation computed from the skill's own code and the result mutation-tested against vacuity. `self_spec`: re-runs the author's own committed oracle (shipped tests or documented I/O examples). `cold_golden`: when an LLM key is set, a code-blind model infers goldens from SKILL.md alone and abstains unless the docs pin exact behavior; divergences are reported as unconfirmed, never as accusations. `non_rederivable`: the honest floor, never a fabricated pass. Functional axis only; complements the security scanners and does not detect malware or prompt injection. Skill code runs only inside the Docker sandbox (or with an explicit opt-in), in a scrubbed environment, and the scanner fails closed otherwise. Not preinstalled in the `clawscan-runtime` image. | none
Optional configRELYABLE_SCAN_ALLOW_HOST_EXEC — explicit ack that the host is disposable when running with --sandbox off.

RELYABLE_LLM_API_KEY (+ RELYABLE_LLM_PROVIDER anthropic|openai, RELYABLE_LLM_MODEL, RELYABLE_LLM_BASE_URL) — explicit per-scanner opt-in that enables the cold_golden lane; key presence only is ever recorded in the payload. Generic ANTHROPIC_API_KEY/OPENAI_API_KEY are honored by standalone relyable-scan but are deliberately not auto-forwarded by ClawScan.
| `clawscan install relyable` — not preinstalled in the runtime image | -| `skillspector` | NVIDIA SkillSpector | [repo](https://github.com/NVIDIA/skillspector) | Local skill or OpenClaw plugin file/directory scanner. Uses LLM mode when provider env vars are set; otherwise runs with `--no-llm`. | none
Optional configSKILLSPECTOR_PROVIDER, SKILLSPECTOR_MODEL, SKILLSPECTOR_MODEL_REGISTRY, SKILLSPECTOR_LOG_LEVEL, SKILLSPECTOR_SSL_VERIFY, NVIDIA_INFERENCE_KEY, OPENAI_API_KEY, OPENAI_BASE_URL, ANTHROPIC_API_KEY, ANTHROPIC_PROXY_ENDPOINT_URL, ANTHROPIC_PROXY_API_KEY, ANTHROPIC_PROXY_API_VERSION.
| `uv tool install git+https://github.com/NVIDIA/skillspector.git` | +| `skillspector` | NVIDIA SkillSpector | [repo](https://github.com/NVIDIA/skillspector) | Local skill or OpenClaw plugin file/directory scanner. Uses LLM mode when provider env vars are set; otherwise runs with `--no-llm`. | none
Optional configSKILLSPECTOR_PROVIDER, SKILLSPECTOR_MODEL, SKILLSPECTOR_REASONING_EFFORT, SKILLSPECTOR_MODEL_REGISTRY, SKILLSPECTOR_LOG_LEVEL, SKILLSPECTOR_SSL_VERIFY, NVIDIA_INFERENCE_KEY, OPENAI_API_KEY, OPENAI_BASE_URL, ANTHROPIC_API_KEY, ANTHROPIC_PROXY_ENDPOINT_URL, ANTHROPIC_PROXY_API_KEY, ANTHROPIC_PROXY_API_VERSION.
| `uv tool install git+https://github.com/NVIDIA/skillspector.git` | | `snyk` | Snyk Agent Scan | [repo](https://github.com/snyk/agent-scan) | Local skill scanner invoked through `uvx snyk-agent-scan`. | `SNYK_TOKEN` | verifies `uvx` launcher | | `socket` | Socket CLI | [repo](https://github.com/SocketDev/socket-cli) | Local file or directory scanner using Socket's public CLI full-scan path. | `SOCKET_CLI_API_TOKEN` | `npm install -g socket` | | `virustotal` | VirusTotal API | [docs](https://docs.virustotal.com/reference/file) | API-backed local file hash lookup. Skill and OpenClaw plugin directories are scanned as deterministic ZIP archives. | `VIRUSTOTAL_API_KEY` | skipped; API-backed | @@ -199,9 +199,17 @@ clawscan profiles -v | Profile | Scanners | Judge | | --- | --- | --- | -| `clawhub` | `skillspector`, `clawscan-static`, `aig` | Codex `gpt-5.5`, high reasoning, bundled ClawHub prompt/schema | +| `clawhub` | `skillspector`, `clawscan-static`, `aig` | Codex `gpt-6-luna`, high reasoning, bundled ClawHub prompt/schema | +| `clawhub-aig` | `skillspector`, `aig` | Codex `gpt-6-luna`, high reasoning, bundled ClawHub prompt/schema | | `openclaw-install-policy` | `skillspector`, `clawscan-static` | none | +Set `SKILLSPECTOR_MODEL=gpt-6-luna` and +`SKILLSPECTOR_REASONING_EFFORT=high` to select the same model and effort for +SkillSpector. Both settings pass through the Docker sandbox. The A.I.G model +uses `DEFAULT_MODEL`; its optional `REASONING_EFFORT` setting also passes +through, but requires an upstream version that supports it. The bundled +A.I.G 0.2.2 runtime does not yet support reasoning effort. + ### Build a custom profile with `.clawscan.yml` Custom profiles can be created in `.clawscan.yml`. diff --git a/cmd/clawscan/main_test.go b/cmd/clawscan/main_test.go index 93b21d7..0debdc6 100644 --- a/cmd/clawscan/main_test.go +++ b/cmd/clawscan/main_test.go @@ -505,7 +505,7 @@ func TestRunCommandScannerDetailPrintsHumanReadableInfo(t *testing.T) { "Repository: https://github.com/Tencent/AI-Infra-Guard/tree/main/skill-scan", "Description: Tencent Zhuque Lab's local directory scanner invoked through aig-skill-scan", "Required env vars: LLM_API_KEY", - "Optional env vars: OPENAI_API_KEY, DEFAULT_MODEL, DEFAULT_BASE_URL, DEFAULT_MODEL_CONTEXT_WINDOW, LOG_LEVEL", + "Optional env vars: OPENAI_API_KEY, DEFAULT_MODEL, REASONING_EFFORT, DEFAULT_BASE_URL, DEFAULT_MODEL_CONTEXT_WINDOW, LOG_LEVEL", "Install:", "pip install aig-skill-scan", } { diff --git a/docs/profiles.md b/docs/profiles.md index 8cde270..1892f3f 100644 --- a/docs/profiles.md +++ b/docs/profiles.md @@ -56,9 +56,17 @@ clawscan profiles -v | Profile | Scanners | Judge | | --- | --- | --- | -| `clawhub` | `skillspector`, `clawscan-static`, `aig` | Codex `gpt-5.5`, high reasoning, bundled ClawHub prompt/schema | +| `clawhub` | `skillspector`, `clawscan-static`, `aig` | Codex `gpt-6-luna`, high reasoning, bundled ClawHub prompt/schema | +| `clawhub-aig` | `skillspector`, `aig` | Codex `gpt-6-luna`, high reasoning, bundled ClawHub prompt/schema | | `openclaw-install-policy` | `skillspector`, `clawscan-static` | none | +Set `SKILLSPECTOR_MODEL=gpt-6-luna` and +`SKILLSPECTOR_REASONING_EFFORT=high` to select the same model and effort for +SkillSpector. Both settings pass through the Docker sandbox. The A.I.G model +uses `DEFAULT_MODEL`; its optional `REASONING_EFFORT` setting also passes +through, but requires an upstream version that supports it. The bundled +A.I.G 0.2.2 runtime does not yet support reasoning effort. + ## Build a custom profile with `.clawscan.yml` Custom profiles can be created in `.clawscan.yml`. diff --git a/docs/scanners.md b/docs/scanners.md index ba310c0..61fc933 100644 --- a/docs/scanners.md +++ b/docs/scanners.md @@ -256,7 +256,7 @@ commas, single-quoted strings, and unquoted keys. | `cisco` | Cisco AI Defense skill-scanner | [repo](https://github.com/cisco-ai-defense/skill-scanner) | Local file or directory scanner invoked through `skill-scanner` with JSON report output. Optional upstream env vars enable LLM, VirusTotal, and Cisco AI Defense analyzers. | none
Optional configSKILL_SCANNER_LLM_API_KEY, SKILL_SCANNER_LLM_PROVIDER, SKILL_SCANNER_LLM_MODEL, SKILL_SCANNER_LLM_BASE_URL, SKILL_SCANNER_LLM_USER, SKILL_SCANNER_LLM_API_VERSION, SKILL_SCANNER_LLM_FORCE_JSON_OBJECT, SKILL_SCANNER_META_LLM_API_KEY, SKILL_SCANNER_META_LLM_MODEL, SKILL_SCANNER_META_LLM_BASE_URL, SKILL_SCANNER_META_LLM_API_VERSION, AWS_PROFILE, AWS_REGION, GOOGLE_APPLICATION_CREDENTIALS, VIRUSTOTAL_API_KEY, AI_DEFENSE_API_KEY, AI_DEFENSE_API_URL.
| `uv pip install cisco-ai-skill-scanner` | | `clawscan-static` | ClawScan Static | [repo](https://github.com/openclaw/clawscan) | Built-in deterministic scanner for high-signal risky skill and OpenClaw plugin patterns; packaged Python bytecode and NUL-obfuscated text are flagged and inspected, while opaque binary omissions remain visible as low-severity evidence. | none | skipped; built in | | `relyable` | Relyable | [repo](https://github.com/veriker/relyable) | Functional re-derivation evidence: does the skill still do what its docs claim, recomputed? Emits the strongest grade that applies. `exogenous`: a declared `rederive.json` property manifest (idempotence / round-trip), with both sides of the relation computed from the skill's own code and the result mutation-tested against vacuity. `self_spec`: re-runs the author's own committed oracle (shipped tests or documented I/O examples). `cold_golden`: when an LLM key is set, a code-blind model infers goldens from SKILL.md alone and abstains unless the docs pin exact behavior; divergences are reported as unconfirmed, never as accusations. `non_rederivable`: the honest floor, never a fabricated pass. Functional axis only; complements the security scanners and does not detect malware or prompt injection. Skill code runs only inside the Docker sandbox (or with an explicit opt-in), in a scrubbed environment, and the scanner fails closed otherwise. Not preinstalled in the `clawscan-runtime` image. | none
Optional configRELYABLE_SCAN_ALLOW_HOST_EXEC — explicit ack that the host is disposable when running with --sandbox off.

RELYABLE_LLM_API_KEY (+ RELYABLE_LLM_PROVIDER anthropic|openai, RELYABLE_LLM_MODEL, RELYABLE_LLM_BASE_URL) — explicit per-scanner opt-in that enables the cold_golden lane; key presence only is ever recorded in the payload. Generic ANTHROPIC_API_KEY/OPENAI_API_KEY are honored by standalone relyable-scan but are deliberately not auto-forwarded by ClawScan.
| `clawscan install relyable` — not preinstalled in the runtime image | -| `skillspector` | NVIDIA SkillSpector | [repo](https://github.com/NVIDIA/skillspector) | Local skill or OpenClaw plugin file/directory scanner. Uses LLM mode when provider env vars are set; otherwise runs with `--no-llm`. | none
Optional configSKILLSPECTOR_PROVIDER, SKILLSPECTOR_MODEL, SKILLSPECTOR_MODEL_REGISTRY, SKILLSPECTOR_LOG_LEVEL, SKILLSPECTOR_SSL_VERIFY, NVIDIA_INFERENCE_KEY, OPENAI_API_KEY, OPENAI_BASE_URL, ANTHROPIC_API_KEY, ANTHROPIC_PROXY_ENDPOINT_URL, ANTHROPIC_PROXY_API_KEY, ANTHROPIC_PROXY_API_VERSION.
| `uv tool install git+https://github.com/NVIDIA/skillspector.git` | +| `skillspector` | NVIDIA SkillSpector | [repo](https://github.com/NVIDIA/skillspector) | Local skill or OpenClaw plugin file/directory scanner. Uses LLM mode when provider env vars are set; otherwise runs with `--no-llm`. | none
Optional configSKILLSPECTOR_PROVIDER, SKILLSPECTOR_MODEL, SKILLSPECTOR_REASONING_EFFORT, SKILLSPECTOR_MODEL_REGISTRY, SKILLSPECTOR_LOG_LEVEL, SKILLSPECTOR_SSL_VERIFY, NVIDIA_INFERENCE_KEY, OPENAI_API_KEY, OPENAI_BASE_URL, ANTHROPIC_API_KEY, ANTHROPIC_PROXY_ENDPOINT_URL, ANTHROPIC_PROXY_API_KEY, ANTHROPIC_PROXY_API_VERSION.
| `uv tool install git+https://github.com/NVIDIA/skillspector.git` | | `snyk` | Snyk Agent Scan | [repo](https://github.com/snyk/agent-scan) | Local skill scanner invoked through `uvx snyk-agent-scan`. | `SNYK_TOKEN` | verifies `uvx` launcher | | `socket` | Socket CLI | [repo](https://github.com/SocketDev/socket-cli) | Local file or directory scanner using Socket's public CLI full-scan path. | `SOCKET_CLI_API_TOKEN` | `npm install -g socket` | | `virustotal` | VirusTotal API | [docs](https://docs.virustotal.com/reference/file) | API-backed local file hash lookup. Skill and OpenClaw plugin directories are scanned as deterministic ZIP archives. | `VIRUSTOTAL_API_KEY` | skipped; API-backed | diff --git a/internal/profiles/clawhub/clawscan.yml b/internal/profiles/clawhub/clawscan.yml index e4bd6a9..2597aee 100644 --- a/internal/profiles/clawhub/clawscan.yml +++ b/internal/profiles/clawhub/clawscan.yml @@ -11,14 +11,17 @@ profiles: - OPENAI_API_KEY - CODEX_API_KEY - SKILLSPECTOR_PROVIDER + - SKILLSPECTOR_MODEL + - SKILLSPECTOR_REASONING_EFFORT - LLM_API_KEY - DEFAULT_MODEL + - REASONING_EFFORT - DEFAULT_BASE_URL judge: command: >- [ -n "$CODEX_API_KEY" ] || export CODEX_API_KEY="$OPENAI_API_KEY"; codex exec --cd {{ workspace }} - --model gpt-5.5 + --model gpt-6-luna --sandbox {{ judge_sandbox }} --skip-git-repo-check --ignore-user-config @@ -41,14 +44,17 @@ profiles: - OPENAI_API_KEY - CODEX_API_KEY - SKILLSPECTOR_PROVIDER + - SKILLSPECTOR_MODEL + - SKILLSPECTOR_REASONING_EFFORT - LLM_API_KEY - DEFAULT_MODEL + - REASONING_EFFORT - DEFAULT_BASE_URL judge: command: >- [ -n "$CODEX_API_KEY" ] || export CODEX_API_KEY="$OPENAI_API_KEY"; codex exec --cd {{ workspace }} - --model gpt-5.5 + --model gpt-6-luna --sandbox {{ judge_sandbox }} --skip-git-repo-check --ignore-user-config diff --git a/internal/profiles/resolver_test.go b/internal/profiles/resolver_test.go index 02c863d..c232a1a 100644 --- a/internal/profiles/resolver_test.go +++ b/internal/profiles/resolver_test.go @@ -66,7 +66,7 @@ func TestResolveArgsUsesEmbeddedClawHubProfile(t *testing.T) { if string(opts.Judge.Files["clawhub/output.schema.json"]) == "" { t.Fatal("expected embedded clawhub output schema file") } - if got := strings.Join(opts.Sandbox.Env, ","); got != "OPENAI_API_KEY,CODEX_API_KEY,SKILLSPECTOR_PROVIDER,LLM_API_KEY,DEFAULT_MODEL,DEFAULT_BASE_URL" { + if got := strings.Join(opts.Sandbox.Env, ","); got != "OPENAI_API_KEY,CODEX_API_KEY,SKILLSPECTOR_PROVIDER,SKILLSPECTOR_MODEL,SKILLSPECTOR_REASONING_EFFORT,LLM_API_KEY,DEFAULT_MODEL,REASONING_EFFORT,DEFAULT_BASE_URL" { t.Fatalf("sandbox env = %q", got) } } @@ -100,22 +100,28 @@ func TestResolveArgsUsesEmbeddedClawHubAIGCandidateProfile(t *testing.T) { t.Fatalf("candidate judge file %s differs from clawhub", path) } } - if got := strings.Join(candidate.Sandbox.Env, ","); got != "OPENAI_API_KEY,CODEX_API_KEY,SKILLSPECTOR_PROVIDER,LLM_API_KEY,DEFAULT_MODEL,DEFAULT_BASE_URL" { + if got := strings.Join(candidate.Sandbox.Env, ","); got != "OPENAI_API_KEY,CODEX_API_KEY,SKILLSPECTOR_PROVIDER,SKILLSPECTOR_MODEL,SKILLSPECTOR_REASONING_EFFORT,LLM_API_KEY,DEFAULT_MODEL,REASONING_EFFORT,DEFAULT_BASE_URL" { t.Fatalf("sandbox env = %q", got) } } -func TestClawHubProfilesAliasOpenAIKeyForCodex(t *testing.T) { +func TestClawHubProfilesConfigureCodex(t *testing.T) { for _, profile := range []string{"clawhub", "clawhub-aig"} { t.Run(profile, func(t *testing.T) { opts, err := ResolveArgs([]string{"./skill", "--profile", profile}, t.TempDir()) if err != nil { t.Fatal(err) } - prefix, _, ok := strings.Cut(opts.Judge.Command, "codex exec") - if !ok { + if !strings.Contains(opts.Judge.Command, "codex exec") { t.Fatalf("judge command missing codex exec: %q", opts.Judge.Command) } + command := strings.NewReplacer( + "{{ workspace }}", "fixture-workspace", + "{{ judge_sandbox }}", "read-only", + "{{ output_schema:clawhub/output.schema.json }}", "fixture-schema", + "{{ output }}", "fixture-output", + "{{ prompt:clawhub/prompt.md }}", "/dev/null", + ).Replace(opts.Judge.Command) for _, test := range []struct { name string @@ -127,7 +133,7 @@ func TestClawHubProfilesAliasOpenAIKeyForCodex(t *testing.T) { {name: "explicit codex key wins", openAI: "openai-marker", codex: "codex-marker", output: "codex-marker"}, } { t.Run(test.name, func(t *testing.T) { - cmd := exec.Command("sh", "-c", prefix+`sh -c 'printf %s "$CODEX_API_KEY"'`) + cmd := exec.Command("sh", "-c", `codex() { printf '%s\n' "$CODEX_API_KEY" "$@"; }; `+command) cmd.Env = append( os.Environ(), strings.Join([]string{"OPENAI_API_KEY", test.openAI}, "="), @@ -137,8 +143,15 @@ func TestClawHubProfilesAliasOpenAIKeyForCodex(t *testing.T) { if err != nil { t.Fatal(err) } - if got := string(output); got != test.output { - t.Fatalf("CODEX_API_KEY = %q, want %q", got, test.output) + lines := strings.Split(strings.TrimSpace(string(output)), "\n") + if lines[0] != test.output { + t.Fatalf("CODEX_API_KEY = %q, want %q", lines[0], test.output) + } + args := strings.Join(lines[1:], "\x00") + for _, want := range []string{"--model\x00gpt-6-luna\x00", "-c\x00model_reasoning_effort=high\x00"} { + if !strings.Contains(args, want) { + t.Fatalf("codex arguments missing %q: %#v", want, lines[1:]) + } } }) } @@ -693,7 +706,7 @@ func TestResolveArgsAppliesCLIOverrides(t *testing.T) { if opts.Sandbox.Image != "ghcr.io/acme/runtime:v1" { t.Fatalf("sandbox image = %q", opts.Sandbox.Image) } - if got := strings.Join(opts.Sandbox.Env, ","); got != "OPENAI_API_KEY,CODEX_API_KEY,SKILLSPECTOR_PROVIDER,LLM_API_KEY,DEFAULT_MODEL,DEFAULT_BASE_URL,ANTHROPIC_API_KEY" { + if got := strings.Join(opts.Sandbox.Env, ","); got != "OPENAI_API_KEY,CODEX_API_KEY,SKILLSPECTOR_PROVIDER,SKILLSPECTOR_MODEL,SKILLSPECTOR_REASONING_EFFORT,LLM_API_KEY,DEFAULT_MODEL,REASONING_EFFORT,DEFAULT_BASE_URL,ANTHROPIC_API_KEY" { t.Fatalf("sandbox env = %q", got) } } diff --git a/internal/runner/aig_scanner_test.go b/internal/runner/aig_scanner_test.go index 253a3f7..aca7a65 100644 --- a/internal/runner/aig_scanner_test.go +++ b/internal/runner/aig_scanner_test.go @@ -149,7 +149,11 @@ func TestAIGScannerDockerRunMountsTargetAndOutputDirectory(t *testing.T) { t.Fatal(err) } artifact, err := Run(opts, RunContext{ - Env: map[string]string{"LLM_API_KEY": "present"}, + Env: map[string]string{ + "LLM_API_KEY": "present", + "DEFAULT_MODEL": "gpt-6-luna", + "REASONING_EFFORT": "high", + }, HostCommandRunner: hostRunner, DockerAvailability: func() error { return nil }, }) @@ -166,6 +170,11 @@ func TestAIGScannerDockerRunMountsTargetAndOutputDirectory(t *testing.T) { if call.command != "docker" { t.Fatalf("command = %q", call.command) } + for _, name := range []string{"LLM_API_KEY", "DEFAULT_MODEL", "REASONING_EFFORT"} { + if !containsArgPair(call.args, "-e", name) { + t.Fatalf("docker args missing env %q: %#v", name, call.args) + } + } outputPath := argValue(call.args, "-o") outputDir := filepath.Dir(outputPath) if !containsArgPair(call.args, "-w", outputDir) { diff --git a/internal/runner/scanner_registry.go b/internal/runner/scanner_registry.go index 1cce62a..16e7f26 100644 --- a/internal/runner/scanner_registry.go +++ b/internal/runner/scanner_registry.go @@ -229,6 +229,7 @@ func defaultScannerAdapters() []ScannerAdapter { OptionalEnv: []string{ "OPENAI_API_KEY", "DEFAULT_MODEL", + "REASONING_EFFORT", "DEFAULT_BASE_URL", "DEFAULT_MODEL_CONTEXT_WINDOW", "LOG_LEVEL", @@ -341,6 +342,7 @@ func defaultScannerAdapters() []ScannerAdapter { OptionalEnv: []string{ "SKILLSPECTOR_PROVIDER", "SKILLSPECTOR_MODEL", + "SKILLSPECTOR_REASONING_EFFORT", "SKILLSPECTOR_MODEL_REGISTRY", "SKILLSPECTOR_LOG_LEVEL", "SKILLSPECTOR_SSL_VERIFY", diff --git a/internal/runner/scanner_registry_test.go b/internal/runner/scanner_registry_test.go index 96bc35c..c1f8a60 100644 --- a/internal/runner/scanner_registry_test.go +++ b/internal/runner/scanner_registry_test.go @@ -91,6 +91,33 @@ func TestDefaultScannerRegistryContainsAllBuiltIns(t *testing.T) { } } +func TestSkillSpectorModelSettingsReachDocker(t *testing.T) { + env := map[string]string{ + "SKILLSPECTOR_MODEL": "gpt-6-luna", + "SKILLSPECTOR_REASONING_EFFORT": "high", + } + opts := Options{Scanners: []string{"skillspector"}} + host := &recordingCommandRunner{} + commandRunner := dockerCommandRunner{ + Host: host, Env: env, Image: DefaultSandboxImage, + EnvNames: sandboxEnvNames(opts, env), + } + if _, err := commandRunner.Run("skillspector", []string{"scan", "fixture"}, "", 0); err != nil { + t.Fatal(err) + } + if len(host.calls) != 1 || host.calls[0].command != "docker" { + t.Fatalf("calls = %#v", host.calls) + } + for name := range env { + if !containsArgPair(host.calls[0].args, "-e", name) { + t.Fatalf("docker args missing env %q: %#v", name, host.calls[0].args) + } + } + if got := sandboxEnvNames(opts, map[string]string{}); len(got) != 0 { + t.Fatalf("unset optional env should not be forwarded: %#v", got) + } +} + func TestScannerAdaptersDeclareTargetKindSupport(t *testing.T) { registry := DefaultScannerRegistry() for _, id := range registry.IDs() { @@ -340,7 +367,7 @@ func TestDefaultScannerRegistryProvidesCatalogInfo(t *testing.T) { } skillspector, _ := registry.Info("skillspector") - if got := strings.Join(skillspector.OptionalEnv, ","); got != "SKILLSPECTOR_PROVIDER,SKILLSPECTOR_MODEL,SKILLSPECTOR_MODEL_REGISTRY,SKILLSPECTOR_LOG_LEVEL,SKILLSPECTOR_SSL_VERIFY,NVIDIA_INFERENCE_KEY,OPENAI_API_KEY,OPENAI_BASE_URL,ANTHROPIC_API_KEY,ANTHROPIC_PROXY_ENDPOINT_URL,ANTHROPIC_PROXY_API_KEY,ANTHROPIC_PROXY_API_VERSION" { + if got := strings.Join(skillspector.OptionalEnv, ","); got != "SKILLSPECTOR_PROVIDER,SKILLSPECTOR_MODEL,SKILLSPECTOR_REASONING_EFFORT,SKILLSPECTOR_MODEL_REGISTRY,SKILLSPECTOR_LOG_LEVEL,SKILLSPECTOR_SSL_VERIFY,NVIDIA_INFERENCE_KEY,OPENAI_API_KEY,OPENAI_BASE_URL,ANTHROPIC_API_KEY,ANTHROPIC_PROXY_ENDPOINT_URL,ANTHROPIC_PROXY_API_KEY,ANTHROPIC_PROXY_API_VERSION" { t.Fatalf("skillspector optional env = %q", got) } @@ -358,7 +385,7 @@ func TestDefaultScannerRegistryProvidesCatalogInfo(t *testing.T) { if got := strings.Join(aig.RequiredEnv, ","); got != "LLM_API_KEY" { t.Fatalf("aig required env = %q", got) } - if got := strings.Join(aig.OptionalEnv, ","); got != "OPENAI_API_KEY,DEFAULT_MODEL,DEFAULT_BASE_URL,DEFAULT_MODEL_CONTEXT_WINDOW,LOG_LEVEL" { + if got := strings.Join(aig.OptionalEnv, ","); got != "OPENAI_API_KEY,DEFAULT_MODEL,REASONING_EFFORT,DEFAULT_BASE_URL,DEFAULT_MODEL_CONTEXT_WINDOW,LOG_LEVEL" { t.Fatalf("aig optional env = %q", got) } if aig.InstallHint != "pip install aig-skill-scan" {