Skip to content

Fuzzer - #346

Open
davida-ps wants to merge 33 commits into
mainfrom
fuzzer
Open

Fuzzer#346
davida-ps wants to merge 33 commits into
mainfrom
fuzzer

Conversation

@davida-ps

@davida-ps davida-ps commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

User description

Opener Type

  • Human
  • Agent (automated)

Summary

Changes Made

Related Issues


Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Security incident (please open a Security Incident Report issue instead of a PR)

Testing

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my changes
  • I have added tests that prove my fix/feature works
  • New and existing tests pass locally

Generated description

Below is a concise technical summary of the changes proposed in this PR:
Introduce clawsec-ps-fuzz, a standalone authorized-testing workflow for provisioning pinned Prompt Security ps-fuzz releases and executing isolated direct-model fuzzing with approved providers, private state, fresh authorization gates, and aggregate-only redacted reporting. The PR also adds signed-release verification with atomic no-replace installation, dependency and capability provenance, an optional pinned loopback Gemma smoke workflow, packaging metadata, operator documentation, third-party notices, and extensive offline security-boundary tests.

TopicDetails
Signed Release Verification and Safe Installation Establishes a trusted first-install path based on an out-of-band verifier, a canonical Ed25519 key fingerprint, signed release manifests, strict archive/SBOM validation, bounded downloads, and platform-native atomic no-replace publication. The implementation rejects unsafe archive layouts, symlinks, malformed metadata, incomplete payloads, and unsupported environments before installation.
Modified files (1)
  • skills/clawsec-ps-fuzz/scripts/verified_install.py
Latest Contributors(1)
UserCommitDate
David.a@prompt.securityfix(ps-fuzz): enforce ...August 19, 2026
Release Provenance and Offline Security Validation Adds release history and legal/provenance documentation covering the reviewed upstream ps-fuzz release, ClawSec wrapper licensing, and the optional Gemma artifact. Extensive offline tests validate package completeness, documentation and trust-ordering contracts, runner authorization and isolation behavior, local smoke shell/HTTP safeguards, signed-install integrity, archive safety, atomic publication, and failure handling.
Modified files (6)
  • skills/clawsec-ps-fuzz/CHANGELOG.md
  • skills/clawsec-ps-fuzz/THIRD_PARTY_NOTICES.md
  • skills/clawsec-ps-fuzz/test/test_local_smoke.py
  • skills/clawsec-ps-fuzz/test/test_package_contract.py
  • skills/clawsec-ps-fuzz/test/test_ps_fuzz_runner.py
  • skills/clawsec-ps-fuzz/test/test_verified_install.py
Latest Contributors(1)
UserCommitDate
David.a@prompt.securitynew skillAugust 20, 2026
Authorized Fuzzing Workflow and Isolation Adds the core ps-fuzz lifecycle: offline preflight, authorization-gated wheel/source provisioning, pinned runtime and dependency enforcement, isolated state and environment handling, provision receipts, approved endpoint validation, prompt isolation, constrained batch execution, assessment-status handling, and redacted aggregate reports. Supporting manifests, capability snapshots, dependency inputs, metadata, and documentation define the reviewed operational boundary and platform scope.
Modified files (7)
  • skills/clawsec-ps-fuzz/README.md
  • skills/clawsec-ps-fuzz/SKILL.md
  • skills/clawsec-ps-fuzz/resources/capabilities-v2.1.0.json
  • skills/clawsec-ps-fuzz/resources/requirements-input.txt
  • skills/clawsec-ps-fuzz/resources/upstream.json
  • skills/clawsec-ps-fuzz/scripts/ps_fuzz_runner.py
  • skills/clawsec-ps-fuzz/skill.json
Latest Contributors(1)
UserCommitDate
David.a@prompt.securitynew skillAugust 20, 2026
Pinned Local Model Smoke Workflow Documents and packages an operator-controlled local-first smoke test using an immutably pinned Gemma GGUF artifact, exact size and hash verification, private staging, loopback-only llama-server execution, hardened flags, readiness checks, and a single harmless completion. The resources clarify that this validates connectivity, isolation, authorization gates, and redaction rather than model security.
Modified files (3)
  • skills/clawsec-ps-fuzz/resources/local-smoke-model.json
  • skills/clawsec-ps-fuzz/resources/local-smoke-system-prompt.txt
  • skills/clawsec-ps-fuzz/resources/local-smoke.md
Latest Contributors(1)
UserCommitDate
David.a@prompt.securityfix(ps-fuzz): reject l...August 19, 2026
Other Other files
Modified files (1)
  • skills/clawsec-ps-fuzz/resources/requirements-lock.txt
Latest Contributors(1)
UserCommitDate
David.a@prompt.securitynew skillAugust 20, 2026
Review this PR on Baz | Customize your next review

@baz-reviewer

baz-reviewer Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Merger

Needs Review

PR exceeds the merge-gate context budget (309180 tokens); escalating to a human reviewer.

Commit 5750f91 · Evaluated 2026-08-20 08:19 UTC

Review this PR on Baz | Customize your next review

Comment on lines +1 to +3
#!/usr/bin/env python3
"""Offline release-package contract tests for clawsec-ps-fuzz."""

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.

New skill tests are never executed

These four modules use test_*.py and are runnable only through if __name__ == "__main__": unittest.main(), while CI runs Node *.test.mjs files and .github/workflows/skill-release.yml excludes skills/*/test/** from release detection and staged archives, so their required SBOM entries do not execute the tests and regressions can pass silently. Could we convert/move the suite to *.test.mjs, or add a documented reachable Python unittest command and explicitly amend the AGENTS.md convention, since SKILL.md and README.md document no Python test command?

Severity

Want Baz to fix this for you? Activate Fixer You can also update your AI coding guidelines based on this comment by apply pr to [branch name]

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`skills/clawsec-ps-fuzz/test/test_package_contract.py` around lines 1-3, replace this
directly runnable-only Python unittest module with the repository’s documented
`*.test.mjs` test convention, ensuring CI discovers and executes the package contract
coverage. Update the SBOM artifact list and any related `SKILL.md`/`README.md` guidance
to reflect the new test location and command; if Python must be retained, instead add a
reachable documented unittest command and explicitly amend the repository convention and
CI workflow so these tests cannot pass silently.


## Safe scope

- The reviewed runtime is CPython 3.9 through 3.11 with `venv` and `pip` on glibc 2.28+ Linux x86_64/aarch64 or macOS 14+ arm64. Source provisioning also needs `git`. Provision and run fail closed on Windows in v0.1.0 because this standard-library wrapper cannot verify a current-user-private Windows DACL; read-only preflight remains available.

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.

Windows preflight documentation promises unavailable interface

On Windows, preflight() calls _runtime_support() first and exits via the unsupported native-wheel platform branch, so it never produces the documented report or runs the venv/pip/configuration checks; test_preflight_reports_windows_runtime_unsupported_without_capability_probes and SKILL.md:58 currently codify that contradiction. Should we implement a Windows read-only result that skips only mutation ACL enforcement while still reporting checks, or update both public guides to say preflight fails on Windows?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`skills/clawsec-ps-fuzz/README.md` around lines 48-48 and
`skills/clawsec-ps-fuzz/SKILL.md` around line 58, fix the contradiction where Windows is
documented as supporting read-only preflight even though `preflight()` exits before
producing its inspection report. Update the runtime/preflight logic so Windows bypasses
only mutation-time ACL enforcement while still reporting venv, pip, and configuration
checks without writes or network calls; keep provision and run fail-closed on Windows.
Adjust the regression tests and documentation to accurately reflect the implemented
behavior.

Comment on lines +72 to +75
python3 scripts/ps_fuzz_runner.py preflight --source wheel \
--target-provider open_ai --target-model gpt-4o-mini \
--attack-provider open_ai --attack-model gpt-4o-mini \
--tests '["system_prompt_stealer"]' --attempts 1 --threads 1

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.

Preflight examples advertise ignored run controls

The published preflight examples pass --attempts 1 --threads 1, but main() neither forwards nor reports them, so argparse silently discards the supplied values and operators receive no validation or report — should we remove these flags from both examples or add explicit preflight validation/reporting?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`skills/clawsec-ps-fuzz/README.md` around lines 72-75, update the preflight example so
it does not pass the run-only `--attempts` and `--threads` options, since `main()` does
not forward them to `preflight()` and the preflight result does not report them. Remove
the same no-op flags from the corresponding preflight example in `SKILL.md`, and keep
those controls only in run examples consistent with the smoke guide and tests.

Comment on lines +166 to +175
valid = (
re.fullmatch(r"v[0-9]+(?:\.[0-9]+)*", tag)
and re.fullmatch(r"[0-9a-f]{40}", commit)
and re.fullmatch(r"[0-9a-f]{64}", str(release_wheel["sha256"]))
and re.fullmatch(r"prompt_security_fuzzer-[A-Za-z0-9][A-Za-z0-9_.-]*\.whl", filename)
and Path(filename).name == filename
and upstream["clone_url"] == "https://github.com/prompt-security/ps-fuzz.git"
and artifact_url.scheme == "https"
and artifact_url.netloc == "github.com"
and artifact_url.path == f"/prompt-security/ps-fuzz/releases/download/{tag}/{filename}"

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.

Capability drift enables mismatched runs

load_capabilities() loads a fixed snapshot independently of the manifest, and provision() calls preflight() without comparing release identity, so run() can combine one ps-fuzz release with another release’s provider flags and attack selectors; at skills/clawsec-ps-fuzz/scripts/ps_fuzz_runner.py:1055-1064, _verify_provision_receipt() records only manifest_sha256, so changing capabilities after provisioning remains undetected. Should we require the manifest tag to match upstream_tag (or a canonical capability fingerprint) before provisioning and record/verify that fingerprint in the receipt?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
skills/clawsec-ps-fuzz/scripts/ps_fuzz_runner.py around lines 166-175 and 1055-1064,
bind the manifest validated by `load_manifest()` to the capability snapshot used by
`preflight()`, `provision()`, and `run()`; currently provisioning silently loads
capabilities independently and the receipt fingerprints only the manifest. Require the
manifest release tag to match `capabilities["upstream_tag"]` (or an equivalent canonical
capability fingerprint), pass the validated capabilities explicitly through
provisioning, and include that fingerprint/version in the provision receipt. Update
`_verify_provision_receipt()` and `run()` to reject receipts or active runs whose
capability snapshot does not match the provision-time binding.

Comment on lines +310 to +312
with urllib.request.urlopen(url) as response: # nosec B310: reviewed manifest URL
while chunk := response.read(1024 * 1024):
_write_all(descriptor, chunk)

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.

Unbounded wheel download can hang or fill state root

urlopen(url) has no finite timeout, so a stalled server can block provisioning indefinitely, while unbounded response data is written to state_root/downloads before upstream.json’s SHA-256 check, allowing invalid responses to consume arbitrary disk space. Should we add a finite timeout, enforce a cumulative streaming limit regardless of Content-Length, and unlink the destination after any failed or rejected download?

Severity web_search

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`skills/clawsec-ps-fuzz/scripts/ps_fuzz_runner.py` around lines 310-312, refactor
`_download` to use a finite network timeout and enforce an explicit maximum wheel size
while streaming, validating `Content-Length` when present and tracking cumulative bytes
even when it is absent or incorrect. Ensure every timeout, read, size-limit, or other
download failure closes the descriptor and unlinks the partial destination, while
preserving the existing exclusive/private file protections and post-download hash
verification.

Comment on lines +1209 to +1210
_validate_provision_destination(state_root)
selected_python = _trusted_executable(python_executable, "selected Python")

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.

Partial failures permanently block reprovisioning

_validate_provision_destination rejects any non-empty state root, so a crash during provisioning leaves partial state that subsequent invocations cannot reconcile or resume, permanently stranding the caller’s directory — should we persist and honor a recoverable FAILED marker, or atomically clean/reconcile partial state before retrying?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`skills/clawsec-ps-fuzz/scripts/ps_fuzz_runner.py` around lines 1209-1210, the
`provision` flow requires an empty state root but then creates virtual environments,
downloads, caches, and receipts incrementally without cleanup or durable failure
tracking, so crashes permanently strand partial state. Refactor provisioning to use a
durable state marker/state machine and reconcile or safely remove incomplete artifacts
on the next invocation before proceeding; preserve and validate completed state for
resumable retries, and ensure failures record a recoverable `FAILED` state.

Comment on lines +1379 to +1386
def _validate_test_authorization(confirm_authorized_test: bool, authorization_id: str) -> None:
if not confirm_authorized_test:
raise ProvisionError("--confirm-authorized-test is required for every active run")
if (
not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,63}", authorization_id)
or _looks_secret_shaped(authorization_id)
):
raise ProvisionError("--authorization-id must be a short non-secret identifier")

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.

Reusable authorization ID permits repeated runs

_validate_test_authorization and _validate_authorization accept syntax-valid IDs repeatedly without recording consumption or binding them to approved parameters, so the runner cannot distinguish fresh approval from replay — should we enforce one-time IDs and invocation binding in an authoritative caller or the runner for both test and provisioning paths?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`skills/clawsec-ps-fuzz/scripts/ps_fuzz_runner.py` around lines 1379-1386, refactor
`_validate_test_authorization` and its provisioning counterpart
`_validate_authorization` so authorization IDs are verified as one-time approvals bound
to the exact invocation parameters, rather than merely syntax-checked. Persist and
atomically consume authorization records in the protected state or require an
authoritative caller callback that provides this guarantee; reject replayed IDs and
parameter mismatches on both provision and run paths. Update the call sites and tests so
repeated use of values such as `AUTH-42` cannot authorize another invocation.

Comment on lines +1406 to +1409
host = parsed.hostname.lower()
netloc = host if port is None else f"{host}:{port}"
path = parsed.path.rstrip("/")
normalized = urlunparse((parsed.scheme.lower(), netloc, path, "", "", ""))

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.

Approved endpoints are reconstructed incorrectly

The URL normalizer drops IPv6 brackets and parsed.params when rebuilding endpoints, so IPv6 URLs become malformed and parameterized paths can pass _approved_role_url() as a different URL before reaching the child argv — should we preserve both components or reject URL parameters before approval and launch?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`skills/clawsec-ps-fuzz/scripts/ps_fuzz_runner.py` around lines 1406-1409, fix
`_normalized_base_url` so URL reconstruction preserves endpoint identity and produces
valid child arguments. Format IPv6 hosts with brackets when rebuilding `netloc`, and
preserve `parsed.params` in `urlunparse` (or explicitly reject URL parameters before
approval and launch). Add or update tests covering bracketed IPv6 URLs and semicolon
path parameters, including approval comparisons.

Comment on lines +1580 to +1584
if len(values) != 4 or not strength or any(not re.fullmatch(r"\d+", value) for value in values):
return None
if footer is not None:
return None
footer = dict(zip(("broken", "resilient", "errors", "skipped"), (int(value) for value in values)))

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.

Oversized footer aborts redacted report

A child-controlled digit field matching \d+ can exceed CPython 3.11’s 4,300-digit limit, so int(value) raises ValueError outside the local try and _aggregate_counts() lets it escape instead of returning None, bypassing invalid-output classification and redacted report generation. Could we bound the digit length and catch conversion failures in _aggregate_counts() by returning None?

Severity web_search

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
`skills/clawsec-ps-fuzz/scripts/ps_fuzz_runner.py` around lines 1580-1584, update
`_aggregate_counts` so child-controlled numeric footer fields cannot trigger an uncaught
`ValueError` during `int()` conversion. Enforce an explicit maximum digit length before
conversion and catch any conversion failure, returning `None` so the active run
classifies the output as `invalid-output` and still writes its redacted report.

Comment on lines +1891 to +1894
def main(argv: Sequence[str] | None = None, *, command: Command = _run_command) -> int:
args = _parser().parse_args(argv)
manifest = load_manifest()
try:

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.

Manifest failures escape stable blocked status

main() calls load_manifest() before the ProvisionError handler, so an unreadable or malformed upstream.json produces an uncaught traceback instead of the documented blocked status — should we move manifest loading inside the handler or add an equivalent outer boundary?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
skills/clawsec-ps-fuzz/scripts/ps_fuzz_runner.py around lines 1891-1894, the main() CLI
loads the manifest before the existing ProvisionError handler, allowing load_manifest()
failures to produce an uncaught traceback. Move manifest loading inside the existing try
block, or add an equivalent outer ProvisionError boundary, so unreadable or malformed
manifests consistently print the blocked message and return exit status 2 for all CLI
commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant