Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
cbc6900
fix(analyzer): flag settings keys that run commands or reroute traffi…
deepujain Sep 26, 2026
c9b05e2
fix(patterns): detect constructed sensitive paths (#545)
deepujain Sep 28, 2026
29c58a4
fix(discovery): report skipped symlinked skills (#499)
deepujain Sep 28, 2026
0c0cba4
fix: strip leading BOM before SKILL.md frontmatter detection (#471)
MohammedAlkindi Sep 28, 2026
6f5c175
fix: detect bidi control characters outside markdown files (CVE-2021-…
MohammedAlkindi Sep 28, 2026
7438b48
Merge pull request #595 from bniladridas/bniladridas/symlink-omission
rng1995 Sep 28, 2026
fe95403
Merge pull request #661 from NVIDIA/naren/fix-ae1-missing-reference-s…
rng1995 Sep 29, 2026
83aca7a
Merge pull request #634 from deepujain/fix/628-shell-assignment-quote…
rng1995 Sep 29, 2026
380997d
Merge pull request #686 from NVIDIA/naren/fix-parameter-operator-pars…
rng1995 Sep 30, 2026
16ca512
Merge pull request #637 from drakeo338/claude/633-baseline-every-occu…
rng1995 Oct 2, 2026
2d0c385
Merge pull request #655 from udsy19/fix/node-options-require-short-flag
rng1995 Oct 2, 2026
f4ede2f
Merge pull request #700 from Harbor404/fix/sc2-stdin-consumer-false-p…
rng1995 Oct 4, 2026
a4adfcc
Merge pull request #702 from Harbor404/fix/meta-review-finding-binding
rng1995 Oct 4, 2026
ee2323c
Merge pull request #601 from efegokdemir/codex/issue-585-declaration-…
rng1995 Oct 4, 2026
41308a9
Merge pull request #632 from deepujain/fix/629-plugin-json-hooks
rng1995 Oct 5, 2026
473ef7a
Merge pull request #627 from NVIDIA/codex/fix-prose-shell-parser-context
rng1995 Oct 5, 2026
04f713f
Merge pull request #623 from NVIDIA/codex/fix-missing-python-ast-columns
rng1995 Oct 5, 2026
a0a0f6c
Merge pull request #593 from udsy19/fix/yara-reverse-shell-multiline
rng1995 Oct 5, 2026
671347f
Merge pull request #740 from NVIDIA/yashraj/fix-xor-key-regex-backtra…
rng1995 Oct 5, 2026
8a71faf
Merge pull request #744 from NVIDIA/yashraj/restrict-mcp-http-loopback
rng1995 Oct 5, 2026
a12ac8d
Merge pull request #746 from NVIDIA/yashraj/pin-release-checkout
rng1995 Oct 5, 2026
fdc6eae
Merge pull request #729 from YMuskrat/fix/prevent-input-overwrite
rng1995 Oct 5, 2026
2f14287
Merge pull request #733 from NVIDIA/yashraj/fix-batch-markdown-output…
rng1995 Oct 5, 2026
5dd32db
Merge pull request #611 from udsy19/fix/taint-source-order
rng1995 Oct 5, 2026
55a177b
Merge pull request #735 from NVIDIA/yashraj/fix-markdown-report-outpu…
rng1995 Oct 6, 2026
260a24f
Merge pull request #736 from NVIDIA/yashraj/isolate-http-mcp-git-cred…
rng1995 Oct 6, 2026
ffc6bbf
Merge pull request #737 from NVIDIA/yashraj/disable-unsafe-codex-prov…
rng1995 Oct 6, 2026
f4c8815
Merge pull request #748 from NVIDIA/yashraj/refresh-nvidia-build-default
rng1995 Oct 6, 2026
f7ba248
Merge pull request #738 from NVIDIA/yashraj/allowlist-agent-cli-envir…
rng1995 Oct 6, 2026
56488eb
Merge pull request #745 from NVIDIA/yashraj/redact-short-opencode-cre…
rng1995 Oct 6, 2026
4fcb695
Merge pull request #734 from NVIDIA/yashraj/fix-batch-terminal-output…
rng1995 Oct 6, 2026
f95b8cd
Merge pull request #739 from NVIDIA/yashraj/fix-pi-redaction-runtime-…
rng1995 Oct 6, 2026
168e840
fix(static): decode provable printf escapes in shell literals (#704)
Harbor404 Oct 6, 2026
c48064f
fix: prefer tool output for OpenAI Opus 5 TP4 (#691)
chrisknvidia Oct 8, 2026
9279b1e
fix(analyzer): detect bound shell truthiness (#577)
chrisknvidia Oct 8, 2026
de72c52
fix(static_yara): surface dropped rule files instead of reporting com…
Souptik96 Oct 8, 2026
b5e2da8
fix(analyzer): stabilize TM1 window identity (#578)
chrisknvidia Oct 9, 2026
a716989
fix(baseline): preserve exact findings and protect baseline output (#…
chrisknvidia Oct 9, 2026
2998a19
feat(python): classify executable source surfaces (#579)
chrisknvidia Oct 9, 2026
95d2375
fix(taint): detect inline urllib opener network sinks (#721)
agentsope Oct 9, 2026
ea80c62
Merge pull request #791 from NVIDIA/yashraj/fix-pi-input-permissions-…
mohgupta-ship-it Oct 9, 2026
5332960
Merge pull request #792 from NVIDIA/yashraj/isolate-transitive-conten…
mohgupta-ship-it Oct 9, 2026
4934863
fix(static): detect shell decode-and-execute chains (#786)
rajusem Oct 13, 2026
0edacd6
fix: preserve malformed LLM responses as incomplete analysis (#796)
yashrajp22 Oct 13, 2026
d1d5118
Merge pull request #799 from NVIDIA/naren/fix-tt3-function-scope
rng1995 Oct 14, 2026
165e1b5
Merge pull request #802 from efegokdemir/codex/skill-800-scope-exclus…
rng1995 Oct 14, 2026
0c60498
Merge pull request #758 from YMuskrat/ymuskrat/registry-output-guard
rng1995 Oct 14, 2026
6b2dd8c
Merge pull request #767 from NVIDIA/yashraj/release-checkout-credenti…
rng1995 Oct 14, 2026
c0c86b9
fix(release): complete structured-output backport prerequisites
yashrajp22 Oct 14, 2026
998ba92
fix(baseline): keep shared-file updates atomic
yashrajp22 Oct 14, 2026
057bd1e
release: prepare selected v2.13.0 security and blocking fixes
yashrajp22 Oct 14, 2026
35b2939
test(release): align regression mocks with retained synchronous TP4
yashrajp22 Oct 14, 2026
1c5359c
docs(release): describe the selected registry output guard
yashrajp22 Oct 14, 2026
eef7605
fix(release): keep static verification independent of LLM providers
yashrajp22 Oct 14, 2026
3656d1b
fix: bound TypeScript declaration recognition
yashrajp22 Oct 14, 2026
352a36f
docs: clarify v2.13.0 scope and verification
yashrajp22 Oct 14, 2026
e4a17cd
Merge pull request #725 from YMuskrat/fix/terminal-literal-text
yashrajp22 Oct 15, 2026
2633b23
Merge pull request #793 from NVIDIA/yashraj/reserve-archive-path-name…
yashrajp22 Oct 15, 2026
f44a4ac
docs(release): include terminal and archive collision fixes
yashrajp22 Oct 15, 2026
76da6a1
test(release): match archive checks to supported exclusion policy
yashrajp22 Oct 15, 2026
fa44071
style(tests): format release archive parameter cases
yashrajp22 Oct 15, 2026
2783520
Merge pull request #844 from NVIDIA/yashraj/fix-release-scan-regressi…
mohgupta-ship-it Oct 16, 2026
8ebd201
Merge pull request #795 from NVIDIA/yashraj/scan-unverified-oms-content
rng1995 Oct 16, 2026
92799a7
Merge pull request #828 from NVIDIA/naren/fix-sc8-thread-time-budget
yashrajp22 Oct 16, 2026
a44f6f3
Merge pull request #794 from NVIDIA/yashraj/fix-printable-magic-evasi…
yashrajp22 Oct 16, 2026
e0c568f
docs(release): include final scan hardening and runtime fixes
yashrajp22 Oct 16, 2026
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
8 changes: 5 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ ENV=dev # options: dev|s
# metadata, and default-model lookups. Leave unset to default to nv_build.
# Options: openai | anthropic | anthropic_proxy | bedrock | nv_build |
# ollama | azure_openai | openai_compatible | claude_cli |
# codex_cli | gemini_cli | opencode_cli
# gemini_cli | opencode_cli
# codex_cli is registered but disabled: its read-only sandbox permits host-file reads.
SKILLSPECTOR_PROVIDER=

# Aggregate deadline for one complete scan workflow. Defaults to 600 seconds;
Expand All @@ -28,7 +29,8 @@ NVIDIA_INFERENCE_KEY=
OPENAI_API_KEY=
OPENAI_BASE_URL=
# Optional provider- and model-dependent reasoning-effort setting. Non-empty values
# are trimmed and passed through unchanged; unset or blank uses the provider default.
# are trimmed and passed through unchanged. Unset or blank sends high for nv_build
# with z-ai/glm-5.3; other provider/model combinations keep their endpoint defaults.
SKILLSPECTOR_REASONING_EFFORT=
# Optional language for human-readable LLM finding text. Machine-readable values
# such as rule IDs and severity values remain unchanged.
Expand Down Expand Up @@ -67,7 +69,7 @@ AZURE_OPENAI_ENDPOINT=
SKILLSPECTOR_COMPAT_API_KEY=
SKILLSPECTOR_COMPAT_BASE_URL=

# claude_cli, codex_cli, gemini_cli, and opencode_cli use their CLI's existing local
# claude_cli, gemini_cli, and opencode_cli use their CLI's existing local
# authentication session and do not need an API key here.

# SkillSpector config
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ name: CI

on:
pull_request:
branches: ["main"]
branches: ["main", "yashraj/v2.13.0-base"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should revert?

push:
branches: ["main"]
branches: ["main", "yashraj/v2.13.0-base"]

# Least privilege: these jobs only read the repo; no write scopes are needed.
permissions:
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ name: Publish GitHub Release

on:
pull_request:
branches: ["main"]
branches: ["main", "yashraj/v2.13.0-base"]
types: [closed]

permissions:
Expand All @@ -30,12 +30,13 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
persist-credentials: false
- name: Set up uv
# Pinned to a full commit SHA (third-party action); comment tracks the tag.
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
with:
version: "${{ env.UV_VERSION }}"
enable-cache: true
Expand Down
101 changes: 85 additions & 16 deletions .opencode/tools/skillspector_scan_lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,94 @@ export function truncate(text: string, max: number): string {
export function redact(text: string, env: Env = process.env): string {
const credentialValues = [...new Set(
CREDENTIAL_ENV_NAMES.map((name) => env[name]?.trim()).filter(
(value): value is string => Boolean(value && value.length >= 4),
(value): value is string => Boolean(value),
),
)].sort((left, right) => right.length - left.length)
const credentialPattern = credentialValues.length
? new RegExp(
credentialValues
.map((value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
.join("|"),
"g",
)
const valuePatterns = credentialValues.map((value) => {
const literal = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
// Short values are usually local-provider placeholders. Treat them as
// tokens so they cannot change words, JSON fields, or longer credentials.
return value.length < 4 ? `(?<![A-Za-z0-9_-])${literal}(?![A-Za-z0-9_-])` : literal
})
// Look ahead so overlapping configured values are all covered. Matching
// still advances through the original text, never through replacement markers.
const credentialPattern = valuePatterns.length
? new RegExp(`(?=(${valuePatterns.join("|")}))`, "g")
: undefined
const redacted = credentialPattern ? text.replace(credentialPattern, "[REDACTED]") : text
return redacted
.replace(/sk-ant-[A-Za-z0-9_-]+/g, "[REDACTED]")
.replace(/\bsk-[A-Za-z0-9_-]{6,}\b/g, "[REDACTED]")
.replace(
/\b([A-Z][A-Z0-9_]*(?:API_KEY|TOKEN|ACCESS_KEY_ID|SECRET_ACCESS_KEY|INFERENCE_KEY))(\s*[:=]\s*["']?)[^"'\s,}]+/g,
"$1$2[REDACTED]",
)
// A full configured credential can act as the separator before another key.
// Match on the original text so overlapping values never split that key.
// Short values cannot end here unless their existing token boundary allows it.
const longValues = valuePatterns.filter((_, index) => credentialValues[index].length >= 4)
const keyBoundary = longValues.length ? `(?:\\b|(?<=${longValues.join("|")}))` : "\\b"
const keyPattern = new RegExp([
"sk-ant-[A-Za-z0-9_-]+",
`${keyBoundary}sk-[A-Za-z0-9_-]{6,}\\b`,
// Consume each uppercase name once, even when it is not an assignment.
// Otherwise a known value like AAAA permits a costly retry at every A.
`${keyBoundary}([A-Z][A-Z0-9_]*)`,
].join("|"), "g")
const assignmentName = /[A-Z0-9_](?:API_KEY|TOKEN|ACCESS_KEY_ID|SECRET_ACCESS_KEY|INFERENCE_KEY)$/
const assignmentValue = /(\s*[:=]\s*["']?)[^"'\s,}]+/y
const scrub = (value: string): string => {
function* spans(pattern: RegExp | undefined, literal: boolean): Generator<[number, number]> {
if (!pattern) return
for (const match of value.matchAll(pattern)) {
if (!literal && match[1]) {
if (!assignmentName.test(match[1])) continue
assignmentValue.lastIndex = match.index + match[0].length
const assigned = assignmentValue.exec(value)
if (assigned) {
yield [assigned.index + assigned[1].length, assigned.index + assigned[0].length]
}
} else {
yield [match.index, match.index + (literal ? match[1].length : match[0].length)]
}
}
}
const known = spans(credentialPattern, true)
const shaped = spans(keyPattern, false)
let nextKnown = known.next().value
let nextShaped = shaped.next().value
let cursor = 0
let start = -1
let end = -1
const parts: string[] = []
// Merge two ordered span streams in linear time. A shorter key/assignment
// match cannot expose the tail of a longer configured credential.
while (nextKnown || nextShaped) {
let span: [number, number]
if (nextKnown && (!nextShaped || nextKnown[0] <= nextShaped[0])) {
span = nextKnown
nextKnown = known.next().value
} else {
span = nextShaped!
nextShaped = shaped.next().value
}
if (span[0] > end) {
if (start >= 0) {
parts.push(value.slice(cursor, start), "[REDACTED]")
cursor = end
}
start = span[0]
}
end = Math.max(end, span[1])
}
if (start >= 0) {
parts.push(value.slice(cursor, start), "[REDACTED]")
cursor = end
}
parts.push(value.slice(cursor))
return parts.join("")
}
try {
const report = JSON.parse(text)
if (report === null || typeof report !== "object") return scrub(text)
// Preserve JSON numbers and booleans, including a score equal to a short
// placeholder. Credentials inside JSON strings still receive full scrubbing.
return text.replace(/"(?:[^"\\]|\\.)*"/g, (token) => JSON.stringify(scrub(JSON.parse(token))))
} catch {
return scrub(text)
}
}

export function isScpGitTarget(target: string): boolean {
Expand Down
63 changes: 63 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,66 @@
### 2.13.0 (Unreleased)
### Security and blocking fixes
* Selected release based on v2.12.0; includes 55 PRs, with #743 included through #734.
* Reads SKILL.md metadata correctly when the file starts with an invisible encoding marker. (#471)
* Detects text-direction control characters that can disguise code in bundled scripts, not just Markdown. (#473)
* Marks recursive scan coverage as incomplete when linked skill folders are deliberately skipped. (#499)
* Detects access to sensitive files even when their paths are assembled from fixed strings. (#545)
* Marks analysis incomplete when custom YARA rules cannot be loaded or compiled. (#557)
* Preserves dangerous shell-command findings when deeper Python analysis cannot confidently replace them. (#577)
* Keeps distinct shell-command findings separate, even when their displayed snippets look identical. (#578)
* Recognizes more executable Python files, including .pyw files and extensionless launchers. (#579)
* Adds lower-confidence warnings for multiline scripts that appear to open a remote shell, while preserving existing one-line detections. (#593)
* Shows how many linked skill folders were skipped and includes that count in recursive scan coverage. (#595)
* Checks complete TypeScript declaration files before treating them as non-executable, so partial or disguised runtime code is not skipped. (#601)
* Flags bundled settings that run commands, redirect AI traffic, or automatically approve connected tool servers. (#604)
* Finds Python flows from secret reads to risky uses even when the assignments and uses appear at different nesting depths. (#611)
* Keeps Python findings when parser location columns are missing, instead of crashing or merging distinct findings together. (#623)
* Recognizes more ordinary Markdown sentences and lists so their apostrophes do not cause false shell-parsing and incomplete-scan warnings. (#627)
* Inspects hooks declared in Claude plugin manifests and reports incomplete coverage when those declarations cannot be fully checked. (#632)
* Parses quoted shell assignments correctly so harmless trailing text does not falsely make a scan incomplete. (#634)
* Makes saved baselines cover every occurrence of a repeated finding, so later scans suppress them consistently. (#637)
* Detects bundled Node settings that use the short -r flag to load and run extra code. (#655)
* Preserves exact findings in baselines and protects existing baseline files during regeneration. (#657)
* Avoids misleading high-severity warnings on SKILL.md self-references while still reporting missing or ambiguous files. (#661)
* Accepts ordinary shell variable defaults and assignment values without falsely marking their files as partly scanned. (#686)
* Improves TP4 responses for the specific OpenAI-provider Opus 5 route and rejects missing, malformed, or contradictory tool results. (#691)
* Lowers alerts for simple download pipelines that demonstrably parse incoming data rather than execute it. (#700)
* Attaches AI review decisions to the correct findings and distinguishes disagreement from missing or failed reviews. (#702)
* Recognizes safely decodable shell printf escapes without incorrectly marking analysis incomplete. (#704)
* Detects sensitive data passed to urllib opener requests, including inline opener calls. (#721)
* Displays untrusted names, filenames and findings literally in terminal reports, preventing markup crashes and spoofing. (#725)
* Rejects scan output that points to the input file, including symbolic or hard links, preventing accidental overwrites. (#729)
* Prevents untrusted names and findings from inserting fake headings, rows or HTML into batch Markdown reports. (#733)
* Displays untrusted skill names and errors literally in batch terminals so markup and control characters cannot spoof the output. (#734)
* Prevents untrusted finding text and filenames from changing Markdown report structure, using shared protection for core and batch reports. (#735)
* Stops remote MCP scans from using the server’s Git credentials to clone private repositories while preserving trusted local Git access. (#736)
* Disables the Codex CLI analysis provider because its read-only sandbox still permits host-secret reads, directing users to another provider. (#737)
* Limits environment variables passed to agent CLI providers so unexpected secret variables are not forwarded to child processes. (#738)
* Prevents long scanner output from blocking Pi’s secret redaction while still hiding secrets before the displayed output is shortened. (#739)
* Prevents crafted Python literals and unfinished function headers from causing extremely slow scans. (#740)
* Enforces per-skill batch timeouts by stopping stuck workers while other scans continue. (#743)
* Limits unauthenticated MCP HTTP access to the local machine; remote access requires an authenticating proxy. (#744)
* Redacts short configured secrets from OpenCode’s returned output without corrupting JSON values; separately written report files remain outside this redactor. (#745)
* Pins the release workflow’s checkout action to a fixed commit so a moving version tag cannot silently change the code it runs. (#746)
* Switches NVIDIA Build’s default from retired GLM-5.2 to GLM-5.3 while preserving explicit model overrides. (#748)
* Prevents registry reports from overwriting their input files. (#758)
* Keeps the release GitHub token out of Git configuration during dependency installation and builds. (#767)
* Detects encoded shell payloads that are decoded and executed. (#786)
* Requires approval for external Pi scan inputs and blocks file-reading YARA includes. (#791)
* Separates local and referenced-content caches so baseline suppression cannot use the wrong file. (#792)
* Keeps real disk content separate from archive members with colliding paths and reports failed coverage for ambiguous paths. (#793)
* Inspects readable instructions hidden behind binary prefixes while preserving benign PDF/GIF and XMP handling and explicit limits for referenced or executable binaries. (#794)
* Inspects untrusted OMS signing metadata and decoded payloads, preserving raw evidence and incomplete coverage on parsing failures. (#795)
* Reports malformed LLM responses as incomplete analysis instead of successful empty results. (#796)
* Tracks sensitive-data flow by function scope, avoiding collisions between unrelated variable names. (#799)
* Keeps intentionally excluded files separate from failed analyzer work in coverage reports. (#802)
* Preserve atomic baseline updates and refuse updates that cannot retain ownership or group safely.
* Keep static-only scans independent of inactive LLM provider settings while retaining strict provider checks for LLM-enabled scans.
* Bound TypeScript declaration matching so malformed files cannot stall scans; timed-out checks leave the files eligible for executable analysis.
* Counts only the bytecode walk’s own CPU time toward its processing limit, while retaining the overall scan deadline. Waiting for other analyzers no longer makes clean scans incomplete. (#828)
* Avoids execution warnings for simple decode-to-checksum pipelines and preserves disk-versus-archive error locations when paths collide. (#844)
* Enable CI and label-gated publishing for the selected v2.13.0 release branch.
---
### 2.12.0 (Unreleased)
### Features/Bug Fixes
* fix(security): retain incomplete coverage for runtime-selected commands and remeasure active Git clones strictly (#514)
Expand Down
Loading
Loading