Module 16 Release Notes — Global Security Intelligence, Bug Bounty Platform & Collaboration
Hardening pass (post-initial-close, task #454-#460)
Per explicit user direction, a second pass closed the one real gap the
initial security audit disclosed rather than leaving it open:
ExtensionPermissionEnforcer now has a real invocation-time consumer.
Summary (full detail in the "Extension API Registry" and "Security audit
summary" sections below, updated in place):
POST /extensions/:id/invoke— a permission-checked, SSRF-guarded, timeout-bounded HTTP relay to an extension's declaredentrypoint, reusingassertSafeWebhookUrland thefetch+AbortSignal.timeoutpattern fromWebhookDeliveryQueueServicerather than a new egress path.- 8 new unit tests covering disabled/undeclared-permission/cross-workspace/ unsafe-URL/oversized-input rejection and success/failure result shapes.
- Schema:
AuditEventType.EXTENSION_INVOKEDadded (schema + migration.sql, both edited consistently). - Two unbounded
findManyqueries found during a performance pass (ListWatchersHandler,ListReactionsHandler,ListExtensionsHandler) were given explicittakecaps to match the convention every other Module 16 list handler already followed. packages/shared'sdist/was stale (missing theextension-api/knowledge-graphmodules from earlier in this session) — rebuilt vianpx tsc, which is what letpackages/sdk-typescripttypecheck clean for the first time this session (previously blocked by that staleness, not by anything wrong in the SDK source itself).- Found, but did not fix (pre-existing, predates Module 16, out of this
pass's scope):
packages/sdk-typescript/src/resources/plugins.resource.tshas had the same "named DTO passed where an index-signatureRecordis expected" typecheck error since the Module 10 commit (357d97f) that introduced it.extensions.resource.ts's own copy of the same pattern (introduced this session) was fixed with an explicit, documented cast. prisma/jestCLIs re-checked in this sandbox — same result as every prior check this session: both exist only as broken.binstub scripts (Cannot find module '.../jest/bin/jest.js'/'.../prisma/build/index.js'), the actual packages were never installed. Not re-litigated further; see "Known gaps" below, unchanged from before this pass.apps/api/prisma/schema.prismare-verified brace-balanced (6261 lines, final depth 0) andAuditEventTypehas no duplicate values; the consolidatedmigration.sqlre-verified paren-balanced with itsCREATE TABLE/CREATE UNIQUE INDEX/ALTER TYPEstatements consistent with the schema — the closest verification possible without a workingprismaCLI in this sandbox.
Features added
Scope Engine & pre-execution gate — ScopeEngine validates every
recon/scan/tool invocation against a target's declared in-scope/out-of-scope
rules before execution is allowed to start, wired in front of ToolRunner/
JobQueue as a hard gate rather than an advisory check.
Research Session, recon/technology/asset-graph intelligence — session tracking for research work, expanded recon-tool normalization, a Target–Technology–Finding asset graph, technology-fingerprint normalization, an Attack Surface dashboard, and change detection between successive scans of the same target.
Finding Intelligence & Risk Engine — deterministic finding fingerprints
(stable across re-scans), a correlation engine that links related findings
across tools/scans, and a RiskEngine that scores findings using both
intrinsic severity and asset/business-context signals.
CVE intelligence & vulnerability matching — a CVE enrichment service and a matching engine that ties detected technology versions to known CVEs.
AI Investigation Mode & approval gates — controlled-autonomy AI
investigation flows gated by a generic SecurityApprovalService/
ApprovalGate mechanism (reused, not reimplemented, by every subsequent
Module 16 feature that needed human-in-the-loop confirmation), plus a
GlobalSecurityPolicyService enforcing platform-wide security policy
independent of any single workspace's settings.
Bug Bounty submissions, Safe Terminal, Playbooks — reward-tracked bug bounty submissions, a sandboxed Safe Terminal for controlled command execution with a dedicated security test suite, and a playbook data model + visual builder for repeatable research workflows.
Collaboration extensions — Watcher/Reaction models and CQRS layers
over Comments/Assignments/Tasks; comments previously published zero
domain events, so this pass also added CommentCreatedEvent/
MentionCreatedEvent (apps/api/src/modules/collaboration/events/ comment-events.ts), wired CreateCommentHandler to publish them, and
subscribed ActivityRecordingHandler to CommentCreatedEvent — comments
now correctly appear on the Activity Timeline, closing a pre-existing gap
rather than working around it.
Real-time gateway — RealtimeModule (GET /realtime/stream, SSE) plus
POST /realtime/typing and POST /realtime/presence/heartbeat. Deviation
from the original "WebSocket gateway" wording, disclosed deliberately: this
codebase has no @nestjs/websockets/socket.io dependency anywhere in the
monorepo (verified by grep) — SSE is the platform's one and only real-time
transport (used since Module 5's AI chat streaming). Rather than introduce a
new transport library, the gateway follows the same pattern: EventBus →
RealtimePublishHandler (a curated 14-event @EventsHandler(...) subscriber,
covering finding/job/comment/notification categories per spec) →
RealtimeHubService (in-process RxJS Subject, keyed by workspaceId) → SSE
stream to connected clients. Typing/presence are client→server signals
delivered over plain authenticated REST rather than the SSE channel, since SSE
is server→client only. Every client-supplied workspaceId is validated
against a real WorkspaceMember row via the existing resolveWorkspaceMembership
helper before a stream is opened or a signal is accepted — no query or event
payload trusts the client's claim outright. Single-process-only (Phase-1
limitation), matching the same disclosed constraint already documented for
AiChatStreamHubService and the recon/vuln worker.
Public Sharing — ShareLink model (sha256-hashed tokens, following the
same secret-hashing convention as API keys), a public-share.controller.ts
serving one legitimately unauthenticated, rate-limited
(@Throttle({ limit: 20, ttl: 60_000 })) endpoint, with dedicated security
tests.
Knowledge Graph query layer — GET /knowledge-graph/search,
/node/:type/:id, /relationships/:type/:id, /path, resolving a caller's
implicit personal workspace via ResearcherMemoryWorkspaceResolver (the same
resolver AI Memory uses) and joining every node type back through its owning
Target.workspaceId so results never cross a tenant boundary. Depth-capped
(maxDepth ≤ 4) path-finding to bound query cost.
AI Memory — long-term researcher memory over ResearcherMemoryEntry,
reusing the Module 11 AI-memory conventions rather than a new store.
Extension API Registry — ExtensionRegistration model + ExtensionType
enum, full CRUD (register/list/get/enable/disable/unregister)
under /extensions, an ExtensionPermissionEnforcer.assertAuthorized()
utility for permission-scoped checks, and DTOs with hard caps
(@ArrayMaxSize(50), @MaxLength) on every list/string field accepted from
extension manifests. Hardening pass: POST /extensions/:id/invoke is
now that enforcer's real caller — authorize (enabled + permission declared)
→ SSRF-guard the entrypoint (assertSafeWebhookUrl, re-checked every call,
same DNS-rebinding posture as webhook delivery) → a single, timeout-bounded
(10s) HTTP POST relaying { extensionId, type, capabilities, permission, input } → an EXTENSION_INVOKED audit event either way. This is a uniform,
type-agnostic relay (authorize + relay, not execute) — it does not itself
run recon/scanner/agent/workflow-specific logic per ExtensionType; a
deeper per-type integration (e.g. feeding a RECON_PROVIDER straight into
ToolRunner) remains future work, not implied by this endpoint's existence.
SDK & CLI — Knowledge Graph and Extensions resources added to the
TypeScript SDK (packages/sdk-typescript), Python SDK (sdks/python), Go
SDK (sdks/go), and the CLI (packages/cli — pentesthub knowledge-graph ..., pentesthub extensions ...), extending the same deliberately-scoped
"v1 surface" convention already documented for Auth/Organizations/
Distributed Jobs/Plugins/Integrations. Collaboration extensions, Sharing, AI
Memory, and the Real-time SSE gateway remain unwrapped in the SDKs — not part
of this pass's scope.
Explicitly out of scope (user decision)
Mobile (apps/mobile), Desktop Agent (apps/desktop), and Browser Extension
UI integration for Module 16 surfaces were explicitly deferred by user
decision during this module's build (chosen over "API-client layers only"
and "deep-dive one platform") — no work was attempted on these three
surfaces for Module 16 and none should be inferred as done.
Security audit summary (this pass)
- All new controllers sit behind the global
JwtAuthGuard(APP_GUARDinapp.module.ts); none use@Public()except the one intentionally public, rate-limitedpublic-share.controller.tsendpoint. RolesGuardis a no-op unless a route opts in via@Roles(...)— none of the new Module 16 routes use role gating, which is correct/expected (they are workspace-membership-scoped, not role-scoped).- Global
ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true })applies to every new DTO; every new DTO carriesclass-validatordecorators (@IsString,@IsIn,@ArrayMaxSize,@MaxLength,@Min/@Max, etc.) — none accept unbounded strings/arrays. - Grep sweep for client-supplied
workspaceIdflowing directly into a Prismawhereclause (workspaceId: query\.|dto\.|body\.) across all ofapps/api/src/modulesfound two hits, both in this module's ownrealtime.controller.ts— both confirmed safe: used only to key an outgoing event payload afterresolveWorkspaceMembershiphas already validated real membership, never in a database query. - Every "not found" response across new modules stays enumeration-safe (identical wording whether an id doesn't exist or belongs to another tenant) — the existing platform convention.
- Resolved this pass (previously the one disclosed gap):
ExtensionPermissionEnforcer.assertAuthorized()now has a real invocation-time consumer —InvokeExtensionHandler/POST /extensions/:id/invoke. Verified: disabled extensions and undeclared permissions are rejected beforeassertSafeWebhookUrlorfetchare ever reached (asserted directly in the unit tests, not just by reading the code); a cross-workspace extension id returns the same enumeration-safe 404 as every other extension-registry endpoint; an unsafe entrypoint URL is rejected before any network call; oversized invocation input (>100KB JSON) is rejected before the URL check even runs. Remaining, disclosed scope limit: this is one generic HTTP-relay invocation path shared by all sevenExtensionTypes, not seven type-specific integrations intoToolRunner/the Agent framework/the Workflow engine — see the Extension API Registry feature entry above. - No plaintext secrets found in new code paths;
ShareLinktokens follow the established sha256-of-random-token hashing convention, not reversible encryption or plaintext storage.
Known gaps
prisma generate/ a real migration run — still not executed in this sandbox (prismapackage is missing — only a broken.binstub script resolves — re-confirmed during the hardening pass, unchanged from every prior module). Best-effort substitute performed instead: the schema was confirmed brace-balanced with no duplicate enum values, andmigration.sqlwas confirmed paren-balanced with its newCREATE TABLE/index/ALTER TYPEstatements consistent with the schema. The migration must still be verified withprisma migrate diffagainst a real database before use.jest, fullapps/apiboot —jestre-confirmed unavailable this pass (same "stub script, package never installed" result asprismaabove); the 8 new invoke-path tests exist and are internally consistent (mocked dependencies, explicit assertions on call order and rejected network calls) but were not executed.packages/sharedandpackages/sdk-typescriptwere verified clean vianpx tsc --noEmitthis pass (after rebuildingpackages/shared's staledist/). Python SDK files verified viapython3 -m py_compile(clean).- Go SDK unverifiable — the Go toolchain is still entirely absent from
this sandbox; the new
Invokemethod added tosdks/go/extensions.gothis pass is pattern-consistent with the rest of that file but not compiled or tested. packages/clistill has no installednode_modulesin this sandbox — re-confirmed this pass; the newextensions invokeCLI command could not be typechecked, same as the rest of the CLI's Module 16 commands.- Pre-existing, unrelated bug found (not fixed, out of scope):
packages/sdk-typescript/src/resources/plugins.resource.tshas a typecheck error (ListPluginsQueryDtopassed where an index-signatureRecordis expected) dating to the Module 10 commit that introduced it — predates and is unrelated to Module 16.extensions.resource.ts's own instance of the identical pattern (introduced this session) was fixed with an explicit cast;plugins.resource.tswas left as found. - Mobile, Desktop Agent, Browser Extension — see "Explicitly out of scope" above; unchanged by the hardening pass.