diff --git a/BLACK_HOUSE_COLLECTIVE.md b/BLACK_HOUSE_COLLECTIVE.md new file mode 100644 index 000000000..d61ed4ca7 --- /dev/null +++ b/BLACK_HOUSE_COLLECTIVE.md @@ -0,0 +1,80 @@ +# Black House Collective + +Black House Collective is a **synthetic-range adversary-emulation control-plane design** for CyberStrikeZYRA. It models fleet-scale orchestration without creating a real-world attack swarm. + +## Capacity target + +| Item | Value | +| --- | ---: | +| Logical agents | 7,000,000 | +| Logical agents per shard | 10,000 | +| Deterministic shards | 700 | +| Scheduler complexity | O(shards) | +| Network actuation from collective | Disabled | + +Seven million is a logical capacity target. The scheduler represents agents as deterministic numeric ranges rather than allocating seven million processes, sockets, workers, or agent objects. + +## Shard model + +For zero-based shard `s` and shard size `10,000`: + +- first logical agent = `s * 10,000` +- last logical agent = `min(first + 9,999, 6,999,999)` +- shard count = `ceil(7,000,000 / 10,000) = 700` + +This makes replay, telemetry aggregation, and capacity testing practical on a development machine. + +## Execution boundary + +The collective is restricted to an authorized synthetic range. Its scheduler must not perform network operations itself. + +Required invariants: + +- scope is `synthetic-range` +- an authorized range identifier is mandatory +- public-Internet discovery is disabled +- network actuation is disabled in the collective scheduler +- credential operations are disabled +- persistence mechanisms are disabled +- destructive actions are disabled +- data exfiltration is disabled +- traffic flooding is disabled + +## Simulation scenarios + +The control plane may generate aggregate events for these non-payload scenarios: + +1. `surface-map` — synthetic asset/topology coverage. +2. `control-validation` — verifies policy and authorization boundaries. +3. `detection-pressure` — produces synthetic detection telemetry. +4. `containment-drill` — exercises containment state transitions. +5. `recovery-verification` — validates restoration and recovery workflows. + +## Proposed package boundary + +```text +packages/cyberstrike/src/black-house/ + types.ts + policy.ts + collective.ts + simulation.ts + index.ts + collective.test.ts +``` + +The implementation should remain pure TypeScript with no socket, browser, shell, exploit, credential, persistence, or external-target primitives in this layer. + +## Acceptance tests + +- 7,000,000 logical agents produce exactly 700 shards at a shard size of 10,000. +- The final logical agent index is 6,999,999. +- Agent-to-shard lookup is deterministic. +- Memory growth is proportional to shard count, not logical-agent count. +- Any non-synthetic scope is rejected. +- Missing authorization metadata is rejected. +- One simulated cycle emits at most one aggregate record per shard per scenario. +- Every aggregate record states that network actuation is disabled. + +## Black House role + +Black House consumes the collective as a simulation and observability layer for authorized cyber-range exercises. Existing pentest capabilities in CyberStrikeZYRA remain separately permissioned and are not multiplied into a seven-million-node real-world attack fabric by this component. diff --git a/packages/cyberstrike/src/black-house/capacity.test.ts b/packages/cyberstrike/src/black-house/capacity.test.ts new file mode 100644 index 000000000..68bde4dc8 --- /dev/null +++ b/packages/cyberstrike/src/black-house/capacity.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, test } from "bun:test" +import { + createCollectiveCapacityPlan, + DEFAULT_LOGICAL_AGENTS, + DEFAULT_SHARD_SIZE, + locateLogicalAgent, + virtualAgentId, +} from "./capacity" +import { assertBlackHouseAuthorization, BLACK_HOUSE_COLLECTIVE_POLICY, BLACK_HOUSE_SCOPE } from "./policy" + +describe("Black House collective capacity", () => { + test("represents seven million logical agents in 700 shards", () => { + const plan = createCollectiveCapacityPlan() + + expect(plan.logicalAgents).toBe(DEFAULT_LOGICAL_AGENTS) + expect(plan.shardSize).toBe(DEFAULT_SHARD_SIZE) + expect(plan.shardCount).toBe(700) + expect(plan.shards).toHaveLength(700) + expect(plan.shards[0]).toEqual({ + shardId: "bh-0000", + firstAgent: 0, + lastAgent: 9_999, + logicalAgents: 10_000, + }) + expect(plan.shards[699]?.lastAgent).toBe(6_999_999) + }) + + test("locates logical agents without allocating per-agent objects", () => { + const plan = createCollectiveCapacityPlan() + + expect(locateLogicalAgent(plan, 0)?.shardId).toBe("bh-0000") + expect(locateLogicalAgent(plan, 6_999_999)?.shardId).toBe("bh-00jf") + expect(virtualAgentId(6_999_999)).toBe("black-house:000461bz") + }) + + test("enforces synthetic-range authorization and non-actuation policy", () => { + expect(() => + assertBlackHouseAuthorization({ scope: BLACK_HOUSE_SCOPE, authorizedRangeId: "range-lab-01" }), + ).not.toThrow() + expect(() => assertBlackHouseAuthorization({ scope: "internet", authorizedRangeId: "range-lab-01" })).toThrow() + expect(() => assertBlackHouseAuthorization({ scope: BLACK_HOUSE_SCOPE, authorizedRangeId: "" })).toThrow() + + expect(BLACK_HOUSE_COLLECTIVE_POLICY).toEqual({ + networkActuation: false, + publicInternetTargets: false, + credentialOperations: false, + persistence: false, + destructiveActions: false, + dataExfiltration: false, + trafficFlooding: false, + }) + }) +}) diff --git a/packages/cyberstrike/src/black-house/capacity.ts b/packages/cyberstrike/src/black-house/capacity.ts new file mode 100644 index 000000000..cf4f21567 --- /dev/null +++ b/packages/cyberstrike/src/black-house/capacity.ts @@ -0,0 +1,67 @@ +export const DEFAULT_LOGICAL_AGENTS = 7_000_000 +export const DEFAULT_SHARD_SIZE = 10_000 + +export interface CollectiveShard { + readonly shardId: string + readonly firstAgent: number + readonly lastAgent: number + readonly logicalAgents: number +} + +export interface CollectiveCapacityPlan { + readonly logicalAgents: number + readonly shardSize: number + readonly shardCount: number + readonly shards: readonly CollectiveShard[] +} + +function positiveSafeInteger(value: number, label: string) { + if (!Number.isSafeInteger(value) || value <= 0) { + throw new RangeError(`${label} must be a positive safe integer`) + } +} + +export function createCollectiveCapacityPlan( + logicalAgents = DEFAULT_LOGICAL_AGENTS, + shardSize = DEFAULT_SHARD_SIZE, +): CollectiveCapacityPlan { + positiveSafeInteger(logicalAgents, "logicalAgents") + positiveSafeInteger(shardSize, "shardSize") + + const shardCount = Math.ceil(logicalAgents / shardSize) + const shards = Array.from({ length: shardCount }, (_, index): CollectiveShard => { + const firstAgent = index * shardSize + const lastAgent = Math.min(firstAgent + shardSize - 1, logicalAgents - 1) + + return Object.freeze({ + shardId: `bh-${index.toString(36).padStart(4, "0")}`, + firstAgent, + lastAgent, + logicalAgents: lastAgent - firstAgent + 1, + }) + }) + + return Object.freeze({ + logicalAgents, + shardSize, + shardCount, + shards: Object.freeze(shards), + }) +} + +export function locateLogicalAgent(plan: CollectiveCapacityPlan, agentIndex: number) { + if (!Number.isSafeInteger(agentIndex) || agentIndex < 0 || agentIndex >= plan.logicalAgents) { + throw new RangeError("agentIndex is outside this collective plan") + } + + const shardIndex = Math.floor(agentIndex / plan.shardSize) + return plan.shards[shardIndex] +} + +export function virtualAgentId(agentIndex: number, namespace = "black-house") { + if (!Number.isSafeInteger(agentIndex) || agentIndex < 0) { + throw new RangeError("agentIndex must be a non-negative safe integer") + } + + return `${namespace}:${agentIndex.toString(36).padStart(8, "0")}` +} diff --git a/packages/cyberstrike/src/black-house/policy.ts b/packages/cyberstrike/src/black-house/policy.ts new file mode 100644 index 000000000..1e00dba53 --- /dev/null +++ b/packages/cyberstrike/src/black-house/policy.ts @@ -0,0 +1,26 @@ +export const BLACK_HOUSE_SCOPE = "synthetic-range" as const + +export interface BlackHouseAuthorization { + readonly scope: typeof BLACK_HOUSE_SCOPE + readonly authorizedRangeId: string +} + +export const BLACK_HOUSE_COLLECTIVE_POLICY = Object.freeze({ + networkActuation: false as const, + publicInternetTargets: false as const, + credentialOperations: false as const, + persistence: false as const, + destructiveActions: false as const, + dataExfiltration: false as const, + trafficFlooding: false as const, +}) + +export function assertBlackHouseAuthorization(input: unknown): asserts input is BlackHouseAuthorization { + if (!input || typeof input !== "object") throw new TypeError("Black House authorization is required") + + const candidate = input as Partial + if (candidate.scope !== BLACK_HOUSE_SCOPE) throw new Error("Black House collective requires synthetic-range scope") + if (typeof candidate.authorizedRangeId !== "string" || candidate.authorizedRangeId.trim().length === 0) { + throw new Error("Black House collective requires an authorized range identifier") + } +}