Skip to content
Merged
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
120 changes: 114 additions & 6 deletions action_entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import os
import sys
import json
import hashlib
from pathlib import Path

try:
Expand All @@ -22,6 +23,7 @@
from qwed_sdk.guards.system_guard import SystemGuard
from qwed_sdk.guards.config_guard import ConfigGuard
from qwed_new.guards.process_guard import ProcessVerifier
from qwed_new.core.verification_context import _canonical_json


def get_env(name: str, default: str = "") -> str:
Expand All @@ -47,7 +49,7 @@
print(f"⚠️ Invalid GITHUB_OUTPUT path: {output_file}")
return
# deepcode ignore PT: Path validated with commonpath containment check
with open(output_path, "a") as f:

Check notice on line 52 in action_entrypoint.py

View check run for this annotation

QWED Security / QWED Security

QWED: taint_analysis

TAINT_FLOW_BOUNDARY violation: Tainted data from os.environ.get reaches open -- Path traversal. Flow: os -> output_file -> output_path Context=RUNTIME_CODE. Decision reason: Executable runtime path contains a risky but non-blocking pattern. Pre-existing: not introduced by this PR.
f.write(f"{name}={value}\n")
print(f"::set-output name={name}::{value}") # Legacy fallback

Expand Down Expand Up @@ -97,14 +99,19 @@
print(f"❌ Unsupported engine: {engine}")
sys.exit(1)

print(f"🔍 Verdict: {result.verified}")
print(f"📝 Explanation: {result.explanation}")
print(f"🔍 Verdict: {result.is_verified}")
explanation = result.result.get("explanation", "") if result.result else ""
print(f"📝 Explanation: {explanation}")

set_output("verified", str(result.verified).lower())
set_output("explanation", result.explanation)
set_output("badge_url", generate_badge_url(result.verified))
set_output("verified", str(result.is_verified).lower())
set_output("explanation", explanation)
set_output("badge_url", generate_badge_url(result.is_verified))

if not result.verified and get_env("FAIL_ON_FINDINGS", "true") == "true":
verdict = "VERIFIED" if result.is_verified else "UNVERIFIABLE"
admission = "ADMIT" if result.is_verified else "DENY"
_set_vc_outputs(verdict, admission, {"explanation": explanation}, engine)

if not result.is_verified and get_env("FAIL_ON_FINDINGS", "true") == "true":
sys.exit(1)

except Exception as e:
Expand Down Expand Up @@ -182,6 +189,10 @@
set_output("findings_count", str(len(findings)))
set_output("badge_url", generate_badge_url(len(findings) == 0))

verdict = "VERIFIED" if len(findings) == 0 else "UNVERIFIABLE"
admission = "ADMIT" if len(findings) == 0 else "DENY"
_set_vc_outputs(verdict, admission, {"findings_count": len(findings)}, "secrets")

if findings and get_env("FAIL_ON_FINDINGS", "true") == "true":
sys.exit(1)

Expand Down Expand Up @@ -254,6 +265,10 @@
set_output("findings_count", str(len(findings)))
set_output("badge_url", generate_badge_url(len(findings) == 0))

verdict = "VERIFIED" if len(findings) == 0 else "UNVERIFIABLE"
admission = "ADMIT" if len(findings) == 0 else "DENY"
_set_vc_outputs(verdict, admission, {"findings_count": len(findings)}, "code")

if findings and get_env("FAIL_ON_FINDINGS", "true") == "true":
sys.exit(1)

Expand Down Expand Up @@ -302,6 +317,10 @@
set_output("findings_count", str(len(findings)))
set_output("badge_url", generate_badge_url(len(findings) == 0))

verdict = "VERIFIED" if len(findings) == 0 else "UNVERIFIABLE"
admission = "ADMIT" if len(findings) == 0 else "DENY"
_set_vc_outputs(verdict, admission, {"findings_count": len(findings)}, "shell")

if findings and get_env("FAIL_ON_FINDINGS", "true") == "true":
sys.exit(1)

Expand Down Expand Up @@ -372,6 +391,10 @@
set_output("process_rate", f"{milestone_result['process_rate']:.4f}")
set_output("findings", json.dumps(findings))

verdict = "VERIFIED" if authorized else "UNVERIFIABLE"
admission = "ADMIT" if authorized else "DENY"
_set_vc_outputs(verdict, admission, {"irac_score": irac_result["score"], "process_rate": milestone_result["process_rate"]}, "process")

Comment thread
coderabbitai[bot] marked this conversation as resolved.
if not authorized and get_env("FAIL_ON_FINDINGS", "true") == "true":
sys.exit(1)

Expand Down Expand Up @@ -472,6 +495,91 @@
return "https://img.shields.io/badge/QWED-failed-red?logo=data:image/svg+xml;base64,..."


# ============== VERIFICATION CONTEXT v1.0 ==============
def _formal_statement_for(scan_type: str, verdict: str) -> str:
if verdict == "VERIFIED":
return f"QWED {scan_type} verification passed with no findings"
elif verdict == "UNVERIFIABLE":
return f"QWED {scan_type} verification detected findings and was not admitted"
else:
return f"QWED {scan_type} verification was blocked"


def _build_verification_context(
verdict: str,
admission: str,
proof_ref,
evidence: dict,
scan_type: str,
) -> dict:
configuration: dict = {"action": get_env("ACTION", "verify")}
paths = get_env("PATHS", "")
if paths:
configuration["paths"] = paths

return {
"spec_version": "1.0",
"object": {
"formal_statement": _formal_statement_for(scan_type, verdict),
},
Comment thread
rahuldass19 marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"context": {
"interpretation": {
"theory": f"QWED {scan_type} verification",
"logic": "deterministic pattern matching",
},
"proof": {
"verifier": "QWED Action",
"verifier_version": "3.1.0",
"configuration": configuration,
"theory_scope": f"QWED {scan_type} verification",
"trusted_dependencies": ("qwed-verification",),
"outcome_treatment": "unknown/timeout/error resolve to UNVERIFIABLE or BLOCKED",
},
"evidence": {
"evidence": evidence,
"proof_ref": proof_ref,
},
"decision": {
"admission": admission,
},
},
"verdict": verdict,
}


def _compute_vc_proof_ref(formal_statement: str, context: dict) -> str:
bound_context: dict = {k: v for k, v in context.items() if k != "proof_ref"}
if "evidence" in bound_context:
bound_context["evidence"] = {
k: v for k, v in bound_context["evidence"].items() if k != "proof_ref"
}
bound = {
"formal_statement": formal_statement,
"context": bound_context,
}
canonical = _canonical_json(bound)
return f"sha256:{hashlib.sha256(canonical.encode('utf-8')).hexdigest()}"


def _set_vc_outputs(verdict: str, admission: str, evidence: dict, scan_type: str):
output_format = get_env("OUTPUT_FORMAT", "text")
formal_statement = _formal_statement_for(scan_type, verdict)

if verdict == "VERIFIED":
vc_for_hash = _build_verification_context(verdict, admission, None, evidence, scan_type)
proof_ref = _compute_vc_proof_ref(formal_statement, vc_for_hash["context"])
else:
proof_ref = None

set_output("verdict", verdict)
set_output("admission", admission)
set_output("proof_ref", proof_ref if proof_ref is not None else "")

if output_format in ("verification-context", "json"):
vc = _build_verification_context(verdict, admission, proof_ref, evidence, scan_type)
set_output("verification_context", json.dumps(vc, separators=(",", ":")))


# ============== MAIN ==============
def main():
action = get_env("ACTION", "verify")
Expand Down
Loading