Skip to content

feat: expand security scanning with language-scoped tools #38

@bdfinst

Description

@bdfinst

Spec: Expanded Security Scanning

Intent Description

The static-analysis-integration skill's Tier 1 baseline (semgrep, gitleaks, trivy, hadolint, actionlint) covers general polyglot SAST and CI hygiene but leaves three gaps: deep program-analysis dataflow (CodeQL), language-specific security linting (Bandit/Python, ESLint-security/JS+TS, SpotBugs+FSB/JVM), and offline-capable SCA (Grype). Trivy's existing invocation makes network calls that fail in restricted-egress environments.

This change adds nine tools in a capability-scoped, language-detected pipeline: each tool runs only when its target language or asset type is present, preventing false install requirements in polyglot repos. Gitleaks is updated to run with --no-verify (no outbound API calls to confirm active credentials). Trivy's network behavior is tightened to local-DB-only. Checkov fills the IaC gap more completely than trivy-config alone, running alongside it on Dockerfiles and covering Terraform, CloudFormation, and Kubernetes declarative configs.

All new tools normalize to the existing unified finding envelope v1.0 and extend the deduplication priority chain. The combined output remains a single findings array — the scanner count increases but the output contract does not change.


User-Facing Behavior

Feature: Expanded security scanning with language detection

  Background:
    Given the static-analysis-integration skill is invoked

  # ── Gitleaks (updated) ─────────────────────────────────────────────────────
  Scenario: Gitleaks runs with --no-verify flag
    Given gitleaks is installed
    When the skill runs
    Then gitleaks detects --report-format sarif --report-path - --no-verify
    And no outbound API calls are made to verify detected secrets

  # ── CodeQL ────────────────────────────────────────────────────────────────
  Scenario: CodeQL runs once per detected compiled language
    Given the repository contains both Java and Kotlin source files
    And CodeQL is installed
    When the skill detects available tools
    Then CodeQL creates one database per language using the appropriate build command
    And analysis runs against the default query suite for each language in parallel
    And findings from all language runs are merged and normalized to the unified finding envelope v1.0

  Scenario: CodeQL is skipped when no compiled language files are present
    Given the repository contains only JavaScript and Python files
    When the skill runs
    Then CodeQL is skipped silently with no entry in tools_missing

  Scenario: CodeQL database build fails for one language
    Given the repository contains Java and Kotlin source files
    And the Kotlin build command exits non-zero
    When the skill runs
    Then CodeQL is skipped for Kotlin with a warning: "CodeQL database build failed — kotlin skipped"
    And CodeQL continues for Java
    And all other tools continue running

  Scenario: CodeQL is not installed
    Given CodeQL is not on PATH
    And the repository contains Java source files
    When the skill runs
    Then CodeQL produces status: skip
    And an install hint is included in tools_missing

  # ── Bandit ────────────────────────────────────────────────────────────────
  Scenario: Bandit runs when Python source files are present
    Given the repository contains .py source files
    And bandit is installed
    When the skill runs
    Then Bandit scans all Python files
    And findings are normalized to the unified finding envelope v1.0

  Scenario: Bandit is skipped when no Python files are present
    Given the repository contains no .py files
    When the skill runs
    Then Bandit is skipped silently with no entry in tools_missing

  Scenario: Bandit is not installed but Python files are present
    Given the repository contains .py source files
    And bandit is not installed
    When the skill runs
    Then an install hint for bandit appears in tools_missing

  # ── ESLint + eslint-plugin-security ───────────────────────────────────────
  Scenario: ESLint security plugin runs when JS or TS source files are present
    Given the repository contains .js or .ts source files
    And eslint is installed
    And eslint-plugin-security is present in node_modules
    When the skill runs
    Then ESLint runs with the security plugin ruleset
    And findings are normalized to the unified finding envelope v1.0

  Scenario: ESLint security is skipped when no JS or TS files are present
    Given the repository contains no .js or .ts files
    When the skill runs
    Then ESLint security is skipped silently with no entry in tools_missing

  Scenario: eslint-plugin-security is missing even though eslint is installed
    Given eslint is installed
    But eslint-plugin-security is not present in node_modules
    And the repository contains .ts source files
    When the skill runs
    Then an install hint for eslint-plugin-security appears in tools_missing
    And ESLint is skipped for this invocation

  Scenario: ESLint is not installed but JS/TS files are present
    Given the repository contains .ts source files
    And eslint is not installed
    When the skill runs
    Then an install hint for eslint + eslint-plugin-security appears in tools_missing

  # ── SpotBugs + Find Security Bugs ─────────────────────────────────────────
  Scenario: SpotBugs runs when compiled JVM bytecode is available
    Given the repository contains Java or Kotlin source files
    And a build has produced .class files or a JAR artifact
    And SpotBugs with the FSB plugin is installed
    When the skill runs
    Then SpotBugs with FSB runs against the bytecode
    And findings are normalized to the unified finding envelope v1.0

  Scenario: SpotBugs triggers a build when no bytecode exists
    Given the repository contains Java source files
    And no compiled bytecode or JAR exists
    And SpotBugs with the FSB plugin is installed
    When the skill runs
    Then the skill triggers the detected build tool (mvn or gradle)
    And SpotBugs runs against the resulting bytecode

  Scenario: SpotBugs is skipped when build fails
    Given the repository contains Java source files
    And no compiled bytecode exists
    And the build command exits non-zero
    When the skill runs
    Then SpotBugs is skipped
    And a warning is emitted: "SpotBugs skipped — build failed for JVM analysis"

  Scenario: SpotBugs is not installed but JVM files are present
    Given the repository contains .java source files
    And spotbugs is not on PATH
    When the skill runs
    Then an install hint for SpotBugs + Find Security Bugs appears in tools_missing

  # ── Checkov ───────────────────────────────────────────────────────────────
  Scenario: Checkov scans IaC assets when present
    Given the repository contains Terraform, CloudFormation, Kubernetes, or Dockerfile assets
    And checkov is installed
    When the skill runs
    Then Checkov scans all detected IaC asset types
    And findings are normalized to the unified finding envelope v1.0

  Scenario: Checkov and trivy-config both scan Dockerfiles
    Given the repository contains a Dockerfile
    And both checkov and trivy are installed
    When the skill runs
    Then both Checkov and trivy-config scan the Dockerfile
    And findings from both tools are merged and deduplicated before output
    And trivy takes priority over checkov for duplicate findings

  Scenario: Checkov is skipped when no IaC assets are present
    Given the repository contains no Terraform, CloudFormation, Kubernetes, or Dockerfile files
    When the skill runs
    Then Checkov is skipped silently with no entry in tools_missing

  Scenario: Checkov is not installed but IaC assets are present
    Given the repository contains a Dockerfile
    And checkov is not installed
    When the skill runs
    Then an install hint for checkov appears in tools_missing

  # ── Trivy offline ─────────────────────────────────────────────────────────
  Scenario: Trivy runs in offline mode when a local DB is present
    Given trivy is installed
    And a local vulnerability database exists at the expected cache path
    When the skill runs
    Then Trivy runs with --skip-update and --offline-scan flags
    And no network calls are made during the scan

  Scenario: Trivy warns and skips when local DB is absent
    Given trivy is installed
    But no local vulnerability database exists at the expected cache path
    When the skill runs
    Then Trivy is skipped
    And a warning is emitted: "trivy local DB missing — run: trivy image --download-db-only"

  Scenario: Trivy warns when local DB is stale
    Given trivy is installed
    And the local vulnerability database is more than 7 days old
    When the skill runs
    Then Trivy runs but emits a warning: "trivy DB is N days old — consider refreshing with: trivy image --download-db-only"

  # ── Grype ─────────────────────────────────────────────────────────────────
  Scenario: Grype runs against dependency manifests when present
    Given the repository contains package.json, requirements.txt, pom.xml, or go.mod
    And grype is installed with a locally synced vulnerability database
    When the skill runs
    Then Grype scans all detected dependency manifests
    And findings are normalized to the unified finding envelope v1.0
    And no network calls are made during the scan

  Scenario: Grype warns and skips when local DB is absent
    Given grype is installed
    But grype db status shows the database is not present
    When the skill runs
    Then Grype is skipped
    And a warning is emitted: "grype local DB missing — run: grype db update"

  Scenario: Grype warns when local DB is stale
    Given grype is installed
    And the local vulnerability database is more than 7 days old
    When the skill runs
    Then Grype runs but emits a warning: "grype DB is N days old — consider refreshing with: grype db update"

  Scenario: Grype is skipped when no dependency manifests are present
    Given the repository contains no recognized dependency manifests
    When the skill runs
    Then Grype is skipped silently with no entry in tools_missing

  # ── Deduplication ─────────────────────────────────────────────────────────
  Scenario: Duplicate findings across tools are deduplicated by priority
    Given both Semgrep and Bandit report a finding at the same file and line
    When findings are normalized and deduplicated
    Then only one finding appears in the output
    And it is attributed to semgrep (higher priority)

  Scenario: Extended priority chain is respected for CodeQL vs Semgrep
    Given CodeQL and Semgrep both report a finding at the same file and line
    When findings are deduplicated
    Then the CodeQL finding is retained
    And the Semgrep finding is discarded

  # ── Output contract unchanged ─────────────────────────────────────────────
  Scenario: All tool findings appear in a single normalized output
    Given multiple new tools run successfully
    When the skill completes
    Then the output schema is unchanged from unified finding envelope v1.0
    And a summary counts findings by tool and severity
    And tools_missing lists each absent conditional tool with a capability label and install hint

Architecture Specification

Files to change

  • plugins/agentic-dev-team/skills/static-analysis-integration/SKILL.md — tool tiers, language detection gate, offline enforcement, deduplication chain
  • plugins/agentic-dev-team/skills/static-analysis-integration/references/tool-configs.md — per-tool invocation commands, adapter references, install hints
  • plugins/agentic-dev-team/skills/static-analysis-integration/adapters/ — three new bespoke adapters (≤ 40 LOC each)

New adapters

File Tool Input Notes
bandit-adapter.py Bandit Bandit JSON bandit -r <path> -f json
eslint-security-adapter.py ESLint ESLint JSON eslint --format json
spotbugs-adapter.py SpotBugs + FSB SpotBugs XML spotbugs -xml

Tool tier placement

Tool Tier SARIF? Condition
Gitleaks (updated) 1 native always; add --no-verify
CodeQL 2 native .java, .kt, .c, .cpp, .cs, .swift present
Bandit 2 bespoke adapter .py present
ESLint + eslint-plugin-security 2 (promoted from Tier 4) bespoke adapter .js/.ts present + plugin in node_modules
SpotBugs + FSB 2 bespoke adapter .java/.kt present
Checkov 2 native (--output sarif) Terraform/CFn/K8s/Dockerfile present
Trivy (updated) 1 native always; enforce --skip-update --offline-scan
Grype 2 native package.json/requirements.txt/pom.xml/go.mod present

Language detection gate
Each conditional tool checks for target file types via find before dispatch. No tool runs if no matching files exist. Detection runs once per invocation and is shared across tools that overlap (e.g., Java detection covers both CodeQL and SpotBugs).

CodeQL per-language DB strategy
For each detected compiled language, codeql database create --language=<lang> --command=<build> runs independently. Multiple language runs execute in parallel. Build command defaults: mvn clean package -DskipTests (Maven), gradle build -x test (Gradle), dotnet build (C#). Unknown build systems surface a warning and skip that language.

SpotBugs build trigger
If .class files or JARs are absent, the skill detects the build tool (pom.xml → Maven, build.gradle → Gradle) and triggers a build. Build failure skips SpotBugs with a warning; it does not fail the pipeline.

ESLint detection
command -v eslint checks binary presence. Plugin presence is checked via node -e "require('eslint-plugin-security')" from the repo root. Both must pass or ESLint is skipped.

Offline DB enforcement (Trivy, Grype)
Pre-flight check before each run: confirm DB path exists and mtime < 7 days. DB absence → skip + warning. DB stale → run + warning. Neither is a hard pipeline failure.

Deduplication priority chain (extended)

semgrep > codeql > gitleaks > bandit > eslint-security > spotbugs > trivy > checkov > grype > hadolint > actionlint

No output schema changes. All tools normalize to unified finding envelope v1.0. tools_missing entries follow the existing install-hint format.


Acceptance Criteria

  1. Each conditional tool runs only when its target language or asset type is detected — verified by a test with a repo containing only non-matching files.
  2. All new tools produce findings conforming to unified finding envelope v1.0; schema violations fail the run with tool name + rule id.
  3. Missing tools produce install hints in the existing format; absent tools never cause pipeline failure.
  4. Gitleaks invocation includes --no-verify; no outbound network calls are made during secret scanning.
  5. CodeQL creates one database per detected compiled language; a build failure for one language skips that language and continues others.
  6. SpotBugs triggers a build when bytecode is absent; build failure surfaces a warning and skips SpotBugs without failing the pipeline.
  7. ESLint security is skipped if eslint-plugin-security is not in node_modules, even when eslint is installed.
  8. Trivy runs with --skip-update --offline-scan; absence of local DB produces a warning containing "trivy local DB missing".
  9. Grype makes zero network calls during scan execution; absence of local DB produces a warning containing "grype local DB missing".
  10. Stale DB (>7 days) for Trivy or Grype produces a warning with day count but does not skip the tool.
  11. Checkov and trivy-config both run on Dockerfiles; duplicate findings are deduplicated with trivy taking priority.
  12. Deduplication respects the extended priority chain: semgrep > codeql > gitleaks > bandit > eslint-security > spotbugs > trivy > checkov > grype > hadolint > actionlint.
  13. Three new bespoke adapters (Bandit, ESLint-security, SpotBugs) are each ≤ 40 LOC.
  14. All Tier 1 tool behavior (semgrep, trivy IaC, hadolint, actionlint) is unchanged except Gitleaks (--no-verify) and Trivy (offline enforcement).
  15. tools_missing lists conditional tools that are absent only when their target language/asset is present in the repo.

Consistency Gate

  • Intent is unambiguous — two developers would interpret it the same way
  • Every behavior in the intent has at least one BDD scenario
  • Architecture constrains implementation to what the intent requires without over-engineering
  • Terminology is consistent across all four artifacts
  • No artifact contradicts another

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions