Skip to content

Commit eb501e6

Browse files
authored
Added v2 to v3 result transformation that includes package changes (#1079)
* Added v2 to v3 transformation that includes package changes * Changed debug message * Bugfixes and added parameters for generation metadata
1 parent 4cfc957 commit eb501e6

File tree

2 files changed

+214
-1
lines changed

2 files changed

+214
-1
lines changed

src/codemodder/codetf/v3/codetf.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55

66
from pydantic import BaseModel, model_validator
77

8+
from codemodder.logging import logger
9+
810
from ..common import Change, CodeTFWriter, Finding, FixQuality
911
from ..v2.codetf import AIMetadata as AIMetadatav2
12+
from ..v2.codetf import ChangeSet as v2ChangeSet
1013
from ..v2.codetf import CodeTF as CodeTFv2
14+
from ..v2.codetf import Finding as v2Finding
1115
from ..v2.codetf import Result
1216
from ..v2.codetf import Run as Runv2
1317

@@ -148,6 +152,72 @@ def from_v2_aimetadata(ai_metadata: AIMetadatav2) -> AIMetadata:
148152
)
149153

150154

155+
def from_v2_result_per_finding(
156+
result: Result,
157+
strategy: Strategy | None = None,
158+
ai_metadata: AIMetadata | None = None,
159+
provisional: bool | None = None,
160+
) -> FixResult | None:
161+
"""
162+
This transformation assumes that the v2 result will only contain a single fixedFinding for all changesets.
163+
"""
164+
165+
changeset: v2ChangeSet | None = None
166+
finding: v2Finding | None = None
167+
# Find the changeset with a fixedFinding
168+
for cs in result.changeset:
169+
if cs.fixedFindings:
170+
changeset = cs
171+
finding = cs.fixedFindings[0]
172+
break
173+
else:
174+
# check each individual change
175+
for change in cs.changes:
176+
if change.fixedFindings:
177+
changeset = cs
178+
finding = change.fixedFindings[0]
179+
break
180+
if changeset is None or finding is None:
181+
logger.debug("Either no changesets or fixed finding in the result")
182+
return None
183+
184+
v3changesets = [
185+
ChangeSet(
186+
path=cs.path, diff=cs.diff, changes=[c.to_common() for c in cs.changes]
187+
)
188+
for cs in result.changeset
189+
]
190+
191+
# Generate the GenerationMetadata from the changeset if not passed as a parameter
192+
fix_result_strategy = strategy or (
193+
Strategy.ai if changeset.ai else Strategy.deterministic
194+
)
195+
fix_result_ai_metadata = ai_metadata or (
196+
from_v2_aimetadata(changeset.ai) if changeset.ai else None
197+
)
198+
fix_result_provisional = provisional or changeset.provisional or False
199+
200+
generation_metadata = GenerationMetadata(
201+
strategy=fix_result_strategy,
202+
ai=fix_result_ai_metadata,
203+
provisional=fix_result_provisional,
204+
)
205+
206+
fix_metadata = FixMetadata(
207+
id=result.codemod,
208+
summary=result.summary,
209+
description=result.description,
210+
generation=generation_metadata,
211+
)
212+
213+
return FixResult(
214+
finding=Finding(**finding.model_dump()),
215+
fixStatus=FixStatus(status=FixStatusType.fixed),
216+
changeSets=v3changesets,
217+
fixMetadata=fix_metadata,
218+
)
219+
220+
151221
def from_v2_result(result: Result) -> list[FixResult]:
152222
fix_results: list[FixResult] = []
153223
# generate fixed

tests/test_codetf.py

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,19 @@
2323
PackageResult,
2424
Strategy,
2525
)
26+
from codemodder.codetf.v3.codetf import (
27+
AIMetadata,
28+
)
2629
from codemodder.codetf.v3.codetf import Finding as FindingV3
27-
from codemodder.codetf.v3.codetf import FixStatusType, from_v2, from_v2_result
30+
from codemodder.codetf.v3.codetf import (
31+
FixStatusType,
32+
)
33+
from codemodder.codetf.v3.codetf import Strategy as StrategyV3
34+
from codemodder.codetf.v3.codetf import (
35+
from_v2,
36+
from_v2_result,
37+
from_v2_result_per_finding,
38+
)
2839

2940

3041
@pytest.fixture(autouse=True)
@@ -259,6 +270,138 @@ def test_v2_result_to_v3():
259270
assert from_v2_result(result)
260271

261272

273+
def test_v2_result_to_v3_per_finding():
274+
result = Result(
275+
codemod="codeql:java/log-injection",
276+
summary="Introduced protections against Log Inject ion / Forging attacks",
277+
description='This change ensures that log messages can\'t contain newline characters, leaving you vulnerable to Log Forging / Log Injection.\n\nIf malicious users can get newline characters into a log message, they can inject and forge new log entries that look like they came from the server, and trick log analysis tools, administrators, and more . This leads to vulnerabilities like Log Injection, Log Forging, and more attacks from there.\n\nOur change simply strips out newline characters from log messages, ensuring that they can \'t be used to forge new log entries.\n```diff\n+ import io.github.pixee.security.Newlines;\n ...\n String orderId = getUserOrderId();\n- log.info("User order ID: " + orderId);\n+ log. info("User order ID: " + Newlines.stripNewlines(orderId));\n```\n',
278+
detectionTool=DetectionTool(name="CodeQL"),
279+
references=[
280+
Reference(
281+
url="https://owasp.org/www-community/attacks/Log_Inj ection",
282+
description="https://owasp.org/www-community/attacks/Log_Injection",
283+
),
284+
Reference(
285+
url="https://knowledge-base.secureflag.com/vulnerabilities/inadequate_input_validation/log_inject ion_vulnerability.html",
286+
description="https://knowledge-base.secureflag.com/vulnerabilities/inadequate_input_validation/log_injection_vulnerability.html",
287+
),
288+
Reference(
289+
url="https://cwe.mit re.org/data/definitions/117.html",
290+
description="https://cwe.mitre.org/data/definitions/117.html",
291+
),
292+
],
293+
properties={},
294+
failedFiles=[],
295+
changeset=[
296+
ChangeSet(
297+
path="app/src/main/java/org/apache/roller/planet/business/fetcher/RomeFeedFetcher.java",
298+
diff='--- RomeFeedFetcher.java\n+++ RomeFeedFetcher.java\n@@ -26,6 +26,7 @@\n import com.rometools.rome.io.FeedException;\n import com.rometools.rome.io.SyndFeedInput;\n import com.rometools.rome.io.XmlReader;\n+import static io.github.pixee.security.Newlines.stripAll;\n \n import java.io.IOException;\n import java.net.URI;\n@@ -123,7 +124,7 @@\n }\n \n if(log.isDebugEnabled()) {\n- log.debug("Subscription is: " + newSub.toString());\n+ log.debug("Subscription is: " + stripAll(newSub.toString()));\n }\n \n ',
299+
changes=[
300+
Change(
301+
lineNumber=126,
302+
description="Added a call to replace any newlines the value",
303+
diffSide=DiffSide.LEFT,
304+
properties={},
305+
packageActions=[
306+
PackageAction(
307+
action=Action.ADD,
308+
result=PackageResult.COMPLETED,
309+
package="pkg:maven/io.github.pixee/java-security-toolkit@1.2.2",
310+
),
311+
PackageAction(
312+
action=Action.ADD,
313+
result=PackageResult.COMPLETED,
314+
package="pkg:maven/io.github.pixee/java-security-toolkit@1.2.2",
315+
),
316+
],
317+
fixedFindings=[
318+
Finding(
319+
id="915a8320-3ee8-4b0e-849b-c1b380fb83e2",
320+
rule=Rule(
321+
id="log-injection",
322+
name="Log Injection",
323+
url="https://codeql.github.com/codeql-query-help/java/java-log-injection/",
324+
),
325+
)
326+
],
327+
)
328+
],
329+
ai=None,
330+
strategy=Strategy.deterministic,
331+
provisional=False,
332+
fixedFindings=[
333+
Finding(
334+
id="915a8320-3ee8-4b0e-849b-c1b380fb83e2",
335+
rule=Rule(
336+
id="log-injection",
337+
name="Log Injection",
338+
url="https://codeql.github.com/codeql-query-help/java/java-log-injection/",
339+
),
340+
)
341+
],
342+
fixQuality=None,
343+
),
344+
ChangeSet(
345+
path="app/pom.xml",
346+
diff="--- app/pom.xml\n+++ app/pom.xml\n@@ -591,9 +591,12 @@\n <version>5.3.0</version>\n <scope>test</scope>\n </dependency>\n+ <dependency>\n+ <groupId>io.github.pixee</groupId>\n+ <artifactId>java-security-toolkit</artifactId>\n+ </dependency>\n+ </dependencies>\n \n- </dependencies>\n-\n <build>\n \n <finalName>roller</finalName>",
347+
changes=[
348+
Change(
349+
lineNumber=594,
350+
description="This library holds security tools for protecting Java API calls.\n\nLicense: MIT ✅ | [Open source](https://github.com/pixee/java-security-toolkit) ✅ | [More facts](https://mvnrepository.com/artifact/io.github.pixee/java-security-toolkit/1.2.2)\n",
351+
diffSide=DiffSide.RIGHT,
352+
properties={"contextual_description": "true"},
353+
packageActions=[],
354+
fixedFindings=[],
355+
)
356+
],
357+
ai=None,
358+
strategy=Strategy.deterministic,
359+
provisional=False,
360+
fixedFindings=[],
361+
fixQuality=None,
362+
),
363+
ChangeSet(
364+
path="pom.xml",
365+
diff="--- pom.xml\n+++ pom.xml\n@@ -48,7 +48,8 @@\n <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>\n <roller.version>6.1.5</roller.version>\n <slf4j.version>1.7.36</slf4j.version>\n- </properties>\n+ <versions.java-security-toolkit>1.2.2</versions.java-security-toolkit>\n+ </properties>\n \n <modules>\n <module>app</module>\n@@ -110,7 +111,12 @@\n <version>5.11.4</version>\n <scope>test</scope>\n </dependency>\n- </dependencies>\n+ <dependency>\n+ <groupId>io.github.pixee</groupId>\n+ <artifactId>java-security-toolkit</artifactId>\n+ <version>${versions.java-security-toolkit}</version>\n+ </dependency>\n+ </dependencies>\n </dependencyManagement>\n \n </project>",
366+
changes=[
367+
Change(
368+
lineNumber=114,
369+
description="This library holds security tools for protecting Java API calls.\n\nLicense: MIT ✅ | [Open source](https://github.com/pixee/java-security-toolkit) ✅ | [More facts](https://mvnrepository.com/artifact/io.github.pixee/java-security-toolkit/1.2.2)\n",
370+
diffSide=DiffSide.RIGHT,
371+
properties={"contextual_description": "true"},
372+
packageActions=[],
373+
fixedFindings=[],
374+
)
375+
],
376+
ai=None,
377+
strategy=Strategy.deterministic,
378+
provisional=False,
379+
fixedFindings=[],
380+
fixQuality=None,
381+
),
382+
],
383+
unfixedFindings=[],
384+
)
385+
fix_result = from_v2_result_per_finding(
386+
result,
387+
strategy=StrategyV3.ai,
388+
provisional=True,
389+
ai_metadata=AIMetadata(provider="pixee"),
390+
)
391+
assert fix_result
392+
assert len(fix_result.changeSets) == 3
393+
all_paths = {cs.path for cs in fix_result.changeSets}
394+
assert "app/pom.xml" in all_paths
395+
assert "pom.xml" in all_paths
396+
assert fix_result.fixMetadata
397+
# Assert that the metadata complies with the passed parameters
398+
assert fix_result.fixMetadata.generation.strategy == StrategyV3.ai
399+
assert fix_result.fixMetadata.generation.provisional
400+
assert fix_result.fixMetadata.generation.ai
401+
assert fix_result.fixMetadata.generation.ai.provider
402+
assert fix_result.fixMetadata.generation.ai.provider == "pixee"
403+
404+
262405
def test_v2_to_v3_conversion():
263406
with open("tests/samples/codetfv2_sample.codetf", "r") as f:
264407
codetfv2 = CodeTF.model_validate_json(f.read())

0 commit comments

Comments
 (0)