Download library/build_knowledge.py from Devseis/Arx: direct link, hf CLI and curl.
- Browser
- Download file 12.4 kB
-
https://huggingface.co/spaces/Devseis/Arx/resolve/main/library/build_knowledge.py
- Command line
-
hf download hf://spaces/Devseis/Arx/library/build_knowledge.py
-
curl -L -o build_knowledge.py https://huggingface.co/spaces/Devseis/Arx/resolve/main/library/build_knowledge.py
12.4 kB
| #!/usr/bin/env python3 | |
| """Build library/knowledge.json: the passages the chat assistant answers from (and cites). | |
| Generated from the catalog and library so the chat never drifts from the audit: | |
| fix how to fix / why it matters, per check and OS (check_guidance.json + catalog rule + references) | |
| control ISO 27001 / GDPR / EU AI Act control summaries (controls.json) | |
| tool AI tool profiles (ai_tools.json) and EU AI Act risk categories | |
| phone where a setting is on iPhone / Android (catalog/mobile_questions.json) | |
| status what each audit status means | |
| faq the auditor itself: what it checks, privacy, install, phone check | |
| Usage: python3 library/build_knowledge.py | |
| """ | |
| import json | |
| import os | |
| import re | |
| import sys | |
| ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| OS_LABEL = {"windows": "Windows", "linux": "Linux", "macos": "macOS", "ios": "iPhone", "android": "Android"} | |
| def load(*parts): | |
| with open(os.path.join(ROOT, *parts), encoding="utf-8") as handle: | |
| return json.load(handle) | |
| def read_conf(path): | |
| conf = {} | |
| for line in open(os.path.join(ROOT, path), encoding="utf-8"): | |
| if "=" in line and not line.lstrip().startswith("#"): | |
| key, value = line.split("=", 1) | |
| conf[key.strip()] = value.strip().strip('"') | |
| return conf | |
| def fill(text, baseline): | |
| """Same placeholder rules as the report builder (training/build_dataset.py), including derived values.""" | |
| sys.path.insert(0, os.path.join(ROOT, "training")) | |
| from build_dataset import fill as report_fill | |
| return report_fill(text, baseline) | |
| FAQ = [ | |
| ("What the Devseis Endpoint Auditor is", | |
| "The Devseis Endpoint Auditor is a free, open-source, read-only check of a computer or phone against ISO 27001, GDPR " | |
| "technical controls and the EU AI Act. It reads security settings, compares them with a baseline, matches installed " | |
| "software against known vulnerabilities, and an AI auditor that runs on the device writes a report with findings, " | |
| "risk levels and fix steps. It is not a certification and not legal advice."), | |
| ("Does the audit change anything?", | |
| "No. The audit only reads settings such as password rules, firewall, disk encryption and installed software. It never " | |
| "changes them. Administrator rights are requested because some settings can only be read by an administrator."), | |
| ("Privacy: where do audit results go?", | |
| "Nowhere. Evidence and reports stay on the device. The app downloads only two things: the AI auditor model and a signed " | |
| "list of known vulnerabilities. Nothing about the device is uploaded. Chat history is stored only in this browser."), | |
| ("How to run an audit of a computer", | |
| "A web page cannot read a computer's settings, so computers are audited with the free Devseis desktop app for Mac, " | |
| "Windows (beta) and Linux. Download it from the Download page, install it, click Run audit and enter the computer " | |
| "password once. The first run downloads the AI auditor; the audit then takes about 5 to 15 minutes and ends with a PDF report."), | |
| ("How to install on a Mac", | |
| "Open the downloaded .dmg file and drag Devseis Endpoint Auditor to Applications. The first time, macOS says it could not " | |
| "verify the app because it is not yet notarised: click Done, then open System Settings, Privacy & Security, and click " | |
| "Open Anyway. After that it opens normally. It needs macOS 13 or newer."), | |
| ("How to install on Windows", | |
| "Run the downloaded setup .exe. Windows SmartScreen shows 'Windows protected your PC' because the app is not yet " | |
| "code-signed: click More info, then Run anyway. Click Next, Install and Finish. When the audit starts, click Yes in the " | |
| "User Account Control prompt. The Windows version is a beta."), | |
| ("How to install on Linux", | |
| "On Ubuntu, Debian, Linux Mint or Pop!_OS, double-click the .deb file and click Install. On other distributions, make " | |
| "the AppImage executable (Properties, Permissions, Allow executing file as program) and double-click it. The audit asks " | |
| "for the password with the normal desktop prompt."), | |
| ("How the phone check works", | |
| "Phones are checked in the browser: open the Devseis page on the iPhone or Android phone and start the phone check. The " | |
| "browser detects what it can (system version, whether a screen lock is set up) and asks the rest with help on where to " | |
| "look in Settings. Every result is labelled detected or self-reported. The report can be saved or shared as a PDF."), | |
| ("What self-reported and detected mean", | |
| "Detected means the browser or the app read the value itself. Self-reported means the user answered a question; the " | |
| "value was not verified on the device. Phone checks are mostly self-reported because browsers cannot read phone settings."), | |
| ("What is in the audit report", | |
| "The report has a compliance score, the most important gaps, and for each problem: what was found, why it matters, the " | |
| "risk level, step-by-step fixes for that operating system, how to verify the fix, and the related ISO 27001, GDPR and EU " | |
| "AI Act references. It also lists AI tools found and their EU AI Act risk class."), | |
| ("Does the auditor need internet?", | |
| "Only the first time, to download the AI auditor and the vulnerability list. After that audits and chat work offline. " | |
| "Pending security updates can only be checked online because they ask the vendor."), | |
| ("Settings for organisations", | |
| "Before running audits, an organisation can edit baseline.conf (approved administrator accounts, approved AI tools, " | |
| "limits such as screen lock within 5 minutes) and organisational.conf (answers to 8 organisational questions such as " | |
| "record of processing, DPIA and breach procedure). The desktop app keeps them in its settings folder."), | |
| ("The Devseis AI auditor model", | |
| "The assistant and the report writer are a small language model fine-tuned by Devseis from Qwen2.5 and run on the " | |
| "device with WebLLM. Audit verdicts are decided by rules in code, not by the model; the model explains, prioritises and " | |
| "writes, and every report answer is checked against the evidence before it is used."), | |
| ("Who is Arx?", | |
| "Arx is the name of the Devseis security assistant and endpoint auditor (from the Latin for fortress). Arx answers " | |
| "questions about securing computers and phones, ISO 27001, GDPR and the EU AI Act, and audits devices: computers with the " | |
| "Devseis Endpoint Auditor desktop app, phones with the phone check in the browser. Arx runs on the device; nothing is uploaded."), | |
| ] | |
| def main(): | |
| catalog = load("catalog", "checks.json") | |
| guidance = load("library", "check_guidance.json") | |
| controls = load("library", "controls.json") | |
| tools = load("library", "ai_tools.json") | |
| phone = load("catalog", "mobile_questions.json") | |
| baseline = read_conf("baseline.conf") | |
| refs = catalog["references"] | |
| ctl = {(c["framework"], c["id"]): c for c in controls["controls"]} | |
| chunks = [] | |
| def add(cid, kind, title, text, os_family=None, check=None): | |
| chunks.append({"id": cid, "kind": kind, "title": title, "text": re.sub(r"\s+", " ", text).strip(), | |
| **({"os": os_family} if os_family else {}), **({"check": check} if check else {})}) | |
| def ref_text(check): | |
| out = [] | |
| for key, label in (("iso27001", "ISO 27001:2022"), ("gdpr", "GDPR"), ("ai_act", "EU AI Act")): | |
| for r in check["controls"][key]: | |
| title = (ctl.get((label, r)) or {}).get("title") or refs[key].get(r, "") | |
| out.append(f"{label} {r}" + (f" ({title})" if title else "")) | |
| return "; ".join(out) | |
| for check in catalog["checks"]: | |
| if check["section"] == "org": | |
| g = guidance["checks"].get(guidance["aliases"].get(check["id"], check["id"])) | |
| add(f"fix:{check['id']}", "fix", check["name"], | |
| f"{check['name']} is an organisational control, answered once by the organisation, not read from a device. " | |
| f"{g['why'] if g else ''} Rule: {check['rule']} Related: {ref_text(check)}.", check=check["id"]) | |
| continue | |
| g = guidance["checks"].get(guidance["aliases"].get(check["id"], check["id"])) | |
| if not g: | |
| continue | |
| for os_family in check["os"]: | |
| name = (check.get("names") or {}).get(os_family, check["name"]) | |
| steps = g["fix"].get("all", []) + g["fix"].get(os_family, []) | |
| text = (f"{name} on {OS_LABEL[os_family]}. Why it matters: {g['why']} Risk if missing: {g['risk']['level']} - " | |
| f"{g['risk']['reason']} Requirement: {fill(check['rule'], baseline)} How to fix on {OS_LABEL[os_family]}: " | |
| + " ".join(f"({i}) {fill(s, baseline)}" for i, s in enumerate(steps, 1)) | |
| + f" How to verify: {fill(g['verify'], baseline)} Related controls: {ref_text(check)}.") | |
| add(f"fix:{check['id']}:{os_family}", "fix", f"{name} on {OS_LABEL[os_family]}", text, os_family, check["id"]) | |
| for c in controls["controls"]: | |
| add(f"control:{c['framework']}:{c['id']}", "control", f"{c['framework']} {c['id']} {c['title']}", | |
| f"{c['framework']} {c['id']} ({c['title']}): {c['summary']} An auditor looks for: {'; '.join(c.get('auditor_looks_for', []))}. " | |
| f"On endpoints: {c.get('endpoint_relevance', '')}") | |
| for name, t in tools["tools"].items(): | |
| add(f"tool:{name}", "tool", f"{name} (AI tool)", | |
| f"{name}: {t['kind']}, provider {t['provider']}, data location: {t['data_location']}. Default EU AI Act risk class " | |
| f"for general office use: {t['default_risk']}. GDPR: {t['gdpr_notes']} Whether it is allowed at work depends on the " | |
| f"organisation's approved AI tools list and on the purpose: some purposes make any AI tool high-risk or prohibited.") | |
| rules = tools["purpose_rules"] | |
| for key, title in (("prohibited", "EU AI Act prohibited AI practices"), ("high_risk_examples", "EU AI Act high-risk AI uses"), | |
| ("limited_risk", "EU AI Act limited-risk AI (transparency duties)"), ("minimal_risk", "EU AI Act minimal-risk AI")): | |
| items = rules[key] if isinstance(rules[key], list) else [rules[key]] | |
| add(f"aiact:{key}", "control", title, f"{title}: " + "; ".join(items) + ".") | |
| for check_id, per_os in phone["checks"].items(): | |
| for os_family, spec in per_os.items(): | |
| name = next(((c.get("names") or {}).get(os_family, c["name"]) for c in catalog["checks"] if c["id"] == check_id), check_id) | |
| add(f"phone:{check_id}:{os_family}", "phone", f"Where to find {name} on {OS_LABEL[os_family]}", | |
| f"{name} on {OS_LABEL[os_family]}: {spec['question']} Where to look: {spec['where']}", os_family, check_id) | |
| notes = guidance["status_notes"] | |
| add("status:all", "status", "What the audit statuses mean", | |
| "Compliant: the setting meets the baseline. Non-Compliant: it does not meet the baseline. " | |
| f"Pending: {notes['Pending']} NotApplicable: {notes['NotApplicable']} Error: {notes['Error']}") | |
| counts = {o: sum(o in c["os"] for c in catalog["checks"]) for o in OS_LABEL} | |
| add("faq:checks", "faq", "How many checks the audit runs", | |
| "The audit catalog has " + str(len(catalog["checks"])) + " checks: " + ", ".join(f"{OS_LABEL[o]} {n}" for o, n in counts.items()) | |
| + ". They cover passwords and lockout, malware protection, firewall, screen lock, USB storage, administrator accounts, " | |
| "disk encryption, updates and known vulnerabilities, GDPR technical controls (backups, logging, remote access, " | |
| "telemetry and more), EU AI Act controls (AI tools found, shadow AI, risk classification) and 8 organisational controls.") | |
| for i, (title, text) in enumerate(FAQ): | |
| add(f"faq:{i}", "faq", title, text) | |
| out = {"version": catalog["catalog_version"], "chunks": chunks} | |
| with open(os.path.join(ROOT, "library", "knowledge.json"), "w", encoding="utf-8") as handle: | |
| json.dump(out, handle, ensure_ascii=False, indent=0) | |
| kinds = {} | |
| for c in chunks: | |
| kinds[c["kind"]] = kinds.get(c["kind"], 0) + 1 | |
| print(f"knowledge.json: {len(chunks)} passages {kinds}") | |
| if __name__ == "__main__": | |
| main() | |