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
160 lines
7.9 KiB
Markdown
160 lines
7.9 KiB
Markdown
# 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**:
|
|
|
|
1. **Known-good reference.** The HIDS baseline-diff compares the live docroot against it
|
|
and classifies every file (`baseline-match / data / malware / residual`).
|
|
2. **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`](AGENTS.md) first — it carries the rules
|
|
(verification, repo boundary, layer semantics) in machine-facing form.
|
|
|
|
---
|
|
|
|
## Building a baseline (read this first)
|
|
|
|
```sh
|
|
# 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`](RUNBOOK.md) (recipe + module inventory),
|
|
[`PROVENANCE.md`](PROVENANCE.md) (per-extension origins),
|
|
[`malware-iocs.md`](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:
|
|
```sh
|
|
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>/` or `overrides/` 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.
|