{"slug": "my-lunch-break-response-to-artex-a-host-intrusion-sentinel", "title": "My lunch-break response to ARTEX: a host intrusion sentinel", "summary": "A draft, unreleased host intrusion sentinel called ARTEX pairs a per-machine Python agent that scores running programs and their network connections in code with a Cloudflare Worker service that re-scores signals, stores cases encrypted and returns operator decisions and learned weights. The tool was built in response to FBI FLASH-20261113-001 on agentic intrusion frameworks and targets web shells, loaders, remote-access implants, Windows-to-Windows movement, Active Directory attacks and automated-operator pacing, with four response modes (kill, decide, pause, report) and a default of decide when a model is configured or kill when it is not. The author states the project is a draft that has not been released and directs readers to a 'What has and has not been tested' section before relying on it.", "body_md": "A host intrusion sentinel you run yourself. A small agent on each machine watches running programs and their network connections, scores what it sees in code, and reports the cases that look like an attack to a service you deploy in your own Cloudflare account. By default it also ends a hostile program, and everything that program started, on the spot.\n\nIt was built in response to FBI FLASH-20261113-001 on agentic intrusion frameworks, and it is aimed at the things those tools do on a machine: web shells, loaders, remote-access implants, movement between Windows machines, Active Directory attacks, and the pace and repetition of an automated operator.\n\n**Status: draft, not yet released.** Read \"What has and has not been tested\" before relying on it.\n\n```\n each machine                                   your Cloudflare account\n┌──────────────────────────────┐   HTTPS out   ┌─────────────────────────────────┐\n│ agent/sentinel_agent.py      │ ────────────► │ server/src/worker.mjs  (Worker) │\n│  sensors  → scoring → cases  │ ◄──────────── │  cases, decisions, learning     │\n│  pause / stop (if approved)  │  decisions,   │  D1 database, optional AI       │\n└──────────────────────────────┘  weights      └─────────────────────────────────┘\n```\n\n- **The agent** reads the operating system's own process and connection tables. It captures no packets and\nreads no file contents. It opens no port. Scoring happens on the machine.\n- **Only cases leave the machine** : a program that reached the SUSPECT or HOSTILE level, with the signals\nthat fired, the programs that started it, and the addresses it talked to. A command line never leaves the\nmachine; the agent reports which pattern matched, not the text.\n- **The service** checks every field, scores the signals again (the level is never taken from the device),\nstores the case encrypted, raises an alert, and hands back two things: the operator's decision on a program\nthat agent paused, and the learned signal weights.\n- **It acts, within limits.** It never touches operating-system processes, security software, container\nruntimes, remote access (`sshd` ), the web server itself, or the browser, mail or document program a person\nis working in: it ends the harmful thing those programs started, not the programs. It never signals a\nprocess that has already exited.\n\n| Mode | What the agent does about a hostile program | Choose it when | \n|---|---|---|\n| `kill` | ends it and everything it started, at once, then reports what it did | an intruder's minutes matter more than a wrongly stopped job | \n| `decide` | freezes it at once, then the service's decision model says stop, release or hold | you have a model and want a false alarm to cost seconds, not a program | \n| `pause` | freezes it (reversible) and waits for an operator to stop or release it | a wrong stop would be an outage | \n| `report` | nothing; it only reports | you are evaluating, or another tool responds | \n\nSet the ceiling for the whole deployment with the service variable `RESPONSE_MODE`, and a machine's own mode\nwith `--mode` at enrolment. A machine gets the lower of the two.\n\n**The default.** With `RESPONSE_MODE` left empty, a deployment with a model configured runs `decide`, and one\nwithout runs `kill`. Agents ask for `kill` unless told otherwise.\n\n**How `decide` works.** The agent freezes the program the moment it is judged hostile, so the attack is already\nstopped while the decision is made. The service asks the model once, with an eight-second limit, and answers in\nthe same exchange:\n\n- **stop** when the model recommends it with high confidence: the agent ends the program.\n- **release** only if you set`DECISION_RELEASE` to`high-confidence` , the model is highly confident, and no\nlocked signal is involved (a published indicator, credential theft, a directory attack). The agent lets it\nrun and leaves it alone afterwards. A model's release does not mark the program as yours and teaches nothing.\n- **hold** in every other case: it stays frozen until an operator decides.\n\nIf the model fails or times out, `DECISION_FALLBACK` decides: `kill` (the default) or `hold`. An agent that\nhears nothing from the service at all for twenty seconds applies the same fallback by itself.\n\n**Kill is not reversible.** In `kill` mode a false alarm ends a legitimate program; `decide` exists to avoid\nexactly that. The agent acts only on\nspecific launch chains with almost no honest use, or on cases whose evidence spans three categories, and a\nprogram an operator marks \"ours\" is never touched again. Even so: run `pause` or `report` first on machines\nwhere a wrong stop is costly, and watch what it would have done.\n\n**If you run `report` or `pause`, run the AI analyst as well** (see \"The AI part\"). In those modes nothing ends\nan attack until a person reads the case, and an automated intruder works in minutes. A second, independent\nmodel reading every case and recommending stop or release shortens that wait; keep it separate from whatever\nAI your organisation already uses, so one compromised or confused system is not judging its own work.\n\n| Area | Signals | \n|---|---|\n| Email and lures | scripts started by mail, document and browser programs; programs run out of attachments and archives; shortcut and paste-this-command launches | \n| Web applications | a shell or interpreter started by a web server; download-and-run and reverse-shell commands; who-am-I commands | \n| Loaders and implants | system tools used as loaders (MSBuild, cmstp, regsvr32, rundll32, mshta); programs in temporary folders talking to the internet; regular call-home timing; fan-out scanning | \n| Windows domains | credential dumping and Kerberoasting commands; domain enumeration; shells inside WinRM, WMI and PsExec sessions; and, when the agent runs with administrator rights, Security-log patterns for Kerberoasting, DCSync, failed sign-in bursts, cleared logs and privileged group changes | \n| Automated operators | a stream of different commands at machine pace under one foothold; near-copy retries; the same command started twice at once | \n| Web requests | read from the web server's own access log (nginx, Apache, IIS): directory fuzzing, injection-shaped requests and whether the application answered them, walking through record numbers, sign-in guessing, calls to a server-side script with a command parameter, and bursts of application failures on requests that carry a body | \n| Web folders | a server-side script that appears in a web folder after the agent started, with no release around it | \n| Tunnels | Chisel by name; SoftEther by its program names with its install folder or its ports; Tor by its client, by any program sending traffic through the local Tor proxy, and by inbound connections from the Tor Project's published exit nodes | \n| Attempts from outside | bursts on SSH, Remote Desktop, WinRM, SMB, VNC and Telnet | \n| Published indicators | the addresses and domain listed in FBI FLASH-20261113-001, as a scored signal with the FBI's caveat that addresses get reassigned; never an automatic block | \n| Antivirus | new Windows Defender detections become cases | \n| Cloud credentials | a web server process reaching for the cloud metadata service, where instance credentials are handed out | \n| Cloud control planes | from audit records you forward: credentials added to applications and service principals, ownership, role and consent grants, weakened Conditional Access and federation settings (Microsoft Entra); permission mapping by dry-run, key pairs, public addresses, large volumes, role-bearing instances, access keys made for other users, administrator policies (AWS CloudTrail); validation dry-runs, publishing credentials and storage keys (Azure activity log); and any of it from an address in the FLASH | \n\nSignals are weighted and grouped into categories. A case is called HOSTILE only when the evidence spans at least three categories, so no single rule decides it.\n\nNeeds Node.js 22.5 or newer and Python 3.9 or newer.\n\n``` python\ncd server\nexport SENTINEL_ENROLL_KEY=$(python3 -c \"import secrets;print(secrets.token_hex(24))\")\nexport SENTINEL_ADMIN_TOKEN=$(python3 -c \"import secrets;print(secrets.token_hex(24))\")\nnode local.mjs            # in-memory database, for evaluation only\n```\n\nIn another terminal, with the same two variables set:\n\n```\npython3 agent/sentinel_agent.py --state-dir ./state enroll --server http://127.0.0.1:8787\npython3 agent/sentinel_agent.py --state-dir ./state run\ncurl -H \"Authorization: Bearer $SENTINEL_ADMIN_TOKEN\" http://127.0.0.1:8787/v1/devices\ncurl -H \"Authorization: Bearer $SENTINEL_ADMIN_TOKEN\" \"http://127.0.0.1:8787/v1/cases?state=open\"\n```\n\nCopy `server/wrangler.example.jsonc` to `server/wrangler.jsonc`, then follow the commands at the top of that\nfile: create the D1 database, load `schema.sql`, set the three secrets, deploy. Three secrets are required and\neach must be at least 32 characters:\n\n| Secret | What it is for | \n|---|---|\n| `DATA_KEY` | encrypts case evidence at rest (AES-256-GCM) | \n| `ENROLL_KEY` | lets a machine enrol; share it only with the people who install agents | \n| `ADMIN_TOKEN` | lets an operator read cases and make decisions | \n\nOptional: `ALERT_WEBHOOK_URL` receives a JSON alert for each new case. The alert text is written by the\nservice from the signal ids; text from a device is never placed in an alert.\n\n| Where | How | \n|---|---|\n| Linux (systemd) | `sudo SENTINEL_ENROLL_KEY=<key> deploy/install-linux.sh https://your-service [kill\\|pause\\|report]` | \n| Windows (as SYSTEM) | elevated PowerShell: `deploy\\install-windows.ps1 -Server https://your-service -EnrollKey <key> [-Mode kill\\|pause\\|report]` | \n| Kubernetes | build `deploy/Dockerfile` , fill in the Secret in`deploy/kubernetes-daemonset.yaml` , apply it; every node enrols itself | \n| Anything else | `python3 agent/sentinel_agent.py enroll --server ... --key ...` then`... run` under your own supervisor | \n\nRun the agent with administrator or root rights where you can. Without them it sees only the current user's programs, and on Windows it cannot read the Security log.\n\nEvery installer enrols in `kill` mode unless you name another.\n\nWhat an intruder does in Microsoft Entra, Azure or AWS leaves nothing on a machine; it is all in the audit logs. Forward those records to the service and it applies the cloud rules:\n\n```\ncurl -X POST https://your-service/v1/cloud-events \\\n  -H \"Authorization: Bearer $INGEST_TOKEN\" -H \"Content-Type: application/json\" \\\n  -d '{\"source\": \"aws-cloudtrail\", \"records\": [ ...CloudTrail records... ]}'\n```\n\n`source` is `entra-audit`, `entra-signin`, `aws-cloudtrail` or `azure-activity`; send up to 1000 records at a\ntime, exactly as the cloud wrote them. Use whatever already moves your logs (an Event Hub or EventBridge\ntarget, a scheduled export, your SIEM's forwarder). The service never calls a cloud API and holds no cloud\ncredential. Each identity whose records add up to a case gets one, under a stand-in device named after the\nsource; an operator can mark an identity as theirs so it is not raised again.\n\nEach batch is judged on its own, so forward records in batches that cover a few minutes. The rules are written from the vendors' published log references and tested with records in those shapes; they have not been run against a live tenant.\n\nDetection, scoring, stopping, pausing and learning are all plain code. They need no model and work with\n`AI_PROVIDER` set to `none`, which is the default.\n\nOne optional model does two jobs, both in `server/src/analyst.mjs`:\n\n- **Analyst.** It adds a second opinion to every case for the operator: a two-sentence summary, the likely\ntechnique, and a recommendation.\n- **Decision model.** In`decide` mode the same answer settles what happens to a frozen program, within the\nlimits under \"How`decide` works\".\n\nThe model never changes a score and never teaches the policy. It sees signal ids with the service's own sentences, program names and counts; it is never sent a command line, and it is told that everything in the case is data. Choose it with one variable:\n\n| `AI_PROVIDER` | What it uses | Settings | \n|---|---|---|\n| `none` | no model |  | \n| `workers-ai` | Cloudflare Workers AI through the `AI` binding | `AI_MODEL` | \n| `openai` | any OpenAI-compatible chat-completions endpoint, hosted or your own | `AI_BASE_URL` ,`AI_MODEL` , secret`AI_API_KEY` | \n| `custom` | your own analyst: a service binding named `ANALYST` , or a URL in`AI_BASE_URL` | receives `{instructions, case}` as JSON, answers`{summary, technique, recommendation, confidence}` | \n\nTo plug in a different model, add a branch in that file.\n\n**What one decision model covers.** Every case the agent can freeze passes through the same model: email and\ndocument launches, web shells, loaders, implants, credential theft and movement between Windows machines. It\ndoes not decide cases where there is nothing on the machine to stop: web-request attacks, connection bursts,\ndirectory-log patterns and antivirus detections are analysed for the operator, not acted on. And a model adds\njudgement, not sight: it covers only what the sensors report and what you forward.\n\nThe service learns from your operators' own verdicts and nothing else:\n\n- Each case an operator marks \"ours\" or \"an attack\" is remembered as its shape: the signal ids, the platform and the verdict. No program name, path, address or command line is kept in that memory.\n- A signal your operators keep dismissing becomes worth less on your machines; one they keep confirming becomes worth more. A weight moves one point at a time and at most two points in total.\n- Published indicators, antivirus verdicts, attacks on a service and directory attacks never move.\n- A change is adopted only if every attack your operators confirmed before, and every reference attack in\n`server/src/signals.mjs` , would still be reported under it. Otherwise it is rejected.\n- `POST /v1/learning/reset` forgets everything learned.\n\nNo model takes part in this. It is bounded tuning, not a system that rewrites itself.\n\n**Plugging in a memory engine.** Recall of similar past cases works out of the box by comparing shapes. If\nyou have a semantic memory store, bind an object as `SENTINEL_MEMORY` with `remember(text)` and\n`recall(query, k)`; the service will write each verdict to it and show what it recalls beside a new case.\n\nAll operator calls need `Authorization: Bearer <ADMIN_TOKEN>`.\n\n| Call | Purpose | \n|---|---|\n| `GET /v1/devices` ,`POST /v1/devices/:id/revoke` | list machines, remove one | \n| `GET /v1/cases?state=open` ,`GET /v1/cases/:id` | list cases, read one with its evidence, chain and similar past verdicts | \n| `POST /v1/cases/:id/action` | `{\"action\": \"trust\" \\| \"confirm\" \\| \"resolve\" \\| \"reopen\" \\| \"stop\" \\| \"release\"}` | \n| `POST /v1/cases/:id/analyze` | ask the AI analyst again | \n| `GET /v1/learning` ,`POST /v1/learning/reset` | see or forget what was learned | \n| `POST /v1/cloud-events` | forward cloud audit records (accepts the separate `INGEST_TOKEN` as well) | \n\nThere is no web dashboard in this repository yet; the API is the interface.\n\nMeasured on the development machines; your numbers will differ.\n\n| What | Measured | \n|---|---|\n| Time from a staged document-launched script starting to it being ended (Windows, `kill` mode) | within 1.4 seconds | \n| The same in `decide` mode with a stand-in model | frozen after 0.1 seconds, ended on the model's \"stop\" within 1 second | \n| A stand-in false alarm in `decide` mode (model says release, release allowed) | frozen briefly, then left running | \n| Time to pause a staged web shell (Linux) | within one 5-second sample; later runs judge new programs as they start | \n| Cost of one full sample | about 6 ms on Windows, about 15 ms on Linux | \n| Cost of one look for new programs (five a second) | about 3 ms on Windows | \n| Traffic when quiet | one small report per machine every five minutes | \n| Model calls | none, unless the AI analyst is on: then one per new case | \n\n```\ncd agent  && python3 -m unittest discover -s tests -p \"test_*.py\"   # engine, pause/stop, 100 scripted events\ncd agent  && python3 tests/test_cases.py                            # the per-event table\ncd server && npm test                                               # the service\n```\n\nMIT. See [LICENSE](https://github.com/gary23w/agent-garrett-sentinel-service/blob/main/LICENSE).", "url": "https://wpnews.pro/news/my-lunch-break-response-to-artex-a-host-intrusion-sentinel", "canonical_source": "https://github.com/gary23w/agent-garrett-sentinel-service", "published_at": "2026-11-14 21:30:57+00:00", "updated_at": "2026-11-14 21:53:35.654150+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "artificial-intelligence"], "entities": ["ARTEX", "FBI", "Cloudflare", "Cloudflare Workers", "D1", "FBI FLASH-20261113-001"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/my-lunch-break-response-to-artex-a-host-intrusion-sentinel", "markdown": "https://wpnews.pro/news/my-lunch-break-response-to-artex-a-host-intrusion-sentinel.md", "text": "https://wpnews.pro/news/my-lunch-break-response-to-artex-a-host-intrusion-sentinel.txt", "jsonld": "https://wpnews.pro/news/my-lunch-break-response-to-artex-a-host-intrusion-sentinel.jsonld"}}