"""How SiteMaps locks your package: the code that does it, copied unchanged from the running service. Published so you can check the privacy page, not take it on trust. 1. The job API (below, first part) makes a random key for each job. It keeps only a hash of a token made from that key, so the job ID alone opens nothing. A batch's packages get keys made from the batch's key, which is stored nowhere. 2. The key reaches you after "#k=" in the page and email links. Browsers never send that part of an address to a server. 3. The map maker (below, second part) locks every map, the PDF, the ZIP and the by-law link with AES-256-GCM, using a key made from your key, before storing them. 4. The package page (https://site-maps.com/p.js) unlocks them in your browser. """ # ---- Job API: making the key, and checking the token (from api_handler.py) ---- # BL-157 (2026-09-26, a pilot user's confidentiality requirement; CompMaps' # CM-24 method). The row keeps NO address: the address travels only in the queue # message the worker consumes. The job's key goes back to the page and, through # the worker, into the email link after "#k=", which browsers never send to a # server; the row keeps only the hash of the access token derived from it. The # row expires with the package. key = secrets.token_urlsafe(24) now = int(time.time()) _ddb.Table(TABLE).put_item(Item={ "job_id": job_id, "tool": "sitemaps", "status": "queued", "email": email, "created_at": now, "expires_at": now + SITE_MAPS_KEEP_DAYS * 86400, "access_hash": _sha256(_sitemaps_access(key)), }) _sqs.send_message(QueueUrl=QUEUE_URL, MessageBody=json.dumps( {"job_id": job_id, "address": address, "email": email, "key": key, "skip_farm": skip_farm, **({"project_id": project_id} if project_id else {})})) # 437 ... def _batch_child_key(batch_key, child_id): d = hashlib.sha256(("sitemaps-batch-child:%s:%s" % (batch_key, child_id)) .encode("utf-8")).digest() return base64.urlsafe_b64encode(d).decode("ascii").rstrip("=")[:32] def _sha256(s): return hashlib.sha256(s.encode("utf-8")).hexdigest() def _sitemaps_access(key): """The access token the page derives from the job's key (same in p.html).""" return _sha256("sitemaps-access:" + key) # ---- Map maker: locking what it stores (from lambda_handler.py) ---- _LOCK_MAGIC = b"SMLK1" _LOCK_CHUNK = 4 * 1024 * 1024 def _lock_key(key): """BL-157 Phase B: the file key, made from the link's key (as in p.html). It differs from the access token, so the hash in the job row opens nothing.""" return hashlib.sha256(("sitemaps-enc:" + key).encode("utf-8")).digest() def _lock_file(src, dest, enc_key): """Lock src into dest. Format: "SMLK1", the chunk size (4 bytes, big-endian) and 8 random bytes; then each chunk of up to 4 MiB as AES-256-GCM ciphertext and its 16-byte tag. A chunk's nonce is the 8 bytes plus its index (4 bytes, big-endian); its associated data is 1 for the last chunk and 0 otherwise, so a cut-short file fails to open rather than opening short. Chunks let the page unlock a 50 MB ZIP piece by piece and report progress.""" from cryptography.hazmat.primitives.ciphers.aead import AESGCM # noqa: PLC0415 aes = AESGCM(enc_key) base = os.urandom(8) n = max(1, -(-os.path.getsize(src) // _LOCK_CHUNK)) with open(src, "rb") as fi, open(dest, "wb") as fo: fo.write(_LOCK_MAGIC + struct.pack(">I", _LOCK_CHUNK) + base) for i in range(n): fo.write(aes.encrypt(base + struct.pack(">I", i), fi.read(_LOCK_CHUNK), b"\x01" if i == n - 1 else b"\x00")) def _lock_bytes(data, enc_key): """A small value in the same format, one chunk (patch 355b: the by-law link).""" from cryptography.hazmat.primitives.ciphers.aead import AESGCM # noqa: PLC0415 base = os.urandom(8) return (_LOCK_MAGIC + struct.pack(">I", _LOCK_CHUNK) + base + AESGCM(enc_key).encrypt(base + struct.pack(">I", 0), data, b"\x01")) def _put(src, s3_key, ctype, enc_key): """The one place the worker stores a file. With the job's key the stored bytes are locked; without one (an operator's direct invoke) they are plain.""" if not enc_key: _s3.upload_file(str(src), BUCKET, s3_key, ExtraArgs={"ContentType": ctype}) return fd, tmp = tempfile.mkstemp(suffix=".locked") os.close(fd) try: _lock_file(str(src), tmp, enc_key) _s3.upload_file(tmp, BUCKET, s3_key, ExtraArgs={"ContentType": "application/octet-stream"}) finally: os.unlink(tmp)