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
34 changes: 29 additions & 5 deletions gitgalaxy/recorders/record_keeper.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,11 +492,35 @@ def record_mission(
# #366: security_auditor.py's real output key is "is_ml_threat", not
# "is_malware" -- a near-miss rename that left this column always 0.
is_malware = 1 if file_data.get("is_ml_threat", False) else 0
# #367: no producer ever set file_data["has_credentials"]; the engine's
# real hardcoded-secrets signal is equations["sec_hardcoded_secrets"]
# (security_lens.py, correlated in galaxyscope.py's Active Hemorrhage
# step, #348).
has_creds = 1 if file_data.get("equations", {}).get("sec_hardcoded_secrets", 0) > 0 else 0
# #367: no producer ever set file_data["has_credentials"]. #381 then
# read equations["sec_hardcoded_secrets"], but file_data["equations"]
# is not a durable carrier -- it's rebuilt/emptied/pruned across
# several galaxyscope.py phases (= {} at ~L1039, per-key del at
# ~L1090, assembled on a separate logic_data object in the worker),
# so that read was still 100% zero at population scale
# (squid-protocol/gitgalaxy#1144). The durable carriers are the same
# hit_vector / risk_vector slots this recorder already writes as
# threat_private_info / risk_secrets_risk:
# - hit_vector["sec_hardcoded_secrets"]: raw SecurityLens count
# - risk_vector["secrets_risk"]: thresholded score, also spiked to
# 100 for critical leaks via a path that zeroes hit_vector
# (signal_processor.py:360), so both slots are checked.
# equations is kept only as a last-resort fallback.
hs_idx = (
self.SIGNAL_SCHEMA.index("sec_hardcoded_secrets")
if "sec_hardcoded_secrets" in self.SIGNAL_SCHEMA
else -1
)
sr_idx = self.RISK_SCHEMA.index("secrets_risk") if "secrets_risk" in self.RISK_SCHEMA else -1
has_creds = (
1
if (
(hs_idx >= 0 and len(hv) > hs_idx and hv[hs_idx] > 0)
or (sr_idx >= 0 and len(rv) > sr_idx and rv[sr_idx] > 0)
or file_data.get("equations", {}).get("sec_hardcoded_secrets", 0) > 0
)
else 0
)
# #368: no producer ever set file_data["binary_anomaly"]. The Binary
# Analysis Sensor (galaxyscope.py) does exist and does run
# SecurityLens.scan_binary() on suspicious binaries, but its findings
Expand Down
20 changes: 10 additions & 10 deletions tests/ruff_audit_baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@
"gitgalaxy/recorders/record_keeper.py:291: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:292: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:293: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:652: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:653: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:656: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:658: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:659: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:704: RUF005": "Consider iterable unpacking instead of concatenation",
"gitgalaxy/recorders/record_keeper.py:773: RUF005": "Consider iterable unpacking instead of concatenation",
"gitgalaxy/recorders/record_keeper.py:810: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:811: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:842: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:676: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:677: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:680: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:682: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:683: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:728: RUF005": "Consider iterable unpacking instead of concatenation",
"gitgalaxy/recorders/record_keeper.py:797: RUF005": "Consider iterable unpacking instead of concatenation",
"gitgalaxy/recorders/record_keeper.py:834: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:835: W291": "Trailing whitespace",
"gitgalaxy/recorders/record_keeper.py:866: W291": "Trailing whitespace",
"gitgalaxy/recorders/sbom_recorder.py:221: PERF401": "Use `list.extend` to create a transformed list",
"gitgalaxy/security/security_auditor.py:359: RUF046": "Value being cast to `int` is already an integer",
"gitgalaxy/security/security_auditor.py:424: PERF203": "`try`-`except` within a loop incurs performance overhead",
Expand Down
41 changes: 34 additions & 7 deletions tests/tools_recorders/test_record_keeper.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
def keeper():
"""Initializes the RecordKeeper with a controlled schema for deterministic testing."""
mock_schemas = {
"RISK_SCHEMA": ["tech_debt", "cognitive_load"],
"SIGNAL_SCHEMA": ["high_risk_execution", "io", "sec_tainted_injection"],
"RISK_SCHEMA": ["tech_debt", "cognitive_load", "secrets_risk"],
"SIGNAL_SCHEMA": ["high_risk_execution", "io", "sec_tainted_injection", "sec_hardcoded_secrets"],
}
with patch("gitgalaxy.recorders.record_keeper.RECORDING_SCHEMAS", mock_schemas):
return RecordKeeper()
Expand Down Expand Up @@ -58,12 +58,11 @@ def mock_pipeline_state():
},
"is_ml_threat": True,
"equations": {
"sec_hardcoded_secrets": 1, # Maps to has_credentials, #367
"sec_extension_mismatch": 1, # Maps to binary_anomaly, #368
"sec_self_propagation": 1, # Maps to obfuscation_flag, #1150
},
"risk_vector": [80.0, 60.0], # debt, cog_load
"hit_vector": [2, 5, 1], # danger, io, tainted_injection
"risk_vector": [80.0, 60.0, 42.0], # debt, cog_load, secrets_risk (#367)
"hit_vector": [2, 5, 1, 3], # danger, io, tainted_injection, sec_hardcoded_secrets (#367)
"classes": [{"name": "APIRouter", "inheritance": ["BaseRouter"], "method_count": 5}],
"functions": [
{
Expand Down Expand Up @@ -181,8 +180,10 @@ def test_record_keeper_data_insertion(keeper, mock_pipeline_state, tmp_path):
# #366: is_malware reads file_data["is_ml_threat"] (security_auditor.py's
# real output key), not the never-produced "is_malware".
assert file_row["is_malware"] == 1
# #367: has_credentials reads equations["sec_hardcoded_secrets"], not the
# never-produced "has_credentials".
# #367: has_credentials reads the durable hit_vector["sec_hardcoded_secrets"] /
# risk_vector["secrets_risk"] slots (the never-produced "has_credentials" key and
# #381's non-durable equations["sec_hardcoded_secrets"] read were both always 0).
# Note the mock's equations dict deliberately has NO sec_hardcoded_secrets key.
assert file_row["has_credentials"] == 1
# #368: binary_anomaly reads equations["sec_extension_mismatch"], not the
# never-produced "binary_anomaly".
Expand Down Expand Up @@ -221,6 +222,32 @@ def test_record_keeper_data_insertion(keeper, mock_pipeline_state, tmp_path):
conn.close()


# ==============================================================================
# TEST 2b: #367 -- has_credentials stays 0 when no real secret signal survives
# ==============================================================================
def test_has_credentials_zero_without_signal(keeper, mock_pipeline_state, tmp_path):
"""
#367 guard: with both durable carriers empty (hit_vector["sec_hardcoded_secrets"]
== 0, risk_vector["secrets_risk"] == 0.0) and nothing in the equations dict, the
column must be 0 -- proves the new read isn't just always-on.
"""
db_path = tmp_path / "test_no_creds.sqlite"
parsed, unparsable, summary, session = mock_pipeline_state

parsed[0]["hit_vector"] = [2, 5, 1, 0] # sec_hardcoded_secrets slot -> 0
parsed[0]["risk_vector"] = [80.0, 60.0, 0.0] # secrets_risk slot -> 0.0
parsed[0]["equations"] = {}

keeper.record_mission(parsed, unparsable, summary, session, str(db_path))

conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute("SELECT * FROM file_data WHERE file_name='router.py'")
assert cursor.fetchone()["has_credentials"] == 0
conn.close()


# ==============================================================================
# TEST 3: IDEMPOTENCY (THE CASCADE DELETE)
# ==============================================================================
Expand Down
Loading