Module 17 Release Notes — Security Operations Center, Threat Intelligence & Autonomous Research Platform
Features added
Security Asset & Continuous Attack Surface Monitoring — SecurityAsset
ownership model plus scheduled/event-driven attack-surface scans that emit
AttackSurfaceEvent rows, reusing the existing job-queue and EventBus
infrastructure rather than a new scheduler.
Change Intelligence — correlates successive attack-surface scans of the
same target into "potential exposure" signals (new open port, new
subdomain, expired cert, etc.) without a separate diffing engine — built on
the same AttackSurfaceEvent stream.
Threat Intelligence Engine — ThreatActor/ThreatCampaign/Indicator
data model, a provider abstraction (ThreatIntelProviderService) with a
Community-Edition-friendly public-feed adapter (CISA KEV, no API key
required), and a manual STIX 2.1 Bundle import path (StixTaxiiService)
for any workspace that wants richer or paid feeds without the platform
requiring one. See docs/threat-intelligence.md.
Threat matching (ThreatMatch) — correlates active indicators against
a workspace's known targets/assets, feeding both the SOC dashboard and the
security report builder.
Incident Management — full lifecycle (OPEN → INVESTIGATING → CONTAINMENT → ERADICATION → RECOVERY → CLOSED/REOPENED), linked
playbook executions, evidence attachment via the existing Evidence
infrastructure (no new evidence store), and an AI-assisted investigation
workflow. See docs/incidents.md.
AI SOC Analyst & AI Investigation Workflow — reuses the existing AI
provider abstraction and tool-calling framework from Modules 5/11; every
free-text input reaching a prompt (analyst goal, scan-tool output,
indicator description) is wrapped with the shared fenceUntrusted()
utility (task #493) rather than concatenated raw.
Defensive Security Playbooks — extends the existing Playbook engine (Module 16) with SOC-specific playbook templates rather than a second execution engine.
Risk Engine v2 — extends (does not replace) the Module 16 Risk Engine with prioritization scoring (score/tier/factors/recommended priority) for the exposure-management workflow.
Remediation tracking — RemediationTask lifecycle with a verification
step, automation-rule-driven creation from incidents/findings/SLA breaches
via the existing EventBus. See docs/remediation.md.
SLA Engine — auto-derives due dates and breach status per incident
severity/status from workspace-configurable policy, no manual deadline
entry required. See docs/sla.md.
Security Metrics & dashboards — MTTD/MTTR/MTTV/SLA-compliance,
computed on request from existing rows (no separate metrics store), backing
both the composed /soc dashboard and the /executive-security trend
view. See docs/risk.md and docs/soc.md.
Platform surfaces — REST API (/api/v1/soc,/assets,/intel,/incidents, /remediation,/risk,/sla), Swagger, CLI commands, and TS/Python/Go SDK
updates (tasks #489-#491), all built on the existing API/CLI/SDK
conventions from prior modules — no new API framework, no new SDK
generation pipeline.
Knowledge Graph, AI Memory, Global Search, Notification, Automation Rules extensions — every one of these reuses its Module 16 (or earlier) infrastructure directly; none introduces a parallel graph store, memory store, search index, notification channel, or execution engine, per the standing "do not duplicate infrastructure" constraint for this module.
Security audit summary (task #492 + #493)
A dedicated pass covered IDOR, SSRF, XSS, CSRF, SQL/command injection, path traversal, upload handling, SSE auth, tenant isolation, indicator poisoning, STIX parsing safety, feed poisoning, webhook abuse, AI prompt injection, AI tool abuse, AI memory leakage, graph leakage, automation privilege escalation, and SLA/remediation authorization. Findings fixed in place (not merely disclosed):
- Two IDOR-class gaps where a
correlationInsight/similar lookup filtered byprojectIdalone, withoutworkspaceId— fixed inIncidentsService/RemediationService/AiSocAnalystService/AutomationRulesService(task #492) and, found again inIncidentInvestigationService'sCORRELATION_REVIEWstep, fixed the same way (task #493). - Centralized the ad hoc prompt-injection fencing pattern task #492
introduced into a shared, documented utility
(
common/utils/ai-prompt-fencing.util.ts) and applied it to both AI prompt builders that exist in this module (task #493). IncidentPlaybooksService.listForIncident— unbounded N+1 sequential resolution replaced with a capped (take: 50), concurrent (Promise.all) resolution (task #494).- 6 composite indexes added/widened (
ThreatActor,ThreatCampaign,ThreatMatch,Incident×2,RemediationTask×2,SlaEvent) to match the query shapes the dashboard, matching, and breach-detection code paths actually use — one narrower pre-existing index dropped in favor of a widened superset rather than kept redundantly (task #495).
STIX/TAXII import, the one user-controlled bulk-ingest path in this
module, treats every object as untrusted external data by construction:
non-Bundle payloads and malformed objects arrays are rejected outright;
individual malformed/oversized/unrecognized objects are counted as
rejected rather than crashing or corrupting the import; imported content
is always workspace-scoped (never global); string fields are
length-capped. Covered by stix-taxii.service.spec.ts (task #496).
No live prompt-injection vulnerability via threat-intel content was found
— no current AI-prompt builder consumes threat-intel description/
aliases/summary fields directly. The fencing utility exists
preventatively, documented as such, so the first builder that does
consume that content inherits the protection by construction rather than
by remembering to add it later.
Tests (task #496)
7 new spec files covering SLA calculation, automation-rule authorization,
AI SOC Analyst prompt construction, threat matching, threat-intel provider
failure handling (network outage, non-OK HTTP, partial-import accounting,
per-run import cap), STIX/TAXII parsing safety, and SOC dashboard
workspace-resolution auth. All verified via ts.transpileModule() (the
real TypeScript parser, full syntax validation) rather than a live jest
run — see "Known gaps" below for why.
Community Edition guarantee
No paid threat-intelligence subscription is required anywhere in this
module. ThreatIntelProviderService defaults to no provider configured
(explicit THREAT_INTEL_PROVIDER_NOT_CONFIGURED error on refresh, never a
silent no-op); the one built-in feed adapter (CISA KEV) is free and
requires no API key; manual/local import via STIX bundle or the
POST /intel/indicators endpoint works with zero external dependency.
Verification loop (task #498)
Re-attempted every check task #495/#496 had left as BLOCKED BY ENVIRONMENT, plus two checks that hadn't been run yet, and root-caused the failures rather than re-stating them unchanged:
packages/shared— PASSED, for real. Bothtsc --noEmitand a fulltscemit build completed cleanly (exit 0, JS output produced) against the M17 additions (ai.ts,bugbounty.ts,knowledge-graph.ts,module17-endpoints.ts,soc-threat-intel.ts). This package has no@nestjs/*dependency, so it isn't affected by theapps/apifinding below — this is a genuine, complete, type-checked pass, not a substitute.- All 40 Module 17 files (
apps/api+packages/shared) — PASSED real syntax validation.ts.transpileModule()(the actual TypeScript parser) reported zero syntax errors across every new or modified file from tasks #492-#497. apps/apifulltsc --noEmit— completed (did not time out this time) but is not usable as a signal, and here is why. It reports ~2,574 errors, the overwhelming majorityCannot find module '@nestjs/...'/'@opentelemetry/...'/ similar. Traced the root cause:apps/api/node_modules/@nestjs/commonresolves (via symlink) to a pnpm-store directory that has some subfolders (decorators/,interfaces/,services/, etc.) but nopackage.jsonand no rootindex.js/index.d.ts— the package was never fully installed, the exact same defect class already confirmed forprismaandjestin tasks #495/#496, now confirmed to extend to@nestjs/commonand, by inference, the rest of the@nestjsfamily every module in this codebase depends on. This is a sandbox-wide dependency-installation defect, not specific to Module 17 and not fixable from application code. Spot-checked the four non-"Cannot find module" errors that touched Module 17 files, to rule out a real bug hiding in the noise — all four are downstream artifacts of the same defect, not code bugs:AuditEventType"missing"INCIDENT_CREATED/INCIDENT_STATUS_CHANGED/INCIDENT_ASSIGNED(all three are present inschema.prismaat lines 1572-1574 — the generated Prisma client is stale, same root cause as the brokenprismaCLI);IncidentTimelineEventType"missing"PLAYBOOK_EXECUTED(present at line 6852, same stale-client cause);PrismaClient"missing".slaPolicy(theSlaPolicymodel exists at line 3973, same cause, and this one is inenterprise-analytics/enterprise-reporting, pre-existing code Module 17 never touched);AppException"missing".message(it extendsHttpException extends Error, which trivially has.message— the error only appears becauseHttpExceptionitself can't resolve from the broken@nestjs/common). None of the four is a real defect.eslintis also broken in this sandbox — new finding this pass../node_modules/.bin/eslint --versionfails withCannot find module '@eslint-community/eslint-utils'; that package does not exist anywhere undernode_modules/.pnpmin this sandbox. Same defect class asprisma/jest/@nestjs/commonabove. No lint pass was possible for any file in this session, Module 17 or otherwise.prisma/jestCLIs re-confirmed broken, unchanged from tasks #495/#496 — re-checked rather than assumed still-broken; same empty/incomplete pnpm-store package directories found again.- Net assessment: the syntax of every Module 17 file is confirmed
valid, and the one part of the change set with a working toolchain
(
packages/shared) type-checks and builds cleanly.apps/api's type-correctness and the 7 new spec files' pass/fail status remain genuinely unverified in this sandbox — not because the code is suspect, but becausetsc,eslint,jest, andprismaare all broken installs here. A environment with a workingpnpm installshould be able to run all four cleanly; this is disclosed as a sandbox limitation, not swept under a "PASSED".
Final review (task #499)
TODO/FIXME/stub sweep across every M17 module directory: clean, zero
matches. Confirmed the gating infrastructure the standing spec requires
is wired everywhere it needs to be: SecurityApprovalService gates
AutomationRulesService (every action beyond a plain NOTIFY) and
IncidentInvestigationService (every autonomous investigation step);
AiSocAnalystService correctly has none — it is read-only/advisory
(one method, analyzeIncident, never writes anything), so there is
nothing for a gate to gate. Every M17 controller resolves workspaceId
server-side from the authenticated caller (resolveWorkspaceId), with
one deliberate, documented exception:
ThreatIntelController's global-catalog reads (actors/campaigns/
reports/techniques) and POST /intel/refresh operate on the shared
public cache by design, the same posture CveIntelligenceController
(Module 16) already established — refresh is rate-limited
(6/min) since any authenticated user can trigger it.
Went value-by-value through the schema's M17 AuditEventType additions
against the services that should write them, rather than assuming
"the enum exists" meant "someone calls it." Found and fixed three real
gaps — the enum value existed, nothing ever wrote it:
IndicatorsService.createManual/setStatuswrote no audit event at all forINDICATOR_CREATED/INDICATOR_DISABLED/INDICATOR_EXPIRED. Fixed by adding aprivate audit()helper (identical pattern toRemediationService.audit) and calling it from both methods.setStatusgained auserIdparameter to attribute the event to.ThreatMatchService.setAnalystStatus(an analyst manually re-triaging a match) wrote noTHREAT_MATCH_STATUS_CHANGEDevent. Fixed the same way; also gained auserIdparameter. LeftupsertMatch(the automated correlation write, no human actor, potentially thousands of rows per scan) deliberately unaudited via the generic mechanism — see that method's new doc comment for why theThreatMatch.evidenceJSON already serves as its audit record, matchingVulnerabilityMatchingService's existingCveMatchprecedent.SlaService.upsertPolicywrote noSLA_POLICY_CHANGEDevent despite being a security-relevant config change (SLA targets drive breach detection). Fixed the same way; gained auserIdparameter.
Each fix's controller call site was updated, each fix's existing spec
file was updated for the new parameter and given a dedicated audit-log
test, and all 8 touched files were re-verified with
ts.transpileModule() (0 errors).
Considered and left as-is, disclosed rather than fixed:
INDICATOR_IMPORTED/THREAT_INTEL_IMPORT_REJECTED are unused because
StixTaxiiService already writes a more detailed, structured,
queryable audit trail to ThreatIntelImportLog (status/counts/
rejection sample) for every import — a generic AuditEvent on top
would be redundant, not missing coverage.
AUTOMATION_RULE_EXECUTED/AUTOMATION_RULE_BLOCKED are unused for the
identical reason (AutomationRuleExecutionLog). AUTOMATION_RULE_CREATED
and ASSET_OWNERSHIP_CHANGED are genuinely unused with no equivalent
domain-specific log backing them — AssetOwnership in particular has
no write path (create/update) anywhere in this codebase yet, which
reads as a scope gap from task #462 (schema shipped, service didn't)
rather than a missing audit call. Not fixed in this pass — building a
new CRUD surface belongs to a feature task, not a final-review pass —
and carried forward to the final report's technical-debt list.
Known gaps
prismaCLI is broken in this sandbox — confirmed this pass:apps/api/node_modules/prismaresolves (via symlink) to a pnpm-store package directory whosedist/clifolder is entirely empty, with nopackage.jsonpresent — the package's contents were never actually installed, not a path-resolution issue.prisma validate/generatecould not be run. Substituted with a structural schema-field-reference check (every field referenced in a new@@indexactually exists on its model) and manual comparison ofschema.prismaagainstmigration.sql.jestCLI is also broken in this sandbox — confirmed this pass: same class of defect asprisma(apps/api/node_modules/jestsymlinks to a pnpm-store directory that does not contain the expected package contents). No live test run was possible for the 7 new spec files (or any pre-existing Module 17 spec file). Substituted withts.transpileModule()syntax validation (confirms every file parses as valid TypeScript; does not confirm assertions pass, mocks are wired correctly, or cross-file types resolve) — disclosed as the best available substitute, not equivalent to a real test run.- A full-program
tsc --noEmittypecheck ofapps/apidid complete in this pass (unlike earlier modules' timeout reports) but is not a usable signal here — see "Verification loop" above: it surfaces ~2,574 errors that trace back to@nestjs/commonand most of its sibling packages being incomplete pnpm-store installs (missingpackage.json/entry points), the same defect class asprismaandjest.eslintis broken the same way (missing@eslint-community/eslint-utils).packages/shared, which has no@nestjsdependency, typechecks and builds cleanly and is a genuine PASSED result. - The Go toolchain remains absent from this sandbox (consistent with
prior modules) —
packages/sdk-go's Module 17 additions were written and reviewed but not compiled in this pass. - A live end-to-end run of the STIX import, threat-match correlation, or
SLA breach-detection logic against a real Postgres instance was not
performed in this sandbox (no working
prismaCLI to apply the new migration). Manual review ofmigration.sqlagainstschema.prismais the disclosed substitute; a real migration-apply-and-query pass is recommended before production use.