Documentation
¶
Overview ¶
Package monitoring ports src/sec_af/monitoring.py — continuous monitoring: baseline storage and regression detection.
It compares a current SecurityAuditResult against a stored baseline to identify new vulnerabilities (regressions) and fixed issues, keyed on the finding FINGERPRINT (the stable content hash), never on the finding id.
Python parity — scope: monitoring.py is NOT wired into app.py. AuditInput declares `monitoring_mode` and `baseline_path`, but nothing reads them, so no reasoner calls save_baseline/compare_with_baseline today. This package is ported for 1:1 completeness (and because the baseline file format is a user-visible artifact); it must NOT be wired into the Go node either, or the two implementations would diverge in behavior rather than only in language.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CompareWithBaseline ¶
func CompareWithBaseline(current schemas.SecurityAuditResult, baselinePath string) (schemas.MonitoringResult, error)
CompareWithBaseline compares current scan results against a stored baseline.
Ports monitoring.py compare_with_baseline. The returned MonitoringResult is built from schemas.NewMonitoringResult so the two list fields are `[]` rather than null when nothing changed, matching pydantic's default_factory=list.
Python parity, four behaviors that are easy to "clean up" by accident:
- new_findings follows the ORDER of current.findings, and a fingerprint that appears twice in the current scan is reported twice (the loop appends per finding, it does not deduplicate).
- fixed_findings follows the FIRST-SEEN order of fingerprints in the baseline, but carries the LAST record for a repeated fingerprint — that is what the dict comprehension `{f["fingerprint"]: f for f in ...}` produces.
- unchanged_count counts DISTINCT shared fingerprints (a set intersection), so it need not equal len(current.findings) - len(new_findings).
- regression_detected is `len(new_findings) > 0` only; fixed findings never set it.
func SaveBaseline ¶
func SaveBaseline(result schemas.SecurityAuditResult, path string) error
SaveBaseline saves scan results as a baseline for future comparison.
Ports monitoring.py save_baseline. The bytes are `json.dumps(baseline_data, indent=2)` — see pyjson.go for why encoding/json cannot produce them — with NO trailing newline, matching `Path(path).write_text(...)`.
Python parity: write_text neither creates parent directories nor tolerates a missing one; os.WriteFile behaves the same. The 0o644 mode is Go's explicit spelling of what CPython's open() defaults to (0o666 before umask).
Types ¶
type BaselineData ¶
type BaselineData struct {
CommitSha string `json:"commit_sha"`
Timestamp string `json:"timestamp"`
Findings []BaselineFinding `json:"findings"`
// contains filtered or unexported fields
}
BaselineData is the whole baseline document.
Ports monitoring.py's BaselineData TypedDict. Because Python reads the two top-level keys DIFFERENTLY — `baseline.get("commit_sha", "unknown")` tolerates an absent key, `baseline["findings"]` raises KeyError on one — a decoded BaselineData also remembers which keys the file actually carried. Use CommitShaOr and FindingsPresent rather than reading the fields directly when the file may not have come from SaveBaseline.
func BaselineDataFor ¶
func BaselineDataFor(result schemas.SecurityAuditResult) BaselineData
BaselineDataFor projects a SecurityAuditResult into the baseline document save_baseline writes. Exported so callers can inspect (or diff) a baseline without touching the filesystem; SaveBaseline is this plus the write.
Python parity: `result.timestamp.isoformat()` — schemas.Timestamp.String() is that exact spelling — and `.value` on the two enums, which for a `str, Enum` is the plain string.
func LoadBaseline ¶
func LoadBaseline(path string) (BaselineData, error)
LoadBaseline loads baseline scan data from a file.
Ports monitoring.py load_baseline: `json.loads(Path(path).read_text( encoding="utf-8"))`. A missing file or malformed JSON is an error, exactly as Python raises OSError / json.JSONDecodeError.
func (BaselineData) CommitShaOr ¶
func (d BaselineData) CommitShaOr(fallback string) string
CommitShaOr ports `baseline.get("commit_sha", fallback)`: an ABSENT key yields fallback, a present one yields its value even when that value is the empty string.
func (BaselineData) FindingsPresent ¶
func (d BaselineData) FindingsPresent() bool
FindingsPresent reports whether the decoded document carried a "findings" key at all — the condition Python's `baseline["findings"]` subscript turns into a KeyError.
func (*BaselineData) UnmarshalJSON ¶
func (d *BaselineData) UnmarshalJSON(b []byte) error
UnmarshalJSON decodes the document and records top-level key presence.
Python parity: json.loads on a non-object (a list, a bare number) succeeds and the TypedDict cast is a no-op, so the failure surfaces later as a TypeError from `baseline["findings"]`. Go cannot decode a non-object into a struct at all, so the failure moves up to LoadBaseline — same outcome (an error, no result), earlier and with a clearer message.
type BaselineFinding ¶
type BaselineFinding struct {
ID string `json:"id"`
Fingerprint string `json:"fingerprint"`
Title string `json:"title"`
Severity string `json:"severity"`
CweID string `json:"cwe_id"`
Verdict string `json:"verdict"`
FilePath string `json:"file_path"`
StartLine int `json:"start_line"`
}
BaselineFinding is one finding as stored in a baseline file.
Ports monitoring.py's BaselineFinding TypedDict. The Go struct's FIELD ORDER is the dict-literal order in save_baseline, which is what json.dumps writes (Python dicts are insertion-ordered) — see baselineFindingObject.