Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
openlore-PoC — PoC — path traversal via unsanitized LLM-derived domain field in OpenLore (GHSA-5j8x-q7q6-58j5, CVE-2026-87001, CVSS 4.7). | Kitploit
Tools/GitHubGitHub/squeeze440/openlore-poc
Vulnerability AnalysisCode AnalysisExploitationWeb Application ExploitationPenetration TestingPapers & Research
GitHubsqueeze440/openlore-poc

openlore-PoC

PoC — path traversal via unsanitized LLM-derived domain field in OpenLore (GHSA-5j8x-q7q6-58j5, CVE-2026-87001, CVSS 4.7).

View Repository
7 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

Path Traversal in OpenLore Spec-Generation Writer via Unsanitized LLM-Derived domain Field

CVE status: requested, pending assignment. This finding is published as GHSA-5j8x-q7q6-58j5. On CVE assignment this repository is renamed CVE-YYYY-NNNNN-openlore-PoC and this banner is replaced with the CVE link.

ResearcherDostxodjayev Abdullox (@squeeze440)
AdvisoryGHSA-5j8x-q7q6-58j5
CVSS 3.14.7 (Medium)
WeaknessCWE-22, CWE-73

Path Traversal in OpenLore Spec-Generation Writer via Unsanitized LLM-Derived domain Field

Summary: An attacker who controls the content of a repository analyzed by openlore generate can cause the OpenSpec spec-generation pipeline to write attacker-influenced Markdown to an arbitrary filesystem path outside the target project, because the domain value returned by the Stage-3 LLM extraction call is used verbatim to build a spec's output path with no traversal sanitization and no confinement check before fs.writeFile.

Product

OpenLore (clay-good/OpenLore), npm package openlore, spec-generation pipeline (openlore generate / the OpenSpec-format generator + writer). Not the deterministic analyze/orient/MCP guardrail path — this is the one code path in the project that intentionally puts an LLM in the loop for generation, per the project's own README/spec ("an LLM is used only where generation is unavoidable (spec authoring), never in the retrieval or guardrail path").

Tested Version

Git commit 1f2de00dfe75530efb256320c374dce36607ee70 (2026-07-31), package.json version 2.1.7.

Estimated CVSS v3.1

4.7 (Medium) — CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N

  • AV:L — the trigger is a local CLI invocation (openlore generate) against a directory on disk, not a network service.
  • AC:H — exploitation needs the operator to run the LLM-backed generate command (not the default guardrail path) with a provider configured, and needs the analyzed repository's indirect prompt injection to actually make the model emit a traversal string in the domain field — a condition the attacker influences but does not fully control.
  • UI:R — the operator must choose to run spec generation against the malicious repository.
  • I:H / C:N / A:N — the bug is a write primitive with partially attacker-templated content landing at an attacker-chosen path outside the project; it was not demonstrated to read confidential data or to reliably degrade availability, so those metrics are conservatively N.

Details

ExtractedService.domain (src/types/pipeline.ts:105) is populated straight from the Stage 3 LLM call, which is fed raw file content from the repository being analyzed and asked to return a JSON array of services:

  • src/core/generator/stages/stage3-services.ts:45-52 builds the prompt from fileChunks[i] (literal source text of the analyzed repo) and calls pipeline.llm.completeJSON<ExtractedService[]>(..., STAGE3_SERVICE_SCHEMA).
  • src/core/generator/schemas.ts:100 — STAGE3_SERVICE_SCHEMA.items.properties.domain is { type: 'string' }: no pattern, no enum, no allowlist. Any string the model returns is accepted.
  • src/core/generator/openspec-format-generator.ts:161 — const domainName = service.domain || this.inferDomain(...) uses that string as-is.
  • src/core/generator/openspec-format-generator.ts:563 — path: \openspec/specs/${domain.name.toLowerCase()}/spec.md`interpolates it directly into the spec's declared output path. NotenormalizeDomainName() (src/core/generator/openspec-compat.ts:655) exists in the same file's dependency graph and IS applied elsewhere (src/core/generator/openspec-writer.ts:167`, for stale-domain-directory comparison) — but it is never called here, at the one place the value actually becomes a filesystem path.
  • src/core/generator/openspec-writer.ts:248 — const fullPath = join(this.rootPath, spec.path); uses plain node:path.join, which lexically resolves ../ segments, with no call to the project's own safeJoin() (src/utils/path-confinement.ts) that every other untrusted-path surface in this codebase (the MCP handlers, openlore view's /api/skeleton and /api/spec-requirements) is required to route through per openspec/specs/mcp-security/spec.md.
  • src/core/generator/openspec-writer.ts:277 / :294 then writeFile(fullPath, spec.content, 'utf-8'), and ensureDir() (openspec-writer.ts:497-499) does mkdir(dirname(filePath), { recursive: true }) on the same unconfined path, creating any missing intermediate directories on the way out of the project root.

Net effect: a domain value such as ../../../../../../tmp/x survives untouched from the LLM's JSON response to a real writeFile call, landing outside the analyzed project entirely. This is the exact "repo content redirects a write outside the project root" threat model the project's own openspec/specs/mcp-security/spec.md explicitly defines and hardens for on the MCP/serve/view surfaces (safeJoin, symlink-aware confinement, path-parameter coverage gate) — the generator/writer surface simply has no equivalent gate.

Proof of Concept

No live LLM call was made (model compliance with an injected instruction is probabilistic and not the interesting part of this bug); instead the PoC calls OpenLore's real, unmodified OpenSpecFormatGenerator and OpenSpecWriter with a PipelineResult whose one ExtractedService.domain field holds exactly the string shape STAGE3_SERVICE_SCHEMA allows an LLM to return today, and exactly what a source-file comment instructing "set domain to ../../../.../tmp/x" could elicit from Stage 3 (which passes raw file content to the model).

Script: ~/engagements/openlore/source/poc-domain-traversal.ts (repo root of the tested clone), run against a scratch project at ~/engagements/openlore/evidence/victim-project:

root@kitploit:~
cd ~/engagements/openlore/source
npx tsx poc-domain-traversal.ts

Result: OpenSpecFormatGenerator.generateSpecs() (unmodified) emits a domain spec with path: "openspec/specs/../../../../../../../../../../../../../../../../../../../../tmp/openlore_traversal_proof/spec.md", and OpenSpecWriter.writeSpecs() (unmodified) writes it — landing at /tmp/openlore_traversal_proof/spec.md, fully outside the victim-project root, while victim-project/openspec/specs/ only ever contains the two legitimate overview/architecture directories.

Screenshots (genuine Xvfb+fluxbox+xterm captures, scrot -w <window id>):

  • ../evidence/poc-generator-write.png — the PoC run: generator's raw spec.path containing the traversal, the writer log line Wrote openspec/specs/../../.../tmp/openlore_traversal_proof/spec.md, and the script's own confirmation that /tmp/openlore_traversal_proof/spec.md exists with attacker-influenced content.
  • ../evidence/poc-outside-fs-proof.png — independent confirmation: ls of victim-project/openspec/specs/ shows only architecture/overview (no trace of the malicious domain inside the project), while ls -la /tmp/openlore_traversal_proof/ shows the escaped spec.md sitting directly under /tmp.

Impact

An attacker who can get a target to run openlore generate (LLM-backed spec generation) against a repository they authored — a plausible scenario for a CLI tool whose explicit purpose is to be pointed at third-party/untrusted codebases — can write a file with attacker-influenced content to any path the operator's OS user can write to, limited only by how many ../ segments are needed to escape and by filesystem permissions. The write is not confirmed to be directly executable, but a sufficiently shallow project path could let the traversal reach locations that are executed or auto-loaded by other tools on the same account (shell rc files, cron user directories, editor/IDE config), which was not tested and is not claimed here.

Weaknesses

  • CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
  • CWE-73: External Control of File Name or Path
  • CWE-20: Improper Input Validation (root cause — the LLM's JSON output is trusted as if it were internally generated)

Remediation

  1. Apply the existing normalizeDomainName() (or an equivalent allowlist regex, e.g. ^[a-z][a-z0-9-]{0,63}$) to domain.name / service.domain at the point they are first accepted from the LLM response (groupByDomain() in openspec-format-generator.ts), not only at the later stale-directory-comparison site.
  2. Route OpenSpecWriter.writeSpec()'s fullPath computation through the project's own safeJoin() (src/utils/path-confinement.ts) instead of plain node:path.join, matching the confinement already required of every other untrusted-path surface in this codebase.
  3. Add a pattern constraint to STAGE3_SERVICE_SCHEMA.domain (and any other schema field later used to build a filesystem path) so providers that enforce structured-output schemas reject non-conforming values outright.
  4. Add a regression test — in the spirit of the existing MCP "Path-Parameter Coverage Gate" — asserting that a GeneratedSpec.path containing .. or resolving outside rootPath is rejected before OpenSpecWriter touches the filesystem.

Credit

Dostxodjayev Abdullox

Reporting Channel

GitHub private vulnerability reporting is enabled for this repository: https://github.com/clay-good/OpenLore/security/advisories/new (standard GHSA flow). Confirmed via gh api repos/clay-good/OpenLore/security-advisories returning [] (no prior advisories) prior to this audit.

Download Tool