Arx 0.5.3: Fix problems on Linux (fix.sh with undo), nftables detection
Browse files- README.md +2 -1
- app/main.cjs +1 -1
- app/package.json +1 -1
- app/renderer/app.js +9 -6
- app/renderer/index.html +2 -2
- collectors/linux/audit.sh +3 -1
- collectors/linux/fix.sh +251 -0
- index.html +5 -5
- library/fixes.json +71 -0
README.md
CHANGED
|
@@ -177,7 +177,7 @@ folder keeps its old name since the rename to Arx in 0.4.3). Reports: *Documents
|
|
| 177 |
(add `DEVSEIS_SMOKE_MODEL=1` to include the model). The installer background is `app/build/dmg-background.html`,
|
| 178 |
rendered to `dmg-background.png` / `@2x.png`.
|
| 179 |
|
| 180 |
-
## Fix problems (macOS
|
| 181 |
|
| 182 |
After the audit, the app offers **Fix** for problems it can change safely: firewall, stealth mode, Gatekeeper, malware
|
| 183 |
definitions, installing security responses and updates automatically, guest account, analytics sharing and network time
|
|
@@ -187,6 +187,7 @@ fixes and the collector runs again, so the result is re-checked. **Undo** runs `
|
|
| 187 |
ids marked `auto` in `fixes.json` ever reach the script; settings set by a configuration profile (MDM) are skipped.
|
| 188 |
Changes that need the user (screen lock, FileVault, updates that restart, sharing, Time Machine, accounts) are kind
|
| 189 |
`settings`: the app opens the exact System Settings page with the steps. The AI never writes or runs commands.
|
|
|
|
| 190 |
Test without changing anything: `DEVSEIS_SMOKE=<folder> DEVSEIS_FIX_DRY=1 app/node_modules/.bin/electron app`.
|
| 191 |
|
| 192 |
**Model per build** (`app/model-version.json`): `version` (v0.3) for the full-precision build on graphics chips with
|
|
|
|
| 177 |
(add `DEVSEIS_SMOKE_MODEL=1` to include the model). The installer background is `app/build/dmg-background.html`,
|
| 178 |
rendered to `dmg-background.png` / `@2x.png`.
|
| 179 |
|
| 180 |
+
## Fix problems (macOS since 0.5.0, Linux since 0.5.3)
|
| 181 |
|
| 182 |
After the audit, the app offers **Fix** for problems it can change safely: firewall, stealth mode, Gatekeeper, malware
|
| 183 |
definitions, installing security responses and updates automatically, guest account, analytics sharing and network time
|
|
|
|
| 187 |
ids marked `auto` in `fixes.json` ever reach the script; settings set by a configuration profile (MDM) are skipped.
|
| 188 |
Changes that need the user (screen lock, FileVault, updates that restart, sharing, Time Machine, accounts) are kind
|
| 189 |
`settings`: the app opens the exact System Settings page with the steps. The AI never writes or runs commands.
|
| 190 |
+
**Linux** (`collectors/linux/fix.sh`, through the desktop's pkexec prompt): firewall on with incoming blocked (ufw, or firewalld; SSH allowed first if it runs), automatic security updates (unattended-upgrades via an Arx apt drop-in, or dnf-automatic), network time, guest login off, popularity-contest/whoopsie off, automatic login off (GDM, LightDM, SDDM; edited files are backed up next to the state file), and ClamAV definition updates (offered only where ClamAV runs: `requires` in `fixes.json`). Tested in Ubuntu 24.04 containers: fix, re-audit and undo restore every file and service; the packaged app runs headless with `xvfb-run`.
|
| 191 |
Test without changing anything: `DEVSEIS_SMOKE=<folder> DEVSEIS_FIX_DRY=1 app/node_modules/.bin/electron app`.
|
| 192 |
|
| 193 |
**Model per build** (`app/model-version.json`): `version` (v0.3) for the full-precision build on graphics chips with
|
app/main.cjs
CHANGED
|
@@ -147,7 +147,7 @@ function autoFixIds(ids) {
|
|
| 147 |
const fixes = readJson('library', 'fixes.json').fixes;
|
| 148 |
return [...new Set(ids)].filter(id => typeof id === 'string' && fixes[id] && fixes[id][OS_FAMILY] && fixes[id][OS_FAMILY].kind === 'auto');
|
| 149 |
}
|
| 150 |
-
const FIX_SCRIPT = { macos: 'fix.sh' };
|
| 151 |
// Test only (smoke test): the fix script prints what it would change and changes nothing; no administrator prompt.
|
| 152 |
const FIX_DRY = !!(process.env.DEVSEIS_SMOKE && process.env.DEVSEIS_FIX_DRY);
|
| 153 |
const fixHistoryFile = () => path.join(app.getPath('userData'), 'fix-history.json');
|
|
|
|
| 147 |
const fixes = readJson('library', 'fixes.json').fixes;
|
| 148 |
return [...new Set(ids)].filter(id => typeof id === 'string' && fixes[id] && fixes[id][OS_FAMILY] && fixes[id][OS_FAMILY].kind === 'auto');
|
| 149 |
}
|
| 150 |
+
const FIX_SCRIPT = { macos: 'fix.sh', linux: 'fix.sh' };
|
| 151 |
// Test only (smoke test): the fix script prints what it would change and changes nothing; no administrator prompt.
|
| 152 |
const FIX_DRY = !!(process.env.DEVSEIS_SMOKE && process.env.DEVSEIS_FIX_DRY);
|
| 153 |
const fixHistoryFile = () => path.join(app.getPath('userData'), 'fix-history.json');
|
app/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
{
|
| 2 |
"name": "devseis-endpoint-auditor",
|
| 3 |
"productName": "Arx",
|
| 4 |
-
"version": "0.5.
|
| 5 |
"description": "Read-only endpoint audit for ISO 27001, GDPR and the EU AI Act, with a local AI auditor.",
|
| 6 |
"author": {
|
| 7 |
"name": "Devseis",
|
|
|
|
| 1 |
{
|
| 2 |
"name": "devseis-endpoint-auditor",
|
| 3 |
"productName": "Arx",
|
| 4 |
+
"version": "0.5.3",
|
| 5 |
"description": "Read-only endpoint audit for ISO 27001, GDPR and the EU AI Act, with a local AI auditor.",
|
| 6 |
"author": {
|
| 7 |
"name": "Devseis",
|
app/renderer/app.js
CHANGED
|
@@ -184,11 +184,13 @@ async function renderFixes(evidence, fixResults) {
|
|
| 184 |
const name = id => (evidence.results.find(r => r.id === id) || {}).name || id;
|
| 185 |
if (fixResults && fixResults.length) {
|
| 186 |
$('fix-banner').innerHTML = `<b>${fixResults.some(r => r.outcome === 'undone') ? 'Undo finished' : 'Fixes finished'}</b><ul class="fix-results">${fixResults.map(r =>
|
| 187 |
-
`<li>${OUTCOME[r.outcome] || '•'} <b>${escapeHtml(name(r.id))}</b>: ${escapeHtml(r.message)}</li>`).join('')}</ul><p class="muted">The results below come from checking this
|
| 188 |
$('fix-banner').classList.remove('hidden');
|
| 189 |
} else $('fix-banner').classList.add('hidden');
|
| 190 |
|
| 191 |
-
|
|
|
|
|
|
|
| 192 |
const auto = gaps.filter(r => fixes[r.id][os].kind === 'auto');
|
| 193 |
// Several checks can share one settings page and the same steps (screen lock): show them once.
|
| 194 |
const seen = new Set();
|
|
@@ -204,9 +206,9 @@ async function renderFixes(evidence, fixResults) {
|
|
| 204 |
<div class="fix-auto">${auto.map(r => { const f = fixes[r.id][os]; return `<label class="fix-item"><input type="checkbox" data-fix="${r.id}" checked>
|
| 205 |
<span><b>${escapeHtml(f.title)}</b><br><span class="muted">${escapeHtml(f.changes)}${f.note ? ` ${escapeHtml(f.note)}` : ''}</span></span></label>`; }).join('')}</div>
|
| 206 |
<div class="actions"><button class="primary" id="fix-selected">Fix selected (${auto.length})</button></div>` : ''}
|
| 207 |
-
${guided.length ? `<h3>Fix in System Settings</h3><p class="muted">These need your password, a recovery key or a restart, so you make the change. Arx opens the right page.</p>
|
| 208 |
<div class="fix-guided">${guided.map(r => { const f = fixes[r.id][os]; return `<div class="fix-item"><span><b>${escapeHtml(f.title)}</b>
|
| 209 |
-
<ol>${f.steps.map(x => `<li>${escapeHtml(x)}</li>`).join('')}</ol></span><button class="secondary" data-settings="${r.id}">Open Settings</button></div>`; }).join('')}</div>
|
| 210 |
<p class="muted">When you are done, choose <b>Run another audit</b> to check again.</p>` : ''}
|
| 211 |
${undoable.length ? `<div class="undo-row"><span class="muted">Last fixes (${new Date(last.at).toLocaleString()}): ${undoable.map(id => escapeHtml(fixes[id][os].title)).join(', ')}</span>
|
| 212 |
<button class="secondary" id="fix-undo">Undo last fixes</button></div>` : ''}`;
|
|
@@ -225,9 +227,10 @@ function confirmFix(action, ids) {
|
|
| 225 |
const os = plan.os;
|
| 226 |
pendingFix = { action, ids };
|
| 227 |
$('fix-title').textContent = action === 'undo' ? 'Undo these fixes?' : `Fix ${ids.length} problem${ids.length > 1 ? 's' : ''}?`;
|
| 228 |
-
$('fix-intro').textContent = action === 'undo' ? 'Arx will put these settings back as they were before, then check this
|
| 229 |
-
: 'Arx will change these settings, then check this
|
| 230 |
$('fix-confirm-list').innerHTML = ids.map(id => { const f = fixes[id][os]; return `<li><b>${escapeHtml(f.title)}</b><br><span class="muted">${escapeHtml(action === 'undo' ? 'Back to the previous setting.' : f.changes)}</span></li>`; }).join('');
|
|
|
|
| 231 |
$('fix-modal').classList.remove('hidden');
|
| 232 |
}
|
| 233 |
$('fix-cancel').addEventListener('click', () => { pendingFix = null; $('fix-modal').classList.add('hidden'); });
|
|
|
|
| 184 |
const name = id => (evidence.results.find(r => r.id === id) || {}).name || id;
|
| 185 |
if (fixResults && fixResults.length) {
|
| 186 |
$('fix-banner').innerHTML = `<b>${fixResults.some(r => r.outcome === 'undone') ? 'Undo finished' : 'Fixes finished'}</b><ul class="fix-results">${fixResults.map(r =>
|
| 187 |
+
`<li>${OUTCOME[r.outcome] || '•'} <b>${escapeHtml(name(r.id))}</b>: ${escapeHtml(r.message)}</li>`).join('')}</ul><p class="muted">The results below come from checking this computer again after the change.</p>`;
|
| 188 |
$('fix-banner').classList.remove('hidden');
|
| 189 |
} else $('fix-banner').classList.add('hidden');
|
| 190 |
|
| 191 |
+
// A fix can depend on software being present (e.g. ClamAV updates only where ClamAV runs): requires.check's result must contain requires.contains.
|
| 192 |
+
const usable = f => !f.requires || ((evidence.results.find(x => x.id === f.requires.check) || {}).actual || '').toLowerCase().includes(f.requires.contains.toLowerCase());
|
| 193 |
+
const gaps = evidence.results.filter(r => r.status === 'Non-Compliant' && fixes[r.id] && fixes[r.id][os] && usable(fixes[r.id][os]));
|
| 194 |
const auto = gaps.filter(r => fixes[r.id][os].kind === 'auto');
|
| 195 |
// Several checks can share one settings page and the same steps (screen lock): show them once.
|
| 196 |
const seen = new Set();
|
|
|
|
| 206 |
<div class="fix-auto">${auto.map(r => { const f = fixes[r.id][os]; return `<label class="fix-item"><input type="checkbox" data-fix="${r.id}" checked>
|
| 207 |
<span><b>${escapeHtml(f.title)}</b><br><span class="muted">${escapeHtml(f.changes)}${f.note ? ` ${escapeHtml(f.note)}` : ''}</span></span></label>`; }).join('')}</div>
|
| 208 |
<div class="actions"><button class="primary" id="fix-selected">Fix selected (${auto.length})</button></div>` : ''}
|
| 209 |
+
${guided.length ? `<h3>${os === 'macos' ? 'Fix in System Settings' : 'Fix in Settings'}</h3><p class="muted">These need your password, a recovery key or a restart, so you make the change. Arx opens the right page.</p>
|
| 210 |
<div class="fix-guided">${guided.map(r => { const f = fixes[r.id][os]; return `<div class="fix-item"><span><b>${escapeHtml(f.title)}</b>
|
| 211 |
+
<ol>${f.steps.map(x => `<li>${escapeHtml(x)}</li>`).join('')}</ol></span>${f.url ? `<button class="secondary" data-settings="${r.id}">Open Settings</button>` : ''}</div>`; }).join('')}</div>
|
| 212 |
<p class="muted">When you are done, choose <b>Run another audit</b> to check again.</p>` : ''}
|
| 213 |
${undoable.length ? `<div class="undo-row"><span class="muted">Last fixes (${new Date(last.at).toLocaleString()}): ${undoable.map(id => escapeHtml(fixes[id][os].title)).join(', ')}</span>
|
| 214 |
<button class="secondary" id="fix-undo">Undo last fixes</button></div>` : ''}`;
|
|
|
|
| 227 |
const os = plan.os;
|
| 228 |
pendingFix = { action, ids };
|
| 229 |
$('fix-title').textContent = action === 'undo' ? 'Undo these fixes?' : `Fix ${ids.length} problem${ids.length > 1 ? 's' : ''}?`;
|
| 230 |
+
$('fix-intro').textContent = action === 'undo' ? 'Arx will put these settings back as they were before, then check this computer again.'
|
| 231 |
+
: 'Arx will change these settings, then check this computer again. You can undo each fix afterwards.';
|
| 232 |
$('fix-confirm-list').innerHTML = ids.map(id => { const f = fixes[id][os]; return `<li><b>${escapeHtml(f.title)}</b><br><span class="muted">${escapeHtml(action === 'undo' ? 'Back to the previous setting.' : f.changes)}</span></li>`; }).join('');
|
| 233 |
+
$('fix-password-note').textContent = { macos: 'macOS will ask for your administrator password.', linux: 'Your system will ask for your password (administrator rights).' }[os] || 'Your computer will ask for your administrator password.';
|
| 234 |
$('fix-modal').classList.remove('hidden');
|
| 235 |
}
|
| 236 |
$('fix-cancel').addEventListener('click', () => { pendingFix = null; $('fix-modal').classList.add('hidden'); });
|
app/renderer/index.html
CHANGED
|
@@ -88,9 +88,9 @@
|
|
| 88 |
<div class="modal hidden" id="fix-modal" role="dialog" aria-modal="true" aria-labelledby="fix-title">
|
| 89 |
<div class="modal-box">
|
| 90 |
<h2 id="fix-title">Fix these problems?</h2>
|
| 91 |
-
<p class="muted" id="fix-intro">Arx will change these settings, then check this
|
| 92 |
<ul class="fix-list" id="fix-confirm-list"></ul>
|
| 93 |
-
<p class="muted">
|
| 94 |
<div class="actions">
|
| 95 |
<button class="secondary" id="fix-cancel">Cancel</button>
|
| 96 |
<button class="primary" id="fix-go">Fix now</button>
|
|
|
|
| 88 |
<div class="modal hidden" id="fix-modal" role="dialog" aria-modal="true" aria-labelledby="fix-title">
|
| 89 |
<div class="modal-box">
|
| 90 |
<h2 id="fix-title">Fix these problems?</h2>
|
| 91 |
+
<p class="muted" id="fix-intro">Arx will change these settings, then check this computer again. You can undo each fix afterwards.</p>
|
| 92 |
<ul class="fix-list" id="fix-confirm-list"></ul>
|
| 93 |
+
<p class="muted" id="fix-password-note">Your computer will ask for your administrator password.</p>
|
| 94 |
<div class="actions">
|
| 95 |
<button class="secondary" id="fix-cancel">Cancel</button>
|
| 96 |
<button class="primary" id="fix-go">Fix now</button>
|
collectors/linux/audit.sh
CHANGED
|
@@ -322,7 +322,9 @@ if [ -z "$fw_name" ] && active firewalld; then
|
|
| 322 |
fi
|
| 323 |
if [ -z "$fw_name" ] && have nft && is_root; then
|
| 324 |
ruleset=$(nft list ruleset 2>/dev/null)
|
| 325 |
-
|
|
|
|
|
|
|
| 326 |
fw_name=nftables
|
| 327 |
printf '%s' "$ruleset" | grep -E "hook input.*policy drop" >/dev/null && fw_in=deny || fw_in=allow
|
| 328 |
printf '%s' "$ruleset" | grep -E "hook output.*policy drop" >/dev/null && fw_out=deny || fw_out=allow
|
|
|
|
| 322 |
fi
|
| 323 |
if [ -z "$fw_name" ] && have nft && is_root; then
|
| 324 |
ruleset=$(nft list ruleset 2>/dev/null)
|
| 325 |
+
# Counts only if something is blocked: an input chain with policy drop, or drop/reject rules (a disabled ufw leaves
|
| 326 |
+
# empty chains with policy accept behind, which is no firewall).
|
| 327 |
+
if printf '%s' "$ruleset" | grep -q "hook input" && printf '%s' "$ruleset" | grep -qE "policy drop|[[:space:]](drop|reject)([[:space:]]|$)"; then
|
| 328 |
fw_name=nftables
|
| 329 |
printf '%s' "$ruleset" | grep -E "hook input.*policy drop" >/dev/null && fw_in=deny || fw_in=allow
|
| 330 |
printf '%s' "$ruleset" | grep -E "hook output.*policy drop" >/dev/null && fw_out=deny || fw_out=allow
|
collectors/linux/fix.sh
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Arx fixer for Linux: applies or undoes the one-click fixes in library/fixes.json, one tested function per fix.
|
| 3 |
+
# The AI never writes these commands; the app only passes fix ids, and this script accepts only the ids below.
|
| 4 |
+
#
|
| 5 |
+
# fix.sh apply --state FILE [--dry-run] ID [ID ...] save the current values to FILE, then fix
|
| 6 |
+
# fix.sh undo --state FILE [--dry-run] ID [ID ...] put back what was saved in FILE
|
| 7 |
+
#
|
| 8 |
+
# Needs root (the app asks with the desktop's password prompt, pkexec). Prints one line per fix:
|
| 9 |
+
# RESULT <tab> id <tab> applied|undone|unchanged|skipped|failed <tab> message
|
| 10 |
+
# State lines: id <tab> key <tab> previous value. Config files Arx edits are copied first to <state>.files/ and copied
|
| 11 |
+
# back on undo; where possible Arx adds its own drop-in file instead (undo = delete it).
|
| 12 |
+
set -u
|
| 13 |
+
ACTION=${1:-}; shift || true
|
| 14 |
+
STATE=""; DRY=0; IDS=()
|
| 15 |
+
while [ $# -gt 0 ]; do
|
| 16 |
+
case "$1" in
|
| 17 |
+
--state) STATE=$2; shift 2 ;;
|
| 18 |
+
--dry-run) DRY=1; shift ;;
|
| 19 |
+
*) IDS+=("$1"); shift ;;
|
| 20 |
+
esac
|
| 21 |
+
done
|
| 22 |
+
case "$ACTION" in apply|undo) ;; *) echo "usage: fix.sh apply|undo --state FILE [--dry-run] ID..." >&2; exit 2 ;; esac
|
| 23 |
+
[ -n "$STATE" ] || { echo "--state is required" >&2; exit 2; }
|
| 24 |
+
[ "$DRY" -eq 1 ] || [ "$(id -u)" -eq 0 ] || { echo "fix.sh must run as root" >&2; exit 3; }
|
| 25 |
+
FILES="$STATE.files"
|
| 26 |
+
export DEBIAN_FRONTEND=noninteractive
|
| 27 |
+
|
| 28 |
+
have() { command -v "$1" >/dev/null 2>&1; }
|
| 29 |
+
result() { printf 'RESULT\t%s\t%s\t%s\n' "$1" "$2" "$3"; }
|
| 30 |
+
run() { if [ "$DRY" -eq 1 ]; then echo "DRY: $*" >&2; else "$@"; fi; }
|
| 31 |
+
save() { [ "$DRY" -eq 1 ] || printf '%s\t%s\t%s\n' "$1" "$2" "$3" >> "$STATE"; }
|
| 32 |
+
saved() { # id key -> the value saved at apply time (last one wins), only if it is one of the known safe forms
|
| 33 |
+
local v
|
| 34 |
+
v=$(awk -F'\t' -v id="$1" -v key="$2" '$1 == id && $2 == key { v = $3 } END { print v }' "$STATE" 2>/dev/null)
|
| 35 |
+
case "$v" in on|off|yes|no|1|0|unset|created|none|allow|deny|reject) printf '%s' "$v" ;;
|
| 36 |
+
/*) [[ "$v" =~ ^/[A-Za-z0-9/_.-]+$ ]] && printf '%s' "$v" ;;
|
| 37 |
+
*) printf '' ;; esac
|
| 38 |
+
}
|
| 39 |
+
backup() { # id file: keep a copy of a config file before Arx edits it (once per apply)
|
| 40 |
+
[ "$DRY" -eq 1 ] && return 0
|
| 41 |
+
mkdir -p "$FILES"; chmod 700 "$FILES"
|
| 42 |
+
if [ -e "$2" ]; then cp -p "$2" "$FILES/$1$(printf '%s' "$2" | tr '/' '_')"; save "$1" "file:$2" yes
|
| 43 |
+
else save "$1" "file:$2" none; fi
|
| 44 |
+
}
|
| 45 |
+
restore() { # id file
|
| 46 |
+
local was; was=$(awk -F'\t' -v id="$1" -v key="file:$2" '$1 == id && $2 == key { v = $3 } END { print v }' "$STATE" 2>/dev/null)
|
| 47 |
+
case "$was" in
|
| 48 |
+
yes) run cp -p "$FILES/$1$(printf '%s' "$2" | tr '/' '_')" "$2" ;;
|
| 49 |
+
none) run rm -f "$2" ;;
|
| 50 |
+
*) return 1 ;;
|
| 51 |
+
esac
|
| 52 |
+
}
|
| 53 |
+
svc_active() { systemctl is-active --quiet "$1" 2>/dev/null; }
|
| 54 |
+
svc_enabled() { systemctl is-enabled --quiet "$1" 2>/dev/null; }
|
| 55 |
+
pkg_install() { # quietly install a package with the system's package manager
|
| 56 |
+
if have apt-get; then run apt-get install -y -qq "$@" >/dev/null 2>&1
|
| 57 |
+
elif have dnf; then run dnf install -y -q "$@" >/dev/null 2>&1
|
| 58 |
+
else return 1; fi
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
# --- firewall: ufw (Debian/Ubuntu) or firewalld (RHEL/Fedora) ----------------------------------------------------
|
| 62 |
+
ufw_state() { ufw status 2>/dev/null | grep -q "Status: active" && echo on || echo off; }
|
| 63 |
+
ufw_in() { ufw status verbose 2>/dev/null | sed -n 's/^Default: //p' | grep -oE '(deny|reject|allow) \(incoming\)' | cut -d' ' -f1; }
|
| 64 |
+
FW_ST=""; FW_IN=""; FW_A=""; FW_E="" # state before this run's change (firewall.enabled and .inbound share it)
|
| 65 |
+
firewall_fix() { # id
|
| 66 |
+
local id=$1
|
| 67 |
+
if have ufw; then
|
| 68 |
+
if [ "$ACTION" = apply ]; then
|
| 69 |
+
[ -n "$FW_ST" ] || { FW_ST=$(ufw_state); FW_IN=$(ufw_in); FW_IN=${FW_IN:-deny}; }
|
| 70 |
+
local st=$FW_ST in=$FW_IN; save "$id" ufw "$st"; save "$id" incoming "$in"
|
| 71 |
+
[ "$(ufw_state)" = on ] && [ "$(ufw_in)" = deny ] && [ "$st" = on ] && [ "$in" = deny ] && { result "$id" unchanged "The firewall was already on and blocking incoming connections."; return; }
|
| 72 |
+
# Keep remote administration working: allow SSH before blocking incoming connections.
|
| 73 |
+
if svc_active ssh || svc_active sshd; then run ufw allow OpenSSH >/dev/null 2>&1 || run ufw allow 22/tcp >/dev/null 2>&1; fi
|
| 74 |
+
[ "$(ufw_state)" = on ] && [ "$(ufw_in)" = deny ] && { result "$id" applied "Firewall (ufw) is on and blocking incoming connections."; return; }
|
| 75 |
+
run ufw default deny incoming >/dev/null && run ufw default allow outgoing >/dev/null && run ufw --force enable >/dev/null \
|
| 76 |
+
&& result "$id" applied "Firewall (ufw) turned on: incoming connections are blocked, SSH stays allowed if it was running." \
|
| 77 |
+
|| result "$id" failed "Could not turn on ufw."
|
| 78 |
+
else
|
| 79 |
+
local st in; st=$(saved "$id" ufw); in=$(saved "$id" incoming); [ -n "$st" ] || { result "$id" skipped "Nothing saved to undo."; return; }
|
| 80 |
+
[ -n "$in" ] && run ufw default "$in" incoming >/dev/null
|
| 81 |
+
[ "$st" = off ] && run ufw --force disable >/dev/null
|
| 82 |
+
result "$id" undone "Firewall set back to ${st}, incoming ${in:-unchanged}."
|
| 83 |
+
fi
|
| 84 |
+
elif have firewall-cmd; then
|
| 85 |
+
if [ "$ACTION" = apply ]; then
|
| 86 |
+
[ -n "$FW_A" ] || { svc_active firewalld && FW_A=on || FW_A=off; svc_enabled firewalld && FW_E=on || FW_E=off; }
|
| 87 |
+
local a=$FW_A e=$FW_E
|
| 88 |
+
save "$id" firewalld "$a"; save "$id" firewalld_boot "$e"
|
| 89 |
+
[ "$a" = on ] && [ "$e" = on ] && { result "$id" unchanged "firewalld was already running."; return; }
|
| 90 |
+
run systemctl enable --now firewalld >/dev/null 2>&1 && result "$id" applied "Firewall (firewalld) turned on and started at boot." || result "$id" failed "Could not start firewalld."
|
| 91 |
+
else
|
| 92 |
+
local a e; a=$(saved "$id" firewalld); e=$(saved "$id" firewalld_boot); [ -n "$a" ] || { result "$id" skipped "Nothing saved to undo."; return; }
|
| 93 |
+
[ "$a" = off ] && run systemctl stop firewalld >/dev/null 2>&1
|
| 94 |
+
[ "$e" = off ] && run systemctl disable firewalld >/dev/null 2>&1
|
| 95 |
+
result "$id" undone "firewalld set back to $a."
|
| 96 |
+
fi
|
| 97 |
+
else
|
| 98 |
+
result "$id" skipped "No firewall tool is installed. Install ufw (sudo apt install ufw) or firewalld, then try again."
|
| 99 |
+
fi
|
| 100 |
+
}
|
| 101 |
+
fix_firewall_enabled() { firewall_fix firewall.enabled; }
|
| 102 |
+
fix_firewall_inbound() { firewall_fix firewall.inbound; }
|
| 103 |
+
|
| 104 |
+
# --- automatic security updates -------------------------------------------------------------------------------
|
| 105 |
+
APT_DROPIN=/etc/apt/apt.conf.d/52arx-auto-upgrades
|
| 106 |
+
fix_gdpr_auto_updates() {
|
| 107 |
+
local id=gdpr.auto_updates
|
| 108 |
+
if have apt-get; then
|
| 109 |
+
if [ "$ACTION" = apply ]; then
|
| 110 |
+
local uu pkg=no; uu=$(apt-config dump 2>/dev/null | awk -F'"' '/^APT::Periodic::Unattended-Upgrade /{ print $2 }')
|
| 111 |
+
dpkg-query -W -f='${Status}' unattended-upgrades 2>/dev/null | grep -q "install ok installed" && pkg=yes
|
| 112 |
+
save $id package $pkg
|
| 113 |
+
[ "$uu" = 1 ] && [ $pkg = yes ] && { result $id unchanged "Automatic security updates were already on."; return; }
|
| 114 |
+
[ $pkg = yes ] || [ "$DRY" -eq 1 ] || pkg_install unattended-upgrades || { result $id failed "Could not install unattended-upgrades."; return; }
|
| 115 |
+
[ -e "$APT_DROPIN" ] && save $id dropin yes || save $id dropin created
|
| 116 |
+
if [ "$DRY" -eq 1 ]; then echo "DRY: write $APT_DROPIN" >&2
|
| 117 |
+
else printf '// Added by Arx (undo removes this file)\nAPT::Periodic::Update-Package-Lists "1";\nAPT::Periodic::Unattended-Upgrade "1";\n' > "$APT_DROPIN"; fi
|
| 118 |
+
result $id applied "Security updates now install automatically every day (unattended-upgrades)."
|
| 119 |
+
else
|
| 120 |
+
[ "$(saved $id dropin)" = created ] || { result $id skipped "Nothing saved to undo."; return; }
|
| 121 |
+
run rm -f "$APT_DROPIN"
|
| 122 |
+
if [ "$(saved $id package)" = no ]; then run apt-get purge -y -qq unattended-upgrades >/dev/null 2>&1; fi
|
| 123 |
+
result $id undone "Automatic updates set back as they were$( [ "$(saved $id package)" = no ] && echo " (unattended-upgrades removed again)")."
|
| 124 |
+
fi
|
| 125 |
+
elif have dnf; then
|
| 126 |
+
local conf=/etc/dnf/automatic.conf
|
| 127 |
+
if [ "$ACTION" = apply ]; then
|
| 128 |
+
svc_enabled dnf-automatic-install.timer && { save $id timer on; result $id unchanged "Automatic updates were already on."; return; }
|
| 129 |
+
save $id timer off
|
| 130 |
+
[ -e "$conf" ] || [ "$DRY" -eq 1 ] || pkg_install dnf-automatic || { result $id failed "Could not install dnf-automatic."; return; }
|
| 131 |
+
run systemctl enable --now dnf-automatic-install.timer >/dev/null 2>&1 \
|
| 132 |
+
&& result $id applied "Updates now download and install automatically (dnf-automatic-install timer)." || result $id failed "Could not enable dnf-automatic."
|
| 133 |
+
else
|
| 134 |
+
[ "$(saved $id timer)" = off ] || { result $id skipped "Nothing saved to undo."; return; }
|
| 135 |
+
run systemctl disable --now dnf-automatic-install.timer >/dev/null 2>&1; result $id undone "Automatic updates turned off again."
|
| 136 |
+
fi
|
| 137 |
+
else
|
| 138 |
+
result $id skipped "Unknown package manager."
|
| 139 |
+
fi
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
# --- time synchronisation -------------------------------------------------------------------------------------
|
| 143 |
+
fix_gdpr_time_sync() {
|
| 144 |
+
local id=gdpr.time_sync
|
| 145 |
+
have timedatectl || { result $id skipped "timedatectl is not available."; return; }
|
| 146 |
+
if [ "$ACTION" = apply ]; then
|
| 147 |
+
local now; now=$(timedatectl show -p NTP --value 2>/dev/null)
|
| 148 |
+
[ "$now" = yes ] && { result $id unchanged "Network time was already on."; return; }
|
| 149 |
+
if run timedatectl set-ntp true 2>/dev/null; then save $id ntp no; result $id applied "The clock now sets itself from network time servers."
|
| 150 |
+
else result $id failed "Could not turn on network time (is systemd-timesyncd or chrony installed?)."; fi
|
| 151 |
+
else
|
| 152 |
+
[ "$(saved $id ntp)" = no ] || { result $id skipped "Nothing saved to undo."; return; }
|
| 153 |
+
run timedatectl set-ntp false 2>/dev/null; result $id undone "Network time turned off again."
|
| 154 |
+
fi
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
# --- guest login ----------------------------------------------------------------------------------------------
|
| 158 |
+
fix_gdpr_guest_account() {
|
| 159 |
+
local id=gdpr.guest_account changed=0
|
| 160 |
+
if [ "$ACTION" = apply ]; then
|
| 161 |
+
local f
|
| 162 |
+
while IFS= read -r f; do
|
| 163 |
+
[ -n "$f" ] || continue
|
| 164 |
+
backup $id "$f"; run sed -i -E 's/^([[:space:]]*allow-guest[[:space:]]*=[[:space:]]*)true/\1false/I' "$f"; changed=1
|
| 165 |
+
done < <(grep -rlsiE '^[[:space:]]*allow-guest[[:space:]]*=[[:space:]]*true' /etc/lightdm)
|
| 166 |
+
local shell; shell=$(awk -F: '$1 == "guest" { print $7 }' /etc/passwd)
|
| 167 |
+
if [ -n "$shell" ] && ! [[ "$shell" =~ (nologin|false)$ ]]; then
|
| 168 |
+
save $id guest_shell "$shell"; run usermod -s /usr/sbin/nologin guest; changed=1
|
| 169 |
+
fi
|
| 170 |
+
[ $changed -eq 1 ] && result $id applied "Guest login turned off." || result $id unchanged "No guest login was enabled."
|
| 171 |
+
else
|
| 172 |
+
local any=0
|
| 173 |
+
while IFS= read -r f; do restore $id "$f" && any=1; done < <(awk -F'\t' -v id="$id" '$1 == id && $2 ~ /^file:/ { print substr($2, 6) }' "$STATE" 2>/dev/null | sort -u)
|
| 174 |
+
local sh; sh=$(saved $id guest_shell); [ -n "$sh" ] && { run usermod -s "$sh" guest; any=1; }
|
| 175 |
+
[ $any -eq 1 ] && result $id undone "Guest login set back as it was." || result $id skipped "Nothing saved to undo."
|
| 176 |
+
fi
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
# --- usage reporting ------------------------------------------------------------------------------------------
|
| 180 |
+
fix_gdpr_telemetry() {
|
| 181 |
+
local id=gdpr.telemetry changed=0 pc=/etc/popularity-contest.conf
|
| 182 |
+
if [ "$ACTION" = apply ]; then
|
| 183 |
+
if grep -qsE '^PARTICIPATE="?yes' "$pc"; then backup $id "$pc"; run sed -i 's/^PARTICIPATE=.*/PARTICIPATE="no"/' "$pc"; changed=1; fi
|
| 184 |
+
if svc_active whoopsie || svc_enabled whoopsie; then save $id whoopsie on; run systemctl disable --now whoopsie >/dev/null 2>&1; changed=1; fi
|
| 185 |
+
[ $changed -eq 1 ] && result $id applied "Usage and error reporting turned off." || result $id unchanged "Usage reporting was already off."
|
| 186 |
+
else
|
| 187 |
+
local any=0
|
| 188 |
+
restore $id "$pc" && any=1
|
| 189 |
+
[ "$(saved $id whoopsie)" = on ] && { run systemctl enable --now whoopsie >/dev/null 2>&1; any=1; }
|
| 190 |
+
[ $any -eq 1 ] && result $id undone "Usage reporting set back as it was." || result $id skipped "Nothing saved to undo."
|
| 191 |
+
fi
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
# --- automatic login ------------------------------------------------------------------------------------------
|
| 195 |
+
fix_account_unique_login() { # turns off automatic login only; shared accounts need a person to decide
|
| 196 |
+
local id=account.unique_login changed=0 f
|
| 197 |
+
if [ "$ACTION" = apply ]; then
|
| 198 |
+
for f in /etc/gdm3/custom.conf /etc/gdm/custom.conf; do
|
| 199 |
+
if grep -qsiE '^[[:space:]]*AutomaticLoginEnable[[:space:]]*=[[:space:]]*true' "$f"; then
|
| 200 |
+
backup $id "$f"; run sed -i -E 's/^([[:space:]]*AutomaticLoginEnable[[:space:]]*=[[:space:]]*).*/\1false/I' "$f"; changed=1
|
| 201 |
+
fi
|
| 202 |
+
done
|
| 203 |
+
for f in /etc/lightdm/lightdm.conf /etc/lightdm/lightdm.conf.d/*.conf; do
|
| 204 |
+
if [ -f "$f" ] && grep -qsE '^[[:space:]]*autologin-user[[:space:]]*=[[:space:]]*[^[:space:]]' "$f"; then
|
| 205 |
+
backup $id "$f"; run sed -i -E 's/^([[:space:]]*autologin-user[[:space:]]*=).*/\1/' "$f"; changed=1
|
| 206 |
+
fi
|
| 207 |
+
done
|
| 208 |
+
for f in /etc/sddm.conf /etc/sddm.conf.d/*.conf; do
|
| 209 |
+
if [ -f "$f" ] && awk '/^\[Autologin\]/ { a = 1; next } /^\[/ { a = 0 } a && /^[[:space:]]*User[[:space:]]*=[[:space:]]*[^[:space:]]/ { f = 1 } END { exit !f }' "$f"; then
|
| 210 |
+
backup $id "$f"; run sed -i -E '/^\[Autologin\]/,/^\[/ s/^([[:space:]]*User[[:space:]]*=).*/\1/' "$f"; changed=1
|
| 211 |
+
fi
|
| 212 |
+
done
|
| 213 |
+
[ $changed -eq 1 ] && result $id applied "Automatic login turned off: everyone signs in with their own password. Shared accounts (if any) still need a decision by a person." \
|
| 214 |
+
|| result $id unchanged "Automatic login was already off. Shared accounts need a decision by a person (see the report)."
|
| 215 |
+
else
|
| 216 |
+
local any=0
|
| 217 |
+
while IFS= read -r f; do restore $id "$f" && any=1; done < <(awk -F'\t' -v id="$id" '$1 == id && $2 ~ /^file:/ { print substr($2, 6) }' "$STATE" 2>/dev/null | sort -u)
|
| 218 |
+
[ $any -eq 1 ] && result $id undone "Login settings put back as they were." || result $id skipped "Nothing saved to undo."
|
| 219 |
+
fi
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
# --- ClamAV (only when it is the malware protection in use) ------------------------------------------------------
|
| 223 |
+
fix_malware_signatures() {
|
| 224 |
+
local id=malware.signatures
|
| 225 |
+
[ "$ACTION" = undo ] && { result $id unchanged "Malware definition updates are not undone."; return; }
|
| 226 |
+
have freshclam || { result $id skipped "ClamAV is not installed."; return; }
|
| 227 |
+
if svc_active clamav-freshclam; then run systemctl restart clamav-freshclam >/dev/null 2>&1 && result $id applied "Asked ClamAV to download the latest malware definitions." && return; fi
|
| 228 |
+
run freshclam --quiet >/dev/null 2>&1 && result $id applied "Malware definitions (ClamAV) updated." || result $id failed "freshclam could not update (check the internet connection)."
|
| 229 |
+
}
|
| 230 |
+
fix_malware_signature_updates() {
|
| 231 |
+
local id=malware.signature_updates
|
| 232 |
+
have freshclam || { result $id skipped "ClamAV is not installed."; return; }
|
| 233 |
+
if [ "$ACTION" = apply ]; then
|
| 234 |
+
svc_active clamav-freshclam && { save $id freshclam on; result $id unchanged "Definition updates were already automatic."; return; }
|
| 235 |
+
save $id freshclam off
|
| 236 |
+
run systemctl enable --now clamav-freshclam >/dev/null 2>&1 && result $id applied "ClamAV now updates its malware definitions automatically." || result $id failed "Could not start clamav-freshclam."
|
| 237 |
+
else
|
| 238 |
+
[ "$(saved $id freshclam)" = off ] || { result $id skipped "Nothing saved to undo."; return; }
|
| 239 |
+
run systemctl disable --now clamav-freshclam >/dev/null 2>&1; result $id undone "Automatic definition updates turned off again."
|
| 240 |
+
fi
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
# --- main ---------------------------------------------------------------------------------------------------
|
| 244 |
+
[ "$DRY" -eq 1 ] || { touch "$STATE"; chmod 600 "$STATE"; }
|
| 245 |
+
for id in "${IDS[@]}"; do
|
| 246 |
+
case "$id" in
|
| 247 |
+
firewall.enabled|firewall.inbound|gdpr.auto_updates|gdpr.time_sync|gdpr.guest_account|gdpr.telemetry|account.unique_login|malware.signatures|malware.signature_updates)
|
| 248 |
+
"fix_${id//./_}" ;;
|
| 249 |
+
*) result "$id" skipped "Arx has no automatic fix for this check." ;;
|
| 250 |
+
esac
|
| 251 |
+
done
|
index.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
| 6 |
-
<meta name="description" content="Arx: a free security audit for your Mac, Windows or Linux computer. 70 checks in about a minute, offline, mapped to ISO 27001, GDPR and the EU AI Act, with one-click fixes on Mac. Nothing is uploaded.">
|
| 7 |
<meta name="theme-color" content="#ed2939">
|
| 8 |
<title>Arx · Security audit for your computer</title>
|
| 9 |
<link rel="icon" href="app/build/icon.png">
|
|
@@ -33,7 +33,7 @@
|
|
| 33 |
<section class="wrap hero">
|
| 34 |
<p class="eyebrow"><img src="app/build/icon.png" alt="">Arx security audit</p>
|
| 35 |
<h1>Is your computer secure?<br><span class="grad">Find out in a minute.</span></h1>
|
| 36 |
-
<p class="lead">A free audit of your Mac, Windows or Linux computer: <b><span data-count>70</span> checks</b> mapped to <b>ISO 27001</b>, <b>GDPR</b> and the <b>EU AI Act</b>. A small AI on your computer writes the report, and on
|
| 37 |
|
| 38 |
<div class="device-card" id="deviceCard">
|
| 39 |
<span class="device-icon" id="deviceIcon">💻</span>
|
|
@@ -56,7 +56,7 @@
|
|
| 56 |
<label class="btn btn-lg" for="reportFile" title="Open the …-evidence.json file Arx saves next to each report">Open a report</label>
|
| 57 |
<input type="file" id="reportFile" accept=".json,application/json" hidden>
|
| 58 |
</div>
|
| 59 |
-
<p class="tiny muted" id="heroMeta">Free · open source · version 0.5.
|
| 60 |
</section>
|
| 61 |
|
| 62 |
<section class="wrap">
|
|
@@ -78,7 +78,7 @@
|
|
| 78 |
<ol class="steps">
|
| 79 |
<li><span class="step-n">1</span><div><b>Download and open Arx</b><p class="muted">One app for your system. Admin rights let it read every setting; without them, a few checks are skipped.</p></div></li>
|
| 80 |
<li><span class="step-n">2</span><div><b>Run the audit</b><p class="muted">The audit only reads settings: nothing changes unless you later click Fix. The first run downloads the AI once; after that it works offline.</p></div></li>
|
| 81 |
-
<li><span class="step-n">3</span><div><b>Get your report and fix</b><p class="muted">A score, your biggest gaps and how to fix each one, as a PDF. On
|
| 82 |
</ol>
|
| 83 |
</section>
|
| 84 |
|
|
@@ -95,7 +95,7 @@
|
|
| 95 |
</section>
|
| 96 |
|
| 97 |
<section class="wrap section" id="download">
|
| 98 |
-
<div class="section-head"><h2>Download Arx</h2><span class="tiny muted" id="dlVersion">Version 0.5.
|
| 99 |
<div class="dl-grid">
|
| 100 |
<article class="dl-card" data-os="mac">
|
| 101 |
<span class="ribbon ribbon-you hidden">YOUR SYSTEM</span>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="utf-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
| 6 |
+
<meta name="description" content="Arx: a free security audit for your Mac, Windows or Linux computer. 70 checks in about a minute, offline, mapped to ISO 27001, GDPR and the EU AI Act, with one-click fixes on Mac and Linux. Nothing is uploaded.">
|
| 7 |
<meta name="theme-color" content="#ed2939">
|
| 8 |
<title>Arx · Security audit for your computer</title>
|
| 9 |
<link rel="icon" href="app/build/icon.png">
|
|
|
|
| 33 |
<section class="wrap hero">
|
| 34 |
<p class="eyebrow"><img src="app/build/icon.png" alt="">Arx security audit</p>
|
| 35 |
<h1>Is your computer secure?<br><span class="grad">Find out in a minute.</span></h1>
|
| 36 |
+
<p class="lead">A free audit of your Mac, Windows or Linux computer: <b><span data-count>70</span> checks</b> mapped to <b>ISO 27001</b>, <b>GDPR</b> and the <b>EU AI Act</b>. A small AI on your computer writes the report, and on Mac and Linux Arx can <b>fix problems for you</b>. It runs offline. <u>Nothing is uploaded.</u></p>
|
| 37 |
|
| 38 |
<div class="device-card" id="deviceCard">
|
| 39 |
<span class="device-icon" id="deviceIcon">💻</span>
|
|
|
|
| 56 |
<label class="btn btn-lg" for="reportFile" title="Open the …-evidence.json file Arx saves next to each report">Open a report</label>
|
| 57 |
<input type="file" id="reportFile" accept=".json,application/json" hidden>
|
| 58 |
</div>
|
| 59 |
+
<p class="tiny muted" id="heroMeta">Free · open source · version 0.5.3</p>
|
| 60 |
</section>
|
| 61 |
|
| 62 |
<section class="wrap">
|
|
|
|
| 78 |
<ol class="steps">
|
| 79 |
<li><span class="step-n">1</span><div><b>Download and open Arx</b><p class="muted">One app for your system. Admin rights let it read every setting; without them, a few checks are skipped.</p></div></li>
|
| 80 |
<li><span class="step-n">2</span><div><b>Run the audit</b><p class="muted">The audit only reads settings: nothing changes unless you later click Fix. The first run downloads the AI once; after that it works offline.</p></div></li>
|
| 81 |
+
<li><span class="step-n">3</span><div><b>Get your report and fix</b><p class="muted">A score, your biggest gaps and how to fix each one, as a PDF. On Mac and Linux, click <b>Fix</b> and Arx changes the setting for you (on a Mac it also opens the right System Settings page for the rest). Every fix can be undone.</p></div></li>
|
| 82 |
</ol>
|
| 83 |
</section>
|
| 84 |
|
|
|
|
| 95 |
</section>
|
| 96 |
|
| 97 |
<section class="wrap section" id="download">
|
| 98 |
+
<div class="section-head"><h2>Download Arx</h2><span class="tiny muted" id="dlVersion">Version 0.5.3</span></div>
|
| 99 |
<div class="dl-grid">
|
| 100 |
<article class="dl-card" data-os="mac">
|
| 101 |
<span class="ribbon ribbon-you hidden">YOUR SYSTEM</span>
|
library/fixes.json
CHANGED
|
@@ -9,6 +9,13 @@
|
|
| 9 |
"changes": "Turns on the macOS firewall, which blocks unwanted incoming connections.",
|
| 10 |
"note": "Apps you already allowed keep working.",
|
| 11 |
"undo": true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
}
|
| 13 |
},
|
| 14 |
"firewall.inbound": {
|
|
@@ -18,6 +25,13 @@
|
|
| 18 |
"changes": "Turns on the firewall and stealth mode, so the Mac does not answer probes from other computers on the network.",
|
| 19 |
"note": "Other computers can no longer ping this Mac.",
|
| 20 |
"undo": true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
}
|
| 22 |
},
|
| 23 |
"malware.service": {
|
|
@@ -36,6 +50,17 @@
|
|
| 36 |
"changes": "Installs the latest XProtect malware definitions from Apple.",
|
| 37 |
"note": "An update: there is nothing to undo.",
|
| 38 |
"undo": false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
}
|
| 40 |
},
|
| 41 |
"malware.signature_updates": {
|
|
@@ -45,6 +70,17 @@
|
|
| 45 |
"changes": "Turns on 'Install Security Responses and system files' in Software Update, so malware definitions stay current.",
|
| 46 |
"note": "",
|
| 47 |
"undo": true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
}
|
| 49 |
},
|
| 50 |
"gdpr.auto_updates": {
|
|
@@ -54,6 +90,13 @@
|
|
| 54 |
"changes": "Turns on checking for, downloading and installing macOS updates and security responses automatically.",
|
| 55 |
"note": "macOS may restart overnight to finish an update; it warns first.",
|
| 56 |
"undo": true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
}
|
| 58 |
},
|
| 59 |
"gdpr.guest_account": {
|
|
@@ -63,6 +106,13 @@
|
|
| 63 |
"changes": "Turns off guest login and guest access to shared folders.",
|
| 64 |
"note": "",
|
| 65 |
"undo": true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
}
|
| 67 |
},
|
| 68 |
"gdpr.telemetry": {
|
|
@@ -72,6 +122,13 @@
|
|
| 72 |
"changes": "Turns off 'Share Mac Analytics' and 'Share with app developers'.",
|
| 73 |
"note": "",
|
| 74 |
"undo": true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
}
|
| 76 |
},
|
| 77 |
"gdpr.time_sync": {
|
|
@@ -81,6 +138,13 @@
|
|
| 81 |
"changes": "Turns on 'Set time and date automatically' from Apple's time server, so logs have correct times.",
|
| 82 |
"note": "",
|
| 83 |
"undo": true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
}
|
| 85 |
},
|
| 86 |
"screen_lock.enabled": {
|
|
@@ -240,6 +304,13 @@
|
|
| 240 |
"Set 'Automatically log in as' to Off (at the bottom of the page).",
|
| 241 |
"Give each person their own account; remove or rename shared accounts such as office or admin, keeping one named IT administrator."
|
| 242 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
}
|
| 244 |
}
|
| 245 |
}
|
|
|
|
| 9 |
"changes": "Turns on the macOS firewall, which blocks unwanted incoming connections.",
|
| 10 |
"note": "Apps you already allowed keep working.",
|
| 11 |
"undo": true
|
| 12 |
+
},
|
| 13 |
+
"linux": {
|
| 14 |
+
"kind": "auto",
|
| 15 |
+
"title": "Turn on the firewall",
|
| 16 |
+
"changes": "Turns on the firewall (ufw, or firewalld on Red Hat-type systems) and blocks incoming connections. If remote login (SSH) is running, it stays allowed.",
|
| 17 |
+
"note": "Outgoing connections are not affected.",
|
| 18 |
+
"undo": true
|
| 19 |
}
|
| 20 |
},
|
| 21 |
"firewall.inbound": {
|
|
|
|
| 25 |
"changes": "Turns on the firewall and stealth mode, so the Mac does not answer probes from other computers on the network.",
|
| 26 |
"note": "Other computers can no longer ping this Mac.",
|
| 27 |
"undo": true
|
| 28 |
+
},
|
| 29 |
+
"linux": {
|
| 30 |
+
"kind": "auto",
|
| 31 |
+
"title": "Block incoming connections",
|
| 32 |
+
"changes": "Turns on the firewall (ufw or firewalld) with incoming connections blocked by default. If remote login (SSH) is running, it stays allowed.",
|
| 33 |
+
"note": "Programs that serve other computers (for example a local web server) may need a firewall rule.",
|
| 34 |
+
"undo": true
|
| 35 |
}
|
| 36 |
},
|
| 37 |
"malware.service": {
|
|
|
|
| 50 |
"changes": "Installs the latest XProtect malware definitions from Apple.",
|
| 51 |
"note": "An update: there is nothing to undo.",
|
| 52 |
"undo": false
|
| 53 |
+
},
|
| 54 |
+
"linux": {
|
| 55 |
+
"kind": "auto",
|
| 56 |
+
"title": "Update malware definitions",
|
| 57 |
+
"changes": "Downloads the latest ClamAV malware definitions (freshclam).",
|
| 58 |
+
"note": "An update: there is nothing to undo.",
|
| 59 |
+
"undo": false,
|
| 60 |
+
"requires": {
|
| 61 |
+
"check": "malware.antivirus",
|
| 62 |
+
"contains": "ClamAV"
|
| 63 |
+
}
|
| 64 |
}
|
| 65 |
},
|
| 66 |
"malware.signature_updates": {
|
|
|
|
| 70 |
"changes": "Turns on 'Install Security Responses and system files' in Software Update, so malware definitions stay current.",
|
| 71 |
"note": "",
|
| 72 |
"undo": true
|
| 73 |
+
},
|
| 74 |
+
"linux": {
|
| 75 |
+
"kind": "auto",
|
| 76 |
+
"title": "Update malware definitions automatically",
|
| 77 |
+
"changes": "Starts the ClamAV definition updater (clamav-freshclam) and runs it at every start-up.",
|
| 78 |
+
"note": "",
|
| 79 |
+
"undo": true,
|
| 80 |
+
"requires": {
|
| 81 |
+
"check": "malware.antivirus",
|
| 82 |
+
"contains": "ClamAV"
|
| 83 |
+
}
|
| 84 |
}
|
| 85 |
},
|
| 86 |
"gdpr.auto_updates": {
|
|
|
|
| 90 |
"changes": "Turns on checking for, downloading and installing macOS updates and security responses automatically.",
|
| 91 |
"note": "macOS may restart overnight to finish an update; it warns first.",
|
| 92 |
"undo": true
|
| 93 |
+
},
|
| 94 |
+
"linux": {
|
| 95 |
+
"kind": "auto",
|
| 96 |
+
"title": "Turn on automatic security updates",
|
| 97 |
+
"changes": "Installs and turns on unattended-upgrades (Ubuntu, Debian) or dnf-automatic (Red Hat-type systems), so security updates install every day.",
|
| 98 |
+
"note": "Undo turns it off again and removes the tool if Arx installed it.",
|
| 99 |
+
"undo": true
|
| 100 |
}
|
| 101 |
},
|
| 102 |
"gdpr.guest_account": {
|
|
|
|
| 106 |
"changes": "Turns off guest login and guest access to shared folders.",
|
| 107 |
"note": "",
|
| 108 |
"undo": true
|
| 109 |
+
},
|
| 110 |
+
"linux": {
|
| 111 |
+
"kind": "auto",
|
| 112 |
+
"title": "Turn off guest login",
|
| 113 |
+
"changes": "Turns off the guest session on the login screen and the login shell of a 'guest' account.",
|
| 114 |
+
"note": "",
|
| 115 |
+
"undo": true
|
| 116 |
}
|
| 117 |
},
|
| 118 |
"gdpr.telemetry": {
|
|
|
|
| 122 |
"changes": "Turns off 'Share Mac Analytics' and 'Share with app developers'.",
|
| 123 |
"note": "",
|
| 124 |
"undo": true
|
| 125 |
+
},
|
| 126 |
+
"linux": {
|
| 127 |
+
"kind": "auto",
|
| 128 |
+
"title": "Stop usage reporting",
|
| 129 |
+
"changes": "Turns off popularity-contest and the whoopsie error reporter.",
|
| 130 |
+
"note": "",
|
| 131 |
+
"undo": true
|
| 132 |
}
|
| 133 |
},
|
| 134 |
"gdpr.time_sync": {
|
|
|
|
| 138 |
"changes": "Turns on 'Set time and date automatically' from Apple's time server, so logs have correct times.",
|
| 139 |
"note": "",
|
| 140 |
"undo": true
|
| 141 |
+
},
|
| 142 |
+
"linux": {
|
| 143 |
+
"kind": "auto",
|
| 144 |
+
"title": "Set the time automatically",
|
| 145 |
+
"changes": "Turns on network time (timedatectl set-ntp true), so logs have correct times.",
|
| 146 |
+
"note": "",
|
| 147 |
+
"undo": true
|
| 148 |
}
|
| 149 |
},
|
| 150 |
"screen_lock.enabled": {
|
|
|
|
| 304 |
"Set 'Automatically log in as' to Off (at the bottom of the page).",
|
| 305 |
"Give each person their own account; remove or rename shared accounts such as office or admin, keeping one named IT administrator."
|
| 306 |
]
|
| 307 |
+
},
|
| 308 |
+
"linux": {
|
| 309 |
+
"kind": "auto",
|
| 310 |
+
"title": "Turn off automatic login",
|
| 311 |
+
"changes": "Turns off automatic login (GDM, LightDM or SDDM), so everyone signs in with their own password.",
|
| 312 |
+
"note": "Shared accounts are not changed: a person decides which to remove.",
|
| 313 |
+
"undo": true
|
| 314 |
}
|
| 315 |
}
|
| 316 |
}
|