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 |
||
|---|---|---|
| cadline | ||
| core | ||
| deployed | ||
| lib | ||
| overrides | ||
| packages | ||
| tests | ||
| .gitignore | ||
| .gitmodules | ||
| AGENTS.md | ||
| build-baseline.sh | ||
| malware-iocs.md | ||
| malware-iocs.paths | ||
| PROVENANCE.md | ||
| README.md | ||
| RUNBOOK.md | ||
Cadline web baselines — constituents + generator
This repo produces the clean code baselines of the Cadline web sites hosted on
tanis.cadline.hu — malware-free, faithful reproductions of what each site's source
should be, assembled from clean sources rather than copied from the live server.
A baseline has two jobs:
- Known-good reference. The HIDS baseline-diff compares the live docroot against it
and classifies every file (
baseline-match / data / malware / residual). - Deployment source of truth. The baseline is what gets deployed to the server, so that code reaches production through git instead of being edited directly on the live host. Because the live tree is regenerated from the baseline, cleanup becomes a by-product of deployment: anything not in the source disappears.
The repo stores the baseline constituents plus a generator — never the deployed tree.
Sites and branches
There is no separate repo per site. Sites (and CMS versions) live on branches:
| Branch | Site | CMS core | Own code source |
|---|---|---|---|
main |
www.archline.hu | Joomla 3.8.11 | reconstructed here (no usable developer source) |
site/archlinexp.eu |
www.archlinexp.eu | Joomla 3.9.2 | cadline/websource → www.archlinexp.eu/ |
build-baseline.sh is site-agnostic — it walks the constituent directories and has
no site-specific logic. A new site is a new branch with different content, not a new
script and not a new repo. Site branches are parallel and long-lived; they do not merge
into main. Only repo-wide files (this README, AGENTS.md, the generator, lib/) live
on main for every branch to inherit.
Automated agents: read AGENTS.md first — it carries the rules
(verification, repo boundary, layer semantics) in machine-facing form.
Building a baseline (read this first)
# 1. Clone, and check out the branch of the site you want:
git clone --recurse-submodules ssh://git@git.cadline.hu:22222/cadline_web/www_archline_hu.git
cd www_archline_hu
git checkout site/archlinexp.eu # or stay on main for www.archline.hu
# (if you cloned without --recurse-submodules and the branch uses submodules:)
git submodule update --init
# 2. Generate:
./build-baseline.sh # → ./out/
# 3. Use ./out/ as that site's known-good reference / deploy source.
out/ is the deployed code docroot. It is git-ignored and fully regenerated on every
run (the script rm -rf out/ first, so it is idempotent).
Prerequisites: bash, rsync, git, find, sed. No PHP, no build toolchain, no
network at build time. lib/jmap.py (python3) is an authoring aid used when preparing a
package's pristine commit — build-baseline.sh never invokes it.
What out/ contains / excludes:
- Contains: the CMS core + every installed extension + the Cadline-own code, as deployed files.
- Excludes: malware and known planted paths (
malware-iocs.paths), and runtime data (cache/,tmp/,logs/,images/,public/, media). The result is code only — which is also why a deploy must never wipe the data directories.
Verify before you trust it
The build output is not trusted until checked. This is not ceremony: the first
archline baseline candidate contained 34 malware files, and only the check caught them;
on the eu site the check caught a file-write webshell and a leftover Akeeba restoration
file that carried a password — neither of which YARA flagged. See AGENTS.md §4 for
the full checklist (YARA, IOC paths, disguise sweep, the structural core-namespace rule,
core version).
Repo layout (the constituents)
| Path | What | How build-baseline.sh uses it |
|---|---|---|
core/ |
CMS official package (deployed form, installer removed) | rsync → out/, minus demo assets + data dirs |
packages/<slug>/ |
extensions with an obtainable upstream, deployed-layout. Modified ones are authored as two commits (pristine upstream → our delta) so git log/git diff shows exactly how we differ. |
rsync → out/ |
deployed/<slug>/ |
extensions with no obtainable upstream: vetted live files, deployed layout. | rsync → out/ |
cadline/<slug>/ |
Cadline own-code. On main this includes git submodules of the developer repos (cadcommonlib / maintenance). |
rsync → out/ (.git excluded) |
overrides/ |
the site's modifications to core files, on the current core's base. Rsynced last, so it wins. | rsync → out/ |
malware-iocs.paths |
machine-readable exclusion list (droppers, leftovers) | build step 6 |
lib/jmap.py |
Joomla manifest→deployed-path mapper (authoring aid only) | not used at build time |
Details: RUNBOOK.md (recipe + module inventory),
PROVENANCE.md (per-extension origins),
malware-iocs.md (the excluded IOC set, main).
Why customized core files never come from live
A CMS updater skips files the site has customized. The version string moves forward; the
customized files silently stay on the old base. The eu site demonstrated this: it
reported Joomla 3.9.2 while its com_users was still pre-3.8.13 code — 31 stale files.
So: take vanilla for the version you target, then apply only the genuine delta into
overrides/. Never lift the customized file from live — that would preserve the stale
(and possibly vulnerable) base forever.
This branch: www.archline.hu (main)
Joomla 3.8.11, host tanis.cadline.hu. out/ ≈ 23.5k files. It byte-matches the live
docroot for all legit code and is intentionally clean on the 4 files the attacker
infected (3 trojanized core files + shaper_helix3/index.php), so the verifier flags
those on the live server. Verified: non-malware code residual = 0.
The own-code here is reconstructed in this repo, because the developer source
(cadline/websource) does not contain this site — its sub.archline.hu directory is an
older snapshot missing ~630 files and the third-party module layer entirely.
Updating
- Developer-controlled code (
common_cadline_libraries,maintenance): the developer pushes to the cadcommonlib / maintenance repos, then:git submodule update --remote # pull the developer's latest ./build-baseline.sh # regenerate out/ git add -A && git commit # record the new submodule pointers - Everything else (EOL core, 3rd-party extensions): effectively frozen. If a legit
change lands, re-sync the affected
deployed/<slug>/oroverrides/and commit.
Known divergence (heads-up for the verifier): common_cadline_libraries and
maintenance take the authoritative submodule HEAD content, which differs from the
current live docroot (mixed CRLF/LF + a content drift on crm_hardlock.class.php /
POfile.php) because live was not last deployed from those HEADs. The verifier flags
this until live is re-deployed or the drift is reconciled.
This branch: www.archlinexp.eu (site/archlinexp.eu)
Joomla 3.9.2. out/ ≈ 12.3k files. Built from clean sources end to end: vanilla 3.9.2
core + the developer source's own code + a vetted module layer, with only 5 genuine core
modifications in overrides/. The developer source for this site is complete and
clean (its com_users is already on the 3.9.2 base), which is why this site — not
archline.hu — was the first to get a fully source-built baseline.
Authority
- The upstream vendor package is authoritative for the CMS core.
- The developer source repo is authoritative for the code it owns.
- The live docroot (read-only mirror) is authoritative only for reproduction fidelity and for extensions that have no obtainable upstream — and even then the files are vetted, never trusted.
- The earlier single-commit "monolith" baseline is an unvalidated helper only (it dropped extension manifests and clean core files and kept installer junk) — not a target.