@gpzhang2001/sharpkit-reporting 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +10 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +182 -31
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +203 -32
- package/src/state.ts +13 -1
package/lib/index.d.ts
CHANGED
|
@@ -97,6 +97,7 @@ var [ReportState] = [
|
|
|
97
97
|
"",
|
|
98
98
|
"",
|
|
99
99
|
"",
|
|
100
|
+
"",
|
|
100
101
|
""
|
|
101
102
|
]
|
|
102
103
|
];
|
|
@@ -440,6 +441,9 @@ var [ReportingHandle] = [
|
|
|
440
441
|
() => [
|
|
441
442
|
ReportState,
|
|
442
443
|
Promise,
|
|
444
|
+
Record,
|
|
445
|
+
Readonly,
|
|
446
|
+
Promise,
|
|
443
447
|
Promise,
|
|
444
448
|
Promise
|
|
445
449
|
],
|
|
@@ -459,6 +463,12 @@ var [ReportingHandle] = [
|
|
|
459
463
|
"",
|
|
460
464
|
"",
|
|
461
465
|
"",
|
|
466
|
+
"",
|
|
467
|
+
"",
|
|
468
|
+
"",
|
|
469
|
+
"",
|
|
470
|
+
"",
|
|
471
|
+
"",
|
|
462
472
|
""
|
|
463
473
|
]
|
|
464
474
|
];
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.ts","names":["Schema"],"sources":["../src/state.d.ts","../src/dedupe.d.ts","../src/writers.d.ts","../src/cvss.d.ts","../src/sarif.d.ts","../src/index.d.ts"],"sourcesContent":["/**\n * Report state — port of strix report/state.py: the per-scan report store\n * with sequential `vuln-NNNN` ids, strix's exact field insertion order,\n * title cleaning, update whitelist with dependent-field dropping, update\n * history, and hydration from a previous run dir. Key order matters: the\n * stored dicts are JSON-serialized byte-for-byte into vulnerabilities.json,\n * so fields are inserted in state.py `add_vulnerability_report` order.\n * @module @gpzhang2001/sharpkit-reporting/state\n */\n/** A stored vulnerability/dependency report dict (strix shape). */\nexport interface VulnerabilityReport {\n readonly id: string;\n title: string;\n severity: string;\n readonly timestamp: string;\n [key: string]: unknown;\n}\n/** Injectable clock (tests pass fixed times; default wall clock). */\nexport type Clock = () => Date;\n/** strix display timestamp format (state.py :349). */\nexport declare function formatTimestamp(date: Date): string;\n/** strix ISO instant format (start/end times). */\nexport declare function formatIso(date: Date): string;\n/** Control-char → space + whitespace collapse (state.py `_clean_title`). */\nexport declare function cleanTitle(title: string): string;\n/** strix severity order (tool.py `_SEVERITY_ORDER`). */\nexport declare const SEVERITY_ORDER: readonly [\"critical\", \"high\", \"medium\", \"low\", \"info\", \"none\"];\n/** Severity rank with unknown → last (writer parity). */\nexport declare function severityRank(severity: string): number;\n/** Updatable field whitelist (state.py `UPDATABLE_REPORT_FIELDS`). */\nexport declare const UPDATABLE_REPORT_FIELDS: Set<string>;\n/** Dependent fields dropped when their primary changes without replacement. */\nexport declare const DEPENDENT_REPORT_FIELDS: Readonly<Record<string, string>>;\n/** One update-history entry (state.py :467-487). */\nexport interface UpdateHistoryEntry {\n readonly timestamp: string;\n fields: string[];\n dropped_fields?: string[];\n reason?: string;\n agent_id?: string;\n agent_name?: string;\n previous_severity?: string;\n previous_cvss?: number;\n previous_confidence?: string;\n}\nexport interface AddReportInput {\n readonly title: string;\n readonly severity: string;\n readonly findingClass?: string | undefined;\n readonly dependencyMetadata?: Record<string, unknown> | undefined;\n readonly agentId?: string | undefined;\n readonly agentName?: string | undefined;\n /** All remaining report fields (already validated/normalized by the tool layer). */\n readonly fields: Record<string, unknown>;\n}\n/** The revision outcome: null = no-op (strix parity). */\nexport type UpdateOutcome = {\n readonly report: VulnerabilityReport;\n} | {\n readonly noop: true;\n};\n/**\n * One scan's report store. Not a process singleton — the suite runs one per\n * scan id inside the tool package's closure (strix's module-global maps to a\n * per-scan instance in dsh).\n */\nexport declare class ReportState {\n readonly runId: string;\n runName: string | null;\n readonly startTime: string;\n endTime: string | null;\n status: string;\n finalScanResult: string | null;\n readonly vulnerabilityReports: VulnerabilityReport[];\n /** Ids already rendered to markdown (incremental writer input). */\n readonly savedVulnIds: Set<string>;\n updateHistoryAgent: {\n readonly agentId?: string;\n readonly agentName?: string;\n } | undefined;\n private readonly clock;\n constructor(options?: {\n readonly runId?: string;\n readonly runName?: string | null;\n readonly clock?: Clock;\n });\n /** Allocate the next sequential id (state.py :343 — length-derived). */\n private nextId;\n /**\n * Add one report with strix's exact field construction order.\n * @param input - the validated/normalized create payload.\n */\n addVulnerabilityReport(input: AddReportInput): VulnerabilityReport;\n /**\n * Revise one report in place (state.py `update_vulnerability_report`):\n * whitelist + strip/lowercase, unchanged-skip, dependent-field dropping,\n * history append, and the MD invalidation marker.\n * @param reportId - the `vuln-NNNN` id.\n * @param changes - only the fields the model passed.\n * @param reason - the update_reason (truncated to 500).\n */\n updateVulnerabilityReport(reportId: string, changes: Record<string, unknown>, reason: string): UpdateOutcome;\n /**\n * Reload from a previous run's vulnerabilities.json so id allocation does\n * not collide (state.py `hydrate_from_run_dir`; raises on corrupt JSON).\n * @param reports - the parsed JSON array.\n */\n hydrate(reports: unknown): void;\n /** Mark the run complete (status transition + end time). */\n complete(exitStatus?: string): void;\n}\n","/**\n * Dedupe — port of strix report/dedupe.py decision flow: the deterministic\n * dependency identity fast path (CVE × package × ecosystem, distinct\n * manifests are separate findings, legacy word-bounded mention fallback) and\n * an injectable LLM judge for dynamic findings. Every judge failure defaults\n * to NOT duplicate (strix parity: dedupe failures never block a finding).\n * @module @gpzhang2001/sharpkit-reporting/dedupe\n */\nimport type { VulnerabilityReport } from './state.ts';\n/** Dependency identity fields (strix `_dependency_identity`). */\nexport interface DependencyIdentity {\n readonly cve: string;\n readonly packageName: string;\n readonly ecosystem: string;\n}\n/** The dedupe verdict shape (strix `DuplicateCheckResult`). */\nexport interface DuplicateVerdict {\n readonly isDuplicate: boolean;\n readonly duplicateId: string;\n readonly confidence: number;\n readonly reason: string;\n}\n/** Pluggable LLM judge (Config-injected; dsh-side LLM wiring lands with M4). */\nexport type DedupeJudge = (candidate: Record<string, unknown>, existing: readonly VulnerabilityReport[]) => Promise<DuplicateVerdict>;\n/** Extract the dependency identity from metadata (strix :162-177). */\nexport declare function dependencyIdentity(metadata: unknown): DependencyIdentity | null;\n/** Whether two manifest paths are both present AND different (strix `_distinct_manifest_paths`). */\nexport declare function distinctManifestPaths(a: unknown, b: unknown): boolean;\n/** Word-bounded regex mention check over prose fields (strix `_legacy_report_mentions_package`). */\nexport declare function legacyReportMentionsPackage(report: VulnerabilityReport, identity: DependencyIdentity): boolean;\n/**\n * The deterministic dependency fast path (strix `_check_dependency_duplicate`).\n * @param candidateIdentity - the candidate's identity.\n * @param candidateMetadata - the candidate's dependency metadata (manifest comparison).\n * @param existing - current reports.\n * @returns a verdict, or null to defer to the LLM judge.\n */\nexport declare function checkDependencyDuplicate(candidateIdentity: DependencyIdentity, candidateMetadata: Record<string, unknown> | undefined, existing: readonly VulnerabilityReport[]): DuplicateVerdict | null;\n/**\n * Entry point (strix `check_duplicate`): fast path for dependency\n * candidates, then the injected judge; every judge failure is NOT duplicate.\n * @param candidate - the candidate fields sent for comparison.\n * @param candidateMetadata - dependency metadata when present.\n * @param existing - current reports.\n * @param judge - the injected LLM judge (absent → not duplicate).\n */\nexport declare function checkDuplicate(candidate: Record<string, unknown>, candidateMetadata: Record<string, unknown> | undefined, existing: readonly VulnerabilityReport[], judge: DedupeJudge | undefined): Promise<DuplicateVerdict>;\n","/**\n * Artifact writers — port of strix report/writer.py: atomic writes\n * (temp-in-same-dir + rename, no fsync), run.json, vulnerabilities.csv\n * (formula-injection guard, \\r\\n terminators, uppercase severity,\n * severity-then-timestamp ordering), vulnerabilities.json (byte-identical\n * serialization), the vuln-NNNN.md renderer (exact section order), and the\n * executive report template.\n * @module @gpzhang2001/sharpkit-reporting/writers\n */\nimport { type VulnerabilityReport } from './state.ts';\n/** JSON.stringify with Python `json.dumps(ensure_ascii=False, indent=2)` parity. */\nexport declare function dumpsIndent(value: unknown): string;\n/**\n * Atomic text write (writer.py `atomic_write_text` :201-220): temp file in\n * the target's directory + rename; no fsync (strix parity).\n * @param path - target file path.\n * @param payload - exact bytes to write.\n */\nexport declare function atomicWriteText(path: string, payload: string): Promise<void>;\n/**\n * Formula-injection guard (writer.py `csv_safe` :35-53): prefix `'` when the\n * rendered cell starts with `= + - @ \\t \\r`.\n * @param value - the cell value.\n */\nexport declare function csvSafe(value: string): string;\n/**\n * Render vulnerabilities.csv: header + rows sorted by (severity rank,\n * timestamp), uppercase severity, \\r\\n terminators.\n * @param reports - the stored reports.\n */\nexport declare function renderVulnerabilitiesCsv(reports: readonly VulnerabilityReport[]): string;\n/** Update history section (writer.py `render_update_history` :364-396). */\nexport declare function renderUpdateHistory(report: Record<string, unknown>): string[];\n/**\n * Render one vulnerability markdown document (writer.py\n * `render_vulnerability_md` :223-361 section order).\n * @param report - the stored report dict.\n */\nexport declare function renderVulnerabilityMd(report: VulnerabilityReport): string;\n/**\n * Write the per-run artifacts handled outside SARIF (writer.py parity):\n * vulnerabilities/*.md (incremental), vulnerabilities.csv,\n * vulnerabilities.json.\n * @param runDir - the run directory.\n * @param reports - all stored reports.\n * @param savedIds - ids whose MD is already on disk (updated ids must be re-rendered by the caller removing them).\n */\nexport declare function writeVulnerabilities(runDir: string, reports: readonly VulnerabilityReport[], savedIds: ReadonlySet<string>): Promise<void>;\n/**\n * Write the assembled coverage document (coverage.py `write_coverage`).\n * @param runDir - the run directory.\n * @param document - the assembled coverage document.\n */\nexport declare function writeCoverage(runDir: string, document: Record<string, unknown>): Promise<void>;\n/**\n * Write run.json last (state.py ordering).\n * @param runDir - the run directory.\n * @param runRecord - the run record dict.\n */\nexport declare function writeRunRecord(runDir: string, runRecord: Record<string, unknown>): Promise<void>;\n/**\n * Write the executive report (plain truncate-write, writer.py :139-145).\n * @param runDir - the run directory.\n * @param finalScanResult - the composed final report body.\n * @param generatedAt - display timestamp.\n */\nexport declare function writeExecutiveReport(runDir: string, finalScanResult: string, generatedAt: string): Promise<void>;\n/** Severity list used by callers that need the canonical order. */\nexport declare const severityOrder: readonly [\"critical\", \"high\", \"medium\", \"low\", \"info\", \"none\"];\n","/**\n * CVSS v3.1 base-score math, ported from strix's usage of the `cvss`\n * package (tool.py `_calculate_cvss` :134-153): build the vector string,\n * compute the base score and qualitative severity. The score uses the\n * CVSS 3.1 spec formula with the spec's Roundup1 (ceil to one decimal with\n * the IEEE-754 guard), and a \"none\" base severity is remapped to \"info\"\n * (strix parity).\n * @module @gpzhang2001/sharpkit-reporting/cvss\n */\n/** The eight CVSS metrics with their allowed values (strix `_CVSS_VALID`). */\nexport declare const CVSS_VALID: {\n readonly attack_vector: readonly [\"N\", \"A\", \"L\", \"P\"];\n readonly attack_complexity: readonly [\"L\", \"H\"];\n readonly privileges_required: readonly [\"N\", \"L\", \"H\"];\n readonly user_interaction: readonly [\"N\", \"R\"];\n readonly scope: readonly [\"U\", \"C\"];\n readonly confidentiality: readonly [\"N\", \"L\", \"H\"];\n readonly integrity: readonly [\"N\", \"L\", \"H\"];\n readonly availability: readonly [\"N\", \"L\", \"H\"];\n};\nexport type CvssMetricName = keyof typeof CVSS_VALID;\n/**\n * Validate a breakdown against the allowed metric/value sets.\n * @param breakdown - the model-supplied 8-metric dict.\n * @returns validation errors, empty when valid.\n */\nexport declare function validateCvssBreakdown(breakdown: Record<string, unknown>): string[];\n/**\n * Build the vector string (strix format: CVSS:3.1/AV:../AC:../PR:../UI:../S:../C:../I:../A:..).\n * @param breakdown - a validated breakdown.\n */\nexport declare function buildCvssVector(breakdown: Readonly<Record<CvssMetricName, string>>): string;\n/**\n * Compute the CVSS v3.1 base score for a breakdown (the `cvss` package's\n * `compute_base_score`: Scope U ISC = 6.42×ISCBase; Scope C ISC = the 7.52\n * formula; Scope C multiplies the total by 1.08 before the cap).\n * @param breakdown - a validated breakdown.\n * @returns the rounded base score (0.0 when impact is non-positive).\n */\nexport declare function cvssBaseScore(breakdown: Readonly<Record<CvssMetricName, string>>): number;\n/**\n * Qualitative severity banding (the `cvss` package's rating bands).\n * @param score - the base score.\n */\nexport declare function cvssSeverity(score: number): 'none' | 'low' | 'medium' | 'high' | 'critical';\n/**\n * Full computation: vector + score + severity with the \"none\"→\"info\" remap\n * (strix `_calculate_cvss` return contract).\n * @param breakdown - a validated breakdown.\n */\nexport declare function calculateCvss(breakdown: Readonly<Record<CvssMetricName, string>>): {\n readonly vector: string;\n readonly score: number;\n readonly severity: string;\n};\n/**\n * Dependency severity banding from an advisory score (strix\n * `_DEP_SEVERITY_FROM_CVSS` :1363-1378, top band inclusive at 10.0).\n * @param score - the advisory base score.\n */\nexport declare function dependencySeverity(score: number): string;\n","/**\n * SARIF 2.1.0 writer — port of strix report/sarif.py: rule ids normalized\n * CWE→CVE→id→slug, GitHub security-severity, STRIDE tags from the CWE map,\n * physical + synthetic (SECURITY.md anchor) locations with endpoint logical\n * locations, PR-suggestion fixes, deterministic partial fingerprints\n * (sha256), and the strix-namespaced properties (PoC script body never\n * exported). Key insertion order matches Python dict construction\n * byte-for-byte (golden-diff locked).\n * @module @gpzhang2001/sharpkit-reporting/sarif\n */\nimport type { VulnerabilityReport } from './state.ts';\nexport declare const SARIF_SCHEMA = \"https://json.schemastore.org/sarif-2.1.0.json\";\nexport declare const SARIF_VERSION = \"2.1.0\";\nexport declare const TOOL_NAME = \"sharpkit\";\nexport declare const TOOL_INFORMATION_URI = \"https://github.com/gpzhang2001/sharpkit\";\ntype Json = string | number | boolean | null | Json[] | {\n [key: string]: Json;\n};\n/** Stable rule id: CWE → CVE → finding id → slug → sharpkit-finding. */\nexport declare function ruleIdOf(report: VulnerabilityReport): string;\n/** Lowercase slug joined by dashes (sarif.py `_slugify`). */\nexport declare function slugify(value: string): string;\n/** STRIDE legs for a CWE, default legs when unmapped (every finding gets ≥1). */\nexport declare function strideLegsForCwe(cwe: unknown): readonly string[];\n/** First curated keyword in the title, else the first 5 alphanumeric words. */\nexport declare function classKeyword(title: string): string;\n/** SARIF level mapping. */\nexport declare function sarifLevel(severity: unknown): string;\n/** GitHub security-severity: \"%.1f\" CVSS else the label score. */\nexport declare function securitySeverity(report: VulnerabilityReport): string;\n/** Reject unsafe SARIF artifact URIs; normalize backslashes (sarif.py `_sarif_uri`). */\nexport declare function sarifUri(file: string): string | null;\n/** Deterministic per-finding fingerprint (sarif.py `_primary_fingerprint`). */\ndeclare function primaryFingerprint(ruleId: string, report: VulnerabilityReport, locations: readonly Json[], isSynthetic: boolean): string | null;\n/** File-independent class fingerprint (sarif.py `_class_fingerprint`). */\ndeclare function classFingerprint(ruleId: string, report: VulnerabilityReport): string | null;\n/** One coverage entry projected into SARIF (strix coverage.py shape). */\nexport interface SarifCoverageEntry {\n readonly risk_area: string;\n readonly surface: string;\n readonly outcome: string;\n readonly evidence?: unknown;\n readonly recorded_by?: unknown;\n}\n/** The coverage document subset the SARIF bridge consumes. */\nexport interface SarifCoverage {\n readonly entries: readonly SarifCoverageEntry[];\n readonly completeness?: {\n readonly complete?: boolean;\n readonly caveats?: readonly string[];\n } | undefined;\n}\nexport interface SarifOptions {\n readonly toolVersion: string;\n readonly coverage?: SarifCoverage | undefined;\n /** Exactly-one-repository provenance (strix passes None in our port for now). */\n readonly repositoryContext?: {\n readonly repositoryUri?: string;\n readonly repositoryFullName?: string;\n readonly commitSha?: string;\n readonly branch?: string;\n readonly ref?: string;\n } | undefined;\n}\n/**\n * Build the full SARIF document (top-level key order and run properties).\n * Coverage results and invocations join when the coverage tool lands (批 4).\n * @param reports - all stored reports.\n * @param options - tool version and optional repository provenance.\n */\nexport declare function buildSarif(reports: readonly VulnerabilityReport[], options: SarifOptions): {\n [key: string]: Json;\n};\n/**\n * Write findings.sarif (temp sibling + rename, trailing newline; sarif.py\n * `write_sarif_report`). Always emitted, even with zero findings, so a fresh\n * empty doc overwrites stale results.\n * @param runDir - the run directory.\n * @param reports - all stored reports.\n * @param options - tool version and provenance.\n */\nexport declare function writeSarif(runDir: string, reports: readonly VulnerabilityReport[], options: SarifOptions): Promise<void>;\n/** Reproduce a fingerprint for tests (exposed for golden diagnostics). */\nexport declare const internals: {\n primaryFingerprint: typeof primaryFingerprint;\n classFingerprint: typeof classFingerprint;\n};\nexport {};\n","/**\n * Vulnerability / dependency reporting tools — port of strix\n * tools/reporting/tool.py: create_vulnerability_report,\n * create_dependency_report, update_vulnerability_report, list_reports,\n * get_report. Validation parity (runtime-validated value sets, CVSS\n * computation, cross-class update guards), strix's exact response JSON\n * shapes, and the artifacts fan-out on every mutation (md/csv/json/sarif +\n * run.json last, atomic writes; SARIF always emitted even when empty).\n * The dedupe LLM judge is a Config callback (deterministic dependency fast\n * path runs regardless; judge failures default to not-duplicate).\n * @module @gpzhang2001/sharpkit-reporting\n */\nimport type { Context } from '@deepseek-ai/cordis';\nimport type Schema from '@deepseek-ai/schemastery';\nimport { type DedupeJudge } from './dedupe.ts';\nimport { ReportState } from './state.ts';\nimport { renderVulnerabilityMd } from './writers.ts';\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n pentestReporting: ReportingHandle;\n }\n}\nexport { renderVulnerabilityMd };\nexport { ReportState, cleanTitle, severityRank } from './state.ts';\nexport { calculateCvss, cvssBaseScore, cvssSeverity, validateCvssBreakdown, dependencySeverity, buildCvssVector } from './cvss.ts';\nexport { checkDuplicate, dependencyIdentity } from './dedupe.ts';\nexport { buildSarif, sarifUri, ruleIdOf, classKeyword } from './sarif.ts';\n/** Deployment-tunable configuration. */\nexport interface Config {\n /** Run-name slug basis (strix derives it from the target label). */\n readonly runName?: string;\n /** Runs root directory (sharpkit_runs; configurable back to strix_runs for legacy compat). */\n readonly runsRoot?: string;\n /** Sandbox image version stamped into SARIF (tool.driver.version). */\n readonly toolVersion?: string;\n /** Scan mode recorded into run.json. */\n readonly scanMode?: string;\n /** Targets recorded into run.json. */\n readonly targetsInfo?: readonly Record<string, unknown>[];\n /** Pluggable dedupe LLM judge (dynamic findings; failures = not duplicate). */\n readonly dedupeJudge?: DedupeJudge;\n /** File the CVSS-broad-CWE ban borrows from strix's guidance. */\n readonly strictCwe?: boolean;\n /** Coverage document source (the analysis package wires itself in). */\n readonly coverageSource?: CoverageSource;\n /** Auth mode recorded into run.json (strix codex.auth_mode; default \"none\"). */\n readonly authMode?: string;\n /** Scan instruction recorded into run.json (strix set_scan_config). */\n readonly instruction?: string;\n /** Diff scope recorded into run.json (diff-scope scans). */\n readonly diffScope?: string;\n /** Non-interactive flag recorded into run.json. */\n readonly nonInteractive?: boolean;\n /** Local source trees recorded into run.json. */\n readonly localSources?: readonly Record<string, unknown>[];\n /** Scope mode recorded into run.json (strix default \"auto\"). */\n readonly scopeMode?: string;\n /** Diff base ref recorded into run.json (diff-scope scans). */\n readonly diffBase?: string;\n /** MCP connection names recorded into run.json (strix record_mcp_connections). */\n readonly mcpConnections?: readonly string[];\n}\nexport declare const name = \"pentest-tool-reporting\";\nexport declare const inject: string[];\nexport declare const Config: Schema<Config>;\n/** Normalize + validate code_locations (tool.py `_normalize_code_locations`). */\nexport declare function normalizeCodeLocations(raw: unknown): {\n readonly locations?: Record<string, unknown>[];\n readonly errors: string[];\n};\n/** CVE normalization (tool.py `_extract_cve` + `_validate_cve`). */\nexport declare function normalizeCve(value: string | undefined): string | undefined;\n/** CWE normalization (tool.py `_extract_cwe`). */\nexport declare function normalizeCwe(value: string | undefined): string | undefined;\n/** Dependency metadata builder (tool.py `_build_dependency_metadata` order). */\nexport declare function buildDependencyMetadata(fields: {\n readonly packageName: string;\n readonly installedVersion: string;\n readonly advisoryCvss?: number | undefined;\n readonly packageEcosystem: string;\n readonly manifestPath: string;\n readonly fixedVersion?: string | undefined;\n readonly introducedBy?: string | undefined;\n readonly dependencyPath?: string | undefined;\n readonly reachability: string;\n readonly reachabilityEvidence?: string | undefined;\n readonly contextual?: {\n readonly breakdown: Record<string, string>;\n readonly score: number;\n readonly vector: string;\n readonly reasoning: string;\n } | undefined;\n}): Record<string, unknown>;\nexport interface ReportingHandle {\n readonly state: ReportState;\n readonly runDir: string;\n finishScan(sections: {\n readonly executiveSummary: string;\n readonly methodology: string;\n readonly technicalAnalysis: string;\n readonly recommendations: string;\n }, status?: string): Promise<void>;\n writeNow(): Promise<void>;\n readRaw(relative: string): Promise<string>;\n}\n/** Coverage document provider the analysis package supplies at wiring time. */\nexport interface CoverageSource {\n entries(): Array<Record<string, unknown>>;\n outcomeCounts(): Record<string, number>;\n}\nexport declare function apply(ctx: Context, config?: Config): ReportingHandle;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.ts","names":["Schema"],"sources":["../src/state.d.ts","../src/dedupe.d.ts","../src/writers.d.ts","../src/cvss.d.ts","../src/sarif.d.ts","../src/index.d.ts"],"sourcesContent":["/**\n * Report state — port of strix report/state.py: the per-scan report store\n * with sequential `vuln-NNNN` ids, strix's exact field insertion order,\n * title cleaning, update whitelist with dependent-field dropping, update\n * history, and hydration from a previous run dir. Key order matters: the\n * stored dicts are JSON-serialized byte-for-byte into vulnerabilities.json,\n * so fields are inserted in state.py `add_vulnerability_report` order.\n * @module @gpzhang2001/sharpkit-reporting/state\n */\n/** A stored vulnerability/dependency report dict (strix shape). */\nexport interface VulnerabilityReport {\n readonly id: string;\n title: string;\n severity: string;\n readonly timestamp: string;\n [key: string]: unknown;\n}\n/** Injectable clock (tests pass fixed times; default wall clock). */\nexport type Clock = () => Date;\n/** strix display timestamp format (state.py :349). */\nexport declare function formatTimestamp(date: Date): string;\n/** strix ISO instant format (start/end times). */\nexport declare function formatIso(date: Date): string;\n/** Control-char → space + whitespace collapse (state.py `_clean_title`). */\nexport declare function cleanTitle(title: string): string;\n/** strix severity order (tool.py `_SEVERITY_ORDER`). */\nexport declare const SEVERITY_ORDER: readonly [\"critical\", \"high\", \"medium\", \"low\", \"info\", \"none\"];\n/** Severity rank with unknown → last (writer parity). */\nexport declare function severityRank(severity: string): number;\n/** Updatable field whitelist (state.py `UPDATABLE_REPORT_FIELDS`). */\nexport declare const UPDATABLE_REPORT_FIELDS: Set<string>;\n/** Dependent fields dropped when their primary changes without replacement. */\nexport declare const DEPENDENT_REPORT_FIELDS: Readonly<Record<string, string>>;\n/** One update-history entry (state.py :467-487). */\nexport interface UpdateHistoryEntry {\n readonly timestamp: string;\n fields: string[];\n dropped_fields?: string[];\n reason?: string;\n agent_id?: string;\n agent_name?: string;\n previous_severity?: string;\n previous_cvss?: number;\n previous_confidence?: string;\n}\nexport interface AddReportInput {\n readonly title: string;\n readonly severity: string;\n readonly findingClass?: string | undefined;\n readonly dependencyMetadata?: Record<string, unknown> | undefined;\n readonly agentId?: string | undefined;\n readonly agentName?: string | undefined;\n /** All remaining report fields (already validated/normalized by the tool layer). */\n readonly fields: Record<string, unknown>;\n}\n/** The revision outcome: null = no-op (strix parity). */\nexport type UpdateOutcome = {\n readonly report: VulnerabilityReport;\n} | {\n readonly noop: true;\n};\n/**\n * One scan's report store. Not a process singleton — the suite runs one per\n * scan id inside the tool package's closure (strix's module-global maps to a\n * per-scan instance in dsh).\n */\nexport declare class ReportState {\n /** Reseated per resolved run dir (see reseatRunId); NOT per-process — the state is shared for multi-round continuation. */\n runId: string;\n runName: string | null;\n readonly startTime: string;\n endTime: string | null;\n status: string;\n finalScanResult: string | null;\n readonly vulnerabilityReports: VulnerabilityReport[];\n /** Ids already rendered to markdown (incremental writer input). */\n readonly savedVulnIds: Set<string>;\n updateHistoryAgent: {\n readonly agentId?: string;\n readonly agentName?: string;\n } | undefined;\n private readonly clock;\n constructor(options?: {\n readonly runId?: string;\n readonly runName?: string | null;\n readonly clock?: Clock;\n });\n /**\n * Fresh run id for a newly resolved run directory. The state object stays\n * process-shared so a second scan in the same host process CONTINUES the\n * report ledger (multi-round retest mode) — but each run dir must carry its\n * own id: before this, both rounds' run.json showed the same run_id because\n * the id was minted once per process (2026-09-22).\n */\n reseatRunId(): void;\n /** Allocate the next sequential id (state.py :343 — length-derived). */\n private nextId;\n /**\n * Add one report with strix's exact field construction order.\n * @param input - the validated/normalized create payload.\n */\n addVulnerabilityReport(input: AddReportInput): VulnerabilityReport;\n /**\n * Revise one report in place (state.py `update_vulnerability_report`):\n * whitelist + strip/lowercase, unchanged-skip, dependent-field dropping,\n * history append, and the MD invalidation marker.\n * @param reportId - the `vuln-NNNN` id.\n * @param changes - only the fields the model passed.\n * @param reason - the update_reason (truncated to 500).\n */\n updateVulnerabilityReport(reportId: string, changes: Record<string, unknown>, reason: string): UpdateOutcome;\n /**\n * Reload from a previous run's vulnerabilities.json so id allocation does\n * not collide (state.py `hydrate_from_run_dir`; raises on corrupt JSON).\n * @param reports - the parsed JSON array.\n */\n hydrate(reports: unknown): void;\n /** Mark the run complete (status transition + end time). */\n complete(exitStatus?: string): void;\n}\n","/**\n * Dedupe — port of strix report/dedupe.py decision flow: the deterministic\n * dependency identity fast path (CVE × package × ecosystem, distinct\n * manifests are separate findings, legacy word-bounded mention fallback) and\n * an injectable LLM judge for dynamic findings. Every judge failure defaults\n * to NOT duplicate (strix parity: dedupe failures never block a finding).\n * @module @gpzhang2001/sharpkit-reporting/dedupe\n */\nimport type { VulnerabilityReport } from './state.ts';\n/** Dependency identity fields (strix `_dependency_identity`). */\nexport interface DependencyIdentity {\n readonly cve: string;\n readonly packageName: string;\n readonly ecosystem: string;\n}\n/** The dedupe verdict shape (strix `DuplicateCheckResult`). */\nexport interface DuplicateVerdict {\n readonly isDuplicate: boolean;\n readonly duplicateId: string;\n readonly confidence: number;\n readonly reason: string;\n}\n/** Pluggable LLM judge (Config-injected; dsh-side LLM wiring lands with M4). */\nexport type DedupeJudge = (candidate: Record<string, unknown>, existing: readonly VulnerabilityReport[]) => Promise<DuplicateVerdict>;\n/** Extract the dependency identity from metadata (strix :162-177). */\nexport declare function dependencyIdentity(metadata: unknown): DependencyIdentity | null;\n/** Whether two manifest paths are both present AND different (strix `_distinct_manifest_paths`). */\nexport declare function distinctManifestPaths(a: unknown, b: unknown): boolean;\n/** Word-bounded regex mention check over prose fields (strix `_legacy_report_mentions_package`). */\nexport declare function legacyReportMentionsPackage(report: VulnerabilityReport, identity: DependencyIdentity): boolean;\n/**\n * The deterministic dependency fast path (strix `_check_dependency_duplicate`).\n * @param candidateIdentity - the candidate's identity.\n * @param candidateMetadata - the candidate's dependency metadata (manifest comparison).\n * @param existing - current reports.\n * @returns a verdict, or null to defer to the LLM judge.\n */\nexport declare function checkDependencyDuplicate(candidateIdentity: DependencyIdentity, candidateMetadata: Record<string, unknown> | undefined, existing: readonly VulnerabilityReport[]): DuplicateVerdict | null;\n/**\n * Entry point (strix `check_duplicate`): fast path for dependency\n * candidates, then the injected judge; every judge failure is NOT duplicate.\n * @param candidate - the candidate fields sent for comparison.\n * @param candidateMetadata - dependency metadata when present.\n * @param existing - current reports.\n * @param judge - the injected LLM judge (absent → not duplicate).\n */\nexport declare function checkDuplicate(candidate: Record<string, unknown>, candidateMetadata: Record<string, unknown> | undefined, existing: readonly VulnerabilityReport[], judge: DedupeJudge | undefined): Promise<DuplicateVerdict>;\n","/**\n * Artifact writers — port of strix report/writer.py: atomic writes\n * (temp-in-same-dir + rename, no fsync), run.json, vulnerabilities.csv\n * (formula-injection guard, \\r\\n terminators, uppercase severity,\n * severity-then-timestamp ordering), vulnerabilities.json (byte-identical\n * serialization), the vuln-NNNN.md renderer (exact section order), and the\n * executive report template.\n * @module @gpzhang2001/sharpkit-reporting/writers\n */\nimport { type VulnerabilityReport } from './state.ts';\n/** JSON.stringify with Python `json.dumps(ensure_ascii=False, indent=2)` parity. */\nexport declare function dumpsIndent(value: unknown): string;\n/**\n * Atomic text write (writer.py `atomic_write_text` :201-220): temp file in\n * the target's directory + rename; no fsync (strix parity).\n * @param path - target file path.\n * @param payload - exact bytes to write.\n */\nexport declare function atomicWriteText(path: string, payload: string): Promise<void>;\n/**\n * Formula-injection guard (writer.py `csv_safe` :35-53): prefix `'` when the\n * rendered cell starts with `= + - @ \\t \\r`.\n * @param value - the cell value.\n */\nexport declare function csvSafe(value: string): string;\n/**\n * Render vulnerabilities.csv: header + rows sorted by (severity rank,\n * timestamp), uppercase severity, \\r\\n terminators.\n * @param reports - the stored reports.\n */\nexport declare function renderVulnerabilitiesCsv(reports: readonly VulnerabilityReport[]): string;\n/** Update history section (writer.py `render_update_history` :364-396). */\nexport declare function renderUpdateHistory(report: Record<string, unknown>): string[];\n/**\n * Render one vulnerability markdown document (writer.py\n * `render_vulnerability_md` :223-361 section order).\n * @param report - the stored report dict.\n */\nexport declare function renderVulnerabilityMd(report: VulnerabilityReport): string;\n/**\n * Write the per-run artifacts handled outside SARIF (writer.py parity):\n * vulnerabilities/*.md (incremental), vulnerabilities.csv,\n * vulnerabilities.json.\n * @param runDir - the run directory.\n * @param reports - all stored reports.\n * @param savedIds - ids whose MD is already on disk (updated ids must be re-rendered by the caller removing them).\n */\nexport declare function writeVulnerabilities(runDir: string, reports: readonly VulnerabilityReport[], savedIds: ReadonlySet<string>): Promise<void>;\n/**\n * Write the assembled coverage document (coverage.py `write_coverage`).\n * @param runDir - the run directory.\n * @param document - the assembled coverage document.\n */\nexport declare function writeCoverage(runDir: string, document: Record<string, unknown>): Promise<void>;\n/**\n * Write run.json last (state.py ordering).\n * @param runDir - the run directory.\n * @param runRecord - the run record dict.\n */\nexport declare function writeRunRecord(runDir: string, runRecord: Record<string, unknown>): Promise<void>;\n/**\n * Write the executive report (plain truncate-write, writer.py :139-145).\n * @param runDir - the run directory.\n * @param finalScanResult - the composed final report body.\n * @param generatedAt - display timestamp.\n */\nexport declare function writeExecutiveReport(runDir: string, finalScanResult: string, generatedAt: string): Promise<void>;\n/** Severity list used by callers that need the canonical order. */\nexport declare const severityOrder: readonly [\"critical\", \"high\", \"medium\", \"low\", \"info\", \"none\"];\n","/**\n * CVSS v3.1 base-score math, ported from strix's usage of the `cvss`\n * package (tool.py `_calculate_cvss` :134-153): build the vector string,\n * compute the base score and qualitative severity. The score uses the\n * CVSS 3.1 spec formula with the spec's Roundup1 (ceil to one decimal with\n * the IEEE-754 guard), and a \"none\" base severity is remapped to \"info\"\n * (strix parity).\n * @module @gpzhang2001/sharpkit-reporting/cvss\n */\n/** The eight CVSS metrics with their allowed values (strix `_CVSS_VALID`). */\nexport declare const CVSS_VALID: {\n readonly attack_vector: readonly [\"N\", \"A\", \"L\", \"P\"];\n readonly attack_complexity: readonly [\"L\", \"H\"];\n readonly privileges_required: readonly [\"N\", \"L\", \"H\"];\n readonly user_interaction: readonly [\"N\", \"R\"];\n readonly scope: readonly [\"U\", \"C\"];\n readonly confidentiality: readonly [\"N\", \"L\", \"H\"];\n readonly integrity: readonly [\"N\", \"L\", \"H\"];\n readonly availability: readonly [\"N\", \"L\", \"H\"];\n};\nexport type CvssMetricName = keyof typeof CVSS_VALID;\n/**\n * Validate a breakdown against the allowed metric/value sets.\n * @param breakdown - the model-supplied 8-metric dict.\n * @returns validation errors, empty when valid.\n */\nexport declare function validateCvssBreakdown(breakdown: Record<string, unknown>): string[];\n/**\n * Build the vector string (strix format: CVSS:3.1/AV:../AC:../PR:../UI:../S:../C:../I:../A:..).\n * @param breakdown - a validated breakdown.\n */\nexport declare function buildCvssVector(breakdown: Readonly<Record<CvssMetricName, string>>): string;\n/**\n * Compute the CVSS v3.1 base score for a breakdown (the `cvss` package's\n * `compute_base_score`: Scope U ISC = 6.42×ISCBase; Scope C ISC = the 7.52\n * formula; Scope C multiplies the total by 1.08 before the cap).\n * @param breakdown - a validated breakdown.\n * @returns the rounded base score (0.0 when impact is non-positive).\n */\nexport declare function cvssBaseScore(breakdown: Readonly<Record<CvssMetricName, string>>): number;\n/**\n * Qualitative severity banding (the `cvss` package's rating bands).\n * @param score - the base score.\n */\nexport declare function cvssSeverity(score: number): 'none' | 'low' | 'medium' | 'high' | 'critical';\n/**\n * Full computation: vector + score + severity with the \"none\"→\"info\" remap\n * (strix `_calculate_cvss` return contract).\n * @param breakdown - a validated breakdown.\n */\nexport declare function calculateCvss(breakdown: Readonly<Record<CvssMetricName, string>>): {\n readonly vector: string;\n readonly score: number;\n readonly severity: string;\n};\n/**\n * Dependency severity banding from an advisory score (strix\n * `_DEP_SEVERITY_FROM_CVSS` :1363-1378, top band inclusive at 10.0).\n * @param score - the advisory base score.\n */\nexport declare function dependencySeverity(score: number): string;\n","/**\n * SARIF 2.1.0 writer — port of strix report/sarif.py: rule ids normalized\n * CWE→CVE→id→slug, GitHub security-severity, STRIDE tags from the CWE map,\n * physical + synthetic (SECURITY.md anchor) locations with endpoint logical\n * locations, PR-suggestion fixes, deterministic partial fingerprints\n * (sha256), and the strix-namespaced properties (PoC script body never\n * exported). Key insertion order matches Python dict construction\n * byte-for-byte (golden-diff locked).\n * @module @gpzhang2001/sharpkit-reporting/sarif\n */\nimport type { VulnerabilityReport } from './state.ts';\nexport declare const SARIF_SCHEMA = \"https://json.schemastore.org/sarif-2.1.0.json\";\nexport declare const SARIF_VERSION = \"2.1.0\";\nexport declare const TOOL_NAME = \"sharpkit\";\nexport declare const TOOL_INFORMATION_URI = \"https://github.com/gpzhang2001/sharpkit\";\ntype Json = string | number | boolean | null | Json[] | {\n [key: string]: Json;\n};\n/** Stable rule id: CWE → CVE → finding id → slug → sharpkit-finding. */\nexport declare function ruleIdOf(report: VulnerabilityReport): string;\n/** Lowercase slug joined by dashes (sarif.py `_slugify`). */\nexport declare function slugify(value: string): string;\n/** STRIDE legs for a CWE, default legs when unmapped (every finding gets ≥1). */\nexport declare function strideLegsForCwe(cwe: unknown): readonly string[];\n/** First curated keyword in the title, else the first 5 alphanumeric words. */\nexport declare function classKeyword(title: string): string;\n/** SARIF level mapping. */\nexport declare function sarifLevel(severity: unknown): string;\n/** GitHub security-severity: \"%.1f\" CVSS else the label score. */\nexport declare function securitySeverity(report: VulnerabilityReport): string;\n/** Reject unsafe SARIF artifact URIs; normalize backslashes (sarif.py `_sarif_uri`). */\nexport declare function sarifUri(file: string): string | null;\n/** Deterministic per-finding fingerprint (sarif.py `_primary_fingerprint`). */\ndeclare function primaryFingerprint(ruleId: string, report: VulnerabilityReport, locations: readonly Json[], isSynthetic: boolean): string | null;\n/** File-independent class fingerprint (sarif.py `_class_fingerprint`). */\ndeclare function classFingerprint(ruleId: string, report: VulnerabilityReport): string | null;\n/** One coverage entry projected into SARIF (strix coverage.py shape). */\nexport interface SarifCoverageEntry {\n readonly risk_area: string;\n readonly surface: string;\n readonly outcome: string;\n readonly evidence?: unknown;\n readonly recorded_by?: unknown;\n}\n/** The coverage document subset the SARIF bridge consumes. */\nexport interface SarifCoverage {\n readonly entries: readonly SarifCoverageEntry[];\n readonly completeness?: {\n readonly complete?: boolean;\n readonly caveats?: readonly string[];\n } | undefined;\n}\nexport interface SarifOptions {\n readonly toolVersion: string;\n readonly coverage?: SarifCoverage | undefined;\n /** Exactly-one-repository provenance (strix passes None in our port for now). */\n readonly repositoryContext?: {\n readonly repositoryUri?: string;\n readonly repositoryFullName?: string;\n readonly commitSha?: string;\n readonly branch?: string;\n readonly ref?: string;\n } | undefined;\n}\n/**\n * Build the full SARIF document (top-level key order and run properties).\n * Coverage results and invocations join when the coverage tool lands (批 4).\n * @param reports - all stored reports.\n * @param options - tool version and optional repository provenance.\n */\nexport declare function buildSarif(reports: readonly VulnerabilityReport[], options: SarifOptions): {\n [key: string]: Json;\n};\n/**\n * Write findings.sarif (temp sibling + rename, trailing newline; sarif.py\n * `write_sarif_report`). Always emitted, even with zero findings, so a fresh\n * empty doc overwrites stale results.\n * @param runDir - the run directory.\n * @param reports - all stored reports.\n * @param options - tool version and provenance.\n */\nexport declare function writeSarif(runDir: string, reports: readonly VulnerabilityReport[], options: SarifOptions): Promise<void>;\n/** Reproduce a fingerprint for tests (exposed for golden diagnostics). */\nexport declare const internals: {\n primaryFingerprint: typeof primaryFingerprint;\n classFingerprint: typeof classFingerprint;\n};\nexport {};\n","/**\n * Vulnerability / dependency reporting tools — port of strix\n * tools/reporting/tool.py: create_vulnerability_report,\n * create_dependency_report, update_vulnerability_report, list_reports,\n * get_report. Validation parity (runtime-validated value sets, CVSS\n * computation, cross-class update guards), strix's exact response JSON\n * shapes, and the artifacts fan-out on every mutation (md/csv/json/sarif +\n * run.json last, atomic writes; SARIF always emitted even when empty).\n * The dedupe LLM judge is a Config callback (deterministic dependency fast\n * path runs regardless; judge failures default to not-duplicate).\n * @module @gpzhang2001/sharpkit-reporting\n */\nimport type { Context } from '@deepseek-ai/cordis';\nimport type Schema from '@deepseek-ai/schemastery';\nimport { type DedupeJudge } from './dedupe.ts';\nimport { ReportState } from './state.ts';\nimport { renderVulnerabilityMd } from './writers.ts';\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n pentestReporting: ReportingHandle;\n }\n}\nexport { renderVulnerabilityMd };\nexport { ReportState, cleanTitle, severityRank } from './state.ts';\nexport { calculateCvss, cvssBaseScore, cvssSeverity, validateCvssBreakdown, dependencySeverity, buildCvssVector } from './cvss.ts';\nexport { checkDuplicate, dependencyIdentity } from './dedupe.ts';\nexport { buildSarif, sarifUri, ruleIdOf, classKeyword } from './sarif.ts';\n/** Deployment-tunable configuration. */\nexport interface Config {\n /** Run-name slug basis (strix derives it from the target label). */\n readonly runName?: string;\n /** Runs root directory (sharpkit_runs; configurable back to strix_runs for legacy compat). */\n readonly runsRoot?: string;\n /** Sandbox image version stamped into SARIF (tool.driver.version). */\n readonly toolVersion?: string;\n /** Scan mode recorded into run.json. */\n readonly scanMode?: string;\n /** Targets recorded into run.json. */\n readonly targetsInfo?: readonly Record<string, unknown>[];\n /** Pluggable dedupe LLM judge (dynamic findings; failures = not duplicate). */\n readonly dedupeJudge?: DedupeJudge;\n /** File the CVSS-broad-CWE ban borrows from strix's guidance. */\n readonly strictCwe?: boolean;\n /** Coverage document source (the analysis package wires itself in). */\n readonly coverageSource?: CoverageSource;\n /** Auth mode recorded into run.json (strix codex.auth_mode; default \"none\"). */\n readonly authMode?: string;\n /** Scan instruction recorded into run.json (strix set_scan_config). */\n readonly instruction?: string;\n /** Diff scope recorded into run.json (diff-scope scans). */\n readonly diffScope?: string;\n /** Non-interactive flag recorded into run.json. */\n readonly nonInteractive?: boolean;\n /** Local source trees recorded into run.json. */\n readonly localSources?: readonly Record<string, unknown>[];\n /** Scope mode recorded into run.json (strix default \"auto\"). */\n readonly scopeMode?: string;\n /** Diff base ref recorded into run.json (diff-scope scans). */\n readonly diffBase?: string;\n /** MCP connection names recorded into run.json (strix record_mcp_connections). */\n readonly mcpConnections?: readonly string[];\n}\nexport declare const name = \"pentest-tool-reporting\";\nexport declare const inject: string[];\nexport declare const Config: Schema<Config>;\n/** Normalize + validate code_locations (tool.py `_normalize_code_locations`). */\nexport declare function normalizeCodeLocations(raw: unknown): {\n readonly locations?: Record<string, unknown>[];\n readonly errors: string[];\n};\n/** CVE normalization (tool.py `_extract_cve` + `_validate_cve`). */\nexport declare function normalizeCve(value: string | undefined): string | undefined;\n/** CWE normalization (tool.py `_extract_cwe`). */\nexport declare function normalizeCwe(value: string | undefined): string | undefined;\n/** Dependency metadata builder (tool.py `_build_dependency_metadata` order). */\nexport declare function buildDependencyMetadata(fields: {\n readonly packageName: string;\n readonly installedVersion: string;\n readonly advisoryCvss?: number | undefined;\n readonly packageEcosystem: string;\n readonly manifestPath: string;\n readonly fixedVersion?: string | undefined;\n readonly introducedBy?: string | undefined;\n readonly dependencyPath?: string | undefined;\n readonly reachability: string;\n readonly reachabilityEvidence?: string | undefined;\n readonly contextual?: {\n readonly breakdown: Record<string, string>;\n readonly score: number;\n readonly vector: string;\n readonly reasoning: string;\n } | undefined;\n}): Record<string, unknown>;\nexport interface ReportingHandle {\n readonly state: ReportState;\n /** Directory of the most recently resolved run (sync view; resolve via {@link runDirFor} for accuracy). */\n readonly runDir: string;\n /** Resolve (memoize per scan) this session's run directory — see apply() for the rules. */\n runDirFor(session: unknown): Promise<string>;\n finishScan(sections: {\n readonly executiveSummary: string;\n readonly methodology: string;\n readonly technicalAnalysis: string;\n readonly recommendations: string;\n }, status?: string, extras?: Readonly<Record<string, unknown>>): Promise<void>;\n writeNow(): Promise<void>;\n readRaw(relative: string): Promise<string>;\n}\n/** Coverage document provider the analysis package supplies at wiring time. */\nexport interface CoverageSource {\n entries(): Array<Record<string, unknown>>;\n outcomeCounts(): Record<string, number>;\n}\nexport declare function apply(ctx: Context, config?: Config): ReportingHandle;\n"],"mappings":"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"}
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
1
2
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
4
|
import z from "@deepseek-ai/schemastery";
|
|
4
5
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
5
6
|
import { createHash } from "node:crypto";
|
|
@@ -410,6 +411,7 @@ const DEPENDENT_REPORT_FIELDS = {
|
|
|
410
411
|
* per-scan instance in dsh).
|
|
411
412
|
*/
|
|
412
413
|
var ReportState = class {
|
|
414
|
+
/** Reseated per resolved run dir (see reseatRunId); NOT per-process — the state is shared for multi-round continuation. */
|
|
413
415
|
runId;
|
|
414
416
|
runName;
|
|
415
417
|
startTime;
|
|
@@ -427,6 +429,16 @@ var ReportState = class {
|
|
|
427
429
|
this.runName = options.runName ?? null;
|
|
428
430
|
this.startTime = formatIso(this.clock());
|
|
429
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* Fresh run id for a newly resolved run directory. The state object stays
|
|
434
|
+
* process-shared so a second scan in the same host process CONTINUES the
|
|
435
|
+
* report ledger (multi-round retest mode) — but each run dir must carry its
|
|
436
|
+
* own id: before this, both rounds' run.json showed the same run_id because
|
|
437
|
+
* the id was minted once per process (2026-09-22).
|
|
438
|
+
*/
|
|
439
|
+
reseatRunId() {
|
|
440
|
+
this.runId = `run-${Math.random().toString(16).slice(2, 10)}`;
|
|
441
|
+
}
|
|
430
442
|
/** Allocate the next sequential id (state.py :343 — length-derived). */
|
|
431
443
|
nextId() {
|
|
432
444
|
return `vuln-${String(this.vulnerabilityReports.length + 1).padStart(4, "0")}`;
|
|
@@ -1673,39 +1685,154 @@ const DYNAMIC_ONLY_UPDATE_FIELDS = /* @__PURE__ */ new Set([
|
|
|
1673
1685
|
]);
|
|
1674
1686
|
function apply(ctx, config = {}) {
|
|
1675
1687
|
const runsRoot = config.runsRoot ?? "sharpkit_runs";
|
|
1676
|
-
const
|
|
1677
|
-
const
|
|
1678
|
-
const state = new ReportState({ runName });
|
|
1688
|
+
const fallbackRunName = config.runName ?? `pentest-${Math.random().toString(16).slice(2, 6)}`;
|
|
1689
|
+
const state = new ReportState({ runName: fallbackRunName });
|
|
1679
1690
|
const toolVersion = config.toolVersion ?? "0.1.0";
|
|
1680
1691
|
/** scan_results block, set by finishScan and appended to run.json. */
|
|
1681
1692
|
let scanResults;
|
|
1682
|
-
const
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1693
|
+
const scanFactsOf = (session) => {
|
|
1694
|
+
const header = session?.header;
|
|
1695
|
+
const id = typeof header?.id === "string" ? header.id : "";
|
|
1696
|
+
const parent = typeof header?.parentSession === "string" ? header.parentSession : "";
|
|
1697
|
+
const cwd = typeof header?.cwd === "string" && header.cwd !== "" ? header.cwd : "";
|
|
1698
|
+
return {
|
|
1699
|
+
id,
|
|
1700
|
+
key: parent !== "" ? parent : id,
|
|
1701
|
+
cwd
|
|
1702
|
+
};
|
|
1703
|
+
};
|
|
1704
|
+
const sessionOf = (exec) => exec?.agent?.session;
|
|
1705
|
+
const AGENTLESS = ":agentless";
|
|
1706
|
+
/** Per-scan memo: resolved run dir by scan key (root session id). */
|
|
1707
|
+
const resolvedRunDirs = /* @__PURE__ */ new Map();
|
|
1708
|
+
/** The session noted by the most recent tool execute (one scan per host at a time). */
|
|
1709
|
+
let activeSession;
|
|
1710
|
+
/** sessionId → scan key: lets the usage ledger attribute assistant messages to the owning scan. */
|
|
1711
|
+
const scanKeyOfSession = /* @__PURE__ */ new Map();
|
|
1712
|
+
const noteScanKey = (session) => {
|
|
1713
|
+
const facts = scanFactsOf(session);
|
|
1714
|
+
if (facts.id === "") return;
|
|
1715
|
+
const previous = scanKeyOfSession.get(facts.id);
|
|
1716
|
+
scanKeyOfSession.set(facts.id, facts.key);
|
|
1717
|
+
if (previous !== facts.key && facts.key !== facts.id) {
|
|
1718
|
+
const orphan = usageByScan.get(facts.id);
|
|
1719
|
+
if (orphan !== void 0) {
|
|
1720
|
+
const root = usageAccountOf(facts.key);
|
|
1721
|
+
root.requests += orphan.requests;
|
|
1722
|
+
root.inputTokens += orphan.inputTokens;
|
|
1723
|
+
root.outputTokens += orphan.outputTokens;
|
|
1724
|
+
root.totalTokens += orphan.totalTokens;
|
|
1725
|
+
usageByScan.delete(facts.id);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
const noteExec = (rawExec) => {
|
|
1730
|
+
const session = sessionOf(rawExec);
|
|
1731
|
+
if (session !== void 0) {
|
|
1732
|
+
activeSession = session;
|
|
1733
|
+
noteScanKey(session);
|
|
1734
|
+
}
|
|
1735
|
+
};
|
|
1736
|
+
/** Occupancy facts of an existing run dir (best-effort read of its run.json). */
|
|
1737
|
+
const runInfoOf = async (dir) => {
|
|
1738
|
+
try {
|
|
1739
|
+
const raw = JSON.parse(await readFile(join(dir, "run.json"), "utf8"));
|
|
1740
|
+
return {
|
|
1741
|
+
completed: raw["status"] === "completed" || raw["scan_results"]?.["scan_completed"] === true,
|
|
1742
|
+
scanKey: typeof raw["session_id"] === "string" ? raw["session_id"] : null
|
|
1743
|
+
};
|
|
1744
|
+
} catch {
|
|
1745
|
+
return {
|
|
1746
|
+
completed: false,
|
|
1747
|
+
scanKey: null
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1687
1750
|
};
|
|
1688
|
-
|
|
1751
|
+
/**
|
|
1752
|
+
* Resolve this scan's run directory, memoized per scan key:
|
|
1753
|
+
* - an absolute `runsRoot` pins the base (tests, explicit configs — unchanged);
|
|
1754
|
+
* - the default relative root resolves against the session header cwd,
|
|
1755
|
+
* falling back to process.cwd() for agentless calls;
|
|
1756
|
+
* - the name is the configured `runName`, else `pentest-<scan key short>`
|
|
1757
|
+
* (unique per scan, stable across restarts);
|
|
1758
|
+
* - a fresh scan NEVER overwrites a completed or foreign run: the name gains
|
|
1759
|
+
* a -2/-3… suffix; the same scan resuming its own incomplete run reuses
|
|
1760
|
+
* its directory (crash resume).
|
|
1761
|
+
*/
|
|
1762
|
+
const runDirFor = async (session) => {
|
|
1763
|
+
if (session !== void 0) {
|
|
1764
|
+
activeSession = session;
|
|
1765
|
+
noteScanKey(session);
|
|
1766
|
+
}
|
|
1767
|
+
const facts = scanFactsOf(session);
|
|
1768
|
+
const key = facts.key !== "" ? facts.key : AGENTLESS;
|
|
1769
|
+
const memo = resolvedRunDirs.get(key);
|
|
1770
|
+
if (memo !== void 0) return memo;
|
|
1771
|
+
const base = isAbsolute(runsRoot) ? resolve(runsRoot) : resolve(facts.cwd !== "" ? facts.cwd : process.cwd(), runsRoot);
|
|
1772
|
+
const nameBase = config.runName ?? (facts.key !== "" ? `pentest-${facts.key.replace(/[^a-zA-Z0-9]/g, "").slice(0, 8)}` : fallbackRunName);
|
|
1773
|
+
let name = nameBase;
|
|
1774
|
+
let dir = resolve(join(base, name));
|
|
1775
|
+
for (let suffix = 2; existsSync(dir); suffix++) {
|
|
1776
|
+
const info = await runInfoOf(dir);
|
|
1777
|
+
if (!info.completed && info.scanKey === key && key !== AGENTLESS) break;
|
|
1778
|
+
if (suffix === 2) ctx.logger.warn(`pentest-reporting: run dir '${name}' already holds ${info.completed ? "a completed run" : "another run"}; the new run goes to '${nameBase}-<n>'`);
|
|
1779
|
+
name = `${nameBase}-${suffix}`;
|
|
1780
|
+
dir = resolve(join(base, name));
|
|
1781
|
+
}
|
|
1782
|
+
resolvedRunDirs.set(key, dir);
|
|
1783
|
+
if (state.runName !== name) state.runName = name;
|
|
1784
|
+
state.reseatRunId();
|
|
1785
|
+
return dir;
|
|
1786
|
+
};
|
|
1787
|
+
/** Resolve the directory for the last-noted session (tools set it at execute start). */
|
|
1788
|
+
const currentRunDir = () => runDirFor(activeSession);
|
|
1789
|
+
const usageByScan = /* @__PURE__ */ new Map();
|
|
1790
|
+
const usageAccountOf = (key) => {
|
|
1791
|
+
let account = usageByScan.get(key);
|
|
1792
|
+
if (account === void 0) {
|
|
1793
|
+
account = {
|
|
1794
|
+
requests: 0,
|
|
1795
|
+
inputTokens: 0,
|
|
1796
|
+
outputTokens: 0,
|
|
1797
|
+
totalTokens: 0
|
|
1798
|
+
};
|
|
1799
|
+
usageByScan.set(key, account);
|
|
1800
|
+
}
|
|
1801
|
+
return account;
|
|
1802
|
+
};
|
|
1803
|
+
ctx.on("session/event", (session, event) => {
|
|
1689
1804
|
const record = event;
|
|
1690
1805
|
if (record.type !== "assistant/message") return;
|
|
1691
1806
|
const usage = record.data?.usage;
|
|
1692
1807
|
if (usage === void 0 || usage === null) return;
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
input_tokens: usageLedger.inputTokens,
|
|
1701
|
-
output_tokens: usageLedger.outputTokens,
|
|
1702
|
-
total_tokens: usageLedger.totalTokens,
|
|
1703
|
-
cost: null,
|
|
1704
|
-
agents: []
|
|
1808
|
+
const sessionId = typeof session?.id === "string" ? session.id : "";
|
|
1809
|
+
if (sessionId === "") return;
|
|
1810
|
+
const account = usageAccountOf(scanKeyOfSession.get(sessionId) ?? sessionId);
|
|
1811
|
+
account.requests += 1;
|
|
1812
|
+
account.inputTokens += usage.inputTokens ?? 0;
|
|
1813
|
+
account.outputTokens += usage.outputTokens ?? 0;
|
|
1814
|
+
account.totalTokens += usage.totalTokens ?? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
|
|
1705
1815
|
});
|
|
1816
|
+
const llmUsageRecord = () => {
|
|
1817
|
+
const facts = activeSession !== void 0 ? scanFactsOf(activeSession) : {
|
|
1818
|
+
id: "",
|
|
1819
|
+
key: "",
|
|
1820
|
+
cwd: ""
|
|
1821
|
+
};
|
|
1822
|
+
const account = usageAccountOf(facts.key !== "" ? facts.key : AGENTLESS);
|
|
1823
|
+
return {
|
|
1824
|
+
requests: account.requests,
|
|
1825
|
+
input_tokens: account.inputTokens,
|
|
1826
|
+
output_tokens: account.outputTokens,
|
|
1827
|
+
total_tokens: account.totalTokens,
|
|
1828
|
+
cost: null,
|
|
1829
|
+
agents: []
|
|
1830
|
+
};
|
|
1831
|
+
};
|
|
1706
1832
|
const ensureRunDir = async () => {
|
|
1707
|
-
|
|
1708
|
-
|
|
1833
|
+
const dir = await currentRunDir();
|
|
1834
|
+
await mkdir(dir, { recursive: true });
|
|
1835
|
+
return dir;
|
|
1709
1836
|
};
|
|
1710
1837
|
/** Resolve the coverage source: Config hook first, else the analysis package's service. */
|
|
1711
1838
|
const coverageSource = () => {
|
|
@@ -1789,6 +1916,10 @@ function apply(ctx, config = {}) {
|
|
|
1789
1916
|
const runRecord = {
|
|
1790
1917
|
run_id: state.runId,
|
|
1791
1918
|
run_name: state.runName,
|
|
1919
|
+
session_id: (() => {
|
|
1920
|
+
const key = scanFactsOf(activeSession).key;
|
|
1921
|
+
return key !== "" ? key : null;
|
|
1922
|
+
})(),
|
|
1792
1923
|
start_time: state.startTime,
|
|
1793
1924
|
end_time: state.endTime,
|
|
1794
1925
|
status: state.status,
|
|
@@ -1840,7 +1971,7 @@ function apply(ctx, config = {}) {
|
|
|
1840
1971
|
};
|
|
1841
1972
|
ctx.tools.register(defineTool({
|
|
1842
1973
|
name: "create_vulnerability_report",
|
|
1843
|
-
description: "File a vulnerability report — one report per fully-verified finding, with a working PoC. Severity and CVSS are computed from the 8-metric cvss_breakdown you provide. On a duplicate verdict, revise the existing report via update_vulnerability_report instead of retrying. Never for known-CVE dependency findings — use create_dependency_report.",
|
|
1974
|
+
description: "File a vulnerability report — one report per fully-verified finding, with a working PoC. Severity and CVSS are computed from the 8-metric cvss_breakdown you provide. Validation contract (checked before filing, get these right the first time): cvss_breakdown must carry all 8 CVSS v3.1 metrics (attack_vector, attack_complexity, privileges_required, user_interaction, scope, confidentiality, integrity, availability) with their exact enum values; confidence != high requires confidence_rationale; any code_locations entry with fix_after requires fix_verification; cve must match CVE-YYYY-NNNNN and cwe must be a specific CWE-NNN child. On a duplicate verdict, revise the existing report via update_vulnerability_report instead of retrying. Never for known-CVE dependency findings — use create_dependency_report.",
|
|
1844
1975
|
parameters: {
|
|
1845
1976
|
title: {
|
|
1846
1977
|
type: "string",
|
|
@@ -2002,6 +2133,7 @@ function apply(ctx, config = {}) {
|
|
|
2002
2133
|
presentationMeta: (args, value) => findingPresentationMeta(args, value)
|
|
2003
2134
|
},
|
|
2004
2135
|
execute: (async (rawArgs, rawExec) => {
|
|
2136
|
+
noteExec(rawExec);
|
|
2005
2137
|
const args = rawArgs;
|
|
2006
2138
|
const errors = [];
|
|
2007
2139
|
const breakdown = args.cvss_breakdown;
|
|
@@ -2188,6 +2320,7 @@ function apply(ctx, config = {}) {
|
|
|
2188
2320
|
presentationMeta: (args, value) => findingPresentationMeta(args, value)
|
|
2189
2321
|
},
|
|
2190
2322
|
execute: (async (rawArgs, rawExec) => {
|
|
2323
|
+
noteExec(rawExec);
|
|
2191
2324
|
const args = rawArgs;
|
|
2192
2325
|
const reportId = cleanOptional(args.report_id);
|
|
2193
2326
|
const reason = cleanOptional(args.update_reason);
|
|
@@ -2275,7 +2408,7 @@ function apply(ctx, config = {}) {
|
|
|
2275
2408
|
}));
|
|
2276
2409
|
ctx.tools.register(defineTool({
|
|
2277
2410
|
name: "create_dependency_report",
|
|
2278
|
-
description: "File a known-CVE dependency (SCA) finding — one report per CVE x package. For vulnerable third-party package versions pinned in a lockfile/manifest/SBOM; no live PoC needed. Severity comes from the contextual_cvss_breakdown when provided, else the advisory score. Never for dynamically-proven vulnerabilities — use create_vulnerability_report.",
|
|
2411
|
+
description: "File a known-CVE dependency (SCA) finding — one report per CVE x package. For vulnerable third-party package versions pinned in a lockfile/manifest/SBOM; no live PoC needed. Severity comes from the contextual_cvss_breakdown when provided, else the advisory score. Validation contract: besides the marked-required fields, manifest_path, reachability_evidence, contextual_cvss_breakdown AND contextual_cvss_reasoning are all required and must be non-empty (the '(required)' hints in their descriptions are enforced at execution time). Never for dynamically-proven vulnerabilities — use create_vulnerability_report.",
|
|
2279
2412
|
parameters: {
|
|
2280
2413
|
title: {
|
|
2281
2414
|
type: "string",
|
|
@@ -2412,7 +2545,8 @@ function apply(ctx, config = {}) {
|
|
|
2412
2545
|
duplicate_of: { type: "string" },
|
|
2413
2546
|
confidence: { type: "number" },
|
|
2414
2547
|
reason: { type: "string" },
|
|
2415
|
-
warning: { type: "string" }
|
|
2548
|
+
warning: { type: "string" },
|
|
2549
|
+
cvss_score: { type: "number" }
|
|
2416
2550
|
},
|
|
2417
2551
|
additionalProperties: false
|
|
2418
2552
|
},
|
|
@@ -2430,6 +2564,7 @@ function apply(ctx, config = {}) {
|
|
|
2430
2564
|
presentationMeta: (args, value) => findingPresentationMeta(args, value)
|
|
2431
2565
|
},
|
|
2432
2566
|
execute: (async (rawArgs, _rawExec) => {
|
|
2567
|
+
noteExec(_rawExec);
|
|
2433
2568
|
const args = rawArgs;
|
|
2434
2569
|
const errors = [];
|
|
2435
2570
|
const requireText = (value, name) => {
|
|
@@ -2596,6 +2731,7 @@ function apply(ctx, config = {}) {
|
|
|
2596
2731
|
}
|
|
2597
2732
|
},
|
|
2598
2733
|
execute: (async (rawArgs, rawExec) => {
|
|
2734
|
+
noteExec(rawExec);
|
|
2599
2735
|
const args = rawArgs;
|
|
2600
2736
|
const severityFilter = cleanOptional(args.severity)?.toLowerCase();
|
|
2601
2737
|
const classFilter = cleanOptional(args.finding_class)?.toLowerCase();
|
|
@@ -2685,6 +2821,7 @@ function apply(ctx, config = {}) {
|
|
|
2685
2821
|
}
|
|
2686
2822
|
},
|
|
2687
2823
|
execute: (async (rawArgs, rawExec) => {
|
|
2824
|
+
noteExec(rawExec);
|
|
2688
2825
|
const reportId = cleanOptional(rawArgs.report_id);
|
|
2689
2826
|
if (reportId === void 0) return {
|
|
2690
2827
|
success: false,
|
|
@@ -2758,9 +2895,22 @@ function apply(ctx, config = {}) {
|
|
|
2758
2895
|
].join("\n");
|
|
2759
2896
|
const handle = {
|
|
2760
2897
|
state,
|
|
2761
|
-
|
|
2898
|
+
/**
|
|
2899
|
+
* Sync view of the run directory: the resolved dir of the last-noted
|
|
2900
|
+
* session when one exists, else the unguarded default (no occupancy
|
|
2901
|
+
* check). Accuracy for a fresh session comes from runDirFor().
|
|
2902
|
+
*/
|
|
2903
|
+
get runDir() {
|
|
2904
|
+
const facts = scanFactsOf(activeSession);
|
|
2905
|
+
const key = facts.key !== "" ? facts.key : ":agentless";
|
|
2906
|
+
const memo = resolvedRunDirs.get(key);
|
|
2907
|
+
if (memo !== void 0) return memo;
|
|
2908
|
+
const base = isAbsolute(runsRoot) ? resolve(runsRoot) : resolve(facts.cwd !== "" ? facts.cwd : process.cwd(), runsRoot);
|
|
2909
|
+
return resolve(join(base, config.runName ?? (facts.key !== "" ? `pentest-${facts.key.replace(/[^a-zA-Z0-9]/g, "").slice(0, 8)}` : fallbackRunName)));
|
|
2910
|
+
},
|
|
2911
|
+
runDirFor: (session) => runDirFor(session),
|
|
2762
2912
|
/** Mark the scan complete and write the final report + artifacts. */
|
|
2763
|
-
async finishScan(sections, status = "completed") {
|
|
2913
|
+
async finishScan(sections, status = "completed", extras) {
|
|
2764
2914
|
state.finalScanResult = composeFinalReport(sections);
|
|
2765
2915
|
state.complete(status);
|
|
2766
2916
|
scanResults = {
|
|
@@ -2769,7 +2919,8 @@ function apply(ctx, config = {}) {
|
|
|
2769
2919
|
methodology: sections.methodology,
|
|
2770
2920
|
technical_analysis: sections.technicalAnalysis,
|
|
2771
2921
|
recommendations: sections.recommendations,
|
|
2772
|
-
success: status === "completed"
|
|
2922
|
+
success: status === "completed",
|
|
2923
|
+
...extras
|
|
2773
2924
|
};
|
|
2774
2925
|
await writeExecutiveReport(await ensureRunDir(), state.finalScanResult, formatTimestamp(/* @__PURE__ */ new Date()));
|
|
2775
2926
|
await saveArtifacts();
|
|
@@ -2778,7 +2929,7 @@ function apply(ctx, config = {}) {
|
|
|
2778
2929
|
async writeNow() {
|
|
2779
2930
|
await saveArtifacts();
|
|
2780
2931
|
},
|
|
2781
|
-
readRaw: async (relative) => readFile(join(
|
|
2932
|
+
readRaw: async (relative) => readFile(join(await currentRunDir(), relative), "utf8")
|
|
2782
2933
|
};
|
|
2783
2934
|
ctx.provide("pentestReporting", handle);
|
|
2784
2935
|
return handle;
|