Skip to content

fix(greenfield): isolate sealer verification environment - #112

Closed
aryeko wants to merge 4 commits into
mainfrom
fix/isolated-sealer-home
Closed

fix(greenfield): isolate sealer verification environment#112
aryeko wants to merge 4 commits into
mainfrom
fix/isolated-sealer-home

Conversation

@aryeko

@aryeko aryeko commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Purpose

Fix the delivery-package sealer so arbitrary verification commands cannot inherit or log owner-home credentials.

Scope

  • isolates HOME/XDG/global Git/npm user config for preflight and caller-supplied verification commands
  • limits the owner-authorized environment exception to the fixed, non-shell dependency setup (--frozen-lockfile --offline --ignore-scripts --ignore-pnpmfile)
  • fails closed on configDependencies, including quoted, decoded, and ambiguous YAML forms
  • adds hostile pnpmfile/lifecycle, secret-absence, and configuration-guard regressions

Exact candidate evidence

  • head: 589540b20949b44f5dd0d59bb2827d1d8ad861db
  • tree: 64da452bd7d584c8b14dcbb7c26f2eb6554b9de9
  • base: 4b067f46d3141f741c95966d92eee50abb67be3a / 7c37ce192d905e5afd4a2bf2a108a0d890397db8
  • seal: /private/tmp/jig-seal-isolated-home-589540b2/envelope.json (valid: true; setup, diff check, and pnpm check each passed once)
  • independent Protocol B verdict: PASS

This is a prerequisite correction only; it enables no provider, effect, consumer, or public surface.

Summary by CodeRabbit

  • Security Enhancements

    • Strengthened verification isolation by using a temporary home directory and controlled package-manager cache.
    • Prevented inherited credentials, lifecycle scripts, and owner-specific data from affecting candidate verification.
    • Added fail-closed checks for ambiguous workspace configuration and dependency settings.
  • Reliability

    • Added offline, frozen dependency setup with timing and exit-status recording.
    • Expanded validation coverage for escaped, quoted, and merged workspace configuration keys.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Verification now runs in per-run temporary homes with staged Corepack pnpm. Workspace configuration is validated before offline frozen dependency setup, and sealing records the dedicated setup observation. Tests cover credential isolation, hook suppression, Corepack staging, and fail-closed config dependency cases.

Changes

Candidate verification isolation

Layer / File(s) Summary
Per-run verification environment
scripts/seal-candidate.mjs
Commands use a temporary HOME, filtered environment variables, staged Corepack pnpm data, and cleanup after execution.
Validated offline dependency setup
scripts/seal-candidate.mjs
Workspace YAML keys are validated before running offline, frozen-lockfile pnpm install with scripts and pnpmfile processing disabled; sealing uses the recorded setup observation.
Isolation and fail-closed regression coverage
scripts/seal-candidate.test.mjs
Tests verify token isolation, Corepack staging, hook suppression, locked offline setup, and owner-decision failures for ambiguous or encoded config dependencies.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant seal
  participant runCommand
  participant CorepackCache
  participant pnpm
  participant Evidence
  seal->>runCommand: execute verification command
  runCommand->>CorepackCache: stage pinned pnpm in temporary HOME
  runCommand->>pnpm: run with filtered environment
  pnpm-->>runCommand: return command result
  runCommand->>Evidence: record isolated command observation
  seal->>pnpm: run offline frozen dependency setup
  pnpm-->>seal: return setup timing and exit details
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 accurately captures the main change: isolating the sealer's verification environment.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/isolated-sealer-home

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 589540b209

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +306 to +308
const result = spawnSync(
'pnpm',
['install', '--frozen-lockfile', '--offline', '--ignore-scripts', '--ignore-pnpmfile'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Pin the setup package-manager executable

When pnpm on PATH is a Corepack shim and the owner has COREPACK_ENABLE_UNSAFE_CUSTOM_URLS=1, a candidate can change its packageManager to pnpm@<URL> and this setup will download and execute that candidate-selected package manager with the owner's complete environment and HOME before --offline or the script-suppression flags reach pnpm. I checked the installed Corepack parseSpec implementation: its custom-URL guard is explicitly disabled by that inherited variable. Validate the repository's exact package-manager pin and invoke a trusted binary under a sanitized setup environment instead, or a candidate can read/exfiltrate owner credentials during sealing.

AGENTS.md reference: AGENTS.md:L64-L64

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
scripts/seal-candidate.mjs (1)

303-325: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Document why this call deliberately inherits the owner environment.

Unlike runCommand, this spawnSync intentionally omits env and shell so the owner's registry/cache config is available, and safety rests entirely on the fixed argv (--offline --frozen-lockfile --ignore-scripts --ignore-pnpmfile) plus the configDependencies guard. That invariant is invisible here; a one-line comment makes it much harder for a later change to add shell: true or an interpolated argument and silently hand the owner environment to candidate-controlled code.

🤖 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 `@scripts/seal-candidate.mjs` around lines 303 - 325, In
dependencySetupObservation, add a concise comment before the spawnSync call
documenting that it intentionally inherits the owner environment and omits env
and shell; state that safety depends on the fixed offline, frozen-lockfile,
ignore-scripts, and ignore-pnpmfile arguments plus
assertSetupDoesNotUseConfigDependencies. Keep the implementation unchanged.
scripts/seal-candidate.test.mjs (3)

319-355: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

No positive control for a valid pnpm-workspace.yaml.

All five cases assert rejection; none assert that a normal workspace file still seals. That gap is what lets the over-broad root-key check in scripts/seal-candidate.mjs (Lines 293-300) reject idiomatic YAML unnoticed. Add a case with a workspace file whose packages sequence entries sit at column zero and assert seal.valid === true.

🤖 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 `@scripts/seal-candidate.test.mjs` around lines 319 - 355, Add a
positive-control test near the existing config-dependency rejection cases, using
the fixture helper with a valid pnpm-workspace.yaml containing a top-level
packages sequence whose entries are at column zero, and assert that the
resulting seal has valid === true. Keep the test focused on confirming idiomatic
workspace YAML is accepted.

55-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

pnpm install here can reach the network and repeats the version pin.

Line 74 omits --offline, so on a cold store this lockfile generation performs registry resolution and the test becomes network-dependent even though the dependency is file:-local. --offline (or --prefer-offline) keeps it hermetic. The pnpm@11.9.0 literal at Line 64 also duplicates the pin in scripts/seal-candidate.mjs; hoisting it to a single constant read from the root package.json avoids drift across both files.

🤖 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 `@scripts/seal-candidate.test.mjs` around lines 55 - 75, Update
addLockedLocalDependency to run pnpm install with --offline so lockfile
generation remains network-independent. Remove the duplicated pnpm@11.9.0
literal by sourcing the package-manager version from the root package.json
through a shared constant or equivalent used by both seal-candidate test and
implementation flows.

248-291: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

These tests fail for environmental reasons, not defects.

Two ambient assumptions:

  • Lines 255 and 287 hard-assert that a Corepack pnpm payload is already cached in the owner's home. On a fresh checkout or a CI image that has never run corepack, both tests fail even though the sealer is correct. Staging the payload is a precondition, not the behavior under test — prefer t.skip() when the source directory is absent, or provision it explicitly.
  • Line 251 does join(environmentValue('HOME'), '.cache') and Line 284 does join(environmentValue('HOME'), ...) with no fallback; an unset HOME throws TypeError from path.join. stageCorepackPnpm in scripts/seal-candidate.mjs guards this with ?? tmpdir(). Line 284 also ignores XDG_CACHE_HOME, diverging from Line 251 and from the source lookup order.
🤖 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 `@scripts/seal-candidate.test.mjs` around lines 248 - 291, Update the Corepack
cache setup in both tests to resolve the source using XDG_CACHE_HOME first, then
HOME with a tmpdir fallback, matching stageCorepackPnpm. Treat a missing pnpm
payload as an unmet test precondition by skipping the test rather than asserting
existsSync true; adjust the test callbacks as needed to access the skip
mechanism while preserving the existing staging and credential assertions.
🤖 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 `@scripts/seal-candidate.mjs`:
- Around line 239-301: Update assertSetupDoesNotUseConfigDependencies in
scripts/seal-candidate.mjs (lines 239-301) to skip root-indentation lines that
are exactly "-" or begin with "- " before calling yamlRootMappingKey, while
retaining fail-closed handling for ambiguous mapping keys. Add a positive
regression case in scripts/seal-candidate.test.mjs (lines 319-355) with
packages: and column-zero sequence entries, asserting seal.valid is true and
setupError is absent.

---

Nitpick comments:
In `@scripts/seal-candidate.mjs`:
- Around line 303-325: In dependencySetupObservation, add a concise comment
before the spawnSync call documenting that it intentionally inherits the owner
environment and omits env and shell; state that safety depends on the fixed
offline, frozen-lockfile, ignore-scripts, and ignore-pnpmfile arguments plus
assertSetupDoesNotUseConfigDependencies. Keep the implementation unchanged.

In `@scripts/seal-candidate.test.mjs`:
- Around line 319-355: Add a positive-control test near the existing
config-dependency rejection cases, using the fixture helper with a valid
pnpm-workspace.yaml containing a top-level packages sequence whose entries are
at column zero, and assert that the resulting seal has valid === true. Keep the
test focused on confirming idiomatic workspace YAML is accepted.
- Around line 55-75: Update addLockedLocalDependency to run pnpm install with
--offline so lockfile generation remains network-independent. Remove the
duplicated pnpm@11.9.0 literal by sourcing the package-manager version from the
root package.json through a shared constant or equivalent used by both
seal-candidate test and implementation flows.
- Around line 248-291: Update the Corepack cache setup in both tests to resolve
the source using XDG_CACHE_HOME first, then HOME with a tmpdir fallback,
matching stageCorepackPnpm. Treat a missing pnpm payload as an unmet test
precondition by skipping the test rather than asserting existsSync true; adjust
the test callbacks as needed to access the skip mechanism while preserving the
existing staging and credential assertions.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 16034a77-87d0-4d07-b488-102a3cfb77f9

📥 Commits

Reviewing files that changed from the base of the PR and between 4b067f4 and 589540b.

📒 Files selected for processing (2)
  • scripts/seal-candidate.mjs
  • scripts/seal-candidate.test.mjs

Comment on lines +239 to +301
function yamlRootMappingKey(line) {
const mapping = line.trimEnd();
if (['?', '&', '*', '!', '{', '[', '-'].includes(mapping[0])) return null;
if (mapping.startsWith('"')) {
let escaped = false;
for (let index = 1; index < mapping.length; index += 1) {
const character = mapping[index];
if (escaped) {
escaped = false;
continue;
}
if (character === '\\') {
escaped = true;
continue;
}
if (character !== '"') continue;
if (!/^\s*:/.test(mapping.slice(index + 1))) return null;
try {
return JSON.parse(mapping.slice(0, index + 1));
} catch {
return null;
}
}
return null;
}
if (mapping.startsWith("'")) {
for (let index = 1; index < mapping.length; index += 1) {
if (mapping[index] !== "'") continue;
if (mapping[index + 1] === "'") {
index += 1;
continue;
}
if (!/^\s*:/.test(mapping.slice(index + 1))) return null;
return mapping.slice(1, index).replaceAll("''", "'");
}
return null;
}
const separator = mapping.indexOf(':');
if (separator <= 0) return null;
const key = mapping.slice(0, separator).trim();
return key && key !== '<<' && !/\s/.test(key) ? key : null;
}

function assertSetupDoesNotUseConfigDependencies(repository) {
const workspace = join(repository, 'pnpm-workspace.yaml');
if (!existsSync(workspace)) return;
const lines = readFileSync(workspace, 'utf8')
.split('\n')
.map((line) => ({ line, indentation: line.match(/^\s*/)[0].length }))
.filter(
({ line }) => line.trim() && !line.trimStart().startsWith('#') && !/^(---|\.\.\.)\s*(?:#.*)?$/.test(line.trim()),
);
if (lines.length === 0) return;
const rootIndentation = Math.min(...lines.map(({ indentation }) => indentation));
for (const { line, indentation } of lines) {
if (indentation !== rootIndentation) continue;
const key = yamlRootMappingKey(line.slice(rootIndentation));
if (key === null)
fail('OWNER_DECISION_REQUIRED: dependency setup cannot safely inspect an ambiguous top-level pnpm workspace key');
if (key === 'configDependencies')
fail('OWNER_DECISION_REQUIRED: dependency setup cannot inherit owner environment with configDependencies');
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Over-broad root-key classification in the workspace guard, unguarded by any accept-path test. yamlRootMappingKey returns null for any root-indentation line beginning with -, but YAML allows block sequence entries at the parent key's indentation (packages: followed by - 'packages/*' at column zero), so idiomatic pnpm-workspace.yaml files abort sealing with OWNER_DECISION_REQUIRED. The regression suite only exercises rejection paths, which is why the false positive is invisible.

  • scripts/seal-candidate.mjs#L239-L301: skip root-indentation lines that start with - (or are exactly -) before calling yamlRootMappingKey; they cannot be mapping keys, so configDependencies detection stays fail-closed.
  • scripts/seal-candidate.test.mjs#L319-L355: add a positive case writing a workspace file with packages: and its sequence entries at column zero, asserting seal.valid === true and no setupError.
📍 Affects 2 files
  • scripts/seal-candidate.mjs#L239-L301 (this comment)
  • scripts/seal-candidate.test.mjs#L319-L355
🤖 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 `@scripts/seal-candidate.mjs` around lines 239 - 301, Update
assertSetupDoesNotUseConfigDependencies in scripts/seal-candidate.mjs (lines
239-301) to skip root-indentation lines that are exactly "-" or begin with "- "
before calling yamlRootMappingKey, while retaining fail-closed handling for
ambiguous mapping keys. Add a positive regression case in
scripts/seal-candidate.test.mjs (lines 319-355) with packages: and column-zero
sequence entries, asserting seal.valid is true and setupError is absent.

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.

1 participant