Skip to content

security: gate verify.spec child modules at the execution chokepoint (GHSA-wmwj-g59x-c8px) - #82

Merged
ChesterHsu merged 1 commit into
mainfrom
claude/verify-spec-policy-gate
Aug 26, 2026
Merged

security: gate verify.spec child modules at the execution chokepoint (GHSA-wmwj-g59x-c8px)#82
ChesterHsu merged 1 commit into
mainfrom
claude/verify-spec-policy-gate

Conversation

@ChesterHsu

Copy link
Copy Markdown
Member

Closes the reported verify.spec policy bypass.

The defect

verify.spec picks its child modules out of the caller's own ruleset —
rules[].source.module / rules[].target.module, with free-form params — and
dispatched them with instance.execute(). Both locks (the module filter and
the dangerous-permission grant) live in BaseModule.run(), so a caller
restricted to verify.spec (FLYTO_MODULE_ALLOWLIST=verify.spec, no
FLYTO_GRANTED_PERMISSIONS) could name shell.exec in a rule and run host
commands as the service account, outside FLYTO_SANDBOX_DIR.

Same omission as the nested Warroom/test steps fixed in testing/runner.py
(GHSA-675h-j4qg-m52x); verify.spec was the sibling call site that was missed.

The fix

  • spec_runner.execute_module_dynamic() calls the policy-gated run(). A
    ModulePolicyError from a child is re-raised rather than folded into a
    per-rule error, so a denied module fails the call instead of reading like an
    ordinary failed verification.
  • BaseModule._execute_with_resilience() no longer retries or repackages a
    ModulePolicyError — blocked must not read as broken.
  • POST /v1/execute gained the nested-module pre-flight the MCP transport
    already had, reusing the same two helpers so the two boundaries cannot drift.
  • A registry-wide AST test fails on any future function that resolves a module
    by a non-constant id and then awaits <obj>.execute().

Verified against the built artifact

The reporter's PoC, three configurations (default denylist, strict allowlist,
target rule branch):

Package Result
flyto-core[api]==2.31.0 (PyPI) exploit_reproduced: true, marker written outside the sandbox
2.31.1 wheel built from this branch bypass_top_level_ok: false, no marker, Module 'verify.spec' declares nested module(s) blocked by security policy: shell.exec

The negative control — a direct shell.exec request is denied — holds in every
run, and the new tests were confirmed to fail on the unpatched dispatcher first.

Repository gates: offline suite 3,175 passed, 11 skipped, 63.67% coverage;
documentation, brand, project-memory, release-drift, build + twine check,
npm audit, and strict Indexer 20/20 all pass. Ruff: no new findings.

After merge

v2.31.1 still needs to be tagged and published before GHSA-wmwj-g59x-c8px is
given 2.31.1 as its patched version and published.

verify.spec picks its child modules out of the caller's own ruleset —
rules[].source.module / rules[].target.module with free-form params — and
dispatched them with instance.execute(). Both locks live in BaseModule.run(),
so a caller restricted to verify.spec (FLYTO_MODULE_ALLOWLIST=verify.spec, no
FLYTO_GRANTED_PERMISSIONS) could name shell.exec in a rule and run host
commands as the service account, outside FLYTO_SANDBOX_DIR. Same omission as
the nested Warroom/test steps fixed in testing/runner.py; verify.spec was the
sibling call site that was missed. GHSA-wmwj-g59x-c8px.

The dispatcher now calls the policy-gated run(), and a ModulePolicyError from
a child is re-raised rather than folded into a per-rule error, so a denied
module fails the call instead of reading like a failed verification.
_execute_with_resilience stops retrying and repackaging ModulePolicyError —
blocked must not read as broken. POST /v1/execute gained the nested-module
pre-flight the MCP transport already had, reusing the same two helpers so the
boundaries cannot drift.

A registry-wide AST test fails on any future function that resolves a module
by a non-constant id and then awaits <obj>.execute().

Verified against the built artifact, not the source tree: the reporter's PoC
reproduces on flyto-core[api]==2.31.0 from PyPI and is refused by the 2.31.1
wheel built here in all three reported configurations. Offline suite 3,175
passed, 11 skipped, 63.67% coverage; build + twine, npm audit, documentation,
brand, project-memory and strict Indexer (20/20) all pass.
@ChesterHsu
ChesterHsu force-pushed the claude/verify-spec-policy-gate branch from eaa00ae to e4d48c3 Compare August 26, 2026 08:40
@ChesterHsu
ChesterHsu merged commit 4c64fbf into main Aug 26, 2026
12 checks passed
@ChesterHsu
ChesterHsu deleted the claude/verify-spec-policy-gate branch August 26, 2026 09:26
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