import { type Result } from "@fjall/generator"; /** * The AWS-account/region context a deploy session's inline policy is scoped to. * `appName` is the deploy target; `partition` is derived via `partitionForRegion` * (re-exported from `buildSecretSession.js`). `region` and `accountId` are * deliberately NOT pinned into the policy's ARNs (see the packed-budget note on * `buildDeploySessionPolicy`) — they remain in the context so call sites keep * compiling and the caller's intent stays explicit. */ export interface DeploySessionContext { appName: string; region: string; accountId: string; partition: string; } /** * CloudFormation stack-mutation actions the caller's deploy session may perform * ONLY on the target app's own stacks (and the CDK bootstrap stack). Read/describe * actions are deliberately absent — synth performs `cloudformation:Describe*` and * change-set describes, which must stay allowed. */ export declare const CFN_MUTATION_ACTIONS: string[]; /** * ECR mutation actions the caller's deploy session may perform ONLY on the target * app's own repository (and the CDK asset repos). Layer uploads + PutImage are the * `docker push` path; the delete/policy actions are fenced off foreign repos. */ export declare const ECR_MUTATION_ACTIONS: string[]; /** * Build a defence-in-depth inline session policy that narrows a 1-hour deploy * session's DIRECT CloudFormation/ECR mutation to the target app's stack-name * prefix and ECR repository. The connected `FjallDeploy` role carries * `AdministratorAccess` and the OIDC subject encodes no resource narrowing, so * without this the session is an org-wide admin bearer credential. * * Three accepted v1 limitations (not app-confinement guarantees): * - Session policies do not propagate across `sts:AssumeRole`, so mutation via * the CDK deploy/cfn-exec roles is out of this policy's reach — the real * confinement is the Phase E `FjallDeployBoundary` permission boundary. * - The `stack/${appPascal}*` prefix wildcard over-grants a prefix-collision * sibling app (`Web` vs `WebApp`) — same low-risk, same-account class as the * ECR baseline-prefix note in `buildSecretSession.ts`. * - The `-cache` NotResource entry exempts `repository/-cache` from * the foreign-ECR Deny. CLI app creation reserves the `-cache` suffix * (`hasReservedAppNameSuffix`, `@fjall/util`); the webapp create route does * NOT yet enforce it (its pinned @fjall/util predates the helper — wiring * lands with the 2.26.x bump), so until then a webapp-created sibling app * could take that name. The residual beyond that is a pre-existing * customer-created repo of that name in the BYOC account — same accepted * class as `cdk-*`. * * DENY-list, not allow-list (deliberate). `fjall deploy` runs `cdk deploy`: the * caller's session performs synth lookups, asset uploads to the CDK asset bucket, * ECR pushes, `iam:PassRole` on the cfn-exec role, and `CreateStack`/`UpdateStack`. * CloudFormation then provisions all downstream resources under the pre-bootstrapped * cfn-exec role (AdministratorAccess), NOT under this session. An allow-list scoped * to "app stacks + ECR" would strip synth/asset/PassRole and break every deploy, so * we instead lead with `Allow * on *` (inherit the role's admin) and Deny only * FOREIGN CloudFormation-stack mutation and FOREIGN ECR mutation. * * Naming is coupled to `getApplicationStackName` (`../../types/operations.js`): * every one of an app's stacks is named `toPascalCase(appName) + stack`, so a * CloudFormation stack-ARN wildcard on the `toPascalCase(appName)` prefix covers * all eight `APPLICATION_STACKS` members — the two us-east-1 stacks included, * because the region segment is wildcarded (below). If that derivation ever * changes, this wildcard must change with it. The * ECR repo is `toKebab(appName)` (matching the ECR construct at * components/infrastructure/lib/app.ts) — never a re-implemented `.toLowerCase()`. * * The REGION segment of every ARN is wildcarded, NOT pinned from `ctx` — a * pinned region wrongly DENIED an app's own stacks in any second region, and * every pinned segment spends packed budget: STS compresses the policy into a * packed binary whose real ceiling sits near 1256 chars for this content class * (measured 2026-08-23 — see MAX_PACKED_SAFE_POLICY_CHARS), and the fully * pinned form crossed it for app names of 19+ chars, killing the mint with * PackedPolicyTooLarge. The ACCOUNT segment is asymmetric by design: * - CloudFormation entries wildcard it. CFN has no resource-based policies, so * cross-account stack mutation would require an `sts:AssumeRole` this policy * documentedly does not survive — pinning the account there restated what * the role boundary already enforces, for 12 chars apiece. * - ECR entries PIN it. ECR repositories carry resource-based policies: a * foreign account's repo policy (e.g. an `aws:PrincipalOrgID` grant on a * shared mirror) can admit this identity with NO AssumeRole, so an * account-wildcarded NotResource would exempt same-named foreign repos — * `cdk-*` would exempt every bootstrap asset repo in every account — from * the one Deny whose job is fencing foreign ECR mutation. The account pin * here is the load-bearing cross-account fence, worth 4 × 11 packed chars. * Statements carry no Sids for the same budget reason — they are * documentation, and this policy's documentation lives here. * * Fails closed when the serialised policy would exceed the packed-safe STS * session-policy budget. */ export declare function buildDeploySessionPolicy(ctx: DeploySessionContext): Result;