Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
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
1119 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:

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

Download Tool