Research Operations: From Intake To Publication
This is the operating guide for running SecOpsAI as a security research program: continuous registry surveillance, investigating leads, reaching a defensible verdict, and publishing research that holds up to scrutiny.
Every command on this page was run end to end on 9 October 2026 against an isolated ledger (a live npm release and a synthetic positive control), and the results are recorded under Verification record.
Platform map
| Surface | URL / location | Runs on | Purpose |
|---|---|---|---|
| Website | https://secopsai.dev |
Cloudflare Pages (www/) |
Product site and installers |
| Documentation | https://docs.secopsai.dev |
Cloudflare Pages (MkDocs) | This guide |
| Research blog | https://blog.secopsai.dev |
Cloudflare Pages + Worker, comments in D1 | Published research |
| Mission Control | https://dashboard.secopsai.dev |
Cloudflare Pages + Worker | Operator console |
| Core Edge API | https://core.secopsai.dev |
Worker + D1 | Alerts, heartbeats, ontology, coordinator |
| Ledger store | https://ledger.secopsai.dev |
Worker + R2 | Research ledger checkpoints |
| Research worker | GitHub Actions Research Worker | Free scheduled runner | Registry collectors, triage, daily automation |
Every public domain publishes /.well-known/security.txt (RFC 9116).
Daily loop
- The worker runs every 30 minutes (
.github/workflows/research-worker.yml). Each run restores the ledger from R2, runs 20 worker cycles (eight registry collectors, npm enrichment and static triage, external advisory intake, scoring, daily automation, retention), checkpoints the ledger and exits. One run at a time is enforced. - Review alerts in Mission Control (Research) or with
secopsai research alert list. Severity now reflects corroborated behaviour:critical/highrequire a behaviour chain such as install-time execution with network egress; generic capability strings alone stay low. - Open a case for anything worth a closer look (next section).
Health checks:
gh run list --workflow research-worker.yml --limit 5
curl -s https://core.secopsai.dev/healthz
curl -s https://ledger.secopsai.dev/healthz
Investigate a package
secopsai research package --ecosystem npm --package <name> --version <version> \
--comparison-ecosystem npm --comparison-package <name> --comparison-version <previous> \
--research-type package_compromise
The pipeline resolves registry metadata, verifies checksums, quarantines the
artifact, inspects it statically (nothing is installed or executed), compares
it with the previous release, validates IOCs, and opens a case when the
evidence warrants one. Read these fields in the JSON (--json):
| Field | Meaning |
|---|---|
scan.findings |
Rule hits (single regex hits are medium confidence) |
comparison |
Added/removed files, lifecycle-hook changes, indicator deltas |
validated_iocs / rejected_iocs |
Accepted indicators and why others were rejected (e.g. expected service endpoint for this package) |
verdict, severity, confidence |
Deterministic triage, not a publication verdict |
case_id, evidence_ids |
The case and its EVD- evidence IDs for the verdict command |
For a local artifact (for example, one shared by a partner) add
--artifact path/to/package.tgz --source-reference <url>.
Case to publication
Run these in order. Each gate exists to stop a weak claim from being published; none of them can be skipped by a flag.
C=RSC-XXXXXXXXXXXX
# 1. Evidence and brief
secopsai research workflow evidence-matrix $C
secopsai research workflow analyst-brief $C
# 2. Verdict (needs at least one EVD- id that belongs to the case)
secopsai research workflow verdict $C --verdict likely --confidence 80 \
--rationale "What the evidence shows, and what it does not" \
--evidence-id EVD-... --actor "<your name>"
# 3. Reliability chain
for step in plan run-scaffold verify-transition run-full verify-claims \
audit-completeness audit-originality queue-specialist; do
secopsai research reliability $step $C --actor "<your name>"
done
# 4. Specialist + blinded independent review (human path)
secopsai research reliability human-review $C --stage primary \
--verdict likely --reviewer "<analyst A>" --evidence-id EVD-... \
--summary "At least 40 characters explaining the primary assessment"
secopsai research reliability human-review $C --stage reviewer \
--verdict likely --reviewer "<analyst B>" --evidence-id EVD-... \
--summary "Independent confirmation or disagreement, with reasons"
# 5. Visual QA of the would-be post (headless Chrome/Chromium/Brave/Edge)
secopsai research reliability visual-qa $C --auto
# 6. Publication safety review and editorial approval
secopsai research workflow publication-check $C
secopsai research workflow publication-approve $C --actor "<editor>"
# 7. Disclosure: prepare a draft (only for likely/credible verdicts), deliver
# it through the approved channel, or record why none is required
secopsai research workflow prepare-disclosure $C --recipient [email protected]
secopsai research case update $C --disclosure-status disclosed # or not_required
# 8. Draft, publish, deploy
secopsai research case update $C --status ready_to_publish --actor "<editor>"
secopsai research case draft-blog $C
secopsai blog publish blog/drafts/<draft>.json --publish
gh workflow run blog-ops.yml -f action=deploy
Rules the pipeline enforces:
- The independent reviewer must be a different person from the primary
analyst; opposite verdicts require adjudication
(
research reliability adjudicate-review). - Disclosure drafts are refused unless the latest verdict is
likelyorcredible, and re-preparing updates the open draft instead of creating a duplicate. - Visual QA fails on horizontal overflow at 1280 px or 390 px, missing image alt text, or WCAG AA contrast failures; screenshots are stored as evidence.
- Every factual sentence in the draft is checked against the claim ledger; unsupported claims block the draft.
The model-backed specialist path (research reliability queue-specialist
with an execute tier) can replace step 4 when the intelligence bridge is
healthy; see Model bridge.
Security news
News posts are commentary on other publishers' work, so they face a strict gate: they need extracted intelligence (a CVE, CERT/CC VU# or GHSA ID, package, product, IP, or hash), must not be vendor marketing, and need a specific SecOpsAI angle rather than the template.
secopsai blog news-run --limit 8 # fetch + draft
secopsai blog news-review list
secopsai blog news-review show <draft>
secopsai blog news-review edit <draft> ... # add the specific analysis
secopsai blog news-review approve <draft>
secopsai blog news-publish-approved --rebuild
gh workflow run blog-ops.yml -f action=deploy
Prefer original research. A news post should exist only when SecOpsAI adds something: an affected-package check, a detection, or a mitigation.
Blog maintenance
secopsai blog quality-audit # re-check every published post against the gate
secopsai blog retire <slug> ... # remove posts (history stays in git)
secopsai blog rebuild-feeds # index, RSS, JSON feed, sitemap, 404, security.txt
python scripts/verify_blog.py
Comments are stored in D1 (secopsai-blog-comments) as pending. Approve
one with:
npx wrangler d1 execute secopsai-blog-comments --remote \
--command "UPDATE blog_comments SET status='approved', moderated_at=datetime('now'), moderated_by='<you>' WHERE id='<id>'"
Mission Control
- Sign in at
https://dashboard.secopsai.dev. A session is not enough on its own: the account must be inDASHBOARD_OPERATOR_EMAILS(Pages secret) or carryapp_metadata.secopsai_role=operator. - Cloudflare-native mode (Access + D1) activates when
CF_ACCESS_TEAM_DOMAINandCF_ACCESS_AUDare set on the Pages project; theDASHBOARD_DBD1 binding is already in place. See the dashboard repository'sCLOUDFLARE_PAGES.md. - Local mode:
./start-local-dashboard-stack.shinsecopsai-dashboard/secopsai-dashboard, thenhttp://127.0.0.1:45680.
Model bridge
secopsai intelligence bridge doctor
secopsai intelligence bridge configure-models --primary gpt-6.1-sol \
--fallback xai/grok-4.6 --fallback google-antigravity/gemini-3.5-flash-low \
--fallback-mode quota_auth
quota_auth falls back only when the primary model is out of quota or
fails authentication. Choosing fallbacks decides which providers receive
minimized research context; pick providers you are comfortable with.
Verification record
Run on 9 October 2026 with an isolated ledger; no production data changed.
| Pipeline | Result |
|---|---|
Source-first package research (@skyline-ts/[email protected] vs 0.4.2) |
Scan clean, verdict not_substantiated, no case (benign Telegram client) |
| Positive control (synthetic credential-exfil package, never executed) | Flagged: install hook, credential discovery, download-execute, exfil staging; case opened |
| Evidence matrix / analyst brief | 3/3 claims supported; brief lists all detections |
| Reliability chain | Plan, scaffold, transition, full bundle succeeded; completeness 95; originality passed |
| Human specialist + blinded review | Same-reviewer review refused; independent review recorded without disagreement |
| Visual QA (auto) | Desktop 1280 px and mobile 390 px: no overflow, no missing alt text, no contrast failures |
| Publication check, approval, disclosure gate, draft | Draft created with 100% claim evidence coverage |
| Publish + rebuild | Post, JSON feed, RSS, sitemap updated; tables render |
| Worker cycle (fresh ledger) | 8 collectors complete; 100 npm events, 10 static analyses, 1 calibrated candidate |
| News intake (live feeds) | KEV and CERT/CC notes pass; marketing, newsletters, navigation junk blocked |
Defects found during these runs and fixed: false-positive static rules (DGA, browser data, persistence, download-execute, IP:port), code tokens reported as domain IOCs, expected service endpoints treated as IOCs, an overly broad exfiltration heuristic, unusable evidence IDs, an analyst brief that missed scan detections, a claim verifier that blocked every source-first case, specialist review that could never complete under the default tier, visual QA with no way to render a draft, markdown tables rendered as raw text, a crash on redacted artifact locators, missing mobile gutters, daily automation summaries exceeding the database bound, oversized artifacts retried forever, Atom links pointing at comment feeds, and navigation links ingested as news.