Skip to content

fix(security): keep paths on a short leash - #1499

Merged
clay-good merged 7 commits into
Fission-AI:mainfrom
clay-good:codex/security-hardening
Aug 4, 2026
Merged

fix(security): keep paths on a short leash#1499
clay-good merged 7 commits into
Fission-AI:mainfrom
clay-good:codex/security-hardening

Conversation

@clay-good

@clay-good clay-good commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Status: LGTM 🐕

What was wrong: Files could wander.

How it was fixed: Put every path on a leash without tripping linked monorepos.

Replication / proof: 3,527 tests pass; build, types, lint, package smoke, and audit pass.

Notes / nits: All review comments resolved.

@clay-good
clay-good requested a review from a team as a code owner August 3, 2026 20:27
@clay-good
clay-good requested review from alfred-openspec and removed request for a team August 3, 2026 20:27
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change adds canonical path validation across artifact, schema, specification, project, archive, and local-state operations. It preserves symlinks during archive fallback, protects lock ownership, and adds regression coverage for traversal and symlink escapes.

Changes

Path safety enforcement

Layer / File(s) Summary
Path validation contracts and containment checks
src/utils/file-system.ts, src/core/artifact-graph/{types,resolver}.ts, src/utils/spec-discovery.ts, src/core/specs-apply.ts, src/core/validation/validator.ts, src/commands/spec.ts, related tests
Relative paths and canonical trust roots reject traversal, absolute paths, invalid ancestors, and escaping symlinks.
Artifact and template resolution
src/core/artifact-graph/*, src/commands/schema.ts, src/commands/workflow/{instructions,templates}.ts, related tests
Artifact outputs, tracking files, templates, and schema copies use validated paths. Unsafe links, cycles, and unsupported entries are rejected.
Managed project and specification files
src/commands/change.ts, src/core/{init,update}.ts, related tests
Change and generated-artifact operations validate paths before reads, writes, creation, or deletion. Failed tool operations now reject after reporting results.
Archive and local-state safety
src/core/{archive,file-state}.ts, related tests, .changeset/tidy-path-leash.md
Archive fallback preserves symlinks and validates roots and change names. Lock files and atomic state files use mode 600, and release checks ownership tokens.

Estimated code review effort: 5 (Critical) | ~90 minutes

Possibly related PRs

Suggested reviewers: tabishb, alfred-openspec

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the security-focused path-handling changes in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
test/core/archive.test.ts (1)

157-179: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add coverage for an OpenSpec root accessed through a symlink alias.

ArchiveCommand canonicalizes existing parent directories during assertPathWithin, so a root symlink like macOS /tmp/private/tmp would not trigger archive_path_outside_root. This test only covers a symlinked archiveDir inside the managed tree; add a regression that creates the OpenSpec structure under a symlink alias and archives normally.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/core/archive.test.ts` around lines 157 - 179, Add a regression test
alongside the existing archive tests that creates the OpenSpec directory
structure through a symlinked root alias, prepares a change, and invokes
archiveCommand.execute normally. Assert the archive succeeds and the expected
change is archived, covering root canonicalization without triggering
archive_path_outside_root; skip the test on Windows if symlink support is
unavailable.

Source: Path instructions

src/commands/workflow/templates.ts (1)

70-79: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Wrap the containment error for a friendlier CLI message.

assertPathWithin throws directly inside .map(). This error propagates uncaught to the top-level catch in templatesCommand, which surfaces the raw message ("Path is outside the allowed directory: ...") instead of an actionable, per-artifact message. src/commands/schema.ts's validateSchema catches the same kind of error and reports which artifact and template caused it.

Consider catching per-artifact here too, so users see which artifact's template escaped the schema directory.

♻️ Proposed fix for friendlier per-artifact error
     const templatesDir = path.join(schemaDir, 'templates');
     const templates: TemplateInfo[] = graph.getAllArtifacts().map((artifact) => {
       const templatePath = path.join(templatesDir, artifact.template);
-      FileSystemUtils.assertPathWithin(templatesDir, templatePath);
+      try {
+        FileSystemUtils.assertPathWithin(templatesDir, templatePath);
+      } catch {
+        throw new Error(
+          `Template '${artifact.template}' for artifact '${artifact.id}' points outside the schema templates directory`
+        );
+      }
       return {
         artifactId: artifact.id,
         templatePath: FileSystemUtils.canonicalizeExistingPath(templatePath),
         source,
       };
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/workflow/templates.ts` around lines 70 - 79, Update the artifact
mapping in templatesCommand so errors from assertPathWithin or
canonicalizeExistingPath are caught per artifact and rethrown with the artifact
ID and template name, matching validateSchema’s actionable error style. Preserve
successful template generation and allow the top-level command handler to report
the contextualized message.
src/utils/file-system.ts (1)

107-173: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Correct containment and dangling-symlink logic.

The two-phase check (lexical, then canonical) correctly rejects paths that escape allowedDirectory directly, through an intermediate symlink, or through a dangling symlink anywhere in the missing-suffix walk. Manual tracing of parent-escape, dangling-leaf, and dangling-intermediate-segment scenarios confirms the logic holds.

One optional readability note: canonicalizePotentialPath nests two try/catch blocks and three exit conditions in a single loop. Consider extracting the dangling-symlink check into a small named helper (for example isDanglingSymlink(path)) to make the loop body easier to scan, since this function guards a security-sensitive path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/file-system.ts` around lines 107 - 173, Optionally improve
readability in FileSystem.canonicalizePotentialPath by extracting the nested
dangling-symlink detection into a small named helper, such as isDanglingSymlink,
and use it during the missing-segment walk while preserving the existing
rejection behavior for dangling links and resolution errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/archive.ts`:
- Around line 317-331: Extend the existing containment-validation loop in the
archive flow to also validate the per-change `changeDir` against `changesDir`
before the `fs.stat` and subsequent processing. Reuse the same
`FileSystemUtils.assertPathWithin` and `ArchiveBlockedError` handling used for
`archiveDir` and the other managed directories, ensuring symlinked change
directories outside `changesDir` are rejected.

In `@src/core/artifact-graph/resolver.ts`:
- Around line 95-104: The resolver’s lexical name validation does not prevent
schema-directory symlink escapes. In resolveSchema() and the related
loadTemplate() path, canonicalize the candidate schema.yaml path and its
corresponding schema root, then verify the canonical file remains within that
root before returning the schema directory; reject candidates that resolve
outside their configured root while preserving existing invalid-name handling.

In `@test/core/artifact-graph/instruction-loader.test.ts`:
- Around line 45-64: Add a Windows-specific test alongside should reject a
template symlink that escapes its schema that passes a template name containing
..\\ to loadTemplate and asserts it throws TemplateLoadError. Do not skip this
case on Windows; retain the existing platform guard only for symlink behavior,
and use the same temporary schema setup and cleanup pattern.

---

Nitpick comments:
In `@src/commands/workflow/templates.ts`:
- Around line 70-79: Update the artifact mapping in templatesCommand so errors
from assertPathWithin or canonicalizeExistingPath are caught per artifact and
rethrown with the artifact ID and template name, matching validateSchema’s
actionable error style. Preserve successful template generation and allow the
top-level command handler to report the contextualized message.

In `@src/utils/file-system.ts`:
- Around line 107-173: Optionally improve readability in
FileSystem.canonicalizePotentialPath by extracting the nested dangling-symlink
detection into a small named helper, such as isDanglingSymlink, and use it
during the missing-segment walk while preserving the existing rejection behavior
for dangling links and resolution errors.

In `@test/core/archive.test.ts`:
- Around line 157-179: Add a regression test alongside the existing archive
tests that creates the OpenSpec directory structure through a symlinked root
alias, prepares a change, and invokes archiveCommand.execute normally. Assert
the archive succeeds and the expected change is archived, covering root
canonicalization without triggering archive_path_outside_root; skip the test on
Windows if symlink support is unavailable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3148a44e-1fdd-45cf-b00a-6254ac425dc9

📥 Commits

Reviewing files that changed from the base of the PR and between 45cca5d and efa0745.

📒 Files selected for processing (16)
  • .changeset/tidy-path-leash.md
  • src/commands/schema.ts
  • src/commands/workflow/instructions.ts
  • src/commands/workflow/templates.ts
  • src/core/archive.ts
  • src/core/artifact-graph/index.ts
  • src/core/artifact-graph/instruction-loader.ts
  • src/core/artifact-graph/outputs.ts
  • src/core/artifact-graph/resolver.ts
  • src/core/artifact-graph/types.ts
  • src/utils/file-system.ts
  • test/core/archive.test.ts
  • test/core/artifact-graph/instruction-loader.test.ts
  • test/core/artifact-graph/outputs.test.ts
  • test/core/artifact-graph/resolver.test.ts
  • test/core/artifact-graph/schema.test.ts

Comment thread src/core/archive.ts
Comment thread src/core/artifact-graph/resolver.ts
Comment thread test/core/artifact-graph/instruction-loader.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
test/core/artifact-graph/resolver.test.ts (1)

141-151: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Exercise resolveSchema for the escaping-file case.

This regression checks only getSchemaDir(). Add an assertion through resolveSchema() so the public schema-loading path also proves that the containment guard rejects the symlink.

Suggested regression assertion
       expect(getSchemaDir('linked-file', tempDir)).toBeNull();
+      expect(() => resolveSchema('linked-file', tempDir)).toThrow(/not found/u);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/core/artifact-graph/resolver.test.ts` around lines 141 - 151, Extend the
escaping-file symlink test to also call the public resolveSchema function with
the linked schema identifier and tempDir, asserting that it rejects the escaping
schema consistently with getSchemaDir returning null. Keep the existing setup
and platform guard unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@test/core/artifact-graph/resolver.test.ts`:
- Around line 141-151: Extend the escaping-file symlink test to also call the
public resolveSchema function with the linked schema identifier and tempDir,
asserting that it rejects the escaping schema consistently with getSchemaDir
returning null. Keep the existing setup and platform guard unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e047eacf-2f25-4bef-a461-57e10f1fab88

📥 Commits

Reviewing files that changed from the base of the PR and between efa0745 and bae3d44.

📒 Files selected for processing (9)
  • .changeset/tidy-path-leash.md
  • src/commands/schema.ts
  • src/commands/workflow/templates.ts
  • src/core/archive.ts
  • src/core/artifact-graph/resolver.ts
  • test/commands/schema.test.ts
  • test/core/archive.test.ts
  • test/core/artifact-graph/instruction-loader.test.ts
  • test/core/artifact-graph/resolver.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/commands/workflow/templates.ts
  • test/core/artifact-graph/instruction-loader.test.ts
  • .changeset/tidy-path-leash.md
  • src/core/archive.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/commands/spec.ts (1)

112-119: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Close the check-then-read window at the reported sites. Each site resolves the file path, runs a containment check once, and then reads the file later through a separate call. A symlink swapped during the async gap can bypass the earlier containment guarantee. Revalidate the resolved path immediately before each read and normalize the canonical trust root before reading files under symlinked capability directories.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/spec.ts` around lines 112 - 119, Close the check-then-read
window by revalidating each resolved path immediately before its file read and
normalizing the canonical trust root first. Apply this to src/commands/spec.ts
lines 112-119 and 248-253, src/commands/change.ts lines 82-136, and
src/core/validation/validator.ts lines 285-302; preserve existing not-found and
validation behavior while ensuring reads under symlinked capability directories
remain contained.
🧹 Nitpick comments (1)
test/core/init.test.ts (1)

160-162: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Strengthen the test oracle for both symlink cases.

The assertions only prove that the external target was not changed. They do not prove whether InitCommand.execute rejected the unsafe path or completed the intended safe behavior. Assert the expected command result and local filesystem state. If rejection is the contract, assert the rejection. If safe fallback is the contract, assert the fallback artifact and symlink state.

Also applies to: 179-182

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/core/init.test.ts` around lines 160 - 162, Strengthen both symlink-case
tests around InitCommand.execute by asserting the command’s expected outcome,
not only that outsideDir remains unchanged. If unsafe paths must be rejected,
assert the execute call rejects; otherwise assert the intended local fallback
artifact and resulting symlink state, while preserving the external-target
safety assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/init.ts`:
- Around line 67-80: Move resolveProjectArtifactPath and
assertProjectArtifactPath into src/utils/file-system.ts as the shared
FileSystemUtils methods or exported functions, preserving their existing
containment and absolute-path checks. Remove both local helper pairs from
src/core/init.ts (lines 67-80) and src/core/update.ts (lines 74-87), and update
each file to import and use the shared implementation.

---

Outside diff comments:
In `@src/commands/spec.ts`:
- Around line 112-119: Close the check-then-read window by revalidating each
resolved path immediately before its file read and normalizing the canonical
trust root first. Apply this to src/commands/spec.ts lines 112-119 and 248-253,
src/commands/change.ts lines 82-136, and src/core/validation/validator.ts lines
285-302; preserve existing not-found and validation behavior while ensuring
reads under symlinked capability directories remain contained.

---

Nitpick comments:
In `@test/core/init.test.ts`:
- Around line 160-162: Strengthen both symlink-case tests around
InitCommand.execute by asserting the command’s expected outcome, not only that
outsideDir remains unchanged. If unsafe paths must be rejected, assert the
execute call rejects; otherwise assert the intended local fallback artifact and
resulting symlink state, while preserving the external-target safety assertion.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d3b51add-52eb-487a-88f3-41f460ad6158

📥 Commits

Reviewing files that changed from the base of the PR and between 4408e7c and 11a99eb.

📒 Files selected for processing (22)
  • .changeset/tidy-path-leash.md
  • src/commands/change.ts
  • src/commands/spec.ts
  • src/core/archive.ts
  • src/core/artifact-graph/instruction-loader.ts
  • src/core/file-state.ts
  • src/core/init.ts
  • src/core/project-config.ts
  • src/core/specs-apply.ts
  • src/core/update.ts
  • src/core/validation/validator.ts
  • src/utils/spec-discovery.ts
  • test/core/archive.test.ts
  • test/core/artifact-graph/instruction-loader.test.ts
  • test/core/commands/change-command.show-validate.test.ts
  • test/core/commands/spec-command.security.test.ts
  • test/core/file-state.test.ts
  • test/core/init.test.ts
  • test/core/project-config.test.ts
  • test/core/specs-apply.security.test.ts
  • test/core/update.test.ts
  • test/utils/spec-discovery.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • test/core/artifact-graph/instruction-loader.test.ts
  • .changeset/tidy-path-leash.md
  • src/core/archive.ts
  • src/core/artifact-graph/instruction-loader.ts

Comment thread src/core/init.ts Outdated
alfred-openspec
alfred-openspec previously approved these changes Aug 3, 2026

@alfred-openspec alfred-openspec left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at exact head afc9df2. The path-containment primitive, linked-capability boundary, caller-side rechecks, schema/output confinement, and lock ownership changes are coherent. Isolated build plus 514 focused security, archive, init/update, artifact-graph, config, and discovery tests pass; hosted CI/security is green.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/core/validation/validator.ts (1)

287-303: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Use options.mainSpecsDir as the confinement root.

path.dirname(mainSpecFile) is derived from mainSpecFile itself. The lexical check then always passes, because path.relative(dirname(x), x) is spec.md. The canonical check only rejects a spec.md that is itself a symlink out of its own capability directory. Neither check confines specId to options.mainSpecsDir, so a capability directory that is a symlink to an outside tree still resolves and is read.

Pass options.mainSpecsDir as the root. The same value flows into findScenarioLossIssues as mainSpecRoot (Line 300), so the assertion at Line 458 inherits the same weakness and is fixed by this change.

🔒️ Proposed fix
           const mainSpecFile = path.join(
             options.mainSpecsDir,
             ...specId.split('/'),
             'spec.md'
           );
-          FileSystemUtils.assertPathWithin(path.dirname(mainSpecFile), mainSpecFile);
+          FileSystemUtils.assertPathWithin(options.mainSpecsDir, mainSpecFile);
           issues.push(
             ...(await this.findScenarioLossIssues(
               plan.modified,
               plan.renamed,
               mainSpecFile,
               entryPath,
-              path.dirname(mainSpecFile)
+              options.mainSpecsDir
             ))
           );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/validation/validator.ts` around lines 287 - 303, Use
options.mainSpecsDir as the confinement root in the assertPathWithin call for
mainSpecFile, rather than path.dirname(mainSpecFile). Keep passing
options.mainSpecsDir as mainSpecRoot to findScenarioLossIssues so its assertion
uses the same root and rejects capability directories that resolve outside the
configured main specs directory.
🧹 Nitpick comments (2)
src/core/artifact-graph/outputs.ts (1)

19-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider hiding the recursion state behind a small wrapper.

Four of the seven parameters are internal recursion state (segmentIndex, visited, canonicalChangeDir, ancestors). A future caller can pass a canonicalChangeDir that does not match changeDir and silently widen the allowed root. Wrap the recursion in a private inner function that closes over canonicalChangeDir, visited, and ancestors, and expose only (changeDir, directorySegments).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/artifact-graph/outputs.ts` around lines 19 - 37, Refactor
assertGlobDirectoryTraversal so its public signature accepts only changeDir and
directorySegments, moving the recursive traversal into a private inner function
that closes over canonicalChangeDir, visited, and ancestors. Keep segmentIndex
as inner recursion state, and ensure canonicalChangeDir is always derived from
the supplied changeDir so callers cannot override the traversal root.
test/commands/schema.test.ts (1)

320-322: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use it.skipIf for the platform guard.

if (process.platform === 'win32') return; reports the test as passed on Windows. it.skipIf(process.platform === 'win32') reports it as skipped, which matches the pattern used in test/core/commands/spec-command.security.test.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/commands/schema.test.ts` around lines 320 - 322, Replace the early
platform return in the test “should dereference a confined template link into an
independent fork” with the test framework’s it.skipIf guard for Windows,
preserving the existing test body while reporting the case as skipped rather
than passed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/schema.ts`:
- Around line 247-256: Update resolveSchemaCopyPath so containment errors from
FileSystemUtils.assertPathWithin and filesystem errors from fs.realpathSync are
not replaced by the generic message. Preserve and propagate the original error
cause/message while retaining the existing successful canonical-path behavior.

---

Outside diff comments:
In `@src/core/validation/validator.ts`:
- Around line 287-303: Use options.mainSpecsDir as the confinement root in the
assertPathWithin call for mainSpecFile, rather than path.dirname(mainSpecFile).
Keep passing options.mainSpecsDir as mainSpecRoot to findScenarioLossIssues so
its assertion uses the same root and rejects capability directories that resolve
outside the configured main specs directory.

---

Nitpick comments:
In `@src/core/artifact-graph/outputs.ts`:
- Around line 19-37: Refactor assertGlobDirectoryTraversal so its public
signature accepts only changeDir and directorySegments, moving the recursive
traversal into a private inner function that closes over canonicalChangeDir,
visited, and ancestors. Keep segmentIndex as inner recursion state, and ensure
canonicalChangeDir is always derived from the supplied changeDir so callers
cannot override the traversal root.

In `@test/commands/schema.test.ts`:
- Around line 320-322: Replace the early platform return in the test “should
dereference a confined template link into an independent fork” with the test
framework’s it.skipIf guard for Windows, preserving the existing test body while
reporting the case as skipped rather than passed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4eeaa073-4ef5-4cd8-abce-0b07f62304b1

📥 Commits

Reviewing files that changed from the base of the PR and between 11a99eb and 7e26b58.

📒 Files selected for processing (21)
  • src/commands/change.ts
  • src/commands/schema.ts
  • src/commands/spec.ts
  • src/core/archive.ts
  • src/core/artifact-graph/outputs.ts
  • src/core/file-state.ts
  • src/core/init.ts
  • src/core/specs-apply.ts
  • src/core/update.ts
  • src/core/validation/validator.ts
  • src/utils/file-system.ts
  • src/utils/spec-discovery.ts
  • test/commands/schema.test.ts
  • test/core/archive.test.ts
  • test/core/artifact-graph/outputs.test.ts
  • test/core/commands/spec-command.security.test.ts
  • test/core/file-state.test.ts
  • test/core/init.test.ts
  • test/core/specs-apply.security.test.ts
  • test/core/update.test.ts
  • test/utils/spec-discovery.test.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/utils/spec-discovery.ts
  • src/core/specs-apply.ts
  • src/core/file-state.ts
  • src/core/archive.ts
  • test/core/file-state.test.ts
  • src/commands/spec.ts
  • src/core/init.ts
  • src/core/update.ts

Comment thread src/commands/schema.ts

@alfred-openspec alfred-openspec left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at exact head 6e4dfce. The follow-up preserves documented linked monorepo workflows while still confining nested spec, schema, archive, and glob targets, and fork failures now retain the underlying cause. Fresh isolated build plus 353 focused security, archive, schema, init/update, apply, discovery, and lock tests pass; hosted CI/security is green.

@clay-good
clay-good force-pushed the codex/security-hardening branch from 3c7a3d4 to e364336 Compare August 4, 2026 18:21
@clay-good
clay-good added this pull request to the merge queue Aug 4, 2026
Merged via the queue into Fission-AI:main with commit 9cd845f Aug 4, 2026
13 checks passed
@clay-good
clay-good deleted the codex/security-hardening branch August 4, 2026 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants