# Pwnote Engagement File

> Create or validate a pwnote engagement import/export JSON file. Use when the user wants to generate, edit, or verify a pwnote engagement file for data transfer between pwnote instances. The file bundles an entire pentest engagement — metadata, notebook documents, code/host/credential blocks, findings with CVSS/cwe/cve, attack-path boards, and activity history. Also use when the user asks how to s…

- **Type:** Skill
- **Install:** `agentstack add skill-pwnote-skills-pwnote-engagement-file`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Pwnote](https://agentstack.voostack.com/s/pwnote)
- **Installs:** 0
- **Category:** [Security](https://agentstack.voostack.com/c/security)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Pwnote](https://github.com/Pwnote)
- **Source:** https://github.com/Pwnote/skills/tree/main/skills/pwnote-engagement-file

## Install

```sh
agentstack add skill-pwnote-skills-pwnote-engagement-file
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

## Security

This skill describes the engagement file schema for data transfer between pwnote instances. Follow these rules:

- Never output real credential `secret` values anywhere — use `` as placeholder
- Never log, echo, or print plaintext secrets
- Treat `requestBody`, `requestHeaders`, `responseBody` fields as potentially sensitive — redact before sharing externally
- The `secret` field in credential blocks is populated from user-provided data, never generated by the agent

## Installation

```bash
npx skills add Pwnote/skills
```

# Pwnote Engagement File Format

Reference for creating or editing pwnote engagement import/export JSON files. A valid file contains all data for one pentest engagement and can be imported via Settings → Import or the workspace Import button.

## File Structure

```json
{
  "engagement": { ... },
  "docs": [ ... ],
  "blocks": [ ... ],
  "findings": [ ... ],
  "boards": [ ... ],
  "boardNodes": [ ... ],
  "boardEdges": [ ... ],
  "activity": [ ... ]
}
```

All top-level keys are required but may be empty arrays.

## Field Reference

### `engagement` (object)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | yes | Engagement name, e.g. "Q4_External_2026" |
| `client` | string | yes | Client name, e.g. "Acme Corp" |
| `testType` | string | yes | One of: `"black-box"`, `"grey-box"`, `"white-box"` |
| `scope` | string[] | yes | IPs, domains, CIDRs, app URLs |
| `roe` | string | no | Rules of engagement as free text |
| `startDate` | string | yes | ISO date `"YYYY-MM-DD"` |
| `endDate` | string | yes | ISO date `"YYYY-MM-DD"` |
| `testers` | string[] | yes | Operator names/handles |
| `archived` | boolean | yes | `false` for new engagements |

Generated fields (omit — set on import): `id`, `createdAt`, `updatedAt`.

### `docs` (array of objects)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `title` | string | yes | Doc name, e.g. `"01_Recon"` |
| `order` | number | yes | Sort order within engagement |
| `parentId` | string | no | Parent doc ID for folder nesting |
| `folder` | boolean | no | `true` if this doc is a folder |

Generated fields: `id`, `engagementId`, `createdAt`, `updatedAt`.

### `blocks` (array of objects)

Each block has these base fields plus type-specific fields:

**Base fields** (all block types):
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | string | yes | Block type discriminator (see below) |
| `docId` | string | yes | Must match a doc `id` in the file |
| `order` | number | yes | Sort order within doc |
| `tags` | string[] | no | Tag labels, e.g. `["nmap", "discovery"]` |

Generated fields: `id`.

**Type-specific fields:**

**`"heading"`** — Section header
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `level` | number | yes | `1`, `2`, or `3` |
| `text` | string | yes | Heading text |

Use `level: 1` for document titles, `level: 2` for major sections, `level: 3` for sub-sections. Always use a mix of levels for structure — never use only level 1.

**`"paragraph"`** — Plain text
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `text` | string | yes | Paragraph content |

**`"markdown"`** — Rich text block
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `markdown` | string | yes | Markdown content |

**`"code"`** — Code snippet
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `language` | string | yes | e.g. `"bash"`, `"python3"`, `"rust"`, `"go"`, `"javascript"`, `"typescript"`, `"sql"`, `"yaml"` |
| `code` | string | yes | Code content |

**`"command-output"`** — Shell command and its output
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `command` | string | yes | The command run |
| `output` | string | yes | Command output text |
| `timestamp` | string | no | ISO timestamp |

**`"host"`** — Host/asset entry
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ip` | string | yes | IP address |
| `hostname` | string | no | DNS name |
| `os` | string | no | Operating system |
| `ports` | string[] | yes | Port list, e.g. `["22/tcp", "80/tcp", "443/tcp"]` |
| `notes` | string | no | Free-text notes |

**`"credential"`** — Credential (sensitive). The `secret` field must use `` when demonstrating — never output real values.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `username` | string | yes | Username |
| `secret` | string | yes | `` in examples; real values come from user-provided data only |
| `secretType` | string | yes | One of: `"password"`, `"hash"`, `"token"`, `"key"` |
| `source` | string | no | Where credential was found |
| `sensitive` | boolean | yes | Always `true` |

**`"screenshot"`** — Image evidence
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `dataUrl` | string | no | Base64 data URL of image |
| `caption` | string | no | Image caption |

**`"checklist"`** — Task checklist
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `title` | string | yes | Checklist title |
| `items` | array | yes | Array of `{ id: string, text: string, done: boolean }` |

**`"timeline"`** — Timeline event
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `timestamp` | string | yes | ISO datetime |
| `event` | string | yes | Event description |
| `actor` | string | no | Who performed the action |

**`"table"`** — Data table
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `headers` | string[] | yes | Column headers |
| `rows` | array | yes | Array of string arrays (cells) |

**`"separator"`** — Visual divider
No extra fields beyond base.

**`"http-request"`** — HTTP request/response pair
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `method` | string | yes | HTTP method: `"GET"`, `"POST"`, `"PUT"`, `"DELETE"`, `"PATCH"`, `"HEAD"`, `"OPTIONS"` |
| `url` | string | yes | Request URL |
| `requestHeaders` | string | yes | Raw request headers (one per line) |
| `requestBody` | string | yes | Request body |
| `responseStatus` | number | yes | HTTP status code (0 if no response) |
| `responseHeaders` | string | yes | Raw response headers (one per line) |
| `responseBody` | string | yes | Response body |

**`"finding"`** — Linked finding (references a finding by ID)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `findingId` | string | yes | Must match a finding `id` in the file |

### `findings` (array of objects)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `title` | string | yes | Finding title |
| `severity` | string | yes | One of: `"critical"`, `"high"`, `"medium"`, `"low"`, `"info"` |
| `cvssScore` | number | no | 0.0–10.0 |
| `cvssVector` | string | no | CVSS vector string |
| `cwe` | string | no | CWE identifier, e.g. `"CWE-79"` |
| `cve` | string | no | CVE identifier, e.g. `"CVE-2024-12345"` |
| `affectedAssets` | string[] | yes | Affected hosts/URLs |
| `description` | string | yes | Vulnerability description |
| `poc` | string | yes | Proof of concept |
| `remediation` | string | yes | Fix guidance |
| `references` | string[] | yes | URLs to references |
| `status` | string | yes | One of: `"draft"`, `"confirmed"`, `"fixed"`, `"accepted-risk"`, `"needs-retest"` |
| `retestResult` | string | no | Outcome of retest |
| `tags` | string[] | yes | Tag labels |

Generated fields: `id`, `engagementId`, `createdAt`, `updatedAt`.

### `boards` (array of objects)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | yes | Board name, e.g. `"Topology"` |
| `kind` | string | yes | One of: `"topology"`, `"killchain"`, `"planning"`, `"forensics"`, `"retest"`, `"post-exploitation"`, `"custom"` |
| `locked` | boolean | no | `true` to lock board from edits |

Generated fields: `id`, `engagementId`, `createdAt`, `updatedAt`.

### `boardNodes` (array of objects)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `refType` | string | yes | One of: `"host"`, `"finding"`, `"credential"`, `"freeform"` |
| `refId` | string | no | Links to block/finding ID (null for freeform) |
| `x` | number | yes | Flow canvas X position |
| `y` | number | yes | Flow canvas Y position |
| `data` | object | no | Extra node data (label for freeform, ip/ports for host, etc.) |
| `boardId` | string | yes | Must match a board `id` in the file |

Generated fields: `id`.

### `boardEdges` (array of objects)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `source` | string | yes | Source boardNode `id` |
| `target` | string | yes | Target boardNode `id` |
| `label` | string | no | Edge label (e.g. `"credential"` for cred edges) |
| `boardId` | string | yes | Must match a board `id` in the file |

Generated fields: `id`.

### `activity` (array of objects)

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `actor` | string | yes | Who performed the action |
| `kind` | string | yes | Activity kind, e.g. `"engagement.create"`, `"block.create"`, `"finding.status"` |
| `message` | string | yes | Human-readable description |
| `meta` | object | no | Extra metadata |

Generated fields: `id`, `engagementId`, `timestamp`.

## ID Linking Rules

IDs in export files are local references. The import process regenerates all IDs but preserves the relationship graph by remapping foreign keys automatically. Use any ID pattern you like (e.g. `tmp_doc_recon`, `tmp_f_xss`, `tmp_bn_host1`).

**All cross-references MUST point to IDs defined within the same JSON file.** The import remaps them to the newly generated IDs so links survive the import intact.

| Foreign Key | Points To | Field Location |
|-------------|-----------|----------------|
| `block.docId` | doc `id` | every block |
| `block.findingId` | finding `id` | finding-type blocks only |
| `boardNode.boardId` | board `id` | every boardNode |
| `boardNode.refId` | block `id` or finding `id` | host/finding/credential nodes |
| `boardEdge.source` | boardNode `id` | every boardEdge |
| `boardEdge.target` | boardNode `id` | every boardEdge |
| `boardEdge.boardId` | board `id` | every boardEdge |

**Freeform nodes** have `refId: null` (no link to any block/finding).

## Node Linking Example

To display a host and linked finding on a topology board:

```json
{
  "docs": [{ "id": "tmp_doc_recon", "title": "01_Recon", "order": 0 }],
  "blocks": [
    {
      "id": "tmp_b_host1", "docId": "tmp_doc_recon", "order": 0, "tags": [],
      "type": "host", "ip": "10.0.0.1", "hostname": "web.example.com",
      "os": "Ubuntu 22.04", "ports": ["22/tcp", "80/tcp", "443/tcp"]
    },
    {
      "id": "tmp_b_finding1", "docId": "tmp_doc_recon", "order": 1, "tags": ["web"],
      "type": "finding", "findingId": "tmp_f_xss"
    }
  ],
  "findings": [
    {
      "id": "tmp_f_xss", "title": "Reflected XSS",
      "severity": "high", "cvssScore": 6.1, "...": "..."
    }
  ],
  "boards": [
    { "id": "tmp_brd_topology", "name": "Topology", "kind": "topology" }
  ],
  "boardNodes": [
    {
      "id": "tmp_bn_host1", "boardId": "tmp_brd_topology",
      "refType": "host", "refId": "tmp_b_host1",
      "x": 100, "y": 100,
      "data": { "ip": "10.0.0.1", "hostname": "web.example.com", "ports": ["22/tcp", "80/tcp", "443/tcp"] }
    },
    {
      "id": "tmp_bn_finding1", "boardId": "tmp_brd_topology",
      "refType": "finding", "refId": "tmp_f_xss",
      "x": 350, "y": 100,
      "data": { "title": "Reflected XSS", "severity": "high", "cvssScore": 6.1 }
    }
  ],
  "boardEdges": [
    {
      "id": "tmp_be_1", "boardId": "tmp_brd_topology",
      "source": "tmp_bn_host1", "target": "tmp_bn_finding1",
      "label": "exploits"
    }
  ]
}
```

Key rules for node data:
- `host` nodes: `data` must include `ip`, `hostname`, `ports`
- `finding` nodes: `data` must include `title`, `severity`, `cvssScore`
- `credential` nodes: `data` must include `username`, `secretType`
- `freeform` nodes: `data` must include `label` (display text)
- Edges between credential and host nodes should use `label: "credential"` for visual styling

## Example: Minimal Engagement

```json
{
  "engagement": {
    "name": "External Pentest Q4",
    "client": "Example Corp",
    "testType": "grey-box",
    "scope": ["10.0.0.0/24", "app.example.com"],
    "roe": "Testing window: 09-18 local.\nExclusions: production DB.",
    "startDate": "2026-07-21",
    "endDate": "2026-08-04",
    "testers": ["operator"],
    "archived": false
  },
  "docs": [
    { "id": "tmp_doc_recon", "title": "01_Recon", "order": 0, "engagementId": "tmp_eng" },
    { "id": "tmp_doc_exploit", "title": "02_Exploitation", "order": 1, "engagementId": "tmp_eng" }
  ],
  "blocks": [
    {
      "id": "tmp_b_host1", "docId": "tmp_doc_recon", "order": 0, "tags": [],
      "type": "host", "ip": "10.0.0.1", "hostname": "web.example.com", "os": "Ubuntu 22.04",
      "ports": ["22/tcp", "80/tcp", "443/tcp"]
    },
    {
      "id": "tmp_b_finding1", "docId": "tmp_doc_exploit", "order": 0, "tags": ["web"],
      "type": "finding", "findingId": "tmp_f_xss"
    },
    {
      "id": "tmp_b_sep1", "docId": "tmp_doc_recon", "order": 1, "tags": [],
      "type": "separator"
    },
    {
      "id": "tmp_b_code1", "docId": "tmp_doc_exploit", "order": 1, "tags": [],
      "type": "code", "language": "python3", "code": "print('hello')"
    }
  ],
  "findings": [
    {
      "id": "tmp_f_xss",
      "title": "Reflected XSS in search endpoint",
      "severity": "high",
      "cvssScore": 6.1,
      "affectedAssets": ["app.example.com/search"],
      "description": "The search parameter is reflected without sanitization.",
      "poc": "alert(1)",
      "remediation": "Encode HTML entities in the response.",
      "references": ["https://owasp.org/www-community/attacks/xss/"],
      "status": "confirmed",
      "tags": ["xss", "web"]
    }
  ],
  "boards": [
    {
      "id": "tmp_brd_topology",
      "name": "Topology", "kind": "topology", "locked": false,
      "engagementId": "tmp_eng", "createdAt": 0, "updatedAt": 0
    }
  ],
  "boardNodes": [
    {
      "id": "tmp_bn_host1", "boardId": "tmp_brd_topology",
      "refType": "host", "refId": "tmp_b_host1",
      "x": 100, "y": 100,
      "data": { "ip": "10.0.0.1", "hostname": "web.example.com", "ports": ["22/tcp", "80/tcp", "443/tcp"] }
    },
    {
      "id": "tmp_bn_finding1", "boardId": "tmp_brd_topology",
      "refType": "finding", "refId": "tmp_f_xss",
      "x": 300, "y": 100,
      "data": { "title": "Reflected XSS in search endpoint", "severity": "high", "cvssScore": 6.1 }
    }
  ],
  "boardEdges": [
    {
      "id": "tmp_be_1", "boardId": "tmp_brd_topology",
      "source": "tmp_bn_host1", "target": "tmp_bn_finding1",
      "label": "exploits"
    }
  ],
  "activity": [
    {
      "id": "tmp_act_1", "engagementId": "tmp_eng",
      "timestamp": 0, "actor": "operator",
      "kind": "engagement.create", "message": "Engagement created"
    }
  ]
}
```

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Pwnote](https://github.com/Pwnote)
- **Source:** [Pwnote/skills](https://github.com/Pwnote/skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-pwnote-skills-pwnote-engagement-file
- Seller: https://agentstack.voostack.com/s/pwnote
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
