Summary
Security hardening (defence in depth): the Extractor will currently package and serve any in-scope file the caller selects — including credential-bearing files. On the live smoke test (2026-07-23), the production install root contained wp-config.php.bak-20260717-212309 and wp-config.php.bak-redis-20260718-074109, each holding the full secret family in clear text (DB_PASSWORD, DB_USER/NAME/HOST, AUTH_KEY, SECURE_AUTH_KEY, LOGGED_IN_KEY, NONCE_KEY, all four salts — 24–25 defines each). Both were packaged into the sealed container and landed on the client machine.
This defeats the plugin's own safety posture: GET /environment deliberately masks the secret-define family server-side ("the secrets that never come down"), but the file layer hands the same secrets over anyway, via config backups. Leaving a .bak next to wp-config.php after an edit is extremely common in the wild.
The primary fix lives in the client (kntnt-wp-skills must always-exclude credential patterns from its selection — tracked there), but the server should not rely on every client getting its exclusion set right. The Extractor is the last line of defence for the site's secrets.
Proposed behaviour
Introduce a shared deny-list of credential-bearing path patterns and reject extraction jobs that select a matching file at POST /extractions validation time — fail fast with an explicit error listing the offending paths, so a misconfigured client learns its selection is wrong instead of silently receiving (or silently missing) secrets.
Suggested pattern family (install-root-relative, case-insensitive):
wp-config.php and any sibling variant: wp-config.php.* (.bak*, .save, .orig, .old, ~, editor swap files), wp-config-*.php — with wp-config-sample.php allowed (no secrets).
.env, .env.* anywhere in the tree.
- Root-level database dumps:
*.sql, *.sql.gz, *.sql.zip directly in the install root.
- Auth/key material:
*.pem, *.key, id_rsa* directly in the install root.
Decisions folded into this proposal (adjust if the maintainer disagrees):
- Reject at job creation rather than silently skipping at packaging time — silent skips would desynchronise the client's manifest/baseline accounting and hide the misconfiguration.
GET /files keeps listing these paths (the manifest stays truthful; clients need to see them to exclude them and to diff baselines). Optionally annotate matching entries (e.g. "restricted": true) so clients can build their exclusion sets from the server's own verdict.
- The deny-list must be a single shared code constant with unit tests — not prose — and documented in the REST contract.
Acceptance criteria
POST /extractions with a selection containing e.g. wp-config.php.bak-x returns a 4xx naming the offending path(s); no job is created.
wp-config-sample.php is not blocked.
- The pattern list lives in one constant, covered by tests for each pattern class (positive and negative cases).
GET /files behaviour documented (unchanged listing, plus the optional restricted annotation if adopted).
References
- Observed in the field: two
wp-config.php.bak-* files with full production secrets transferred in clear inside the sealed container (smoke test 2026-07-23).
- Complements the
/environment secret-define masking; closes the same hole at the file layer.
- Client-side primary fix: kntnt-wp-skills exclusion-set assembler (tracked in that repo).
Filed from the second live smoke test of the kntnt-wp-skills ⇄ kntnt-extractor cutover, 2026-07-23.
Summary
Security hardening (defence in depth): the Extractor will currently package and serve any in-scope file the caller selects — including credential-bearing files. On the live smoke test (2026-07-23), the production install root contained
wp-config.php.bak-20260717-212309andwp-config.php.bak-redis-20260718-074109, each holding the full secret family in clear text (DB_PASSWORD,DB_USER/NAME/HOST,AUTH_KEY,SECURE_AUTH_KEY,LOGGED_IN_KEY,NONCE_KEY, all four salts — 24–25 defines each). Both were packaged into the sealed container and landed on the client machine.This defeats the plugin's own safety posture:
GET /environmentdeliberately masks the secret-define family server-side ("the secrets that never come down"), but the file layer hands the same secrets over anyway, via config backups. Leaving a.baknext towp-config.phpafter an edit is extremely common in the wild.The primary fix lives in the client (kntnt-wp-skills must always-exclude credential patterns from its selection — tracked there), but the server should not rely on every client getting its exclusion set right. The Extractor is the last line of defence for the site's secrets.
Proposed behaviour
Introduce a shared deny-list of credential-bearing path patterns and reject extraction jobs that select a matching file at
POST /extractionsvalidation time — fail fast with an explicit error listing the offending paths, so a misconfigured client learns its selection is wrong instead of silently receiving (or silently missing) secrets.Suggested pattern family (install-root-relative, case-insensitive):
wp-config.phpand any sibling variant:wp-config.php.*(.bak*,.save,.orig,.old,~, editor swap files),wp-config-*.php— withwp-config-sample.phpallowed (no secrets)..env,.env.*anywhere in the tree.*.sql,*.sql.gz,*.sql.zipdirectly in the install root.*.pem,*.key,id_rsa*directly in the install root.Decisions folded into this proposal (adjust if the maintainer disagrees):
GET /fileskeeps listing these paths (the manifest stays truthful; clients need to see them to exclude them and to diff baselines). Optionally annotate matching entries (e.g."restricted": true) so clients can build their exclusion sets from the server's own verdict.Acceptance criteria
POST /extractionswith a selection containing e.g.wp-config.php.bak-xreturns a 4xx naming the offending path(s); no job is created.wp-config-sample.phpis not blocked.GET /filesbehaviour documented (unchanged listing, plus the optionalrestrictedannotation if adopted).References
wp-config.php.bak-*files with full production secrets transferred in clear inside the sealed container (smoke test 2026-07-23)./environmentsecret-define masking; closes the same hole at the file layer.Filed from the second live smoke test of the kntnt-wp-skills ⇄ kntnt-extractor cutover, 2026-07-23.