--- name: security-monitoring description: How to monitor and fix security issues in Massing — CodeQL alerts, dependency audits, secret scanning, and ReDoS/XXE fixes. Invoke after a push (standing directive) or when doing a hardening pass. Emphasises that a green CodeQL *run* != zero alerts. --- # Security monitoring — Massing > Standing directions for this repo: [docs/roadmap-directions.md](../../../docs/roadmap-directions.md). Read those first. Standing directive: **check CodeQL after every push and fix any HIGH that appears.** ## CodeQL — check ALERTS, not run status A "CodeQL Advanced" workflow run showing `completed success` only means the scan executed — it does NOT mean zero alerts. Alerts live in the code-scanning API: ``` gh api repos/ibuilder/massing/code-scanning/alerts --jq '[.[] | select(.state=="open")] | length' gh api "repos/ibuilder/massing/code-scanning/alerts?state=open&per_page=30" \ --jq '.[] | "\(.rule.security_severity_level // .rule.severity) | \(.rule.id) | \(.most_recent_instance.location.path):\(.most_recent_instance.location.start_line)"' ``` After pushing a fix, wait for the next CodeQL run to re-scan, then re-query — the count drops when alerts auto-close. ## Common fixes - **`py/polynomial-redos` (ReDoS):** a free-text scanner using unbounded `\d+` / `[\d,]+` / `\s*` under `re.search` re-scans → polynomial on a crafted long string. Fix: **bound the quantifiers** (`{1,n}` not `+`/`*`) and **cap the input** (`text[:20_000]`) before the regex. Verify detection still works on real inputs + a 100k-char crafted input returns in <100 ms. - **XXE:** parse untrusted XML with `defusedxml.ElementTree.fromstring` (catch `ET.ParseError` + `defusedxml.common.DefusedXmlException` → return empty). Never bare `xml.etree`. - **Weak-hash flags** (`py/weak-sensitive-data-hashing`, bandit B324): SHA-1/MD5 used for non-crypto identity/cache keys → add `usedforsecurity=False`. ## Local audits (run before a hardening release) ``` cd apps/web && npm audit --omit=dev cd services/api && ./.venv/Scripts/python.exe -m bandit -r src ../data/src -ll -ii # HIGH+MED, high-confidence cd services/api && ./.venv/Scripts/python.exe -m pip_audit --progress-spinner off # pip install pip-audit first # secret scan (no gitleaks): grep the source tree for high-signal patterns, exclude node_modules/venv/tests ``` Pin CVE'd transitive deps in `services/data/requirements.txt` (e.g. `pillow>=12.3.0`). ## SEC-DATAFLOW — where to spend review effort (empirical, from vuln-localization research) Difficulty follows **structure, not severity**: multi-file / cross-import data flows are the hardest class to localize and the least covered by SAST pattern matching. Prioritize hand/agentic review on flows spanning **router → dependency helper → model/engine → storage** (request data crossing module boundaries), not single-file sinks CodeQL already patterns. SAST is coverage-limited by its rules; the search→verify→refine agentic review (the security-review subagent + this checklist) is the complement, not a duplicate. ## Hand-audit checklist (HARDEN passes — beyond CodeQL's reach) Run over a release range (`git diff vX..vY --stat`, then read the changed files). Every class below produced a real finding in the v0.3.510 HARDEN-2 pass: - **Privilege side doors:** a stricter endpoint (admin + audited) whose work is ALSO reachable through a generic gate — the job queue (`routers/jobs.py` `_KIND_MIN_ROLE`), bulk endpoints, MCP dispatch. New privileged operations must gate every path, not just the front door, and audit each. - **Unbounded stored data → cheap-GET amplification:** anything an editor stores that a viewer-level GET later evaluates (rules, configs, lists) needs count/size caps at save (`rule_library.MAX_*` is the pattern; `schedule_baselines._MAX` too). - **Payload caps must keep the NEWEST rows:** `order_by(asc).limit()` silently hides everything created after row N — cap with `desc().limit()` then re-sort ascending for stable display. - **innerHTML interpolation:** any file/server/model-derived free-text must pass `esc()` (panels, from `ui/charts`) or `escapeHtml` (viewer). Numbers/`.toFixed`/server-constant labels are fine. CodeQL catches some (`js/xss-through-dom`) but not all sinks. - **Hand-rolled parsers:** operator splitting must be leftmost + quote-aware (the QUERY-DSL `_find_op` fix); validate what stored selectors will DO, not just that they parse — a silently-never-matching rule is a false "pass" downstream. - **Serializer/format parity on swaps:** replacing a serializer (orjson) or parser needs the FULL suite as the parity gate + explicit deltas checked: non-str keys, float subclasses (numpy.float64), NaN/Infinity, tuples/sets, str subclasses. - **Live-UI teardown:** modal/panel tools with timers or visibility state need an `onClose` hook (`ui/result.ts showResult(title, render, onClose)`) — closing must stop timers + restore state. - **External-format imports:** skip container/rollup rows (MSPDI `1`; XER non-TASK tables) — named+dated summary rows import as phantom records otherwise. ## SEC-SUPPLY — license / SBOM audit (run before a hardening release) `aec_api.supply_chain` is a dependency-free (stdlib `importlib.metadata`) supply-chain tool: ``` cd services/api && PYTHONPATH="src;../data/src" ./.venv/Scripts/python.exe -m aec_api.supply_chain # informational cd services/api && PYTHONPATH="src;../data/src" ./.venv/Scripts/python.exe -m aec_api.supply_chain --gate # exit 1 on STRONG copyleft ``` - Classifies every installed distribution's license: **permitted** (MIT/BSD/Apache/ISC/PSF/Unlicense/Zlib) · **copyleft** · **unknown**, splitting **STRONG** (GPL/AGPL — the disallowed hard line) from **weak** (LGPL/MPL — accepted for the ifcopenshell/certifi core deps, but surfaced). Word-boundary matched (naive substring makes "EXE**MPL**ARY" false-match `mpl`). - `--gate` fails only on STRONG copyleft, so it never breaks CI over the known LGPL/MPL core deps. **Known strong hits are venv-incidental, NOT declared deps** (e.g. `pymupdf`/AGPL, `pyinstaller`/GPL build tool) — confirm a strong hit is absent from `services/*/requirements*` before acting; a declared strong-copyleft dep is a real violation to remove. - `supply_chain.sbom()` emits a minimal CycloneDX 1.5 component list. `supply_chain.pdf_sanity(bytes)` is a lightweight pre-ingest PDF check (header/EOF/size + active-content flags: JavaScript/Launch/EmbeddedFile/OpenAction) — NOT a full parser (no AGPL PyMuPDF). ## Not a vulnerability (don't manufacture fixes) Per the security-review exclusions: DoS/resource-exhaustion, secrets-on-disk (handled elsewhere), rate-limiting, SSRF that only controls the path, client-side authz, log-spoofing, outdated-dep advisories (handled separately). Fix concrete, exploitable HIGH/MED with a clear path. See memory: `codeql-monitoring`, `perf-sec-p0-patterns`, `innerhtml-xss-esc`, `query-dsl-selector-spine`.