Security checklist (applied to every change)¶
Security is built in as each piece is written, not reviewed afterwards. Most items are enforced automatically; the rest are ticked in the pull request.
Enforced automatically (CI fails if broken)¶
| Guard | Where |
|---|---|
No eval, exec, compile, __import__, pickle, marshal, shelve, subprocess, os.system, os.popen, input in src/ |
tests/test_security_guards.py (AST scan) |
No .format() / .format_map() called on non-literal strings in src/ (format-string attacks like "{0.__class__}") |
tests/test_security_guards.py |
No network modules (urllib, http, socket, requests, ftplib, smtplib, …) in src/, except the future opt-in geo/download.py and localserver.py (tabsight serve, 127.0.0.1 by default) |
tests/test_security_guards.py |
pandas file readers called only in tabsight/io.py, which rejects URLs |
tests/test_security_guards.py + tests/test_io.py |
Bandit-style lint rules (ruff S) |
pyproject.toml, CI lint job |
| Known-vulnerable dependencies | pip-audit CI job; Dependabot for pip, npm and GitHub Actions |
| Vendored files unchanged | SHA-256 lock checked by tools/fetch_vendor.py; byte-exact via .gitattributes |
| Public functions never change the input DataFrame | "unchanged input" tests per module (tests/test_never_mutates.py) |
| Formula language cannot run code or exhaust resources | tests/test_formulas.py: injection attempts, length/token/depth limits, fuzzing, overflow handling |
Rules for every new piece of code¶
- Validate every user-supplied string at the boundary: colours (hex only), fonts (safe characters only), format patterns (own parser), locale and currency codes (fixed patterns), column names (must exist; "did you mean"), saved specs and schemas (strict schema, unknown keys rejected, size limits).
- Never build code or markup from data. No string formatting of user text into HTML, JS, CSS or regexes. HTML escaping happens at render time and is tested with malicious values.
- Untrusted text in messages is truncated and
repr()-quoted (tabsight.warnings.safe_repr), so huge or control-character values cannot flood or spoof warnings. - No network, no clock, no randomness in library code. "Today" comes from the data or
as_of=. - Bounded work on user data: sampling, caps on distinct values (hierarchies, near-duplicates), anchored linear regexes only (no nested quantifiers).
- Safe file reading: local paths only; XML formats need
defusedxml; Parquet needs a patchedpyarrow(>= 15, after CVE-2023-47248). - Every new input gets a malicious-input test in the same change.
Pull request sign-off¶
Each pull request states: new inputs added, how each is validated, and which security tests cover them.