T3MP3ST v1 — hardened launch (AGPL, verify-claims 24/24, leak-scrubbed)
This commit is contained in:
1 parent
c7a356bde0
commit
6c57e09023
242 files changed
+1761
-51038
No files matched your search
+13
-3
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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)),
|
||||
|
||||
Reference in new issue
Block a user