GrandLine Architecture Intelligence. Security White Paper
Revision: 2026-Q2 · Audience: security architects, CISOs, compliance leads evaluating GrandLine Architecture Intelligence for multi-cloud architecture, security, and FinOps.
This is an engineering-honest document. If a control is partial, we say so. If a control is on the roadmap, we say so. If a question is unanswered here, ask us and we will answer it in writing.
1. Summary
GrandLine Architecture Intelligence is a multi-tenant SaaS (and optional self-hosted) platform that ingests read-only metadata from AWS, Azure, and GCP, builds a normalised inventory and relationship graph, renders architecture diagrams, runs security and cost rules against the graph, and publishes reports. GrandLine is a metadata system: we do not proxy production traffic, we do not read object data, we do not handle payment card data, and we do not hold workload secrets for our customers.
The primary security claims are:
- Tenant isolation is defence-in-depth, not a single control: logical (tenant-scoped IDs), database (Postgres RLS), cryptographic (per-tenant AWS KMS customer managed keys for report objects and connector secrets), and network (per-tenant S3 prefixes + scoped IAM on the SaaS control plane).
- Read-only by design for every cloud connector. AWS uses
AssumeRolewith anExternalId; Azure uses Microsoft Entra ID Workload Identity Federation (federated credential on an app registration) grantedReader; GCP uses Workload Identity Federation to a service account withroles/viewer,roles/iam.securityReviewer, androles/bigquery.dataViewer. We publish the IAM policies we use and diff them per release. - Least privilege inside the product too. The API enforces ABAC on every query. Role assignments are scoped to tenant, account, or a resource tag.
- Audit is immutable and exportable. Every privileged action writes to an append-only log with a SHA-256 chain and an optional S3 Object Lock vault (7 years, Governance mode).
- Supply chain is attested. Every container we ship is built with hermetic builders, produces an SBOM (CycloneDX), and is signed with Sigstore Cosign. Customers can verify before running.
This paper explains each of these claims with specifics.
2. Threat model
We model four adversary classes:
A1. External attacker on the internet. Network unauthenticated. Goal: extract tenant data, pivot into customer clouds, take over the control plane.
A2. Malicious or compromised tenant user. Authenticated to tenant T. Goal: read or modify data belonging to tenant T' ≠ T; escalate from analyst to admin within T.
A3. Malicious GrandLine operator (insider). Has production access. Goal: read any tenant's data, tamper with findings, exfiltrate secrets.
A4. Supply chain attacker. Compromise of a dependency, CI, or container registry. Goal: ship backdoored code that runs in customer environments.
Out of scope in this paper: nation-state attacks with lawful access to cloud infrastructure (we document data residency separately), physical attacks on Anthropic / AWS datacentres, and compromise of the customer's IdP (we cannot defend session tokens we did not issue).
For each class we identify the primary controls:
| Adversary | Primary mitigations |
|---|---|
| A1 | WAF + CloudFront + rate-limiting; TLS 1.3 only; CSP + SRI; short-lived JWTs with rotating keys; MFA required for all human accounts; login-anomaly detection. |
| A2 | Tenant-scoped IDs on every table; Postgres RLS enforced at the DB role the API uses; ABAC permission check in every request handler; per-tenant AWS KMS customer managed key on report objects; separate connector secrets per tenant. |
| A3 | Break-glass workflow for any production data access with mandatory second approver + full audit; all prod data access logged to a separate account the prod role cannot write to; per-tenant customer managed keys limit blast radius of a single compromised operator session. |
| A4 | Deterministic, hermetic container builds; SBOM on every release; Sigstore signing; dependency pinning via pnpm-lock.yaml and requirements.txt with hashes; npm audit + pip-audit gate in CI; weekly Dependabot; release notes enumerate every upgraded dep. |
3. Tenant isolation
Tenant isolation is the single most important property of the platform. We implement it at four layers.
3.1 Logical isolation. every row carries tenantId
Every tenant-scoped table in the primary data model has a non-null tenantId column, indexed, and is referenced by a foreign key to Tenant. This is visible in packages/db/prisma/schema.prisma: User, Role, Connector, CloudAccount, Resource, Relationship, Finding, CostDaily, Report, AuditEvent, and ApiToken all carry tenantId.
A query that does not filter on tenantId is a bug. We do not rely on application code alone to remember to filter. see 3.2.
3.2 Database isolation. Postgres Row-Level Security
The API connects to Postgres as a non-superuser role with FORCE ROW LEVEL SECURITY enabled on every tenant-scoped table. Each request starts a transaction, issues SET LOCAL app.tenant_id = '<tenantId>', runs the query, then commits or rolls back. Every RLS policy is USING (tenant_id = current_setting('app.tenant_id')::text).
This gives us three things:
- A forgotten
whereclause in application code cannot leak cross-tenant rows. the database refuses to return them. - A buggy service that invokes a different service's DB role still cannot escape the scope. RLS is on the role used, not on the application identity.
- Migrations and operational tooling run as a separate role that bypasses RLS, and that role is only usable via the break-glass path documented in §7.
We run a CI job that asserts, for every model in schema.prisma marked tenant-scoped, that a matching RLS policy exists. A PR that adds a model without its policy fails CI.
3.3 Cryptographic isolation. per-tenant AWS KMS customer managed key
Every tenant has a dedicated AWS KMS customer managed key (alias/grandline/tenant/<tenantId>), created on tenant provisioning, with:
- Key policy: only the tenant's control-plane role may
Encrypt/Decrypt; only the bootstrapper mayScheduleKeyDeletion. - Scope of use: envelope-encryption of (a) connector secrets at rest, (b) report objects in S3, (c) snapshots of the graph when exported.
- Rotation: automatic annual rotation; the key identifier does not change.
- Deletion: 30-day pending-window when a tenant is deleted; GrandLine operators cannot bypass the window.
We do not use a single "platform" key for all tenants. A compromised app-level credential cannot Decrypt across tenants because the IAM policy attached to the runtime scopes AWS KMS usage to keys whose alias suffix matches the request's tenant.
Customers on the Enterprise plan can supply their own customer managed key (BYOK) with a grant that lets GrandLine Encrypt/Decrypt only. Revoking the grant makes their reports and stored secrets unreadable within minutes; we designed the platform to degrade gracefully in that case rather than fail open.
3.4 Network and storage isolation
Report objects live in a single S3 bucket under s3://grandline-reports/<tenantId>/… with a bucket policy that denies any GetObject/PutObject whose key prefix does not match the tag on the calling role. In practice, the runtime role has no s3: on the bucket at all. it has a scoped-down policy built at tenant-provisioning time that grants only <tenantId>/.
Every signed URL we hand back is scoped to a single object key, expires in 15 minutes, and is logged to the audit trail with the requesting user, the object key, and the expiry.
4. Cryptographic posture
| Surface | Algorithm / control |
|---|---|
| External TLS | TLS 1.3 only; HSTS 2 years with preload; OCSP stapling; cert via ACM; certificate transparency monitored. |
| Internal service-to-service (SaaS) | mTLS via a service mesh inside the cluster; workload identities are short-lived SPIFFE IDs. |
| At-rest (Postgres) | AWS Aurora with customer managed key storage encryption; snapshots encrypted with the same key. |
| At-rest (Redis) | Encrypted with a platform-level customer managed key; no tenant secrets are persisted in Redis. it is a BullMQ queue and a short-lived cache. |
| At-rest (S3 reports) | SSE-KMS with per-tenant customer managed key; bucket key enabled. |
| At-rest (connector secrets) | Envelope-encrypted with per-tenant customer managed key, stored as Connector.secretRef (opaque reference); the plaintext never touches Postgres. |
| At-rest (user passwords) | argon2id, m=65536, t=3, p=4, per-user salt, 32-byte hash. We prefer WebAuthn and SSO; passwords exist for break-glass. |
| In transit (cloud connector calls) | TLS 1.2+ using the cloud SDK defaults; AWS SigV4 / Microsoft Entra ID OAuth 2.0 bearer tokens (via MSAL) / GCP OAuth 2.0 bearer tokens obtained via Workload Identity Federation. |
| Signed URLs | 15-minute expiry, scope-limited, logged. |
| JWTs | EdDSA (Ed25519); keys in AWS KMS; 10-minute access token, 24-hour refresh token bound to session + user-agent fingerprint. |
| Randomness | OS CSPRNG (/dev/urandom); no custom RNG. |
We do not roll our own crypto. All primitives are from the cloud SDKs, libsodium, Node's crypto, or Python's cryptography.
5. Cloud access model
GrandLine reads cloud metadata. We do not write to customer clouds. The only requests we make are describe/list/get-style API calls.
5.1 AWS
Customers deploy a CloudFormation or Terraform module that creates a role, GrandLineReadOnly, with a trust policy that permits sts:AssumeRole from our SaaS control-plane account only if sts:ExternalId equals a per-connector secret we generate once and never display again. This defeats the classic confused-deputy problem. a customer who reused the same role ARN across vendors cannot be impersonated, because the ExternalId is unique to this connector.
The permission policy attached to the role is published verbatim at packages/connectors-aws/policy/grandline-readonly.json. It is a superset of SecurityAudit and ViewOnlyAccess. we explicitly did not use the managed policies because they both have gaps (e.g., SecurityAudit allows iam:GenerateCredentialReport, which writes) and we wanted a predictable surface. The policy grants only Describe, Get, List*, and a handful of ce: cost-explorer reads. We diff this policy on every release and call out changes in the release notes.
Sessions last 15 minutes. We refresh on demand. We do not cache long-lived AWS credentials anywhere; the assumed role's credentials live in the worker process memory and are discarded when the scan finishes.
5.2 Azure
Customers create a Microsoft Entra ID app registration in their tenant and grant it the Reader built-in role at subscription or management-group scope. We authenticate with Microsoft Entra ID Workload Identity Federation. a federated credential on the app registration. so our GitHub Actions and our production cluster both present OIDC tokens, and Microsoft Entra ID validates them against that federated credential. No client secret leaves the customer tenant. We rotate nothing; there is nothing to rotate.
5.3 GCP
Customers create a Google Cloud service account with roles/viewer, roles/iam.securityReviewer, and roles/bigquery.dataViewer at the organisation or project scope, then configure Workload Identity Federation with an external identity provider that trusts our control-plane cluster's OIDC issuer. Our workloads exchange their OIDC tokens at the WIF pool to impersonate the customer's service account with short-lived tokens (1 hour). Again, no long-lived key material crosses the boundary.
5.4 What we do NOT do
We do not read object contents (Amazon S3 objects, Azure Blob Storage contents, Cloud Storage (GCS) objects). We do not call IAM credential-issuing actions. We do not call any create/update/delete API. We log every API call we make, with the caller, the target, and the response size, to the audit trail.
6. Identity, authentication, and authorization
6.1 Authentication for humans
Supported factors:
- SSO (SAML 2.0, OIDC). recommended. We support Okta, Microsoft Entra ID, Google Workspace, JumpCloud, and any generic SAML/OIDC IdP. Group-based role mapping is rule-driven.
- WebAuthn passkeys. first-class. Stored per-user as
webauthnCredsin theUsertable. We require a passkey or TOTP for accounts not behind SSO. - TOTP. RFC 6238, 30-second step, 6-digit codes. Secret is stored encrypted under the tenant customer managed key in
mfaSecret. - Password. argon2id, minimum 12 chars, checked against HaveIBeenPwned k-anonymity API at signup and at change. Passwords alone are never enough. MFA is required on every Pro/Enterprise tenant.
Session tokens are 10-minute JWTs, bound to the originating IP /24 and user-agent hash. Refresh is a rotating token stored in a httpOnly, Secure, SameSite=Strict cookie. We revoke all sessions on password change, MFA change, or admin-initiated "sign everyone out".
6.2 Authentication for machines
ApiToken records store argon2-hashed tokens, a non-empty scopes array, and an optional expiresAt. Tokens can be scoped to a tenant only. they cannot cross tenants. A token is presented as Bearer <opaque> and every use writes to the audit trail.
6.3 Authorization. ABAC on top of RBAC
UserRole is the join table. Each row grants user → role within a (scopeType, scopeId) pair, where scopeType is one of tenant, account, or resource_tag. This means a user can be Viewer on the whole tenant, SecurityAdmin on a specific cloud account, and Owner on resources tagged team=platform, all at once.
Permission checks happen in a single middleware that takes the request's target tenant, cloud account, and/or resource, walks the user's role assignments, and returns the union of permissions. The middleware is ~100 lines and is covered by 400+ unit tests because it is the single most security-critical piece of code in the product.
System roles (isSystem = true) are shipped by GrandLine and cannot be edited by tenants. Custom roles are fine but must compose out of the permissions we ship.
7. Operator access and break-glass
GrandLine operators do not have standing access to customer data. The production database is in an AWS account that human engineers cannot read from directly. Access requires:
- An on-call or incident-response justification linked to a ticket.
- Approval from a second engineer (four-eyes) recorded in the same ticket.
- A time-boxed session (max 60 minutes) that produces a JIT IAM role.
- Full session recording. every SQL query is logged to a separate AWS account that the break-glass role cannot write to. We cannot delete our own tracks.
We run a quarterly tabletop where we assume an operator is malicious and walk through what they could achieve with 60 minutes of access. The output is a short report we share on request with Enterprise customers under NDA.
8. Audit and retention
AuditEvent is the append-only log. Every privileged action writes a row with tenantId, actorId, action (e.g., connector.create, report.download, user.role.assign), targetType, targetId, ip, ua, and a free-form details JSON.
On the Enterprise plan we mirror the log to S3 with Object Lock in Governance mode, a 7-year retention, and per-tenant customer managed keys. Customers can stream the log to their SIEM via:
- S3 → Athena (partitioned by date)
- Kinesis Firehose → HTTP endpoint (Splunk HEC, Datadog, etc.)
- A JSON-lines subscription via our webhook delivery system
We store a Merkle-chain hash in each row's details that links to the previous event's hash for that tenant, so a customer can detect tampering even without Object Lock.
Retention defaults:
| Data | Retention | Deletion on tenant offboarding |
|---|---|---|
Primary graph (Resource, Relationship) | 90 days of history | Hard-delete in 30 days |
| Findings (closed) | 2 years | Hard-delete in 30 days |
| Cost data | 13 months (rolling) | Hard-delete in 30 days |
| Reports | 2 years | Hard-delete in 30 days |
| Audit events | 7 years (Enterprise: customer choice) | Customer managed key deletion makes them cryptographically unreadable; hard-delete is a customer-triggered job |
9. Data residency
SaaS is offered in three regions: us-east-1 (N. Virginia), eu-west-1 (Ireland), and ap-southeast-2 (Sydney). Tenant data is pinned to the tenant's region. Postgres, S3, KMS, and workers all run in-region. We do not replicate tenant data across regions for disaster recovery; instead, cross-AZ multi-writer Aurora gives us DR within the region. Customers who need cross-region DR use the self-hosted edition.
Self-hosted installs run wherever the customer runs them. We do not phone home except for (a) optional license validation (Enterprise only, monthly) and (b) opt-in telemetry.
10. Supply chain
10.1 Build
All container images are built in a GitHub Actions hosted runner, in a dedicated release account, using a reproducible Dockerfile with pinned base images (digests, not tags). Build outputs are:
- A container image, tagged
grandline/<component>:<semver>@<digest>. - A CycloneDX SBOM (
*.sbom.json) listing every runtime dependency with its version and hash. - A Sigstore cosign signature over the image digest, produced with keyless OIDC (the GitHub Actions workflow is the signer identity).
- A SLSA v1.0 provenance attestation showing the source commit, builder, and inputs.
Customers pull our images from ghcr.io/grandline/* and can verify before running:
cosign verify ghcr.io/grandline/api:3.4.0 \
--certificate-identity https://github.com/grandline/grandline/.github/workflows/release.yml@refs/tags/v3.4.0 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
10.2 Dependencies
Node deps are pinned via pnpm-lock.yaml; Python deps via requirements.txt with --hash entries. Every PR runs npm audit --audit-level=high, pip-audit, and trivy fs against the lockfiles. Findings block merge unless a CVE is annotated as not-applicable with a written reason in a dedicated file we publish quarterly.
We do not pull dependencies from public registries at runtime. All images are self-contained.
10.3 Secrets hygiene
No secrets in the repo. Pre-commit hooks run gitleaks. CI runs trufflehog against every PR. We rotate the customer-facing ExternalId on demand via a self-service flow.
11. Vulnerability disclosure and secure SDLC
- Security issues →
[email protected]with[security]in the subject line. PGP key on the website. We acknowledge in 1 business day, triage in 3, and patch critical CVEs within 7 days. - Responsible disclosure: we run a public policy; safe-harbour text published on the website.
- External penetration test: annual, executed by a third party. Enterprise customers receive the summary under NDA.
- Red-team exercise: annual, full-scope, including the SaaS control plane.
- Every PR requires a "threats?" section in its description; the reviewer confirms it has been considered.
12. Compliance mapping
GrandLine targets SOC 2 Type II, ISO 27001, and GDPR. The table below maps frequently-asked controls to the corresponding section of this paper.
| Control family | Reference |
|---|---|
| SOC 2 CC6.1. logical access | §3, §6 |
| SOC 2 CC6.6. encryption in transit | §4 |
| SOC 2 CC6.7. encryption at rest | §4 |
| SOC 2 CC7.2. detection of unauthorised access | §7, §8 |
| SOC 2 CC8.1. change management | §10, §11 |
| ISO 27001 A.5.23. cloud services | §5 |
| ISO 27001 A.8.3. information access restriction | §3, §6 |
| ISO 27001 A.8.24. cryptography | §4 |
| ISO 27001 A.8.28. secure coding | §10, §11 |
| GDPR Art. 32. security of processing | §3, §4, §5, §7, §8 |
| GDPR Art. 30. records of processing | §8 |
| GDPR Art. 17. right to erasure | §8 (retention + hard-delete job) |
SOC 2 Type II report and ISO 27001 certificate are provided to Enterprise customers under NDA. Readiness letters are available earlier in the sales cycle.
13. What is on the roadmap
To be honest about what is not yet done:
- FedRAMP Moderate. on the 2026-H2 roadmap; no blocker except the certification process itself.
- HIPAA BAA. we do not sign BAAs today because GrandLine is not designed to store PHI; customers who want to run us in a HIPAA-regulated environment should use the self-hosted edition.
- Automatic customer-managed encryption on the free tier. currently Pro+ only.
- Continuous SBOM diff in the UI. today customers pull the SBOM from releases; a UI "what's new" is in the works for 2026-Q3.
14. Questions
If anything in this paper is unclear, is inconsistent with what you observe in the product, or is missing a control you need, write to [email protected] with [security] in the subject line. We will answer in writing and update this document.
This document is reviewed and published quarterly. See the "Revision" field at the top for the current version.