Skip to content

Commit dd25a33

Browse files
committed
fix(arcsight-incidents): stable modified, redact errors, close responses
Address the remaining Copilot review plus an independent senior pass, and keep the connector installable against current master: - converter_to_stix.py: the Case-Incident `modified` now falls back to the already-stable `created` value (never "now") when the ArcSight modified timestamp is missing or unparseable, so the deterministic id is no longer re-sent with a drifting `modified` each run. - connector.py: the top-level error handler no longer logs or forwards `str(err)` (the ESM auth flow carries the password/token in query parameters, which a requests exception string can embed); it logs the exception type via structured `meta` and sends a sanitized work message. - arcsight_client/api_client.py: `_request` closes the HTTP response on the 429/5xx-retry and non-retriable HTTPError paths so a periodic run does not keep connections checked out of the requests Session pool. - src/requirements.txt: bump the pinned pycti to 7.260615.0 to match the current connectors-sdk; align the README minimum and the manifest support_version.
1 parent ada36c2 commit dd25a33

7 files changed

Lines changed: 42 additions & 8 deletions

File tree

external-import/arcsight-incidents/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ author identity and marked with a configurable TLP.
3737

3838
## Requirements
3939

40-
- OpenCTI Platform >= 7.260609.0
40+
- OpenCTI Platform >= 7.260615.0
4141
- A reachable ArcSight ESM Manager (Service Layer REST API enabled)
4242
- An ESM account allowed to read cases
4343

external-import/arcsight-incidents/__metadata__/connector_manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"last_verified_date": null,
1212
"playbook_supported": false,
1313
"max_confidence_level": 50,
14-
"support_version": ">=7.260609.0",
14+
"support_version": ">=7.260615.0",
1515
"subscription_link": "https://www.opentext.com/products/arcsight-enterprise-security-manager",
1616
"source_code": "https://github.com/OpenCTI-Platform/connectors/tree/master/external-import/arcsight-incidents",
1717
"manager_supported": true,

external-import/arcsight-incidents/src/arcsight_client/api_client.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,10 +214,14 @@ def _request(self, method: str, path: str, **kwargs) -> Optional[requests.Respon
214214
continue
215215

216216
if response.status_code == 429 or response.status_code >= 500:
217+
status_code = response.status_code
218+
# Release the connection back to the Session pool: the body is
219+
# never consumed on this retry/error path.
220+
response.close()
217221
if last_attempt:
218222
self.helper.connector_logger.warning(
219223
"[API] ArcSight request failed",
220-
meta={"path": path, "status_code": response.status_code},
224+
meta={"path": path, "status_code": status_code},
221225
)
222226
return None
223227
time.sleep(self.BACKOFF_FACTOR * (2**attempt))
@@ -234,6 +238,7 @@ def _request(self, method: str, path: str, **kwargs) -> Optional[requests.Respon
234238
"error_type": type(err).__name__,
235239
},
236240
)
241+
response.close()
237242
return None
238243
return response
239244
return None

external-import/arcsight-incidents/src/connector/connector.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,18 @@ def process_message(self) -> None:
7575
self.helper.connector_logger.info("[CONNECTOR] Connector stopped...")
7676
sys.exit(0)
7777
except Exception as err:
78-
self.helper.connector_logger.error(str(err))
78+
# Do not log or forward str(err): the ESM auth flow passes the
79+
# password/token as query parameters and a requests exception string
80+
# usually embeds the full request URL, so it could leak credentials.
81+
self.helper.connector_logger.error(
82+
"[CONNECTOR] Run failed", meta={"error_type": type(err).__name__}
83+
)
7984
if work_id:
80-
self.helper.api.work.to_processed(work_id, str(err), in_error=True)
85+
self.helper.api.work.to_processed(
86+
work_id,
87+
f"ArcSight Incidents connector run failed: {type(err).__name__}",
88+
in_error=True,
89+
)
8190

8291
def run(self) -> None:
8392
self.helper.schedule_process(

external-import/arcsight-incidents/src/connector/converter_to_stix.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,13 @@ def create_case_incident(
232232
if source_dt is not None
233233
else _FALLBACK_TIMESTAMP
234234
)
235-
modified = self._to_iso(
236-
case.get("modifiedTimestamp") or case.get("modifiedTime") or created
235+
# Fall back to the already-stable created value (never "now") when the
236+
# modified timestamp is missing or unparseable, so the deterministic
237+
# Case-Incident id is not re-sent with a drifting modified each run.
238+
modified_dt = self._parse_timestamp(
239+
case.get("modifiedTimestamp") or case.get("modifiedTime")
237240
)
241+
modified = self._format_iso(modified_dt) if modified_dt is not None else created
238242
severity = self._map_severity(
239243
case.get("consequenceSeverity", case.get("severity"))
240244
)

external-import/arcsight-incidents/src/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
pycti==7.260609.0
1+
pycti==7.260615.0
22
pydantic~=2.11.3
33
requests~=2.33.0
44
stix2~=3.0.1

external-import/arcsight-incidents/tests/test_converter.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,19 @@ def test_case_incident_timestamps_use_stable_fallback_without_timestamp():
179179
case = converter.create_case_incident({"name": "x", "resourceid": "A"})
180180
assert str(case["created"]).startswith("1970-01-01")
181181
assert str(case["modified"]).startswith("1970-01-01")
182+
183+
184+
def test_case_incident_modified_falls_back_to_created_when_unparseable():
185+
# A non-empty but unparseable modified timestamp must fall back to the stable
186+
# created value (not "now"), so the deterministic id is not re-sent with a
187+
# drifting modified each run.
188+
converter = _converter()
189+
case = converter.create_case_incident(
190+
{
191+
"name": "x",
192+
"resourceid": "A",
193+
"createdTimestamp": 1700000000000,
194+
"modifiedTimestamp": "not-a-timestamp",
195+
}
196+
)
197+
assert case["modified"] == case["created"]

0 commit comments

Comments
 (0)