Skip to content

feat(security): close the process-sink gaps Bandit cross-check found - #69

Merged
Rinkia merged 1 commit into
masterfrom
feat/security-process-sinks
Jul 23, 2026
Merged

Rinkia merged 1 commit into
masterfrom
feat/security-process-sinks

Conversation

@Rinkia

@Rinkia Rinkia commented Jul 23, 2026

Copy link
Copy Markdown
Owner

What

Closes #61, driven by a non-circular cross-check against Bandit (an external authority we don't control) — the M3 validation work.

The cross-check

Ran both tools on click / pygments / sqlalchemy, comparing only within the scope the lens claims (code execution, deserialization, process) — Bandit's weak-crypto/assert/SQL tests are excluded because the lens explicitly doesn't cover them.

package agree bandit-only lens-only recall
click 7 2 1 7/9
pygments 4 0 0 4/4
sqlalchemy 14 0 39 14/14
  • 1 of the 2 misses was a comparison artifact — Bandit attributes a multi-line subprocess.Popen to line 533; we report the call's start at 531. Same finding.
  • 1 was a real gap: os.startfile wasn't catalogued.
  • The 40 "lens-only" are not false positives — they're __reduce__ (×35) and builtin compile, which Bandit has no tests for. We cover more here, not less.

The fix

  • New process sinks: os.popen, os.startfile, the os.exec*/os.spawn*/posix_spawn family, commands.getoutput — split the way Bandit splits B605 from B606: shell-invoking calls rate high, no-shell launches rate medium.
  • shell=True elevates to high (B602 vs B603). Only a literal True counts — a variable could be either, and guessing would inflate severity on evidence the parser doesn't have. With the gate's fail-on: high, a new shell=True now fails a PR while an ordinary subprocess.run still doesn't.

After the fix: click 7/9 → 8/9, and the only remaining difference is the line-attribution artifact.

Documented wrinkle

Both sides of the gate are scanned with the same build, so a release that adds catalogue entries can report sinks that were always in the code — new to the detector, not the codebase. Noted in the Action header + CHANGELOG; modscan-version is the mitigation. Verified this PR doesn't trip it: no newly-catalogued sink exists in modscan/ itself.

Test plan

  • tests/test_security_sinks.py: shell=True → high, plain call → medium, non-literal shell=flag → not elevated; new process sinks present; os.path.join still not a sink.
  • Cross-check re-run confirms the gap closed.
  • Suite 24/24, benchmark 9/20, moddability untouched, YAML valid.

🤖 Generated with Claude Code

Cross-checked the security lens against Bandit on click/pygments/sqlalchemy —
an external authority we do not control, so the check is non-circular. Within
the scope the lens claims (code execution, deserialization, process spawning) it
matched 25 of 27 Bandit findings. One of the two misses was a comparison
artifact (Bandit attributes a multi-line call to a continuation line; we use the
call's start). The other was a real gap: os.startfile was not catalogued.

Closes that gap and the neighbouring ones: os.popen, os.startfile, the
os.exec*/os.spawn*/posix_spawn family, and commands.getoutput. They are split
the way Bandit splits B605 from B606 — handing a string to a shell (os.system,
os.popen) rates high; launching a program without one rates medium.

Also elevates a subprocess.* call passing a literal shell=True to high, the
B602/B603 distinction. Only a literal True counts: a variable could be either,
and guessing would inflate severity on evidence the parser does not have. With
the gate's fail-on: high default, a newly-introduced shell=True now fails a PR
while an ordinary subprocess.run still does not.

Re-running the cross-check after the fix: click 7/9 -> 8/9, and the single
remaining difference is the known line-attribution artifact.

Documents a real wrinkle this exposes: because both sides of the gate are scanned
with the same build, a release that adds catalogue entries can report sinks that
were always in the code. Pin the Action's modscan-version to control that.

Moddability core untouched; suite 24/24; benchmark unchanged at 9/20.

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

Copy link
Copy Markdown

MODScan attack-surface diff — modscan

No new execution sinks

New attack surface to review — NOT a vulnerability verdict. This compares enumerated execution sinks between two snapshots. It does not trace whether input reaches them (no taint analysis), match CVEs, or detect secrets. An empty diff means no new catalogued sinks — not that the change is safe.

The two snapshots enumerate the same execution sinks.

@github-actions

Copy link
Copy Markdown

MODScan extension-point diff

No breaking changes to extension points

@Rinkia
Rinkia merged commit 325bb3a into master Jul 23, 2026
7 checks passed
@Rinkia
Rinkia deleted the feat/security-process-sinks branch July 23, 2026 13:47
@Rinkia Rinkia mentioned this pull request Jul 23, 2026
3 of 5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security lens: elevate shell=True and add os.popen/os.exec* sinks

1 participant