Download lib/mission.mjs from teddyoas/Asnip: direct link, hf CLI and curl.
- Browser
- Download file 6.58 kB
-
https://huggingface.co/teddyoas/Asnip/resolve/main/lib/mission.mjs
- Command line
-
hf download hf://teddyoas/Asnip/lib/mission.mjs
-
curl -L -o mission.mjs https://huggingface.co/teddyoas/Asnip/resolve/main/lib/mission.mjs
6.58 kB
| import fs from "fs"; | |
| import { resolve } from "path"; | |
| import { homedir } from "os"; | |
| const MISSIONS_DIR = resolve(homedir(), ".config", "phantom", "missions"); | |
| export function createMission(program, scope) { | |
| const id = `mission-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; | |
| const now = new Date().toISOString(); | |
| return { | |
| id, | |
| programName: program.name || program.handle, | |
| programHandle: program.handle, | |
| programUrl: program.url, | |
| scope, | |
| status: "planning", | |
| objectives: [ | |
| "Passive reconnaissance of in-scope assets", | |
| "Active HTTP probing and technology detection", | |
| "Vulnerability validation and reporting" | |
| ], | |
| restrictions: [], | |
| createdAt: now, | |
| updatedAt: now, | |
| activityLog: [{ | |
| timestamp: now, | |
| action: "created", | |
| details: `Mission created for ${program.name || program.handle} (${program.handle})` | |
| }] | |
| }; | |
| } | |
| export function saveMission(mission) { | |
| if (!fs.existsSync(MISSIONS_DIR)) fs.mkdirSync(MISSIONS_DIR, { recursive: true }); | |
| const f = resolve(MISSIONS_DIR, `${mission.id}.json`); | |
| fs.writeFileSync(f, JSON.stringify(mission, null, 2), "utf-8"); | |
| } | |
| export function loadMission(missionId) { | |
| const f = resolve(MISSIONS_DIR, `${missionId}.json`); | |
| if (!fs.existsSync(f)) return null; | |
| return JSON.parse(fs.readFileSync(f, "utf-8")); | |
| } | |
| export function listMissions() { | |
| if (!fs.existsSync(MISSIONS_DIR)) return []; | |
| return fs.readdirSync(MISSIONS_DIR) | |
| .filter(f => f.endsWith(".json")) | |
| .map(f => { | |
| const m = JSON.parse(fs.readFileSync(resolve(MISSIONS_DIR, f), "utf-8")); | |
| return { | |
| id: m.id, | |
| programName: m.programName, | |
| programHandle: m.programHandle, | |
| status: m.status, | |
| createdAt: m.createdAt, | |
| updatedAt: m.updatedAt, | |
| _inScope: (m?.scope?.inScope || []).length | |
| }; | |
| }).sort((a,b)=> (b._inScope - a._inScope) || String(b.createdAt).localeCompare(String(a.createdAt))) | |
| .map(({_inScope,...rest})=>rest); | |
| } | |
| export function updateMissionStatus(missionId, status) { | |
| const mission = loadMission(missionId); | |
| if (!mission) return null; | |
| mission.status = status; | |
| mission.updatedAt = new Date().toISOString(); | |
| saveMission(mission); | |
| return mission; | |
| } | |
| export function addMissionActivity(missionId, activity) { | |
| const mission = loadMission(missionId); | |
| if (!mission) return; | |
| mission.activityLog = mission.activityLog || []; | |
| mission.activityLog.push({ timestamp: new Date().toISOString(), ...activity }); | |
| saveMission(mission); | |
| } | |
| export function isInScope(target, mission) { | |
| const normalized = normalizeTarget(target); | |
| for (const scopeEntry of mission.scope.inScope) { | |
| if (scopeEntry.isWildcard) { | |
| const base = scopeEntry.normalized.replace(/^\*\./, ""); | |
| if (normalized === base || normalized.endsWith("." + base)) { | |
| return { allowed: true, reason: "Matches wildcard scope", matchingScopeEntry: scopeEntry }; | |
| } | |
| } else if (normalized === scopeEntry.normalized) { | |
| return { allowed: true, reason: "Exact match", matchingScopeEntry: scopeEntry }; | |
| } | |
| } | |
| return { allowed: false, reason: "Not in scope", matchingScopeEntry: null }; | |
| } | |
| export function normalizeTarget(target) { | |
| return target | |
| .replace(/^https?:\/\//, "") | |
| .replace(/\/.*$/, "") | |
| .toLowerCase() | |
| .trim(); | |
| } | |
| export function generateReconPlan(mission) { | |
| const targets = mission.scope.inScope.map(s => s.normalized); | |
| return { | |
| phases: [ | |
| { | |
| phase: 1, | |
| name: "Passive Reconnaissance", | |
| description: "Subdomain enumeration, DNS resolution", | |
| targets, | |
| tools: ["subfinder", "dnsx"] | |
| }, | |
| { | |
| phase: 2, | |
| name: "Active HTTP Probing", | |
| description: "HTTP probing, technology detection", | |
| targets, | |
| tools: ["httpx"] | |
| }, | |
| { | |
| phase: 3, | |
| name: "Validation & Reporting", | |
| description: "Validate findings, generate report", | |
| targets, | |
| tools: ["nuclei", "custom"] | |
| } | |
| ], | |
| notes: [ | |
| "Stage 1 only — NO active exploitation", | |
| "Default deny for all targets not in scope", | |
| "All findings saved per-target per-phase" | |
| ] | |
| }; | |
| } | |
| export async function fetchProgramAndScope(handle) { | |
| const { hackerTools } = await import("./tools.mjs"); | |
| const result = await hackerTools.hackerone(`scope ${handle}`); | |
| const inScope = []; | |
| const outScope = []; | |
| // The result has literal \n (backslash-n) as line separators between scope entries, | |
| // and actual newlines within multi-line descriptions. | |
| // First split by literal \n to get the logical lines. | |
| const lines = result.split("\\n"); | |
| let current = "in"; | |
| for (const line of lines) { | |
| const trimmed = line.trim(); | |
| // Check for section headers on the trimmed line | |
| if (trimmed.includes("IN SCOPE")) current = "in"; | |
| else if (trimmed.includes("OUT OF SCOPE")) current = "out"; | |
| // Check for scope entries on the raw line (which preserves leading spaces) | |
| else if (line.startsWith(" ") && trimmed.includes("(")) { | |
| // Match: "identifier (TYPE) — description" (trimmed has no leading spaces) | |
| const match = trimmed.match(/([^\s]+)\s+\(([^)]+)\)/); | |
| if (match) { | |
| const [, identifier, assetType] = match; | |
| const entry = { | |
| identifier, | |
| normalized: identifier.replace(/^\*\./, "").toLowerCase(), | |
| assetType, | |
| isWildcard: identifier.startsWith("*."), | |
| maxSeverity: "critical", | |
| instruction: current === "out" ? "Not eligible" : "" | |
| }; | |
| if (current === "in") inScope.push(entry); | |
| else outScope.push(entry); | |
| } | |
| } | |
| } | |
| // Offline/degraded fallback: when HackerOne creds/network are unavailable, | |
| // keep the agent usable with a minimal scoped asset derived from the handle. | |
| if (inScope.length === 0) { | |
| const fallbackHost = `${handle}.com`; | |
| inScope.push({ | |
| identifier: fallbackHost, | |
| normalized: fallbackHost, | |
| assetType: "URL", | |
| isWildcard: false, | |
| maxSeverity: "critical", | |
| instruction: "Offline fallback scope", | |
| asset: fallbackHost | |
| }); | |
| inScope.push({ | |
| identifier: `*.${fallbackHost}`, | |
| normalized: fallbackHost, | |
| assetType: "URL", | |
| isWildcard: true, | |
| maxSeverity: "critical", | |
| instruction: "Offline fallback scope", | |
| asset: fallbackHost | |
| }); | |
| } | |
| return { | |
| program: { handle, name: handle, url: `https://hackerone.com/${handle}` }, | |
| scope: { inScope, exclusions: outScope, restrictions: [] } | |
| }; | |
| } |