Skip to main content

Security analysis

ovecc ships two complementary security surfaces, both deterministic and offline:

  • ovecc security — findings in your code: hardcoded secrets, insecure patterns, weak crypto, permissive CORS, and tainted source→sink flows.
  • ovecc audit — known vulnerabilities in your dependencies, checked against the OSV database.

Neither uses an LLM. Everything comes with file:line evidence.

The code security sweep

$ ovecc security
Security findings: 13 (scanned the indexed repository)
secrets 1, insecure 2, weak-crypto 1, cors 1, tainted-flows 8

[Critical] Hardcoded secret: Stripe secret key
Evidence: src/services/billing.ts:5 (Stripe secret key)

[Critical] Potential tainted flow: POST /admin/eval -> eval (eval)
Evidence: src/routes/api.ts:55 (sink: eval)
Evidence: src/routes/api.ts:54 (source: POST /admin/eval)

[High] OS command execution: execSync
Evidence: src/routes/api.ts:60 (execSync)

[High] Potential tainted flow: POST /billing/charge -> invoices (writes)
Evidence: src/services/billing.ts:18 (sink: writes)
Evidence: src/routes/api.ts:29 (source: POST /billing/charge)

[Medium] Permissive CORS configuration: Access-Control-Allow-Origin: *
Evidence: src/routes/api.ts:12 (Access-Control-Allow-Origin: *)

[Medium] Weak hash algorithm: MD5
Evidence: src/utils/auth.ts:4 (MD5)
...

What the detectors cover

CategoryRuleDetected patterns
Secretssecurity/secret (critical)Provider-pattern credentials (e.g. AWS access keys, Stripe live keys) and high-entropy values assigned to secret-named bindings. Well-known documentation placeholder keys are deliberately not flagged.
Dynamic codesecurity/eval (high)eval(...), new Function(...)
Command executionsecurity/command-exec (high)exec / execSync / spawn (and friends) called on a child_process-style module object; os.system / subprocess.* in Python; exec.Command in Go; process::Command in Rust
Weak cryptosecurity/weak-hash (medium)MD5, SHA-1 (e.g. createHash("md5"), hashlib.sha1)
Permissive CORSsecurity/cors (medium)origin: "*" middleware config and the raw setHeader("Access-Control-Allow-Origin", "*") form
Tainted flowstaint/*see below

Findings located in test code are down-ranked to Low rather than hidden.

Taint tracking: route → sink

At index time, ovecc connects extracted HTTP routes to dangerous sinks (database reads/writes, eval, command execution) through the resolved call graph — including inline arrow handlers, which get a synthetic symbol so the route stays connected:

[Critical] Potential tainted flow: GET /admin/ping -> command (command)
Evidence: src/routes/api.ts:60 (sink: command)
Evidence: src/routes/api.ts:59 (source: GET /admin/ping)

Severity depends on the sink:

RuleSinkSeverity
taint/evaldynamic code executioncritical
taint/commandOS command executioncritical
taint/writesdatabase write (injection candidate)high
taint/readsdatabase readmedium

:::note Honest over-approximation Taint flows are reachability-based: "user-controlled input from this route may reach this sink via this call path." A parameterized query on a reachable path will still be reported as a medium taint/reads — the finding's description says Reachability over-approximation — requires review explicitly. Treat critical/high flows as action items and medium read-flows as a review checklist. :::

The JSON output includes the full propagation path:

{
"kind": "tainted_flow",
"severity": "critical",
"rule_name": "taint/command",
"title": "Potential tainted flow: GET /admin/ping -> command (command)",
"description": "User-controlled input from GET /admin/ping may reach OS command execution via GET /admin/ping -> <GET /admin/ping handler>. Reachability over-approximation — requires review.",
"evidence": [
{ "file_path": "src/routes/api.ts", "line": 61, "symbol": "<GET /admin/ping handler>", "detail": "sink: command" },
{ "file_path": "src/routes/api.ts", "line": 60, "detail": "source: GET /admin/ping" }
]
}

Dependency auditing (OSV)

audit checks declared dependencies against the local OSV advisory cache in .ovecc/osv/. It is offline by default and honest when the cache is empty:

$ ovecc audit
Dependency audit (OSV): scanned 3 package(s) against 0 advisor(ies)
Vulnerabilities: 0
(no OSV database in .ovecc/osv/ — sync advisories to enable matching)

--fetch downloads the advisories for exactly the packages you use — the only ovecc operation that ever touches the network, and only with this flag:

$ ovecc audit --fetch
Fetched 8 new advisory(ies) for 3 package(s).
Dependency audit (OSV): scanned 3 package(s) against 8 advisor(ies)
Vulnerabilities: 8

[High] Vulnerable dependency: lodash@4.17.15 (GHSA-35jh-r3h4-6jhm)
[High] Vulnerable dependency: express@4.18.2 (GHSA-rv95-896h-c2vc)
...

Severity follows the advisory's own label; unlabeled advisories default to High. npm package-lock.json is read today; other lockfile formats are planned.

Advisories are tracked as their own metric (dependency_advisories) — separate from security_findings — so fetching fresh advisories never shows up as a code-quality regression in drift.

Security in CI

# fail the build on any new high-severity code finding
ovecc security --fail-on high

# GitHub code scanning (SARIF upload)
ovecc violations --format sarif > ovecc.sarif

# fail on vulnerable dependencies
ovecc audit --fetch --fail-on high

See CI integration for full pipelines, and note that a failing --fail-on still prints the complete report (exit code 1 is a signal, not a crash — exit codes).

Suppressing a finding

False positive, or accepted risk? Silence one specific finding inline:

const digest = createHash("md5").update(cacheKey).digest("hex"); // ovecc-ignore

// ovecc-ignore-next-line and Python's # ovecc-ignore also work; the finding is dropped at index time. ovecc even reports suppressions that no longer suppress anything (stale-suppression), so dead ignores don't silently swallow the next real finding on that line. See governance.