Compare commits

..
Author SHA1 Message Date
Sharang ParnerkarandClaude Opus 4.8 9c8f864a1d docs(control-mapping): MCP emission loop + flags now default-on
CI / Check (push) Skipped
CI / Check (pull_request) Successful in 5m47s
CI / Detect Changes (pull_request) Skipped
CI / Deploy Agent (pull_request) Skipped
CI / Deploy Dashboard (pull_request) Skipped
CI / Deploy Docs (pull_request) Skipped
CI / Deploy MCP (pull_request) Skipped
- Add 'Emitting over MCP' section: the oscal_assessment tool, breakpilot's
  /v1/cra/oscal-from-scanner pull, and the tenant-context operational note
  (task_local lost across rmcp's session spawn -> bind tenant to the session).
- Configuration: semantic + grounded passes are now default-on (validated live),
  still no-op without BREAKPILOT_BASE_URL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 13:14:59 +02:00
12 changed files with 34 additions and 101 deletions
+11 -11
View File
@@ -72,7 +72,7 @@ jobs:
echo '[source.crates-io]'
echo 'replace-with = "kellnr"'
echo '[registries.kellnr]'
echo 'index = "sparse+https://crates.breakpilot.com/api/v1/cratesio/"'
echo 'index = "sparse+https://crates.meghsakha.com/api/v1/cratesio/"'
} >> "$CARGO_HOME/config.toml"
env:
RUSTC_WRAPPER: ""
@@ -94,8 +94,8 @@ jobs:
- name: Configure git auth for private tramiton dependency
run: |
git config --global \
url."https://sharang:${{ secrets.TRAMITON_FETCH_TOKEN }}@git.breakpilot.com/".insteadOf \
"ssh://git@git.breakpilot.com:22222/"
url."https://sharang:${{ secrets.TRAMITON_FETCH_TOKEN }}@gitea.meghsakha.com/".insteadOf \
"ssh://git@gitea.meghsakha.com:22222/"
env:
RUSTC_WRAPPER: ""
@@ -213,8 +213,8 @@ jobs:
apk add --no-cache git curl openssl
git init && git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git fetch --depth=1 origin "${GITHUB_SHA}" && git checkout FETCH_HEAD
IMAGE=repo.breakpilot.com/certifai/compliance-agent
echo "$REGISTRY_PASSWORD" | docker login repo.breakpilot.com -u "$REGISTRY_USERNAME" --password-stdin
IMAGE=repo.meghsakha.com/certifai/compliance-agent
echo "$REGISTRY_PASSWORD" | docker login repo.meghsakha.com -u "$REGISTRY_USERNAME" --password-stdin
DOCKER_BUILDKIT=1 docker build --secret id=tramiton_token,env=TRAMITON_FETCH_TOKEN \
-f Dockerfile.agent -t "$IMAGE:latest" -t "$IMAGE:${GITHUB_SHA}" .
docker push "$IMAGE:latest" && docker push "$IMAGE:${GITHUB_SHA}"
@@ -240,8 +240,8 @@ jobs:
apk add --no-cache git curl openssl
git init && git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git fetch --depth=1 origin "${GITHUB_SHA}" && git checkout FETCH_HEAD
IMAGE=repo.breakpilot.com/certifai/compliance-dashboard
echo "$REGISTRY_PASSWORD" | docker login repo.breakpilot.com -u "$REGISTRY_USERNAME" --password-stdin
IMAGE=repo.meghsakha.com/certifai/compliance-dashboard
echo "$REGISTRY_PASSWORD" | docker login repo.meghsakha.com -u "$REGISTRY_USERNAME" --password-stdin
DOCKER_BUILDKIT=1 docker build --secret id=tramiton_token,env=TRAMITON_FETCH_TOKEN \
-f Dockerfile.dashboard -t "$IMAGE:latest" -t "$IMAGE:${GITHUB_SHA}" .
docker push "$IMAGE:latest" && docker push "$IMAGE:${GITHUB_SHA}"
@@ -265,8 +265,8 @@ jobs:
apk add --no-cache git curl openssl
git init && git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git fetch --depth=1 origin "${GITHUB_SHA}" && git checkout FETCH_HEAD
IMAGE=repo.breakpilot.com/certifai/compliance-docs
echo "$REGISTRY_PASSWORD" | docker login repo.breakpilot.com -u "$REGISTRY_USERNAME" --password-stdin
IMAGE=repo.meghsakha.com/certifai/compliance-docs
echo "$REGISTRY_PASSWORD" | docker login repo.meghsakha.com -u "$REGISTRY_USERNAME" --password-stdin
docker build -f Dockerfile.docs -t "$IMAGE:latest" -t "$IMAGE:${GITHUB_SHA}" .
docker push "$IMAGE:latest" && docker push "$IMAGE:${GITHUB_SHA}"
{ command -v cosign >/dev/null 2>&1 || curl -sSfLo /usr/local/bin/cosign https://github.com/sigstore/cosign/releases/download/v2.4.3/cosign-linux-amd64 || wget -qO /usr/local/bin/cosign https://github.com/sigstore/cosign/releases/download/v2.4.3/cosign-linux-amd64; } || echo "::warning::cosign fetch failed"
@@ -291,8 +291,8 @@ jobs:
apk add --no-cache git curl openssl
git init && git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git fetch --depth=1 origin "${GITHUB_SHA}" && git checkout FETCH_HEAD
IMAGE=repo.breakpilot.com/certifai/compliance-mcp
echo "$REGISTRY_PASSWORD" | docker login repo.breakpilot.com -u "$REGISTRY_USERNAME" --password-stdin
IMAGE=repo.meghsakha.com/certifai/compliance-mcp
echo "$REGISTRY_PASSWORD" | docker login repo.meghsakha.com -u "$REGISTRY_USERNAME" --password-stdin
DOCKER_BUILDKIT=1 docker build --secret id=tramiton_token,env=TRAMITON_FETCH_TOKEN \
-f Dockerfile.mcp -t "$IMAGE:latest" -t "$IMAGE:${GITHUB_SHA}" .
docker push "$IMAGE:latest" && docker push "$IMAGE:${GITHUB_SHA}"
+2 -2
View File
@@ -8,8 +8,8 @@ COPY . .
RUN --mount=type=secret,id=tramiton_token \
if [ -s /run/secrets/tramiton_token ]; then \
git config --global \
url."https://sharang:$(cat /run/secrets/tramiton_token)@git.breakpilot.com/".insteadOf \
"ssh://git@git.breakpilot.com:22222/"; \
url."https://sharang:$(cat /run/secrets/tramiton_token)@gitea.meghsakha.com/".insteadOf \
"ssh://git@gitea.meghsakha.com:22222/"; \
fi && \
CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release -p compliance-agent
+2 -2
View File
@@ -13,8 +13,8 @@ ENV DOCS_URL=${DOCS_URL}
RUN --mount=type=secret,id=tramiton_token \
if [ -s /run/secrets/tramiton_token ]; then \
git config --global \
url."https://sharang:$(cat /run/secrets/tramiton_token)@git.breakpilot.com/".insteadOf \
"ssh://git@git.breakpilot.com:22222/"; \
url."https://sharang:$(cat /run/secrets/tramiton_token)@gitea.meghsakha.com/".insteadOf \
"ssh://git@gitea.meghsakha.com:22222/"; \
fi && \
CARGO_NET_GIT_FETCH_WITH_CLI=true dx build --release --package compliance-dashboard
+2 -2
View File
@@ -8,8 +8,8 @@ COPY . .
RUN --mount=type=secret,id=tramiton_token \
if [ -s /run/secrets/tramiton_token ]; then \
git config --global \
url."https://sharang:$(cat /run/secrets/tramiton_token)@git.breakpilot.com/".insteadOf \
"ssh://git@git.breakpilot.com:22222/"; \
url."https://sharang:$(cat /run/secrets/tramiton_token)@gitea.meghsakha.com/".insteadOf \
"ssh://git@gitea.meghsakha.com:22222/"; \
fi && \
CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release -p compliance-mcp
+2 -55
View File
@@ -277,10 +277,8 @@ impl PipelineOrchestrator {
}
}
// Dedup against existing findings: insert first-seen ones, and refresh the
// control mappings on ones we've seen before.
// Dedup against existing findings and insert new ones
let mut new_count = 0u32;
let mut refreshed_count = 0u32;
let mut new_findings: Vec<Finding> = Vec::new();
for mut finding in all_findings {
finding.scan_run_id = Some(scan_run_id.to_string());
@@ -295,25 +293,8 @@ impl PipelineOrchestrator {
finding.id = result.inserted_id.as_object_id();
new_findings.push(finding);
new_count += 1;
} else if !finding.control_refs.is_empty() {
// Re-scan refresh: a mapping pass (newly enabled or tuned) computed
// control_refs for a finding first seen before mapping ran. Persist
// them onto the existing row — the insert path alone never would.
self.db
.findings()
.update_one(
doc! { "fingerprint": &finding.fingerprint },
doc! { "$set": { "control_refs": finding.control_refs.clone() } },
)
.await?;
refreshed_count += 1;
}
}
if refreshed_count > 0 {
tracing::info!(
"[{repo_id}] Refreshed control_refs on {refreshed_count} existing findings"
);
}
// Remove stale SBOM entries for this repo before reinserting
if !sbom_entries.is_empty() {
@@ -586,21 +567,7 @@ impl PipelineOrchestrator {
let Some(path) = ingest_set.get(&a.id).and_then(|ia| ia.working_path.clone()) else {
continue;
};
let mut source_findings = crate::pipeline::plc::analyze_tree(&path, target_id);
// Control mapping for the PLC path (run_plc_scan is separate from
// run_pipeline, which does its own mapping). PLC findings carry
// file_path/line/cwe, so the semantic pass reads each region under this
// source's `path` and stamps master-control refs. The LUT + grounded
// surface passes are code-pattern / CRA-specific and don't apply to
// IEC 61131-3 control logic, so only the semantic pass runs here.
crate::controls::semantic_stamp_findings(
&self.config,
self.llm.clone(),
&path,
&mut source_findings,
)
.await;
all_findings.extend(source_findings);
all_findings.extend(crate::pipeline::plc::analyze_tree(&path, target_id));
// Control-application SBOM: CODESYS libraries + runtime from a
// `.projectarchive` (uploaded, or committed in the working tree).
let archive = a
@@ -625,7 +592,6 @@ impl PipelineOrchestrator {
);
let mut new_count = 0u32;
let mut refreshed_count = 0u32;
for mut finding in all_findings {
finding.scan_run_id = Some(scan_run_id.to_string());
if self
@@ -637,27 +603,8 @@ impl PipelineOrchestrator {
{
self.db.findings().insert_one(&finding).await?;
new_count += 1;
} else if !finding.control_refs.is_empty() {
// Re-scan refresh: mirror run_pipeline — persist newly-computed
// control_refs onto a PLC finding first seen before the semantic
// pass ran. The insert path alone never would, so without this a
// PLC re-scan can only pick up mappings via a delete + re-add.
self.db
.findings()
.update_one(
doc! { "fingerprint": &finding.fingerprint },
doc! { "$set": { "control_refs": finding.control_refs.clone() } },
)
.await?;
refreshed_count += 1;
}
}
if refreshed_count > 0 {
tracing::info!(
target_id,
"Refreshed control_refs on {refreshed_count} existing PLC findings"
);
}
if !all_sbom.is_empty() {
if let Err(e) = self
+2 -2
View File
@@ -6,7 +6,7 @@ Control mapping connects the scanner's raw output — deterministic tool finding
The design has one rule, borrowed from the ZeroFalse / IRIS line of research: **deterministic tools are the detectors; the LLM is only ever a grounded false-positive filter, never the thing that finds the issue.**
- A tool (semgrep, gitleaks, syft/osv, the PLC linter; the DAST agents today, with **Nuclei and ZAP planned** as deterministic web/OT detectors underneath them — see [Tools & Scanners](/reference/tools#planned-integrations-decided-2026-08-31-not-yet-in-the-code)) detects.
- A tool (semgrep, gitleaks, syft/osv, ZAP, nuclei) detects deterministically.
- An **authored, human-reviewed lookup table** (`control-map`) maps that detection to the control(s) it's evidence for.
- The LLM enters last, to *confirm or refute* the mapping against the actual code — and every surviving verdict is anchored to a verbatim snippet by the grounding gate.
@@ -34,7 +34,7 @@ At scale, the **master-controls** corpus (breakpilot's deduped clusters, exporte
```mermaid
flowchart TD
T[Deterministic tools\nsemgrep · gitleaks · syft/osv · DAST · PLC linter] --> F[Findings]
T[Deterministic tools\nsemgrep · gitleaks · syft/osv · ZAP] --> F[Findings]
F --> B["Stage 5b — LUT triage\ncontrols_for(tool, cwe / rule_id)"]
F --> C["Stage 5c — Semantic\nembed region+intent → top-K master controls"]
R[Repo source] --> D["Stage 5d — Grounded surface\nretrieve surface for absence-based controls"]
-4
View File
@@ -92,7 +92,3 @@ Filters can be combined. A count indicator shows how many findings match the cur
::: tip
Findings marked as **Confirmed** exploitable were verified with a successful attack payload. **Unconfirmed** findings show suspicious behavior that may indicate a vulnerability but could not be fully exploited.
:::
## Deterministic detectors (planned)
The DAST engine above is agentic: an LLM drives crawler, browser and testing tools and decides what to try next. That gives depth and code-aware exploitation, but not run-to-run reproducibility. The next step (decided 2026-08-31, not yet implemented) adds two deterministic open-source detectors **under** the agents: **Nuclei** (template checks incl. ICS/OT and default-credential templates) first, then an **OWASP ZAP** baseline scan. Their findings will appear alongside agent findings, carry CWE + compliance `control_refs`, and seed the agent's context so it verifies and chains instead of rediscovering. See [Tools & Scanners](/reference/tools#planned-integrations-decided-2026-08-31-not-yet-in-the-code).
+2 -2
View File
@@ -26,7 +26,7 @@ Filters can be combined. Results are paginated with 20 findings per page.
| Severity | Color-coded badge: Critical (red), High (orange), Medium (yellow), Low (green), Info (blue) |
| Title | Short description of the vulnerability (clickable) |
| Type | SAST, SBOM, CVE, GDPR, OAuth, Secrets, or Code Review |
| Scanner | Tool that found the issue (e.g. Semgrep, Syft/OSV) |
| Scanner | Tool that found the issue (e.g. Semgrep, Grype) |
| File | Source file path where the issue was found |
| Status | Current triage status |
@@ -73,7 +73,7 @@ If the finding has been pushed to an issue tracker (GitHub, GitLab, Gitea, Jira)
| Type | Source | Description |
|------|--------|-------------|
| **SAST** | Semgrep | Code-level vulnerabilities found through static analysis |
| **SBOM** | Syft + OSV.dev/NVD | Vulnerable dependencies identified in your software bill of materials |
| **SBOM** | Syft + Grype | Vulnerable dependencies identified in your software bill of materials |
| **CVE** | NVD | Known CVEs matching your dependency versions |
| **GDPR** | Custom rules | Personal data handling and consent issues |
| **OAuth** | Custom rules | OAuth/OIDC misconfigurations and insecure token handling |
+1 -1
View File
@@ -6,7 +6,7 @@ The SBOM (Software Bill of Materials) feature provides a complete inventory of a
A Software Bill of Materials is a list of every component (library, package, framework) that your software depends on, along with version numbers, licenses, and known vulnerabilities. SBOMs are increasingly required for compliance audits, customer security questionnaires, and supply chain transparency.
Certifai generates SBOMs automatically during each scan using Syft for dependency extraction and OSV.dev + NVD for vulnerability matching.
Certifai generates SBOMs automatically during each scan using Syft for dependency extraction and Grype for vulnerability matching.
## Packages Tab
+2 -2
View File
@@ -8,7 +8,7 @@ When a scan is triggered, Certifai runs through these phases in order:
1. **Clone** -- pulls the latest code from the Git remote (or clones it for the first time)
2. **SAST** -- runs static analysis using Semgrep with rules covering OWASP, GDPR, OAuth, secrets, and general security patterns
3. **SBOM** -- extracts all dependencies using Syft, identifying packages, versions, licenses, and known vulnerabilities via OSV.dev + NVD
3. **SBOM** -- extracts all dependencies using Syft, identifying packages, versions, licenses, and known vulnerabilities via Grype
4. **CVE Check** -- cross-references dependencies against the NVD database for known CVEs
5. **Graph Build** -- parses the codebase to construct a code knowledge graph of functions, classes, and their relationships
6. **AI Triage** -- new findings are reviewed by an LLM that assesses severity, considers blast radius using the code graph, and generates remediation guidance
@@ -52,7 +52,7 @@ A full scan runs multiple analysis engines, each producing different types of fi
| Scan Type | What It Detects | Scanner |
|-----------|----------------|---------|
| **SAST** | Code-level vulnerabilities (injection, XSS, insecure crypto, etc.) | Semgrep |
| **SBOM** | Dependency inventory, outdated packages, known vulnerabilities | Syft + OSV.dev/NVD |
| **SBOM** | Dependency inventory, outdated packages, known vulnerabilities | Syft + Grype |
| **CVE** | Known CVEs in dependencies cross-referenced against NVD | NVD API |
| **GDPR** | Personal data handling issues, consent violations | Custom rules |
| **OAuth** | OAuth/OIDC misconfigurations, insecure token handling | Custom rules |
+2 -2
View File
@@ -58,8 +58,8 @@ An open-source static analysis tool that finds bugs and enforces code standards
**Syft**
An open-source tool for generating SBOMs from container images and filesystems. Used by Certifai to extract dependency information.
**OSV.dev**
Google's open distributed vulnerability database, queried by package URL. Certifai uses it (together with NVD) to match SBOM components against known vulnerabilities.
**Grype**
An open-source vulnerability scanner for container images and filesystems. Used by Certifai to match dependencies against known vulnerabilities.
## Protocols
+6 -16
View File
@@ -24,14 +24,15 @@ Semgrep produces SAST-type findings with file paths, line numbers, and rule desc
Syft output feeds into both the SBOM feature and the vulnerability scanning pipeline.
## OSV.dev + NVD -- Vulnerability Matching
## Grype -- Vulnerability Scanning
Certifai matches every SBOM component directly against two public vulnerability sources (no separate scanner binary):
[Grype](https://github.com/anchore/grype) is an open-source vulnerability scanner that matches your dependencies against known vulnerability databases. It takes Syft's SBOM output and cross-references it against:
- [OSV.dev](https://osv.dev/) -- batch queried by package URL (purl) for ecosystem advisories (npm, PyPI, crates.io, Go, Maven, ...)
- [NVD](https://nvd.nist.gov/) -- queried per CVE for the CVSS v3.1 base score, and by CPE for CODESYS runtime versions found in PLC projects
- National Vulnerability Database (NVD)
- GitHub Advisory Database
- OS-specific advisory databases
Matches are stored as CVE alerts with CVSS scores and re-checked hourly, so newly published CVEs against an unchanged dependency still raise a notification.
Grype produces SBOM-type findings with CVE identifiers, severity ratings, and links to advisories.
## Custom OAuth Scanner
@@ -96,14 +97,3 @@ When you mark findings as false positives or provide developer feedback, this in
::: tip
The AI triage is a starting point, not a final verdict. Always review the rationale and code evidence before acting on a finding. See [Understanding Findings](/guide/findings#human-in-the-loop) for more on the human-in-the-loop workflow.
:::
## Planned integrations (decided 2026-08-31, not yet in the code)
The product spec keeps an **OSS-only** tooling policy and a control-mapping rule of *tools detect, the LLM judges*. Two deterministic detectors are therefore being added **underneath** the agentic DAST/pentest layer — the agents stay on top for context-seeded exploitation, chaining and explanation:
| Tool | Role | Status |
|------|------|--------|
| [Nuclei](https://github.com/projectdiscovery/nuclei) | Template-driven checks (CVE probes, default credentials, exposed panels, misconfigurations) including ICS/OT templates for WebVisu / OpenPLC / HMI endpoints. Runs as a DAST phase and as a Werkbank job with vendored templates so it works on-prem. | Planned — tracked as an issue |
| [OWASP ZAP](https://www.zaproxy.org/) | Baseline (passive) and, behind the destructive-tests flag, active scan for reproducible spider + rule coverage; results seed the pentest agent. | Planned — follows Nuclei |
Both feed the same `control-map` lookup table as Semgrep, so their findings receive compliance `control_refs` through the grounded judge. An **offline vulnerability database** (Trivy preferred, Grype as alternative) is planned for the on-prem Werkbank runner, which cannot reach the OSV.dev / NVD APIs. Until these land, DAST findings come exclusively from the in-house agents described above.