Five built-in generators that produce deterministic fake artifacts
keyed by the token slug:
- aws_creds — passive [default]/[prod] credentials block, no
callback wiring (AWS-key tokens require an external
trap, which is post-v1)
- git_config — .git/config with origin url = http_base/c/<slug>/repo.git
- env_file — .env with API_BASE_URL + WEBHOOK_NOTIFY_URL embedding
the callback URL plus inert realism filler
- ssh_key — PEM-shaped fake private key whose host comment carries
<slug>.<dns_zone> when DNS is deployed, else the
http_base host
- honeydoc — minimal HTML report with a 1x1 tracking-pixel <img>
whose src is the callback URL; fallback for the
deploy-time baseline before the operator uploads a
real DOCX/PDF
Tests assert byte-stability (same ctx -> same bytes), slug presence
in the embedded fields, that aws_creds is intentionally URL-free,
and that every artifact carries operator-facing notes for the
preview endpoint.
57 lines
1.9 KiB
Python
57 lines
1.9 KiB
Python
"""Fake ``.env`` with embedded callback URLs.
|
|
|
|
Modern web stacks read environment variables for everything from
|
|
database DSNs to webhook URLs, so dropping a few realistic-looking
|
|
``KEY=value`` pairs alongside the canary URL is unremarkable. The
|
|
slug appears in two fields:
|
|
|
|
* ``API_BASE_URL`` — the obvious one; an attacker scripting against
|
|
the credentials hits the worker on first invocation.
|
|
* ``WEBHOOK_NOTIFY_URL`` — secondary, in case the attacker greps for
|
|
``WEBHOOK`` and pivots there.
|
|
|
|
Other fields (``DB_PASSWORD``, ``REDIS_URL``, ``JWT_SECRET``) are
|
|
plausible but inert — they're realism filler, not detection
|
|
mechanisms.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
|
|
from decnet.canary.base import CanaryArtifact, CanaryContext, CanaryGenerator
|
|
|
|
|
|
def _stable_token(seed: str, prefix: str = "") -> str:
|
|
h = hashlib.sha256((prefix + seed).encode()).hexdigest()
|
|
return h[:32]
|
|
|
|
|
|
class EnvFileGenerator(CanaryGenerator):
|
|
name = "env_file"
|
|
|
|
def generate(self, ctx: CanaryContext) -> CanaryArtifact:
|
|
base = ctx.http_base.rstrip("/")
|
|
slug = ctx.callback_token
|
|
api_url = f"{base}/c/{slug}"
|
|
body = (
|
|
"# Production environment — DO NOT COMMIT\n"
|
|
f"API_BASE_URL={api_url}\n"
|
|
f"WEBHOOK_NOTIFY_URL={api_url}/webhook\n"
|
|
f"DB_PASSWORD={_stable_token(slug, 'db:')}\n"
|
|
f"REDIS_URL=redis://:{_stable_token(slug, 'redis:')[:16]}@redis.internal:6379/0\n"
|
|
f"JWT_SECRET={_stable_token(slug, 'jwt:')}\n"
|
|
"LOG_LEVEL=info\n"
|
|
"ENVIRONMENT=production\n"
|
|
)
|
|
return CanaryArtifact(
|
|
path="",
|
|
content=body.encode("utf-8"),
|
|
mode=0o600,
|
|
mtime_offset=-86400 * 7, # last edited a week ago
|
|
generator=self.name,
|
|
notes=[
|
|
f"API_BASE_URL embeds {api_url}",
|
|
f"WEBHOOK_NOTIFY_URL embeds {api_url}/webhook",
|
|
],
|
|
)
|