Skip to content

Security: theofanis-tsakanikas/watermark

Security

SECURITY.md

Security

Scope

This is a portfolio reference implementation. It processes synthetic data only — the fleet, the customers and the readings are generated by data/ from a fixed seed, and no real personal data has ever entered it. The AWS estate it deploys is stood up, driven and destroyed; its resting state is a state bucket, a KMS key, three SSM parameters and a deploy role.

Reporting

Open a GitHub issue, or contact the maintainer directly for anything you would rather not file in public. There is no bug bounty and no SLA.

What the design guarantees

No long-lived access keys exist. CI authenticates through GitHub OIDC to watermark-deploy, whose trust policy names exactly four subjects — the deploy and destroy environments of this repository, in both of the forms GitHub's claim can take. No IAM user can assume it, which means no human can, from a laptop or from the console. check_oidc_subjects.py refuses a trust policy that widens to a branch or a wildcard.

Devices are pinned to their own topic. Each meter and charger carries its own X.509 certificate, and the IoT policy scopes publication with ${iot:Connection.Thing.ThingName} — so a compromised device can publish as itself and as nothing else.

Data is encrypted with customer-managed keys, including one KMS key per data subject for the crypto-shredding path. Rotation is deliberately off on those keys: rotation retains the previous backing key so old ciphertext stays readable, and a shredding key that keeps a copy shreds nothing.

Access to the lakehouse is Lake Formation, and it is evaluated as well as applied. make policy computes the full principal × resource lattice offline — 24 pairs, every reachable set exact and every closed path closed — with no AWS account. The live estate matches it: the account owner, with IAM administrator rights, cannot read a single column of watermark_silver.meter_interval, and the catalogue does not list it for them.

Secrets are gated on every push. gitleaks runs in CI. No account id, credential or endpoint is committed; the account id is resolved at deploy time from parameters infra/bootstrap published.

Known limitations

  • The estate is single-account, single-region, single-tenant. Everything is eu-central-1. There is no network segmentation beyond one VPC with private subnets and interface endpoints, and no WAF, because nothing is publicly reachable.
  • One KMS key per data subject does not scale. Forty-one subjects here; a real fleet would be past the account quota. See ADR-0009.
  • The account id is legible in terminal screenshots in images/ — inside bucket names and ARNs. It is the account this estate was destroyed in, and an account id is an identifier rather than a credential, but it is stated here rather than left to be noticed.
  • The processing container image is not scanned by any gate. checkov reaches zero findings across the Terraform layers; nothing reads an ECR image scan. The image is built by CI from a pinned base, so the input is fixed and the build is checked, but advisories against that base are nobody's gate.
  • Erasure reaches a declared boundary, not the weights. A model trained before a request retains the subject statistically. The certificate says so on its face, quarantine plus retraining within a stated residual window is the mitigation, and machine unlearning is not claimed.

There aren't any published security advisories