runcloud 0.2.168 → 0.2.169

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -145,8 +145,10 @@ export function hostReport(host, canonical, extra, diagnoses, now, fleetTtl, dee
145
145
  processScan: Object.keys(process).length ? {
146
146
  observedAt: stamp(process.observedAt), staleAfterSeconds: num(process.staleAfterSeconds) ?? 900,
147
147
  sourceUnavailable: process.sourceUnavailable === true || Boolean(extra.collectorError), status: str(process.status) ?? 'unknown', error: str(process.error), root: process.root === true,
148
- iocs: rows(process.iocs).map(ioc => ({ status: str(ioc.status) ?? 'unknown', contained: ioc.contained === true })),
149
- findings: rows(process.findings).map(finding => ({ reason: str(finding.reason) ?? 'unknown' })),
148
+ iocs: rows(process.iocs).map(ioc => ({ status: str(ioc.status) ?? 'unknown', contained: ioc.contained === true,
149
+ signals: Array.isArray(ioc.signals) ? ioc.signals.filter((signal) => typeof signal === 'string') : [], lastObservedAt: stamp(ioc.lastObservedAt) })),
150
+ findings: rows(process.findings).map(finding => ({ reason: str(finding.reason) ?? 'unknown', confidence: str(finding.confidence) ?? undefined,
151
+ lastObservedAt: stamp(finding.lastObservedAt) })),
150
152
  coverage: { iocs: str(row(process.coverage).iocs) ?? 'partial' }, baseline: { state: str(row(process.baseline).state) ?? 'missing' },
151
153
  } : null,
152
154
  };
@@ -10,6 +10,22 @@ export function processFreshness(scan, now) {
10
10
  return 'stale';
11
11
  return 'fresh';
12
12
  }
13
+ export function processSecurityAlert(scan, held = false) {
14
+ if (!scan)
15
+ return null;
16
+ const strongIocs = (scan.iocs ?? []).filter(ioc => ['present', 'active'].includes(ioc.status) && ((ioc.signals ?? []).some(signal => ['binary_hash', 'running_hash'].includes(signal)) ||
17
+ ((ioc.signals ?? []).includes('original_path') && (ioc.signals ?? []).some(signal => ['plist_hash', 'loaded_label', 'root_daemon', 'running_path'].includes(signal)))));
18
+ const knownFindings = (scan.findings ?? []).filter(finding => finding.reason === 'known_ioc' && finding.confidence === 'high');
19
+ const contained = (scan.iocs ?? []).filter(ioc => ioc.contained || ioc.status === 'contained');
20
+ const detected = scan.status === 'critical' || strongIocs.length > 0 || knownFindings.length > 0;
21
+ if (!detected && (!held || !contained.length))
22
+ return null;
23
+ const timestamps = (detected ? [...strongIocs.map(ioc => ioc.lastObservedAt), ...knownFindings.map(finding => finding.lastObservedAt)]
24
+ : contained.map(ioc => ioc.lastObservedAt)).filter((value) => !!value && Number.isFinite(Date.parse(value)));
25
+ const observedAt = timestamps.sort((a, b) => Date.parse(b) - Date.parse(a))[0] ?? scan.observedAt;
26
+ return detected ? { state: 'detected', label: 'Known malware detected', observedAt }
27
+ : { state: 'contained-hold', label: 'Malware contained · hold reported', observedAt };
28
+ }
13
29
  export function leaseStateLabel(state) {
14
30
  return { accepting: 'Lease gate open', rejecting: 'Lease gate closed', unknown: 'Lease gate unknown', held: 'Operator hold' }[state] ?? state;
15
31
  }
@@ -102,11 +118,12 @@ export function machineDiagnosis(host, hosted, observedState, now, staleAfterSec
102
118
  checks.push('process checks missing');
103
119
  else {
104
120
  const freshness = processFreshness(scan, now);
105
- const malicious = scan.iocs.some(ioc => ['present', 'active'].includes(ioc.status)) || scan.findings.some(finding => finding.reason === 'known_ioc');
121
+ const alert = processSecurityAlert(scan);
122
+ const malicious = alert?.state === 'detected';
106
123
  if (malicious)
107
- findings.unshift(`known malicious daemon detected${freshness === 'fresh' ? '' : ' in retained evidence; recheck required'}`);
108
- else if (scan.status === 'critical')
109
- findings.unshift('critical process finding requires review');
124
+ findings.unshift(`known malicious daemon detected${processFreshness({ ...scan, observedAt: alert.observedAt }, now) === 'fresh' ? '' : ' in retained evidence; recheck required'}`);
125
+ else if (scan.iocs.some(ioc => ['present', 'active'].includes(ioc.status)))
126
+ checks.push('process IOC signals need corroboration; see scanner details');
110
127
  const review = scan.findings.filter(finding => ['rooted_workload', 'long_lived_workload', 'identity_changed', 'unreviewed_persistence'].includes(finding.reason));
111
128
  if (review.length)
112
129
  findings.push(`${review.length} process or persistence finding(s) need review`);
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const CLI_VERSION = '0.2.168';
1
+ export const CLI_VERSION = '0.2.169';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "runcloud",
3
- "version": "0.2.168",
3
+ "version": "0.2.169",
4
4
  "description": "Create and control run.cloud remote mobile simulators and cloud sandboxes",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
Binary file
Binary file