From 9f9905a3e8053d068b2448a8b86c1f43df65fb5b Mon Sep 17 00:00:00 2001 From: Moezzarella Date: Tue, 23 Jun 2026 12:26:26 +0200 Subject: [PATCH] chore: bundle schemas, drop postinstall fetch, and modernize build Closes #39 Install / supply chain: - Remove the postinstall schema download. It failed npm install in offline, air-gapped, and egress-restricted environments and was an install-time supply-chain surface. Schemas are now bundled into src/schemas.generated.ts (generated by scripts/generate-schemas.js) and read in-memory, so the package has zero filesystem or network dependency at runtime. - Patch fast-uri via an ajv bump (^8.20.0) and an overrides pin; the production dependency tree now audits clean. Move tar to devDependencies. Add a blocking production-only npm audit step in CI. - Harden the maintainer-only fetch-schemas.js: HTTPS-only host allowlist, bounded redirects, and a download size cap. Runtime / validation: - Read schemas from the in-memory bundle instead of the filesystem; drop the findSchemasDir()/process.cwd() discovery logic and the dead recursive $ref loader in SchemaValidator. - Add an optional maxInputBytes guard to parse() to bound untrusted input. Build / packaging: - Dual ESM + CommonJS build via tsup with a condition-specific exports map. - Move moduleResolution to NodeNext (node10 is deprecated/removed in TS 7). - Raise Node engine floor to >=18. Docs / correctness: - Fix the strict-mode "throws" claim in README and JSDoc (it does not throw). - Fix the CHANGELOG v3 unknown-type fallback description. - Derive VERSION from package.json; export BUNDLED_SPEC_VERSION. Tests: 207 -> 229 passing; coverage thresholds raised. --- .github/workflows/ci.yml | 12 +- .github/workflows/publish.yml | 4 + .prettierignore | 10 + CHANGELOG.md | 43 +- README.md | 20 +- jest.config.js | 12 +- package-lock.json | 1378 +++++++++++++++++++++++++- package.json | 44 +- scripts/fetch-schemas.js | 72 +- scripts/generate-schemas.js | 141 +++ src/index.ts | 5 +- src/parser.ts | 48 +- src/schema-registry.ts | 58 +- src/schema-utils.ts | 84 +- src/schema-validator.ts | 424 ++------ src/schemas.generated.ts | 55 + src/validator.ts | 20 +- src/version.ts | 9 +- tests/bundle.test.ts | 81 ++ tests/parser.test.ts | 70 ++ tests/schema-validator-guard.test.ts | 42 + tsconfig.json | 4 +- tsup.config.ts | 16 + 23 files changed, 2165 insertions(+), 487 deletions(-) create mode 100644 .prettierignore create mode 100644 scripts/generate-schemas.js create mode 100644 src/schemas.generated.ts create mode 100644 tests/bundle.test.ts create mode 100644 tests/schema-validator-guard.test.ts create mode 100644 tsup.config.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb8bf6c..4953b38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,9 +31,12 @@ jobs: run: npm run lint - name: Format check - run: npm run format:check || true + run: npm run format:check - name: Type check + run: npm run typecheck + + - name: Build run: npm run build test: @@ -81,6 +84,11 @@ jobs: - name: Install dependencies run: npm ci - - name: Run npm audit + # Blocking: anything that ships to consumers must be clean. + - name: Audit production dependencies + run: npm audit --omit=dev --audit-level=high + + # Informational: dev-only advisories should be tracked but not block CI. + - name: Audit all dependencies run: npm audit --audit-level=high continue-on-error: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 22bbb6e..e2e997e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,6 +25,10 @@ jobs: - name: Install dependencies run: npm ci + # The committed src/schemas.generated.ts is the source of truth and is + # built/published as-is (no network fetch at publish time). `npm test` + # asserts BUNDLED_SPEC_VERSION matches package.json's xarfSpec.version + # (see tests/bundle.test.ts), so a stale bundle fails here, offline. - name: Run tests run: npm test diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..f17d9b2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,10 @@ +# Build output +dist/ +coverage/ + +# Auto-generated (intentionally minified) bundled schemas +src/schemas.generated.ts + +# Fetched schemas (gitignored, but excluded here for completeness) +schemas/ +.xarf-temp/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 70ccf5d..f053171 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,52 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Security + +- **Removed the `postinstall` schema fetch.** The package no longer runs a + network download on install, which previously failed `npm install` in + offline/air-gapped/proxied environments and was an install-time supply-chain + surface. Schemas are now bundled into the package at build time. +- **Patched `fast-uri`** (the only runtime-tree advisory) via an `ajv` bump and + a dependency override; the production dependency tree now audits clean. +- **Hardened `scripts/fetch-schemas.js`** (a maintainer-only tool): host + allowlist, redirect-depth cap, and download size cap. +- Added an optional `maxInputBytes` guard to `parse()` to bound untrusted input. + +### Added + +- `BUNDLED_SPEC_VERSION` export — the xarf-spec version bundled into the build. +- `VERSION` export now derived from `package.json` (no longer hardcoded). +- Dual **ESM + CommonJS** build with an `exports` map and type declarations. + +### Changed + +- Schemas are read from an in-memory bundle (`src/schemas.generated.ts`, + produced by `scripts/generate-schemas.js`) instead of from the filesystem at + runtime. This removes the `findSchemasDir()` discovery logic and makes the + library work in bundled, serverless, and edge environments. +- `tar` moved from runtime `dependencies` to `devDependencies`. +- Minimum Node version raised to `>=18`. + +### Removed + +- Dead, unreachable recursive `$ref` loader in `SchemaValidator`. + +### Fixed + +- Documentation: `parse()`/`createReport()` `strict` mode does **not** throw — + it reports warnings and `x-recommended` fields as errors in the returned + result. The README and JSDoc previously claimed it threw. + ## [1.0.0] - 2025-11-30 ### Breaking Changes - **Category Correction**: Removed "other" category to align with XARF v4.0.0 specification - XARF spec defines exactly 7 categories (not 8) - - Unknown v3 report types now map to `content` category with type `unclassified` + - Unknown v3 report types now throw `XARFParseError` listing the supported types - Migration: Replace any usage of `category: 'other'` with `category: 'content'` ### Added @@ -27,7 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Version**: Updated from v1.0.0-alpha.2 to v1.0.0 (production release) - **Category Validation**: Stricter validation for all 7 official categories -- **v3 Legacy Mapping**: Unknown v3 types now map to `content/unclassified` instead of `other/unclassified` +- **v3 Legacy Mapping**: Unknown v3 types now throw `XARFParseError` (the removed `other/unclassified` fallback no longer exists) - **Documentation**: Updated all references from 8 to 7 categories ### Fixed diff --git a/README.md b/README.md index 8a471cc..44c6753 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,11 @@ const { report, errors, warnings, info } = parse(jsonData, options?); **Parameters:** - `jsonData: string | Record` — JSON string or object containing a XARF report -- `options.strict?: boolean` — Throw `XARFValidationError` on validation failures (default: `false`) +- `options.strict?: boolean` — Treat warnings (e.g. unknown fields) and `x-recommended` fields as errors (default: `false`) - `options.showMissingOptional?: boolean` — Include info about missing optional fields (default: `false`) +> `parse()` does not throw on validation failures — inspect the returned `errors` array. It only throws `XARFParseError` when `jsonData` is a malformed JSON string. + **Returns `ParseResult`:** - `report: XARFReport` — The parsed report, typed by category @@ -135,9 +137,11 @@ const { report, errors, warnings } = createReport(input, options?); **Parameters:** - `input: ReportInput` — Report data. A discriminated union on `category` that narrows type-safe fields per category (e.g., `MessagingReportInput`, `ConnectionReportInput`, etc.) -- `options.strict?: boolean` — Throw on validation failures (default: `false`) +- `options.strict?: boolean` — Treat warnings and `x-recommended` fields as errors (default: `false`) - `options.showMissingOptional?: boolean` — Include info about missing optional fields (default: `false`) +> Like `parse()`, `createReport()` does not throw on validation failures — the report is always returned alongside any `errors`. + **Returns `CreateReportResult`:** - `report: XARFReport` — The generated report @@ -237,7 +241,9 @@ Unknown v3 report types cause a parse error listing the supported types. See [MI ## Schema Management -This library validates against the official [xarf-spec](https://github.com/xarf/xarf-spec) JSON schemas. Schemas are fetched automatically on `npm install` based on the version configured in `package.json`: +This library validates against the official [xarf-spec](https://github.com/xarf/xarf-spec) JSON schemas. The schemas are **bundled into the package** at build time, so installation requires no network access and the library works in any environment (Node, bundlers, serverless, edge) with zero filesystem dependency. The bundled spec version is exposed as `BUNDLED_SPEC_VERSION`. + +The schema version is configured in `package.json`: ```json "xarfSpec": { @@ -245,15 +251,17 @@ This library validates against the official [xarf-spec](https://github.com/xarf/ } ``` +For **maintainers**, updating to a newer spec version is a two-step, network-only-at-build-time process: + ```bash # Check if a newer version of xarf-spec is available npm run check-schema-updates -# Re-fetch schemas (e.g., if missing or to force a refresh) -npm run fetch-schemas +# Bump "xarfSpec.version" in package.json, then refresh the bundled schemas: +npm run sync-schemas # fetch-schemas + generate-schemas (regenerates src/schemas.generated.ts) ``` -To update to a newer spec version, change the version in `package.json` and run `npm install`. +Consumers never run these scripts — they receive the pre-bundled schemas with the published package. ## Development diff --git a/jest.config.js b/jest.config.js index 820db89..ee356ea 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,14 +6,16 @@ module.exports = { collectCoverageFrom: [ 'src/**/*.ts', '!src/**/*.d.ts', - '!src/index.ts' + '!src/index.ts', + // Generated data module (inlined JSON schemas), not logic to test. + '!src/schemas.generated.ts' ], coverageThreshold: { global: { - branches: 71, - functions: 80, - lines: 77, - statements: 77 + branches: 78, + functions: 90, + lines: 88, + statements: 88 } }, coverageDirectory: 'coverage', diff --git a/package-lock.json b/package-lock.json index 678f742..7d51063 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,16 +7,14 @@ "": { "name": "@xarf/xarf", "version": "1.0.1", - "hasInstallScript": true, "license": "MIT", "dependencies": { - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "tar": "^7.5.2" + "ajv": "^8.20.0", + "ajv-formats": "^3.0.1" }, "devDependencies": { "@types/jest": "^29.5.11", - "@types/node": "^25.0.3", + "@types/node": "^18.19.0", "@typescript-eslint/eslint-plugin": "^8.57.0", "@typescript-eslint/parser": "^8.57.0", "eslint": "^8.56.0", @@ -29,12 +27,14 @@ "jest": "^29.7.0", "lint-staged": "^16.2.7", "prettier": "^3.7.4", + "tar": "^7.5.2", "ts-jest": "^29.4.6", "ts-prune": "^0.10.3", + "tsup": "^8.5.1", "typescript": "^5.3.3" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, "node_modules/@babel/code-frame": { @@ -580,6 +580,448 @@ "node": ">=10" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -780,6 +1222,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.4" @@ -1291,12 +1734,401 @@ "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/pkgr" - } + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@sinclair/typebox": { "version": "0.27.8", @@ -1421,9 +2253,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -1476,13 +2308,13 @@ } }, "node_modules/@types/node": { - "version": "25.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", - "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~5.26.4" } }, "node_modules/@types/parse-json": { @@ -1767,9 +2599,9 @@ "license": "ISC" }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", "bin": { @@ -1790,9 +2622,9 @@ } }, "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -1877,6 +2709,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -2141,6 +2980,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -2151,6 +3006,16 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2219,10 +3084,27 @@ "node": ">=10" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" @@ -2424,6 +3306,23 @@ "dev": true, "license": "MIT" }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2628,6 +3527,48 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3163,9 +4104,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "funding": [ { "type": "github", @@ -3241,6 +4182,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -4378,6 +5331,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4515,6 +5478,19 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -4650,6 +5626,16 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4818,6 +5804,16 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -4958,6 +5954,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -4967,6 +5964,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.1.2" @@ -4988,6 +5986,19 @@ "node": ">=10" } }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4995,6 +6006,18 @@ "dev": true, "license": "MIT" }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nano-spawn": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", @@ -5059,6 +6082,16 @@ "node": ">=8" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-deep-merge": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-2.0.0.tgz", @@ -5255,6 +6288,13 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5367,6 +6407,61 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5503,6 +6598,20 @@ "dev": true, "license": "MIT" }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/refa": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz", @@ -5707,6 +6816,51 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -6013,6 +7167,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6059,6 +7246,7 @@ "version": "7.5.16", "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -6075,6 +7263,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" @@ -6126,6 +7315,36 @@ "dev": true, "license": "MIT" }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -6211,6 +7430,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/true-myth": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/true-myth/-/true-myth-4.1.1.tgz", @@ -6234,6 +7463,13 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/ts-jest": { "version": "29.4.6", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", @@ -6339,6 +7575,79 @@ "node": ">= 6" } }, + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/tsup/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tsup/node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6389,6 +7698,13 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -6404,9 +7720,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index ebf082a..a063fa6 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,27 @@ "repository": "https://github.com/xarf/xarf-spec" }, "main": "dist/index.js", + "module": "dist/index.mjs", "types": "dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "./package.json": "./package.json" + }, "scripts": { - "build": "tsc && npm run copy-schemas", - "copy-schemas": "cp -r schemas dist/", + "build": "tsup", "fetch-schemas": "node scripts/fetch-schemas.js", + "generate-schemas": "node scripts/generate-schemas.js", + "sync-schemas": "npm run fetch-schemas && npm run generate-schemas", "check-schema-updates": "node scripts/check-schema-updates.js", - "postinstall": "npm run fetch-schemas", "test": "jest", "test:watch": "jest --watch", "test:coverage": "jest --coverage", @@ -24,13 +38,9 @@ "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"", "dead-code": "ts-prune --error", "dead-code:check": "ts-prune", - "complexity": "eslint src --ext .ts --no-eslintrc --plugin complexity --rule 'complexity: [error, 10]'", - "complexity:check": "eslint src --ext .ts --no-eslintrc --plugin complexity --rule 'complexity: [warn, 10]'", - "jsdoc": "eslint src --ext .ts --no-eslintrc --plugin jsdoc --parser @typescript-eslint/parser --rule 'jsdoc/require-jsdoc: error'", - "jsdoc:check": "eslint src --ext .ts", "prepublishOnly": "npm run build && npm test", "typecheck": "tsc --noEmit", - "prepare": "husky" + "prepare": "husky || true" }, "keywords": [ "xarf", @@ -63,7 +73,7 @@ }, "devDependencies": { "@types/jest": "^29.5.11", - "@types/node": "^25.0.3", + "@types/node": "^18.19.0", "@typescript-eslint/eslint-plugin": "^8.57.0", "@typescript-eslint/parser": "^8.57.0", "eslint": "^8.56.0", @@ -76,22 +86,26 @@ "jest": "^29.7.0", "lint-staged": "^16.2.7", "prettier": "^3.7.4", + "tar": "^7.5.2", "ts-jest": "^29.4.6", "ts-prune": "^0.10.3", + "tsup": "^8.5.1", "typescript": "^5.3.3" }, "files": [ - "scripts", "dist", "README.md", - "LICENSE" + "LICENSE", + "CHANGELOG.md" ], "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "dependencies": { - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "tar": "^7.5.2" + "ajv": "^8.20.0", + "ajv-formats": "^3.0.1" + }, + "overrides": { + "fast-uri": "^3.1.2" } } diff --git a/scripts/fetch-schemas.js b/scripts/fetch-schemas.js index 63b0f02..7399fda 100644 --- a/scripts/fetch-schemas.js +++ b/scripts/fetch-schemas.js @@ -11,7 +11,6 @@ const https = require('https'); const fs = require('fs'); const path = require('path'); -const zlib = require('zlib'); const tar = require('tar'); // Configuration @@ -19,6 +18,31 @@ const GITHUB_REPO = 'xarf/xarf-spec'; const SCHEMAS_DIR = path.join(__dirname, '..', 'schemas'); const PACKAGE_JSON = path.join(__dirname, '..', 'package.json'); +// Download hardening. This script is a maintainer-only tool (run via +// `npm run sync-schemas` when bumping the spec version) and is NOT executed on +// consumer installs, but we still constrain it defensively. +const ALLOWED_HOSTS = new Set(['github.com', 'codeload.github.com', 'objects.githubusercontent.com']); +const MAX_REDIRECTS = 5; +const MAX_DOWNLOAD_BYTES = 25 * 1024 * 1024; // 25 MB +// Accept only tagged semver-ish versions to avoid building an arbitrary URL. +const VERSION_PATTERN = /^v?\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/; + +/** + * Assert a URL is HTTPS and targets an allowlisted host. + * @param {string} url - URL to validate + * @returns {URL} Parsed URL + */ +function assertSafeUrl(url) { + const parsed = new URL(url); + if (parsed.protocol !== 'https:') { + throw new Error(`Refusing non-HTTPS URL: ${url}`); + } + if (!ALLOWED_HOSTS.has(parsed.hostname)) { + throw new Error(`Refusing download from non-allowlisted host: ${parsed.hostname}`); + } + return parsed; +} + /** * Get the xarf-spec version from package.json * @returns {string} Version string (e.g., "v4.1.0") @@ -34,32 +58,62 @@ function getConfiguredVersion() { ); } + if (!VERSION_PATTERN.test(version)) { + throw new Error( + `Invalid xarfSpec.version "${version}" — expected a tagged version like "v4.2.0".` + ); + } + return version; } /** - * Download a file from a URL, following redirects - * @param {string} url - URL to download + * Download a file over HTTPS from an allowlisted host, following a bounded + * number of redirects and enforcing a maximum response size. + * @param {string} url - HTTPS URL to download + * @param {number} [redirectsLeft] - Remaining redirects allowed * @returns {Promise} Downloaded content */ -function download(url) { +function download(url, redirectsLeft = MAX_REDIRECTS) { return new Promise((resolve, reject) => { - const protocol = url.startsWith('https') ? https : require('http'); + let parsed; + try { + parsed = assertSafeUrl(url); + } catch (error) { + reject(error); + return; + } - const request = protocol.get(url, (response) => { - // Handle redirects + const request = https.get(parsed, (response) => { + // Handle redirects (bounded, and re-validated against the allowlist) if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) { - download(response.headers.location).then(resolve).catch(reject); + response.resume(); // discard body + if (redirectsLeft <= 0) { + reject(new Error(`Too many redirects while downloading ${url}`)); + return; + } + const next = new URL(response.headers.location, parsed).toString(); + download(next, redirectsLeft - 1).then(resolve).catch(reject); return; } if (response.statusCode !== 200) { + response.resume(); reject(new Error(`HTTP ${response.statusCode}: ${url}`)); return; } const chunks = []; - response.on('data', (chunk) => chunks.push(chunk)); + let total = 0; + response.on('data', (chunk) => { + total += chunk.length; + if (total > MAX_DOWNLOAD_BYTES) { + request.destroy(); + reject(new Error(`Download exceeded ${MAX_DOWNLOAD_BYTES} bytes: ${url}`)); + return; + } + chunks.push(chunk); + }); response.on('end', () => resolve(Buffer.concat(chunks))); response.on('error', reject); }); diff --git a/scripts/generate-schemas.js b/scripts/generate-schemas.js new file mode 100644 index 0000000..ae2209c --- /dev/null +++ b/scripts/generate-schemas.js @@ -0,0 +1,141 @@ +#!/usr/bin/env node +/** + * Generate a bundled, self-contained TypeScript module from the on-disk XARF + * schemas. + * + * The library validates against the official xarf-spec JSON schemas. Rather + * than read those files from disk at runtime (which is fragile in bundled, + * serverless, and edge environments where `fs`/`__dirname` are unreliable, and + * which previously required a network fetch on install), we inline the schemas + * into `src/schemas.generated.ts` at maintenance time. The generated module is + * committed to the repository and imported directly, so the published package + * carries the schemas with zero filesystem or network dependency. + * + * Run this whenever the configured `xarfSpec.version` changes, after + * `npm run fetch-schemas` has refreshed the local `schemas/` directory: + * + * npm run sync-schemas # fetch-schemas && generate-schemas + */ + +const fs = require('fs'); +const path = require('path'); + +const SCHEMAS_DIR = path.join(__dirname, '..', 'schemas'); +const TYPES_DIR = path.join(SCHEMAS_DIR, 'types'); +const OUTPUT_FILE = path.join(__dirname, '..', 'src', 'schemas.generated.ts'); +const VERSION_FILE = path.join(SCHEMAS_DIR, '.version'); + +/** + * Read and parse a JSON schema file. + * @param {string} filePath - Absolute path to the schema file + * @returns {object} Parsed schema + */ +function readJson(filePath) { + return JSON.parse(fs.readFileSync(filePath, 'utf-8')); +} + +/** + * Collect every schema file keyed by its relative path within the schemas dir. + * Keys match the relative `$ref` paths used inside the schemas, e.g. + * "xarf-core.json" and "types/messaging-spam.json". + * @returns {Record} Map of relative path to parsed schema + */ +function collectSchemas() { + const schemas = {}; + + const rootFiles = fs + .readdirSync(SCHEMAS_DIR) + .filter((f) => f.endsWith('.json')) + .sort(); + for (const file of rootFiles) { + schemas[file] = readJson(path.join(SCHEMAS_DIR, file)); + } + + if (fs.existsSync(TYPES_DIR)) { + const typeFiles = fs + .readdirSync(TYPES_DIR) + .filter((f) => f.endsWith('.json')) + .sort(); + for (const file of typeFiles) { + schemas[`types/${file}`] = readJson(path.join(TYPES_DIR, file)); + } + } + + return schemas; +} + +/** + * Read the fetched spec version from schemas/.version, if present. + * @returns {string} Version string, or "unknown" + */ +function readSpecVersion() { + try { + return JSON.parse(fs.readFileSync(VERSION_FILE, 'utf-8')).version || 'unknown'; + } catch { + return 'unknown'; + } +} + +/** + * Generate the TypeScript source for the bundled schemas module. + * @param {Record} schemas - Map of relative path to schema + * @param {string} specVersion - xarf-spec version the schemas were taken from + * @returns {string} TypeScript source + */ +function renderModule(schemas, specVersion) { + const entries = Object.keys(schemas) + .sort() + .map((key) => ` ${JSON.stringify(key)}: ${JSON.stringify(schemas[key])},`) + .join('\n'); + + return `/* eslint-disable */ +/** + * AUTO-GENERATED FILE — DO NOT EDIT BY HAND. + * + * Generated by scripts/generate-schemas.js from the official xarf-spec schemas. + * To refresh: \`npm run sync-schemas\` (fetches schemas, then regenerates this file). + * + * Source: xarf-spec ${specVersion} + */ + +/** + * Every XARF JSON schema, keyed by its relative path (matching the relative + * \`$ref\` paths used inside the schemas), e.g. "xarf-core.json" and + * "types/messaging-spam.json". + */ +export const bundledSchemas: Readonly>> = Object.freeze({ +${entries} +}); + +/** The xarf-spec version these bundled schemas were generated from. */ +export const BUNDLED_SPEC_VERSION = ${JSON.stringify(specVersion)}; +`; +} + +/** + * Main entry point. + */ +function main() { + if (!fs.existsSync(path.join(SCHEMAS_DIR, 'xarf-core.json'))) { + console.error( + `[xarf] No schemas found at ${SCHEMAS_DIR}. Run "npm run fetch-schemas" first.` + ); + process.exit(1); + } + + const schemas = collectSchemas(); + const specVersion = readSpecVersion(); + const source = renderModule(schemas, specVersion); + + fs.writeFileSync(OUTPUT_FILE, source); + console.log( + `[xarf] Generated ${path.relative(process.cwd(), OUTPUT_FILE)} ` + + `with ${Object.keys(schemas).length} schemas (xarf-spec ${specVersion}).` + ); +} + +if (require.main === module) { + main(); +} + +module.exports = { collectSchemas, renderModule }; diff --git a/src/index.ts b/src/index.ts index c5a7b63..739ce99 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,8 @@ * (eXtended Abuse Reporting Format) reports. */ -export { SPEC_VERSION } from './version'; +export { SPEC_VERSION, VERSION } from './version'; +export { BUNDLED_SPEC_VERSION } from './schemas.generated'; export { parse, type ParseOptions, type ParseResult } from './parser'; export { createReport, @@ -124,5 +125,3 @@ export { type XARFv3ReporterInfo, type XARFv3Attachment, } from './v3-legacy'; - -export const VERSION = '1.0.0'; diff --git a/src/parser.ts b/src/parser.ts index 6a0604a..db59e48 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -22,6 +22,13 @@ import { isXARFv3, convertV3toV4, getV3DeprecationWarning, type XARFv3Report } f export interface ParseOptions { strict?: boolean; showMissingOptional?: boolean; + /** + * Maximum size, in bytes, of a string `jsonData` input. When set, inputs + * larger than this are rejected with an `XARFParseError` before `JSON.parse` + * runs. Use this to bound untrusted input (abuse reports are adversarial by + * nature and may carry large base64 evidence payloads). Defaults to no limit. + */ + maxInputBytes?: number; } /** @@ -36,17 +43,42 @@ export interface ParseResult { const validator = new XARFValidator(); +/** + * Compute the UTF-8 byte length of a string, working in both Node and + * Buffer-less (e.g. edge) runtimes. + * @param value - The string to measure + * @returns Byte length in UTF-8 + */ +function utf8ByteLength(value: string): number { + if (typeof Buffer !== 'undefined') { + return Buffer.byteLength(value, 'utf8'); + } + return new TextEncoder().encode(value).length; +} + /** * Parse JSON data into object - * @param jsonData - * @throws {XARFParseError} If JSON parsing fails + * @param jsonData - JSON string or already-parsed object + * @param maxInputBytes - Optional maximum byte size for string input + * @throws {XARFParseError} If JSON parsing fails or input exceeds maxInputBytes */ -function parseJSON(jsonData: string | Record): Record { - try { - if (typeof jsonData === 'string') { - return JSON.parse(jsonData) as Record; - } +function parseJSON( + jsonData: string | Record, + maxInputBytes?: number +): Record { + if (typeof jsonData !== 'string') { return jsonData; + } + + if (maxInputBytes !== undefined) { + const bytes = utf8ByteLength(jsonData); + if (bytes > maxInputBytes) { + throw new XARFParseError(`Input exceeds maxInputBytes (${bytes} > ${maxInputBytes} bytes)`); + } + } + + try { + return JSON.parse(jsonData) as Record; } catch (error) { throw new XARFParseError( `Invalid JSON: ${error instanceof Error ? error.message : String(error)}` @@ -121,7 +153,7 @@ export function parse( const errors: string[] = []; const warnings: string[] = []; - let data = parseJSON(jsonData); + let data = parseJSON(jsonData, options?.maxInputBytes); data = handleV3Conversion(data, warnings); const result = validator.validate(data as XARFReport, strict, showMissingOptional); diff --git a/src/schema-registry.ts b/src/schema-registry.ts index c5ccc9a..227f01f 100644 --- a/src/schema-registry.ts +++ b/src/schema-registry.ts @@ -5,10 +5,13 @@ * eliminating hardcoded validation lists throughout the codebase. */ -import * as fs from 'fs'; -import * as path from 'path'; import type { XARFCategory } from './types'; -import { findSchemasDir, loadSchemaFile } from './schema-utils'; +import { + getCoreSchema, + listTypeSchemaPaths, + getBundledSchema, + resolveBaseSchemaRef, +} from './schema-utils'; /** * Schema property definition @@ -64,7 +67,6 @@ export interface FieldMetadata { */ export class SchemaRegistry { private static instance: SchemaRegistry | null = null; - private schemasDir: string; private coreSchema: SchemaDefinition | null = null; private typeSchemas: Map = new Map(); @@ -78,7 +80,6 @@ export class SchemaRegistry { * Private constructor - use getInstance() instead */ private constructor() { - this.schemasDir = findSchemasDir(); this.loadCoreSchema(); this.scanTypeSchemas(); } @@ -105,38 +106,28 @@ export class SchemaRegistry { * Load the core schema */ private loadCoreSchema(): void { - this.coreSchema = loadSchemaFile( - path.join(this.schemasDir, 'xarf-core.json') - ); + this.coreSchema = getCoreSchema() as SchemaDefinition | null; } /** - * Scan type schemas directory and build category->types map + * Scan bundled type schemas and build the category->types map */ private scanTypeSchemas(): void { - const typesDir = path.join(this.schemasDir, 'types'); - if (!fs.existsSync(typesDir)) { - return; - } - - try { - const files = fs.readdirSync(typesDir); - for (const file of files) { - if (file.endsWith('.json') && file !== 'content-base.json') { - // Parse filename: {category}-{type}.json - const match = file.match(/^([^-]+)-(.+)\.json$/); - if (match) { - const schemaPath = path.join(typesDir, file); - const schema = loadSchemaFile(schemaPath); - if (schema) { - const normalizedType = match[2].replace(/-/g, '_'); - this.typeSchemas.set(`${match[1]}/${normalizedType}`, schema); - } - } - } + for (const relativePath of listTypeSchemaPaths()) { + const file = relativePath.slice('types/'.length); + if (file === 'content-base.json') { + continue; + } + // Parse filename: {category}-{type}.json + const match = file.match(/^([^-]+)-(.+)\.json$/); + if (!match) { + continue; + } + const schema = getBundledSchema(relativePath) as SchemaDefinition | null; + if (schema) { + const normalizedType = match[2].replace(/-/g, '_'); + this.typeSchemas.set(`${match[1]}/${normalizedType}`, schema); } - } catch { - // Directory read failed, continue with empty type schemas } } @@ -414,10 +405,7 @@ export class SchemaRegistry { * @returns Schema definition or null */ private loadBaseSchema(ref: string): SchemaDefinition | null { - // Extract filename from ref - const filename = ref.replace(/^\.\//, '').replace(/^\.\.\//, ''); - const schemaPath = path.join(this.schemasDir, 'types', filename); - return loadSchemaFile(schemaPath); + return resolveBaseSchemaRef(ref) as SchemaDefinition | null; } /** diff --git a/src/schema-utils.ts b/src/schema-utils.ts index 0c0f0c5..8e323e5 100644 --- a/src/schema-utils.ts +++ b/src/schema-utils.ts @@ -1,47 +1,65 @@ /** - * Shared schema utilities + * Shared schema utilities. * - * Provides common schema loading and discovery functions used by - * SchemaRegistry, SchemaValidator, and XARFValidator. + * The XARF JSON schemas are bundled into the package at build time (see + * `schemas.generated.ts`, produced by `scripts/generate-schemas.js`). Reading + * them from this in-memory bundle — rather than from disk — keeps the library + * working in bundled, serverless, and edge environments where `fs`/`__dirname` + * are unreliable, and removes the install-time network fetch entirely. */ -import * as fs from 'fs'; -import * as path from 'path'; +import { bundledSchemas } from './schemas.generated'; /** - * Find the schemas directory by searching common locations - * @returns Path to schemas directory + * A parsed JSON Schema object. */ -export function findSchemasDir(): string { - const possiblePaths = [ - path.join(__dirname, 'schemas'), - path.join(__dirname, '..', 'schemas'), - path.join(__dirname, '..', '..', 'schemas'), - path.join(process.cwd(), 'schemas'), - ]; +export type SchemaObject = Record; - for (const p of possiblePaths) { - if (fs.existsSync(p) && fs.existsSync(path.join(p, 'xarf-core.json'))) { - return p; - } - } +/** + * Get a bundled schema by its relative path. + * @param relativePath - Relative path used as the bundle key, e.g. + * "xarf-core.json" or "types/messaging-spam.json" + * @returns The schema object, or null if not bundled + */ +export function getBundledSchema(relativePath: string): SchemaObject | null { + const schema = bundledSchemas[relativePath]; + return schema ? (schema as SchemaObject) : null; +} + +/** + * Get the core XARF schema. + * @returns The core schema, or null if not bundled + */ +export function getCoreSchema(): SchemaObject | null { + return getBundledSchema('xarf-core.json'); +} - return possiblePaths[0]; +/** + * Get the master XARF schema (all category+type combinations). + * @returns The master schema, or null if not bundled + */ +export function getMasterSchema(): SchemaObject | null { + return getBundledSchema('xarf-v4-master.json'); } /** - * Load and parse a JSON schema file - * @param schemaPath - Path to schema file - * @returns Parsed schema object or null if not found/invalid + * List the relative paths of all type-specific schemas. + * @returns Array of relative paths, e.g. ["types/messaging-spam.json", ...] + */ +export function listTypeSchemaPaths(): string[] { + return Object.keys(bundledSchemas).filter((key) => key.startsWith('types/')); +} + +/** + * Resolve a relative `$ref` to a base schema (e.g. "./content-base.json" or + * "../content-base.json") to its bundled schema. + * + * Base schemas live under `types/`, so the leading relative segments are + * stripped and the filename is looked up there. + * @param ref - The `$ref` string from a schema + * @returns The referenced base schema, or null if not found */ -export function loadSchemaFile>(schemaPath: string): T | null { - try { - if (!fs.existsSync(schemaPath)) { - return null; - } - const content = fs.readFileSync(schemaPath, 'utf-8'); - return JSON.parse(content) as T; - } catch { - return null; - } +export function resolveBaseSchemaRef(ref: string): SchemaObject | null { + const filename = ref.replace(/^(\.\.?\/)+/, ''); + return getBundledSchema(`types/${filename}`); } diff --git a/src/schema-validator.ts b/src/schema-validator.ts index 57d0e78..030fce2 100644 --- a/src/schema-validator.ts +++ b/src/schema-validator.ts @@ -1,16 +1,27 @@ /** * XARF Schema Validator - * Production-ready validation using AJV with JSON Schema support + * Production-ready validation using AJV with JSON Schema support. + * + * Schemas are read from the in-memory bundle (see `schema-utils.ts`), so no + * filesystem or network access is required at runtime. */ import Ajv from 'ajv'; import addFormats from 'ajv-formats'; import type { XARFReport } from './types'; -import * as fs from 'fs'; -import * as path from 'path'; -import { findSchemasDir } from './schema-utils'; +import { + getCoreSchema, + getMasterSchema, + listTypeSchemaPaths, + getBundledSchema, + type SchemaObject, +} from './schema-utils'; import { schemaRegistry } from './schema-registry'; +const SCHEMA_BASE_URL = 'https://xarf.org/schemas/v4'; +const MASTER_SCHEMA_ID = `${SCHEMA_BASE_URL}/xarf-v4-master.json`; +const CORE_SCHEMA_ID = `${SCHEMA_BASE_URL}/xarf-core.json`; + /** * Validation result containing status and error details */ @@ -19,34 +30,24 @@ export interface ValidationResult { errors: string[]; } -/** - * Schema validation error with detailed context - */ -export interface ValidationError { - field?: string; - message: string; - value?: unknown; -} - /** * SchemaValidator class for validating XARF reports against JSON schemas * * Features: - * - Validates against xarf-core.json base schema - * - Applies type-specific validation based on category+type - * - Proper $ref resolution for nested schemas - * - Comprehensive error handling and reporting - * - Singleton pattern for easy reuse + * - Validates against the bundled xarf-v4-master schema (core + type-specific) + * - Registers every schema under its canonical `$id` so AJV resolves all `$ref`s + * - Optional strict mode that promotes `x-recommended` fields to required + * - Comprehensive error formatting + * - Singleton instance for easy reuse */ export class SchemaValidator { private ajv: Ajv; private strictAjv: Ajv; - private coreSchemaLoaded = false; private masterSchemaLoaded = false; - private schemasDir: string; /** * Create a configured AJV instance + * @returns A configured AJV instance with formats registered */ private static createAjvInstance(): Ajv { const ajv = new Ajv({ @@ -68,10 +69,6 @@ export class SchemaValidator { constructor() { this.ajv = SchemaValidator.createAjvInstance(); this.strictAjv = SchemaValidator.createAjvInstance(); - - // Determine schemas directory path - // Schemas are fetched from xarf-spec to project_root/schemas/ and copied to dist/schemas/ on build - this.schemasDir = findSchemasDir(); } /** @@ -89,355 +86,141 @@ export class SchemaValidator { /** * Recursively walk a schema node and add x-recommended properties to required arrays. * Mutates the node in place. - * @param node + * @param node - The schema node to walk */ private promoteRecommendedToRequired(node: unknown): void { - if (typeof node !== 'object' || node === null) return; + if (typeof node !== 'object' || node === null) { + return; + } if (Array.isArray(node)) { - for (const item of node) { - this.promoteRecommendedToRequired(item); - } + node.forEach((item) => this.promoteRecommendedToRequired(item)); return; } const obj = node as Record; - - // Promote x-recommended properties to required - if (obj.properties && typeof obj.properties === 'object' && !Array.isArray(obj.properties)) { - const properties = obj.properties as Record>; - const required = new Set( - Array.isArray(obj.required) ? (obj.required as string[]) : [] - ); - - for (const [propName, propDef] of Object.entries(properties)) { - if ( - propDef && - typeof propDef === 'object' && - !Array.isArray(propDef) && - propDef['x-recommended'] === true - ) { - required.add(propName); - } - } - - obj.required = Array.from(required); - } - - // Recurse into schema-relevant sub-structures only - const schemaKeys = [ - 'properties', - '$defs', - 'allOf', - 'anyOf', - 'oneOf', - 'items', - 'if', - 'then', - 'else', - 'not', - 'additionalProperties', - ]; - for (const key of schemaKeys) { - if (!obj[key] || typeof obj[key] !== 'object') continue; - - if (key === 'properties' || key === '$defs') { - // These are dictionaries — recurse into each value - for (const value of Object.values(obj[key] as Record)) { - this.promoteRecommendedToRequired(value); - } - } else { - this.promoteRecommendedToRequired(obj[key]); - } - } - } - - /** - * Load a schema file from the schemas directory - * Helper method to load schemas synchronously - * @param relativePath - Relative path to schema file within schemas directory - * @returns Parsed schema object - */ - private loadSchemaFile(relativePath: string): object { - const schemaPath = path.join(this.schemasDir, relativePath); - - if (!fs.existsSync(schemaPath)) { - throw new Error(`Schema file not found: ${schemaPath}`); - } - - const schemaContent = fs.readFileSync(schemaPath, 'utf-8'); - return JSON.parse(schemaContent); - } - - /** - * Recursively load all referenced schemas from a base schema - * This manually handles $ref resolution for nested schemas - * @param schema - Schema object to scan for $ref references - * @param basePath - Base path for resolving relative schema references - */ - private loadReferencedSchemas(schema: unknown, basePath: string = ''): void { - const schemaObj = schema as Record; - - // Process $ref if present - if (schemaObj.$ref && typeof schemaObj.$ref === 'string') { - this.processSchemaRef(schemaObj.$ref, basePath); - } - - // Recursively process nested structures - this.processNestedSchemas(schemaObj, basePath); + this.promoteNodeProperties(obj); + this.recurseIntoSubSchemas(obj); } /** - * Process a schema $ref and load it if needed - * @param ref - Schema reference string - * @param basePath - Base path for resolving relative references + * Add this node's `x-recommended` properties to its `required` array. + * @param obj - The schema node to mutate */ - private processSchemaRef(ref: string, basePath: string): void { - // Skip meta-schemas and anchor references - if (this.shouldSkipRef(ref)) { + private promoteNodeProperties(obj: Record): void { + const properties = obj.properties; + if (typeof properties !== 'object' || properties === null || Array.isArray(properties)) { return; } - const relativePath = this.normalizeRelativePath(ref, basePath); - const schemaId = this.buildSchemaId(relativePath); - - // Load and add schema if not already loaded - if (!this.ajv.getSchema(schemaId)) { - this.loadAndAddSchema(relativePath); - } - } - - /** - * Check if a schema reference should be skipped - * @param ref - Schema reference string - * @returns True if ref should be skipped (handled by AJV internally) - */ - private shouldSkipRef(ref: string): boolean { - return ref.includes('json-schema.org') || ref.startsWith('#'); - } - - /** - * Normalize a relative path based on context - * @param ref - Schema reference string - * @param basePath - Base path for resolving relative references - * @returns Normalized relative path - */ - private normalizeRelativePath(ref: string, basePath: string): string { - let relativePath = ref; - - // Remove leading "./" for same-directory references - if (relativePath.startsWith('./')) { - relativePath = relativePath.substring(2); - - // If we have a basePath (e.g., we're in "types/content-phishing.json"), - // prepend the directory from basePath - if (basePath) { - const baseDir = path.dirname(basePath); - if (baseDir && baseDir !== '.') { - relativePath = `${baseDir}/${relativePath}`; - } - } - } + const required = new Set(Array.isArray(obj.required) ? (obj.required as string[]) : []); - // If it's a full URL, extract the relative path - if (ref.includes('schemas/v4/')) { - const match = ref.match(/schemas\/v4\/(.+\.json)/); - if (match) { - relativePath = match[1]; + for (const [propName, propDef] of Object.entries(properties as Record)) { + if ( + propDef && + typeof propDef === 'object' && + !Array.isArray(propDef) && + (propDef as Record)['x-recommended'] === true + ) { + required.add(propName); } } - return relativePath; - } - - /** - * Build schema ID from relative path - * @param relativePath - Relative path to schema file - * @returns Full schema ID URL - */ - private buildSchemaId(relativePath: string): string { - return relativePath.startsWith('http') - ? relativePath - : `https://xarf.org/schemas/v4/${relativePath}`; + obj.required = Array.from(required); } /** - * Load and add a schema file - * @param relativePath - Relative path to schema file (also used as basePath for nested schemas) + * Recurse into the schema-relevant sub-structures of a node. + * @param obj - The schema node whose children should be walked */ - private loadAndAddSchema(relativePath: string): void { - try { - const referencedSchema = this.loadSchemaFile(relativePath); - this.ajv.addSchema(referencedSchema); - this.strictAjv.addSchema( - this.transformSchemaForStrict(referencedSchema) as Record - ); - - // Recursively load any schemas referenced by this schema - this.loadReferencedSchemas(referencedSchema, relativePath); - } catch (error) { - // Ignore errors for already-loaded or missing schemas - // Explicitly acknowledge error to satisfy linter - void error; - } - } + private recurseIntoSubSchemas(obj: Record): void { + const dictionaryKeys = ['properties', '$defs']; + const nestedKeys = [ + 'allOf', + 'anyOf', + 'oneOf', + 'items', + 'if', + 'then', + 'else', + 'not', + 'additionalProperties', + ]; - /** - * Recursively process nested schemas (objects and arrays) - * @param schemaObj - Schema object to process - * @param basePath - Base path for resolving relative references - */ - private processNestedSchemas(schemaObj: Record, basePath: string): void { - // Recursively check all object properties - if (typeof schemaObj === 'object' && schemaObj !== null) { - for (const key in schemaObj) { - if (typeof schemaObj[key] === 'object') { - this.loadReferencedSchemas(schemaObj[key], basePath); + for (const key of dictionaryKeys) { + const value = obj[key]; + if (value && typeof value === 'object') { + for (const child of Object.values(value as Record)) { + this.promoteRecommendedToRequired(child); } } } - // Check array items - if (Array.isArray(schemaObj)) { - schemaObj.forEach((item) => this.loadReferencedSchemas(item, basePath)); - } - } - - /** - * Load and compile the core XARF schema - * This must be called before validation can occur - */ - private loadCoreSchema(): void { - if (this.coreSchemaLoaded) { - return; - } - - try { - const coreSchemaPath = path.join(this.schemasDir, 'xarf-core.json'); - if (!fs.existsSync(coreSchemaPath)) { - throw new Error(`Core schema not found at: ${coreSchemaPath}`); + for (const key of nestedKeys) { + const value = obj[key]; + if (value && typeof value === 'object') { + this.promoteRecommendedToRequired(value); } - - const coreSchema = JSON.parse(fs.readFileSync(coreSchemaPath, 'utf-8')) as Record< - string, - unknown - >; - const strictCoreSchema = this.transformSchemaForStrict(coreSchema) as Record; - - // Register core schema under BOTH the relative path and full URL - // Master schema uses relative path "xarf-core.json" - const relativePath = 'xarf-core.json'; - if (!this.ajv.getSchema(relativePath)) { - this.ajv.addSchema({ ...coreSchema, $id: relativePath }); - this.strictAjv.addSchema({ ...strictCoreSchema, $id: relativePath }); - } - - // Also register under full URL for completeness - const fullUrl = 'https://xarf.org/schemas/v4/xarf-core.json'; - if (!this.ajv.getSchema(fullUrl)) { - this.ajv.addSchema({ ...coreSchema, $id: fullUrl }); - this.strictAjv.addSchema({ ...strictCoreSchema, $id: fullUrl }); - } - - this.coreSchemaLoaded = true; - } catch (error) { - throw new Error( - `Failed to load core schema: ${error instanceof Error ? error.message : String(error)}` - ); } } /** - * Pre-load all type-specific schemas into AJV - * This ensures all $refs can be resolved during compilation + * Register a schema under the given `$id` in both the lenient and strict AJV + * instances, unless it is already registered. + * @param schema - The schema object to register + * @param id - The canonical `$id` to register it under */ - private preloadAllTypeSchemas(): void { - const typesDir = path.join(this.schemasDir, 'types'); - - if (!fs.existsSync(typesDir)) { - return; + private addSchemaUnderId(schema: SchemaObject, id: string): void { + if (!this.ajv.getSchema(id)) { + this.ajv.addSchema({ ...schema, $id: id }); } - - const files = fs.readdirSync(typesDir); - - for (const file of files) { - if (file.endsWith('.json')) { - try { - const schemaPath = path.join(typesDir, file); - const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf-8')) as Record< - string, - unknown - >; - - // Build the FULL URL that AJV will resolve - // Master schema id is https://xarf.org/schemas/v4/xarf-v4-master.json - // When it references "types/messaging-spam.json", AJV resolves to full URL - const relativePath = `types/${file}`; - const fullUrl = `https://xarf.org/schemas/v4/${relativePath}`; - - // Add schema under the FULL URL (what AJV resolves to) - if (!this.ajv.getSchema(fullUrl)) { - this.ajv.addSchema({ ...schema, $id: fullUrl }); - const strictSchema = this.transformSchemaForStrict(schema) as Record; - this.strictAjv.addSchema({ ...strictSchema, $id: fullUrl }); - } - } catch (error) { - // Ignore errors loading individual schemas - // Explicitly acknowledge error to satisfy linter - void error; - } - } + if (!this.strictAjv.getSchema(id)) { + const strict = this.transformSchemaForStrict(schema) as SchemaObject; + this.strictAjv.addSchema({ ...strict, $id: id }); } } /** - * Load and compile the master XARF schema with type-specific validation - * This includes all category+type combinations - * - * Note: Some type-specific schemas may be missing from the master schema. - * In that case, we create a filtered version that only includes existing schemas. + * Load and compile the master XARF schema together with the core and all + * type-specific schemas, so AJV can resolve every `$ref`. Idempotent. */ private loadMasterSchema(): void { if (this.masterSchemaLoaded) { return; } - try { - // First ensure core schema is loaded - this.loadCoreSchema(); - - // Pre-load all type-specific schemas - this.preloadAllTypeSchemas(); + const coreSchema = getCoreSchema(); + if (!coreSchema) { + throw new Error('Core schema (xarf-core.json) is missing from the bundle'); + } + const masterSchema = getMasterSchema(); + if (!masterSchema) { + throw new Error('Master schema (xarf-v4-master.json) is missing from the bundle'); + } - // Load the master schema - const masterSchema = this.loadSchemaFile('xarf-v4-master.json') as Record; + // Register core under both its full URL and the relative id the master uses. + this.addSchemaUnderId(coreSchema, CORE_SCHEMA_ID); + this.addSchemaUnderId(coreSchema, 'xarf-core.json'); - // Add the master schema to both AJV instances - const masterSchemaId = 'https://xarf.org/schemas/v4/xarf-v4-master.json'; - if (!this.ajv.getSchema(masterSchemaId)) { - this.ajv.addSchema(masterSchema); - } - if (!this.strictAjv.getSchema(masterSchemaId)) { - const strictMasterSchema = this.transformSchemaForStrict(masterSchema) as Record< - string, - unknown - >; - this.strictAjv.addSchema(strictMasterSchema); + // Register every type schema under its canonical full URL. + for (const relativePath of listTypeSchemaPaths()) { + const schema = getBundledSchema(relativePath); + if (schema) { + this.addSchemaUnderId(schema, `${SCHEMA_BASE_URL}/${relativePath}`); } - - this.masterSchemaLoaded = true; - } catch (error) { - throw new Error( - `Failed to load master schema: ${error instanceof Error ? error.message : String(error)}` - ); } + + // Finally register the master schema itself. + this.addSchemaUnderId(masterSchema, MASTER_SCHEMA_ID); + + this.masterSchemaLoaded = true; } /** * Validate a XARF report against the appropriate schema * Validates against both the core schema and the type-specific schema * @param report - The XARF report to validate - * @param strict + * @param strict - When true, `x-recommended` fields are treated as required * @returns ValidationResult with status and any error messages * @example * ```typescript @@ -454,8 +237,7 @@ export class SchemaValidator { this.loadMasterSchema(); const ajvInstance = strict ? this.strictAjv : this.ajv; - const masterSchemaId = 'https://xarf.org/schemas/v4/xarf-v4-master.json'; - const masterValidate = ajvInstance.getSchema(masterSchemaId); + const masterValidate = ajvInstance.getSchema(MASTER_SCHEMA_ID); if (!masterValidate) { return { valid: false, errors: ['Master schema not found after loading'] }; @@ -597,7 +379,7 @@ export class SchemaValidator { * ```typescript * import { validator } from './schema-validator'; * - * const result = await validator.validate(report); + * const result = validator.validate(report); * if (!result.valid) { * console.error(result.errors); * } diff --git a/src/schemas.generated.ts b/src/schemas.generated.ts new file mode 100644 index 0000000..f9dd482 --- /dev/null +++ b/src/schemas.generated.ts @@ -0,0 +1,55 @@ +/* eslint-disable */ +/** + * AUTO-GENERATED FILE — DO NOT EDIT BY HAND. + * + * Generated by scripts/generate-schemas.js from the official xarf-spec schemas. + * To refresh: `npm run sync-schemas` (fetches schemas, then regenerates this file). + * + * Source: xarf-spec v4.2.0 + */ + +/** + * Every XARF JSON schema, keyed by its relative path (matching the relative + * `$ref` paths used inside the schemas), e.g. "xarf-core.json" and + * "types/messaging-spam.json". + */ +export const bundledSchemas: Readonly>> = Object.freeze({ + "types/connection-ddos.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/connection-ddos.json","title":"XARF v4 Connection - DDoS Type Schema","description":"Schema for Distributed Denial of Service attack reports including volumetric attacks (SYN floods, UDP floods, HTTP floods) and amplification/reflection attacks (DNS, NTP, memcached, SSDP)","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"connection"},"type":{"const":"ddos"},"evidence_source":{"type":"string","enum":["firewall_logs","ids_detection","flow_analysis","traffic_monitoring","honeypot"],"x-recommended":true,"description":"RECOMMENDED: Source of DDoS attack evidence"},"destination_ip":{"type":"string","anyOf":[{"format":"ipv4"},{"format":"ipv6"}],"x-recommended":true,"description":"RECOMMENDED: Target IP address of the DDoS attack"},"destination_port":{"type":"integer","minimum":1,"maximum":65535,"x-recommended":true,"description":"RECOMMENDED: Target port number","examples":[80,443,53,25]},"protocol":{"type":"string","enum":["tcp","udp","icmp","sctp"],"description":"REQUIRED: Network protocol used in the attack"},"attack_vector":{"type":"string","x-recommended":true,"description":"RECOMMENDED: Specific DDoS attack method","examples":["syn_flood","udp_flood","icmp_flood","http_flood","dns_amplification","ntp_amplification","memcached_amplification"]},"peak_pps":{"type":"integer","minimum":1,"x-recommended":true,"description":"RECOMMENDED: Peak packets per second during attack"},"peak_bps":{"type":"integer","minimum":1,"x-recommended":true,"description":"RECOMMENDED: Peak bits per second during attack"},"duration_seconds":{"type":"integer","minimum":1,"description":"OPTIONAL: Duration of the DDoS attack in seconds"},"amplification_factor":{"type":"number","minimum":1,"description":"OPTIONAL: Amplification factor for reflection attacks"},"first_seen":{"type":"string","format":"date-time","description":"REQUIRED: When DDoS attack was first observed"},"last_seen":{"type":"string","format":"date-time","description":"OPTIONAL: When DDoS attack was last observed"},"threshold_exceeded":{"type":"string","format":"date-time","description":"OPTIONAL: When detection threshold was exceeded"},"mitigation_applied":{"type":"boolean","description":"OPTIONAL: Whether mitigation measures were applied"},"service_impact":{"type":"string","enum":["none","degraded","unavailable"],"description":"OPTIONAL: Impact on target service availability"}},"required":["protocol","first_seen"],"if":{"properties":{"source_identifier":{"anyOf":[{"format":"ipv4"},{"format":"ipv6"}]}}},"then":{"required":["source_port"]}}],"examples":[{"xarf_version":"4.2.0","report_id":"789a0123-b456-78c9-d012-345678901234","timestamp":"2024-01-15T16:55:42Z","reporter":{"org":"DDoS Protection Service","contact":"ddos@ddos-monitor.example","domain":"ddos-monitor.example"},"sender":{"org":"DDoS Protection Service","contact":"ddos@ddos-monitor.example","domain":"ddos-monitor.example"},"source_identifier":"192.0.2.155","category":"connection","type":"ddos","destination_ip":"203.0.113.100","destination_port":80,"protocol":"tcp","attack_vector":"syn_flood","peak_pps":250000,"peak_bps":1200000000,"duration_seconds":2700,"evidence_source":"flow_analysis","service_impact":"degraded","tags":["attack:syn_flood","volume:high"]}]}, + "types/connection-infected-host.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/connection-infected-host.json","title":"XARF v4 Connection - Infected Host Type Schema","description":"Schema for compromised systems participating in botnets or being remotely controlled for malicious activities (DDoS, spam distribution, click fraud, cryptocurrency mining, credential stuffing)","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"connection"},"type":{"const":"infected_host"},"destination_ip":{"type":"string","anyOf":[{"format":"ipv4"},{"format":"ipv6"}],"x-recommended":true,"description":"RECOMMENDED: Target IP address"},"destination_port":{"type":"integer","minimum":1,"maximum":65535,"x-recommended":true,"description":"RECOMMENDED: Target port number"},"protocol":{"type":"string","enum":["tcp","udp"],"default":"tcp","description":"REQUIRED: Network protocol used"},"bot_type":{"type":"string","enum":["search_engine","ai_agent","monitoring","seo_analyzer","link_checker","feed_reader","social_media","advertising","malicious","unknown"],"description":"REQUIRED: Classification of bot type"},"bot_name":{"type":"string","x-recommended":true,"description":"RECOMMENDED: Identified bot name or signature","examples":["Googlebot","GPTBot","ChatGPT-User","Claude-Web","FacebookBot","TwitterBot","UptimeRobot","PingdomBot"]},"user_agent":{"type":"string","x-recommended":true,"description":"RECOMMENDED: Full User-Agent string"},"behavior_pattern":{"type":"string","enum":["legitimate_crawling","aggressive_crawling","api_abuse","form_submission","comment_spam","account_creation","content_harvesting","vulnerability_probing","mixed"],"x-recommended":true,"description":"RECOMMENDED: Observed behavior pattern"},"request_rate":{"type":"number","description":"OPTIONAL: Average requests per second"},"total_requests":{"type":"integer","minimum":1,"description":"OPTIONAL: Total number of requests"},"respects_robots_txt":{"type":"boolean","description":"OPTIONAL: Whether bot respects robots.txt directives"},"follows_crawl_delay":{"type":"boolean","description":"OPTIONAL: Whether bot follows crawl-delay directive"},"javascript_execution":{"type":"boolean","description":"OPTIONAL: Whether bot executes JavaScript"},"accepts_cookies":{"type":"boolean","description":"OPTIONAL: Whether bot accepts and maintains cookies"},"api_endpoints_accessed":{"type":"array","items":{"type":"string"},"description":"OPTIONAL: List of API endpoints accessed"},"verification_status":{"type":"string","enum":["verified","unverified","spoofed","unknown"],"x-recommended":true,"description":"RECOMMENDED: Whether bot identity has been verified"},"first_seen":{"type":"string","format":"date-time","description":"REQUIRED: When bot activity was first observed"},"last_seen":{"type":"string","format":"date-time","description":"OPTIONAL: When bot activity was last observed"}},"required":["protocol","bot_type","first_seen"]}],"examples":[{"xarf_version":"4.2.0","report_id":"d5e6f7a8-b9c0-1234-defa-123456789012","timestamp":"2025-01-15T11:30:00Z","reporter":{"org":"Bot Detection Service","contact":"botreport@example.com","domain":"example.com"},"sender":{"org":"Bot Detection Service","contact":"botreport@example.com","domain":"example.com"},"source_identifier":"203.0.113.42","category":"connection","type":"infected_host","destination_ip":"198.51.100.80","destination_port":443,"protocol":"tcp","bot_type":"ai_agent","bot_name":"GPTBot","user_agent":"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.0)","behavior_pattern":"legitimate_crawling","request_rate":2.5,"total_requests":150,"respects_robots_txt":true,"follows_crawl_delay":true,"verification_status":"verified","first_seen":"2025-01-15T11:00:00Z","last_seen":"2025-01-15T11:28:00Z"}]}, + "types/connection-login-attack.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/connection-login-attack.json","title":"XARF v4 Connection - Login Attack Type Schema","description":"Schema for brute force login attempts, credential stuffing campaigns, password spraying attacks, and repeated authentication failures against authentication systems (SSH, RDP, web logins, API authentication)","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"connection"},"type":{"const":"login_attack"},"destination_ip":{"type":"string","anyOf":[{"format":"ipv4"},{"format":"ipv6"}],"x-recommended":true,"description":"RECOMMENDED: Target IP address of the login attack"},"destination_port":{"type":"integer","minimum":1,"maximum":65535,"x-recommended":true,"description":"RECOMMENDED: Target port number"},"protocol":{"type":"string","enum":["tcp","udp","icmp","sctp"],"description":"REQUIRED: Network protocol used in the attack"},"first_seen":{"type":"string","format":"date-time","description":"REQUIRED: When attack activity was first observed"},"last_seen":{"type":"string","format":"date-time","description":"OPTIONAL: When attack activity was last observed"}},"required":["protocol","first_seen"],"if":{"properties":{"source_identifier":{"anyOf":[{"format":"ipv4"},{"format":"ipv6"}]}}},"then":{"required":["source_port"]}}]}, + "types/connection-port-scan.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/connection-port-scan.json","title":"XARF v4 Connection - Port Scan Type Schema","description":"Schema for Network port scanning and reconnaissance activities","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"connection"},"type":{"const":"port_scan"},"destination_ip":{"type":"string","anyOf":[{"format":"ipv4"},{"format":"ipv6"}],"x-recommended":true,"description":"RECOMMENDED: Target IP address of the port scan"},"destination_port":{"type":"integer","minimum":1,"maximum":65535,"x-recommended":true,"description":"RECOMMENDED: Target port number"},"protocol":{"type":"string","enum":["tcp","udp","icmp","sctp"],"description":"REQUIRED: Network protocol used in the attack"},"first_seen":{"type":"string","format":"date-time","description":"REQUIRED: When attack activity was first observed"},"last_seen":{"type":"string","format":"date-time","description":"OPTIONAL: When attack activity was last observed"}},"required":["protocol","first_seen"],"if":{"properties":{"source_identifier":{"anyOf":[{"format":"ipv4"},{"format":"ipv6"}]}}},"then":{"required":["source_port"]}}]}, + "types/connection-reconnaissance.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/connection-reconnaissance.json","title":"XARF v4 Connection - Reconnaissance Type Schema","description":"Schema for reconnaissance and probing activities (e.g., .env, .git, .htaccess files)","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"connection"},"type":{"const":"reconnaissance"},"destination_ip":{"type":"string","anyOf":[{"format":"ipv4"},{"format":"ipv6"}],"x-recommended":true,"description":"RECOMMENDED: Target IP address"},"destination_port":{"type":"integer","minimum":1,"maximum":65535,"x-recommended":true,"description":"RECOMMENDED: Target port number"},"protocol":{"type":"string","enum":["tcp","udp"],"default":"tcp","description":"REQUIRED: Network protocol used"},"probed_resources":{"type":"array","items":{"type":"string"},"description":"REQUIRED: List of resources that were probed","examples":[["/.env","/.git/config","/.htaccess","/wp-config.php.bak","/config.json","/.aws/credentials","/.docker/config.json","/admin/.htpasswd"]]},"resource_categories":{"type":"array","items":{"type":"string","enum":["environment_files","version_control","configuration_files","backup_files","admin_panels","database_files","log_files","credential_files","api_endpoints","debug_endpoints","other"]},"x-recommended":true,"description":"RECOMMENDED: Categories of resources being probed"},"http_methods":{"type":"array","items":{"type":"string","enum":["GET","POST","HEAD","OPTIONS","PUT","DELETE","TRACE","CONNECT"]},"description":"OPTIONAL: HTTP methods used in reconnaissance"},"response_codes":{"type":"array","items":{"type":"integer"},"description":"OPTIONAL: HTTP response codes received"},"successful_probes":{"type":"array","items":{"type":"string"},"x-recommended":true,"description":"RECOMMENDED: Resources that returned success responses (200, 301, 302)"},"user_agent":{"type":"string","description":"OPTIONAL: User-Agent string used"},"first_seen":{"type":"string","format":"date-time","description":"REQUIRED: When reconnaissance activity was first observed"},"last_seen":{"type":"string","format":"date-time","description":"OPTIONAL: When reconnaissance activity was last observed"},"total_probes":{"type":"integer","minimum":1,"description":"OPTIONAL: Total number of probe attempts"},"automated_tool":{"type":"boolean","description":"OPTIONAL: Whether activity appears to be from an automated tool"}},"required":["protocol","probed_resources","first_seen"]}],"examples":[{"xarf_version":"4.2.0","report_id":"e6f7a8b9-c0d1-2345-efab-234567890123","timestamp":"2025-01-15T16:45:00Z","reporter":{"org":"Web Security Service","contact":"security@webhost.example","domain":"webhost.example"},"sender":{"org":"Web Security Service","contact":"security@webhost.example","domain":"webhost.example"},"source_identifier":"192.0.2.99","category":"connection","type":"reconnaissance","destination_ip":"198.51.100.75","destination_port":443,"protocol":"tcp","probed_resources":["/.env","/.git/config","/.aws/credentials","/wp-config.php.bak","/admin/.htpasswd"],"resource_categories":["environment_files","version_control","credential_files","backup_files"],"http_methods":["GET","HEAD"],"response_codes":[404,403,200],"successful_probes":["/.git/config"],"automated_tool":true,"total_probes":47,"first_seen":"2025-01-15T16:30:00Z","last_seen":"2025-01-15T16:44:00Z"}]}, + "types/connection-scraping.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/connection-scraping.json","title":"XARF v4 Connection - Scraping Type Schema","description":"Schema for web crawling and scraping activities","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"connection"},"type":{"const":"scraping"},"destination_ip":{"type":"string","anyOf":[{"format":"ipv4"},{"format":"ipv6"}],"x-recommended":true,"description":"RECOMMENDED: Target IP address being scraped"},"destination_port":{"type":"integer","minimum":1,"maximum":65535,"x-recommended":true,"description":"RECOMMENDED: Target port number (typically 80 or 443)"},"protocol":{"type":"string","enum":["tcp","udp"],"default":"tcp","description":"REQUIRED: Network protocol used"},"scraping_pattern":{"type":"string","enum":["sequential","random","targeted","sitemap_following","api_harvesting","deep_crawling","breadth_first","depth_first"],"x-recommended":true,"description":"RECOMMENDED: Pattern of scraping behavior observed"},"target_content":{"type":"string","enum":["product_data","pricing_information","user_profiles","contact_information","news_articles","images","documents","api_data","search_results","general_content","other"],"x-recommended":true,"description":"RECOMMENDED: Type of content being scraped"},"user_agent":{"type":"string","x-recommended":true,"description":"RECOMMENDED: User-Agent string used by the scraper"},"bot_signature":{"type":"string","description":"OPTIONAL: Known bot or scraper signature if identified","examples":["Googlebot","Bingbot","AhrefsBot","SemrushBot","MJ12bot","DotBot","Custom Python Script","Scrapy"]},"request_rate":{"type":"number","description":"OPTIONAL: Average requests per second"},"total_requests":{"type":"integer","minimum":1,"description":"REQUIRED: Total number of requests made"},"unique_urls":{"type":"integer","minimum":1,"description":"OPTIONAL: Number of unique URLs accessed"},"data_volume":{"type":"integer","description":"OPTIONAL: Total bytes of data transferred"},"respects_robots_txt":{"type":"boolean","description":"OPTIONAL: Whether the scraper respects robots.txt"},"session_duration":{"type":"integer","description":"OPTIONAL: Duration of scraping session in seconds"},"concurrent_connections":{"type":"integer","description":"OPTIONAL: Maximum concurrent connections observed"},"first_seen":{"type":"string","format":"date-time","description":"REQUIRED: When scraping activity was first observed"},"last_seen":{"type":"string","format":"date-time","description":"OPTIONAL: When scraping activity was last observed"}},"required":["protocol","first_seen","total_requests"]}],"examples":[{"xarf_version":"4.2.0","report_id":"a2b3c4d5-e6f7-8901-abcd-ef1234567890","timestamp":"2025-01-15T14:00:00Z","reporter":{"org":"Website Protection Service","contact":"abuse@hosting.example","domain":"hosting.example"},"sender":{"org":"Website Protection Service","contact":"abuse@hosting.example","domain":"hosting.example"},"source_identifier":"192.0.2.150","category":"connection","type":"scraping","destination_ip":"198.51.100.25","destination_port":443,"protocol":"tcp","scraping_pattern":"deep_crawling","target_content":"product_data","user_agent":"Mozilla/5.0 (compatible; DataBot/1.0)","request_rate":15.5,"total_requests":45000,"unique_urls":3500,"respects_robots_txt":false,"concurrent_connections":25,"first_seen":"2025-01-15T10:00:00Z","last_seen":"2025-01-15T13:45:00Z"}]}, + "types/connection-sql-injection.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/connection-sql-injection.json","title":"XARF v4 Connection - SQL Injection Type Schema","description":"Schema for SQL injection attack attempts","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"connection"},"type":{"const":"sql_injection"},"destination_ip":{"type":"string","anyOf":[{"format":"ipv4"},{"format":"ipv6"}],"x-recommended":true,"description":"RECOMMENDED: Target IP address"},"destination_port":{"type":"integer","minimum":1,"maximum":65535,"x-recommended":true,"description":"RECOMMENDED: Target port number (typically 80, 443)"},"protocol":{"type":"string","enum":["tcp","udp"],"default":"tcp","description":"REQUIRED: Network protocol used"},"http_method":{"type":"string","enum":["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS"],"x-recommended":true,"description":"RECOMMENDED: HTTP method used in the attack"},"target_url":{"type":"string","format":"uri","x-recommended":true,"description":"RECOMMENDED: Full URL that was targeted"},"injection_point":{"type":"string","enum":["query_parameter","post_body","cookie","header","path","json_parameter"],"x-recommended":true,"description":"RECOMMENDED: Where the SQL injection was attempted"},"payload_sample":{"type":"string","description":"OPTIONAL: Sample of the SQL injection payload (sanitized)","maxLength":1000},"attack_technique":{"type":"string","enum":["union_based","error_based","boolean_blind","time_blind","stacked_queries","out_of_band","second_order","other"],"x-recommended":true,"description":"RECOMMENDED: SQL injection technique used"},"first_seen":{"type":"string","format":"date-time","description":"REQUIRED: When attack activity was first observed"},"last_seen":{"type":"string","format":"date-time","description":"OPTIONAL: When attack activity was last observed"},"attempts_count":{"type":"integer","minimum":1,"description":"OPTIONAL: Number of injection attempts observed"}},"required":["protocol","first_seen"]}],"examples":[{"xarf_version":"4.2.0","report_id":"b3c4d5e6-f7a8-9012-bcde-f01234567890","timestamp":"2025-01-15T12:00:00Z","reporter":{"org":"Web Application Firewall","contact":"security@example.com","domain":"example.com"},"sender":{"org":"Web Application Firewall","contact":"security@example.com","domain":"example.com"},"source_identifier":"192.0.2.45","category":"connection","type":"sql_injection","destination_ip":"198.51.100.10","destination_port":443,"protocol":"tcp","http_method":"GET","target_url":"https://example.com/products.php?id=1","injection_point":"query_parameter","attack_technique":"union_based","attempts_count":15,"first_seen":"2025-01-15T11:45:00Z","last_seen":"2025-01-15T12:00:00Z"}]}, + "types/connection-vulnerability-scan.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/connection-vulnerability-scan.json","title":"XARF v4 Connection - Vulnerability Scan Type Schema","description":"Schema for vulnerability scanning and automated exploit attempt activities (Nmap, Masscan, Nikto, OpenVAS, web vulnerability scanners)","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"connection"},"type":{"const":"vulnerability_scan"},"destination_ip":{"type":"string","anyOf":[{"format":"ipv4"},{"format":"ipv6"}],"x-recommended":true,"description":"RECOMMENDED: Target IP address being scanned"},"scan_type":{"type":"string","enum":["port_scan","vulnerability_scan","version_detection","os_fingerprinting","service_enumeration","web_vuln_scan","directory_brute_force","mixed"],"description":"REQUIRED: Type of scanning activity"},"scanner_signature":{"type":"string","x-recommended":true,"description":"RECOMMENDED: Known scanner tool signature if identified","examples":["Nmap","Masscan","Nikto","OpenVAS","Nessus","Acunetix","Burp Scanner"]},"targeted_ports":{"type":"array","items":{"type":"integer","minimum":1,"maximum":65535},"x-recommended":true,"description":"RECOMMENDED: List of ports that were scanned"},"targeted_services":{"type":"array","items":{"type":"string"},"description":"OPTIONAL: Services that were targeted","examples":[["http","https","ssh","ftp","mysql","postgresql","mongodb"]]},"vulnerabilities_probed":{"type":"array","items":{"type":"string"},"description":"OPTIONAL: Specific vulnerabilities or CVEs that were probed"},"scan_rate":{"type":"number","description":"OPTIONAL: Requests per second if measurable"},"protocol":{"type":"string","enum":["tcp","udp","icmp","mixed"],"description":"REQUIRED: Network protocol(s) used in scanning"},"first_seen":{"type":"string","format":"date-time","description":"REQUIRED: When scanning activity was first observed"},"last_seen":{"type":"string","format":"date-time","description":"OPTIONAL: When scanning activity was last observed"},"total_requests":{"type":"integer","minimum":1,"description":"OPTIONAL: Total number of scanning requests observed"},"user_agent":{"type":"string","description":"OPTIONAL: User-Agent string if HTTP-based scanning"}},"required":["scan_type","protocol","first_seen"]}],"examples":[{"xarf_version":"4.2.0","report_id":"c4d5e6f7-a8b9-0123-cdef-012345678901","timestamp":"2025-01-15T09:30:00Z","reporter":{"org":"Network Security Monitor","contact":"noc@example.net","domain":"example.net"},"sender":{"org":"Network Security Monitor","contact":"noc@example.net","domain":"example.net"},"source_identifier":"203.0.113.77","category":"connection","type":"vulnerability_scan","destination_ip":"198.51.100.50","scan_type":"web_vuln_scan","scanner_signature":"Nikto","targeted_ports":[80,443,8080,8443],"protocol":"tcp","total_requests":1250,"first_seen":"2025-01-15T09:15:00Z","last_seen":"2025-01-15T09:28:00Z"}]}, + "types/content-base.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/content-base.json","title":"XARF v4 Content Category - Base Schema","description":"Base schema for all content category abuse types with shared fields","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"content"},"url":{"type":"string","format":"uri","description":"REQUIRED: The URL of the abusive content","examples":["https://phishing-site.example.com/login","http://malware-host.example.net/payload.exe"]},"domain":{"type":"string","pattern":"^([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}$","x-recommended":true,"description":"RECOMMENDED: Fully qualified domain name of the abusive content","examples":["phishing-site.example.com","malware.example.net"]},"registrar":{"type":"string","description":"OPTIONAL: Domain registrar if known","examples":["GoDaddy","Namecheap","CloudFlare"]},"nameservers":{"type":"array","items":{"type":"string"},"description":"OPTIONAL: DNS nameservers for the domain","examples":[["ns1.example.com","ns2.example.com"]]},"dns_records":{"type":"object","properties":{"a":{"type":"array","items":{"type":"string","format":"ipv4"}},"aaaa":{"type":"array","items":{"type":"string","format":"ipv6"}},"mx":{"type":"array","items":{"type":"string"}},"txt":{"type":"array","items":{"type":"string"}}},"description":"OPTIONAL: Key DNS evidence records"},"screenshot_url":{"type":"string","format":"uri","description":"OPTIONAL: Reference URL to screenshot evidence"},"verified_at":{"type":"string","format":"date-time","x-recommended":true,"description":"RECOMMENDED: When content was last verified as active"},"verification_method":{"type":"string","enum":["manual","automated_crawler","user_report","honeypot","threat_intelligence"],"x-recommended":true,"description":"RECOMMENDED: How the abusive content was verified"},"attack_vector":{"type":"string","enum":["phishing","malware","fraud","brand_infringement","copyright_infringement","data_leak","remote_compromise","suspicious_registration"],"description":"OPTIONAL: Primary attack vector classification"},"target_brand":{"type":"string","x-recommended":true,"description":"RECOMMENDED: Impersonated brand or entity if applicable","examples":["PayPal","Microsoft","Amazon"]},"hosting_provider":{"type":"string","description":"OPTIONAL: Identified hosting provider","examples":["AWS","CloudFlare","DigitalOcean"]},"asn":{"type":"integer","minimum":1,"maximum":4294967295,"description":"OPTIONAL: Autonomous System Number"},"country_code":{"type":"string","pattern":"^[A-Z]{2}$","description":"OPTIONAL: ISO 3166-1 alpha-2 country code"},"ssl_certificate":{"type":"object","properties":{"issuer":{"type":"string","description":"OPTIONAL: Certificate issuer"},"subject":{"type":"string","description":"OPTIONAL: Certificate subject"},"valid_from":{"type":"string","format":"date-time"},"valid_to":{"type":"string","format":"date-time"},"fingerprint":{"type":"string","description":"OPTIONAL: SHA256 fingerprint of the certificate"}},"description":"OPTIONAL: SSL certificate details if HTTPS is used"},"whois":{"type":"object","properties":{"registrant":{"type":"string","description":"OPTIONAL: Domain registrant name or organization"},"created_date":{"type":"string","format":"date-time","description":"OPTIONAL: Domain creation date"},"updated_date":{"type":"string","format":"date-time","description":"OPTIONAL: Domain last updated date"},"expiry_date":{"type":"string","format":"date-time","description":"OPTIONAL: Domain expiration date"},"registrar_abuse_contact":{"type":"string","format":"email","description":"OPTIONAL: Registrar's abuse contact email"}},"description":"OPTIONAL: WHOIS data for the domain"},"dns_response":{"type":"object","properties":{"query_time":{"type":"string","format":"date-time","description":"OPTIONAL: When the DNS query was made"},"authoritative":{"type":"boolean","description":"OPTIONAL: Whether response was from authoritative nameserver"},"response_code":{"type":"string","enum":["NOERROR","NXDOMAIN","SERVFAIL","REFUSED"],"description":"OPTIONAL: DNS response code"}},"description":"OPTIONAL: DNS query response metadata"}},"required":["url"]}]}, + "types/content-brand_infringement.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/content-brand_infringement.json","title":"XARF v4 Content - Brand Infringement Type Schema","description":"Schema for brand impersonation and trademark violations","allOf":[{"$ref":"./content-base.json"},{"type":"object","properties":{"type":{"const":"brand_infringement"},"infringement_type":{"type":"string","enum":["counterfeit","typosquatting","lookalike","homograph","unauthorized_reseller","trademark_violation","brand_impersonation","logo_misuse","other"],"description":"REQUIRED: Specific type of brand infringement"},"legitimate_site":{"type":"string","format":"uri","description":"REQUIRED: URL of the legitimate brand website"},"similarity_score":{"type":"number","minimum":0,"maximum":1,"x-recommended":true,"description":"RECOMMENDED: Visual or textual similarity score (0.0 = no similarity, 1.0 = identical)"},"trademark_details":{"type":"object","properties":{"registration_number":{"type":"string","description":"OPTIONAL: Trademark registration number"},"jurisdiction":{"type":"string","description":"OPTIONAL: Trademark jurisdiction (country/region)"},"category":{"type":"array","items":{"type":"integer","minimum":1,"maximum":45},"description":"OPTIONAL: Nice Classification classes"}},"description":"OPTIONAL: Trademark registration details if applicable"},"infringing_elements":{"type":"array","items":{"type":"string","enum":["logo","brand_name","tagline","color_scheme","layout","product_images","domain_name","other"]},"x-recommended":true,"description":"RECOMMENDED: Specific brand elements being infringed"},"products_offered":{"type":"array","items":{"type":"string"},"description":"OPTIONAL: Products or services offered on the infringing site"},"previous_enforcement":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","format":"date","description":"OPTIONAL: Date of enforcement action"},"action":{"type":"string","enum":["cease_desist","takedown_notice","domain_dispute","legal_action","other"],"description":"OPTIONAL: Type of action taken"},"result":{"type":"string","description":"OPTIONAL: Result of the action"}}},"description":"OPTIONAL: Previous enforcement actions taken"}},"required":["infringement_type","legitimate_site"]}],"examples":[{"xarf_version":"4.2.0","report_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","timestamp":"2025-01-15T14:45:00Z","reporter":{"org":"Brand Protection Services","contact":"enforcement@brandprotect.example","domain":"brandprotect.example"},"sender":{"org":"Brand Protection Services","contact":"enforcement@brandprotect.example","domain":"brandprotect.example"},"source_identifier":"203.0.113.77","category":"content","type":"brand_infringement","url":"https://sh0p-deals.example.com","infringement_type":"typosquatting","legitimate_site":"https://www.shop.example","similarity_score":0.87,"infringing_elements":["logo","brand_name","color_scheme"],"target_brand":"Major Retailer","evidence":[{"content_type":"image/png","description":"Screenshot showing brand impersonation","payload":"base64_encoded_screenshot"}]}]}, + "types/content-csam.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/content-csam.json","title":"XARF v4 Content - CSAM Type Schema","description":"Schema for Child Sexual Abuse Material (baseline/A1/A2/B1/B2 illegal content)","allOf":[{"$ref":"./content-base.json"},{"type":"object","properties":{"type":{"const":"csam"},"classification":{"type":"string","enum":["baseline","A1","A2","B1","B2"],"description":"REQUIRED: Legal classification category for the material"},"media_type":{"type":"string","enum":["image","video","audio","text","mixed"],"x-recommended":true,"description":"RECOMMENDED: Type of media containing CSAM"},"detection_method":{"type":"string","enum":["hash_match","ai_detection","manual_review","user_report","automated_scan"],"description":"REQUIRED: Method used to detect the CSAM"},"hash_values":{"type":"object","properties":{"md5":{"type":"string","pattern":"^[a-fA-F0-9]{32}$","description":"OPTIONAL: MD5 hash"},"sha1":{"type":"string","pattern":"^[a-fA-F0-9]{40}$","description":"OPTIONAL: SHA1 hash"},"sha256":{"type":"string","pattern":"^[a-fA-F0-9]{64}$","description":"RECOMMENDED: SHA256 hash"},"photodna":{"type":"string","description":"RECOMMENDED: PhotoDNA hash for image matching"}},"x-recommended":true,"description":"RECOMMENDED: Hash values of the illegal content"},"ncmec_report_id":{"type":"string","x-recommended":true,"description":"RECOMMENDED: NCMEC CyberTipline report ID if applicable"},"content_removed":{"type":"boolean","x-recommended":true,"description":"RECOMMENDED: Whether the content has been removed"},"account_suspended":{"type":"boolean","description":"OPTIONAL: Whether associated accounts were suspended"}},"required":["classification","detection_method"]}],"examples":[{"xarf_version":"4.2.0","report_id":"f7a8b9c0-d1e2-3456-fabc-def012345678","timestamp":"2025-01-15T10:30:00Z","reporter":{"org":"Content Safety Service","contact":"safety@example.org","domain":"example.org"},"sender":{"org":"Content Safety Service","contact":"safety@example.org","domain":"example.org"},"source_identifier":"198.51.100.42","category":"content","type":"csam","url":"https://example.com/illegal-content","classification":"A1","detection_method":"hash_match","media_type":"image","hash_values":{"sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},"ncmec_report_id":"12345678","content_removed":true,"account_suspended":true}]}, + "types/content-csem.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/content-csem.json","title":"XARF v4 Content - CSEM Type Schema","description":"Schema for Child Sexual Exploitation Material (grooming, solicitation, and other exploitation activities)","allOf":[{"$ref":"./content-base.json"},{"type":"object","properties":{"type":{"const":"csem"},"exploitation_type":{"type":"string","enum":["grooming","solicitation","sextortion","trafficking","distribution","production","possession"],"description":"REQUIRED: Type of exploitation activity"},"victim_age_range":{"type":"string","enum":["infant","toddler","prepubescent","pubescent","unknown"],"x-recommended":true,"description":"RECOMMENDED: Estimated age range of victim"},"platform":{"type":"string","enum":["social_media","messaging_app","gaming_platform","forum","email","darkweb","other"],"x-recommended":true,"description":"RECOMMENDED: Platform where exploitation occurred"},"detection_method":{"type":"string","enum":["behavioral_analysis","keyword_detection","user_report","ai_detection","manual_review","law_enforcement_referral"],"description":"REQUIRED: Method used to detect the exploitation"},"evidence_type":{"type":"array","items":{"type":"string","enum":["chat_logs","images","videos","user_profile","metadata"]},"x-recommended":true,"description":"RECOMMENDED: Types of evidence collected"},"perpetrator_indicators":{"type":"object","properties":{"account_id":{"type":"string","description":"OPTIONAL: Account identifier of perpetrator"},"ip_addresses":{"type":"array","items":{"type":"string","format":"ipv4"},"description":"OPTIONAL: IP addresses associated with perpetrator"},"pattern_of_behavior":{"type":"string","description":"OPTIONAL: Description of behavioral patterns"}},"description":"OPTIONAL: Indicators associated with the perpetrator"},"reporting_obligations":{"type":"array","items":{"type":"string","enum":["NCMEC","IWF","local_law_enforcement","europol","interpol","platform_safety_team","other"]},"x-recommended":true,"description":"RECOMMENDED: Entities to which this has been or should be reported"}},"required":["exploitation_type","detection_method"]}],"examples":[{"xarf_version":"4.2.0","report_id":"a8b9c0d1-e2f3-4567-abcd-ef0123456789","timestamp":"2025-01-20T14:20:00Z","reporter":{"org":"Platform Safety Team","contact":"safety@platform.example","domain":"platform.example"},"sender":{"org":"Platform Safety Team","contact":"safety@platform.example","domain":"platform.example"},"source_identifier":"203.0.113.55","category":"content","type":"csem","url":"https://platform.example/user/suspicious-account","exploitation_type":"grooming","victim_age_range":"pubescent","detection_method":"behavioral_analysis","platform":"social_media","evidence_type":["chat_logs","user_profile"],"reporting_obligations":["NCMEC","local_law_enforcement","platform_safety_team"],"perpetrator_indicators":{"account_id":"user123456","ip_addresses":["203.0.113.55","203.0.113.56"],"pattern_of_behavior":"Repeated contact with minors, requesting private information"}}]}, + "types/content-exposed-data.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/content-exposed-data.json","title":"XARF v4 Content - Exposed Data Type Schema","description":"Schema for exposed sensitive data and information leaks","allOf":[{"$ref":"./content-base.json"},{"type":"object","properties":{"type":{"const":"exposed_data"},"data_types":{"type":"array","items":{"type":"string","enum":["personal_information","credentials","financial","medical","government_id","email_addresses","phone_numbers","api_keys","database_dumps","source_code","internal_documents","customer_data","employee_data","intellectual_property","other"]},"minItems":1,"description":"REQUIRED: Types of data exposed"},"exposure_method":{"type":"string","enum":["misconfigured_server","open_directory","database_exposure","git_repository","backup_file","log_file","cloud_storage","paste_site","forum_post","ransomware_leak","intentional_leak","other"],"description":"REQUIRED: How the data was exposed"},"record_count":{"type":"integer","minimum":0,"x-recommended":true,"description":"RECOMMENDED: Number of records exposed (if known)"},"affected_organization":{"type":"string","x-recommended":true,"description":"RECOMMENDED: Organization whose data was exposed"},"data_format":{"type":"string","enum":["plaintext","csv","json","xml","sql","excel","pdf","mixed","other"],"description":"OPTIONAL: Format of the exposed data"},"sensitive_fields":{"type":"array","items":{"type":"string"},"x-recommended":true,"description":"RECOMMENDED: Specific sensitive data fields exposed","examples":[["ssn","credit_card","password"],["email","phone","address"]]},"encryption_status":{"type":"string","enum":["unencrypted","encrypted","partially_encrypted","hashed","unknown"],"x-recommended":true,"description":"RECOMMENDED: Whether the exposed data was encrypted"},"accessibility":{"type":"string","enum":["public","requires_authentication","requires_payment","dark_web","removed"],"description":"OPTIONAL: Current accessibility of the exposed data"},"discovery_source":{"type":"string","enum":["security_researcher","automated_scan","breach_monitoring","user_report","law_enforcement","threat_intelligence","other"],"description":"OPTIONAL: How the data exposure was discovered"},"sample_records":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string","description":"OPTIONAL: Description of sample record"},"redacted_sample":{"type":"string","description":"OPTIONAL: Redacted sample content"}}},"maxItems":5,"description":"OPTIONAL: Redacted samples of exposed records for verification"}},"required":["data_types","exposure_method"]}],"examples":[{"xarf_version":"4.2.0","report_id":"f1e2d3c4-b5a6-7890-abcd-ef1234567890","timestamp":"2025-01-15T09:20:00Z","reporter":{"org":"Data Breach Monitor","contact":"alerts@breachmonitor.example","domain":"breachmonitor.example"},"sender":{"org":"Data Breach Monitor","contact":"alerts@breachmonitor.example","domain":"breachmonitor.example"},"source_identifier":"198.51.100.99","category":"content","type":"exposed_data","url":"http://exposed-database.example.com:8080/dump.sql","data_types":["credentials","personal_information","financial"],"exposure_method":"misconfigured_server","record_count":150000,"affected_organization":"Example Corp","data_format":"sql","sensitive_fields":["email","password_hash","credit_card"],"encryption_status":"hashed","accessibility":"public","evidence":[{"content_type":"text/plain","description":"Sample of exposed database structure","payload":"base64_encoded_sample"}]}]}, + "types/content-fraud.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/content-fraud.json","title":"XARF v4 Content - Fraud Type Schema","description":"Schema for fraud and scam websites","allOf":[{"$ref":"./content-base.json"},{"type":"object","properties":{"type":{"const":"fraud"},"fraud_type":{"type":"string","enum":["investment","romance","tech_support","lottery","advance_fee","cryptocurrency","shopping","charity","employment","government_impersonation","other"],"description":"REQUIRED: Specific type of fraud"},"payment_methods":{"type":"array","items":{"type":"string","enum":["credit_card","bank_transfer","cryptocurrency","gift_cards","wire_transfer","paypal","western_union","moneygram","cashapp","venmo","other"]},"x-recommended":true,"description":"RECOMMENDED: Payment methods requested by fraudsters"},"cryptocurrency_addresses":{"type":"array","items":{"type":"object","properties":{"currency":{"type":"string","enum":["bitcoin","ethereum","usdt","bnb","monero","other"],"description":"OPTIONAL: Cryptocurrency type"},"address":{"type":"string","description":"OPTIONAL: Cryptocurrency wallet address"}},"required":["currency","address"]},"description":"OPTIONAL: Cryptocurrency addresses used in the fraud"},"claimed_entity":{"type":"string","x-recommended":true,"description":"RECOMMENDED: Organization or person the fraudster claims to represent"},"loss_amount":{"type":"object","properties":{"currency":{"type":"string","pattern":"^[A-Z]{3}$","description":"OPTIONAL: ISO 4217 currency code"},"amount":{"type":"number","minimum":0,"description":"OPTIONAL: Estimated or actual loss amount"}},"description":"OPTIONAL: Financial loss information if known"}},"required":["fraud_type"]}],"examples":[{"xarf_version":"4.2.0","report_id":"550e8400-e29b-41d4-a716-446655440000","timestamp":"2025-01-15T10:30:00Z","reporter":{"org":"Anti-Fraud Coalition","contact":"reports@antifraud.example","domain":"antifraud.example"},"sender":{"org":"Anti-Fraud Coalition","contact":"reports@antifraud.example","domain":"antifraud.example"},"source_identifier":"198.51.100.45","category":"content","type":"fraud","url":"https://get-rich-quick.example.com","fraud_type":"investment","payment_methods":["cryptocurrency","wire_transfer"],"cryptocurrency_addresses":[{"currency":"bitcoin","address":"1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"}],"evidence":[{"content_type":"image/png","description":"Screenshot of fraudulent investment site","payload":"base64_encoded_screenshot"}]}]}, + "types/content-malware.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/content-malware.json","title":"XARF v4 Content - Malware Type Schema","description":"Schema for malware hosting and distribution","allOf":[{"$ref":"./content-base.json"},{"type":"object","properties":{"type":{"const":"malware"},"malware_family":{"type":"string","x-recommended":true,"description":"RECOMMENDED: Known malware family name","examples":["Emotet","TrickBot","Qakbot","Cobalt Strike"]},"malware_type":{"type":"string","enum":["trojan","ransomware","dropper","loader","backdoor","rootkit","infostealer","banking_trojan","cryptominer","adware","spyware","worm","bot","rat","other"],"x-recommended":true,"description":"RECOMMENDED: Classification of the malware"},"file_hashes":{"type":"object","properties":{"md5":{"type":"string","pattern":"^[a-fA-F0-9]{32}$","description":"OPTIONAL: MD5 hash of malware file"},"sha1":{"type":"string","pattern":"^[a-fA-F0-9]{40}$","description":"OPTIONAL: SHA1 hash of malware file"},"sha256":{"type":"string","pattern":"^[a-fA-F0-9]{64}$","description":"RECOMMENDED: SHA256 hash of malware file"},"ssdeep":{"type":"string","description":"OPTIONAL: SSDeep fuzzy hash"}},"x-recommended":true,"description":"RECOMMENDED: Hash values of the malware file"},"file_metadata":{"type":"object","properties":{"filename":{"type":"string","description":"OPTIONAL: Original filename"},"file_size":{"type":"integer","minimum":0,"description":"OPTIONAL: File size in bytes"},"file_type":{"type":"string","description":"OPTIONAL: File type description","examples":["PE32 executable","PDF document","Microsoft Word document","ZIP archive"]},"mime_type":{"type":"string","description":"OPTIONAL: MIME type"}},"description":"OPTIONAL: Metadata about the malware file"},"distribution_method":{"type":"string","enum":["direct_download","drive_by_download","email_attachment","malvertising","exploit_kit","watering_hole","supply_chain","social_engineering","other"],"x-recommended":true,"description":"RECOMMENDED: How the malware is being distributed"},"c2_servers":{"type":"array","items":{"type":"object","properties":{"address":{"type":"string","description":"OPTIONAL: IP or domain of C2 server"},"port":{"type":"integer","minimum":1,"maximum":65535,"description":"OPTIONAL: Port number"},"protocol":{"type":"string","enum":["http","https","tcp","udp","dns","other"],"description":"OPTIONAL: Protocol used"}}},"description":"OPTIONAL: Command and control servers if known"},"sandbox_analysis":{"type":"object","properties":{"sandbox_name":{"type":"string","description":"OPTIONAL: Name of sandbox used","examples":["VirusTotal","Hybrid Analysis","Joe Sandbox"]},"analysis_url":{"type":"string","format":"uri","description":"OPTIONAL: URL to analysis report"},"verdict":{"type":"string","enum":["malicious","suspicious","clean","unknown"],"description":"OPTIONAL: Analysis verdict"},"score":{"type":"number","minimum":0,"maximum":100,"description":"OPTIONAL: Maliciousness score"}},"description":"OPTIONAL: Results from automated malware analysis"},"exploit_cve":{"type":"array","items":{"type":"string","pattern":"^CVE-\\d{4}-\\d{4,}$"},"description":"OPTIONAL: CVEs exploited by this malware"},"persistence_mechanism":{"type":"array","items":{"type":"string","enum":["registry","scheduled_task","service","startup_folder","dll_hijacking","wmi","other"]},"description":"OPTIONAL: How the malware maintains persistence"},"targeted_platforms":{"type":"array","items":{"type":"string","enum":["windows","linux","macos","android","ios","multi_platform"]},"description":"OPTIONAL: Operating systems targeted by the malware"}}}],"examples":[{"xarf_version":"4.2.0","report_id":"e5f6a7b8-c9d0-1234-efab-cdef01234567","timestamp":"2025-01-15T12:45:00Z","reporter":{"org":"Malware Analysis Lab","contact":"alerts@malwarelab.example","domain":"malwarelab.example"},"sender":{"org":"Malware Analysis Lab","contact":"alerts@malwarelab.example","domain":"malwarelab.example"},"source_identifier":"198.51.100.123","category":"content","type":"malware","url":"https://malicious-downloads.example.com/invoice.exe","malware_family":"Emotet","malware_type":"trojan","file_hashes":{"sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"},"distribution_method":"email_attachment","evidence":[{"content_type":"application/octet-stream","description":"Malware sample","payload":"base64_encoded_malware"}]}]}, + "types/content-phishing.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/content-phishing.json","title":"XARF v4 Content - Phishing Type Schema","description":"Schema for phishing websites and credential harvesting","allOf":[{"$ref":"./content-base.json"},{"type":"object","properties":{"type":{"const":"phishing"},"credential_fields":{"type":"array","items":{"type":"string"},"x-recommended":true,"description":"RECOMMENDED: Form fields present on the phishing page","examples":[["username","password"],["email","password","pin"],["card_number","cvv","expiry"]]},"phishing_kit":{"type":"string","description":"OPTIONAL: Known phishing kit identifier if detected","examples":["Kr3pto","16Shop","LogoKit"]},"redirect_chain":{"type":"array","items":{"type":"string","format":"uri"},"description":"OPTIONAL: URL redirect sequence leading to phishing page"},"submission_url":{"type":"string","format":"uri","x-recommended":true,"description":"RECOMMENDED: Where credentials are submitted"},"cloned_site":{"type":"string","format":"uri","x-recommended":true,"description":"RECOMMENDED: Legitimate site being impersonated"},"detection_evasion":{"type":"array","items":{"type":"string","enum":["geo_blocking","user_agent_filtering","referrer_checking","captcha","time_based_display","ip_blacklisting","obfuscation","other"]},"description":"OPTIONAL: Evasion techniques used by the phishing page"},"lure_type":{"type":"string","enum":["account_suspension","security_alert","payment_issue","prize_notification","document_share","password_reset","shipping_notification","tax_refund","other"],"x-recommended":true,"description":"RECOMMENDED: Social engineering lure used"}}}],"examples":[{"xarf_version":"4.2.0","report_id":"b2c3d4e5-f6a7-8901-bcde-f2345678901a","timestamp":"2025-01-15T15:15:24Z","reporter":{"org":"Phishing Detection Service","contact":"reports@antiphishing.example","domain":"antiphishing.example"},"sender":{"org":"Phishing Detection Service","contact":"reports@antiphishing.example","domain":"antiphishing.example"},"source_identifier":"203.0.113.45","category":"content","type":"phishing","url":"https://secure-banking-login.example.com/auth","target_brand":"Major Bank Corp","cloned_site":"https://www.bank.example","credential_fields":["username","password","pin"],"lure_type":"security_alert","evidence":[{"content_type":"image/png","description":"Screenshot of phishing page","payload":"base64_encoded_screenshot"}]}]}, + "types/content-remote_compromise.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/content-remote_compromise.json","title":"XARF v4 Content - Remote Compromise Type Schema","description":"Schema for compromised websites, webshells, and unauthorized access","allOf":[{"$ref":"./content-base.json"},{"type":"object","properties":{"type":{"const":"remote_compromise"},"compromise_type":{"type":"string","enum":["webshell","backdoor","defacement","malicious_redirect","seo_spam","cryptominer","phishing_kit","malware_host","c2_server","proxy","scanner","other"],"description":"REQUIRED: Type of compromise detected"},"compromise_indicators":{"type":"array","items":{"type":"object","properties":{"type":{"type":"string","enum":["file_path","process","network_connection","user_account","scheduled_task","registry_key","service"],"description":"REQUIRED: Type of indicator"},"value":{"type":"string","description":"REQUIRED: Indicator value (e.g., file path, process name)"},"description":{"type":"string","description":"OPTIONAL: Additional description"}},"required":["type","value"]},"x-recommended":true,"description":"RECOMMENDED: Specific indicators of compromise"},"webshell_details":{"type":"object","properties":{"family":{"type":"string","description":"OPTIONAL: Known webshell family","examples":["WSO","C99","B374K","R57"]},"capabilities":{"type":"array","items":{"type":"string","enum":["file_manager","command_execution","database_access","network_scanning","privilege_escalation","persistence","other"]},"description":"OPTIONAL: Webshell capabilities"},"password_protected":{"type":"boolean","description":"OPTIONAL: Whether webshell is password protected"}},"x-recommended":true,"description":"RECOMMENDED: Details specific to webshell compromises"},"affected_cms":{"type":"string","enum":["wordpress","joomla","drupal","magento","prestashop","opencart","custom","unknown","other"],"x-recommended":true,"description":"RECOMMENDED: Content management system if identified"},"vulnerability_exploited":{"type":"object","properties":{"cve":{"type":"string","pattern":"^CVE-\\d{4}-\\d{4,}$","description":"OPTIONAL: CVE identifier"},"description":{"type":"string","description":"OPTIONAL: Vulnerability description"},"component":{"type":"string","description":"OPTIONAL: Vulnerable component (e.g., plugin name)"}},"description":"OPTIONAL: Vulnerability used for initial compromise if known"},"persistence_mechanisms":{"type":"array","items":{"type":"string","enum":["cron_job","modified_core_files","hidden_admin_account","autoload_backdoor","htaccess_modification","database_backdoor","other"]},"x-recommended":true,"description":"RECOMMENDED: Methods used to maintain access"},"malicious_activities":{"type":"array","items":{"type":"string","enum":["spam_sending","ddos_attacks","cryptocurrency_mining","data_exfiltration","lateral_movement","hosting_malware","hosting_phishing","scanning","other"]},"x-recommended":true,"description":"RECOMMENDED: Observed malicious activities from the compromised site"},"cleanup_status":{"type":"string","enum":["not_cleaned","partially_cleaned","cleaned","reinfected","unknown"],"description":"OPTIONAL: Current cleanup status of the compromise"}},"required":["compromise_type"]}],"examples":[{"xarf_version":"4.2.0","report_id":"d4e5f6a7-b8c9-0123-def4-567890abcdef","timestamp":"2025-01-15T11:30:00Z","reporter":{"org":"Web Security Scanner","contact":"abuse@websecscanner.example","domain":"websecscanner.example"},"sender":{"org":"Web Security Scanner","contact":"abuse@websecscanner.example","domain":"websecscanner.example"},"source_identifier":"192.0.2.150","category":"content","type":"remote_compromise","url":"https://compromised-site.example.com/wp-content/uploads/shell.php","compromise_type":"webshell","affected_cms":"wordpress","webshell_details":{"family":"WSO","capabilities":["file_manager","command_execution","database_access"],"password_protected":true},"compromise_indicators":[{"type":"file_path","value":"/wp-content/uploads/2025/01/shell.php","description":"Webshell located in uploads directory"}],"evidence":[{"content_type":"text/plain","description":"Webshell source code snippet","payload":"base64_encoded_code"}]}]}, + "types/content-suspicious_registration.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/content-suspicious_registration.json","title":"XARF v4 Content - Suspicious Registration Type Schema","description":"Schema for newly registered suspicious domains and preemptive threat detection","allOf":[{"$ref":"./content-base.json"},{"type":"object","properties":{"type":{"const":"suspicious_registration"},"registration_date":{"type":"string","format":"date-time","description":"REQUIRED: When the domain was registered"},"days_since_registration":{"type":"integer","minimum":0,"x-recommended":true,"description":"RECOMMENDED: Number of days since domain registration"},"suspicious_indicators":{"type":"array","items":{"type":"string","enum":["typosquatting","homograph_attack","brand_keyword","suspicious_tld","bulk_registration","privacy_protection","suspicious_registrant","fast_flux","dga_pattern","known_bad_nameserver","suspicious_ssl_cert","immediate_activation","parked_page","other"]},"minItems":1,"description":"REQUIRED: Indicators that make this registration suspicious"},"risk_score":{"type":"number","minimum":0,"maximum":1,"x-recommended":true,"description":"RECOMMENDED: Calculated risk score for this domain"},"targeted_brands":{"type":"array","items":{"type":"string"},"x-recommended":true,"description":"RECOMMENDED: Brands potentially targeted by this domain"},"registrant_details":{"type":"object","properties":{"email_domain":{"type":"string","description":"OPTIONAL: Domain of registrant's email"},"country":{"type":"string","pattern":"^[A-Z]{2}$","description":"OPTIONAL: ISO 3166-1 alpha-2 country code"},"privacy_protected":{"type":"boolean","description":"OPTIONAL: Whether WHOIS privacy is enabled"},"bulk_registrations":{"type":"integer","description":"OPTIONAL: Number of domains registered by same entity"}},"x-recommended":true,"description":"RECOMMENDED: Information about the domain registrant"},"related_domains":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string","description":"OPTIONAL: Related domain name"},"relationship":{"type":"string","enum":["same_registrant","same_nameserver","same_ip","same_ssl_cert","similar_pattern","same_campaign"],"description":"OPTIONAL: Type of relationship"}}},"maxItems":20,"description":"OPTIONAL: Other domains related to this suspicious registration"},"predicted_usage":{"type":"array","items":{"type":"string","enum":["phishing","malware","spam","fraud","brand_abuse","botnet_c2","unknown"]},"x-recommended":true,"description":"RECOMMENDED: Predicted malicious usage based on patterns"},"ssl_certificate_details":{"type":"object","properties":{"issued_immediately":{"type":"boolean","description":"OPTIONAL: Certificate issued immediately after registration"},"free_certificate":{"type":"boolean","description":"OPTIONAL: Using free SSL certificate provider"},"wildcard":{"type":"boolean","description":"OPTIONAL: Wildcard certificate"}},"description":"OPTIONAL: SSL certificate details"},"activation_behavior":{"type":"object","properties":{"time_to_activation":{"type":"integer","description":"OPTIONAL: Hours between registration and first content"},"initial_content":{"type":"string","enum":["parked","under_construction","immediate_malicious","cloned_site","blank","other"],"description":"OPTIONAL: Type of initial content"}},"description":"OPTIONAL: Activation behavior details"}},"required":["registration_date","suspicious_indicators"]}],"examples":[{"xarf_version":"4.2.0","report_id":"c3d4e5f6-a7b8-9012-bcde-f34567890123","timestamp":"2025-01-15T08:00:00Z","reporter":{"org":"Domain Threat Intelligence","contact":"alerts@domainthreat.example","domain":"domainthreat.example"},"sender":{"org":"Domain Threat Intelligence","contact":"alerts@domainthreat.example","domain":"domainthreat.example"},"source_identifier":"192.0.2.200","category":"content","type":"suspicious_registration","url":"https://amaz0n-secure.example","domain":"amaz0n-secure.example","registration_date":"2025-01-14T22:00:00Z","days_since_registration":0,"suspicious_indicators":["typosquatting","brand_keyword","privacy_protection","immediate_activation"],"risk_score":0.92,"targeted_brands":["Amazon"],"registrant_details":{"privacy_protected":true,"country":"CN","bulk_registrations":47},"predicted_usage":["phishing","fraud"],"evidence":[{"content_type":"application/json","description":"WHOIS and DNS records","payload":"base64_encoded_data"}]}]}, + "types/copyright-copyright.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/copyright-copyright.json","title":"XARF v4 Copyright - Copyright Type Schema","description":"Schema for Copyright infringement and DMCA violations","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"copyright"},"type":{"const":"copyright"},"infringing_url":{"type":"string","format":"uri","description":"REQUIRED: URL of the infringing content - this is what's being reported","examples":["http://piracy-site.example.com/movies/copyrighted-movie.mp4","https://file-sharing.example.org/download/12345"]},"work_title":{"type":"string","maxLength":500,"x-recommended":true,"description":"RECOMMENDED: Title of the copyrighted work","examples":["Avengers: Endgame","Beatles - Abbey Road","Adobe Photoshop"]},"rights_holder":{"type":"string","maxLength":200,"x-recommended":true,"description":"RECOMMENDED: Organization or person holding the copyright","examples":["Disney Enterprises, Inc.","Sony Music","Adobe Systems"]},"original_url":{"type":"string","format":"uri","description":"OPTIONAL: URL of the legitimate/original content","examples":["https://www.studio.example/movies/blockbuster-film"]},"infringement_type":{"type":"string","enum":["direct_copy","modified_copy","streaming","download","distribution"],"x-recommended":true,"description":"RECOMMENDED: Type of copyright infringement"}},"required":["infringing_url"]}]}, + "types/copyright-cyberlocker.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/copyright-cyberlocker.json","title":"XARF v4 Copyright - Cyberlocker Type Schema","description":"Schema for cyberlocker/file hosting service copyright infringement reports","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"copyright"},"type":{"const":"cyberlocker"},"evidence_source":{"type":"string","enum":["automated_crawl","manual_discovery","user_report","rights_holder","search_engine"],"x-recommended":true,"description":"RECOMMENDED: Source of cyberlocker infringement evidence"},"infringing_url":{"type":"string","format":"uri","description":"REQUIRED: URL to the infringing file on the hosting service"},"hosting_service":{"type":"string","maxLength":200,"description":"REQUIRED: Name of the file hosting service","examples":["Rapidshare","Megaupload","4shared","MediaFire","Zippyshare","Uploaded.net"]},"file_info":{"type":"object","properties":{"filename":{"type":"string","maxLength":500,"description":"OPTIONAL: Name of the infringing file"},"file_size":{"type":"integer","minimum":0,"description":"OPTIONAL: File size in bytes"},"file_hash":{"type":"string","pattern":"^(md5|sha1|sha256):[a-fA-F0-9]+$","description":"OPTIONAL: File hash with algorithm prefix"},"upload_date":{"type":"string","format":"date-time","description":"OPTIONAL: When file was uploaded to service"},"download_count":{"type":"integer","minimum":0,"description":"OPTIONAL: Number of downloads if available"}},"additionalProperties":false,"x-recommended":true,"description":"RECOMMENDED: File information details"},"uploader_info":{"type":"object","properties":{"username":{"type":"string","maxLength":200,"description":"OPTIONAL: Username of the uploader"},"user_id":{"type":"string","maxLength":100,"description":"OPTIONAL: User ID on the hosting service"},"account_type":{"type":"string","enum":["free","premium","business","unknown"],"description":"OPTIONAL: Type of user account"}},"additionalProperties":false,"description":"OPTIONAL: Information about the uploader"},"work_title":{"type":"string","maxLength":500,"x-recommended":true,"description":"RECOMMENDED: Title of the copyrighted work"},"rights_holder":{"type":"string","maxLength":200,"x-recommended":true,"description":"RECOMMENDED: Organization or person holding the copyright"},"work_category":{"type":"string","enum":["movie","tv_show","music","software","ebook","audiobook","game","document","other"],"x-recommended":true,"description":"RECOMMENDED: Category of copyrighted work"},"access_method":{"type":"string","enum":["direct_link","password_protected","premium_only","time_limited","captcha_protected"],"description":"OPTIONAL: How the file can be accessed"},"takedown_info":{"type":"object","properties":{"previous_requests":{"type":"integer","minimum":0,"description":"OPTIONAL: Number of previous takedown requests for this file"},"service_response_time":{"type":"string","description":"OPTIONAL: Expected response time from hosting service"},"automated_removal":{"type":"boolean","description":"OPTIONAL: Whether service supports automated removal"}},"additionalProperties":false,"description":"OPTIONAL: Takedown request information"}},"required":["infringing_url","hosting_service"]}],"examples":[{"xarf_version":"4.2.0","report_id":"456b7890-c123-45d6-e789-012345678901","timestamp":"2024-01-15T11:15:20Z","reporter":{"org":"Anti-Piracy Coalition","contact":"takedowns@anti-piracy.example","domain":"anti-piracy.example"},"sender":{"org":"Anti-Piracy Coalition","contact":"takedowns@anti-piracy.example","domain":"anti-piracy.example"},"source_identifier":"cyberlocker-service.example","category":"copyright","type":"cyberlocker","infringing_url":"https://filehost.example/download/abc123def456","hosting_service":"FileHost Pro","work_title":"Popular Movie 2024","rights_holder":"Entertainment Studios LLC","work_category":"movie","evidence_source":"automated_crawl","file_info":{"filename":"Popular.Movie.2024.1080p.WEBRip.x264.mp4","file_size":2147483648,"upload_date":"2024-01-14T20:30:00Z"},"uploader_info":{"username":"movieshare123","account_type":"premium"},"access_method":"direct_link","evidence":[{"content_type":"text/html","description":"Screenshot of download page showing copyrighted content","payload":"PGh0bWw+PGhlYWQ+PHRpdGxlPkRvd25sb2FkIFBvcHVsYXIgTW92aWUgMjAyNC4uLjwvdGl0bGU+PC9oZWFkPjwvaHRtbD4="}],"tags":["copyright:cyberlocker","service:filehost","media:movie"]}]}, + "types/copyright-link-site.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/copyright-link-site.json","title":"XARF v4 Copyright - Link Site Type Schema","description":"Schema for link aggregation site copyright infringement reports","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"copyright"},"type":{"const":"link_site"},"evidence_source":{"type":"string","enum":["automated_crawl","manual_monitoring","user_report","rights_holder","search_monitoring"],"x-recommended":true,"description":"RECOMMENDED: Source of link site infringement evidence"},"infringing_url":{"type":"string","format":"uri","description":"REQUIRED: URL to the page containing infringing links"},"site_name":{"type":"string","maxLength":200,"description":"REQUIRED: Name of the link aggregation site","examples":["The Pirate Bay","1337x","RARBG","Torrentz2","ExtraTorrent","KickassTorrents"]},"site_category":{"type":"string","enum":["torrent_index","direct_download_links","streaming_links","usenet_index","search_engine","forum_links","other"],"x-recommended":true,"description":"RECOMMENDED: Category of link aggregation site"},"link_info":{"type":"object","properties":{"page_title":{"type":"string","maxLength":500,"description":"OPTIONAL: Title of the page containing links"},"posting_date":{"type":"string","format":"date-time","description":"OPTIONAL: When links were posted"},"uploader":{"type":"string","maxLength":200,"description":"OPTIONAL: Username who posted the links"},"download_count":{"type":"integer","minimum":0,"description":"OPTIONAL: Number of downloads/views"},"link_count":{"type":"integer","minimum":1,"description":"OPTIONAL: Number of infringing links on the page"},"comments_count":{"type":"integer","minimum":0,"description":"OPTIONAL: Number of user comments"}},"additionalProperties":false,"x-recommended":true,"description":"RECOMMENDED: Information about the link page"},"linked_content":{"type":"array","items":{"type":"object","properties":{"target_url":{"type":"string","format":"uri","description":"REQUIRED: URL that the link points to"},"link_type":{"type":"string","enum":["torrent_file","magnet_link","direct_download","streaming_link","usenet_nzb","other"],"description":"REQUIRED: Type of link"},"hosting_service":{"type":"string","maxLength":200,"description":"OPTIONAL: Service hosting the linked content"},"file_size":{"type":"integer","minimum":0,"description":"OPTIONAL: Size of linked file in bytes"}},"required":["target_url","link_type"],"additionalProperties":false},"maxItems":50,"x-recommended":true,"description":"RECOMMENDED: Details about the linked infringing content"},"work_title":{"type":"string","maxLength":500,"x-recommended":true,"description":"RECOMMENDED: Title of the copyrighted work"},"rights_holder":{"type":"string","maxLength":200,"x-recommended":true,"description":"RECOMMENDED: Organization or person holding the copyright"},"work_category":{"type":"string","enum":["movie","tv_show","music","software","ebook","audiobook","game","adult_content","other"],"x-recommended":true,"description":"RECOMMENDED: Category of copyrighted work"},"search_terms":{"type":"array","items":{"type":"string","maxLength":200},"maxItems":10,"description":"OPTIONAL: Search terms used to find the infringing links"},"site_ranking":{"type":"object","properties":{"alexa_rank":{"type":"integer","minimum":1,"description":"OPTIONAL: Alexa traffic ranking of the site"},"popularity_score":{"type":"number","minimum":0,"maximum":10,"description":"OPTIONAL: Popularity score (0.0-10.0)"}},"additionalProperties":false,"description":"OPTIONAL: Site ranking information"}},"required":["infringing_url","site_name"]}],"examples":[{"xarf_version":"4.2.0","report_id":"012c3456-d789-01e2-f345-678901234567","timestamp":"2024-01-15T20:10:30Z","reporter":{"org":"Digital Rights Enforcement","contact":"enforcement@digital-rights.example","domain":"digital-rights.example"},"sender":{"org":"Digital Rights Enforcement","contact":"enforcement@digital-rights.example","domain":"digital-rights.example"},"source_identifier":"torrent-indexer.example","category":"copyright","type":"link_site","infringing_url":"https://torrentindex.example/torrent/12345/blockbuster-movie-2024","site_name":"TorrentIndex","site_category":"torrent_index","work_title":"Blockbuster Movie 2024","rights_holder":"Hollywood Studios","work_category":"movie","evidence_source":"automated_crawl","link_info":{"page_title":"Blockbuster Movie 2024 1080p BluRay x264","posting_date":"2024-01-14T22:30:00Z","uploader":"movieuploader123","download_count":2500,"link_count":3},"linked_content":[{"target_url":"magnet:?xt=urn:btih:da39a3ee5e6b4b0d3255bfef95601890afd80709","link_type":"magnet_link","hosting_service":"BitTorrent DHT"},{"target_url":"https://filehost.example/download/abc123","link_type":"direct_download","hosting_service":"FileHost Service","file_size":4294967296}],"search_terms":["Blockbuster Movie 2024","1080p BluRay"],"evidence":[{"content_type":"text/html","description":"Screenshot of torrent page showing copyrighted content links","payload":"PGh0bWw+PGhlYWQ+PHRpdGxlPkJsb2NrYnVzdGVyIE1vdmllIDIwMjQ8L3RpdGxlPjwvaGVhZD48L2h0bWw+"}],"tags":["copyright:link_site","site:torrent_index","media:movie"]}]}, + "types/copyright-p2p.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/copyright-p2p.json","title":"XARF v4 Copyright - P2P Type Schema","description":"Schema for peer-to-peer copyright infringement reports","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"copyright"},"type":{"const":"p2p"},"evidence_source":{"type":"string","enum":["automated_crawl","manual_monitoring","user_report","rights_holder","watermark_detection"],"x-recommended":true,"description":"RECOMMENDED: Source of P2P infringement evidence"},"p2p_protocol":{"type":"string","enum":["bittorrent","edonkey","gnutella","kademlia","other"],"description":"REQUIRED: P2P protocol used for infringement"},"swarm_info":{"type":"object","properties":{"info_hash":{"type":"string","pattern":"^[a-fA-F0-9]{40}$","description":"RECOMMENDED: BitTorrent info hash (SHA-1)"},"magnet_uri":{"type":"string","pattern":"^magnet:\\?xt=urn:","description":"RECOMMENDED: Magnet link for the infringing content"},"torrent_name":{"type":"string","maxLength":500,"description":"OPTIONAL: Name of the torrent"},"file_count":{"type":"integer","minimum":1,"description":"OPTIONAL: Number of files in the torrent"},"total_size":{"type":"integer","minimum":0,"description":"OPTIONAL: Total size in bytes"}},"additionalProperties":false,"x-recommended":true,"description":"RECOMMENDED: Swarm information (info_hash or magnet_uri required)"},"peer_info":{"type":"object","properties":{"peer_id":{"type":"string","maxLength":100,"description":"OPTIONAL: P2P client peer ID"},"client_version":{"type":"string","maxLength":100,"description":"OPTIONAL: P2P client software and version"},"upload_amount":{"type":"integer","minimum":0,"description":"OPTIONAL: Amount uploaded in bytes"},"download_amount":{"type":"integer","minimum":0,"description":"OPTIONAL: Amount downloaded in bytes"}},"additionalProperties":false,"description":"OPTIONAL: Peer information"},"work_title":{"type":"string","maxLength":500,"x-recommended":true,"description":"RECOMMENDED: Title of the copyrighted work"},"rights_holder":{"type":"string","maxLength":200,"x-recommended":true,"description":"RECOMMENDED: Organization or person holding the copyright"},"work_category":{"type":"string","enum":["movie","tv_show","music","software","ebook","audiobook","game","other"],"x-recommended":true,"description":"RECOMMENDED: Category of copyrighted work"},"release_date":{"type":"string","format":"date","description":"OPTIONAL: Official release date of the work"},"detection_method":{"type":"string","enum":["automated_crawl","fingerprinting","metadata_match","manual_verification"],"description":"OPTIONAL: Method used to detect the infringement"}},"required":["p2p_protocol"],"anyOf":[{"required":["swarm_info"],"properties":{"swarm_info":{"anyOf":[{"required":["info_hash"]},{"required":["magnet_uri"]}]}}}]}],"examples":[{"xarf_version":"4.2.0","report_id":"789a1234-b567-89c0-d123-456789abcdef","timestamp":"2024-01-15T18:30:45Z","reporter":{"org":"Content Protection Agency","contact":"reports@content-protection.example","domain":"content-protection.example"},"sender":{"org":"Content Protection Agency","contact":"reports@content-protection.example","domain":"content-protection.example"},"source_identifier":"203.0.113.150","source_port":6881,"category":"copyright","type":"p2p","p2p_protocol":"bittorrent","work_title":"Movie Title 2024","rights_holder":"Major Studio Inc","work_category":"movie","evidence_source":"automated_crawl","swarm_info":{"info_hash":"da39a3ee5e6b4b0d3255bfef95601890afd80709","torrent_name":"Movie.Title.2024.1080p.BluRay.x264","file_count":1,"total_size":8589934592},"peer_info":{"client_version":"uTorrent/3.5.5","upload_amount":1073741824},"evidence":[{"content_type":"application/x-bittorrent","description":"Torrent file containing copyrighted content","payload":"ZDg6YW5ub3VuY2UyNzpodHRwOi8vdHJhY2tlci5leGFtcGxlLmNvbS9hbm5vdW5jZQ=="}],"tags":["copyright:p2p","protocol:bittorrent","media:movie"]}]}, + "types/copyright-ugc-platform.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/copyright-ugc-platform.json","title":"XARF v4 Copyright - UGC Platform Type Schema","description":"Schema for user-generated content platform copyright infringement reports","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"copyright"},"type":{"const":"ugc_platform"},"evidence_source":{"type":"string","enum":["automated_detection","user_report","rights_holder","content_id_match","fingerprint_match","manual_review"],"x-recommended":true,"description":"RECOMMENDED: Source of UGC platform infringement evidence"},"infringing_url":{"type":"string","format":"uri","description":"REQUIRED: URL to the infringing content on the platform"},"platform_name":{"type":"string","maxLength":200,"description":"REQUIRED: Name of the UGC platform","examples":["YouTube","TikTok","Instagram","Twitter","Facebook","Vimeo","Twitch","SoundCloud"]},"content_info":{"type":"object","properties":{"content_id":{"type":"string","maxLength":200,"description":"OPTIONAL: Platform-specific content identifier"},"content_title":{"type":"string","maxLength":500,"description":"OPTIONAL: Title of the infringing content"},"content_description":{"type":"string","maxLength":2000,"description":"OPTIONAL: Description of the infringing content"},"upload_date":{"type":"string","format":"date-time","description":"OPTIONAL: When content was uploaded to platform"},"content_duration":{"type":"integer","minimum":0,"description":"OPTIONAL: Duration in seconds for video/audio content"},"view_count":{"type":"integer","minimum":0,"description":"OPTIONAL: Number of views/plays"},"like_count":{"type":"integer","minimum":0,"description":"OPTIONAL: Number of likes/upvotes"}},"additionalProperties":false,"x-recommended":true,"description":"RECOMMENDED: Information about the infringing content"},"uploader_info":{"type":"object","properties":{"username":{"type":"string","maxLength":200,"description":"OPTIONAL: Username of the content uploader"},"user_id":{"type":"string","maxLength":100,"description":"OPTIONAL: Platform-specific user identifier"},"account_verified":{"type":"boolean","description":"OPTIONAL: Whether uploader account is verified"},"subscriber_count":{"type":"integer","minimum":0,"description":"OPTIONAL: Number of subscribers/followers"},"account_creation_date":{"type":"string","format":"date-time","description":"OPTIONAL: When uploader account was created"}},"additionalProperties":false,"x-recommended":true,"description":"RECOMMENDED: Information about the uploader"},"work_title":{"type":"string","maxLength":500,"x-recommended":true,"description":"RECOMMENDED: Title of the copyrighted work"},"rights_holder":{"type":"string","maxLength":200,"x-recommended":true,"description":"RECOMMENDED: Organization or person holding the copyright"},"work_category":{"type":"string","enum":["movie","tv_show","music","music_video","audiobook","podcast","live_performance","sports_event","documentary","other"],"x-recommended":true,"description":"RECOMMENDED: Category of copyrighted work"},"infringement_type":{"type":"string","enum":["full_work","substantial_portion","compilation","remix_unauthorized","background_music","clip_mashup"],"x-recommended":true,"description":"RECOMMENDED: Type of copyright infringement"},"match_details":{"type":"object","properties":{"match_confidence":{"type":"number","minimum":0,"maximum":1,"description":"OPTIONAL: Confidence level of content match (0.0-1.0)"},"match_duration":{"type":"integer","minimum":0,"description":"OPTIONAL: Duration of matching content in seconds"},"match_percentage":{"type":"number","minimum":0,"maximum":100,"description":"OPTIONAL: Percentage of original work that matches"},"reference_id":{"type":"string","maxLength":200,"description":"OPTIONAL: Reference ID from content identification system"}},"additionalProperties":false,"x-recommended":true,"description":"RECOMMENDED: Content match details"},"monetization_info":{"type":"object","properties":{"monetized":{"type":"boolean","description":"OPTIONAL: Whether infringing content is monetized"},"ad_revenue":{"type":"boolean","description":"OPTIONAL: Whether content generates ad revenue"},"premium_content":{"type":"boolean","description":"OPTIONAL: Whether content is behind paywall"}},"additionalProperties":false,"description":"OPTIONAL: Monetization information"}},"required":["infringing_url","platform_name"]}],"examples":[{"xarf_version":"4.2.0","report_id":"789c0123-d456-78e9-f012-345678901234","timestamp":"2024-01-15T13:45:15Z","reporter":{"org":"Music Rights Management","contact":"copyright@music-rights.example","domain":"music-rights.example"},"sender":{"org":"Music Rights Management","contact":"copyright@music-rights.example","domain":"music-rights.example"},"source_identifier":"video-platform.example","category":"copyright","type":"ugc_platform","infringing_url":"https://platform.example/watch?v=abc123def456","platform_name":"VideoShare","work_title":"Hit Song 2024","rights_holder":"Record Label Inc","work_category":"music","evidence_source":"content_id_match","infringement_type":"background_music","content_info":{"content_id":"vid_abc123def456","content_title":"My Vacation Highlights","upload_date":"2024-01-14T16:20:00Z","content_duration":180,"view_count":15000},"uploader_info":{"username":"travelblogger2024","account_verified":false,"subscriber_count":1200},"match_details":{"match_confidence":0.95,"match_duration":45,"match_percentage":25,"reference_id":"ref_hit_song_2024_001"},"monetization_info":{"monetized":true,"ad_revenue":true},"evidence":[{"content_type":"application/json","description":"Content ID match report with timestamps","payload":"eyJtYXRjaF9kZXRhaWxzIjogeyJzdGFydF90aW1lIjogNjAsICJlbmRfdGltZSI6IDEwNX19"}],"tags":["copyright:ugc","platform:video","media:music","type:background"]}]}, + "types/copyright-usenet.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/copyright-usenet.json","title":"XARF v4 Copyright - Usenet Type Schema","description":"Schema for Usenet newsgroup copyright infringement reports","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"copyright"},"type":{"const":"usenet"},"evidence_source":{"type":"string","enum":["automated_monitoring","newsgroup_crawl","user_report","rights_holder","nzb_index_monitoring"],"x-recommended":true,"description":"RECOMMENDED: Source of Usenet infringement evidence"},"newsgroup":{"type":"string","maxLength":200,"description":"REQUIRED: Name of the newsgroup containing infringing content","examples":["alt.binaries.movies.divx","alt.binaries.tv","alt.binaries.sounds.mp3","alt.binaries.games","alt.binaries.multimedia"]},"message_info":{"type":"object","properties":{"message_id":{"type":"string","maxLength":500,"description":"RECOMMENDED: Usenet Message-ID header"},"subject":{"type":"string","maxLength":500,"description":"OPTIONAL: Subject line of the post"},"from_header":{"type":"string","maxLength":200,"description":"OPTIONAL: From header of the post"},"posting_date":{"type":"string","format":"date-time","description":"OPTIONAL: When message was posted to newsgroup"},"part_number":{"type":"integer","minimum":1,"description":"OPTIONAL: Part number if multi-part post"},"total_parts":{"type":"integer","minimum":1,"description":"OPTIONAL: Total number of parts in posting"},"file_size":{"type":"integer","minimum":0,"description":"OPTIONAL: Size of the posted file in bytes"}},"additionalProperties":false,"x-recommended":true,"description":"RECOMMENDED: Message information (message_id required)"},"nzb_info":{"type":"object","properties":{"nzb_name":{"type":"string","maxLength":500,"description":"OPTIONAL: Name of the NZB file"},"nzb_url":{"type":"string","format":"uri","description":"OPTIONAL: URL to NZB file on indexing site"},"indexer_site":{"type":"string","maxLength":200,"description":"OPTIONAL: NZB indexing site name"},"completion_percentage":{"type":"number","minimum":0,"maximum":100,"description":"OPTIONAL: Completion percentage of the post"}},"additionalProperties":false,"description":"OPTIONAL: NZB file information"},"server_info":{"type":"object","properties":{"nntp_server":{"type":"string","maxLength":200,"description":"OPTIONAL: NNTP server hostname"},"server_group":{"type":"string","maxLength":200,"description":"OPTIONAL: News server provider group"},"retention_days":{"type":"integer","minimum":1,"description":"OPTIONAL: Server retention period in days"}},"additionalProperties":false,"description":"OPTIONAL: Server information"},"work_title":{"type":"string","maxLength":500,"x-recommended":true,"description":"RECOMMENDED: Title of the copyrighted work"},"rights_holder":{"type":"string","maxLength":200,"x-recommended":true,"description":"RECOMMENDED: Organization or person holding the copyright"},"work_category":{"type":"string","enum":["movie","tv_show","music","software","ebook","audiobook","magazine","game","adult_content","other"],"x-recommended":true,"description":"RECOMMENDED: Category of copyrighted work"},"encoding_info":{"type":"object","properties":{"encoding_format":{"type":"string","enum":["yenc","uuencode","base64","other"],"description":"OPTIONAL: Binary encoding format used"},"par2_recovery":{"type":"boolean","description":"OPTIONAL: Whether PAR2 recovery files are included"},"rar_compression":{"type":"boolean","description":"OPTIONAL: Whether content is RAR compressed"}},"additionalProperties":false,"description":"OPTIONAL: Encoding information"},"detection_method":{"type":"string","enum":["subject_line_match","header_analysis","content_sampling","nzb_metadata"],"description":"OPTIONAL: Method used to detect infringement"}},"required":["newsgroup"],"anyOf":[{"required":["message_info"],"properties":{"message_info":{"required":["message_id"]}}}]}],"examples":[{"xarf_version":"4.2.0","report_id":"345d6789-e012-34f5-a678-901234567890","timestamp":"2024-01-15T07:20:45Z","reporter":{"org":"Usenet Monitoring Service","contact":"reports@usenet-monitor.example","domain":"usenet-monitor.example"},"sender":{"org":"Usenet Monitoring Service","contact":"reports@usenet-monitor.example","domain":"usenet-monitor.example"},"source_identifier":"news.example-provider.com","category":"copyright","type":"usenet","newsgroup":"alt.binaries.movies.divx","work_title":"Latest Movie 2024","rights_holder":"Film Distribution Corp","work_category":"movie","evidence_source":"automated_monitoring","message_info":{"message_id":"","subject":"[1/50] Latest.Movie.2024.1080p.BluRay.x264 - File 01 of 50","from_header":"movieposter@anon.example.com (MoviePoster)","posting_date":"2024-01-14T05:30:00Z","part_number":1,"total_parts":50,"file_size":4294967296},"nzb_info":{"nzb_name":"Latest Movie 2024 1080p BluRay x264.nzb","indexer_site":"NZB Indexer Pro","completion_percentage":100},"server_info":{"nntp_server":"news.example-provider.com","retention_days":3000},"encoding_info":{"encoding_format":"yenc","par2_recovery":true,"rar_compression":true},"detection_method":"subject_line_match","evidence":[{"content_type":"message/rfc822","description":"Usenet post headers showing copyrighted movie","payload":"TWVzc2FnZS1JRDogPGFiYzEyM2RlZjQ1NkBuZXdzLnByb3ZpZGVyLmNvbT4="}],"tags":["copyright:usenet","newsgroup:movies","media:movie"]}]}, + "types/infrastructure-botnet.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/infrastructure-botnet.json","title":"XARF v4 Infrastructure - Botnet Type Schema","description":"Schema for Botnet infections and compromised systems","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"infrastructure"},"type":{"const":"botnet"},"malware_family":{"type":"string","maxLength":200,"x-recommended":true,"description":"RECOMMENDED: Malware family classification","examples":["conficker","mirai","emotet","zeus"]},"c2_server":{"type":"string","x-recommended":true,"description":"RECOMMENDED: Command and control server domain or IP","examples":["evil-c2.example.com","192.0.2.100"]},"c2_protocol":{"type":"string","enum":["http","https","tcp","udp","dns","irc","p2p","custom"],"x-recommended":true,"description":"RECOMMENDED: Protocol used for C2 communications"},"bot_capabilities":{"type":"array","items":{"type":"string","enum":["ddos","spam","proxy","keylogger","file_download","remote_shell","cryptocurrency_mining","data_theft"]},"x-recommended":true,"description":"RECOMMENDED: Capabilities observed in the bot"},"compromise_evidence":{"type":"string","description":"REQUIRED: Evidence of how compromise was detected","examples":["C2 communication observed","Malicious process running","Suspicious network traffic patterns"]}},"required":["compromise_evidence"]}]}, + "types/infrastructure-compromised-server.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/infrastructure-compromised-server.json","title":"XARF v4 Infrastructure - Compromised Server Type Schema","description":"Schema for Compromised servers and infrastructure","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"infrastructure"},"type":{"const":"compromised_server"},"compromise_method":{"type":"string","description":"REQUIRED: Method used to compromise the server"}},"required":["compromise_method"]}]}, + "types/messaging-bulk-messaging.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/messaging-bulk-messaging.json","title":"XARF v4 Messaging - Bulk Messaging Type Schema","description":"Schema for bulk messaging reports - legitimate but unwanted bulk communications","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"messaging"},"type":{"const":"bulk_messaging"},"evidence_source":{"type":"string","enum":["user_complaint","automated_filter","reputation_feed","volume_analysis"],"x-recommended":true,"description":"RECOMMENDED: Source of bulk messaging evidence"},"protocol":{"type":"string","enum":["smtp","sms","whatsapp","telegram","social_media","push_notification","other"],"description":"REQUIRED: Communication protocol used for bulk messaging"},"smtp_from":{"type":"string","format":"email","description":"REQUIRED: SMTP envelope sender address (required when protocol=smtp)"},"subject":{"type":"string","maxLength":500,"x-recommended":true,"description":"RECOMMENDED: Message subject line"},"sender_name":{"type":"string","maxLength":200,"description":"OPTIONAL: Display name of the sender"},"recipient_count":{"type":"integer","minimum":100,"description":"REQUIRED: Number of recipients (bulk requires minimum 100 recipients)"},"unsubscribe_provided":{"type":"boolean","x-recommended":true,"description":"RECOMMENDED: Whether message provides unsubscribe mechanism"},"opt_in_evidence":{"type":"boolean","description":"OPTIONAL: Whether there is evidence of recipient opt-in"},"bulk_indicators":{"type":"object","properties":{"high_volume":{"type":"boolean","description":"OPTIONAL: High volume sending pattern detected"},"template_based":{"type":"boolean","description":"OPTIONAL: Message appears to be template-based"},"commercial_sender":{"type":"boolean","description":"OPTIONAL: Sender appears to be commercial entity"}},"description":"OPTIONAL: Indicators specific to bulk messaging detection","additionalProperties":false}},"required":["protocol","recipient_count"],"if":{"properties":{"protocol":{"const":"smtp"}}},"then":{"required":["smtp_from","source_port"]}}],"examples":[{"xarf_version":"4.2.0","report_id":"456e7890-a12b-34c5-d678-901234567890","timestamp":"2024-01-15T16:45:10Z","reporter":{"org":"Email Service Provider","contact":"abuse@email-provider.example","domain":"email-provider.example"},"sender":{"org":"Email Service Provider","contact":"abuse@email-provider.example","domain":"email-provider.example"},"source_identifier":"192.0.2.200","category":"messaging","type":"bulk_messaging","protocol":"smtp","smtp_from":"newsletter@company.example","subject":"Weekly Newsletter - January Edition","evidence_source":"user_complaint","recipient_count":50000,"unsubscribe_provided":false,"tags":["bulk:commercial","complaint:unsubscribe"]}]}, + "types/messaging-spam.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/messaging-spam.json","title":"XARF v4 Messaging - Spam Type Schema","description":"Schema for spam email reports - unsolicited commercial messages","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"messaging"},"type":{"const":"spam"},"evidence_source":{"type":"string","enum":["spamtrap","user_complaint","automated_filter","honeypot","content_analysis","reputation_feed"],"x-recommended":true,"description":"RECOMMENDED: Source of spam evidence"},"protocol":{"type":"string","enum":["smtp","sms","whatsapp","telegram","signal","chat","social_media","push_notification","other"],"description":"REQUIRED: Communication protocol used for spam delivery"},"smtp_from":{"type":"string","format":"email","description":"REQUIRED: SMTP envelope sender address (required when protocol=smtp)","examples":["spam@example.com","noreply@malicious-domain.example"]},"smtp_to":{"type":"string","format":"email","x-recommended":true,"description":"RECOMMENDED: SMTP envelope recipient address","examples":["victim@example.org","spamtrap@spamtrap.example"]},"subject":{"type":"string","maxLength":500,"x-recommended":true,"description":"RECOMMENDED: Message subject line","examples":["Urgent: Account Verification Required","Your package is ready for delivery","Limited Time Offer - Act Now!"]},"sender_name":{"type":"string","maxLength":200,"description":"OPTIONAL: Display name of the sender","examples":["Customer Support","No Reply","Sales Team"]},"message_id":{"type":"string","maxLength":200,"x-recommended":true,"description":"RECOMMENDED: Message ID from headers - helps with deduplication","examples":["","msg_1234567890"]},"user_agent":{"type":"string","maxLength":200,"description":"OPTIONAL: User agent string from message headers","examples":["Outlook 16.0","bulk_mailer_v2.1"]},"recipient_count":{"type":"integer","minimum":1,"description":"OPTIONAL: Number of recipients for bulk spam campaigns"},"language":{"type":"string","pattern":"^[a-z]{2}(-[A-Z]{2})?$","description":"OPTIONAL: Primary language of message content (ISO 639-1)","examples":["en","es","de","ja","en-US"]},"spam_indicators":{"type":"object","properties":{"suspicious_links":{"type":"array","items":{"type":"string","format":"uri"},"description":"OPTIONAL: Suspicious URLs found in the message"},"commercial_content":{"type":"boolean","description":"OPTIONAL: Whether message contains commercial offers"},"bulk_characteristics":{"type":"boolean","description":"OPTIONAL: Whether message shows bulk mailing characteristics"}},"description":"OPTIONAL: Indicators specific to spam detection","additionalProperties":false}},"required":["protocol"],"if":{"properties":{"protocol":{"const":"smtp"}}},"then":{"required":["smtp_from","source_port"]}}],"examples":[{"xarf_version":"4.2.0","report_id":"123e4567-e89b-12d3-a456-426614174000","timestamp":"2024-01-15T14:30:25Z","reporter":{"org":"Spam Reporting Service","contact":"reports@spam-reports.example","domain":"spam-reports.example"},"sender":{"org":"Spam Reporting Service","contact":"reports@spam-reports.example","domain":"spam-reports.example"},"source_identifier":"192.0.2.123","source_port":25,"category":"messaging","type":"spam","protocol":"smtp","smtp_from":"fake@example.com","subject":"Urgent: Verify Your Account","evidence_source":"spamtrap","evidence":[{"content_type":"message/rfc822","description":"Complete spam email with headers","payload":"UmVjZWl2ZWQ6IGZyb20gW3NwYW1tZXIuZXhhbXBsZS5jb21d..."}],"tags":["spam:commercial","campaign:fake_bank_2024"],"confidence":0.92}]}, + "types/reputation-blocklist.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/reputation-blocklist.json","title":"XARF v4 Reputation - Blocklist Type Schema","description":"Schema for IP/domain blocklist inclusion reports","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"reputation"},"type":{"const":"blocklist"},"threat_type":{"type":"string","description":"REQUIRED: Type of threat for blocklist inclusion"}},"required":["threat_type"]}]}, + "types/reputation-threat-intelligence.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/reputation-threat-intelligence.json","title":"XARF v4 Reputation - Threat Intelligence Type Schema","description":"Schema for Threat intelligence and IOC reports","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"reputation"},"type":{"const":"threat_intelligence"},"threat_type":{"type":"string","description":"REQUIRED: Type of threat for intelligence report"}},"required":["threat_type"]}]}, + "types/vulnerability-cve.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/vulnerability-cve.json","title":"XARF v4 Vulnerability - CVE Type Schema","description":"Schema for Common Vulnerabilities and Exposures (CVE) reports","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"vulnerability"},"type":{"const":"cve"},"evidence_source":{"type":"string","enum":["vulnerability_scan","researcher_analysis","automated_discovery","penetration_testing"],"x-recommended":true,"description":"RECOMMENDED: Source of CVE vulnerability evidence"},"service":{"type":"string","maxLength":200,"description":"REQUIRED: Vulnerable service or application name","examples":["Apache HTTP Server","OpenSSL","Windows SMB","SSH Server","MySQL Database"]},"service_version":{"type":"string","maxLength":100,"x-recommended":true,"description":"RECOMMENDED: Version of the vulnerable service","examples":["2.4.41","1.1.1a","OpenSSH_7.4","5.7.33"]},"service_port":{"type":"integer","minimum":1,"maximum":65535,"description":"REQUIRED: Port number where vulnerable service is running","examples":[80,443,22,3389,21,23,25]},"cve_id":{"type":"string","pattern":"^CVE-[0-9]{4}-[0-9]+$","description":"REQUIRED: CVE identifier","examples":["CVE-2021-44228","CVE-2014-0160","CVE-2017-5638"]},"cve_ids":{"type":"array","items":{"type":"string","pattern":"^CVE-[0-9]{4}-[0-9]+$"},"description":"OPTIONAL: Multiple CVE identifiers if vulnerability involves multiple CVEs","maxItems":10,"uniqueItems":true},"cvss_score":{"type":"number","minimum":0,"maximum":10,"x-recommended":true,"description":"RECOMMENDED: CVSS vulnerability score (0.0-10.0)"},"cvss_vector":{"type":"string","pattern":"^CVSS:3\\.[01]/.*","description":"OPTIONAL: CVSS v3 vector string","examples":["CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"]},"cvss_version":{"type":"string","enum":["2.0","3.0","3.1"],"description":"OPTIONAL: CVSS version used for scoring"},"risk_level":{"type":"string","enum":["info","low","medium","high","critical"],"x-recommended":true,"description":"RECOMMENDED: Risk assessment level"},"severity":{"type":"string","enum":["informational","low","medium","high","critical"],"x-recommended":true,"description":"RECOMMENDED: Severity classification"},"exploitability":{"type":"string","enum":["theoretical","poc_available","functional","weaponized"],"x-recommended":true,"description":"RECOMMENDED: Level of exploit availability and maturity"},"patch_available":{"type":"boolean","x-recommended":true,"description":"RECOMMENDED: Whether a patch or fix is available"},"patch_version":{"type":"string","maxLength":100,"description":"OPTIONAL: Version that fixes the vulnerability","examples":["2.4.46","1.1.1k","OpenSSH_8.0","5.7.35"]},"patch_url":{"type":"string","format":"uri","description":"OPTIONAL: URL to patch, security advisory, or fix information"},"vendor_advisory":{"type":"string","format":"uri","description":"OPTIONAL: URL to vendor security advisory"},"disclosure_date":{"type":"string","format":"date-time","description":"OPTIONAL: When CVE was publicly disclosed"},"impact_assessment":{"type":"object","properties":{"confidentiality":{"type":"string","enum":["none","low","high"],"description":"OPTIONAL: Impact on data confidentiality"},"integrity":{"type":"string","enum":["none","low","high"],"description":"OPTIONAL: Impact on data integrity"},"availability":{"type":"string","enum":["none","low","high"],"description":"OPTIONAL: Impact on system availability"}},"description":"OPTIONAL: CIA triad impact assessment","additionalProperties":false},"remediation_priority":{"type":"string","enum":["low","medium","high","critical","emergency"],"description":"OPTIONAL: Recommended priority for remediation"}},"required":["service","service_port","cve_id"]}],"examples":[{"xarf_version":"4.2.0","report_id":"901b2345-c678-90d1-e234-567890123456","timestamp":"2024-01-15T09:20:30Z","reporter":{"org":"Vulnerability Scanner Service","contact":"security@vuln-scanner.example","domain":"vuln-scanner.example"},"sender":{"org":"Vulnerability Scanner Service","contact":"security@vuln-scanner.example","domain":"vuln-scanner.example"},"source_identifier":"203.0.113.75","category":"vulnerability","type":"cve","service":"Apache HTTP Server","service_version":"2.4.41","service_port":80,"cve_id":"CVE-2021-41773","cvss_score":7.5,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N","risk_level":"high","severity":"high","patch_available":true,"patch_version":"2.4.51","evidence_source":"vulnerability_scan","evidence":[{"content_type":"text/plain","description":"Vulnerability scan results showing Apache version","payload":"QXBhY2hlIEhUVFAgU2VydmVyIDIuNC40MSBkZXRlY3RlZCB3aXRoIENWRS0yMDIxLTQxNzcz"}],"tags":["cve:CVE-2021-41773","severity:high","service:apache"]}]}, + "types/vulnerability-misconfiguration.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/vulnerability-misconfiguration.json","title":"XARF v4 Vulnerability - Misconfiguration Type Schema","description":"Schema for Security misconfigurations and hardening issues","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"vulnerability"},"type":{"const":"misconfiguration"},"service":{"type":"string","description":"REQUIRED: Service or component that is misconfigured"}},"required":["service"]}]}, + "types/vulnerability-open-service.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/types/vulnerability-open-service.json","title":"XARF v4 Vulnerability - Open Service Type Schema","description":"Schema for open services that should not be publicly accessible (DNS resolvers, NTP servers, memcached, SSDP) and can be exploited for DDoS amplification or other attacks","allOf":[{"$ref":"../xarf-core.json"},{"type":"object","properties":{"category":{"const":"vulnerability"},"type":{"const":"open_service"},"service":{"type":"string","description":"REQUIRED: Name of the open service"}},"required":["service"]}]}, + "xarf-core.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/xarf-core.json","title":"XARF v4 Core Schema","description":"Base schema defining common fields and structures for all XARF v4 abuse reports","type":"object","required":["xarf_version","report_id","timestamp","reporter","sender","source_identifier","category","type"],"properties":{"xarf_version":{"type":"string","pattern":"^4\\.[0-9]+\\.[0-9]+$","description":"REQUIRED: XARF schema version using semantic versioning (e.g., '4.0.0')","examples":["4.0.0","4.1.2","4.6.1"]},"report_id":{"type":"string","format":"uuid","description":"REQUIRED: Unique report identifier using UUID v4 format","examples":["550e8400-e29b-41d4-a716-446655440000"]},"timestamp":{"type":"string","format":"date-time","description":"REQUIRED: ISO 8601 timestamp when the abuse incident occurred","examples":["2024-01-15T14:30:25Z","2024-01-15T14:30:25.123Z"]},"reporter":{"$ref":"#/$defs/contact_info","description":"REQUIRED: The organization that owns/generated the abuse complaint (the victim or complainant)"},"sender":{"$ref":"#/$defs/contact_info","description":"REQUIRED: The organization that transmitted/filed this report (may be same as reporter or a service provider)"},"source_identifier":{"type":"string","description":"REQUIRED: IP address, domain, or other identifier of the abuse source","examples":["192.0.2.1","2001:db8::1","example.com","abuse-source.example.org"]},"source_port":{"type":"integer","minimum":1,"maximum":65535,"x-recommended":true,"description":"RECOMMENDED: Source port number - critical for identifying sources in CGNAT environments","examples":[25,80,443,3389]},"category":{"type":"string","enum":["messaging","content","copyright","connection","vulnerability","infrastructure","reputation"],"description":"REQUIRED: Primary abuse classification category"},"type":{"type":"string","description":"REQUIRED: Specific abuse type within the category - validation depends on category value","examples":["spam","phishing","ddos","port_scan","bot","blocklist"]},"evidence_source":{"type":"string","x-recommended":true,"description":"RECOMMENDED: Quality and reliability indicator for the evidence provided","examples":["spamtrap","user_complaint","automated_filter","honeypot","crawler","user_report","automated_scan","spam_analysis","firewall_logs","ids_detection","flow_analysis","vulnerability_scan","researcher_analysis","automated_discovery","traffic_analysis","threat_intelligence"]},"evidence":{"type":"array","items":{"$ref":"#/$defs/evidence_item"},"x-recommended":true,"description":"RECOMMENDED: Array of evidence items supporting this abuse report","maxItems":50},"tags":{"type":"array","items":{"type":"string","pattern":"^[a-z0-9][a-z0-9_+-]*:[a-z0-9][a-z0-9_+-]*$","description":"OPTIONAL: Namespaced tag in format 'namespace:predicate'"},"description":"OPTIONAL: Namespaced tags for categorization, correlation, and automation","examples":[["malware:conficker","campaign:winter-2024"],["botnet:command-and-control","malware:cobalt-strike"],["language:c++","attack:syn-flood"]],"maxItems":20},"confidence":{"type":"number","minimum":0,"maximum":1,"x-recommended":true,"description":"RECOMMENDED: Confidence score for this report (0.0 = no confidence, 1.0 = complete confidence)","examples":[0.85,0.95,1]},"description":{"type":"string","maxLength":1000,"description":"OPTIONAL: Human-readable description of the abuse incident","examples":["Spam email campaign targeting financial institutions","DDoS attack against web services using SYN flood technique"]},"legacy_version":{"type":"string","enum":["3"],"description":"OPTIONAL: Original XARF version if this report was converted from v3 format"},"_internal":{"$ref":"#/$defs/internal_metadata","description":"OPTIONAL: Internal operational metadata - NEVER transmitted between systems"}},"additionalProperties":true,"$defs":{"contact_info":{"type":"object","required":["org","contact","domain"],"properties":{"org":{"type":"string","maxLength":200,"description":"REQUIRED: Organization name","examples":["Acme Corporation","Abusix","Security Research Lab"]},"contact":{"type":"string","format":"email","description":"REQUIRED: Contact email address","examples":["abuse@example.com","reports@abusix.com"]},"domain":{"type":"string","format":"hostname","description":"REQUIRED: Organization domain for verification","examples":["example.com","abusix.com","security-lab.org"]}},"additionalProperties":false},"evidence_item":{"type":"object","required":["content_type","payload"],"properties":{"content_type":{"type":"string","description":"REQUIRED: MIME type of the evidence content","examples":["text/plain","text/csv","application/json","message/rfc822","text/email","image/png","image/jpeg","image/gif","application/pdf","text/html","application/octet-stream","application/zip"]},"description":{"type":"string","maxLength":500,"x-recommended":true,"description":"RECOMMENDED: Human-readable description of this evidence item","examples":["Original spam email with headers","Screenshot of phishing page","Network flow analysis logs"]},"payload":{"type":"string","description":"REQUIRED: Base64-encoded evidence data","contentEncoding":"base64"},"hash":{"type":"string","pattern":"^(md5|sha1|sha256|sha512):[a-fA-F0-9]+$","x-recommended":true,"description":"RECOMMENDED: Hash of evidence for integrity verification in format 'algorithm:hexvalue'","examples":["sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","md5:d41d8cd98f00b204e9800998ecf8427e"]},"size":{"type":"integer","minimum":0,"maximum":5242880,"description":"OPTIONAL: Size of evidence in bytes (max 5MB per item)"}},"additionalProperties":false},"internal_metadata":{"type":"object","description":"OPTIONAL: Internal operational metadata - completely flexible, organization-defined structure. NEVER transmitted between systems.","additionalProperties":true,"examples":[{"ticket":"ABUSE-1234","analyst":"john.doe","priority":"high"},{"threat_id":"THR-2024-001","ml_confidence":0.94,"campaign_cluster":"winter_2024_phishing"}]}}}, + "xarf-v4-master.json": {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://xarf.org/schemas/v4/xarf-v4-master.json","title":"XARF v4 Master Schema","description":"Complete XARF v4 schema with type-specific validation for all categories and event types. This provides granular validation for each specific abuse type.","type":"object","allOf":[{"$ref":"xarf-core.json"},{"description":"Valid category and type combinations. Rejects unknown types that do not match any defined category+type pair.","anyOf":[{"properties":{"category":{"const":"messaging"},"type":{"enum":["spam","bulk_messaging"]}}},{"properties":{"category":{"const":"connection"},"type":{"enum":["login_attack","port_scan","ddos","infected_host","reconnaissance","scraping","sql_injection","vulnerability_scan"]}}},{"properties":{"category":{"const":"vulnerability"},"type":{"enum":["cve","open_service","misconfiguration"]}}},{"properties":{"category":{"const":"reputation"},"type":{"enum":["blocklist","threat_intelligence"]}}},{"properties":{"category":{"const":"infrastructure"},"type":{"enum":["botnet","compromised_server"]}}},{"properties":{"category":{"const":"content"},"type":{"enum":["phishing","malware","csam","csem","exposed_data","brand_infringement","fraud","remote_compromise","suspicious_registration"]}}},{"properties":{"category":{"const":"copyright"},"type":{"enum":["copyright","p2p","cyberlocker","ugc_platform","link_site","usenet"]}}}]},{"if":{"properties":{"category":{"const":"messaging"},"type":{"const":"spam"}}},"then":{"$ref":"types/messaging-spam.json"}},{"if":{"properties":{"category":{"const":"messaging"},"type":{"const":"bulk_messaging"}}},"then":{"$ref":"types/messaging-bulk-messaging.json"}},{"if":{"properties":{"category":{"const":"connection"},"type":{"const":"login_attack"}}},"then":{"$ref":"types/connection-login-attack.json"}},{"if":{"properties":{"category":{"const":"connection"},"type":{"const":"port_scan"}}},"then":{"$ref":"types/connection-port-scan.json"}},{"if":{"properties":{"category":{"const":"connection"},"type":{"const":"ddos"}}},"then":{"$ref":"types/connection-ddos.json"}},{"if":{"properties":{"category":{"const":"connection"},"type":{"const":"infected_host"}}},"then":{"$ref":"types/connection-infected-host.json"}},{"if":{"properties":{"category":{"const":"connection"},"type":{"const":"reconnaissance"}}},"then":{"$ref":"types/connection-reconnaissance.json"}},{"if":{"properties":{"category":{"const":"connection"},"type":{"const":"scraping"}}},"then":{"$ref":"types/connection-scraping.json"}},{"if":{"properties":{"category":{"const":"connection"},"type":{"const":"sql_injection"}}},"then":{"$ref":"types/connection-sql-injection.json"}},{"if":{"properties":{"category":{"const":"connection"},"type":{"const":"vulnerability_scan"}}},"then":{"$ref":"types/connection-vulnerability-scan.json"}},{"if":{"properties":{"category":{"const":"vulnerability"},"type":{"const":"cve"}}},"then":{"$ref":"types/vulnerability-cve.json"}},{"if":{"properties":{"category":{"const":"vulnerability"},"type":{"const":"open_service"}}},"then":{"$ref":"types/vulnerability-open-service.json"}},{"if":{"properties":{"category":{"const":"vulnerability"},"type":{"const":"misconfiguration"}}},"then":{"$ref":"types/vulnerability-misconfiguration.json"}},{"if":{"properties":{"category":{"const":"reputation"},"type":{"const":"blocklist"}}},"then":{"$ref":"types/reputation-blocklist.json"}},{"if":{"properties":{"category":{"const":"reputation"},"type":{"const":"threat_intelligence"}}},"then":{"$ref":"types/reputation-threat-intelligence.json"}},{"if":{"properties":{"category":{"const":"infrastructure"},"type":{"const":"botnet"}}},"then":{"$ref":"types/infrastructure-botnet.json"}},{"if":{"properties":{"category":{"const":"infrastructure"},"type":{"const":"compromised_server"}}},"then":{"$ref":"types/infrastructure-compromised-server.json"}},{"if":{"properties":{"category":{"const":"content"},"type":{"const":"phishing"}}},"then":{"$ref":"types/content-phishing.json"}},{"if":{"properties":{"category":{"const":"content"},"type":{"const":"malware"}}},"then":{"$ref":"types/content-malware.json"}},{"if":{"properties":{"category":{"const":"content"},"type":{"const":"csam"}}},"then":{"$ref":"types/content-csam.json"}},{"if":{"properties":{"category":{"const":"content"},"type":{"const":"csem"}}},"then":{"$ref":"types/content-csem.json"}},{"if":{"properties":{"category":{"const":"content"},"type":{"const":"exposed_data"}}},"then":{"$ref":"types/content-exposed-data.json"}},{"if":{"properties":{"category":{"const":"content"},"type":{"const":"brand_infringement"}}},"then":{"$ref":"types/content-brand_infringement.json"}},{"if":{"properties":{"category":{"const":"content"},"type":{"const":"fraud"}}},"then":{"$ref":"types/content-fraud.json"}},{"if":{"properties":{"category":{"const":"content"},"type":{"const":"remote_compromise"}}},"then":{"$ref":"types/content-remote_compromise.json"}},{"if":{"properties":{"category":{"const":"content"},"type":{"const":"suspicious_registration"}}},"then":{"$ref":"types/content-suspicious_registration.json"}},{"if":{"properties":{"category":{"const":"copyright"},"type":{"const":"copyright"}}},"then":{"$ref":"types/copyright-copyright.json"}},{"if":{"properties":{"category":{"const":"copyright"},"type":{"const":"p2p"}}},"then":{"$ref":"types/copyright-p2p.json"}},{"if":{"properties":{"category":{"const":"copyright"},"type":{"const":"cyberlocker"}}},"then":{"$ref":"types/copyright-cyberlocker.json"}},{"if":{"properties":{"category":{"const":"copyright"},"type":{"const":"ugc_platform"}}},"then":{"$ref":"types/copyright-ugc-platform.json"}},{"if":{"properties":{"category":{"const":"copyright"},"type":{"const":"link_site"}}},"then":{"$ref":"types/copyright-link-site.json"}},{"if":{"properties":{"category":{"const":"copyright"},"type":{"const":"usenet"}}},"then":{"$ref":"types/copyright-usenet.json"}}],"additionalProperties":true,"properties":{"xarf_version":{"description":"This schema validates XARF v4 reports using type-specific validation. Supported versions match pattern ^4\\\\.[0-9]+\\\\.[0-9]+$"}},"examples":[{"title":"Messaging Spam Report","category":"messaging","type":"spam","description":"Spam email detected by spamtrap"},{"title":"Connection DDoS Report","category":"connection","type":"ddos","description":"DDoS attack with SYN flood technique"},{"title":"Vulnerability CVE Report","category":"vulnerability","type":"cve","description":"Apache HTTP Server CVE-2021-41773 vulnerability"},{"title":"Content Phishing Report","category":"content","type":"phishing","description":"Phishing website targeting banking credentials"}]}, +}); + +/** The xarf-spec version these bundled schemas were generated from. */ +export const BUNDLED_SPEC_VERSION = "v4.2.0"; diff --git a/src/validator.ts b/src/validator.ts index 80f0a8d..55915fe 100644 --- a/src/validator.ts +++ b/src/validator.ts @@ -7,8 +7,7 @@ import type { XARFReport } from './types'; import { validator as schemaValidator } from './schema-validator'; import { schemaRegistry } from './schema-registry'; -import { findSchemasDir, loadSchemaFile } from './schema-utils'; -import * as path from 'path'; +import { getCoreSchema as getBundledCoreSchema, resolveBaseSchemaRef } from './schema-utils'; /** * Validation result with detailed error information @@ -84,7 +83,6 @@ export class XARFValidator { private warnings: ValidationWarning[] = []; private info: ValidationInfo[] = []; private useSchemaValidation: boolean; - private schemasDir: string; private coreSchemaCache: SchemaDefinition | null = null; /** @@ -93,7 +91,6 @@ export class XARFValidator { */ constructor(useSchemaValidation = true) { this.useSchemaValidation = useSchemaValidation; - this.schemasDir = findSchemasDir(); } /** @@ -104,9 +101,7 @@ export class XARFValidator { if (this.coreSchemaCache) { return this.coreSchemaCache; } - this.coreSchemaCache = loadSchemaFile( - path.join(this.schemasDir, 'xarf-core.json') - ); + this.coreSchemaCache = getBundledCoreSchema() as SchemaDefinition | null; return this.coreSchemaCache; } @@ -150,8 +145,7 @@ export class XARFValidator { if (!ref.includes('-base.json')) { return null; } - const filename = ref.replace(/^\.\//, '').replace(/^\.\.\//, ''); - return loadSchemaFile(path.join(this.schemasDir, 'types', filename)); + return resolveBaseSchemaRef(ref) as SchemaDefinition | null; } /** @@ -249,10 +243,10 @@ export class XARFValidator { /** * Validate a XARF report comprehensively * @param report - The XARF report to validate - * @param strict - If true, warnings are treated as errors + * @param strict - If true, warnings (e.g. unknown fields) are reported as errors * @param showMissingOptional - If true, includes info about missing optional fields - * @returns Validation result with errors, warnings, and optionally info - * @throws {XARFValidationError} If strict mode and validation fails + * @returns Validation result with errors, warnings, and optionally info. Does + * not throw on validation failures — inspect `result.valid`/`result.errors`. */ validate(report: XARFReport, strict = false, showMissingOptional = false): ValidationResult { this.errors = []; @@ -305,7 +299,7 @@ export class XARFValidator { /** * Validate report using JSON schema * @param report - The XARF report to validate - * @param strict + * @param strict - If true, `x-recommended` fields are treated as required * @returns Validation result from schema validation */ validateWithSchema(report: XARFReport, strict = false): ValidationResult { diff --git a/src/version.ts b/src/version.ts index 6cea46b..a7c842e 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,2 +1,7 @@ -import pkg from '../package.json'; -export const SPEC_VERSION = pkg.xarfSpec.version.replace(/^v/, ''); +import { version, xarfSpec } from '../package.json'; + +/** The XARF specification version this library targets (without the leading "v"). */ +export const SPEC_VERSION = xarfSpec.version.replace(/^v/, ''); + +/** The version of this library, derived from package.json. */ +export const VERSION = version; diff --git a/tests/bundle.test.ts b/tests/bundle.test.ts new file mode 100644 index 0000000..f07ad92 --- /dev/null +++ b/tests/bundle.test.ts @@ -0,0 +1,81 @@ +/** + * Tests for bundled schemas and version exports. + */ + +import pkg from '../package.json'; +import { VERSION, SPEC_VERSION, BUNDLED_SPEC_VERSION, schemaRegistry, validator } from '../src'; +import { + getCoreSchema, + getMasterSchema, + listTypeSchemaPaths, + getBundledSchema, + resolveBaseSchemaRef, +} from '../src/schema-utils'; +import { bundledSchemas } from '../src/schemas.generated'; + +describe('version exports', () => { + it('derives VERSION from package.json (no hardcoded drift)', () => { + expect(VERSION).toBe(pkg.version); + }); + + it('exposes SPEC_VERSION without the leading "v"', () => { + expect(SPEC_VERSION).toBe(pkg.xarfSpec.version.replace(/^v/, '')); + expect(SPEC_VERSION).not.toMatch(/^v/); + }); + + it('exposes BUNDLED_SPEC_VERSION matching the configured spec', () => { + expect(BUNDLED_SPEC_VERSION).toBe(pkg.xarfSpec.version); + }); +}); + +describe('bundled schemas', () => { + it('bundles the core and master schemas', () => { + expect(getCoreSchema()).not.toBeNull(); + expect(getMasterSchema()).not.toBeNull(); + expect(getBundledSchema('xarf-core.json')).not.toBeNull(); + }); + + it('returns null for an unknown bundle key', () => { + expect(getBundledSchema('does-not-exist.json')).toBeNull(); + }); + + it('lists only type schema paths', () => { + const paths = listTypeSchemaPaths(); + expect(paths.length).toBeGreaterThan(0); + expect(paths.every((p) => p.startsWith('types/'))).toBe(true); + expect(paths).toContain('types/messaging-spam.json'); + }); + + it('exposes a frozen, non-empty bundle', () => { + expect(Object.isFrozen(bundledSchemas)).toBe(true); + expect(Object.keys(bundledSchemas).length).toBeGreaterThan(30); + }); + + it('resolves relative base-schema refs to the bundled type schema', () => { + expect(resolveBaseSchemaRef('./content-base.json')).not.toBeNull(); + expect(resolveBaseSchemaRef('../content-base.json')).not.toBeNull(); + expect(resolveBaseSchemaRef('content-base.json')).not.toBeNull(); + }); +}); + +describe('registry/validator driven by the bundle', () => { + it('discovers all 7 XARF categories from the bundled core schema', () => { + expect(schemaRegistry.getCategories().size).toBe(7); + }); + + it('exposes supported category/type combinations from the bundle', () => { + const supported = validator.getSupportedTypes(); + expect(supported.length).toBeGreaterThan(0); + expect(supported).toContainEqual({ category: 'messaging', type: 'spam' }); + }); + + it("getAllFieldsForCategory returns a superset of an individual type's fields", () => { + const all = schemaRegistry.getAllFieldsForCategory('content'); + const phishingFields = schemaRegistry.getCategoryFields('content', 'phishing'); + expect(all.size).toBeGreaterThan(0); + expect(phishingFields.length).toBeGreaterThan(0); + for (const field of phishingFields) { + expect(all.has(field)).toBe(true); + } + }); +}); diff --git a/tests/parser.test.ts b/tests/parser.test.ts index 0ed2f12..24e6f5b 100644 --- a/tests/parser.test.ts +++ b/tests/parser.test.ts @@ -274,4 +274,74 @@ describe('parse', () => { expect(warnings.some((w) => w.includes('contentType') || w.includes('unknown'))).toBe(true); }); }); + + describe('maxInputBytes guard', () => { + it('should parse string input within the limit', () => { + const json = JSON.stringify(validMessagingReport); + const { errors } = parse(json, { maxInputBytes: json.length + 100 }); + expect(errors).toHaveLength(0); + }); + + it('should throw XARFParseError when string input exceeds the limit', () => { + const json = JSON.stringify(validMessagingReport); + expect(() => parse(json, { maxInputBytes: 10 })).toThrow(XARFParseError); + expect(() => parse(json, { maxInputBytes: 10 })).toThrow(/maxInputBytes/); + }); + + it('should not apply the limit to already-parsed object input', () => { + // Object inputs are not measured — the guard only protects string parsing. + expect(() => parse(validMessagingReport, { maxInputBytes: 1 })).not.toThrow(); + }); + + it('should impose no limit when maxInputBytes is omitted', () => { + const json = JSON.stringify(validMessagingReport); + expect(() => parse(json)).not.toThrow(); + }); + + it('should count UTF-8 bytes, not characters, for multibyte input', () => { + // A multibyte emoji makes the UTF-8 byte length exceed the UTF-16 length. + const report = { ...validMessagingReport, subject: '😀😀😀' }; + const json = JSON.stringify(report); + const byteLen = Buffer.byteLength(json, 'utf8'); + expect(byteLen).toBeGreaterThan(json.length); + expect(() => parse(json, { maxInputBytes: byteLen - 1 })).toThrow(/maxInputBytes/); + expect(() => parse(json, { maxInputBytes: byteLen })).not.toThrow(); + }); + + it('should enforce the size guard BEFORE JSON.parse (oversized + malformed)', () => { + // The guard's whole purpose is to reject oversized untrusted input without + // parsing it. An oversized malformed payload must fail with maxInputBytes, + // NOT "Invalid JSON" — proving the size check runs first. + const oversizedMalformed = '{' + 'x'.repeat(200); + expect(() => parse(oversizedMalformed, { maxInputBytes: 10 })).toThrow(/maxInputBytes/); + expect(() => parse(oversizedMalformed, { maxInputBytes: 10 })).not.toThrow(/Invalid JSON/); + }); + + it('should use the TextEncoder fallback when Buffer is unavailable (edge runtime)', () => { + const originalBuffer = globalThis.Buffer; + // Simulate a Buffer-less runtime (e.g. an edge/serverless environment). + (globalThis as any).Buffer = undefined; + try { + const json = JSON.stringify(validMessagingReport); + expect(() => parse(json, { maxInputBytes: 5 })).toThrow(/maxInputBytes/); + expect(() => parse(json, { maxInputBytes: json.length + 100 })).not.toThrow(); + } finally { + (globalThis as any).Buffer = originalBuffer; + } + }); + }); + + describe('showMissingOptional info channel', () => { + it('surfaces missing-optional info through parse() when enabled', () => { + const { info } = parse(validMessagingReport, { showMissingOptional: true }); + expect(info).toBeDefined(); + expect(Array.isArray(info)).toBe(true); + expect(info!.length).toBeGreaterThan(0); + }); + + it('omits info when showMissingOptional is not set', () => { + const { info } = parse(validMessagingReport); + expect(info).toBeUndefined(); + }); + }); }); diff --git a/tests/schema-validator-guard.test.ts b/tests/schema-validator-guard.test.ts new file mode 100644 index 0000000..3d8f8c1 --- /dev/null +++ b/tests/schema-validator-guard.test.ts @@ -0,0 +1,42 @@ +/** + * Tests for SchemaValidator's degraded-bundle guards. + * + * The schema engine reads from the in-memory bundle. If codegen ever produces a + * bundle missing the core or master schema, loadMasterSchema() throws and + * validate() surfaces it as a structured error (rather than crashing). These + * guards replaced the old filesystem "file not found" errors, so they need + * their own coverage. + */ + +jest.mock('../src/schema-utils', () => { + const actual = jest.requireActual('../src/schema-utils'); + return { __esModule: true, ...actual }; +}); + +import * as schemaUtils from '../src/schema-utils'; +import { SchemaValidator } from '../src/schema-validator'; +import type { XARFReport } from '../src/types'; + +const report = { category: 'messaging', type: 'spam' } as unknown as XARFReport; + +describe('SchemaValidator missing-bundle guards', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('returns a validation error when the core schema is missing from the bundle', () => { + jest.spyOn(schemaUtils, 'getCoreSchema').mockReturnValue(null); + const validator = new SchemaValidator(); + const result = validator.validate(report); + expect(result.valid).toBe(false); + expect(result.errors.join(' ')).toMatch(/core schema .* missing from the bundle/i); + }); + + it('returns a validation error when the master schema is missing from the bundle', () => { + jest.spyOn(schemaUtils, 'getMasterSchema').mockReturnValue(null); + const validator = new SchemaValidator(); + const result = validator.validate(report); + expect(result.valid).toBe(false); + expect(result.errors.join(' ')).toMatch(/master schema .* missing from the bundle/i); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index c5a17a2..c212705 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,6 @@ { "compilerOptions": { "target": "ES2020", - "module": "commonjs", "lib": ["ES2020"], "outDir": "./dist", "rootDir": "./src", @@ -17,7 +16,8 @@ "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, - "moduleResolution": "node", + "module": "NodeNext", + "moduleResolution": "NodeNext", "types": ["node", "jest"] }, "include": ["src/**/*"], diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..aa01b48 --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'tsup'; + +/** + * Build configuration: emit a dual CommonJS + ESM bundle with type + * declarations. The XARF schemas are inlined via `src/schemas.generated.ts`, + * so the output has no filesystem or network dependency. + */ +export default defineConfig({ + entry: ['src/index.ts'], + format: ['cjs', 'esm'], + dts: true, + sourcemap: true, + clean: true, + target: 'node18', + outDir: 'dist', +});