Security review¶
codesynapse audit, the Security page and the security_report MCP tool grade a repository A to F and list findings. The grade only means something next to what was checked, so every report also states its coverage.
Where findings come from¶
flowchart TB
subgraph rules[Rule catalogue: rules/*.yaml]
rc[rules/code/*.yaml]
rg[rules/config/*.yaml]
end
subgraph detectors
gd[golang/findings.go<br/>type-aware]
ld[lang/rules.go<br/>Python, JS, TS, Vue]
yd[config/yaml.go<br/>Kubernetes, compose, Actions, Renovate]
end
subgraph deps[Dependencies]
lock[go.mod and lockfiles:<br/>npm, PyPI, Cargo, Composer,<br/>Bundler, NuGet, Gradle]
osv[osv.dev<br/>names and versions only]
end
rc --> gd
rc --> ld
rg --> yd
lock --> osv
gd --> F[Finding nodes]
ld --> F
yd --> F
osv --> V[Vulnerability nodes + reachability]
F --> B[security.Build]
V --> B
cov[Coverage + Inventory] --> B
B --> out[Grade, findings, exposure,<br/>priority, Markdown / JSON]
Each rule is a readable YAML file (title, severity, CWE, why, fix, vulnerable to fixed examples per language). It surfaces as How to fix on a finding, through rule_guide, and at GET /api/v1/rules.
Finding categories¶
- Code: shell and command injection, SQL/Cypher from strings, disabled TLS verification, old TLS, weak hashes and ciphers, hardcoded credentials,
template.HTMLfrom variables, HTTP servers/clients without timeouts, request-derived paths and URLs, unbounded bodies, world-writable files, unverified JWTs,unsafe. - Configuration: privileged pods, privilege escalation, host namespaces,
hostPath, root users, dangerous capabilities, unpinned images, literal secrets, GitHub Actions script injection,pull_request_targetcheckouts, unpinned third-party actions, privileged compose services. - Update automation: manifests but no Renovate or Dependabot; Renovate configs that are broken, disabled, automerge without
minimumReleaseAge, or leave actions and images unpinned. - Dependencies: known CVEs with fixed version and reachability.
Vulnerability reachability¶
flowchart LR
v[CVE affects module M] --> req{M in go.mod / lockfile?}
req -- yes --> required[required]
required --> imp{Package of M imported?}
imp -- yes --> imported[imported]
imported --> call{Vulnerable symbol called<br/>over CALLS edges?}
call -- yes --> called[called<br/>with call sites]
Symbol-level reachability exists for Go. For the other ecosystems, advisories rarely list symbols, so the levels are imported (npm, PyPI, Cargo, NuGet and RubyGems, when a source file imports the package; for NuGet, a using directive whose namespace the package ID prefixes; for RubyGems, a require of the gem) or required (everything else: PHP and Java imports are not matched to Composer or Maven names). --fail-on gates CI on reachable vulnerabilities only.
Priority¶
A finding's priority combines:
- Severity and CWE.
- Exposure: reachable from
main, an exported API or a handler. - Test context: whether a test reaches the owning function.
- For vulnerabilities, reachability level.
Coverage¶
At ingest security.CoverageOf counts what rules read, and security.Inventory counts file kinds with no rules yet (Dockerfile, Terraform, shell, Java, ...). Both are stored on the Repository node.
| Reported as checked | Reported as not checked |
|---|---|
| Code files per language | YAML that does not parse |
| YAML files, Kubernetes resources | Helm templates whose output is not YAML |
| Workflows, Compose files | File kinds without rules |
| Helm templates (literal values) | Values set through {{ .Values }} |
| Dependencies |
A repository where nothing is scannable gets N/A, not a grade. The report also names the commit it describes.
CI usage¶
codesynapse audit . # Markdown
codesynapse audit . --fail-on high # exit 1 on high/critical (reachable vulns only)
codesynapse audit . -o json
--vulns=false (or CODESYNAPSE_VULN_SCAN=false) keeps the scan offline.
Languages¶
| Symbols & calls | Tests | Security rules | Dependency CVEs | |
|---|---|---|---|---|
| Go | type-checked (go/types), interface dispatch | _test.go |
~20 rules | go.mod + stdlib, symbol-level reachability |
| Python | structural parser, imports, self. calls |
pytest files/functions | shell, eval, pickle/yaml, SQL, TLS, JWT, secrets, debug | requirements*.txt, poetry.lock, uv.lock, Pipfile.lock |
| JavaScript / TypeScript / Vue | structural parser, ES imports/require, this. calls |
*.test.*, *.spec.*, __tests__ |
child_process, eval, DOM XSS, v-html, SQL, TLS, JWT, secrets, redirects |
package-lock.json, yarn.lock, pnpm-lock.yaml |
| YAML, Helm, Go templates | resources, key paths, include/.Values links |
– | Kubernetes, compose, GitHub Actions, Renovate/Dependabot | – |
Non-Go call edges are name-based (imports, enclosing class, names unique in the repo), so they can miss dynamic dispatch; Go keeps exact type-checked edges. Parsers are pure Go: no cgo, static binaries.
Go calls through function values (handler tables, injected functions) have no typed edge. --dataflow (or CODESYNAPSE_INGEST_DATAFLOW=true) runs an optional SSA/VTA analysis that links them, marked as dataflow evidence; it makes Go extraction about 40% slower and finds only targets declared in the repository. See edge evidence.
Adding a rule¶
Every rule is a readable YAML file in rules/ with why it matters, how to fix it and vulnerable → fixed examples per language. They show up as How to fix on each finding, through the rule_guide MCP tool and at GET /api/v1/rules. just rules lists them.
flowchart LR
y[1. rules/code/id.yaml] --> d[2. Detector + list id in RuleIDs]
d --> t[3. Fixture that triggers it<br/>and one that must not]
t --> g[4. just test]
g --> c{rules.TestCatalog and<br/>TestEveryDetectorHasARule}
c -- pass --> done([Done])
c -- fail --> y