www_archline_hu/AGENTS.md
imre.agent e60b81f085 docs: document the repo as a multi-site baseline generator
The repo now carries more than one site: main holds www.archline.hu and
site/archlinexp.eu holds the eu site, both driven by the same
site-agnostic generator. README.md still described a single-site repo.

Rewrite README.md around the actual model: one branch per site, the
baseline's dual role (known-good reference for the HIDS diff AND the
deployment source of truth), the verify-before-you-trust rule, and why a
customized core file must never be taken from live (the updater skips
customized files, so live silently keeps a stale base -- the eu site ran
a 3.9.2 version string over pre-3.8.13 com_users code).

Add AGENTS.md for automated agents: the cardinal never-build-from-an-
infected-tree rule, the mandatory post-build checklist (YARA, IOC paths,
disguise sweep, the structural core-namespace rule that caught what YARA
missed, core version assertion), the repository boundary, commit rules
and layer semantics.

Assisted-by: claude-code@claude-opus-4-8
2026-07-17 12:52:40 +02:00

128 lines
6.2 KiB
Markdown

# AGENTS.md — machine-facing rules for this repo
**Read this before touching anything.** `README.md` explains the project to a human;
this file states the rules an automated agent must follow. Where the two seem to
disagree, this file wins for process questions.
---
## 1. What this repo is
It produces **clean, deployable code baselines** for the Cadline web sites hosted on
`tanis.cadline.hu`. A baseline serves two purposes at once:
1. the **known-good reference** the HIDS baseline-diff compares the live docroot against;
2. the **source of truth for deployment** — the tree that will be pushed to the server,
replacing direct editing on the live host.
The repo stores baseline **constituents plus a generator**, never the deployed tree.
`build-baseline.sh` assembles `out/` deterministically; `out/` is git-ignored.
## 2. One site per branch
There is **no per-site repo**. Sites and CMS versions are separated by **git branch**:
| Branch | Site | CMS core |
|---|---|---|
| `main` | www.archline.hu | Joomla 3.8.11 |
| `site/archlinexp.eu` | www.archlinexp.eu | Joomla 3.9.2 |
`build-baseline.sh` is **site-agnostic**: it walks `core/`, `packages/`, `deployed/`,
`cadline/`, `overrides/` and contains no site-specific logic. A new site means a new
branch with different *content*, **not** a modified script. If you find yourself editing
the generator to special-case a site, stop — that is the wrong branch of the design.
Site branches are **parallel and long-lived**. They do **not** merge into `main`.
Only repo-wide things (this file, `README.md`, the generator, `lib/`) belong on `main`.
## 3. The cardinal rule: never build a baseline from an infected tree
Every one of these sites is or was compromised. A baseline built by copying the live
docroot **would cement the malware into the reference and into every future deploy**.
Therefore:
- Take the CMS core from the **official upstream package**, never from live.
- Take the site's own code from the **developer source repo**
(`git.cadline.hu/cadline/websource`, per-site subdirectory), never from live.
- Only for extensions with **no obtainable upstream** may files come from live — and
then they are *vetted*, not trusted.
- **The build output must be double-checked before it is used.** This is not optional:
the archline baseline's first candidate contained 34 malware files, and only the
double-check caught them.
## 4. Mandatory verification (do not skip, do not weaken)
After every build, run all of these against `out/`:
1. **YARA** with the operator's ruleset. Expected: **zero** hits.
2. **IOC paths**: not one entry of `malware-iocs.paths` may exist under `out/`.
3. **Disguise sweep**: no `*.php.json`, `*.phar*`, `*.phtml*`, `_h3x_*`; no `.json` file
containing a PHP open tag.
4. **Structural check** — the rule that catches what content matching misses:
> An extra file on a **CMS core component path** that is **not** a template override
> (`templates/<tpl>/html/...`) is **not a module**. It is a leftover or a plant.
Diff `out/` against the vanilla package: for every path under a core component that
the vanilla does not have, decide explicitly. This rule found a file-write webshell
(`components/com_mailto/mail.php`) and an Akeeba Kickstart leftover carrying a
password — **both invisible to YARA**.
5. **Core version**: assert the built `libraries/src/Version.php` is the intended release.
A contaminated baseline must **never** be shipped. If verification fails, fix the
constituents and rebuild — do not filter the symptom out of `out/`.
## 5. Repository boundary (hard rule)
This repo lives on **`git.cadline.hu`** — the customer's Gitea, a different server from
the operator's. Therefore:
- **Never reference the operator's infrastructure here.** No operator issue URLs, no
operator hostnames, no `Addresses-Issue:` trailers pointing at operator trackers —
not in files, not in commit messages, not in branch names.
- Issue tracking for this work lives on the operator side and is **not** linked from
here. Describe *what* and *why* in the commit message itself instead.
- An `@illusion.hu` e-mail address in a commit trailer (authorship attribution) is the
one accepted exception.
## 6. Commits and branches
- Conventional Commits, **English**, subject ≤72 chars, imperative mood.
- Trailer: `Assisted-by: claude-code@<model>` on AI-assisted commits.
- **No** `Addresses-Issue:` / `Closes` (see §5).
- Never force-push a branch someone else may be building on; on your own fresh branch it
is fine (use `--force-with-lease`).
- **Never push to `main`.** Open a pull request; a human merges.
- **Never commit a secret.** These trees legitimately contain third-party code with
embedded credentials — if you must refer to one, cite `file:line` and the *type*, never
the value. Redaction applies to commit messages, docs and any output you produce.
## 7. Layer semantics (what goes where)
| Directory | Holds | Source of truth |
|---|---|---|
| `core/` | CMS official package, deployed form, installer removed | upstream vendor |
| `packages/<slug>/` | extensions with obtainable upstream; modified ones authored as two commits (pristine → delta) so `git diff` documents our change | upstream + our patch |
| `deployed/<slug>/` | extensions with **no** obtainable upstream: vetted live files | live (vetted) |
| `cadline/<slug>/` | the site's own code | developer source repo |
| `overrides/` | the site's modifications to core files, **on the current core's base** | developer source |
| `malware-iocs.paths` | paths excluded from the build (droppers, leftovers) | this repo |
`overrides/` rsyncs **last**, so it wins over `core/`.
**Do not take a customized core file from live.** The updater skips customized files, so
the live copy silently stays on an old base (the eu site ran a 3.9.2 version string over
pre-3.8.13 `com_users` code). Take vanilla for the version you target, then apply only
the genuine delta.
## 8. Definition of done
A baseline branch is done when:
- `build-baseline.sh` runs unmodified and produces `out/`;
- all five checks in §4 pass, and the numbers are recorded in the commit message;
- every layer's provenance is traceable (which file came from upstream, from the
developer source, or vetted from live);
- no secret value appears anywhere in the diff or the message.