T3MP3ST v1 — hardened launch (AGPL, verify-claims 24/24, leak-scrubbed)

This commit is contained in:
faber committed 2026-07-04 21:11:05 -04:00
1 parent c7a356bde0
commit 6c57e09023
242 files changed
+1761 -51038

No files matched your search

+13 -3
View File
@@ -106,7 +106,8 @@ export class AgentLoop extends EventEmitter<AgentEvents> {
task: Task,
systemPrompt: string,
target?: Target,
sourceContext?: string
sourceContext?: string,
sharedContext?: string
): Promise<AgentResult> {
const startTime = Date.now();
const steps: AgentStep[] = [];
@@ -127,7 +128,7 @@ export class AgentLoop extends EventEmitter<AgentEvents> {
// Build initial messages
const messages: LLMMessage[] = [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: this.buildTaskPrompt(task, target, toolDefs, sourceContext) },
{ role: 'user', content: this.buildTaskPrompt(task, target, toolDefs, sourceContext, sharedContext) },
];
for (let i = 0; i < this.options.maxIterations; i++) {
@@ -340,7 +341,7 @@ export class AgentLoop extends EventEmitter<AgentEvents> {
/**
* Build the initial task prompt with target context and prior intel
*/
private buildTaskPrompt(task: Task, target?: Target, tools?: LLMToolDefinition[], sourceContext?: string): string {
private buildTaskPrompt(task: Task, target?: Target, tools?: LLMToolDefinition[], sourceContext?: string, sharedContext?: string): string {
const parts: string[] = [];
parts.push(`## MISSION TASK: ${task.name}`);
@@ -358,6 +359,15 @@ export class AgentLoop extends EventEmitter<AgentEvents> {
parts.push(sourceContext);
}
// Shared intel from the pack board (Phase-2 coordination): what sibling operators have already
// found/claimed on this mission. Lets this operator build on tool-verified leads and NOT re-tread
// surface a teammate already owns. Absent/empty keeps the solo-operator prompt (the baseline).
if (sharedContext && sharedContext.trim().length > 0) {
parts.push(`\n### Shared intel from the pack (your teammates' live board)`);
parts.push(`Other operators are working this same target in parallel. Below is the current lead-board — tool-verified leads, who has claimed what, and open surface. Build on verified leads, do not duplicate a teammate's claimed work, and chase the hottest UNCLAIMED lead that fits your role.`);
parts.push(sharedContext);
}
if (target) {
parts.push(`\n### Target Intelligence`);
parts.push(`- **Address**: ${target.address}`);
+32 -3
View File
@@ -3078,6 +3078,30 @@ export async function runSubprocess(
* External CLI tools that wrap real security tools when available.
* These gracefully degrade — if the tool isn't installed, they return a helpful message.
*/
// LLM-supplied `flags` for shell-out tools are ATTACKER-INFLUENCED: word-splitting them into argv is
// flag injection that reads/writes the OPERATOR's own machine (curl -o/-K/-T/-x/--config, nmap
// -oN/--script/-iL). This sanitizer drops the file-write/read/config/script flags (and their values);
// curl bodies are separately forced through --data-raw with an explicit @/< reject. Mirrors the
// hardened templates in adapter-tools.ts; fail-closed on the known-dangerous vectors.
const DANGEROUS_EXTERNAL_FLAGS: Record<string, RegExp> = {
curl: /^(-o|-O|--output|--remote-name|-K|--config|-T|--upload-file|-x|--proxy|--preproxy|-D|--dump-header|--trace|--trace-ascii|-w|--write-out|--cert|--key|--cacert|--capath|--cert-type|--key-type|--output-dir|--create-dirs|-c|--cookie-jar)$/i,
nmap: /^(-o[NXGAS]|--script|--script-args|--script-args-file|--script-help|--script-updatedb|-iL|--excludefile|--datadir|--servicedb|--versiondb|--resume|--stylesheet|--webxml)$/i,
};
function sanitizeExternalFlags(tool: 'curl' | 'nmap', raw?: string): string[] {
const bad = DANGEROUS_EXTERNAL_FLAGS[tool];
const toks = (raw || '').split(/\s+/).filter(Boolean);
const out: string[] = [];
for (let i = 0; i < toks.length; i++) {
const name = toks[i].split('=')[0];
if (bad.test(name)) {
if (!toks[i].includes('=') && toks[i + 1] && !toks[i + 1].startsWith('-')) i++; // also drop the flag's value token
continue;
}
out.push(toks[i]);
}
return out;
}
export const EXTERNAL_TOOLS: CustomTool[] = [
{
name: 'nmap_scan',
@@ -3093,7 +3117,7 @@ export const EXTERNAL_TOOLS: CustomTool[] = [
return { success: false, error: 'nmap is not installed. Install it with: apt install nmap' };
}
const target = context.parameters.target as string;
const flags = (context.parameters.flags as string || '-sV -T4').split(/\s+/);
const flags = sanitizeExternalFlags('nmap', context.parameters.flags as string || '-sV -T4'); // drop -oN/--script/-iL injection
const ports = context.parameters.ports as string | undefined;
const args = [...flags];
@@ -3225,13 +3249,18 @@ export const EXTERNAL_TOOLS: CustomTool[] = [
const flags = context.parameters.flags as string | undefined;
const args = ['-s', '-i', '-X', method];
if (data) args.push('-d', data);
if (data) {
// A body starting with @ (read local file) or < (read stdin) turns curl into a local-file
// disclosure primitive. --data-raw sends it verbatim and disables @/< interpretation.
if (/^[@<]/.test(data)) return { success: false, error: `curl_request: refusing a data value starting with '${data[0]}' (would read a local file/stdin)` };
args.push('--data-raw', data);
}
if (headers) {
for (const h of headers.split(',')) {
args.push('-H', h.trim());
}
}
if (flags) args.push(...flags.split(/\s+/));
if (flags) args.push(...sanitizeExternalFlags('curl', flags)); // drop -o/-K/-T/-x file-read/write injection
args.push(url);
const result = await runSubprocess('curl', args, { timeout: 30000 });
+7 -7
View File
@@ -88,11 +88,11 @@ const DEFAULT_SETTINGS: TempestSettings = {
apiKeys: {},
defaultProvider: 'openrouter',
defaultModel: 'anthropic/claude-opus-4.6',
defaultModel: 'anthropic/claude-opus-4.8',
openrouter: {
baseUrl: 'https://openrouter.ai/api/v1',
defaultModel: 'anthropic/claude-opus-4.6',
defaultModel: 'anthropic/claude-opus-4.8',
siteUrl: 'https://github.com/tempest',
siteName: 'T3MP3ST',
},
@@ -104,7 +104,7 @@ const DEFAULT_SETTINGS: TempestSettings = {
anthropic: {
baseUrl: 'https://api.anthropic.com',
defaultModel: 'claude-opus-4-6',
defaultModel: 'claude-opus-4-8',
},
openai: {
@@ -174,8 +174,8 @@ export const AVAILABLE_MODELS: Record<LLMProvider, ModelInfo[]> = {
openrouter: [
// Anthropic (Feb 2026)
{
id: 'anthropic/claude-opus-4.6',
name: 'Claude Opus 4.6',
id: 'anthropic/claude-opus-4.8',
name: 'Claude Opus 4.8',
provider: 'Anthropic',
contextWindow: 200000,
maxOutput: 32000,
@@ -319,8 +319,8 @@ export const AVAILABLE_MODELS: Record<LLMProvider, ModelInfo[]> = {
],
anthropic: [
{
id: 'claude-opus-4-6',
name: 'Claude Opus 4.6',
id: 'claude-opus-4-8',
name: 'Claude Opus 4.8',
provider: 'Anthropic',
contextWindow: 200000,
maxOutput: 32000,
+98 -2
View File
@@ -10,7 +10,7 @@
*
* const tempest = createTempest({
* name: 'Operation Midnight',
* llm: { provider: 'openrouter', model: 'anthropic/claude-opus-4-6' },
* llm: { provider: 'openrouter', model: 'anthropic/claude-opus-4-8' },
* opsec: { level: 'covert' },
* });
*
@@ -217,7 +217,9 @@ import type {
export { KillChainPhase } from './types/index.js';
export type { OpsecConfig, Finding, Credential, Target, DetectionEvent } from './types/index.js';
import { OperatorCell, OperatorAgent, ARCHETYPE_PROFILES } from './operators/index.js';
import { OperatorCell, OperatorAgent, ARCHETYPE_PROFILES, PHASE_ARCHETYPES, KILL_CHAIN_ORDER } from './operators/index.js';
import { PackBoard } from './pack/board.js';
import { randomUUID } from 'node:crypto';
import { MissionControl, TaskQueue } from './mission/index.js';
import { TargetEnvironment } from './target/index.js';
import { EvidenceVault } from './evidence/index.js';
@@ -329,6 +331,27 @@ export class TempestCommand extends EventEmitter<CommandEvents> {
*/
private whiteboxSource: string = '';
/**
* The swarm's shared, verifiable blackboard (Phase-2 coordination). One board per mission run:
* every finding posts a lead here, carrying its tool-vs-model-asserted provenance — the verifiable
* feedback signal that will drive the refinement loop (findings → targeted follow-up tasks).
*/
private readonly packBoard = new PackBoard();
/**
* Swarm coordination (Phase-2), OPT-IN so the swarm-vs-single-agent bake-off can toggle it: set
* `T3MP3ST_SWARM_COORD=on` to enable the finding→follow-up refinement loop. Off = the legacy
* phase-sequenced queue (the single-agent-equivalent baseline). Default OFF until it's proven.
*/
private readonly coordinationEnabled = /^(1|true|on)$/i.test(process.env.T3MP3ST_SWARM_COORD ?? '');
/** Findings that already spawned a follow-up (dedup — a finding chases exactly once). */
private readonly spawnedFollowups = new Set<string>();
/** Per-run cap on follow-up tasks so the refinement loop can never explode. */
private readonly maxFollowups = Number(process.env.T3MP3ST_SWARM_MAX_FOLLOWUPS) || 24;
/** Coordination telemetry — the artifact that distinguishes a coordinated run from N solo agents. */
private leadsPosted = 0;
private followupsSpawned = 0;
constructor(config: TempestConfig) {
super();
this.name = config.name;
@@ -518,6 +541,61 @@ export class TempestCommand extends EventEmitter<CommandEvents> {
// Sync finding intelligence back to the target object
this.syncFindingToTarget(finding);
// Post the finding to the shared board as a lead — the swarm's verifiable blackboard.
// `provenance` carries the tool-vs-model-asserted signal (the refinement loop's feedback);
// dedup + provenance-endorsement are the board's job. Best-effort: never break the mission.
// Gated on coordination so the baseline (coordination off) leaves the board fully inert.
if (this.coordinationEnabled) try {
const prov = finding.verifyGate?.provenance ?? 'none';
this.packBoard.postLead(operator.id, {
kind: 'lead',
title: finding.title,
where: { targetId: finding.id },
vulnClass: finding.cwe?.[0] ?? 'unclassified',
confidence: prov === 'tool' ? 'high' : prov === 'context' ? 'medium' : 'low',
provenance: prov,
cwe: finding.cwe?.[0],
severity: finding.severity,
});
this.leadsPosted++;
} catch { /* best-effort */ }
// [Phase-2 refinement loop] A TOOL-VERIFIED finding spawns a targeted follow-up task for the
// NEXT kill-chain phase's operator — chase the verifiable feedback signal (the research's
// load-bearing condition). Model-asserted findings spawn NO work (no chasing hallucinations).
// Dedup + a per-run cap keep the loop bounded. Gated by T3MP3ST_SWARM_COORD for the bake-off.
if (
this.coordinationEnabled &&
finding.verifyGate?.provenance === 'tool' &&
!this.spawnedFollowups.has(finding.id) &&
this.spawnedFollowups.size < this.maxFollowups
) {
const mission = this.mission.getActiveMission();
const queue = this.mission.getTaskQueue();
const idx = KILL_CHAIN_ORDER.indexOf(finding.phase);
const nextPhase = idx >= 0 && idx < KILL_CHAIN_ORDER.length - 1 ? KILL_CHAIN_ORDER[idx + 1] : undefined;
const nextOp = nextPhase ? PHASE_ARCHETYPES[nextPhase]?.[0] : undefined;
if (mission && queue && nextPhase && nextOp) {
this.spawnedFollowups.add(finding.id);
const cwe = finding.cwe?.length ? `, ${finding.cwe.join('/')}` : '';
queue.add({
id: randomUUID(),
missionId: mission.id,
name: `Chase: ${finding.title}`.slice(0, 120),
description:
`A prior operator TOOL-VERIFIED this lead: "${finding.title}" (${finding.severity}${cwe}) on ${finding.targetId}. ` +
`${finding.description} Focus this ${nextPhase} step on THIS specific surface — confirm and advance it; do not re-scan broadly.`,
phase: nextPhase,
operatorType: nextOp,
status: 'pending',
priority: 20,
dependencies: [],
createdAt: Date.now(),
});
this.followupsSpawned++;
}
}
});
operator.on('credential:harvested', ({ credential }) => {
@@ -1060,6 +1138,9 @@ export class TempestCommand extends EventEmitter<CommandEvents> {
tools: profile.defaultTools,
});
operator.attachArsenal(this.arsenal, agentLoop);
// [Phase-2] Give the operator the shared board ONLY when swarm coordination is on — so the
// baseline (coordination off) keeps the solo-operator prompt with zero shared context.
if (this.coordinationEnabled) operator.attachBoard(this.packBoard);
// If a white-box source was already set (repo ingested before this operator
// spawned), hand it to the new operator so it also sees the source excerpt.
@@ -1086,6 +1167,21 @@ export class TempestCommand extends EventEmitter<CommandEvents> {
}
}
/**
* Coordination telemetry — the machine-readable artifact that distinguishes a coordinated swarm
* run from N independent agents: how many findings became shared leads, how many spawned targeted
* follow-up work, and how many distinct findings were chased. Zero across the board (with
* `enabled:false`) is the single-agent-equivalent baseline.
*/
public getCoordinationStats(): { enabled: boolean; leadsPosted: number; followupsSpawned: number; uniqueFindingsChased: number } {
return {
enabled: this.coordinationEnabled,
leadsPosted: this.leadsPosted,
followupsSpawned: this.followupsSpawned,
uniqueFindingsChased: this.spawnedFollowups.size,
};
}
/**
* Get command status
*/
+3 -1
View File
@@ -114,7 +114,9 @@ export class TaskQueue extends EventEmitter<TaskQueueEvents> {
* Get all pending tasks
*/
getPending(): Task[] {
return this.tasks.filter(t => t.status === 'pending');
// Priority-ordered (desc): tool-verified follow-up tasks carry a higher priority than the static
// seed tasks, so the swarm chases its hottest verified leads first — light orchestration.
return this.tasks.filter(t => t.status === 'pending').sort((a, b) => b.priority - a.priority);
}
/**
+13 -1
View File
@@ -7,6 +7,7 @@
import { EventEmitter } from 'eventemitter3';
import { randomUUID } from 'crypto';
import type { PackBoard } from '../pack/board.js';
import {
KillChainPhase,
type OperatorArchetype,
@@ -270,6 +271,8 @@ export class OperatorAgent extends EventEmitter<OperatorEvents> {
private _state: OperatorState;
private llm?: LLMBackbone;
private agentLoop?: AgentLoop;
/** Shared pack board (Phase-2). Attached only when swarm coordination is on; absent = solo baseline. */
private board?: PackBoard;
private cooldownTimer: NodeJS.Timeout | null = null;
private findings: Finding[] = [];
private credentials: Credential[] = [];
@@ -406,6 +409,11 @@ export class OperatorAgent extends EventEmitter<OperatorEvents> {
this.agentLoop = agentLoop;
}
/** Attach the shared pack board so this operator sees the swarm's live lead-board (Phase-2). */
attachBoard(board: PackBoard): void {
this.board = board;
}
/**
* Execute a task with an optional target context
*/
@@ -414,7 +422,11 @@ export class OperatorAgent extends EventEmitter<OperatorEvents> {
// Pass the white-box source excerpt (if any) so the model sees the target's
// real source alongside the task; empty string keeps black-box behavior.
if (this.agentLoop && this.llm) {
const result = await this.agentLoop.run(task, this.profile.systemPrompt, target, this.whiteboxSource);
// [Phase-2] Register as live on the board and pull the shared situation report so this
// operator sees teammates' verified leads/claims — it builds on them instead of running blind.
this.board?.heartbeat(this.id, 'hunting', task.name);
const sharedContext = this.board?.situationReport(this.id);
const result = await this.agentLoop.run(task, this.profile.systemPrompt, target, this.whiteboxSource, sharedContext);
// Convert agent findings to operator findings.
// PROVENANCE-HONEST: only a tool-backed finding gets tool-output evidence (the raw
+1 -1
View File
@@ -10,7 +10,7 @@
* Each decomposed query is a legitimate code-analysis question:
* "Does this function validate the length parameter before the memcpy?"
* "Trace the data flow from recvfrom() to all sinks in this file."
* "What is sizeof(DirectLidarStateInfo) and which fields are <4 bytes?"
* "What is sizeof(TlvRecord) and which fields are <4 bytes?"
*
* The orchestrator never sends the full offensive context to the worker model.
* Only the orchestrator knows the attack plan — workers see benign analytical
+40 -40
View File
@@ -398,12 +398,12 @@ function clientLedgerId(value: unknown, prefix: string): string {
}
// =============================================================================
// DUAL-MODE CONTRACTS - STANDALONE + ORGAN MODE
// DUAL-MODE CONTRACTS - STANDALONE + T3MP3ST ORGAN
// =============================================================================
type TempestMode = 'standalone' | 'organ';
type TempestMode = 'standalone' | 't3mp3st';
type DraftStatus = 'draft' | 'queued' | 'launched' | 'archived';
type DraftSource = 'human' | 'agent' | 'organ';
type DraftSource = 'human' | 'agent' | 't3mp3st';
type MissionFamily = 'web_api' | 'ai_red_team' | 'cloud_infra' | 'smart_contract' | 'code_supply_chain' | 'crypto_secrets' | 'reverse_binary' | 'agent_warfare' | 'social_osint' | 'reporting_remediation';
type OperationMode = 'wizard' | 'agent_harness' | 'expert_console' | 'range' | 'review_only';
type GuardAction = 'command_execution' | 'network_request' | 'mission_execution' | 'autonomous_execution' | 'model_call';
@@ -757,12 +757,12 @@ const ROUTE_SCORECARDS: Record<string, Record<string, number | string>> = {
};
function currentMode(): TempestMode {
return process.env.ORGAN_STATE_DIR || process.env.T3MP3ST_MODE === 'organ' ? 'organ' : 'standalone';
return process.env.T3MP3ST_STATE_DIR || process.env.T3MP3ST_MODE === 't3mp3st' ? 't3mp3st' : 'standalone';
}
function stateRoot(): string {
return process.env.T3MP3ST_STATE_DIR ||
(process.env.ORGAN_STATE_DIR ? `${process.env.ORGAN_STATE_DIR}/organs/t3mp3st` : 'memory');
(process.env.T3MP3ST_STATE_DIR ? `${process.env.T3MP3ST_STATE_DIR}/organs/t3mp3st` : 'memory');
}
function stateFilePath(): string | null {
@@ -931,7 +931,7 @@ function replaceMapContents<T extends { id: string }>(map: Map<string, T>, value
function buildStateSnapshot(): Record<string, unknown> {
return {
schema_version: 't3mp3st.state/v1',
schema_version: 't3mp3st_state/v1',
savedAt: nowIso(),
mode: currentMode(),
missionDrafts: [...missionDrafts.values()],
@@ -1088,7 +1088,7 @@ function createApprovalRequest(action: GuardAction, target: string, reason: stri
reason,
status: 'pending',
operationId: typeof operationDraft?.operation_id === 'string' ? operationDraft.operation_id : undefined,
requestedBy: ['human', 'agent', 'organ'].includes(String(body.source)) ? body.source as DraftSource : 'system',
requestedBy: ['human', 'agent', 't3mp3st'].includes(String(body.source)) ? body.source as DraftSource : 'system',
createdAt: nowIso(),
updatedAt: nowIso(),
};
@@ -1208,7 +1208,7 @@ function buildRoutePreview(draft: MissionDraft): RoutePreview {
warnings,
},
operationDraft: {
schema_version: 't3mp3st.operation/v1',
schema_version: 't3mp3st_operation/v1',
operation_id: `op-${draft.id.replace(/^draft_/, '')}`,
mission_id: draft.id,
family,
@@ -1319,7 +1319,7 @@ function buildMissionBundle(params: { draft?: MissionDraft; operationDraft?: Rec
const pressurePaths = buildPressurePaths({ missionId, operationId, family, operationDraft, reproPacks });
return redactSecrets({
schema_version: 't3mp3st.mission_bundle/v1',
schema_version: 't3mp3st_mission_bundle/v1',
generatedAt: nowIso(),
family,
missionId: missionId || null,
@@ -1485,7 +1485,7 @@ function buildMissionGate(operationDraft: Record<string, unknown>): Record<strin
const readinessHold = Boolean(!hypotheses.length || !findings.length || openWorkOrders.length || findingsWithoutRetest.length || findingsWithoutStrongEvidence.length || unresolvedRetests.length);
const status = blockCount ? 'blocked' : score >= 80 && freshMissionApprovals.length && !readinessHold ? 'ready' : 'hold';
return {
schema_version: 't3mp3st.mission_gate/v1',
schema_version: 't3mp3st_mission_gate/v1',
generatedAt: nowIso(),
family,
missionId: missionId || null,
@@ -1600,7 +1600,7 @@ function latestMissionContext(): Record<string, unknown> {
const latest = records[0];
if (!latest) {
return {
schema_version: 't3mp3st.mission_context/v1',
schema_version: 't3mp3st_mission_context/v1',
missionId: null,
operationId: null,
family: null,
@@ -1630,7 +1630,7 @@ function latestMissionContext(): Record<string, unknown> {
const laneSummary = missionLaneSummary({ hypotheses, workOrders, findings, retests });
return redactSecrets({
schema_version: 't3mp3st.mission_context/v1',
schema_version: 't3mp3st_mission_context/v1',
missionId: missionId || null,
operationId: operationId || null,
family: latest.family || laneSummary[0]?.family || null,
@@ -2286,7 +2286,7 @@ async function buildSelfHealReport(params: Record<string, unknown>): Promise<Rec
const health = blocks ? 'blocked' : actionCount ? 'repair' : watchCount ? 'watch' : 'ok';
return redactSecrets({
schema_version: 't3mp3st.self_heal/v1',
schema_version: 't3mp3st_self_heal/v1',
generatedAt: nowIso(),
scope: {
missionId: scope.missionId || null,
@@ -2412,7 +2412,7 @@ function buildEvidenceGraph(params: Record<string, unknown>): Record<string, unk
const laneSummary = missionLaneSummary({ hypotheses, workOrders, findings, retests });
const evidenceProvenance = summarizeEvidenceProvenance(evidence);
return redactSecrets({
schema_version: 't3mp3st.evidence_graph/v1',
schema_version: 't3mp3st_evidence_graph/v1',
generatedAt: nowIso(),
scope: { missionId: missionId || null, operationId: operationId || null, family: family || null },
summary: {
@@ -2593,7 +2593,7 @@ function buildReproPacks(params: Record<string, unknown>): Record<string, any> {
}, {}),
};
return redactSecrets({
schema_version: 't3mp3st.repro_packs/v1',
schema_version: 't3mp3st_repro_packs/v1',
generatedAt: nowIso(),
scope: {
missionId: missionId || null,
@@ -2977,7 +2977,7 @@ function buildPressurePaths(params: Record<string, unknown>): Record<string, any
}, {}),
};
return redactSecrets({
schema_version: 't3mp3st.pressure_paths/v1',
schema_version: 't3mp3st_pressure_paths/v1',
generatedAt: nowIso(),
scope: {
missionId: missionId || null,
@@ -3017,7 +3017,7 @@ function buildPressureCanary(params: Record<string, unknown>): Record<string, an
if (!path) {
return redactSecrets({
schema_version: 't3mp3st.pressure_canary/v1',
schema_version: 't3mp3st_pressure_canary/v1',
generatedAt: now,
status: 'no_path',
canary: null,
@@ -3141,7 +3141,7 @@ function buildPressureCanary(params: Record<string, unknown>): Record<string, an
});
return redactSecrets({
schema_version: 't3mp3st.pressure_canary/v1',
schema_version: 't3mp3st_pressure_canary/v1',
generatedAt: now,
status,
path: {
@@ -3182,7 +3182,7 @@ function buildPressureDuel(params: Record<string, unknown>): Record<string, any>
if (!path) {
return redactSecrets({
schema_version: 't3mp3st.pressure_duel/v1',
schema_version: 't3mp3st_pressure_duel/v1',
generatedAt: now,
status: 'no_path',
survivabilityScore: 0,
@@ -3423,7 +3423,7 @@ function buildPressureDuel(params: Record<string, unknown>): Record<string, any>
});
return redactSecrets({
schema_version: 't3mp3st.pressure_duel/v1',
schema_version: 't3mp3st_pressure_duel/v1',
generatedAt: now,
status,
survivabilityScore,
@@ -3465,7 +3465,7 @@ function buildPressureMutations(params: Record<string, unknown>): Record<string,
if (!path) {
return redactSecrets({
schema_version: 't3mp3st.pressure_mutations/v1',
schema_version: 't3mp3st_pressure_mutations/v1',
generatedAt: now,
status: 'no_path',
summary: { total: 0, queued: 0, maxFangScore: 0 },
@@ -3685,7 +3685,7 @@ function buildPressureMutations(params: Record<string, unknown>): Record<string,
});
return redactSecrets({
schema_version: 't3mp3st.pressure_mutations/v1',
schema_version: 't3mp3st_pressure_mutations/v1',
generatedAt: now,
status: survivedDuel ? 'queued' : 'needs_duel',
path: {
@@ -3729,7 +3729,7 @@ function buildPressureChains(params: Record<string, unknown>): Record<string, an
if (!path) {
return redactSecrets({
schema_version: 't3mp3st.pressure_chains/v1',
schema_version: 't3mp3st_pressure_chains/v1',
generatedAt: now,
status: 'no_path',
summary: { total: 0, queued: 0, maxChainScore: 0, workOrders: 0 },
@@ -3750,7 +3750,7 @@ function buildPressureChains(params: Record<string, unknown>): Record<string, an
if (!mutations.length) {
return redactSecrets({
schema_version: 't3mp3st.pressure_chains/v1',
schema_version: 't3mp3st_pressure_chains/v1',
generatedAt: now,
status: 'no_mutations',
path: {
@@ -3999,7 +3999,7 @@ function buildPressureChains(params: Record<string, unknown>): Record<string, an
});
return redactSecrets({
schema_version: 't3mp3st.pressure_chains/v1',
schema_version: 't3mp3st_pressure_chains/v1',
generatedAt: now,
status: summary.queued ? 'queued' : 'hold',
path: {
@@ -4424,7 +4424,7 @@ async function buildArsenalStatus(family?: string): Promise<Record<string, unkno
const installedCommandReady = commandReadyTools.filter(tool => tool.available).length;
const missingCommandReady = commandReadyTools.filter(tool => !tool.available).map(tool => tool.id);
return {
schema_version: 't3mp3st.arsenal_status/v1',
schema_version: 't3mp3st_arsenal_status/v1',
family: normalizedFamily || 'all',
summary: {
...summarizeToolCatalog(catalog),
@@ -4500,7 +4500,7 @@ function buildArsenalPlan(params: Record<string, unknown>): Record<string, unkno
};
});
return {
schema_version: 't3mp3st.arsenal_plan/v1',
schema_version: 't3mp3st_arsenal_plan/v1',
family,
target,
objective: typeof params.objective === 'string' ? params.objective : 'Build a scoped, evidence-first tool plan.',
@@ -4549,7 +4549,7 @@ function buildArsenalActivationPlan(): Record<string, unknown> {
}
}
return {
schema_version: 't3mp3st.arsenal_activation/v1',
schema_version: 't3mp3st_arsenal_activation/v1',
summary: {
...summarizeToolCatalog(),
frontierMilestone: FRONTIER_ARSENAL_MILESTONE,
@@ -4663,7 +4663,7 @@ app.get('/api/arsenal/catalog', (req: Request, res: Response) => {
.filter(adapter => !category || adapter.category === category)
.filter(adapter => !execution || adapter.execution === execution);
res.json({
schema_version: 't3mp3st.arsenal_catalog/v1',
schema_version: 't3mp3st_arsenal_catalog/v1',
family: family || 'all',
summary: summarizeToolCatalog(adapters),
adapters,
@@ -4675,7 +4675,7 @@ app.get('/api/arsenal/catalog', (req: Request, res: Response) => {
// defanged, transferable taxonomy the ai_red_team specialist (garak/promptfoo) reasons from.
app.get('/api/ai-redteam/playbook', (_req: Request, res: Response) => {
res.json({
schema_version: 't3mp3st.ai_redteam_playbook/v1',
schema_version: 't3mp3st_ai_redteam_playbook/v1',
source: 'L1B3RT4S + P4RS3LT0NGV3 (public corpus) — defanged methodology, not payloads',
doc: 'docs/AI_REDTEAM_TECHNIQUES.md',
count: AI_REDTEAM_PLAYBOOK.length,
@@ -4839,7 +4839,7 @@ app.get('/api/operator-runbooks/:family', (req: Request, res: Response) => {
app.get('/api/forefront-radar', (req: Request, res: Response) => {
const family = typeof req.query.family === 'string' ? req.query.family : '';
res.json({
schema_version: 't3mp3st.forefront_radar/v1',
schema_version: 't3mp3st_forefront_radar/v1',
lanes: forefrontPressureForFamily(family),
mandate: {
purpose: 'show what is possible early in controlled arenas, then convert offensive insight into defensive artifacts',
@@ -5163,7 +5163,7 @@ function handleHypothesisDecompose(req: Request, res: Response): void {
const existing = [...workOrderLedger.values()].filter(order => order.hypothesisId === hypothesis.id);
emitContractEvent('hypothesis.decomposed', { hypothesisId: hypothesis.id, created: orders.length, total: existing.length });
res.status(201).json({
schema_version: 't3mp3st.work_order_decomposition/v1',
schema_version: 't3mp3st_work_order_decomposition/v1',
hypothesis,
created: orders,
workOrders: existing.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)),
@@ -5276,7 +5276,7 @@ app.get('/api/watch-loop/status', (req: Request, res: Response) => {
const cycles = latestWatchCycles(scope.missionId, scope.operationId, scope.family).slice(0, 10);
const signals = cycles[0]?.signals || buildWatchSignals(scope);
res.json({
schema_version: 't3mp3st.watch_loop_status/v1',
schema_version: 't3mp3st_watch_loop_status/v1',
scope: {
missionId: scope.missionId || null,
operationId: scope.operationId || null,
@@ -5299,7 +5299,7 @@ app.get('/api/watch-loop/status', (req: Request, res: Response) => {
app.post('/api/watch-loop/run', (req: Request, res: Response) => {
const cycle = runWatchLoop(req.body as Record<string, unknown>);
res.status(201).json({
schema_version: 't3mp3st.watch_loop_cycle/v1',
schema_version: 't3mp3st_watch_loop_cycle/v1',
...cycle,
});
});
@@ -5506,7 +5506,7 @@ app.post('/api/mission-drafts', (req: Request, res: Response) => {
urgency: ['low', 'normal', 'high', 'critical'].includes(String(body.urgency)) ? body.urgency as MissionDraft['urgency'] : 'normal',
opsecPreference: ['overt', 'normal', 'covert', 'ghost'].includes(String(body.opsecPreference)) ? body.opsecPreference as MissionDraft['opsecPreference'] : 'normal',
mode: currentMode(),
source: ['human', 'agent', 'organ'].includes(String(body.source)) ? body.source as DraftSource : 'human',
source: ['human', 'agent', 't3mp3st'].includes(String(body.source)) ? body.source as DraftSource : 'human',
status: 'draft',
createdAt: now,
updatedAt: now,
@@ -5569,7 +5569,7 @@ app.post('/api/route-preview', (req: Request, res: Response) => {
urgency: ['low', 'normal', 'high', 'critical'].includes(String(body.urgency)) ? body.urgency as MissionDraft['urgency'] : 'normal',
opsecPreference: ['overt', 'normal', 'covert', 'ghost'].includes(String(body.opsecPreference)) ? body.opsecPreference as MissionDraft['opsecPreference'] : 'normal',
mode: currentMode(),
source: ['human', 'agent', 'organ'].includes(String(body.source)) ? body.source as DraftSource : 'human',
source: ['human', 'agent', 't3mp3st'].includes(String(body.source)) ? body.source as DraftSource : 'human',
status: 'draft',
createdAt: nowIso(),
updatedAt: nowIso(),
@@ -5641,7 +5641,7 @@ app.post('/api/promotion/evaluate', (req: Request, res: Response) => {
app.get('/api/learning/status', (_req: Request, res: Response) => {
res.json({
schema_version: 't3mp3st.learning_status/v1',
schema_version: 't3mp3st_learning_status/v1',
policy: {
silentLearning: false,
proposalRequired: true,
@@ -5684,14 +5684,14 @@ app.get('/api/learning/status', (_req: Request, res: Response) => {
app.post('/api/learning/run-review', (req: Request, res: Response) => {
const review = buildLearningReview(req.body as Record<string, unknown>);
res.status(201).json({
schema_version: 't3mp3st.learning_review/v1',
schema_version: 't3mp3st_learning_review/v1',
...review,
});
});
app.get('/api/memory/capsule', (_req: Request, res: Response) => {
res.json({
schema_version: 't3mp3st.memory_capsule/v1',
schema_version: 't3mp3st_memory_capsule/v1',
entries: [...memoryCapsule.values()],
policy: 'accepted memory only; proposals are separate and inspectable',
});
@@ -5700,7 +5700,7 @@ app.get('/api/memory/capsule', (_req: Request, res: Response) => {
app.get('/api/memory/proposals', (req: Request, res: Response) => {
const status = typeof req.query.status === 'string' ? req.query.status : '';
res.json({
schema_version: 't3mp3st.memory_proposals/v1',
schema_version: 't3mp3st_memory_proposals/v1',
proposals: [...memoryProposals.values()]
.filter(proposal => !status || proposal.status === status)
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)),