← ALL DOCS
DOCS · SECURITY REVIEWERS

Security overview

This document is for security reviewers. It covers what Cloud Certainty TraceHound protects against, how users are identified, how access is limited, where data goes and how it is encrypted. Everything described here runs in your own AWS account.

Threat model

TraceHound reads your CloudWatch Logs for many people, across accounts. What it must prevent:

ThreatMitigation
A user reads log groups no one granted themTwo independent layers: grants checked by the API on every call, and IAM credentials that a per-request session policy limits to the ARNs of the log groups the user may read. Logs Insights, FilterLogEvents, GetLogEvents and Live Tail are all evaluated by AWS against those credentials, so a query that names any other log group fails in AWS (see Permissions)
A user reads data masked by a data protection policyThe roles never have logs:Unmask, and session policies can't add it. Masked data stays masked for everyone, administrators included
A user reads someone else's query resultsEach execution belongs to the person who started it: only they (and admins) can see its status, read its rows or cancel it, and reading the rows checks the user's read on every log group of the query again. Results are never reused across people
A shared snapshot leaks dataSnapshots exist only when someone creates one on purpose (the portal warns that everyone signed in can read it), need the export permission, are audited, are part of Team and above, and expire (30 days by default, at most what an admin allows)
Someone shortens retention and destroys evidenceRetention changes need the Team plan, a connection role with Manage, and a grant with manage; the session for each change allows PutRetentionPolicy and DeleteRetentionPolicy on that one log group only; every change is audited with the old and new values; the local role can't change the retention of TraceHound's own log groups, the audit log among them
A user runs up the CloudWatch billPer-user daily scan budgets (Business), a daily limit of live tail minutes per person, one shared Live Tail session per set of log groups, per-user result reuse, usage per user and Region, and the cost shown on every query
Someone impersonates another userFederated sign-in; the API trusts only emails the user can't choose, and binds the bootstrap admin to an immutable ID (see Identity)
A stolen token is used from elsewhereOne-hour ID tokens, revocable 8-hour refresh tokens, optional IP allowlist checked on every call, the live tail stream included
The public live tail endpoint is abusedThe function URL has no IAM authorisation, but the function validates the person's sign-in token itself and applies the same licence, IP allowlist and permission checks as the API (Streaming live tail)
TraceHound is used to change its own stackThe roles users act through have no IAM, CloudFormation, DynamoDB or S3 permissions, and the local role is denied TraceHound's own artifacts bucket
Actions can't be tracedApplication audit log and query history with who ran what, plus SourceIdentity = <user email> on every assumed-role session, so CloudTrail in every account names the person behind each CloudWatch Logs call
Another account's role is assumed by a third party ("confused deputy")Spoke roles trust only the hub role's ARN and require an external ID and a source identity
Data leaves your organisationNo vendor service in the data path; the stack never contacts the vendor at runtime

Out of scope: a malicious administrator of the AWS account (they can change any IAM policy), and the security of the identity provider and of users' devices.

Controls

PrincipleControl
Strong identity on every requestEvery API route requires a Cognito ID token, validated by the API Gateway JWT authorizer (issuer and audience). The live tail function URL validates the same token itself. Admin routes check the role on each call.
Least privilegeEach component has its own role with resource-scoped permissions (IAM summary). Users act only through roles under the IAM path /tracehound/, narrowed per request to their grants.
Context-aware accessIP allowlist checked on every call (Business and above); outside it, every route except the sign-in check returns 403 ip_not_allowed.
Short-lived credentialsSTS sessions of 15 minutes per user, connection and session policy.
Strong authenticationYour identity provider's MFA applies. Cognito-only users must use TOTP MFA and passwords of at least 14 characters. User-existence errors are suppressed.
Cost guard railsPer-user daily scan budgets, a daily limit of live tail minutes, shared Live Tail sessions, per-user reuse.
Hardened portalStrict CSP, HSTS, X-Frame-Options: DENY, Referrer-Policy: no-referrer, restrictive Permissions-Policy, no source maps (Portal).
AuditabilityQuery history with the person, application audit log, API access logs, and CloudTrail in every account with the user's SourceIdentity.

Identity and authorization

The API trusts the Cognito ID token's signature, issuer and audience (checked by API Gateway, and by the live tail function itself), and then decides who the caller is from its claims. Roles, grants, history and the bootstrap admin are keyed on the email claim, so a user who could pick their own email could take over someone else's access. The API therefore accepts the email only when it is one the user can't choose; otherwise it refuses the caller with 403 forbidden on every route.

How the user signs inEmail trusted whenWhy
SAML: IAM Identity Center, Entra ID, OktaalwaysThe email is asserted by your directory and signed by the IdP. Map it from an attribute users can't edit (Identity providers).
OIDCemail_verified is trueMany OIDC IdPs let users or guests set their email. The stack maps the IdP's verification claim to Cognito's email_verified.
Cognito user pool useremail_verified is trueUsers are created by administrators and can't change their email.

The provider is read from the signed token, never from anything the client sends.

Bootstrap admin. BootstrapAdminEmail is always an admin, so it is bound to an immutable identity: the first trusted sign-in with that email records the caller's Cognito user ID, and from then on only that user is the bootstrap admin. Sign in as the bootstrap admin straight after deployment. Resetting the binding is described in the admin guide.

Permissions, grants and session policies

What a user can do is the intersection of two independent layers.

  1. The connection's IAM role sets the ceiling for everyone: the local role is limited by the stack parameters LocalAccessLevel (Read or Manage) and LogGroupPrefixes; a spoke role by AccessLevel and LogGroupPrefixes of its own stack. TraceHound can never do more in an account than its role allows, and the account owner controls that role.
  2. TraceHound grants decide what each user may do within that ceiling: a subject (user or group), a connection, Regions, log group name patterns and permissions (browse, read, export, manage). See the admin guide.

Grants are not only checked in code. For every request, the API calls sts:AssumeRole on the connection's role with:

  • SourceIdentity = the user's email. It can't be changed for the rest of the session, also in role chains, and it appears in CloudTrail for every CloudWatch Logs call in the target account;
  • a role session name th-<email>, also visible in CloudTrail;
  • a session policy (for non-admins) scoped to the call:
Used forAllows
The shared log group list, the collector, the Lambda function listlogs:DescribeLogGroups, cloudwatch:GetMetricData, lambda:ListFunctions (AWS has no resource-level permissions for these; TraceHound filters the results by the caller's grants)
One log group's detailsIts streams, subscription and metric filters, tags, data protection policy and anomaly detectors, on that log group's ARNs, and its CloudWatch metrics
Queries, searches, context, field lists, live taillogs:StartQuery, FilterLogEvents, GetLogEvents, DescribeLogStreams, GetLogGroupFields and StartLiveTail only on the ARNs of the log group patterns the user may read; reading, stopping and listing queries (which have no resource-level permissions)
A retention changelogs:PutRetentionPolicy and DeleteRetentionPolicy on the one log group being changed (administrators get this scope too)

If a user's grants are too many to fit the session policy size limit, the request is refused rather than widened. Grant patterns use the same matching as IAM (* any run of characters, ? one, case-sensitive), so what TraceHound shows and what AWS allows agree.

Your SCPs, permissions boundaries and resource policies still apply on top: TraceHound adds restrictions and never removes yours.

Streaming live tail

Browsers can't carry CloudWatch Logs Live Tail's event stream through API Gateway, so a separate Lambda function serves it through a Lambda function URL with response streaming (stack output LiveTailUrl).

  • The function URL has auth type NONE because browsers can't sign requests with IAM. The function itself validates the Cognito ID token on every request (signature against the user pool's keys, issuer, audience, token use and expiry), then applies the same checks as the API: trusted email, licence, IP allowlist, and read on every log group requested (names only, at most 10). A request that fails any check gets no data.
  • CORS allows only the portal's origin. Each stream ends after about 14 minutes and the browser reconnects, which repeats every check.
  • AWS calls use the same per-user credentials and session policy as the API, limited to the log groups the user may read.
  • Sharing between viewers. To limit Live Tail cost, people who tail the same log groups with the same filter share one Live Tail session: one stream runs it and writes the events to the shared cache, and the others read them from there. Each viewer's permissions are checked before they can join. The events are kept in the cache for about two minutes; DynamoDB removes expired items in the background, and expired items are never served.

IAM summary

RoleAssumed byPermissions
Hub role (<NamePrefix>-api-<Region>)The API, live tail and collector LambdasIts two DynamoDB tables; sts:AssumeRole, SetSourceIdentity and TagSession on roles under /tracehound/ only; read the account's licences in AWS License Manager; invoke the collector; its log groups; KMS only through DynamoDB. No CloudWatch Logs access of its own
Local role (/tracehound/<NamePrefix>-local-<Region>)The hub role, with a source identity (an email, or tracehound-collector)List log groups, metric filters and anomaly detectors, read CloudWatch metrics, list Lambda functions, read, stop and list queries (*, no resource-level permissions); on log groups of this account (or under LogGroupPrefixes): streams, subscription filters, tags, data protection policy, StartQuery, FilterLogEvents, GetLogEvents, GetLogGroupFields, StartLiveTail. Manage adds PutRetentionPolicy and DeleteRetentionPolicy. Explicit denies: log group actions outside this account, every S3 action on TraceHound's artifacts bucket, and retention changes of TraceHound's own log groups. Never logs:Unmask; no IAM, CloudFormation, DynamoDB or S3
Spoke role (/tracehound/<RoleName>, other accounts)The hub role, with the external ID and a source identityAs the local role, by its AccessLevel and LogGroupPrefixes (without the denies that name the hub's own resources)
Update notifier roleLambdaIts settings and audit items; publish to the update topic
Portal deployer roleLambdaDeploy the portal's Amplify app from one file in the artifacts bucket
Installer rolesECS (one-off task) and LambdaRun the installer task; read the account's licences; pull the image from the AWS Marketplace registry; write the code to the artifacts bucket

Data flows

FlowPathProtection
Sign-inBrowser ⇄ Cognito managed login ⇄ your IdPTLS; SAML signatures or OIDC; authorization code flow with PKCE
APIBrowser → API Gateway → LambdaTLS 1.2+; JWT on every call; CORS limited to the portal origin; throttled
Queries and eventsLambda → STS → CloudWatch Logs in any connected accountTLS; per-request, per-user scoped STS credentials with SourceIdentity
Results on screenCloudWatch Logs GetQueryResults → Lambda → browserTLS; the owner (or an admin) only, with read checked again; up to 10,000 rows
Live tailBrowser → live tail function URL → CloudWatch Logs Live TailTLS; the ID token validated by the function; per-user scoped STS credentials; CORS limited to the portal
Storage historyCollector → STS → CloudWatch Logs and CloudWatch metrics → DynamoDBTLS; a list-and-metrics-only session with the source identity tracehound-collector
LicenceInstaller and API → AWS License Manager in your accountTLS. A read of the account's licences for the product
Code installAWS Marketplace container registry → installer task → artifacts bucketImage pulled by digest; every file checked against a SHA-256 manifest; written to a private, TLS-only bucket

The shared cache

The log group list is slow and rate-limited to read, so TraceHound keeps it in a second DynamoDB table shared by every user: each Region's log groups (names, sizes and settings, no events), field names per log group, Lambda function names, the live tail session events (about two minutes) and the daily usage counters. Every read is filtered by the caller's grants exactly as a live call would be: field names only for callers with read on that log group. It never holds query results. It is reachable only by the hub role, encrypted like the main table, and can be rebuilt at any time.

What stays in your account

Everything: identities, connections and external IDs, grants, the query history with query text, storage history, saved queries, dashboards, snapshots (which contain result rows), usage figures and the audit log, as well as your logs and the query results, which stay in CloudWatch Logs. Cloud Certainty operates no service in the data path, and the stack never contacts the vendor at runtime. The AWS Marketplace container registry and the vendor's public bucket in us-east-1 (templates and the release manifest) are read-only distribution points used at install and upgrade, and, with UpdateChecks = Enabled, for a daily read of the public release manifest that sends nothing about you. Links to cloudcertainty.com in the portal are plain links, followed only when someone clicks them.

Encryption

  • In transit. TLS for every AWS API, the portal and the live tail stream (HTTPS only, with HSTS). The artifacts bucket denies non-TLS requests.
  • At rest. With KmsKeyArn, your customer managed key encrypts the DynamoDB tables, TraceHound's own CloudWatch log groups, the update topic and the artifacts bucket. Without it, AWS owned keys and SSE-S3 are used. Your log groups keep their own encryption.

Portal hardening

Amplify Hosting serves the portal with these headers:

  • A strict Content-Security-Policy: no inline or third-party script, frame-ancestors 'none', and AWS endpoints allowed only for sign-in, the API and the live tail function URL in the stack's Region.
  • Strict-Transport-Security, X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer, X-Frame-Options: DENY, and a Permissions-Policy that turns off camera, microphone, geolocation, payment and USB.

Charts are drawn in the browser; nothing is loaded from third parties. Tokens are kept in session storage, never in cookies; refresh tokens last 8 hours and can be revoked. CSV and TSV exports defuse spreadsheet formulas.

Audit

Every administrative change (roles, connections, grants, settings), every retention change (with the old and new values), every full result export, every collector run started by hand, every snapshot created or deleted, every saved query and dashboard change, and refused requests are written to the audit log with the actor, source IP and target. Admins read it in Administration › Audit log. Events are kept for the plan's audit days: 30 on Starter, 90 on Team, 365 on Business and 3,650 on Enterprise, and are also written to TraceHound's CloudWatch log groups. The query history keeps who ran every query, and what it scanned and cost, for 30 to 730 days by plan. In each account, CloudTrail shows the assumed-role session behind each call with sourceIdentity set to the user's email. Forward CloudTrail and the log groups to your SIEM.

Licensing

TraceHound reads its AWS Marketplace licence from AWS License Manager in your account: at install and in the API. Nothing about your usage or data is sent anywhere. Without a valid licence the installer stops and the API answers 402 licence_required. If License Manager is unreachable, or a licence that was active disappears (which bridges a move between plans), TraceHound keeps working for up to 72 hours from the last successful check. Your data stays in place either way.

Shared responsibility

  • Keep BootstrapAdminEmail on a protected identity and enforce MFA at your IdP. Grant Admin to a second person or group.
  • Keep connection roles at Read unless people should change retention through TraceHound, and set LogGroupPrefixes where TraceHound should read only some log groups of an account.
  • Grant groups rather than single users, with log group prefixes per team (/aws/lambda/payments-*).
  • Use data protection policies for sensitive fields in your logs; TraceHound never unmasks them.
  • Set a daily live tail minute limit and, on Business, daily scan budgets, and review Administration › Usage.
  • Treat snapshots and exports like copies of your logs: share only data everyone signed in may see, with short expiries.
  • On Business and above, set the IP allowlist to your corporate egress ranges if users should only connect from managed networks.
  • Forward CloudTrail and TraceHound's log groups to your SIEM.

Something unclear or missing? Email [email protected].

Tell us what you have. We'll make it better and cheaper.

Send a short description of your current setup. We will tell you what we would improve, what it would cost, and where the savings are. A considered reply from an engineer, not a sales sequence.

TELL US ABOUT YOUR SETUP →