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
6.2 KiB
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:
- the known-good reference the HIDS baseline-diff compares the live docroot against;
- 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/:
-
YARA with the operator's ruleset. Expected: zero hits.
-
IOC paths: not one entry of
malware-iocs.pathsmay exist underout/. -
Disguise sweep: no
*.php.json,*.phar*,*.phtml*,_h3x_*; no.jsonfile containing a PHP open tag. -
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. -
Core version: assert the built
libraries/src/Version.phpis 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.hue-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:lineand 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.shruns unmodified and producesout/;- 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.