Repository navigation
feat: fix confirmed findings during scans with native agents #1365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
yoni-at-strix
wants to merge
41
commits into
main
Choose a base branch
from
devin/1790308365-verified-fix-preparation
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 2 commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
f23afb4
Add verified fix preparation engine
yoni-at-strix 49eca20
Harden fix preparation against review findings
yoni-at-strix 2b413da
Keep prepared candidates and staleness consistent across revisions
yoni-at-strix be1c2e4
Withhold automatic fixes when repairs exceed the recorded draft
yoni-at-strix 9d525ad
Add bounded fix verification feedback loop
yoni-at-strix d02b74c
Preserve explicit blocked repair outcomes
yoni-at-strix a41000d
Retry unchanged repairs when verification changes
yoni-at-strix c510f58
fix: retry transient verifier inconclusive results
yoni-at-strix df313c1
refactor fix preparation gates
yoni-at-strix 00fcb4c
fix: classify distinct check failures correctly
yoni-at-strix f213a7d
Require functional fix evidence and preserve partial preparation work
198a254
Make preparation history factory explicit for strict type checking
791ef91
fix: let independent evidence resolve repair timeout
yoni-at-strix 85b3030
Preserve partial fixes and require consistent execution evidence
f03fd72
Simplify fix preparation around native tests and independent review
0faa7b7
Make fix handoffs actionable and require customer unit tests
43391eb
Let repair and review agents own the fix workflow
d184142
Let fix reviewer own validation and completion
348fbf2
Support native fix-agent assignments and final reviewed patches
5badb2d
Remove retired fix execution and proof machinery
d35197b
Move complete fix workflow into OSS and add strix fix CLI
acf262f
Keep fix outputs private and outside the source checkout
1c1a899
Focus fix agents and preserve completion evidence
77bd5da
Require an explicit fix handoff for source-backed findings
yoni-at-strix 868ba53
Scope fix validation and warn on repeated commands
yoni-at-strix e4f1fe6
Merge remote-tracking branch 'origin/main' into devin/1790308365-veri…
yoni-at-strix ba6bbaf
Include repair follow-ups in the readable review
yoni-at-strix 1789400
Keep an approved fix when only the PR text changes
yoni-at-strix 8317665
Run confirmed finding fixes as native agents in the scan sandbox
yoni-at-strix b71ed13
Use native child delegation for finding fixes and strengthen completi…
yoni-at-strix 1f8295c
Launch native fixes after persistence and bound completion failures
yoni-at-strix f75fb5f
fix: harden fix dispatch and verification (STR-815)
yoni-at-strix bba4aa2
fix: require reviewed current patches and enforce fix network isolation
60d4ce1
feat: allow scans to skip automatic fixes
yoni-at-strix 3763a67
fix: finalize cancelled fix agents
yoni-at-strix 1fa7d21
feat: publish verified fixes as local branches
yoni-at-strix f144685
fix: complete interactive autofix scans before cleanup
yoni-at-strix 098fa36
fix: guard resumed assessments and ignore withdrawn fix records
yoni-at-strix 9edb2ae
fix: disable automatic fix agents for PR review scans
yoni-at-strix 8bd7f4c
refactor: use one auto-fix setting and infer fix delivery
yoni-at-strix 818d583
Accept stray characters in validation status and hide auto-fix guidan…
yoni-at-strix File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| """Verified fix preparation contracts and runtime.""" | ||
|
|
||
| from strix.fix.contracts import ( | ||
| CandidateLocation, | ||
| CheckResult, | ||
| CheckStatus, | ||
| CommandSpec, | ||
| FileManifestEntry, | ||
| FixCandidateV1, | ||
| FixEdit, | ||
| FixPreparationRequestV1, | ||
| FixPreparationResultV1, | ||
| PreparationState, | ||
| ReproductionSpec, | ||
| SourceIdentity, | ||
| SourceIdentityKind, | ||
| VerificationDecision, | ||
| VerifierResult, | ||
| candidate_from_legacy_report, | ||
| ) | ||
| from strix.fix.prepare import ( | ||
| PreparationCancelledError, | ||
| PreparationContext, | ||
| PreparationPolicy, | ||
| build_git_manifest, | ||
| prepare_fix, | ||
| ) | ||
|
|
||
|
|
||
| __all__ = [ | ||
| "CandidateLocation", | ||
| "CheckResult", | ||
| "CheckStatus", | ||
| "CommandSpec", | ||
| "FileManifestEntry", | ||
| "FixCandidateV1", | ||
| "FixEdit", | ||
| "FixPreparationRequestV1", | ||
| "FixPreparationResultV1", | ||
| "PreparationCancelledError", | ||
| "PreparationContext", | ||
| "PreparationPolicy", | ||
| "PreparationState", | ||
| "ReproductionSpec", | ||
| "SourceIdentity", | ||
| "SourceIdentityKind", | ||
| "VerificationDecision", | ||
| "VerifierResult", | ||
| "build_git_manifest", | ||
| "candidate_from_legacy_report", | ||
| "prepare_fix", | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,278 @@ | ||
| """Versioned contracts for verified fix preparation.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import hashlib | ||
| import json | ||
| from enum import StrEnum | ||
| from pathlib import PurePosixPath | ||
| from typing import TYPE_CHECKING, Literal, cast | ||
|
|
||
| from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator | ||
|
|
||
|
|
||
| if TYPE_CHECKING: | ||
| from collections.abc import Mapping | ||
|
|
||
|
|
||
| class ContractModel(BaseModel): | ||
| model_config = ConfigDict(extra="forbid") | ||
|
|
||
|
|
||
| class SourceIdentityKind(StrEnum): | ||
| COMMIT = "commit" | ||
| ARCHIVE = "archive" | ||
|
|
||
|
|
||
| class PreparationState(StrEnum): | ||
| PREPARING = "preparing" | ||
| READY = "ready" | ||
| READY_WITH_GAPS = "ready_with_gaps" | ||
| NEEDS_REVIEW = "needs_review" | ||
| BLOCKED = "blocked" | ||
| FAILED = "failed" | ||
| STALE = "stale" | ||
|
|
||
|
|
||
| class CheckStatus(StrEnum): | ||
| PASSED = "passed" | ||
| FAILED = "failed" | ||
| UNAVAILABLE = "unavailable" | ||
| CANCELLED = "cancelled" | ||
|
|
||
|
|
||
| class VerificationDecision(StrEnum): | ||
| VERIFIED = "verified" | ||
| REJECTED = "rejected" | ||
| INCONCLUSIVE = "inconclusive" | ||
|
|
||
|
|
||
| class SourceIdentity(ContractModel): | ||
| kind: SourceIdentityKind | ||
| value: str = Field(min_length=1) | ||
| repository: str | None = None | ||
|
|
||
| @field_validator("value") | ||
| @classmethod | ||
| def validate_value(cls, value: str) -> str: | ||
| normalized = value.strip().lower() | ||
| if not normalized or any(char not in "0123456789abcdef" for char in normalized): | ||
| raise ValueError("source identity must be a hexadecimal digest") | ||
| return normalized | ||
|
|
||
| @model_validator(mode="after") | ||
| def validate_digest_length(self) -> SourceIdentity: | ||
| valid_lengths = {40, 64} if self.kind is SourceIdentityKind.COMMIT else {64} | ||
| if len(self.value) not in valid_lengths: | ||
| expected = "40 or 64" if self.kind is SourceIdentityKind.COMMIT else "64" | ||
| raise ValueError(f"{self.kind} identity must contain {expected} hexadecimal characters") | ||
| return self | ||
|
|
||
|
|
||
| def _validate_relative_path(value: str) -> str: | ||
| path = value.strip() | ||
| parsed = PurePosixPath(path) | ||
| if not path or parsed.is_absolute() or ".." in parsed.parts or path.startswith("./"): | ||
| raise ValueError("path must be relative to the repository root") | ||
| return path | ||
|
|
||
|
|
||
| class CandidateLocation(ContractModel): | ||
| file: str | ||
| start_line: int = Field(ge=1) | ||
| end_line: int = Field(ge=1) | ||
| snippet: str | None = None | ||
| label: str | None = None | ||
|
|
||
| _relative_file = field_validator("file")(_validate_relative_path) | ||
|
|
||
| @model_validator(mode="after") | ||
| def validate_range(self) -> CandidateLocation: | ||
| if self.end_line < self.start_line: | ||
| raise ValueError("end_line must be greater than or equal to start_line") | ||
| return self | ||
|
|
||
|
|
||
| class FixEdit(CandidateLocation): | ||
| before: str = Field(min_length=1) | ||
| after: str | ||
| original_sha256: str | None = None | ||
|
|
||
|
|
||
| class CommandSpec(ContractModel): | ||
| name: str = Field(min_length=1, max_length=120) | ||
| argv: list[str] = Field(min_length=1) | ||
| required: bool = True | ||
| timeout_seconds: int = Field(default=300, ge=1, le=3600) | ||
| cwd: str = "." | ||
|
|
||
| _relative_cwd = field_validator("cwd")(_validate_relative_path) | ||
|
|
||
| @field_validator("argv") | ||
| @classmethod | ||
| def validate_argv(cls, value: list[str]) -> list[str]: | ||
| if any(not argument or "\x00" in argument for argument in value): | ||
| raise ValueError("command arguments must be non-empty and cannot contain NUL") | ||
| return value | ||
|
|
||
|
|
||
| class ReproductionSpec(ContractModel): | ||
| instructions: str = Field(min_length=1) | ||
| command: CommandSpec | None = None | ||
|
|
||
|
|
||
| class ReportedCheck(ContractModel): | ||
| name: str | ||
| result: str | ||
| executed: bool = False | ||
|
|
||
|
|
||
| class FixCandidateV1(ContractModel): | ||
| version: Literal["1"] = "1" | ||
| source_identity: SourceIdentity | None = None | ||
| security_invariant: str = Field(min_length=1) | ||
| finding_locations: list[CandidateLocation] = [] | ||
| draft_edits: list[FixEdit] = [] | ||
| reproduction: ReproductionSpec | None = None | ||
| reported_checks: list[ReportedCheck] = [] | ||
| known_gaps: list[str] = [] | ||
|
|
||
| def digest(self) -> str: | ||
| payload = json.dumps( | ||
| self.model_dump(mode="json"), | ||
| sort_keys=True, | ||
| separators=(",", ":"), | ||
| ).encode() | ||
| return hashlib.sha256(payload).hexdigest() | ||
|
|
||
|
|
||
| class FixPreparationRequestV1(ContractModel): | ||
| version: Literal["1"] = "1" | ||
| organization_id: str | None = None | ||
| scan_id: str | ||
| finding_id: str | ||
| repository_id: str | None = None | ||
| candidate: FixCandidateV1 | ||
| checks: list[CommandSpec] = [] | ||
| max_repair_attempts: int = Field(default=2, ge=1, le=5) | ||
| timeout_seconds: int = Field(default=1800, ge=30, le=14400) | ||
| network_allowed: bool = False | ||
| credentials_allowed: list[str] = [] | ||
|
|
||
|
|
||
| class CheckResult(ContractModel): | ||
| name: str | ||
| argv: list[str] | ||
| status: CheckStatus | ||
| exit_code: int | None = None | ||
| duration_seconds: float = Field(ge=0) | ||
| output: str = "" | ||
| required: bool = True | ||
|
|
||
|
|
||
| class VerifierResult(ContractModel): | ||
| decision: VerificationDecision | ||
| summary: str | ||
| security_invariant_closed: bool = False | ||
| reproduction_executed: bool = False | ||
| reproduction_summary: str | None = None | ||
| sibling_paths_reviewed: list[str] = [] | ||
| preserved_behaviors: list[str] = [] | ||
| gaps: list[str] = [] | ||
|
|
||
|
|
||
| class FileManifestEntry(ContractModel): | ||
| path: str | ||
| operation: Literal["add", "modify", "delete"] | ||
| original_sha256: str | None = None | ||
| resulting_sha256: str | None = None | ||
| artifact_ref: str | None = None | ||
|
|
||
| _relative_path = field_validator("path")(_validate_relative_path) | ||
|
|
||
|
|
||
| class FixPreparationResultV1(ContractModel): | ||
| version: Literal["1"] = "1" | ||
| state: PreparationState | ||
| stop_reason: str | ||
| source_identity: SourceIdentity | None | ||
| candidate_digest: str | ||
| final_file_manifest: list[FileManifestEntry] = [] | ||
| artifact_ref: str | None = None | ||
| changed_files: list[str] = [] | ||
| diff_summary: str = "" | ||
| checks: list[CheckResult] = [] | ||
| security_reproduction: CheckResult | None = None | ||
| verifier: VerifierResult | None = None | ||
| gaps: list[str] = [] | ||
| attempts: int = Field(default=0, ge=0) | ||
| elapsed_seconds: float = Field(default=0, ge=0) | ||
| cost_usd: float | None = Field(default=None, ge=0) | ||
|
|
||
|
|
||
| def candidate_from_legacy_report( | ||
| report: Mapping[str, object], | ||
| *, | ||
| source_identity: SourceIdentity | None = None, | ||
| ) -> FixCandidateV1 | None: | ||
| raw_locations = report.get("code_locations") | ||
| if not isinstance(raw_locations, list): | ||
| return None | ||
| location_values = cast("list[object]", raw_locations) | ||
|
|
||
| locations: list[CandidateLocation] = [] | ||
| edits: list[FixEdit] = [] | ||
| for raw_value in location_values: | ||
| if not isinstance(raw_value, dict): | ||
| continue | ||
| raw = cast("dict[str, object]", raw_value) | ||
| file_path = raw.get("file") | ||
| start_line = raw.get("start_line") | ||
| end_line = raw.get("end_line") | ||
| if not isinstance(file_path, str) or type(start_line) is not int: | ||
| continue | ||
| if type(end_line) is not int: | ||
| end_line = start_line | ||
| common = { | ||
| "file": file_path, | ||
| "start_line": start_line, | ||
| "end_line": end_line, | ||
| "snippet": raw.get("snippet") if isinstance(raw.get("snippet"), str) else None, | ||
| "label": raw.get("label") if isinstance(raw.get("label"), str) else None, | ||
| } | ||
| try: | ||
| locations.append(CandidateLocation.model_validate(common)) | ||
| before = raw.get("fix_before") | ||
| after = raw.get("fix_after") | ||
| if isinstance(before, str) and isinstance(after, str): | ||
| edits.append(FixEdit.model_validate({**common, "before": before, "after": after})) | ||
| except ValueError: | ||
| continue | ||
|
|
||
| if not locations: | ||
| return None | ||
|
|
||
| invariant = str( | ||
| report.get("remediation_steps") or report.get("technical_analysis") or "" | ||
| ).strip() | ||
| if not invariant: | ||
| invariant = "Resolve the reported security finding without changing legitimate behavior." | ||
|
|
||
| reported_checks: list[ReportedCheck] = [] | ||
| verification = str(report.get("fix_verification") or "").strip() | ||
| if verification: | ||
| reported_checks.append( | ||
| ReportedCheck(name="reporting-agent verification", result=verification, executed=False) | ||
| ) | ||
|
|
||
| return FixCandidateV1( | ||
| source_identity=source_identity, | ||
| security_invariant=invariant, | ||
| finding_locations=locations, | ||
| draft_edits=edits, | ||
| reproduction=ReproductionSpec( | ||
| instructions=str(report.get("poc_description") or report.get("evidence") or invariant) | ||
| ), | ||
| reported_checks=reported_checks, | ||
| known_gaps=["The reporting-agent verification is not independent."], | ||
| ) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.