Pencheff

Risk, reporting, and compliance · Platform

Re-examination

Verify any fix on demand with targeted re-test probes against the same finding.

Reporting turns raw scanner output into evidence-backed decisions: executive posture, technical dossiers, compliance mappings, retest history, threat models, and clear remediation ownership.

Scanner pipelineSARIF-ready
8coverage areas
5operator steps
4evidence fields
Coverage8
Execution5
Evidence4
Controls4
SemgrepgitleaksSARIFFix PRSBOM

Source findings carry scanner provenance, line evidence, and fix state through the same pipeline.

ScopeDeliverables
SectionPlatform
MethodDeterministic-first
OutputUnified evidence
ProfileRisk, reporting, and compliance
01

Coverage

What does Re-examination test?

  • Verify any fix on demand with targeted re-test probes against the same finding.
  • This page is part of Platform under Deliverables.
  • It links back into the broader a complete adversarial security platform experience.
  • Executive dashboard, letter grade, risk trends, severity rollups, and portfolio posture.
  • Technical dossier with findings, reproduction, affected components, remediation, evidence, and re-examination state.
  • Compliance mapping for OWASP, PCI DSS, SOC 2, NIST, ISO 27001, HIPAA, OWASP LLM, MITRE ATLAS, NIST AI RMF, EU AI Act, and GDPR.
  • Threat modeling with STRIDE, DREAD, attack trees, abuse cases, mitigations, and scan context.
  • Unified findings stream, AI triage, advisory enrichment, comments, suppressions, and audit appendices.
02

Execution

How does Pencheff run this?

  • Collect findings from runtime, repo, supply chain, infrastructure, AI, and manual sources.
  • Normalize severity, confidence, category, exploitability, reachability, and owner state.
  • Generate executive, engineering, compliance, or retest views from the same source record.
  • Track suppression, comments, fixes, re-examinations, and residual risk across scan history.
  • Export reports and feed integrations without losing the underlying evidence chain.
03

Evidence

What evidence does this produce?

  • Executive summaries, trend charts, severity counts, grade drivers, and business impact language.
  • Technical evidence, scanner provenance, reproduction steps, remediation, and references.
  • Framework control mappings and audit appendix entries tied to actual findings.
  • Retest and verification history for closure and residual risk decisions.
04

Controls

How is this kept safe to run?

  • Compliance rollups are deterministic and recomputed from finding state.
  • Triage output distinguishes verified facts from advisory context.
  • Reports inherit the same authorization and workspace boundaries as scans.
  • Executives and auditors can read summaries while engineers keep deep evidence.
01

From the Pencheff docs

Auto-fix PRs

Pencheff turns SAST, DAST, and SCA findings into ready-to-merge GitHub pull requests. Click Propose fix on any finding and Pencheff:

  1. Materialises a working tree of the connected repo.
  2. Generates a unified diff via the appropriate strategy:
    • SCA — deterministic version-bump in the offending manifest.
    • SAST — scanner-native autofix from Semgrep when present; LLM patch otherwise.
    • DAST — provenance-rank candidate handlers, then patch the most likely one.
  3. Opens a branch, commits the diff, pushes, and opens a PR via the GitHub App. The PR body cites the finding, evidence, and remediation guidance.

SCA: deterministic, free, no LLM

The SCA path is the simplest and the cheapest: Pencheff parses the manifest, finds the line, replaces the version, and writes the diff — no LLM call, no per-fix cost.

Supported manifests (9 ecosystems)

EcosystemManifest
Pythonrequirements.txt, pyproject.toml, Pipfile
Node.jspackage.json
Gogo.mod
RustCargo.toml
RubyGemfile
PHPcomposer.json
Javapom.xml

Lockfiles are deliberately not edited

Editing package-lock.json, poetry.lock, Cargo.lock, etc. in place would break integrity hashes for most ecosystems. The PR body instructs the developer to run the right installer (npm install, poetry lock, go mod tidy, …) — the lockfile regenerates correctly that way.

SAST + DAST: synthesised patches

When no scanner-native autofix exists, Pencheff calls an operator-configured chat-completions backend to produce a unified diff. Token usage and PAYG cost are recorded in fix_llm_usage per call.

Configuration

Add to .env (or set as env vars):

# Operator-supplied credentials for the patch-synthesis backend.
FIX_LLM_API_KEY=sk-...

# Optional overrides
# FIX_LLM_BASE_URL=
# FIX_LLM_MODEL=
# FIX_LLM_REQUEST_TIMEOUT=60.0

API

  • POST /findings/{kind}/{finding_id}/propose_fix — generate a draft proposal. kind is sast or dast; SCA findings ride the dast kind (Pencheff detects the SCA payload from evidence and routes internally).
  • POST /fix-proposals/{id}/apply — open the PR.
  • POST /fix-proposals/{id}/revert — close the PR + delete the branch.

See Findings reference for the full API.

Reliability

Recent improvements to the agentic fixer:

Runs go to completion. The per-run token budget cap and the month-to-date spend cap that could abort runs mid-flight have been removed. The fixer now runs until it either produces a PR or exhausts the model context — not until an arbitrary cost threshold is hit.

npm lockfile regeneration. SCA fixes for npm CVEs now include a npm audit fix step that regenerates package-lock.json after the version bump. This ensures transitive upgrades land in the lockfile where the scanner reads them, not just in package.json.

SAST false-positive triage. After a repo scan, Pencheff runs an AI-assisted false-positive classifier over SAST findings. Verified false positives (for example, Bandit B608 flagging parameterized SQL queries) are suppressed in Pencheff — your source code is not modified. Suppressed findings are marked with the classifier's reasoning and can be un-suppressed from the findings view.

GitHub App cleanup. Disconnecting or uninstalling the GitHub App now hard-deletes the disconnected repository records from the workspace.

What's tested

cd apps/api && uv run pytest tests/test_sca_patcher.py

19 unit tests cover all 9 supported manifest formats plus the "lockfile rejected" contract.

02

From the Pencheff docs

Scans API

POST /scans

Trigger a new scan.

POST /scans
{
  "target_id": "...",
  "profile": "standard",
  "consent_payload": {
    "authorization_text": "I am authorised to test example.com as of 2026-05-06 and I accept the disclosed actions.",
    "acknowledged": true
  }
}

profile is one of quick, standard, or deep. Older specialised names (engage, compliance, api-only, cicd, sca, iac, supply-chain, network-va, hackme, continuous, compliance-full) are still accepted and coerced to the matching tier at the runner — see Picking a profile for the fold-in table.

consent_payload is required when swarm mode is active (default). The API returns 422 Unprocessable Entity if the field is absent, if authorization_text is shorter than 50 characters, or if acknowledged is not true. The payload is persisted on Scan.consent_payload (JSONB) and included in every audit export. Shape (Pydantic schema ConsentPayload):

type ConsentPayload = {
  authorization_text: string;  // >= 50 characters
  acknowledged: boolean;       // must be true
};

GET /scans

List scans for the current org (paginated).

GET /scans/{id}

Fetch a single scan — includes summary counts, grade, progress.

GET /scans/{id}/progress (SSE)

Server-Sent Events stream of live progress ticks during an active scan. Events:

  • stage_start: recon_passive
  • stage_done: recon_passive: 47 endpoints
  • finding: HIGH SQLi on /api/users
  • finished

GET /scans/{id}/findings

Returns every finding for the scan with CVSS, EPSS, KEV, risk_score, compliance mapping.

GET /scans/{id}/linked-repos

Returns the repositories attached to this URL scan's target. Source-code findings (Semgrep · Bandit · gosec · Brakeman · ESLint · OSV · secret-scan) live on each repo's own assessment page (/repos/{repository_id}) — the URL scan deliberately does not mix SAST findings into its own results list. Use this endpoint to render a "Linked repositories" sidebar with deep-links.

type LinkedRepo = {
  repository_id: string;
  full_name: string;
  provider: string | null;
  scan_url: string;   // e.g. "/repos/abc-123"
};

Returns [] for scans whose target has no attached repos. Available on URL targets only.

GET /scans/{a}/compare/{b}

Compare two completed scans of the same workspace and return a structured diff. Particularly useful for LLM red-team scans where you want to gate a PR on safety regressions, or A/B different model versions on the same suite.

GET /scans/3a35.../compare/9359...
Authorization: Bearer <token>

Response:

type ScanCompare = {
  baseline: { name: string; summary: RedTeamSummary };
  candidate: { name: string; summary: RedTeamSummary };
  regressions: Finding[];     // candidate-only
  fixes: Finding[];           // baseline-only
  common_failures: Finding[]; // present in both
  counts: { regressions: number; fixes: number; common_failures: number };
  keys: { regressions: string[]; fixes: string[]; common_failures: string[] };
  scan_a: { id: string; profile: string; grade: string | null;
            score: number | null; created_at: string };
  scan_b: { id: string; profile: string; grade: string | null;
            score: number | null; created_at: string };
};

The dedup key is endpoint|parameter|technique|title, so re-running the identical suite against an unchanged target produces zero regressions. The web UI exposes the same diff at /scans/compare?a=…&b=… with a JUnit-XML download for the regressions list — wire it into CI to fail builds on new findings.

POST /scans/{id}/share (LLM only)

Issues a Fernet-encrypted token granting public read access to the scan's LLM-flavored report. Available only when the underlying target is kind: "llm".

POST /scans/3a35.../share?ttl_seconds=604800
Authorization: Bearer <token>

Response:

{
  "token": "gAAAAA...",
  "expires_in": 604800,
  "url_path": "/share/llm/gAAAAA..."
}

The companion public route GET /share/llm/{token} renders the scan as HTML (default), Markdown, CSV, or JSON depending on the ?download= query param. Token expiry is the only revocation — let it expire to revoke. PII is redacted in evidence snippets before rendering, regardless of the public/private path.

GET /scans/{id}/llm-traces

Returns the full LLM call trace for a scan. Each row corresponds to one chat-completions call made by a swarm agent during the scan.

Authentication required. Returns [] for scans run with SWARM_ENABLED=false.

type ScanLLMTrace = {
  id: string;
  scan_id: string;
  agent: string;             // e.g. "InjectionAgent", "ChainAgent"
  turn: number;              // agent conversation turn number
  request_messages: object[];  // full messages array sent to the LLM
  response: object;          // raw LLM response including tool-call blocks
  input_tokens: number;
  output_tokens: number;
  cache_read_tokens: number;
  reasoning: string | null;  // thinking/reasoning block if present
  created_at: string;
};

GET /scans/{id}/evidence/{finding_id}.png

Returns the Playwright evidence screenshot captured by EvidenceCaptureAgent for the specified finding. PII is redacted before storage.

Authentication required. Returns 404 Not Found if no screenshot exists for that finding (e.g. the finding was below high severity, the agent did not run, or the scan used the legacy single-agent path).

GET /scans/3a35.../evidence/f7b2c4....png
Authorization: Bearer <token>

Response: image/png binary. Cache the response client-side — screenshots do not change after the scan completes.

DELETE /scans/{id}

Cancel an in-flight scan or remove a finished one.

Scan object

type Scan = {
  id: string;
  target_id: string;
  status: "queued" | "running" | "completed" | "failed" | "cancelled";
  profile: string;
  progress_pct: number;      // 0-100
  current_stage: string | null;
  summary: {
    critical: number; high: number; medium: number; low: number; info: number;
    suppressed: number;
  } | null;
  grade: "A" | "B" | "C" | "D" | "F" | null;
  score: number | null;
  started_at: string | null;
  finished_at: string | null;
};
?

FAQ

Common questions

What is a re-examination in Pencheff?
A re-examination re-runs the specific tests that produced previously reported findings against your updated application. It confirms whether remediation was successful and produces a formal closure certificate that can be submitted as evidence in compliance audits.
Can Pencheff automatically open pull requests to fix vulnerabilities?
Yes. For dependency vulnerabilities and some SAST findings, Pencheff can open auto-fix pull requests that bump the affected package to a patched version or apply a known secure code pattern — reviewed and merged by your team.
How long does a re-examination take?
A targeted re-examination that re-tests only previously open findings typically completes in 2–10 minutes, depending on the number of findings and the depth of the original test.