An open-source, local application security assessment engine and Claude Code plugin. It keeps a ledger of what was checked, how, with which evidence, and what could not be decided. Formerly PenSec.
Security scanners tend to report a clean result when a check did not run, and AI review tools can present a guess as a confirmed vulnerability. A self-assessment of your own project needs to separate what was proven from what was merely not found, and must not send traffic anywhere a person has not authorized.
PandaWard assesses the security of a repository you own. It runs as a standalone Go CLI and as a Claude Code plugin. In Claude Code one command, /pandaward:start, runs the scan, has read-only review agents look at what automation could not decide, triages the results, builds a fix plan and writes the reports.
The default profile only reads the repository. It sends no traffic to any target and executes nothing from the repository: no install scripts, Makefiles or build steps. Findings, evidence and human decisions live in a local SQLite ledger that survives context compaction and session restarts.
The project started on 27 October 2026 as PenSec and was renamed to PandaWard in 0.2.0 on the next day (#107, #108). The first command run in a project moves .pensec/ to .pandaward/, old PSEC- finding IDs keep working, and approvals made with 0.1.x stay valid (Moving from PenSec).
The README states the limit plainly: a PandaWard report is an automated technical assessment, not a certification, an attestation or an independently signed penetration test.
Passive unless a person approves a scopeAnything that sends traffic needs a scope manifest filled in by a person and approved from an interactive terminal. The approval is bound to the manifest hash and to a per-user key outside the project, and it expires.
A check that did not run is not a passBLOCKED, UNSUPPORTED, SUSPECTED, INCONCLUSIVE, ERROR and STALE never count as secure. A crashed or missing scanner is reported as ERROR or BLOCKED.
A model cannot confirm a findingModel-proposed issues are stored as HYPOTHESIS, and the model cannot supply the evidence text.
Secrets are never stored in clearValues are masked before they are stored, printed, reported or returned over MCP. .env and key files are recorded by name only.
No account and no cloud backend; telemetry only if you turn it onUsage counters are off by default, can only be turned on from an interactive terminal, and `pandaward telemetry show` prints the exact payload.
Creator and maintainer. The repository was created on 2026-10-27 as PenSec and renamed to PandaWard in release 0.2.0; its commits are authored under my GitHub account.
cmd/pandaward is the CLI. internal/ holds the engine, the adapters, the SQLite store, scope handling, redaction, evidence, reports, the MCP server and the hook handlers. The Claude Code side is plain files: skills/ for the /pandaward:* commands, agents/ for seven read-only review agents, hooks/hooks.json and the MCP configuration, all of which call the same binary.
An assessment starts with a read-only inventory of files, routes, manifests, workflows, infrastructure code, mobile manifests and AI or MCP usage. It plans the applicable controls from the catalog, runs the adapters that cover them, and records each run, finding and evidence file in the ledger. Findings are fingerprinted, so a fix becomes REMEDIATED only when the check runs again and no longer sees the problem. Cross-domain edges, such as identity to authorization or webhook to payment state, are created only when both sides share concrete assets.
In Claude Code, hooks keep the ledger current: edited files are marked stale, and when a turn that edited files ends, a changed-only passive retest (bounded at 40 seconds) hands new or reopened findings back to the session. During a guided assessment, pandaward progress shows what each review agent has read and searched and how many hypotheses it has recorded, using file names and search patterns only, never file contents (#154).
pandaward init builds an inventory of files and routes. pandaward plan selects applicable controls from the catalog. pandaward scan runs the built-in and optional external adapters and records runs, findings and hashed evidence in the SQLite ledger. Only PASS and FAIL are decisive. pandaward retest re-runs affected checks after changes. pandaward report writes HTML, Markdown, JSON, SARIF and CSV.
Technology stack
Technology
Version
Used for
Go
1.26
CLI, assessment engine, adapters and local MCP server
SQLite
not stated
Local ledger of assessments, findings, evidence and approvals, through modernc.org/sqlite (no cgo)
Claude Code plugin
not stated
Skills, seven read-only review agents, lifecycle hooks and an MCP server
SARIF
2.1.0
Report format for code scanning tools, next to HTML, Markdown, JSON and CSV; also imported from analyzers you run yourself
Sigstore attestations
not stated
Signed build provenance and SBOM attestations for every release binary
Every adapter declares the controls it covers, its maturity and whether it needs the network. The engine runs each one through a wrapper, so an adapter that panics produces an ERROR run instead of stopping the scan or leaving its controls looking clean, and a missing optional tool is recorded as BLOCKED.
Optional scanners (gitleaks, semgrep, osv-scanner, trivy, apktool) are used only when they are already installed. CI exercises the adapters on every pull request against pinned versions of those tools and fails when a parser misses the expected findings on the bundled vulnerable fixture. SARIF files from analyzers you ran yourself, such as CodeQL, are imported as hypotheses with snippets read and redacted from your files, not taken from the SARIF.
Runtime profiles exist for approved targets: production-observe reviews headers and cookies, staging-active runs authorization checks with test identities and then ZAP through a PandaWard proxy that applies the scope guard to every request, and local-runtime reads what a debuggable test build stored on an emulator or simulator, refusing real devices.
Each release is built twice and fails if the hashes differ, and the binaries ship with checksums, a CycloneDX SBOM and signed build provenance that gh attestation verify can check (docs/VERIFYING.md).
Store AI-proposed issues as hypotheses with engine-read evidence
Problem
The review agents in the plugin can propose findings. If the model could also supply the quoted code, a mistaken or manipulated proposal could carry evidence that does not exist in the repository.
Decision
The MCP tool accepts a control id, a file path and a line range, not evidence text. The engine checks that the file is a regular file inside the project, reads at most 40 lines itself, redacts them, records the file hash and stores the finding as HYPOTHESIS.
Tradeoff or limitation
Hypotheses only make a control SUSPECTED, never FAIL, so a real issue found by review stays undecided until a person or a deterministic test confirms it.
Bind network approval to the manifest hash, a human terminal and a per-user key
Problem
A coding agent running PandaWard could, in principle, write a scope manifest and approve it, or widen the scope after approval. A repository could also arrive with a scope file and a ledger that already contain an approval.
Decision
`pandaward scope approve` refuses to run when stdin is not a terminal, requires a name, shows every target and asks the person to type APPROVE followed by the first eight characters of the manifest SHA-256. The approval expires after 1 to 72 hours, any edit to scope.yaml invalidates it, and it is bound to a key in the user config directory, so a ledger copied in from elsewhere never counts as approved.
Tradeoff or limitation
Network tests cannot be fully automated in CI; that is intended.
Check the address at connection time, not only the URL
Problem
A target that is in scope by name can resolve to a private, loopback or cloud metadata address, or redirect there.
Decision
The HTTP client resolves the host inside its dialer and rejects loopback, private and link-local addresses, and always rejects cloud metadata addresses. Redirects are never followed. In staging-active, ZAP is chained to a loopback proxy that sends each request through the same guard.
Tradeoff or limitation
Targets marked as production are refused by staging-active, so active tests need a staging copy.
The built-in code rules pass PandaWard's own corpus, but that corpus was written by the same author as the rules, so it cannot show real-world accuracy.
Decision
Run the built-in rules against the public OWASP Benchmark for Java at a pinned commit and publish the result as measured: a score of 0.00, with no true positives and no false positives, and an explanation of why single-line rules cannot follow the data flow the suite is built around.
Tradeoff or limitation
The README now tells Java users not to rely on the built-in rules for injection, crypto or randomness, and to run a data flow aware analyzer through PandaWard instead.
Check the address at connection time, not only the URLPrivate addresses can be allowed only by an explicit manifest setting meant for a controlled local fixture.
Publish a benchmark score of zeroTuning the rules to the suite was ruled out; the document says that tuning to one suite overstates real-world accuracy.
The first two days were mostly about getting PandaWard into people's hands. The plugin originally expected a binary in bin/, so installing it from GitHub did not work until the release workflow started publishing prebuilt, checksum-verified binaries to dedicated plugin branches. The Claude plugin directory then rejected those branches because of files over 5 MiB, which led to a separate branch without binaries (#114).
Running PandaWard on a real project exposed confidence overclaims and false positives on secrets and SQL templates (#130), and the bundled Semgrep rules matched db.query() as an SSRF source and had fixtures that never ran in CI (#121). Both were fixed in pull requests #131 and #122. Release binaries had also been built with Go 1.23.6, which no longer gets security fixes; CI and releases now build with Go 1.26.10 and are gated on govulncheck (#125).
Releases 0.2.4 to 0.2.7 came from running PandaWard on more of my own projects and from the CI fuzzers. A fuzz target found an API key that line redaction joined back together (#144), retest could credit a fix to the wrong one of two identical findings and read a moved file as a fix (#149), and four rules produced eighteen false positives, from Next.js build output, fixed-origin fetches, an OAuth callback and SQL built from constants. All of them are fixed in v0.2.7. A false negative is still open: Next.js server actions are not listed as entry points yet (#163). The write-up covers each one.
The SECURITY.md scope lists the failures that matter most for this tool: repository content making PandaWard run commands, widen scope or confirm a finding, traffic outside the approved scope, unredacted secrets, a crashed adapter producing PASS, and evidence that can be modified without failing verification.
pandaward selftest runs a hostile fixture repository, with instructions planted in comments, the README, OpenAPI, MCP descriptions and scanner output, through scan, reports, MCP and hooks. CI and every release run it and fail on any case.
In Claude Code, a PreToolUse hook blocks edits to .pandaward/private/ and asks for confirmation before scope commands and manifest edits.
PandaWard has not had an external security review. SECURITY.md says so.
Go tests cover the engine against a deliberately vulnerable fixture repository, remediation and reopening of findings, staleness after an edit, a panicking adapter, a missing tool, evidence tampering and hypothesis validation, plus the scope guard, redaction (with fuzz tests), report output, hook decisions and the MCP protocol.
CI runs on Linux, macOS and Windows with govulncheck on the source and the binary, the hostile-repository self-test, the rule benchmark in strict mode, the optional tool adapters against pinned scanner versions, and a double build that fails if the binaries are not byte-identical. Actions are pinned to commit hashes.
All adapters are implemented but unvalidated. No rule meets the project's own bar for "benchmarked" yet: at least 3 vulnerable and 3 safe cases with precision and recall of 0.95 or more.
The built-in code rules match single lines and do not follow data flow; on the OWASP Benchmark for Java they find none of the test cases.
Login, reset and MFA flows, API contract versus runtime, and prompt-injection tests against an LLM endpoint are reported as UNSUPPORTED.
Running PandaWard on real repositories and under a fuzzer found a secret that redaction put back together, a retest that marked the wrong finding as fixed, eighteen false positives from four rules, and 213 server actions the inventory never listed. Four releases on 29 October fixed all but the last.
I ran PandaWard's built-in rules against the OWASP Benchmark for Java. They found none of its 2,740 test cases and raised no false alarms. The result is published as measured, with the reason and what users should do instead.
Experiment
Found something inaccurate? Tell me and it will be corrected with a note.