TILLSHIELD · POSTURE

What your infrastructure looks like.

Beta

A connector reads one account’s settings on a schedule and keeps an inventory of what it finds: each resource, the settings that matter for security, when it first appeared, when it last changed and when it went. Connectors only read, and they read settings, never secret values. Start with your own TillDev workspace, then your AWS accounts, Google Cloud projects, Azure subscriptions, Cloudflare accounts and DigitalOcean teams. The clouds join through federation, so no cloud key is ever stored; Cloudflare and DigitalOcean, which have no federation, read with a read-only token sealed in TillSecrets. Manage connectors under Shield → Posture. Every sync judges each resource against a library of 96 checks, opens a finding for each one that fails, and notes what changed since the resource’s baseline. It sits beside TillDrill, which tests what your sites show from outside.

01Start

Connect this workspace

Under Shield → Posture, pick TillDev and connect. The first sync starts at once and usually takes seconds. From the CLI:

sh
tilldev shield posture add till --wait                       # read this workspace first
tilldev shield posture                                       # every connector and its state
tilldev shield posture show <connector-id>                   # recent syncs, and the identity to trust
tilldev shield posture sync <connector-id> --wait            # exits 1 when the sync fails
tilldev shield posture schedule <connector-id> hourly        # or daily, or off
tilldev shield posture resources --type till.forge_repo      # the inventory, 100 a page
tilldev shield posture resources show <resource-id>          # the settings last read
tilldev shield posture rm <connector-id>                     # asks first; --yes in scripts

The TillDev connector reads these, and nothing else:

TypeWhat is read
till.forge_repo
TillForge repository
Visibility, status, default branch, whether it is a fork or a mirror, the commit identity check, and the branch policy guarding the default branch: signed commits and their suite, linear history, required approvals, how many checks are required, force pushes, deletions and MFA to push.
till.secrets_token
TillSecrets service token
Its project and scope, whether it is pinned to one environment, how many keys it may read, whether it is bound to a host key or a workload identity, when it expires and the day it was last used. Revoked tokens are left out.
till.api_key
Workspace API key
Its scopes, the resources it is pinned to, how many addresses its allowlist holds, whether minting it took MFA, when it expires and when it was last used.
till.auth_app
TillAuth app
MFA enforcement, each sign-in method, self sign-up and the allowlist, how many redirect origins it accepts and whether any is plain http off loopback, SCIM, and whether it signs with its own key.
02Providers

How each provider is reached

ProviderAccessAccountAvailable
TillDevBuilt in; no credentialThis workspaceYes
AWSFederated; no stored keyAccount IDYes
Google CloudFederated; no stored keyProject IDYes
AzureFederated; no stored keySubscription IDYes
CloudflareA read-only API token, sealed in TillSecretsAccount IDYes
DigitalOceanA read-only API token, sealed in TillSecretsTeam UUIDYes

For a cloud account, TillDev signs a token that lasts five minutes, naming the connector as its subject, and the account exchanges it for read-only access through its own identity federation. You trust two values in the account: the issuer, https://tilldev.dev/oidc/connectors, and the subject the connector shows, workspace:<workspace-id>:connector:<connector-id>. Pin the subject, not only the issuer, so no other workspace’s connector can use the trust. The issuer publishes its discovery document at /oidc/connectors/.well-known/openid-configuration and its public keys at /oidc/connectors/jwks.json. Remove the trust in the account and the next sync can’t read.

A provider without federation keeps a read-only token. It is sealed with your workspace’s own key in TillSecrets, unsealed only for a sync that is running, and never returned by the API: you see its last four characters. Replace it any time; disconnecting destroys it. Every time a sync is given a token or a federation token is signed for it, TillSecrets records it in the audit log.

Read only
Each connector reads with the narrowest read-only access its provider offers, listed in its section below. TillDev never writes to a connected account, so grant nothing more.
03AWS

Connect an AWS account

An AWS connector holds no AWS key. Each sync assumes a role in your account for fifteen minutes, using a token TillDev signs for that connector alone. Adding the connector starts no sync: first the account has to trust it, in three steps, and Shield → Posture shows them with your account and connector filled in.

  1. In IAM, add an OpenID Connect identity provider with the URL https://tilldev.dev/oidc/connectors and the audience sts.amazonaws.com. One provider serves every connector in the account.
  2. Create a role, TillDevPosture unless you name another, with the trust policy below. It names this connector’s subject, so no other workspace and no other connector can assume it.
  3. Give the role the read policy below. It allows exactly the 21 calls a sync makes, all of them reads. The AWS managed SecurityAudit policy works too, and allows more.
sh
tilldev shield posture add aws --account 123456789012       # --role and --regions are optional
tilldev shield posture trust <connector-id> --out .          # writes trust.json and read.json
aws iam create-open-id-connect-provider --url https://tilldev.dev/oidc/connectors --client-id-list sts.amazonaws.com
aws iam create-role --role-name TillDevPosture --assume-role-policy-document file://trust.json
aws iam put-role-policy --role-name TillDevPosture --policy-name TillDevPostureRead --policy-document file://read.json
tilldev shield posture sync <connector-id> --wait
json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/tilldev.dev/oidc/connectors"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "tilldev.dev/oidc/connectors:aud": "sts.amazonaws.com",
          "tilldev.dev/oidc/connectors:sub": "workspace:<workspace-id>:connector:<connector-id>"
        }
      }
    }
  ]
}
json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "cloudtrail:DescribeTrails",
        "cloudtrail:GetTrailStatus",
        "ec2:DescribeInstances",
        "ec2:DescribeRegions",
        "ec2:DescribeSecurityGroups",
        "ec2:GetEbsEncryptionByDefault",
        "iam:GenerateCredentialReport",
        "iam:GetAccountPasswordPolicy",
        "iam:GetAccountSummary",
        "iam:GetCredentialReport",
        "iam:ListRoles",
        "rds:DescribeDBInstances",
        "s3:GetAccountPublicAccessBlock",
        "s3:GetBucketLocation",
        "s3:GetBucketLogging",
        "s3:GetBucketOwnershipControls",
        "s3:GetBucketPolicyStatus",
        "s3:GetBucketPublicAccessBlock",
        "s3:GetBucketVersioning",
        "s3:GetEncryptionConfiguration",
        "s3:ListAllMyBuckets"
      ],
      "Resource": "*"
    }
  ]
}

A sync reads every region enabled for the account unless the connector lists the regions to read. A region the role can’t read, for example because a service control policy blocks it, leaves the regional types partial and names the region; list only the regions you use and the next sync reads in full. A sync makes at most 10,000 AWS calls and retries when AWS slows it down. Only the commercial AWS partition can be connected; China and GovCloud can’t yet.

TypeWhat is read
aws.account
AWS account
Whether the root user has MFA and active access keys and when its password was last used, how many users, roles and MFA devices the account holds, the IAM password policy, and S3 Block Public Access for the account.
aws.iam_user
IAM user
From the IAM credential report: console access, MFA, the day the password was last used and changed, each access key with whether it is active, when it was rotated and the day it was last used, and active signing certificates.
aws.iam_role
IAM role
Its path, whether AWS manages it, its longest session, and who its trust policy lets in: services, identity providers, accounts (and how many are outside this one), and whether it trusts anyone, with or without a condition.
aws.s3_bucket
S3 bucket
Its region, Block Public Access, whether its policy makes it public, default encryption and whether that uses a KMS key, versioning and MFA delete, access logging and object ownership.
aws.region
AWS region
Each enabled region: whether new EBS volumes are encrypted by default, and whether a logging CloudTrail trail covers it.
aws.security_group
EC2 security group
Its VPC, every inbound rule with its protocol, ports and sources, the rules open to the internet, and whether all outbound traffic is allowed.
aws.ec2_instance
EC2 instance
State, type, image, network, public addresses, whether IMDSv2 is required, its instance profile and security groups. Terminated instances are left out.
aws.rds_instance
RDS instance
Engine and version, whether it is publicly accessible and its endpoint, storage encryption, IAM authentication, backup retention, deletion protection, Multi-AZ and minor version upgrades.
aws.cloudtrail_trail
CloudTrail trail
Whether it covers every region and global services, log file validation, KMS encryption, its bucket, whether it is an organization trail, and whether it is logging and delivering.

A setting the role isn’t allowed to read is listed under unread on that resource rather than guessed. When a sync can’t start a session it fails and says why: the account doesn’t trust the issuer yet, the role is missing or its trust policy names another connector, or the audience is wrong.

04Google Cloud

Connect a Google Cloud project

A Google Cloud connector holds no service account key. Each sync exchanges a token TillDev signs for that connector at Google’s security token service, through a workload identity pool you control, and reads with an access token that lasts 15 minutes. Adding the connector starts no sync: first the project has to trust it, and Shield → Posture shows the steps with your project and connector filled in.

  1. Choose the pool and provider names, and add the connector with the provider’s full name, starting //iam.googleapis.com/, as its audience. The connector then shows its subject.
  2. Create the workload identity pool and an OpenID Connect provider in it with the issuer https://tilldev.dev/oidc/connectors, the attribute mapping google.subject=assertion.sub, the allowed audiences left empty, and the attribute condition assertion.sub == '<subject>'. The condition names this connector, so no other workspace and no other connector can use the provider.
  3. Grant the connector’s principal read access on the project: a custom role with the 9 permissions a sync uses, all of them reads, or the predefined Security Reviewer and Viewer roles, which allow more.
sh
tilldev shield posture add gcp --account my-project-123 --audience //iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/tilldev/providers/tilldev
tilldev shield posture trust <connector-id>                  # the subject, principal and commands, filled in
gcloud iam workload-identity-pools create tilldev --project=123456 --location=global
gcloud iam workload-identity-pools providers create-oidc tilldev --project=123456 --location=global \
  --workload-identity-pool=tilldev --issuer-uri=https://tilldev.dev/oidc/connectors \
  --attribute-mapping=google.subject=assertion.sub --attribute-condition="assertion.sub == 'workspace:<workspace-id>:connector:<connector-id>'"
gcloud iam roles create tillDevPostureRead --project=my-project-123 --title="TillDev posture read" \
  --permissions=cloudsql.instances.list,compute.firewalls.list,compute.instances.list,iam.serviceAccountKeys.list,iam.serviceAccounts.list,resourcemanager.projects.get,resourcemanager.projects.getIamPolicy,storage.buckets.getIamPolicy,storage.buckets.list
gcloud projects add-iam-policy-binding my-project-123 --role=projects/my-project-123/roles/tillDevPostureRead \
  --member="principal://iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/tilldev/subject/workspace:<workspace-id>:connector:<connector-id>"
tilldev shield posture sync <connector-id> --wait

To read as a service account instead, name it on the connector, grant it the read role, and grant the connector’s principal Workload Identity User on that service account. The IAM Service Account Credentials API must be on in the project that owns it. An API that is off in the project, such as Cloud SQL, leaves its type empty and is listed on the project rather than failing the sync. A zone Google Cloud can’t reach leaves instances partial and names it. A sync makes at most 10,000 Google Cloud calls.

TypeWhat is read
gcp.project
Google Cloud project
Its number, state and parent, and from its IAM policy: roles granted to anyone on the internet, owners and editors, personal Gmail accounts, service accounts holding owner or editor, who outside the service accounts may impersonate any of them, conditional bindings, data access audit logging, and the APIs that are turned off.
gcp.service_account
Service account
Whether it is disabled, whether it is a default Compute Engine or App Engine account, how many user-managed keys are active and the day the oldest was created.
gcp.storage_bucket
Cloud Storage bucket
Location and class, uniform bucket-level access, public access prevention, roles its IAM policy grants to anyone on the internet, versioning, access logging, a customer-managed key, and retention with its lock.
gcp.firewall_rule
VPC firewall rule
Its network, direction, priority and whether it is disabled, source and destination ranges, allowed and denied protocols and ports, its targets, whether it lets the internet in, and firewall logging.
gcp.compute_instance
Compute Engine instance
Zone, status and machine type, external addresses, its service accounts and whether one is the default with full API access, Shielded VM and Confidential VM, OS Login, the serial port, blocked project SSH keys, IP forwarding and deletion protection.
gcp.sql_instance
Cloud SQL instance
Engine and version, public IP and private network, authorized networks and whether any is the whole internet, the SSL mode, backups and point-in-time recovery, high availability, deletion protection and database flags.

When the exchange is refused the sync fails and says why: the pool or provider doesn’t exist or is disabled, the attribute condition names another connector, the provider limits its audiences, or its issuer isn’t TillDev’s.

05Azure

Connect an Azure subscription

An Azure connector holds no client secret. Each sync signs in to Microsoft Entra ID as an app registration in your tenant, using a token TillDev signs for that connector alone as the app’s federated credential, and reads with the Reader role. Adding the connector starts no sync: first the app has to trust it, and Shield → Posture shows the steps with your tenant, app and subscription filled in.

  1. Register an app in your tenant, or use one you have, and make sure it has a service principal. One app can serve every connector; add the connector with the tenant ID and the app’s client ID.
  2. Add a federated credential to the app with the issuer https://tilldev.dev/oidc/connectors, the audience api://AzureADTokenExchange and the subject the connector shows. The subject names this connector, so no other workspace can sign in as the app.
  3. Assign the app the built-in Reader role on the subscription. Reader reads settings and never the data in storage, databases or vaults.
sh
az login --tenant <tenant-id>
az ad app create --display-name "TillDev posture"            # note its appId, the client ID
az ad sp create --id <client-id>
tilldev shield posture add azure --account 00000000-0000-0000-0000-000000000000 --tenant-id <tenant-id> --client-id <client-id>
tilldev shield posture trust <connector-id> --out .          # writes credential.json
az ad app federated-credential create --id <client-id> --parameters credential.json
az role assignment create --assignee <client-id> --role Reader --scope /subscriptions/00000000-0000-0000-0000-000000000000
tilldev shield posture sync <connector-id> --wait
json
{
  "name": "tilldev-<connector-id>",
  "issuer": "https://tilldev.dev/oidc/connectors",
  "subject": "workspace:<workspace-id>:connector:<connector-id>",
  "audiences": [
    "api://AzureADTokenExchange"
  ],
  "description": "TillDev posture connector"
}

A sync makes 12 kinds of Azure Resource Manager read, each pinned to an API version, and follows the next page only on Resource Manager. It checks the subscription belongs to the connector’s tenant before reading anything else. A resource provider that isn’t registered leaves its type empty and is listed on the subscription rather than failing the sync. A sync makes at most 10,000 Azure calls and waits as long as Azure asks when it slows down. Only the global Azure cloud can be connected for now.

TypeWhat is read
azure.subscription
Azure subscription
Its state and tenant, who holds Owner, Contributor and User Access Administrator directly on it and how many owners are service principals, custom roles that allow every action, each Defender for Cloud plan and the ones on the free tier, whether the activity log is exported, and resource providers that aren’t registered.
azure.storage_account
Storage account
Kind and SKU, anonymous blob access, HTTPS only, minimum TLS, shared key access, public network access and the network default, a customer-managed key, infrastructure encryption and cross-tenant replication.
azure.network_security_group
Network security group
Every inbound rule with its priority, access, protocol, sources and ports, the rules that let the internet in, and how many subnets and interfaces it guards.
azure.virtual_machine
Virtual machine
Size and OS, whether Linux allows password sign-in, public addresses, an interface with no security group, Trusted Launch with secure boot and vTPM, encryption at host, managed identity and boot diagnostics.
azure.sql_server
Azure SQL server
Version, public network access, minimum TLS, Microsoft Entra-only authentication, firewall rules and whether one allows all of Azure or the whole internet, and auditing.
azure.key_vault
Key vault
SKU, soft delete and its days, purge protection, RBAC or access policies, public network access and the network default.

When sign-in is refused the sync fails and says why: no federated credential trusts the issuer or names this connector, the audience is wrong, the app or the tenant can’t be found, or the app is disabled.

06Cloudflare

Connect a Cloudflare account

Cloudflare has no identity federation for API access, so a Cloudflare connector reads with a read-only token you create. Create it as an Account API token on the account, or as a user token limited to it, at https://dash.cloudflare.com/profile/api-tokens, with only the permissions below. The token is sealed in TillSecrets as it arrives, shown only by its last four characters, and unsealed only for a sync that is running. The first sync starts as soon as the connector is added.

ScopePermissionWhat it lets a sync read
AccountAccount Settings · Readthe account, its two-factor rule and its members
AccountAccount API Tokens · Readthe account’s API tokens
AccountWorkers R2 Storage · ReadR2 buckets and their public access
ZoneZone · Readthe zones
ZoneZone Settings · Readeach zone’s TLS and security settings
ZoneDNS · ReadDNSSEC and records that aren’t proxied
sh
printf %s "$CF_TOKEN" | tilldev shield posture add cloudflare --account <account-id> --token-stdin --wait
tilldev shield posture trust <connector-id>                   # the permissions, for a replacement token
printf %s "$NEW_TOKEN" | tilldev shield posture token <connector-id> --token-stdin

Every call goes to the Cloudflare API with the token and nothing else. A sync first reads the account and stops if the token answers for a different one. Leave out a permission for what you don’t use and that type reads as partial, naming the permission it needs; a zone whose settings or DNS can’t be read keeps its name and lists what wasn’t read. A token Cloudflare no longer accepts fails the sync and says to replace it. Give the token read permissions only: TillDev never writes, and a token that can’t write can’t be misused if it ever leaks.

TypeWhat is read
cloudflare.account
Cloudflare account
Its type, whether it enforces two-factor sign-in, how many members it has and how many are invited but not yet joined, super administrators, and members without two-factor.
cloudflare.member
Account member
Each member by email: status, roles and permission groups, whether they are a super administrator, and two-factor.
cloudflare.api_token
Account API token
The account’s own API tokens: status, permission groups, whether any can write, whether a policy covers every resource, IP limits, when it was issued, when it expires and the day it was last used.
cloudflare.zone
Zone
Status, plan and whether it is paused, SSL mode, Always Use HTTPS, minimum TLS, TLS 1.3, Automatic HTTPS Rewrites, HSTS and whether it lasts a year, security level, development mode, DNSSEC, and how many A and AAAA records aren’t proxied, with a few by name.
cloudflare.r2_bucket
R2 bucket
Location, jurisdiction and storage class, whether its public r2.dev address is on, its custom domains with their minimum TLS, and whether it is public at all.
07DigitalOcean

Connect a DigitalOcean team

DigitalOcean has no identity federation for its API either, so a DigitalOcean connector reads with a personal access token with custom scopes. Make it in the team you connect, at https://cloud.digitalocean.com/account/api/tokens, with only the read scopes below, and add the connector with the team’s UUID. The token is sealed in TillSecrets as it arrives, like a Cloudflare token, and the first sync starts at once.

ScopeWhat it lets a sync read
account:readthe team the token belongs to
ssh_key:readthe SSH keys new Droplets can be given
droplet:readDroplets, their addresses, backups and monitoring
firewall:readcloud firewalls and what they let in
database:readmanaged databases and their trusted sources
kubernetes:readKubernetes clusters and their upgrades
load_balancer:readload balancers and their forwarding rules
sh
printf %s "$DO_TOKEN" | tilldev shield posture add digitalocean --account <team-uuid> --token-stdin --wait
tilldev shield posture trust <connector-id>                   # the scopes, for a replacement token

A sync first reads the account and stops if the token belongs to another team. A scope left out makes its type partial and names the scope; without firewall:read Droplets are still read, but which firewalls guard them is left blank rather than guessed. Spaces buckets aren’t read: the DigitalOcean API can’t list them with a token.

TypeWhat is read
digitalocean.team
DigitalOcean team
The team’s name and status, whether its email is verified, its Droplet limit and how many SSH keys new Droplets can be given.
digitalocean.droplet
Droplet
Status, size, image and region, public IPv4 and IPv6 addresses, backups, the monitoring agent, VPC, the cloud firewalls that guard it by id or tag, whether none do, and tags.
digitalocean.firewall
Cloud firewall
Every inbound rule with its protocol, ports and addresses, the rules open to the internet, whether outbound traffic may go anywhere, and how many Droplets and tags it covers.
digitalocean.database
Managed database
Engine, version, status and nodes, SSL, private network, its trusted sources and whether any address can connect, which is the case when there are none.
digitalocean.kubernetes_cluster
Kubernetes cluster
Version, status, auto-upgrade and surge upgrade, high availability, VPC, the control plane firewall and the addresses it allows, node pools and nodes, and the registry.
digitalocean.load_balancer
Load balancer
Status and address, its entry protocols and ports, plain HTTP and whether it redirects to HTTPS, TLS passthrough, its firewall and how many Droplets it serves.
08Syncs

What a sync changes

  • A resource seen for the first time is added.
  • A resource whose name, region or settings differ from the last read is marked changed.
  • A resource is removed only when the sync read its whole type and didn’t find it. A type that couldn’t be read in full, because the provider refused or a cap was reached, keeps what it had, and the sync ends as partial with the reason.
  • A removed resource that appears again comes back, with its history.

A sync that fails changes nothing and says why in words, such as a role that can no longer be assumed. Removed resources stay visible under Removed for 30 days; sync history is kept for 90 days. Disconnecting a connector drops its inventory, and its audit trail stays.

09Checks

Checks and findings

After each sync, every resource is judged against the checks for its type. A check that fails opens a finding that names the fields that failed, such as internet_admin_ports = 22. A finding stays open while the check fails and resolves on its own when a later full read shows it fixed, when the resource leaves the inventory, or when the check is turned off. A check that needs a field the sync couldn’t read, because the provider didn’t answer or a permission was left out, is counted as not run: it never passes and never closes a finding. Open a resource in the inventory to see how every check judges it.

sh
tilldev shield posture findings                              # failing checks, worst first
tilldev shield posture finding <finding-id>                  # the evidence, the fix, and any exception
tilldev shield posture checks --category network             # the library: level, mode and open count
tilldev shield posture check posture-aws-s3-logging off      # or alert, report, default
tilldev shield posture except <check-id> --resource <resource-id> --reason "…" --days 90
tilldev shield posture exceptions                            # --ended adds revoked and expired ones
tilldev shield posture drift                                 # what changed since each baseline
tilldev shield posture baseline <resource-id>                # or --connector <id>

The library, by category:

CheckLevelApplies to
S3 Block Public Access is not fully on for the account
Data exposure · posture-aws-account-public-access
highAWS account
An S3 bucket still honours object ACLs
Data exposure · posture-aws-s3-acls
lowS3 bucket
An S3 bucket does not block public access itself
Data exposure · posture-aws-s3-public-access-block
mediumS3 bucket
An S3 bucket policy makes the bucket public
Data exposure · posture-aws-s3-public
criticalS3 bucket
A storage account allows anonymous blob access
Data exposure · posture-azure-storage-public-blob
highStorage account
An R2 bucket is public
Data exposure · posture-cf-r2-public
highR2 bucket
A Cloud Storage bucket is public
Data exposure · posture-gcp-bucket-public
criticalCloud Storage bucket
A Cloud Storage bucket uses object ACLs
Data exposure · posture-gcp-bucket-uniform
lowCloud Storage bucket
A project grants a role to everyone
Data exposure · posture-gcp-public-iam
criticalGoogle Cloud project
A repository is public
Data exposure · posture-till-repo-public
informationalTillForge repository
The IAM password policy is missing or allows short passwords
Identity · posture-aws-password-policy
mediumAWS account
An IAM role can be assumed by any AWS principal
Identity · posture-aws-role-open-trust
criticalIAM role
The root user has access keys
Identity · posture-aws-root-access-keys
criticalAWS account
The root user has no MFA
Identity · posture-aws-root-mfa
criticalAWS account
The root user signed in within the last 30 days
Identity · posture-aws-root-recent-use
mediumAWS account
An IAM user can sign in to the console without MFA
Identity · posture-aws-user-mfa
highIAM user
An IAM user has an access key older than 90 days
Identity · posture-aws-user-stale-keys
mediumIAM user
An IAM user has credentials unused for 90 days
Identity · posture-aws-user-unused-credentials
mediumIAM user
A storage account accepts shared key authorisation
Identity · posture-azure-storage-shared-key
lowStorage account
A custom role allows every action
Identity · posture-azure-wildcard-role
highAzure subscription
The Cloudflare account does not require 2FA
Identity · posture-cf-enforce-2fa
highCloudflare account
A Cloudflare member has no 2FA
Identity · posture-cf-member-2fa
highAccount member
A Cloudflare API token can write, never expires and works from anywhere
Identity · posture-cf-token-unrestricted
mediumAccount API token
A Cloudflare API token has not been used for 90 days
Identity · posture-cf-token-unused
lowAccount API token
Personal Gmail accounts hold project roles
Identity · posture-gcp-personal-accounts
mediumGoogle Cloud project
Users can impersonate every service account in the project
Identity · posture-gcp-project-impersonation
mediumGoogle Cloud project
A service account key is older than 90 days
Identity · posture-gcp-sa-key-age
mediumService account
A service account has user-managed keys
Identity · posture-gcp-sa-keys
lowService account
A service account holds Owner or Editor
Identity · posture-gcp-sa-primitive
highGoogle Cloud project
A binding grants cluster-admin
Identity · posture-k8s-cluster-admin-binding
highk8s.role_binding
A binding grants a role to anonymous or every signed-in user
Identity · posture-k8s-public-binding
criticalk8s.role_binding
A role grants every verb on every resource
Identity · posture-k8s-wildcard-role
highk8s.role
A workspace API key never expires and works from any address
Identity · posture-till-api-key-open
lowWorkspace API key
A TillAuth app has MFA turned off
Identity · posture-till-auth-mfa-off
mediumTillAuth app
A protected default branch still allows force pushes
Identity · posture-till-repo-force-push
mediumTillForge repository
A repository's default branch is unprotected
Identity · posture-till-repo-unprotected
mediumTillForge repository
A TillSecrets service token never expires
Identity · posture-till-secrets-token-no-expiry
lowTillSecrets service token
A TillSecrets service token is bound to nothing
Identity · posture-till-secrets-token-unbound
mediumTillSecrets service token
A default security group allows inbound traffic
Network · posture-aws-default-sg-rules
mediumEC2 security group
An RDS instance is publicly accessible
Network · posture-aws-rds-public
highRDS instance
An Azure SQL server allows every IPv4 address
Network · posture-azure-sql-open
criticalAzure SQL server
A storage account accepts traffic from all networks
Network · posture-azure-storage-network-open
mediumStorage account
A VM with a public address has a network interface without an NSG
Network · posture-azure-vm-no-nsg
mediumVirtual machine
A zone is not signed with DNSSEC
Network · posture-cf-zone-dnssec
lowZone
A zone has address records that bypass the proxy
Network · posture-cf-zone-unproxied
informationalZone
A managed database accepts connections from any address
Network · posture-do-db-open
criticalManaged database
A Droplet with a public address has no cloud firewall
Network · posture-do-droplet-no-firewall
highDroplet
A Kubernetes control plane accepts any address
Network · posture-do-k8s-cp-firewall
mediumKubernetes cluster
An instance forwards traffic for other addresses
Network · posture-gcp-ip-forwarding
lowCompute Engine instance
A Cloud SQL instance accepts connections from any address
Network · posture-gcp-sql-open
criticalCloud SQL instance
A Service opens a load balancer to every address
Network · posture-k8s-public-load-balancer
mediumk8s.service
A remote administration port is open to the internet
Network · posture-net-admin-open
highEC2 security group, Network security group, Cloud firewall, VPC firewall rule
Every port is open to the internet
Network · posture-net-all-ports-open
highEC2 security group, Network security group, Cloud firewall, VPC firewall rule
A database port is open to the internet
Network · posture-net-db-open
highEC2 security group, Network security group, Cloud firewall, VPC firewall rule
No CloudTrail trail logs an enabled region
Logging · posture-aws-cloudtrail-off
highAWS region
An S3 bucket has no access logging
Logging · posture-aws-s3-logging
lowS3 bucket
A CloudTrail trail is not logging
Logging · posture-aws-trail-stopped
highCloudTrail trail
A CloudTrail trail does not validate its log files
Logging · posture-aws-trail-validation
mediumCloudTrail trail
The activity log is not exported
Logging · posture-azure-activity-log
mediumAzure subscription
An Azure SQL server has auditing off
Logging · posture-azure-sql-auditing
mediumAzure SQL server
Data access audit logs are off for some services
Logging · posture-gcp-audit-logs
mediumGoogle Cloud project
New EBS volumes are not encrypted by default
Encryption · posture-aws-ebs-default-encryption
mediumAWS region
An RDS instance stores data unencrypted
Encryption · posture-aws-rds-unencrypted
highRDS instance
A CloudTrail trail is not encrypted with a KMS key
Encryption · posture-aws-trail-kms
lowCloudTrail trail
An Azure SQL server accepts TLS older than 1.2
Encryption · posture-azure-sql-tls
mediumAzure SQL server
A storage account accepts plain HTTP
Encryption · posture-azure-storage-https
mediumStorage account
A storage account accepts TLS older than 1.2
Encryption · posture-azure-storage-tls
mediumStorage account
A zone does not send HSTS
Encryption · posture-cf-zone-hsts
lowZone
A zone serves plain HTTP without redirecting
Encryption · posture-cf-zone-https
mediumZone
A zone accepts TLS older than 1.2
Encryption · posture-cf-zone-min-tls
mediumZone
A zone sends traffic to the origin without verified TLS
Encryption · posture-cf-zone-ssl
highZone
A managed database does not require TLS
Encryption · posture-do-db-ssl
mediumManaged database
A load balancer serves plain HTTP without redirecting
Encryption · posture-do-lb-plain-http
mediumLoad balancer
A Cloud SQL instance accepts unencrypted connections
Encryption · posture-gcp-sql-ssl
mediumCloud SQL instance
A TillAuth app redirects to a plain HTTP address
Encryption · posture-till-auth-http-redirect
highTillAuth app
An EC2 instance still answers IMDSv1
Compute · posture-aws-imdsv1
mediumEC2 instance
Some Microsoft Defender for Cloud plans are off
Compute · posture-azure-defender-off
lowAzure subscription
A Linux VM allows SSH password login
Compute · posture-azure-vm-password-login
mediumVirtual machine
A Kubernetes cluster does not upgrade patch releases
Compute · posture-do-k8s-auto-upgrade
lowKubernetes cluster
An instance runs as the default service account with full API access
Compute · posture-gcp-default-sa-full-access
highCompute Engine instance
An instance boots without Secure Boot
Compute · posture-gcp-secure-boot
lowCompute Engine instance
An instance accepts serial console connections
Compute · posture-gcp-serial-port
mediumCompute Engine instance
A container adds a capability that breaks isolation
Compute · posture-k8s-dangerous-capabilities
highk8s.workload
A workload shares the node's network, process or IPC namespace
Compute · posture-k8s-host-namespaces
highk8s.workload
A workload mounts a path from the node
Compute · posture-k8s-host-path
highk8s.workload
A container has no memory limit
Compute · posture-k8s-no-memory-limit
lowk8s.workload
A container can gain more privileges than it started with
Compute · posture-k8s-privilege-escalation
mediumk8s.workload
A container runs privileged
Compute · posture-k8s-privileged
highk8s.workload
A container may run as root
Compute · posture-k8s-run-as-root
mediumk8s.workload
A container image is not pinned to a version
Compute · posture-k8s-unpinned-image
lowk8s.workload
An RDS instance keeps under 7 days of backups
Backup · posture-aws-rds-backups
mediumRDS instance
An RDS instance can be deleted without a safeguard
Backup · posture-aws-rds-deletion-protection
lowRDS instance
An S3 bucket keeps no previous versions
Backup · posture-aws-s3-versioning
lowS3 bucket
A key vault has purge protection off
Backup · posture-azure-vault-purge
mediumKey vault
A Droplet has backups off
Backup · posture-do-droplet-backups
lowDroplet
A Cloud SQL instance has automated backups off
Backup · posture-gcp-sql-backups
mediumCloud SQL instance
10Modes

Modes and exceptions

Each check has a mode for the workspace:

  • Alert opens findings, and a sync that finds new failures sends one message naming them over the workspace’s security notifications: email, Slack or a signed webhook. Critical and high checks alert by default.
  • Report opens findings without alerting. Medium, low and informational checks report by default.
  • Off doesn’t evaluate the check, and its open findings resolve on the next sync.

An exception accepts a risk you have decided to keep: for one resource, or for every resource a check applies to. It needs a reason of up to 500 characters and ends after 1 to 365 days. While it is in force the check still runs, but its findings move from Open to Excepted and send no alert. Revoking it, or letting it end, puts them back. Changing a mode, adding an exception and revoking one each take an owner or admin and are in the audit log.

No benchmark is claimed
The checks are TillDev’s own. The library doesn’t claim to implement any published benchmark in full.
11Drift

Drift from a baseline

A resource’s first read is its baseline. When a later read differs in a setting that matters, the resource is marked drifted and listed under Drift with each changed field’s baseline and current value. Fields that change in normal use, such as sign-in times, status and versions, are left out, and a field the last read couldn’t see is never counted as a change. Accept the new settings as the baseline for one resource or for every drifted resource of a connector; whoever accepts it is recorded. The alert for new failures also says how many resources drifted in that sync.

12Limits

Limits

WhatLimit
Connectors25 a workspace, one per account
ScheduleDaily unless set hourly or off
A sync15 minutes, 50,000 resources and 10,000 provider calls
Syncs by handone connector once every 5 minutes; 20 a workspace an hour
Settings kept per resource16 KB
Exceptions500 in force a workspace, one per check and resource, at most 365 days each
Evidence per finding6 fields
Drift per resource50 changed fields listed
Baselines accepted60 times a workspace an hour

Connecting, changing and disconnecting take an owner or admin, and every change and every sync started by hand is in the audit log. The API is under /api/v1/shield/posture in the API reference.