Skip to content

What MarshalCloud can touch, said plainly.Read-only to connect

What a cloud cost tool can read in your AWS account, what it can change, and what it never touches. Every claim describes what the product does today.

Read role

MarshalCloud reads

Draft

You approveWaits for you

Fix role acts

Revoke

How does the AWS access work?

Read-only first. Fixes need a second role you choose to install. You can read the role template line by line before you run it.

What can the read role do?

Connecting AWS creates one role in your own account, MarshalReadRole, through a CloudFormation stack you can read line by line before you run it. It can only be assumed with a secret External ID unique to your organization, so a leaked role name is not access. Its permissions are describe, list and get on metadata. It cannot create, modify, or delete anything in your account.

How do I revoke it?

Delete the stack whenever you like and our access ends instantly. If you connected with an access key instead of the stack, deleting the stack does nothing: disconnect the account here and delete that key in AWS.

What does the fix role add?

One-click fixes need a second, separate role: MarshalRemediateRole. Never installing it is a supported way to use MarshalCloud. Its permissions cover exactly the fixes we support, nothing wider, and our build checks that the two never drift apart. An action without a reviewed implementation cannot start.

Where does the guardrail live?

An action that is not in that catalog cannot be proposed, approved, or executed. The guardrail lives in code and in IAM, never in a prompt, and a test in our build fails if the permission list and the catalog ever disagree. You can switch off any kind of fix you never want proposed, in settings.

What happens to the keys you give us?

By default we never hold a key of yours. Anything you hand us is encrypted at rest and never shown back. In a cluster it is a Kubernetes agent with no cluster credentials.

Do you hold my AWS keys?

The default connection is a cross-account role plus a unique External ID. There are no long-lived AWS credentials for us to hold, rotate, or lose. If you paste access keys instead (the interface says why you should not), they are encrypted at rest before they are stored. Same for every credential you hand us: Datadog, Sentry and GitHub tokens, your own AI key. Our own service secrets live in a secrets store, never in the database.

Can anyone read a stored secret back?

No API returns a stored secret, to anyone, including you. Webhook URLs are shown with the host only, and a failed delivery logs the type of error, never the address. And if encryption is not configured, connection setup refuses to store a secret at all rather than store it unprotected.

What about Google Cloud and Kubernetes?

A Google Cloud connection keeps only the project and billing table identifiers. The cluster agent is read-only, runs inside your cluster, and pushes metrics out, so it gives us no credentials to your cluster. If you deploy to an EKS cluster through MarshalCloud, each approved deploy signs in with a short-lived token made from the AWS access you gave that deploy target.

How are your own logins protected?

Every MarshalCloud login can turn on two-step sign-in with an authenticator app, with one-time recovery codes. The Team page shows who has it on, so an auditor's question about MFA is answered from the list, not from memory.

What can MarshalCloud read, and never read?

Bills and settings. Not your customers' data. This is what the permissions allow, not a policy promise: the CloudFormation template lists every permission, and you read it before you install it.

What it reads, in full

Your billing export, from the S3 bucket the setup creates in your own account. Cost and pricing APIs, to price a fix at the rate you actually pay. The list of your resources: servers, disks, databases, load balancers, clusters. Utilization metrics such as CPU and network, to judge what is oversized. Account-level security settings: root MFA, audit logging, default disk encryption, public-access blocks. Your cloud's own record of recent changes (AWS CloudTrail, changes only, not reads): which person or role changed what, and the IP address it came from, so a cost or security change can be traced to who made it. Your web firewall's sample of recent requests (AWS WAF, the last three hours): MarshalCloud counts how many were allowed and blocked per rule and keeps only those counts, never the addresses or pages in the sample.

What it never reads, in full

The contents of your storage buckets: the read role can fetch objects from the billing export bucket only. Rows in your databases: MarshalCloud reads a table's settings, never the data inside it. Your application logs, your traffic beyond that firewall sample, or your customer records. Anything at all in write mode, until you install the separate fix role and approve a specific action.

Can MarshalCloud change something in my cloud?

Only after a person approves it. Propose, approve, execute, in that order, every time; there is no path around the gate. See approval-gated fixes.

1. Propose

MarshalCloud drafts the exact change, in plain language, with the dollar impact priced from your own bill and one sentence saying whether it can be undone. Then it stops.

2. Approve

A person with approval rights approves that specific action, and both names are recorded. For every fix the API refuses fully automatic execution. The one exception is a server stop schedule a person has already approved; a pause switch halts it at once. An admin can let an approved server stop schedule run on its own: MarshalCloud then stops and starts those servers on that schedule, checks for traffic, maintenance and open incidents before every stop, and records every stop and start. Actions marked irreversible never run without a person.

3. Execute

MarshalCloud re-checks live state before touching anything and records every step. Each plan states whether it is reversible, can only recreate an equivalent from a snapshot, or is irreversible before approval.

Can every fix be undone?

No, and every plan says which kind it is. The label comes from what the code can actually roll back, not from how safe an action feels. Reversible: You can undo this with one click. A rollback restores the same resource (stopping a server, changing a disk type, resizing an instance). Compensating: Undo recreates an equivalent - the original is gone. Deleting an unattached disk restores from the safety snapshot, so you get a new disk with the same data, and only while the 14-day snapshot lives. Irreversible: This cannot be undone. Releasing an IP address, deleting a backup, or buying a savings plan spends something MarshalCloud cannot put back. These sit behind an extra confirmation.

What can MarshalCloud write to your code, or open on your servers?

Pull requests on its own branch, and a recorded shell on a server it built. A person on your team merges every pull request and approves every production or staging shell.

What can the GitHub App write?

Only to a repository you have turned on in MarshalCloud, and only on a branch it creates, named marshal/ followed by what it is for; never your default branch. Each access token is made for one repository with only the permissions that step needs (read the code, or open a pull request), kept in memory until it expires, and never stored. It writes workflow files only if your installation granted that permission. If the installation is suspended, every write stops.

Does it ever merge?

No. MarshalCloud's GitHub client has no merge or approve call. A fix arrives as a draft pull request; the pull request that adds a deploy pipeline opens as an ordinary one. Either way a person on your team reviews and merges it.

How is the approval recorded?

When a fix pull request merges, MarshalCloud reads its reviews from GitHub and records who approved the commit that merged. An approval of an earlier commit is marked as that, a merge with no approving review is recorded as that, and a review it could not read is unknown, never approved. A merged fix is not called fixed until the next scan no longer finds the problem; after seven days not live, it says so.

What can the AI analyst do with code?

It can draft a fix pull request for a person with the SRE role or above, and the pull request opens as a draft. It has no tool that merges, approves or deploys anything.

Who can open a shell, and on what?

Only on a server or container MarshalCloud built itself, inside the environment the request names, through AWS Session Manager or ECS Exec and the build role you installed with its Operate switch on. The person asking needs the SRE role and has to say why. Shell access is on the Scaled plan.

Who approves it, and how is it signed in?

On staging and production, a second person with the InfoSec role approves it in the app: nobody approves their own request, and an approval from Slack is refused. A development environment approves its own requests. An approval lapses after 30 minutes if nobody connects. Your browser's sign-in travels in the connection handshake, never in the address, and the plan, your role and the server's readiness are checked again when you connect. Production also asks you to type a confirmation sentence, and raises an alert when the session opens.

Where is it recorded?

In your own AWS account. MarshalCloud refuses to open a session unless session recording is on there (Session Manager logging to CloudWatch Logs or S3 with encryption, or the cluster's ECS Exec logging). Each request, approval, refusal, start and end is also recorded with the person and the time.

How does it end, and what is not finished?

A session closes after 10 minutes with no typing and at a time box, 30 minutes in production and 60 elsewhere, by default; your owner can change both and turn shell access off for any kind of environment. Not finished: nobody in your organization can yet end another person's open session from the app (it ends when they close it, at either limit, or when MarshalCloud staff end it at your request), and a role or plan change stops new connections, not a session already open. The host agent you install on your own servers has no shell at all: it sends facts and metrics out and runs a fixed set of read commands.

What we keep, and how it goes away

Billing lines, inventory metadata and the results of our checks, stored per tenant. Delete the stack and our access ends; ask us and we erase your organization.

What do you store?

Billing lines from your cost export, so we can price things against what you actually paid. Inventory metadata: the resources you run, their configuration, and how they connect to each other. Results of the checks we run for you: uptime and certificate checks, security posture findings, budgets and alerts.

How is my data kept apart?

Every organization's data is stored and queried per tenant, and every request for a record is checked against your organization, so one customer can never open another's.

How do I end access?

Disconnect a cloud account, or delete the CloudFormation stack in your own account, and our access to it ends immediately. An account connected with an access key instead of the stack needs both: disconnect it here and delete that key in AWS.

How do I delete everything?

Ask us to delete your organization and we erase it: the records in our database and your billing rows in our warehouse, along with cached copies. The deletion reports back whether each of those stores was actually cleared, so nobody assumes erasure finished when half of it did. Deleting an organization is irreversible, so it takes an exact-name confirmation to run.

What can the AI analyst do?

MarshalCloud's analyst answers questions about your own data with read-only evidence tools. It can investigate and prepare. It cannot approve or execute a change; those gates live outside the model.

No approval or execution tools, structurally

The model may prepare an allowlisted proposal for an authorized user, but it cannot approve it or execute a cloud change. A person controls both gates.

Tool results are data, never instructions

Text pulled from your cloud is treated as data, never as an instruction to follow. The guardrails live outside the model, not inside a prompt.

Playbooks are allowlisted

The playbooks the analyst can follow are a fixed list we review. Instructions your team writes shape wording and emphasis, never what the analyst may do.

Capped by organization

Each organization has a daily AI budget. Past it, the analyst declines rather than running up a bill.

The things we have not finished yet

We would rather you heard these from us. The technical controls on the other tabs are built today.

What exactly is still in progress?

Formal SOC 2 work is in progress, not done, and some security controls are still being finished before general availability. We hold no SOC 2 or ISO 27001 report today. Ask us and we will walk your security team through the detail under NDA.

How do we ask for that walkthrough?

Write to hello@marshalcloud.com with "Security review" in the subject and we will walk your team through our written security analysis line by line.

The read role, exactly as shipped

These are the two policies the connect template creates in your account for MarshalReadRole. The Ref and Fn:: entries are filled in by CloudFormation when you create the stack: your External ID, your billing bucket.

Who may use the role
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": {
          "Ref": "MarshalRoleArn"
        }
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": {
            "Ref": "ExternalId"
          }
        }
      }
    }
  ]
}
What it may do: 205 allowed actions
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "CostData",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        {
          "Fn::GetAtt": [
            "CostExportBucket",
            "Arn"
          ]
        },
        {
          "Fn::Sub": "${CostExportBucket.Arn}/*"
        }
      ]
    },
    {
      "Sid": "CostApis",
      "Effect": "Allow",
      "Action": [
        "ce:Get*",
        "compute-optimizer:Get*",
        "cost-optimization-hub:ListRecommendations",
        "cost-optimization-hub:GetRecommendation",
        "cost-optimization-hub:ListEnrollmentStatuses",
        "trustedadvisor:ListRecommendations",
        "trustedadvisor:GetRecommendation",
        "servicequotas:ListServiceQuotas",
        "pricing:Get*",
        "savingsplans:DescribeSavingsPlans",
        "budgets:ViewBudget",
        "budgets:DescribeBudgets",
        "rds:DescribeReservedDBInstances",
        "elasticache:DescribeReservedCacheNodes",
        "invoicing:ListInvoiceSummaries",
        "freetier:GetFreeTierUsage"
      ],
      "Resource": "*"
    },
    {
      "Sid": "StorageCleanupRead",
      "Effect": "Allow",
      "Action": [
        "s3:GetLifecycleConfiguration",
        "s3:ListBucketMultipartUploads",
        "s3:ListMultipartUploadParts"
      ],
      "Resource": "*"
    },
    {
      "Sid": "BucketOpennessRead",
      "Effect": "Allow",
      "Action": [
        "s3:GetBucketAcl",
        "s3:GetBucketPolicy",
        "s3:GetBucketPolicyStatus",
        "s3:GetBucketEncryption"
      ],
      "Resource": "*"
    },
    {
      "Sid": "InventoryRead",
      "Effect": "Allow",
      "Action": [
        "ec2:Describe*",
        "elasticloadbalancing:Describe*",
        "emr:ListClusters",
        "opensearch:ListDomainNames",
        "opensearch:DescribeDomain",
        "rds:Describe*",
        "lambda:List*",
        "lambda:GetFunctionConcurrency",
        "lambda:GetAccountSettings",
        "s3:ListAllMyBuckets",
        "s3:GetBucketLocation",
        "s3:GetBucketPublicAccessBlock",
        "s3:GetBucketTagging",
        "eks:List*",
        "eks:Describe*",
        "ecs:List*",
        "ecs:Describe*",
        "cloudfront:ListDistributions",
        "dynamodb:ListTables",
        "dynamodb:DescribeTable",
        "dynamodb:DescribeContinuousBackups",
        "s3:GetBucketVersioning",
        "route53:ListHealthChecks",
        "route53:GetHealthCheckStatus",
        "es:ListDomainNames",
        "es:DescribeDomain",
        "fsx:DescribeFileSystems",
        "kafka:ListClustersV2",
        "sagemaker:ListEndpoints",
        "sagemaker:ListNotebookInstances",
        "glue:ListJobs",
        "elasticmapreduce:ListClusters",
        "elasticbeanstalk:DescribeEnvironments",
        "workspaces:DescribeWorkspaces",
        "directconnect:DescribeConnections",
        "transfer:ListServers",
        "ecr:DescribeRepositories",
        "codepipeline:ListPipelines",
        "backup:List*",
        "backup:ListBackupPlans",
        "backup:ListBackupSelections",
        "backup:GetBackupPlan",
        "backup:GetBackupSelection",
        "backup:ListBackupVaults",
        "backup:ListRecoveryPointsByBackupVault",
        "backup:ListRecoveryPointsByResource",
        "rds:DescribeDBSnapshots",
        "rds:DescribeDBClusterSnapshots",
        "sqs:ListQueues",
        "sqs:GetQueueAttributes",
        "sns:ListTopics",
        "elasticache:DescribeCacheClusters",
        "elasticache:ListAllowedNodeTypeModifications",
        "apigateway:GET",
        "elasticfilesystem:DescribeFileSystems",
        "route53:ListHostedZones",
        "redshift:DescribeClusters",
        "kinesis:ListStreams",
        "states:ListStateMachines",
        "cloudwatch:GetMetricData",
        "cloudwatch:ListMetrics",
        "config:DescribeConfigurationRecorders",
        "config:DescribeConfigurationRecorderStatus",
        "config:DescribeDeliveryChannels",
        "cloudwatch:DescribeAlarms",
        "lightsail:GetInstances",
        "cloudtrail:LookupEvents",
        "iam:ListAccountAliases",
        "lightsail:GetInstances",
        "autoscaling:DescribeAutoScalingGroups",
        "autoscaling:DescribeLaunchConfigurations",
        "acm:ListCertificates",
        "acm:DescribeCertificate",
        "secretsmanager:ListSecrets",
        "organizations:ListAccounts",
        "organizations:DescribeOrganization"
      ],
      "Resource": "*"
    },
    {
      "Sid": "SecurityPostureRead",
      "Effect": "Allow",
      "Action": [
        "iam:GetAccountSummary",
        "iam:GetCredentialReport",
        "iam:GenerateCredentialReport",
        "iam:GetAccountPasswordPolicy",
        "iam:ListUsers",
        "iam:ListMFADevices",
        "iam:ListAccessKeys",
        "iam:ListAttachedUserPolicies",
        "iam:ListUserPolicies",
        "iam:GetUserPolicy",
        "iam:ListGroups",
        "iam:ListGroupsForUser",
        "iam:ListGroupPolicies",
        "iam:GetGroupPolicy",
        "iam:ListAttachedGroupPolicies",
        "iam:GetPolicy",
        "iam:GetPolicyVersion",
        "cloudtrail:DescribeTrails",
        "cloudtrail:GetTrailStatus",
        "cloudtrail:GetEventSelectors",
        "s3:GetEncryptionConfiguration",
        "s3:GetBucketLogging",
        "kms:ListKeys",
        "kms:DescribeKey",
        "kms:GetKeyRotationStatus",
        "kms:ListAliases",
        "ec2:GetEbsEncryptionByDefault",
        "s3:GetAccountPublicAccessBlock",
        "rds:DescribeDBSnapshots",
        "rds:DescribeDBSnapshotAttributes",
        "iam:ListAccountAliases"
      ],
      "Resource": "*"
    },
    {
      "Sid": "AlarmCoverageRead",
      "Effect": "Allow",
      "Action": [
        "logs:DescribeLogGroups",
        "logs:DescribeMetricFilters",
        "cloudwatch:DescribeAlarms",
        "sns:ListSubscriptionsByTopic"
      ],
      "Resource": "*"
    },
    {
      "Sid": "ProviderScannerRead",
      "Effect": "Allow",
      "Action": [
        "inspector2:BatchGetAccountStatus",
        "inspector2:ListFindings",
        "inspector2:ListCoverage",
        "securityhub:DescribeHub",
        "securityhub:GetFindings",
        "guardduty:ListDetectors",
        "guardduty:GetDetector",
        "guardduty:ListFindings",
        "guardduty:GetFindings",
        "guardduty:ListCoverage"
      ],
      "Resource": "*"
    },
    {
      "Sid": "WafRead",
      "Effect": "Allow",
      "Action": [
        "wafv2:ListWebACLs",
        "wafv2:GetWebACL",
        "wafv2:GetSampledRequests",
        "wafv2:ListResourcesForWebACL"
      ],
      "Resource": "*"
    },
    {
      "Sid": "AiWorkloadRead",
      "Effect": "Allow",
      "Action": [
        "bedrock:ListCustomModels",
        "bedrock:ListProvisionedModelThroughputs",
        "bedrock:ListGuardrails",
        "bedrock:GetModelInvocationLoggingConfiguration"
      ],
      "Resource": "*"
    },
    {
      "Sid": "DataSecurityRead",
      "Effect": "Allow",
      "Action": [
        "macie2:GetMacieSession",
        "macie2:ListFindings",
        "macie2:GetFindings"
      ],
      "Resource": "*"
    },
    {
      "Sid": "IamInsightsRead",
      "Effect": "Allow",
      "Action": [
        "access-analyzer:ListAnalyzers",
        "access-analyzer:ListFindings",
        "iam:GenerateServiceLastAccessedDetails",
        "iam:GetServiceLastAccessedDetails"
      ],
      "Resource": "*"
    },
    {
      "Sid": "DatabaseInsightsRead",
      "Effect": "Allow",
      "Action": [
        "pi:DescribeDimensionKeys",
        "elasticache:DescribeReplicationGroups",
        "elasticache:DescribeCacheParameters",
        "elasticache:DescribeEvents",
        "dynamodb:DescribeTimeToLive",
        "dynamodb:ListTagsOfResource",
        "redshift:DescribeClusterParameters",
        "redshift:DescribeClusterSnapshots",
        "redshift:DescribeLoggingStatus",
        "es:DescribeDomainConfig"
      ],
      "Resource": "*"
    },
    {
      "Sid": "ServiceDetailRead",
      "Effect": "Allow",
      "Action": [
        "autoscaling:DescribePolicies",
        "autoscaling:DescribeScalingActivities",
        "lambda:GetPolicy",
        "ecr:DescribeImages",
        "ecr:GetLifecyclePolicy",
        "cloudfront:GetDistribution",
        "route53:GetHostedZone",
        "route53:ListResourceRecordSets",
        "elasticfilesystem:DescribeMountTargets",
        "elasticfilesystem:DescribeBackupPolicy",
        "elasticfilesystem:DescribeLifecycleConfiguration",
        "fsx:DescribeBackups",
        "sqs:ListDeadLetterSourceQueues",
        "sns:GetTopicAttributes",
        "kinesis:DescribeStreamSummary",
        "kinesis:ListShards",
        "kinesis:ListStreamConsumers",
        "kafka:DescribeClusterV2",
        "kafka:ListNodes",
        "elasticmapreduce:DescribeCluster",
        "elasticmapreduce:ListInstanceGroups",
        "elasticmapreduce:ListSteps",
        "glue:GetJob",
        "glue:GetJobRuns",
        "states:DescribeStateMachine",
        "states:ListExecutions",
        "secretsmanager:DescribeSecret",
        "iam:GetUser",
        "iam:GetAccessKeyLastUsed"
      ],
      "Resource": "*"
    },
    {
      "Sid": "DatabaseInsightsNoQueryText",
      "Effect": "Deny",
      "Action": [
        "pi:DescribeDimensionKeys",
        "pi:GetDimensionKeyDetails",
        "pi:GetResourceMetrics"
      ],
      "Resource": "*",
      "Condition": {
        "ForAnyValue:StringEquals": {
          "pi:Dimensions": [
            "db.sql.statement",
            "db.sql_tokenized.statement"
          ]
        }
      }
    },
    {
      "Sid": "HealthRead",
      "Effect": "Allow",
      "Action": [
        "health:DescribeEvents",
        "health:DescribeEventDetails",
        "health:DescribeAffectedEntities"
      ],
      "Resource": "*"
    },
    {
      "Sid": "IamGraphRead",
      "Effect": "Allow",
      "Action": [
        "iam:ListRoles",
        "iam:ListAttachedRolePolicies",
        "iam:ListRolePolicies",
        "iam:GetRolePolicy",
        "iam:ListInstanceProfiles",
        "iam:GetPolicy",
        "iam:GetPolicyVersion",
        "iam:GetRole"
      ],
      "Resource": "*"
    }
  ]
}

Found something? Tell us.

Report a vulnerability with the form or by email. A person reads every report.