Compare commits

..

No commits in common. "9fef8645a51aefeeb39009853428b02195273777" and "5603690a726d11ef12222a700dc97da0233b2712" have entirely different histories.

2 changed files with 58 additions and 242 deletions

127
AGENTS.md
View File

@ -1,127 +0,0 @@
# 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.

171
README.md
View File

@ -1,81 +1,52 @@
# Cadline web baselines — constituents + generator # www.archline.hu — clean baseline (constituents + generator)
This repo produces the **clean code baselines** of the Cadline web sites hosted on This repo produces the **clean code baseline** of the www.archline.hu docroot
`tanis.cadline.hu` — malware-free, faithful reproductions of what each site's source (Cadline, Joomla 3.8.11, host `tanis.cadline.hu`) — the post-compromise, malware-free,
*should* be, assembled from clean sources rather than copied from the live server. faithful reproduction of the live server's source files.
A baseline has **two jobs**: The baseline is the **input** to the HIDS baseline-diff verifier: a per-vhost,
known-good reference tree the verifier compares the live docroot against
1. **Known-good reference.** The HIDS baseline-diff compares the live docroot against it (`baseline-match / data / malware / residual`). This repo only *produces* that tree;
and classifies every file (`baseline-match / data / malware / residual`). the verification runs on the HIDS side.
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 ## Building the baseline (read this first)
There is **no separate repo per site**. Sites (and CMS versions) live on **branches**: The repo does **not** contain the deployed docroot — it contains the baseline
*constituents* plus a generator script. Run the script to produce the deployed tree:
| 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 ```sh
# 1. Clone, and check out the branch of the site you want: # 1. Clone WITH submodules (common_cadline_libraries + maintenance are submodules):
git clone --recurse-submodules ssh://git@git.cadline.hu:22222/cadline_web/www_archline_hu.git git clone --recurse-submodules ssh://git@git.cadline.hu:22222/cadline_web/www_archline_hu.git
cd www_archline_hu cd www_archline_hu
git checkout site/archlinexp.eu # or stay on main for www.archline.hu # (if you already cloned without --recurse-submodules:)
# (if you cloned without --recurse-submodules and the branch uses submodules:)
git submodule update --init git submodule update --init
# 2. Generate: # 2. Generate the baseline:
./build-baseline.sh # → ./out/ ./build-baseline.sh # → ./out/
# 3. Use ./out/ as that site's known-good reference / deploy source. # 3. Use ./out/ as the per-vhost vanilla_ref (the known-good reference tree).
``` ```
`out/` is the deployed **code** docroot. It is git-ignored and fully regenerated on every `out/` is the deployed **code** docroot (~23.5k files). It is git-ignored and fully
run (the script `rm -rf out/` first, so it is idempotent). regenerated on every run (the script is idempotent — it `rm -rf out/` first). Point the
verifier's baseline-diff at `out/`, or copy it to wherever the verifier expects the ref.
**Prerequisites:** `bash`, `rsync`, `git`, `find`, `sed`. No PHP, no build toolchain, no **Prerequisites:** `bash`, `rsync`, `git` (for submodules), `find`, `sed`. No PHP, no
network at build time. `lib/jmap.py` (python3) is an *authoring* aid used when preparing a build toolchain, no network at build time (submodules are already cloned).
package's pristine commit — **`build-baseline.sh` never invokes it**. `lib/jmap.py` (python3) is a *maintenance* tool used only when authoring a package's
pristine commit — **it is not invoked by `build-baseline.sh`**.
**What `out/` contains / excludes:** **What `out/` contains / excludes:**
- **Contains:** Joomla 3.8.11 core + every installed extension (110 components, ~98
- **Contains:** the CMS core + every installed extension + the Cadline-own code, as modules, 164 plugins, 8 templates) + the Cadline-own code — all as deployed files.
deployed files. - **Excludes:** malware/IOCs (see [`malware-iocs.md`](malware-iocs.md)) and runtime
- **Excludes:** malware and known planted paths (`malware-iocs.paths`), and runtime **data** (`cache/`, `tmp/`, `logs/`, `images/`, `public/`, media data). The result is
**data** (`cache/`, `tmp/`, `logs/`, `images/`, `public/`, media). The result is code code only.
only — which is also why a deploy must never wipe the data directories. - Byte-matches the live docroot for all legit code; it is intentionally **clean** on the
4 files the attacker infected (3 trojanized core files + `shaper_helix3/index.php`),
### Verify before you trust it so the verifier flags those on the live server. Verified: non-malware code residual = 0.
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).
--- ---
@ -83,77 +54,49 @@ core version).
| Path | What | How `build-baseline.sh` uses it | | Path | What | How `build-baseline.sh` uses it |
|---|---|---| |---|---|---|
| `core/` | CMS official package (deployed form, installer removed) | rsync → `out/`, minus demo assets + data dirs | | `core/` | Joomla 3.8.11 official package (deployed form) | 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/` | | `packages/<slug>/` | 5 upstream-obtainable extensions, deployed-layout, reconciled to live bytes. Modified ones are two commits (pristine upstream → live delta) so `git log`/`git diff` shows exactly how we differ from upstream. | rsync → `out/` |
| `deployed/<slug>/` | extensions with **no** obtainable upstream: vetted live files, deployed layout. | rsync → `out/` | | `deployed/<slug>/` | ~55 no-source extensions: vetted live files (deployed layout). `_shared/` holds cross-extension language/vendor/manifests. | rsync → `out/` |
| `cadline/<slug>/` | Cadline own-code. On `main` this includes git submodules of the developer repos (cadcommonlib / maintenance). | rsync → `out/` (`.git` excluded) | | `cadline/<slug>/` | Cadline own-code. `cadline/backend/common_cadline_libraries` and `…/maintenance` are **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/` | | `overrides/` | Cadline core modifications (live versions overlaid on the package core) + a few package-missing core files. | rsync → `out/` (last, so it wins) |
| `malware-iocs.paths` | machine-readable exclusion list (droppers, leftovers) | build step 6 | | `lib/jmap.py` | Generic Joomla manifest→deployed-path mapper (authoring aid only). | not used at build time |
| `lib/jmap.py` | Joomla manifest→deployed-path mapper (authoring aid only) | not used at build time |
Details: [`RUNBOOK.md`](RUNBOOK.md) (recipe + module inventory), Details: [`RUNBOOK.md`](RUNBOOK.md) (recipe + module inventory),
[`PROVENANCE.md`](PROVENANCE.md) (per-extension origins), [`PROVENANCE.md`](PROVENANCE.md) (per-extension origins),
[`malware-iocs.md`](malware-iocs.md) (the excluded IOC set, `main`). [`malware-iocs.md`](malware-iocs.md) (excluded IOC set).
--- ---
## Why customized core files never come from live ## Updating the baseline
A CMS updater skips files the site has customized. The version string moves forward; the - **Developer-controlled code** (`common_cadline_libraries`, `maintenance`): the
customized files silently stay on the old base. The eu site demonstrated this: it developer pushes to the cadcommonlib / maintenance repos, then:
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 ```sh
git submodule update --remote # pull the developer's latest git submodule update --remote # pull the developer's latest
./build-baseline.sh # regenerate out/ ./build-baseline.sh # regenerate out/
git add -A && git commit # record the new submodule pointers git add -A && git commit # record the new submodule pointers
``` ```
- **Everything else** (EOL core, 3rd-party extensions): effectively frozen. If a legit - **Everything else** (Joomla core EOL, 3rd-party extensions): effectively frozen; if a
change lands, re-sync the affected `deployed/<slug>/` or `overrides/` and commit. legit change ever lands, re-sync the affected `deployed/<slug>/` or `overrides/` from
the live docroot and commit.
**Known divergence (heads-up for the verifier):** `common_cadline_libraries` and **Known divergence (heads-up for the verifier):** `common_cadline_libraries` and
`maintenance` take the *authoritative* submodule HEAD content, which differs from the `maintenance` currently take the *authoritative* submodule HEAD content, which differs
current live docroot (mixed CRLF/LF + a content drift on `crm_hardlock.class.php` / from the current live docroot (mixed CRLF/LF + a content drift on
`POfile.php`) because live was not last deployed from those HEADs. The verifier flags `crm_hardlock.class.php` / `POfile.php`) because live was not last deployed from those
this until live is re-deployed or the drift is reconciled. HEADs. The verifier will flag this until live is re-deployed from the submodules or the
drift is reconciled. Line-ending normalization, if wanted, is a verifier-side choice.
## 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 ## Authority
- The **upstream vendor package** is authoritative for the CMS core. The live docroot (RO mirror) is the source of truth for reproduction; the developer git
- The **developer source repo** is authoritative for the code it owns. repos are authoritative for the code they own. The earlier single-commit "monolith"
- The **live docroot** (read-only mirror) is authoritative only for reproduction fidelity baseline is an unvalidated helper only (it dropped extension manifests and clean core
and for extensions that have no obtainable upstream — and even then the files are files and kept installer junk) — it is not a target.
vetted, never trusted.
- The earlier single-commit "monolith" baseline is an unvalidated helper only (it dropped ## References
extension manifests and clean core files and kept installer junk) — not a target.
- The archline.hu 2026 security audit and incident analysis (operator-held).
- The excluded malware set is documented in [`malware-iocs.md`](malware-iocs.md).