Skip to content

OCCAM Threat Model

Assets

  • Assessments: the conclusion, its confidence grade, and the ACH matrix
  • Evidence sets and actor profiles, whether synthetic or derived from ATT&CK
  • Exported STIX bundles and the TAXII collection consumed downstream
  • Trained classifier artefacts (*.pkl)

Trust boundaries

Boundary Treatment
Ingested reports / IOC text Untrusted data. Parsed only by regex, keyword matching and a linear classifier. Never executed, fetched, or rendered as HTML
Report PDFs (APTnotes) Untrusted files. Text is extracted with pypdf in a child process that has a 60 s timeout and a 40-page cap. PDFs are never rendered or opened in a viewer. Files are checked against their git blob SHA-1 before parsing
Evidence / scenario JSON, API bodies Semi-trusted analyst input. Validation covers Admiralty grades, unknown points_to actors, and duplicate IDs. Request bodies are capped at 1 MB and report text at 500,000 characters; list and string fields have maximum lengths; scenario names are confined to the bundled demo directory (a traversal test covers this)
ATT&CK bundle / KB file Pinned commit plus SHA-256. Parsed as JSON data only
Classifier artefacts Stored with pickle, so only load models you trained yourself. TechniqueClassifier.load also checks the loaded object's type, but that is not a security boundary
Downstream STIX consumers The confidence grade and full matrix are embedded so the uncertainty cannot be silently dropped. Bundles validate strictly against python-stix2

Adversaries and abuse cases

# Threat Mitigation
T1 False-flag operator plants code, language, or metadata markers to frame another actor, or copies the framed actor's rare TTPs/tools (mimicry) Spoofable evidence types are typed as such and down-weighted ×0.5; each framed actor gets a false-flag hypothesis; hard evidence is ranked by least inconsistency. Measured (results/falseflag.md, 274 held-out ATT&CK incidents). With 3 planted markers ACH names the framed group 1.1% of the time, and a baseline that simply ignores spoofable rows never does (0%). Removing the false-flag hypotheses, the discount and the caps still leaves ACH framed only 2.6% of the time: hard evidence contradicting the framed group blocks the frame. Under mimicry ACH names the framed group 27% of the time vs 76% for TTP-similarity and 32% for IDF coverage, but is correct less often than IDF coverage (17.9% vs 28.8%). Residual risk: with authentic markers ACH calls the genuine overlap a frame-up 18.6% of the time
T2 Overconfident analyst or tool names an actor on thin evidence A mandatory unknown hypothesis, conservative tie-breaking, a minimum number of diagnostic items before HIGH is allowed, and leave-one-out sensitivity output. Calibration is measured (Brier/ECE) in results/attribution.md
T3 Poisoned report injects fake TTPs or IOCs Every hit carries a source span so it can be audited, and Admiralty reliability weighting applies. Residual risk: there is no source-reputation store yet
T4 Hallucinated extraction from an ML or LLM extractor The contract requires a SourceSpan on every fact, and classifier hits carry the sentence span. Tests check that the span text matches the source
T5 ReDoS / resource exhaustion from crafted text or PDFs Every regex repetition is bounded (an earlier domain pattern had nested unbounded repeats), and the IOC overlap check is O(log n). A regression test (tests/test_extract.py::test_ioc_regexes_stay_fast_on_crafted_input) requires 200 KB of crafted a. text plus 20,000 IP addresses to extract in under 15 s in CI. In addition, API bodies are size-capped, the TAXII store is capped at 500 bundles, and PDF parsing runs in a child process with a timeout. Extraction is still synchronous, so a large adversarial request occupies a worker until it finishes (no per-request time limit)
T6 Misuse of output as a public accusation about a real group Every output carries a decision-support disclaimer. Benchmark results about ATT&CK groups are evaluation artefacts, and the README gives ethics guidance
T7 Malicious KB or scenario JSON Parsed with json.loads only (no YAML), and values are validated
T8 Exposed API Binds to 127.0.0.1 by default; optional bearer token (OCCAM_API_TOKEN); Host allow-list (OCCAM_ALLOWED_HOSTS) against DNS rebinding; interactive docs (third-party CDN JavaScript) off unless OCCAM_API_DOCS=1; nosniff / frame-deny headers. docker-compose publishes only on loopback, runs as a non-root user with all capabilities dropped, no-new-privileges, a read-only root filesystem and memory/CPU/PID limits. TAXII is read-only

Out of scope

Multi-user auth, a hosted deployment, and storage encryption. The API is a lab and workbench tool; for shared use, put OCCAM behind an authenticating reverse proxy or a real TAXII server.