The project's root rule (see ACCURACY_SPEC.md): the tool never asserts more
than it can prove. Every finding has a verifiable basis; where proof is absent
the answer is unknown, never a guess. This is enforced, not promised — the test
suite includes invariant tests (PrincipleZero) proving the engine never reports
guarded without an admissible sanitiser, and that an untraceable value is always
unknown. What the maintainers cannot verify (live VS Code, real network clone)
is marked unverified rather than implied to work.
A contract-oriented security gate for Python, delivered as a VS Code extension plus
the veritas_core.py engine. Instead of imposing a generic ruleset, VeritasCore learns the guard
contracts your codebase already follows (“dangerous call ⇒ must be wrapped in a guard”) and
enforces consistency on new code.
veritas-core/
├── veritas_core.py # engine (Python 3.10+, stdlib only)
├── veritas-core-1.0.0.vsix # installable extension package
├── extension/ # extension sources
│ ├── package.json tsconfig.json README.md
│ ├── src/{engine,ui,extension}.ts
│ └── out/{engine,ui,extension}.js # compiled output (bundled in the .vsix)
├── build.sh # rebuild the .vsix
└── README.md
code --install-extension veritas-core-1.0.0.vsixThen set veritas.scriptPath to the path of veritas_core.py (default ~/veritas_core.py).
- USAGE.md — all CLI flags,
.veritascoreignore,.gate_policy.json,.veritascore.jsonsettings, team/incremental mode,# veritas-core: skip. - ARCHITECTURE.md — how the engine works: the four-state model, Principle Zero, taint propagation, guards, cross-file, the honest limits.
- EXTENDING.md — add a sink, source, or sanitiser.
- FAQ.md — common questions and troubleshooting.
- ACCURACY_SPEC.md — the accuracy contract and benchmark.
- Real-time contract analysis — on save, runs
veritas_core.py(viaexecFile, 15s timeout). Diagnostics include the full contract, e.g.os.system() без guard. Контракт: shlex.quote (примеров: 5, уверенность 97%). - Security panel — WebView tabs: Контракты (table + «Обновить»), Ошибки (clickable file:line, severity filter), Рефакторинг (diff preview, apply to file/project).
- Quick Fixes — «Добавить guard (контракт проекта)», «Применить все автофиксы в файле», «Пропустить (# veritas-core: skip …)» with a mandatory reason. Correct indentation.
- Local explanation (no AI) — COAA report: sinks, required guards, examples, recommendations.
- Settings panel — form for
pythonPath,scriptPath,runOnSave,minSeverity; saved immediately viaconfiguration.update(). - Refactoring — file and workspace commands; shows how many fixes will be made and a real diff editor preview before applying.
- Built-in help — how contract analysis works, how to add your own guards, examples.
- Status-bar security score
$(shield) VeritasCore: N, coloured by worst severity, click → panel. - Content-hash caching — re-analysis is skipped when file text is unchanged (faster re-runs).
- Native diff preview before applying a refactor (virtual document +
vscode.diff). - Severity model with colour coding, icons, and confidence progress bars in the panel.
- One-click “skip with reason” action that uses the engine’s
# veritas-core: skipbypass safely. - Precise, mode-aware fixes (inline for
;-one-liners, new indented line otherwise) carried from the engine so edits are correct, not just hints.
python3 veritas_core.py <project> # gate staged files (exit 1 on violation)
python3 veritas_core.py <project> --learn # advisory only
python3 veritas_core.py <project> --apply [--no-interactive]
python3 veritas_core.py <project> --json --file <f.py> # editor contract: {policy, violations}Guard detection is structural (AST), no hardcoded names: x = f(x) or sink(f(x));
input() is a source, not a guard. Fallback contracts apply when the project teaches none.
- Status-aware data flow. Every sink is classified as
guarded/unguarded/unknown/safe, not a binary. This is what separates signal from noise:- Guards inside f-strings & concatenation are now seen:
os.system(f"ls {shlex.quote(x)}")andos.system("ls " + shlex.quote(x))are recognised as guarded (previously false positives). unknownfor values whose origin can't be proven statically — function parameters, ternaries, tuple-unpacking, globals. These are not blocked; the editor shows them as a low-severity advisory ("поток данных не прослеживается — проверьте вручную"). This kills the classicdef run(cmd): os.system(cmd)false block.safefor constant commands (os.system("ls -la")) — no external input, no flag.- Transitive flow (
y = f(x); sink(y)) and last-definition-wins (re-binding to a source un-guards) are preserved and tested.
- Guards inside f-strings & concatenation are now seen:
.veritascoreignore+ sensible defaults (.venv,node_modules,build,dist,__pycache__,*_pb2.py,migrations) — don't learn from or gate vendored/generated code.--min-confidence <0..1>— strict vs. advisory gating profiles (default0).
Two ways to judge the engine on real code (this is the most useful thing here):
CLI summary per project:
python3 veritas_core.py <repo> --audit # human summary
python3 veritas_core.py <repo> --audit --json # machine-readableShows files scanned, sink totals, the guarded/unguarded/unknown/safe split, coverage (guarded ÷ determinable), learned contracts, and the worst files.
Web dashboard — paste a GitHub URL or scan a folder of clones:
python3 veritas_web.py # empty dashboard; paste URLs in the UI
python3 veritas_web.py <folder-with-repos> # also audits already-cloned reposThe header has a URL field: paste https://github.com/owner/repo, press
Проверить, and it shallow-clones into a temp dir, audits it, shows the result,
and deletes the clone. Coverage, status bars, learned contracts and worst files are
shown side by side. Stdlib http.server only; JSON at /api.
Requires git and network access (cloning is blocked in offline/sandboxed
environments). Only https/ssh URLs from github/gitlab/bitbucket/codeberg are accepted,
passed to git as an argv element (no shell), so URLs can't inject commands.
-
Source-aware + non-guard denylist. Known taint sources (
input,request.args.get,os.getenv,sys.argv, …) flowing into a sink areunguarded; wrappers that sanitise nothing (str,int,os.path.join, …) are never mistaken for guards — this fixes thesubprocess.run → strfalse contract seen on a real repo. -
One-hop interprocedural taint (same file). A sink fed by a function parameter is resolved through the function's call sites:
def run(cmd): os.system(cmd)is flagged when some caller doesrun(input()), stayssafewhen callers pass constants, and stays honestunknownwith no callers. -
Interprocedural taint — class methods &
self.<attr>(same file).obj.method(input())/self.method(input())now taint the method's parameter;self.attr = input()in one method taintsos.system(self.attr)in another. Same-named methods across classes, or an attr assigned in two classes, stay honestunknown(no guessing). -
Cross-file taint — EXPERIMENTAL, opt-in (
--cross-file). Resolves a parameter leftunknownby single-file analysis from call sites in other files (name-based; ambiguous names skipped). It is off by default and not validated on real multi-file codebases — cross-file resolution is where false positives are most likely, so enable it only to experiment (veritas_core.py <repo> --audit --cross-file, or the dashboard toggle). -
Shell/eval guard allowlist (accuracy). Only real sanitisers count for a shell or eval sink:
shlex.quote/split/joinforos.system/subprocess.*,ast.literal_evalforeval/exec. An arbitrary project function can no longer become a false contract (thesubprocess.run -> stage_zensical_docsbug found on fastapi).OPEN_GUARDSno longer countsos.path.realpath. -
Safer auto-fix.
--applyinserts a missing import after any shebang/docstring/__future__(never breaking line 1), and the result stays valid Python. -
Faster, lighter audit. Single analysis pass (policy derived from it, no re-parse); streams file-by-file unless
--cross-file. Unparseable files are counted (parse_errors) instead of silently dropped. Flow depth raised 3->6. -
VS Code extension hardening (compiled, not runtime-validated here): CSP nonce via
crypto, apostrophe escaping, engine-path existence check with user notifications,applyAllFixesre-analyses and skips save for untitled docs. -
Large-repository performance. Files over ~1.5 MB (almost always generated) are skipped and counted (
skipped_large); interprocedural taint builders are skipped on files over 200 K chars; the audit reports progress (every 500 files) via a callback — the CLI prints it to stderr and the web dashboard to its console, so a long scan of a huge repo (e.g. TensorFlow) is visibly working, not a silent hang. -
Accuracy spec applied (see ACCURACY_SPEC.md). Guards are an allowlist per sink, never an arbitrary wrapper. Fixed two false contracts found on TensorFlow:
pickle.loads -> pickle.dumps(serialization is not a guard; pickle.loads now has an empty allowlist) and the argv-list case —subprocess.run([...])withoutshell=Trueis not shell-injectable and is now classifiedsafeinstead of guessing a guard from a list element. -
Incremental content-hash cache (speed). Audit caches per-file results in
.veritascore_cache.json; unchanged files are restored instead of re-parsed. On a rescan this is ~4-5x faster (validated: 1500 files 456ms -> 103ms), which is the real win on single-core machines where parallelism does not help. Disable withuse_cache=False. Add.veritascore_cache.jsonto your.gitignore. -
Return-value taint (coverage).
def get(): return request.args.get('x')followed byos.system(get())is nowunguarded(wasunknown). Only sources (unguarded) and constants (safe) propagate through returns; a guard applied inside a function is NOT claimed across the call (context safety) — it staysunknown. Ambiguous/passthrough returns stayunknown. -
Parallel audit (
--jobs N). Files are analysed across processes; the dashboard uses all cores. Combined with the content-hash cache, cold scans parallelise and warm rescans hit cache. -
Dangerous-configuration advisory (depth). A sink whose source can't be traced but whose configuration is inherently dangerous —
shell=Truewith a non-constant command,eval/execof a variable,os.systemof a variable — is flaggedhighrisk (medium severity) instead of staying silent. It remainsunknown(never a falseunguarded) and excludes thin parameter wrappers (def run(cmd): os.system(cmd)) to avoid noise. Surfaced in the editor JSON and counted in the audit (high_risk). -
Cross-project learning (
knowledge.py). Accumulate guard contracts across many scanned projects into a shared base (--learn-into <db>), then surface ecosystem deviations on a new project (--knowledge <db>): "everyone guards this; you don't". Four protections keep the base clean: an allowlist gate (an unsafe guard likesubprocess.run -> stris rejected no matter how many projects teach it), three filters (>=3 projects, >=50% in-project frequency,=60% cross-project consensus; otherwise quarantine), provenance (every contract lists its source projects and can be rolled back with
forget_project), and anti-contracts (deviation-from-ecosystem advisories). It's explainable statistics, not ML. The dashboard has a 🧠 Knowledge tab showing each contract's guard, status, consensus and source projects.
"Low false-positive rate" is only a claim until measured, so the repo ships a
benchmark over a labeled corpus (vulnerable cases, safe "FP-trap" cases that fool
naive tools, and genuinely ambiguous cases). Because the engine has four states,
it can ABSTAIN (unknown) — which is NOT counted as a false positive. Current
result on the bundled corpus:
FALSE-POSITIVE RATE : 0.0% (61-case corpus across 7 CWE categories)
FALSE-CLEAR (FN) : 0 (never said safe when vulnerable)
detection : 100.0% (all 29 vuln caught; 0 missed)
over-claim : 0 (never claimed safe without proof)
Run: python3 benchmark.py. The corpus is extensible; add competitor results on
the same snippets to compare honestly. These numbers are on a small curated
corpus, not a proof of real-world superiority — they are a measurable, repeatable
baseline, per Principle Zero.
-
Dashboard learning toggle. With 🎓 learning ON, each scanned/cloned repo is folded into the knowledge base automatically (off by default — Principle Zero: you opt in to what the base learns from). The allowlist gate + three filters mean no single project can activate a bad contract.
-
Secret / credential scanning (
secrets_scan.py). High-precision detection of well-known credential formats (AWS, GitHub, Google, Stripe, Slack, private keys, JWT…) plus hardcoded passwords. True to Principle Zero it favours precision: placeholders, env templates (${VAR}), and example values are deliberately ignored to avoid the false positives that plague secret scanners. Secrets are masked in output (never echoed). CLI:python3 secrets_scan.py <dir>; also shown in the dashboard 🔑 section of the Errors tab. -
Errors tab. The dashboard ⚠ Errors page lists every proven
unguardedand high-risk finding with file:line, sink, and a plain explanation — plus the secret-scan results.unknowncases are excluded by design. -
Expanded sink coverage (7 CWE families). Beyond command/SQL/code/pickle, the engine now covers SSRF (
requests.*,urllib.request.urlopen,httpx.*), SSTI (render_template_string), XXE (lxml.etree.parse/fromstring,xml.etree.ElementTree.*), more deserialisation (yaml.unsafe_load,yaml.full_load,marshal.loads),os.popen, andexecutescript. For multi-arg sinks only the dangerous argument is checked (e.g. the URL inrequests.post(url, data=...)), so a tainted POST body is not mis-flagged as SSRF. All added with empty/known allowlists — no new false positives. -
Flow-sensitive taint (depth). Taint follows assignment chains, and sanitisation is branch-aware: a guard applied only inside an
ifis NOT claimed asguarded(the else-path leaves raw input), while a guard in the same branch as the sink is. Unconditional overwrite with a constant clears taint. Coverage also includes.format(),str.join()of a tainted iterable, and ternary expressions (either branch may reach the sink). No new false positives. -
Import-aware cross-file analysis (
--cross-file). Cross-file taint is now resolved through the caller's imports (from mod import f,import mod+mod.f()), not just by unique name — so aprocess()in one module never absorbs taint meant for an unrelatedprocess()elsewhere. Adds cross-file return taint:from src import get; os.system(get())sees thatget(defined in src) returns user input. Ambiguity is skipped (a module basename mapping to 2+ files is not resolved), keeping false positives out. Still opt-in until validated on large real codebases. -
Secret-scan noise control. Documented dummy credentials (e.g. AWS's
AKIAIOSFODNN7EXAMPLE) and obviously synthetic values (repeated-character tokens) are never reported; findings inside test/fixture/example paths are demoted to low confidence and labelled, not suppressed (a real key in a test is still a leak). Real keys in production paths stay high-confidence. -
Local file upload (dashboard). The audit page has a 📂 Upload .py control: pick one or more local Python files and they're analysed as a project (taint + secrets), results shown on the Errors tab. No git needed.
-
Augmented-assignment taint.
x = 'ls '; x += input()is now correctlyunguarded(previously a falsesafe):+=augments rather than replaces, so appended taint is tracked. -
Dict-access taint.
d = request.args; d.get('x')(and.getlist/.pop/.setdefault) propagates taint when the receiver is a source dict; a benign or unknown dict stays safe/unknown — no false positive. -
Contract guards are allowlist-checked. A derived/learned contract can only surface an admissible sanitiser; the engine never suggests a bogus guard like
picture1.getfor a sink (e.g.requests.get) whose allowlist is empty. -
Deep taint binding (toward 85-90% coverage, Zero FP). Taint now follows for-loop variables over a tainted iterable, walrus
:=, tuple/list unpacking, dict-literal values, list/set/dict comprehensions, subscripts of a tainted container, dict views (.values()/.keys()/.items()), and string transforms (.split/.strip/.lower/.replace/.encode…, which derive from but do not sanitise their receiver). Clean iterables, constants and unknown receivers stay safe/unknown — no new false positives. -
Numeric coercion sanitises (FP fix + coverage).
int(x),float(x),len(x),bool(x),ord(x)and similar produce a number/bool that cannot carry a shell/SQL/code injection, so they yieldsafe— fixing a former false positive onos.system(str(int(user_input)))and letting a helper that returnslen(x)be recognised as safe. SQL f-strings are now classified by their interpolations: a numeric/constant interpolation issafe, anything else staysunguarded(parameterisation is the fix). -
Guard-wrapper inference (new, sound depth). A helper that provably applies a known sanitiser to its parameter —
def safe(x): return shlex.quote(x)— is recognised as a guard wrapper from its body and clears a sink ONLY when the wrapped guard fits that sink's type (a shell wrapper never guardseval). Proven from the function body, never guessed from usage, so no falseguarded. -
Performance. Per-scope parent maps and assignment lists are cached and computed in a single AST walk (cleared between files), cutting redundant tree traversals on the single-core hot path. Parallel audit (
--jobs N) is gated behind a workload-size threshold so small repos run serially (process startup would cost more than the work) while large repos engage the extra cores; the parallel path is verified to produce identical results to the serial one. -
Lazy analysis (measured 4.9x on real-shaped repos). Files with no sink call at all skip every interprocedural taint build — most files in a project (tests, configs, helpers) contain no sinks, so this is a large, sound win (no sinks -> no findings).
-
isinstance numeric narrowing. Inside
if isinstance(x, int|float|bool):the variable is proven a number and cannot inject, so it issafefor that branch. Strictly bounded: numeric types only (astrthat passes isinstance still injects), positive branch only (theelsestays tainted), and voided by any reassignment between the check and the sink.is not None/hasattrnever narrow, since they do not sanitise. -
Cross-file guard-wrapper resolution (cross-file mode). A sanitiser defined in a shared module (e.g.
utils.security.safewrappingshlex.quote) now clears a call in another file that imports it — the most common real-world pattern, previously leftunknown. Sound by construction: the wrapper is proven from its body, only resolved when the name is defined exactly once across the project (no ambiguity), and still checked against the sink's type (a shell wrapper never clearseval). Reduces false positives on correctly-guarded code without any risk of a falseguarded. -
Polished dashboard UI. The web dashboard now shares one cohesive dark theme: sticky blurred header, gradient action buttons, refined cards and tables with hover states, better typography and spacing, and focus rings on inputs. Purely cosmetic — no change to analysis behaviour.
-
Performance: per-scope caching. Hot AST traversals (scope assignments, name binders, function returns, parent map) are memoised per scope/function and cleared between files, removing most redundant walks. Cold single-file audit is measurably faster; warm re-runs stay near-instant via the content-hash cache. Parallel audit is gated by a size threshold so multiprocessing overhead never makes small scans slower than serial.
-
Dashboard: folder analysis + clearer error explanations. A new "Анализ папки" control audits a local directory by path (for large projects, no upload limit; auto-parallelises). Each finding now explains itself: the vulnerability class with CWE id, why it is dangerous with a concrete attack example, and a specific fix — tailored per sink (command injection, SQL, code exec, deserialisation, path traversal, SSRF).
-
Engine hardening: container-mutation taint. Fixed a false-
safe:xs = []; xs.append(input()); sink(xs[0])is nowunguarded— taint added to a list/set/dict after construction (append/extend/insert/add/update) is tracked, while clean containers stay safe (no new false positives). -
Dashboard: fix-it guidance + summary. Each finding shows a ✗ bad / ✓ good one-line code example for its sink, and the errors page leads with at-a-glance counts (injections, dangerous configs, secrets).
-
Engine hardening (two soundness fixes). (1) A guard in one branch of a ternary/boolean no longer clears the whole value —
shlex.quote(x) if c else xis correctlyunguarded(was a falseguarded). (2) A clean unconditional reassignment after a tainting mutation now clears the taint (no false positive). Boolean expressions (a or b) are taint-combined. -
Fast mode for large repos.
--fastuses all cores, parallelises sooner, and skips the slower cross-file pass — for scanning big GitHub repos quickly. -
Friendlier dashboard. An empty dashboard now shows a short welcome with the three ways to start an analysis and where to read explained findings.
-
Team / coop mode (git-incremental).
--changedscans only files changed in the working tree + staging vs HEAD;--since <ref>scans everything changed since a branch. Fast on large repos (untouched files are never parsed), ideal for PR gates and pre-commit — a ready hook ships inhooks/pre-commit. Exit 1 on a proven issue in changed code. -
Redesigned dashboard (clear layout + settings). The cramped header (three forms and six buttons in one bar) is replaced by: a clean top nav (Аудит / Ошибки / База знаний) with active-page highlight; a "Запустить анализ" panel with three labelled method cards (GitHub URL, local folder, file upload); and a "Настройки" panel with on/off switches and descriptions for learning and cross-file analysis. Results live in their own panel. Consistent across pages.
-
Cross-file performance fix (major). Cross-file analysis previously ran serially with no cache, so on a multi-core machine it could be many times slower than the (parallel) single-file path — the dominant cause of slow cross-file scans. It now builds the project-wide summaries once and analyses files in parallel across all cores above the size threshold, producing identical verdicts to the serial path (covered by a serial-equals-parallel test).
-
Code audit fixes.
# veritas-core: skip <reason>is now honored in the audit report and dashboard (not just the gate) — a reviewed finding is suppressed and counted underskipped. Removed dead code (_has_syntax_error). -
Dashboard: team panel + settings. A "Командная работа" panel shows ready-to-copy commands for incremental PR scans (
--changed/--since), the pre-commit hook, the CI workflow, and fast mode. The "Настройки" panel exposes learning and cross-file toggles with descriptions.
A dependency-free unittest suite (96 tests engine + 11 learning), including cases that reproduce the
exact false contracts found on Flask (sqlite3.execute → generate_password_hash,
open → os.path.join) and the FastAPI template (sqlite3.execute → delete) — now
fixed by sink-typed contracts (SQL guard = parameterisation; os.path.join is not a
traversal guard). Run:
python3 -m unittest test_veritas_core -v- The
.vsixhere was assembled directly (valid OPC layout:extension.vsixmanifest,[Content_Types].xml,extension/) because this environment has no network fornpm install/@vscode/vsce. It is unsigned, which is fine for localcode --install-extension. - TypeScript was transpiled with
tsc --noCheck(types stripped;require('vscode')is provided by the editor at runtime). Each emitted JS file passesnode --check. - The engine ↔ extension JSON contract and the fix edit math are verified
end-to-end. The live UI (panels, diff editor, status bar) could not be exercised in a
headless environment; it uses stable VS Code APIs. Run
build.shfor a fully type-checked,vsce-signed package when you have npm.