@memrov2/pentcloud-recon 0.0.0-stage → 0.1.1-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PentCloud contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,113 @@
1
- # Temporary Holding Version
1
+ # PentCloud tools
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![Quality](https://github.com/Memrov/PentCloud-tools/actions/workflows/quality.yml/badge.svg)](https://github.com/Memrov/PentCloud-tools/actions/workflows/quality.yml)
4
+ [![codecov](https://codecov.io/gh/Memrov/PentCloud-tools/branch/main/graph/badge.svg)](https://app.codecov.io/gh/Memrov/PentCloud-tools)
5
+ [![npm](https://badgen.net/npm/v/@memrov2/pentcloud-recon)](https://www.npmjs.com/package/@memrov2/pentcloud-recon)
6
+ [![Adapter license: MIT](https://badgen.net/static/adapter%20license/MIT/blue)](LICENSE)
7
+
8
+ Small, versioned tool runtimes for Cloudflare. The first package, **`@memrov2/pentcloud-recon`**, runs the original httpx, dnsx, Katana and Subfinder binaries together in one lightweight Linux container.
9
+
10
+ Each tool is callable separately. Cloudflare Workflows can orchestrate the calls, or an agent harness can call the same Durable Object binding. This repository does not deploy or modify the PentCloud application.
11
+
12
+ | Included tool | Pinned release | First-release behavior |
13
+ | --- | --- | --- |
14
+ | httpx | v1.12.0 | HTTP status, title, server and technology signals |
15
+ | dnsx | v1.3.1 | A and AAAA resolution |
16
+ | Katana | v1.8.0 | Depth-two crawl within the starting URL prefix |
17
+ | Subfinder | v2.17.0 | Passive certificate discovery using `crtname` |
18
+
19
+ ## Call a tool
20
+
21
+ Install the pinned npm release in your Worker project. Release tarballs and the source checkout support the same imports.
22
+
23
+ ```sh
24
+ npm install @memrov2/pentcloud-recon@0.1.0
25
+ ```
26
+
27
+ ```js
28
+ // Export the Durable Object from your Worker's entry point.
29
+ export { ReconRunner } from '@memrov2/pentcloud-recon/worker';
30
+
31
+ // Use a separate Durable Object identity for each assessment run.
32
+ const runner = env.RECON.getByName(runId);
33
+ try {
34
+ const result = await runner.run({
35
+ tool: 'httpx',
36
+ target: 'https://your-authorized-target.example/'
37
+ });
38
+ // Save this result to your existing run/evidence store.
39
+ } finally {
40
+ await runner.stop();
41
+ }
42
+ ```
43
+
44
+ `httpx` and `katana` accept an absolute HTTP(S) URL. `dnsx` and `subfinder` accept a hostname. The first release accepts exactly `tool` and `target`; additional flags and arbitrary commands are rejected. URLs with embedded login credentials are rejected. JSON stdout, stderr, exit code, timeout/truncation state, execution timing, image identity and tool/pack versions are returned without flattening upstream records.
45
+
46
+ Calls on one identity are serialized. The default allocation is one vCPU, 512 MiB memory and 2,000 MB disk. An unused runtime shuts down after 120 seconds; explicitly stop it when the run ends. These limits were tested for serial execution, not parallel processes within the same container.
47
+
48
+ ## Build and deploy in your account
49
+
50
+ Requirements: Node.js 22 or later, npm, Python 3, Go **1.26.5**, Cloudflare Workers Paid with Containers access, and native Cloudflare CLI authentication. The included example uses the same Wrangler **4.157.0** deployment tooling as the tested runtime. Building the Dockerfile also requires a running Docker-compatible engine capable of building Linux/amd64 images.
51
+
52
+ ```sh
53
+ npm ci
54
+ npm test
55
+ npm run test:runner
56
+ npm run test:image
57
+ npm run typecheck
58
+ npm run image:prepare
59
+ npx wrangler login
60
+ npx wrangler deploy --config examples/cloudflare/wrangler.jsonc
61
+ ```
62
+
63
+ The prepare step downloads four upstream release archives, verifies archive and executable SHA-256 values, copies their license files, and builds the small static execution helper. Tool installation and compilation happen at build time, never during an assessment.
64
+
65
+ The example has no public execution endpoint, workers.dev exposure or preview URL. Start `ReconWorkflow` through Cloudflare's authenticated Workflows API with a payload such as:
66
+
67
+ ```json
68
+ {"tool":"dnsx","target":"your-authorized-target.example"}
69
+ ```
70
+
71
+ For multiple calls within one assessment, reuse that run's Durable Object identity and stop it after the last call. The example Workflow performs one tool call and then stops its runtime. Adapt evidence persistence to your application's existing store; this package does not add a database or exporter.
72
+
73
+ For a build without Docker, install **regctl v0.9.0** and run `npm run image:build`. This creates the OCI layout in `.cache/image` and records its digest in `.cache/image-release.json`. Copy the image into your Cloudflare-managed registry using your authenticated registry tooling. Configure the resulting account-local digest as the `recon` named image. Cloudflare's native registry and deployment commands are documented below.
74
+
75
+ ## Versions and upgrades
76
+
77
+ Version `0.1.0` identifies the recon pack and its SDK contract. `tools.lock.json` records each upstream version, archive checksum, binary checksum and the base image digest. Updating one tool preserves the other pins and produces a new pack/image release. This repository does not fork the upstream tools.
78
+
79
+ Pin the npm package and container image digest together. The runner checks the image's release manifest before executing commands and rejects a mismatched running image. Finish active runs before upgrading their SDK/image pair; retain prior images and matching adapters for retries and rollback. Publishing a package does not deploy it to customer accounts or replace a running container.
80
+
81
+ To prepare a release: update the package/lock versions together, run the tests and Cloudflare smoke check, build the image, inspect `npm pack --dry-run`, and attach the verified npm tarball, image archive and SHA-256 checksums to the release. Authentication belongs in native CLI login or a secret store, never source files. GitHub Actions runs synthetic quality checks only; publishing uses the maintainer’s local npm authentication. No scanners or assessments run in Actions.
82
+
83
+ ## Limits and safety
84
+
85
+ Only test targets you are authorized to assess. Argument validation and Katana's URL-prefix filter are **not network-level scope enforcement**. DNS resolution, redirects, tool behavior and future capabilities require their own assessment. This package does not claim to restrict every possible destination.
86
+
87
+ HTTP/crawl concurrency is one, with a two-request-per-second tool limit; Subfinder uses one request per second. Each command has a 75-second process deadline, with two MiB captured per output stream. Deadline/output cancellation kills the process group. Nonzero command exits remain visible in the returned result and are not automatically retried by the example Workflow.
88
+
89
+ Do not send model keys or unrelated account credentials to this runtime. The execution helper passes a minimal environment to the tools. Treat stdout/stderr as potentially sensitive assessment evidence; store them privately, keep runs separated, and apply your own deletion policy. Browser login, provider API credentials and additional tool options are outside this first release.
90
+
91
+ The runtime supports Linux/amd64 on Cloudflare Containers. It does not make these binaries executable inside the Workers JavaScript isolate. Other Kali tools have not been ported or certified by this release.
92
+
93
+ ## References
94
+
95
+ - [Cloudflare native Container API](https://developers.cloudflare.com/containers/api/durable-object-container/)
96
+ - [Image management and registry](https://developers.cloudflare.com/containers/guides/image-management/)
97
+ - [Workflows](https://developers.cloudflare.com/workflows/)
98
+ - [ProjectDiscovery tools](https://github.com/projectdiscovery)
99
+ - [Third-party notices](THIRD_PARTY_NOTICES.md)
100
+
101
+ MIT license for the PentCloud adapter. Included executables and base-image packages retain their upstream licenses.
102
+
103
+ ## Tool-pack inventory and quality
104
+
105
+ See [tool packs and agent capabilities](docs/tool-packs.md) for the current Kali interface inventory, the needs of each agent, and the proposed next bundles. Candidate tools are clearly separated from released support.
106
+
107
+ Run `npm run coverage` after the development setup in [testing and coverage](docs/testing.md). Reports use standard Node LCOV, Go coverprofile and Python Cobertura formats. The public Quality workflow runs these synthetic tests and uploads explicit coverage reports to Codecov. Cloud account, npm publishing and target credentials are not available to the workflow.
108
+
109
+ The [release table](docs/releases.md) lists the npm SDKs and their image-distribution requirements. [HTTP, Nuclei and SQLmap](docs/bundles.md) and [the other tool families](docs/remaining-bundles.md) each have their own pinned image and bounded interface. Browser automation stays with managed Browser Run. These profiles do not replace every Kali operation.
110
+
111
+ ## Named instance presets (development)
112
+
113
+ The next SDK version uses Cloudflare's named Container presets. Most bounded packs start on `lite`; recon, password and Metasploit use `standard-1`. `ready()` verifies and prepares a runtime without executing a scanner. Actual tool execution still waits for readiness and verifies the image manifest. SDK-only lifecycle/sizing updates can retain the same tool lock and image when their executable payload and image manifest are unchanged. Released SDK versions remain immutable.
@@ -0,0 +1,14 @@
1
+ # Third-party software
2
+
3
+ The npm package contains the PentCloud adapter and tool release metadata. The separately built container contains unchanged upstream executables:
4
+
5
+ | Tool | Release | Source and license |
6
+ | --- | --- | --- |
7
+ | httpx | v1.12.0 | https://github.com/projectdiscovery/httpx/tree/v1.12.0 |
8
+ | dnsx | v1.3.1 | https://github.com/projectdiscovery/dnsx/tree/v1.3.1 |
9
+ | Katana | v1.8.0 | https://github.com/projectdiscovery/katana/tree/v1.8.0 |
10
+ | Subfinder | v2.17.0 | https://github.com/projectdiscovery/subfinder/tree/v2.17.0 |
11
+
12
+ The build verifies each upstream archive and executable using `tools.lock.json`, and includes the archive's license files under `/usr/share/licenses/pentcloud-recon/<tool>/`. The upstream tools retain their own licenses; the adapter's MIT license does not replace them.
13
+
14
+ The base is the pinned Alpine Linux 3.22.2 linux/amd64 image. Alpine packages retain their respective licenses. Base source and package metadata are available from https://alpinelinux.org/ and https://pkgs.alpinelinux.org/packages?branch=v3.22. Review the applicable upstream distribution obligations before redistributing a built image.
package/package.json CHANGED
@@ -1,6 +1,53 @@
1
1
  {
2
2
  "name": "@memrov2/pentcloud-recon",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1-dev.0",
4
+ "description": "A small Cloudflare-native runtime for httpx, dnsx, Katana and Subfinder",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/Memrov/PentCloud-tools.git"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./src/index.d.mts",
14
+ "import": "./src/index.mjs"
15
+ },
16
+ "./worker": {
17
+ "types": "./src/worker.d.mts",
18
+ "import": "./src/worker.mjs"
19
+ },
20
+ "./tools.lock.json": "./tools.lock.json"
21
+ },
22
+ "files": [
23
+ "src",
24
+ "tools.lock.json",
25
+ "THIRD_PARTY_NOTICES.md"
26
+ ],
27
+ "scripts": {
28
+ "test": "node --test test/*.test.mjs",
29
+ "test:runner": "go test ./runner",
30
+ "image:prepare": "python3 scripts/prepare-image.py",
31
+ "image:build": "node scripts/build-image.mjs",
32
+ "prepublishOnly": "npm test && npm run test:runner && npm run test:image && npm run typecheck",
33
+ "typecheck": "wrangler types --config examples/cloudflare/wrangler.jsonc .cache/worker-types.d.ts && tsc --noEmit --strict --skipLibCheck --module nodenext --moduleResolution nodenext --target es2022 .cache/worker-types.d.ts test/types.ts",
34
+ "test:image": "python3 -m unittest discover -s test -p \"*_test.py\"",
35
+ "coverage": "node scripts/coverage.mjs",
36
+ "test:bundles": "node --test test/packs.test.mjs test/pack-runtime.test.mjs",
37
+ "image:pack": "python3 scripts/prepare-pack.py",
38
+ "image:pack:build": "node scripts/build-pack.mjs"
39
+ },
40
+ "engines": {
41
+ "node": ">=22"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "devDependencies": {
47
+ "typescript": "5.9.3",
48
+ "wrangler": "4.157.0"
49
+ },
50
+ "workspaces": [
51
+ "packages/*"
52
+ ]
53
+ }
@@ -0,0 +1,36 @@
1
+ export type ReconTool = "httpx" | "dnsx" | "katana" | "subfinder";
2
+ export interface ReconRequest {
3
+ tool: ReconTool;
4
+ target: string;
5
+ }
6
+ export interface ReconCommand {
7
+ argv: string[];
8
+ stdin: string;
9
+ timeoutMs: number;
10
+ }
11
+ export interface ReconResult {
12
+ tool: ReconTool;
13
+ toolVersion: string;
14
+ packVersion: string;
15
+ image: string;
16
+ startedAt: string;
17
+ finishedAt: string;
18
+ argv: string[];
19
+ exitCode: number;
20
+ stdout: string;
21
+ stderr: string;
22
+ timedOut: boolean;
23
+ truncated: boolean;
24
+ processMs: number;
25
+ firstJSONMs: number;
26
+ cpuUserMs?: number;
27
+ cpuSystemMs?: number;
28
+ maxRSSKiB?: number;
29
+ error?: string;
30
+ }
31
+ export declare const packVersion: string;
32
+ export declare const tools: readonly Readonly<{
33
+ tool: ReconTool;
34
+ version: string;
35
+ }>[];
36
+ export declare function buildCommand(request: ReconRequest): ReconCommand;
package/src/index.mjs ADDED
@@ -0,0 +1,144 @@
1
+ import lock from "../tools.lock.json" with { type: "json" };
2
+ export const packVersion = lock.packVersion;
3
+ export const tools = Object.freeze(
4
+ lock.tools.map(({ tool, version }) => Object.freeze({ tool, version })),
5
+ );
6
+ const prefix = "/opt/pentcloud-recon/bin/";
7
+ function hostname(value) {
8
+ if (
9
+ value.length > 253 ||
10
+ !value
11
+ .split(".")
12
+ .every(
13
+ (s) =>
14
+ s.length > 0 &&
15
+ s.length <= 63 &&
16
+ /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(s),
17
+ )
18
+ )
19
+ throw new TypeError(
20
+ "Use a DNS hostname, without a URL, wildcard or options",
21
+ );
22
+ return value.toLowerCase();
23
+ }
24
+ export function buildCommand(input) {
25
+ if (
26
+ !input ||
27
+ typeof input !== "object" ||
28
+ Array.isArray(input) ||
29
+ Object.keys(input).some((k) => !["tool", "target"].includes(k))
30
+ )
31
+ throw new TypeError("Expected only tool and target");
32
+ const { tool, target } = input;
33
+ if (
34
+ !tools.some((t) => t.tool === tool) ||
35
+ typeof target !== "string" ||
36
+ !target ||
37
+ target.length > 2048 ||
38
+ /[\s\x00-\x1f\x7f]/.test(target)
39
+ )
40
+ throw new TypeError("Unsupported tool or invalid target");
41
+ let args,
42
+ stdin = "";
43
+ if (tool === "httpx" || tool === "katana") {
44
+ let url;
45
+ try {
46
+ url = new URL(target);
47
+ } catch {
48
+ throw new TypeError("Use an absolute HTTP or HTTPS URL");
49
+ }
50
+ if (
51
+ !["http:", "https:"].includes(url.protocol) ||
52
+ !url.hostname ||
53
+ url.username ||
54
+ url.password ||
55
+ url.hash
56
+ )
57
+ throw new TypeError(
58
+ "Use an HTTP(S) URL without credentials or a fragment",
59
+ );
60
+ const normalized = url.href;
61
+ if (tool === "httpx") {
62
+ args = [
63
+ "-j",
64
+ "-silent",
65
+ "-duc",
66
+ "-sc",
67
+ "-title",
68
+ "-server",
69
+ "-td",
70
+ "-nfs",
71
+ "-rl",
72
+ "2",
73
+ "-t",
74
+ "1",
75
+ "-timeout",
76
+ "10",
77
+ ];
78
+ stdin = normalized + "\n";
79
+ } else {
80
+ if (url.search)
81
+ throw new TypeError("Start a crawl with a URL without a query string");
82
+ const scope = normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
83
+ args = [
84
+ "-u",
85
+ normalized,
86
+ "-cs",
87
+ "^" + scope,
88
+ "-d",
89
+ "2",
90
+ "-c",
91
+ "1",
92
+ "-p",
93
+ "1",
94
+ "-rl",
95
+ "2",
96
+ "-ct",
97
+ "20s",
98
+ "-timeout",
99
+ "10",
100
+ "-jc",
101
+ "-no-default-ext-filter",
102
+ "-j",
103
+ "-silent",
104
+ "-duc",
105
+ ];
106
+ }
107
+ } else {
108
+ const host = hostname(target);
109
+ if (tool === "dnsx") {
110
+ args = [
111
+ "-j",
112
+ "-silent",
113
+ "-duc",
114
+ "-a",
115
+ "-aaaa",
116
+ "-rl",
117
+ "2",
118
+ "-t",
119
+ "1",
120
+ "-timeout",
121
+ "5",
122
+ "-retry",
123
+ "1",
124
+ ];
125
+ stdin = host + "\n";
126
+ } else
127
+ args = [
128
+ "-d",
129
+ host,
130
+ "-sources",
131
+ "crtname",
132
+ "-json",
133
+ "-silent",
134
+ "-duc",
135
+ "-rl",
136
+ "1",
137
+ "-timeout",
138
+ "10",
139
+ "-max-time",
140
+ "1",
141
+ ];
142
+ }
143
+ return { argv: [prefix + tool, ...args], stdin, timeoutMs: 75000 };
144
+ }
@@ -0,0 +1,8 @@
1
+ import { DurableObject } from "cloudflare:workers";
2
+ import type { ReconRequest, ReconResult } from "./index.mjs";
3
+ export declare class ReconRunner extends DurableObject {
4
+ ready(): Promise<{ image: string; instance: "standard-1"; readyMs: number }>;
5
+ run(request: ReconRequest): Promise<ReconResult & { readyMs: number }>;
6
+ retire(): Promise<{ stopped: boolean; retired: true }>;
7
+ stop(): Promise<{ stopped: boolean }>;
8
+ }
package/src/worker.mjs ADDED
@@ -0,0 +1,149 @@
1
+ import { DurableObject } from "cloudflare:workers";
2
+ import { buildCommand, packVersion, tools } from "./index.mjs";
3
+ const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
4
+ const decode = (data) => new TextDecoder().decode(data);
5
+ async function exec(
6
+ container,
7
+ argv,
8
+ body,
9
+ signal = AbortSignal.timeout(90000),
10
+ ) {
11
+ const process = await container.exec(argv, {
12
+ signal,
13
+ ...(body ? { stdin: new Blob([body]).stream() } : {}),
14
+ });
15
+ const result = await process.output();
16
+ return {
17
+ exitCode: result.exitCode,
18
+ stdout: decode(result.stdout),
19
+ stderr: decode(result.stderr),
20
+ };
21
+ }
22
+ /** One identity per assessment run. Only trusted Worker/Workflow bindings call this object. */
23
+ export class ReconRunner extends DurableObject {
24
+ #queue = Promise.resolve();
25
+ #serialize(action) {
26
+ const result = this.#queue.then(action);
27
+ this.#queue = result.catch(() => {});
28
+ return result;
29
+ }
30
+ async #ready() {
31
+ if (await this.ctx.storage.get("retired")) throw new Error("Owned tool runtime is retired");
32
+ const container = this.ctx.container,
33
+ image = container.images.recon,
34
+ start = Date.now();
35
+ if (!image) throw new Error("Configure the recon named image");
36
+ if (container.running) {
37
+ const info = await container.inspect();
38
+ if (info?.image && info.image !== image)
39
+ throw new Error(
40
+ "Recon image changed; finish this run and stop its runtime before upgrading",
41
+ );
42
+ } else
43
+ container.start({
44
+ image,
45
+ instance: "standard-1",
46
+ enableInternet: true,
47
+ labels: { pack: "recon", version: packVersion },
48
+ });
49
+ await container.setInactivityTimeout(120000);
50
+ const deadline = AbortSignal.timeout(90000);
51
+ let ready = false;
52
+ while (Date.now() - start < 90000 && !deadline.aborted) {
53
+ try {
54
+ if (
55
+ (await exec(container, ["/bin/true"], undefined, deadline))
56
+ .exitCode === 0
57
+ ) {
58
+ ready = true;
59
+ break;
60
+ }
61
+ } catch {}
62
+ if (!deadline.aborted)
63
+ await wait(Math.min(1000, Math.max(0, 90000 - (Date.now() - start))));
64
+ }
65
+ if (!ready)
66
+ throw new Error("Recon container did not become ready within 90 seconds");
67
+ const marker = await exec(
68
+ container,
69
+ ["/bin/cat", "/opt/pentcloud-recon/pack.json"],
70
+ undefined,
71
+ deadline,
72
+ );
73
+ if (marker.exitCode !== 0)
74
+ throw new Error("Recon image is missing its release manifest");
75
+ let release;
76
+ try {
77
+ release = JSON.parse(marker.stdout);
78
+ } catch {
79
+ throw new Error("Invalid recon image release manifest");
80
+ }
81
+ if (
82
+ release.packVersion !== packVersion ||
83
+ tools.some(
84
+ (t) =>
85
+ !release.tools?.some(
86
+ (v) => v.tool === t.tool && v.version === t.version,
87
+ ),
88
+ )
89
+ )
90
+ throw new Error("Recon image and SDK versions do not match");
91
+ const info = await container.inspect();
92
+ if (info?.image !== image)
93
+ throw new Error("Unable to verify recon image identity");
94
+ return { image, instance: "standard-1", readyMs: Date.now() - start };
95
+ }
96
+ ready() {
97
+ return this.#serialize(() => this.#ready());
98
+ }
99
+ run(input) {
100
+ const command = buildCommand(input);
101
+ return this.#serialize(async () => {
102
+ const startedAt = new Date().toISOString(),
103
+ ready = await this.#ready();
104
+ const output = await exec(
105
+ this.ctx.container,
106
+ ["/opt/pentcloud-recon/bin/recon-exec"],
107
+ JSON.stringify(command),
108
+ );
109
+ if (output.exitCode !== 0)
110
+ throw new Error("Recon execution helper failed");
111
+ let result;
112
+ try {
113
+ result = JSON.parse(output.stdout);
114
+ } catch {
115
+ throw new Error("Invalid recon execution response");
116
+ }
117
+ if (
118
+ !Number.isInteger(result.exitCode) ||
119
+ typeof result.stdout !== "string" ||
120
+ typeof result.stderr !== "string" ||
121
+ typeof result.timedOut !== "boolean" ||
122
+ typeof result.truncated !== "boolean"
123
+ )
124
+ throw new Error("Incomplete recon execution response");
125
+ return {
126
+ ...result,
127
+ ...ready,
128
+ tool: input.tool,
129
+ toolVersion: tools.find((t) => t.tool === input.tool).version,
130
+ packVersion,
131
+ startedAt,
132
+ finishedAt: new Date().toISOString(),
133
+ };
134
+ });
135
+ }
136
+ retire() {
137
+ return this.#serialize(async () => {
138
+ await this.ctx.storage.put("retired", true);
139
+ if (this.ctx.container.running) await this.ctx.container.destroy();
140
+ return { stopped: !this.ctx.container.running, retired: true };
141
+ });
142
+ }
143
+ stop() {
144
+ return this.#serialize(async () => {
145
+ if (this.ctx.container.running) await this.ctx.container.destroy();
146
+ return { stopped: !this.ctx.container.running };
147
+ });
148
+ }
149
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "packVersion": "0.1.0",
4
+ "platform": "linux/amd64",
5
+ "baseImage": "docker.io/library/alpine@sha256:85f2b723e106c34644cd5851d7e81ee87da98ac54672b29947c052a45d31dc2f",
6
+ "tools": [
7
+ {
8
+ "tool": "httpx",
9
+ "version": "v1.12.0",
10
+ "assetSha256": "9d8439e8b6c9aa7d1e2314817a392e00d5178da3af5652f7475f88868f418f76",
11
+ "downloadBytes": 19200026,
12
+ "binaryBytes": 54444194,
13
+ "url": "https://github.com/projectdiscovery/httpx/releases/download/v1.12.0/httpx_1.12.0_linux_amd64.zip",
14
+ "binarySha256": "6d78b83b4da452a7e0bd3613c4fae04cbf523209f083904b563f4d4406a680b0"
15
+ },
16
+ {
17
+ "tool": "dnsx",
18
+ "version": "v1.3.1",
19
+ "assetSha256": "438b964653056dd51dcfe614b1a16f8bced3cc48a1d27bc07cc6fdf2ef2a9533",
20
+ "downloadBytes": 9829391,
21
+ "binaryBytes": 28569784,
22
+ "url": "https://github.com/projectdiscovery/dnsx/releases/download/v1.3.1/dnsx_1.3.1_linux_amd64.zip",
23
+ "binarySha256": "5fca43ddfe95922d8d88a859c1fcc59fee327bf39ae574db66d391c6675b8ba3"
24
+ },
25
+ {
26
+ "tool": "katana",
27
+ "version": "v1.8.0",
28
+ "assetSha256": "cb027ae99d0757061b70c07a15c7a80f62c5a535ab52c4a329f80c4b0a7309d2",
29
+ "downloadBytes": 31910234,
30
+ "binaryBytes": 68030626,
31
+ "url": "https://github.com/projectdiscovery/katana/releases/download/v1.8.0/katana_1.8.0_linux_amd64.zip",
32
+ "binarySha256": "3214c11a0548667b37ec17bbb691706b7e0de1f0f24b3ead2ee12c301ebc1bbc"
33
+ },
34
+ {
35
+ "tool": "subfinder",
36
+ "version": "v2.17.0",
37
+ "assetSha256": "a39c3e5c7a26ef3c430915c4d6cbf7716a00f26769a75e9405aba796142eba37",
38
+ "downloadBytes": 10308162,
39
+ "binaryBytes": 30474402,
40
+ "url": "https://github.com/projectdiscovery/subfinder/releases/download/v2.17.0/subfinder_2.17.0_linux_amd64.zip",
41
+ "binarySha256": "bf4ad8590702b0bb92be9b3be185f96a230b047c9b97f2831cf8914344f0b15a"
42
+ }
43
+ ]
44
+ }