Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions python-sdk/codepathfinder/c_decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
"""
Decorators for C security rules.

Mirrors `go_decorators.py` exactly. The only behavioural difference is the
language tag injected into dataflow IR: ``language="c"`` so the executor
scopes analysis to nodes with ``Node.Language == "c"``.

Pure ``calls()`` matchers (``type == "call_matcher"``) are NOT language-scoped,
matching the @go_rule contract — see PR-11 spec, Gap 1 / Gap 4.
"""

import atexit
import json
import sys
from typing import Callable, List
from dataclasses import dataclass


@dataclass
class CRuleMetadata:
"""Metadata for a C security rule."""

id: str
name: str = ""
severity: str = "MEDIUM"
category: str = "security"
cwe: str = ""
cve: str = ""
tags: str = ""
message: str = ""
owasp: str = ""


@dataclass
class CRuleDefinition:
"""Complete definition of a C security rule."""

metadata: CRuleMetadata
matcher: dict
rule_function: Callable


_c_rules: List[CRuleDefinition] = []
_auto_execute_enabled = False


def _enable_auto_execute() -> None:
"""Enable automatic rule compilation and stdout JSON output at script exit."""
global _auto_execute_enabled
if _auto_execute_enabled:
return
_auto_execute_enabled = True

def _output_rules():
if not _c_rules:
return
from . import c_ir

compiled = c_ir.compile_all_rules()
print(json.dumps(compiled))

atexit.register(_output_rules)


def _register_rule() -> None:
"""Enable auto-execute when a rule file is run as ``__main__``."""
frame = sys._getframe(2)
if frame.f_globals.get("__name__") == "__main__":
_enable_auto_execute()


def c_rule(
id: str,
name: str = "",
severity: str = "MEDIUM",
category: str = "security",
cwe: str = "",
cve: str = "",
tags: str = "",
message: str = "",
owasp: str = "",
) -> Callable:
"""
Decorator for C security rules. Mirrors @go_rule.

Sets ``language="c"`` on the DataflowMatcher dict so DataflowExecutor
scopes analysis to C functions only. Only affects flows() rules
(``type=="dataflow"``); pure calls() rules remain language-agnostic.
"""

def decorator(func: Callable) -> Callable:
matcher_result = func()

if hasattr(matcher_result, "to_ir"):
matcher_dict = matcher_result.to_ir()
elif hasattr(matcher_result, "to_dict"):
matcher_dict = matcher_result.to_dict()
elif isinstance(matcher_result, dict):
matcher_dict = matcher_result
else:
raise ValueError(f"Rule {id} must return a matcher or dict")

if isinstance(matcher_dict, dict) and matcher_dict.get("type") == "dataflow":
matcher_dict["language"] = "c"

metadata = CRuleMetadata(
id=id,
name=name or func.__name__.replace("_", " ").title(),
severity=severity,
category=category,
cwe=cwe,
cve=cve,
tags=tags,
message=message or f"Security issue detected by {id}",
owasp=owasp,
)
rule_def = CRuleDefinition(
metadata=metadata,
matcher=matcher_dict,
rule_function=func,
)
_c_rules.append(rule_def)
_register_rule()

return func

return decorator


def get_c_rules() -> List[CRuleDefinition]:
"""Return a snapshot of registered C rules."""
return _c_rules.copy()


def clear_c_rules() -> None:
"""Clear all registered C rules (test isolation)."""
global _c_rules
_c_rules = []
40 changes: 40 additions & 0 deletions python-sdk/codepathfinder/c_ir.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""
JSON IR (Intermediate Representation) compiler for C security rules.

Mirrors `go_ir.py`. Emits ``language="c"`` in rule metadata for
display/filtering. The same field is also present inside the matcher dict
(injected by ``@c_rule``) for runtime DataflowExecutor scoping.
"""

from typing import List, Dict, Any

from .c_decorators import get_c_rules


def compile_c_rules() -> List[Dict[str, Any]]:
"""Compile all registered C rules into the JSON IR list expected by the Go executor."""
rules = get_c_rules()
compiled = []

for rule in rules:
ir = {
"rule": {
"id": rule.metadata.id,
"name": rule.metadata.name,
"severity": rule.metadata.severity.lower(),
"cwe": rule.metadata.cwe,
"owasp": rule.metadata.owasp,
"description": rule.metadata.message
or f"Security issue: {rule.metadata.id}",
"language": "c",
},
"matcher": rule.matcher,
}
compiled.append(ir)

return compiled


def compile_all_rules() -> List[Dict[str, Any]]:
"""Compile all C rules to the JSON IR array format."""
return compile_c_rules()
138 changes: 138 additions & 0 deletions python-sdk/codepathfinder/cpp_decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
"""
Decorators for C++ security rules.

Mirrors `c_decorators.py` / `go_decorators.py`. The only behavioural
difference is the language tag injected into dataflow IR: ``language="cpp"``
so the executor scopes analysis to nodes with ``Node.Language == "cpp"``.

Pure ``calls()`` matchers (``type == "call_matcher"``) are NOT language-scoped,
matching the @go_rule contract — see PR-11 spec, Gap 1 / Gap 4.
"""

import atexit
import json
import sys
from typing import Callable, List
from dataclasses import dataclass


@dataclass
class CppRuleMetadata:
"""Metadata for a C++ security rule."""

id: str
name: str = ""
severity: str = "MEDIUM"
category: str = "security"
cwe: str = ""
cve: str = ""
tags: str = ""
message: str = ""
owasp: str = ""


@dataclass
class CppRuleDefinition:
"""Complete definition of a C++ security rule."""

metadata: CppRuleMetadata
matcher: dict
rule_function: Callable


_cpp_rules: List[CppRuleDefinition] = []
_auto_execute_enabled = False


def _enable_auto_execute() -> None:
"""Enable automatic rule compilation and stdout JSON output at script exit."""
global _auto_execute_enabled
if _auto_execute_enabled:
return
_auto_execute_enabled = True

def _output_rules():
if not _cpp_rules:
return
from . import cpp_ir

compiled = cpp_ir.compile_all_rules()
print(json.dumps(compiled))

atexit.register(_output_rules)


def _register_rule() -> None:
"""Enable auto-execute when a rule file is run as ``__main__``."""
frame = sys._getframe(2)
if frame.f_globals.get("__name__") == "__main__":
_enable_auto_execute()


def cpp_rule(
id: str,
name: str = "",
severity: str = "MEDIUM",
category: str = "security",
cwe: str = "",
cve: str = "",
tags: str = "",
message: str = "",
owasp: str = "",
) -> Callable:
"""
Decorator for C++ security rules. Mirrors @go_rule / @c_rule.

Sets ``language="cpp"`` on the DataflowMatcher dict so DataflowExecutor
scopes analysis to C++ functions only. Only affects flows() rules
(``type=="dataflow"``); pure calls() rules remain language-agnostic.
"""

def decorator(func: Callable) -> Callable:
matcher_result = func()

if hasattr(matcher_result, "to_ir"):
matcher_dict = matcher_result.to_ir()
elif hasattr(matcher_result, "to_dict"):
matcher_dict = matcher_result.to_dict()
elif isinstance(matcher_result, dict):
matcher_dict = matcher_result
else:
raise ValueError(f"Rule {id} must return a matcher or dict")

if isinstance(matcher_dict, dict) and matcher_dict.get("type") == "dataflow":
matcher_dict["language"] = "cpp"

metadata = CppRuleMetadata(
id=id,
name=name or func.__name__.replace("_", " ").title(),
severity=severity,
category=category,
cwe=cwe,
cve=cve,
tags=tags,
message=message or f"Security issue detected by {id}",
owasp=owasp,
)
rule_def = CppRuleDefinition(
metadata=metadata,
matcher=matcher_dict,
rule_function=func,
)
_cpp_rules.append(rule_def)
_register_rule()

return func

return decorator


def get_cpp_rules() -> List[CppRuleDefinition]:
"""Return a snapshot of registered C++ rules."""
return _cpp_rules.copy()


def clear_cpp_rules() -> None:
"""Clear all registered C++ rules (test isolation)."""
global _cpp_rules
_cpp_rules = []
40 changes: 40 additions & 0 deletions python-sdk/codepathfinder/cpp_ir.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""
JSON IR (Intermediate Representation) compiler for C++ security rules.

Mirrors `c_ir.py`. Emits ``language="cpp"`` in rule metadata for
display/filtering. The same field is also present inside the matcher dict
(injected by ``@cpp_rule``) for runtime DataflowExecutor scoping.
"""

from typing import List, Dict, Any

from .cpp_decorators import get_cpp_rules


def compile_cpp_rules() -> List[Dict[str, Any]]:
"""Compile all registered C++ rules into the JSON IR list expected by the Go executor."""
rules = get_cpp_rules()
compiled = []

for rule in rules:
ir = {
"rule": {
"id": rule.metadata.id,
"name": rule.metadata.name,
"severity": rule.metadata.severity.lower(),
"cwe": rule.metadata.cwe,
"owasp": rule.metadata.owasp,
"description": rule.metadata.message
or f"Security issue: {rule.metadata.id}",
"language": "cpp",
},
"matcher": rule.matcher,
}
compiled.append(ir)

return compiled


def compile_all_rules() -> List[Dict[str, Any]]:
"""Compile all C++ rules to the JSON IR array format."""
return compile_cpp_rules()
11 changes: 11 additions & 0 deletions python-sdk/rules/c_decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
Backward-compatibility shim. c_decorators has moved to the codepathfinder package.
Import from: from codepathfinder.c_decorators import c_rule
"""
from codepathfinder.c_decorators import ( # noqa: F401
CRuleMetadata,
CRuleDefinition,
c_rule,
get_c_rules,
clear_c_rules,
)
7 changes: 7 additions & 0 deletions python-sdk/rules/c_ir.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
Backward-compatibility shim. c_ir has moved to the codepathfinder package.
"""
from codepathfinder.c_ir import ( # noqa: F401
compile_c_rules,
compile_all_rules,
)
11 changes: 11 additions & 0 deletions python-sdk/rules/cpp_decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
Backward-compatibility shim. cpp_decorators has moved to the codepathfinder package.
Import from: from codepathfinder.cpp_decorators import cpp_rule
"""
from codepathfinder.cpp_decorators import ( # noqa: F401
CppRuleMetadata,
CppRuleDefinition,
cpp_rule,
get_cpp_rules,
clear_cpp_rules,
)
7 changes: 7 additions & 0 deletions python-sdk/rules/cpp_ir.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
Backward-compatibility shim. cpp_ir has moved to the codepathfinder package.
"""
from codepathfinder.cpp_ir import ( # noqa: F401
compile_cpp_rules,
compile_all_rules,
)
Loading
Loading