thinkwork-cli 0.9.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +2 -2
  3. package/dist/cli.js +1187 -315
  4. package/dist/terraform/examples/greenfield/main.tf +325 -19
  5. package/dist/terraform/examples/greenfield/terraform.tfvars.example +14 -0
  6. package/dist/terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base +61 -0
  7. package/dist/terraform/modules/app/agentcore-code-interpreter/README.md +54 -0
  8. package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +197 -0
  9. package/dist/terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh +70 -0
  10. package/dist/terraform/modules/app/agentcore-flue/README.md +58 -0
  11. package/dist/terraform/modules/app/agentcore-flue/main.tf +322 -0
  12. package/dist/terraform/modules/app/agentcore-flue/outputs.tf +23 -0
  13. package/dist/terraform/modules/app/agentcore-flue/variables.tf +91 -0
  14. package/dist/terraform/modules/app/agentcore-memory/scripts/create_or_find_memory.sh +0 -0
  15. package/dist/terraform/modules/app/agentcore-runtime/main.tf +165 -0
  16. package/dist/terraform/modules/app/appsync-subscriptions/main.tf +4 -0
  17. package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +5 -0
  18. package/dist/terraform/modules/app/computer-runtime/README.md +15 -0
  19. package/dist/terraform/modules/app/computer-runtime/main.tf +406 -0
  20. package/dist/terraform/modules/app/computer-runtime/outputs.tf +75 -0
  21. package/dist/terraform/modules/app/computer-runtime/variables.tf +66 -0
  22. package/dist/terraform/modules/app/hindsight-memory/main.tf +6 -0
  23. package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +128 -0
  24. package/dist/terraform/modules/app/lambda-api/handlers.tf +1454 -43
  25. package/dist/terraform/modules/app/lambda-api/main.tf +221 -12
  26. package/dist/terraform/modules/app/lambda-api/mcp-oauth.tf +118 -0
  27. package/dist/terraform/modules/app/lambda-api/oauth-secrets.tf +49 -0
  28. package/dist/terraform/modules/app/lambda-api/outputs.tf +38 -0
  29. package/dist/terraform/modules/app/lambda-api/slack-app-secrets.tf +43 -0
  30. package/dist/terraform/modules/app/lambda-api/stripe-secrets.tf +53 -0
  31. package/dist/terraform/modules/app/lambda-api/variables.tf +349 -2
  32. package/dist/terraform/modules/app/lambda-api/workspace-events.tf +125 -0
  33. package/dist/terraform/modules/app/routines-stepfunctions/main.tf +453 -0
  34. package/dist/terraform/modules/app/sandbox-log-scrubber/README.md +66 -0
  35. package/dist/terraform/modules/app/sandbox-log-scrubber/main.tf +200 -0
  36. package/dist/terraform/modules/app/static-site/main.tf +146 -5
  37. package/dist/terraform/modules/app/www-dns/main.tf +118 -15
  38. package/dist/terraform/modules/app/www-dns/outputs.tf +10 -0
  39. package/dist/terraform/modules/app/www-dns/variables.tf +42 -0
  40. package/dist/terraform/modules/data/aurora-postgres/main.tf +164 -3
  41. package/dist/terraform/modules/data/aurora-postgres/outputs.tf +34 -0
  42. package/dist/terraform/modules/data/aurora-postgres/variables.tf +16 -0
  43. package/dist/terraform/modules/data/compliance-audit-bucket/README.md +145 -0
  44. package/dist/terraform/modules/data/compliance-audit-bucket/main.tf +573 -0
  45. package/dist/terraform/modules/data/compliance-audit-bucket/outputs.tf +43 -0
  46. package/dist/terraform/modules/data/compliance-audit-bucket/variables.tf +93 -0
  47. package/dist/terraform/modules/data/compliance-exports-bucket/main.tf +269 -0
  48. package/dist/terraform/modules/data/compliance-exports-bucket/outputs.tf +23 -0
  49. package/dist/terraform/modules/data/compliance-exports-bucket/variables.tf +50 -0
  50. package/dist/terraform/modules/data/s3-backups-bucket/main.tf +123 -0
  51. package/dist/terraform/modules/data/s3-buckets/main.tf +13 -0
  52. package/dist/terraform/modules/foundation/cognito/variables.tf +2 -2
  53. package/dist/terraform/modules/thinkwork/main.tf +439 -21
  54. package/dist/terraform/modules/thinkwork/outputs.tf +121 -0
  55. package/dist/terraform/modules/thinkwork/variables.tf +153 -2
  56. package/dist/terraform/schema.graphql +17 -0
  57. package/package.json +15 -14
@@ -0,0 +1,453 @@
1
+ ################################################################################
2
+ # Routines Step Functions — App Module (stage-level)
3
+ #
4
+ # Stage-scoped substrate for the Step Functions Routines runtime:
5
+ # * Execution role (one per stage, ABAC-tenant-tagged, used by ALL routines).
6
+ # * CloudWatch log group for state machine execution histories.
7
+ # * S3 bucket for python() recipe output offload (mandatory due to the
8
+ # 256KB ASL state-payload + 25K-event execution-history caps).
9
+ #
10
+ # **State machines themselves are NOT created here.** Each Routine
11
+ # provisions its own state machine via the createRoutine GraphQL
12
+ # resolver (Phase B U7) using the role + log group exported by this
13
+ # module. This mirrors the agentcore-code-interpreter pattern: stage
14
+ # substrate in Terraform, per-resource fan-out at runtime.
15
+ #
16
+ # **Tenant isolation via ABAC.** The execution role's inline policies use
17
+ # tag-condition matching (aws:PrincipalTag/tenantId vs.
18
+ # aws:ResourceTag/tenantId). Each state machine is created with the
19
+ # tenantId/agentId/routineId tags so cross-tenant invocations fail at
20
+ # the IAM layer.
21
+ ################################################################################
22
+
23
+ terraform {
24
+ required_providers {
25
+ aws = {
26
+ source = "hashicorp/aws"
27
+ version = ">= 5.0"
28
+ }
29
+ }
30
+ }
31
+
32
+ variable "stage" {
33
+ description = "Deployment stage (dev, prod, etc.)."
34
+ type = string
35
+ }
36
+
37
+ variable "region" {
38
+ description = "AWS region."
39
+ type = string
40
+ }
41
+
42
+ variable "account_id" {
43
+ description = "AWS account ID (used to construct IAM resource ARNs)."
44
+ type = string
45
+ }
46
+
47
+ variable "log_retention_days" {
48
+ description = "CloudWatch log retention for routine state machine executions."
49
+ type = number
50
+ default = 30
51
+ }
52
+
53
+ variable "execution_callback_lambda_arn" {
54
+ description = "ARN of the routine-execution-callback Lambda. EventBridge sends SFN execution-state-change events here so routine_executions lifecycle status mirrors the SFN-side reality. When empty, the EventBridge rule is not provisioned (Phase B U9 hasn't deployed yet)."
55
+ type = string
56
+ default = ""
57
+ }
58
+
59
+ locals {
60
+ log_group_name = "/aws/vendedlogs/states/thinkwork-${var.stage}-routines"
61
+ output_bucket = "thinkwork-${var.stage}-routine-output"
62
+ role_name = "thinkwork-${var.stage}-routines-execution-role"
63
+ }
64
+
65
+ ################################################################################
66
+ # CloudWatch Log Group
67
+ #
68
+ # Uses the /aws/vendedlogs/states/ prefix to dodge the resource-policy size
69
+ # cap when the tenant accumulates hundreds of state machines.
70
+ ################################################################################
71
+
72
+ resource "aws_cloudwatch_log_group" "routines" {
73
+ name = local.log_group_name
74
+ retention_in_days = var.log_retention_days
75
+
76
+ tags = {
77
+ Name = local.log_group_name
78
+ Stage = var.stage
79
+ Purpose = "step-functions-routines"
80
+ }
81
+ }
82
+
83
+ ################################################################################
84
+ # S3 bucket — python() recipe output offload
85
+ #
86
+ # Path layout: s3://<bucket>/<tenantId>/<executionArn>/<nodeId>/{stdout,stderr}.log
87
+ # The python() Task wrapper writes here; the run-detail surface signs URLs
88
+ # server-side with tenant-scoped IAM and short expirations.
89
+ ################################################################################
90
+
91
+ resource "aws_s3_bucket" "routine_output" {
92
+ bucket = local.output_bucket
93
+
94
+ tags = {
95
+ Name = local.output_bucket
96
+ Stage = var.stage
97
+ Purpose = "routine-python-output"
98
+ }
99
+ }
100
+
101
+ resource "aws_s3_bucket_public_access_block" "routine_output" {
102
+ bucket = aws_s3_bucket.routine_output.id
103
+
104
+ block_public_acls = true
105
+ block_public_policy = true
106
+ ignore_public_acls = true
107
+ restrict_public_buckets = true
108
+ }
109
+
110
+ resource "aws_s3_bucket_server_side_encryption_configuration" "routine_output" {
111
+ bucket = aws_s3_bucket.routine_output.id
112
+
113
+ rule {
114
+ apply_server_side_encryption_by_default {
115
+ sse_algorithm = "AES256"
116
+ }
117
+ }
118
+ }
119
+
120
+ resource "aws_s3_bucket_versioning" "routine_output" {
121
+ bucket = aws_s3_bucket.routine_output.id
122
+
123
+ versioning_configuration {
124
+ status = "Disabled"
125
+ }
126
+ }
127
+
128
+ ################################################################################
129
+ # Step Functions Execution Role — single role for all routines
130
+ #
131
+ # Trust: states.amazonaws.com.
132
+ # ABAC: inline policies condition on tag matching where applicable.
133
+ ################################################################################
134
+
135
+ resource "aws_iam_role" "execution" {
136
+ name = local.role_name
137
+
138
+ assume_role_policy = jsonencode({
139
+ Version = "2012-10-17"
140
+ Statement = [{
141
+ Effect = "Allow"
142
+ Principal = { Service = "states.amazonaws.com" }
143
+ Action = "sts:AssumeRole"
144
+ Condition = {
145
+ StringEquals = {
146
+ "aws:SourceAccount" = var.account_id
147
+ }
148
+ }
149
+ }]
150
+ })
151
+
152
+ tags = {
153
+ Name = local.role_name
154
+ Stage = var.stage
155
+ Purpose = "step-functions-routines-execution"
156
+ }
157
+ }
158
+
159
+ # CloudWatch logs (X-Ray + CloudWatch Logs Delivery for Step Functions).
160
+ # Step Functions requires these specific actions to populate execution
161
+ # history into the vendedlogs prefix.
162
+ resource "aws_iam_role_policy" "execution_logs" {
163
+ name = "logs-and-xray"
164
+ role = aws_iam_role.execution.id
165
+
166
+ policy = jsonencode({
167
+ Version = "2012-10-17"
168
+ Statement = [
169
+ {
170
+ Effect = "Allow"
171
+ Action = [
172
+ "logs:CreateLogDelivery",
173
+ "logs:GetLogDelivery",
174
+ "logs:UpdateLogDelivery",
175
+ "logs:DeleteLogDelivery",
176
+ "logs:ListLogDeliveries",
177
+ "logs:PutResourcePolicy",
178
+ "logs:DescribeResourcePolicies",
179
+ "logs:DescribeLogGroups",
180
+ ]
181
+ Resource = "*"
182
+ },
183
+ {
184
+ Effect = "Allow"
185
+ Action = [
186
+ "logs:CreateLogStream",
187
+ "logs:PutLogEvents",
188
+ ]
189
+ Resource = "${aws_cloudwatch_log_group.routines.arn}:*"
190
+ },
191
+ {
192
+ Effect = "Allow"
193
+ Action = [
194
+ "xray:PutTraceSegments",
195
+ "xray:PutTelemetryRecords",
196
+ "xray:GetSamplingRules",
197
+ "xray:GetSamplingTargets",
198
+ ]
199
+ Resource = "*"
200
+ },
201
+ ]
202
+ })
203
+ }
204
+
205
+ # Lambda invocation — scoped to the routine task wrapper Lambdas. The
206
+ # wrappers themselves enforce per-tenant authorization (validating the
207
+ # routine_id + execution_arn against the DB) so wildcard here is
208
+ # acceptable; tightening to specific function ARNs causes a chicken-and-
209
+ # egg with the lambda-api module which depends on outputs from this one.
210
+ resource "aws_iam_role_policy" "execution_lambda_invoke" {
211
+ name = "lambda-invoke-task-wrappers"
212
+ role = aws_iam_role.execution.id
213
+
214
+ policy = jsonencode({
215
+ Version = "2012-10-17"
216
+ Statement = [{
217
+ Effect = "Allow"
218
+ Action = ["lambda:InvokeFunction"]
219
+ Resource = [
220
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-routine-task-*",
221
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-routine-resume",
222
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-routine-approval-callback",
223
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-email-send",
224
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-admin-ops-mcp",
225
+ "arn:aws:lambda:${var.region}:${var.account_id}:function:thinkwork-${var.stage}-api-slack-send",
226
+ ]
227
+ }]
228
+ })
229
+ }
230
+
231
+ # Bedrock AgentCore — code interpreter (python() recipe) and agent runtime
232
+ # (agent_invoke recipe via aws-sdk:bedrockagentcore:invokeAgentRuntime
233
+ # direct integration).
234
+ resource "aws_iam_role_policy" "execution_bedrock_agentcore" {
235
+ name = "bedrock-agentcore"
236
+ role = aws_iam_role.execution.id
237
+
238
+ policy = jsonencode({
239
+ Version = "2012-10-17"
240
+ Statement = [
241
+ {
242
+ Effect = "Allow"
243
+ Action = [
244
+ "bedrock-agentcore:StartCodeInterpreterSession",
245
+ "bedrock-agentcore:InvokeCodeInterpreter",
246
+ "bedrock-agentcore:StopCodeInterpreterSession",
247
+ ]
248
+ Resource = "*"
249
+ },
250
+ {
251
+ Effect = "Allow"
252
+ Action = ["bedrock-agentcore:InvokeAgentRuntime"]
253
+ Resource = "*"
254
+ },
255
+ ]
256
+ })
257
+ }
258
+
259
+ # Secrets Manager — for routines that reach for tenant-scoped secrets
260
+ # (e.g. API tokens) via the python() recipe. Path-scoped per tenant.
261
+ resource "aws_iam_role_policy" "execution_secrets" {
262
+ name = "secrets-manager"
263
+ role = aws_iam_role.execution.id
264
+
265
+ policy = jsonencode({
266
+ Version = "2012-10-17"
267
+ Statement = [{
268
+ Effect = "Allow"
269
+ Action = [
270
+ "secretsmanager:GetSecretValue",
271
+ "secretsmanager:DescribeSecret",
272
+ ]
273
+ Resource = "arn:aws:secretsmanager:${var.region}:${var.account_id}:secret:thinkwork/${var.stage}/routines/*"
274
+ }]
275
+ })
276
+ }
277
+
278
+ # Step Functions self-invocation — for the routine_invoke recipe (one
279
+ # routine calling another via startExecution.sync:2). Plus task-token
280
+ # completion (SendTaskSuccess / SendTaskFailure) needed by the inbox-
281
+ # approval bridge that runs out-of-band from this state machine.
282
+ #
283
+ # **ABAC tenant isolation is application-layer in Phase A.** A previous
284
+ # revision had an ABAC tag-condition on StartExecution
285
+ # (aws:ResourceTag/tenantId vs aws:PrincipalTag/tenantId). The shared
286
+ # execution role has no tenantId principal tag, so the condition resolved
287
+ # to '' on every invocation and denied all sub-routine calls. True ABAC
288
+ # for a shared role requires session tags via sts:AssumeRole + TagSession
289
+ # (Phase B follow-up — see review residuals). For now tenant isolation
290
+ # lives at the GraphQL resolver layer (routine_id auth check + tenant_id
291
+ # FK), matching the rest of the platform's tenant-scoped resources.
292
+ resource "aws_iam_role_policy" "execution_states" {
293
+ name = "states-self-invoke"
294
+ role = aws_iam_role.execution.id
295
+
296
+ policy = jsonencode({
297
+ Version = "2012-10-17"
298
+ Statement = [
299
+ {
300
+ # StartExecution operates on the stateMachine ARN.
301
+ Effect = "Allow"
302
+ Action = [
303
+ "states:StartExecution",
304
+ ]
305
+ Resource = "arn:aws:states:${var.region}:${var.account_id}:stateMachine:thinkwork-${var.stage}-routine-*"
306
+ },
307
+ {
308
+ # DescribeExecution and StopExecution operate on the execution
309
+ # ARN, not the stateMachine ARN. startExecution.sync:2 polls
310
+ # DescribeExecution under the hood; without this statement the
311
+ # routine_invoke recipe would fail at runtime with an implicit
312
+ # IAM deny.
313
+ Effect = "Allow"
314
+ Action = [
315
+ "states:DescribeExecution",
316
+ "states:StopExecution",
317
+ ]
318
+ Resource = "arn:aws:states:${var.region}:${var.account_id}:execution:thinkwork-${var.stage}-routine-*:*"
319
+ },
320
+ {
321
+ Effect = "Allow"
322
+ Action = [
323
+ "states:SendTaskSuccess",
324
+ "states:SendTaskFailure",
325
+ "states:SendTaskHeartbeat",
326
+ ]
327
+ Resource = "*"
328
+ },
329
+ ]
330
+ })
331
+ }
332
+
333
+ # S3 — write to the python() output bucket, scoped by tenant prefix.
334
+ resource "aws_iam_role_policy" "execution_s3_output" {
335
+ name = "s3-routine-output"
336
+ role = aws_iam_role.execution.id
337
+
338
+ policy = jsonencode({
339
+ Version = "2012-10-17"
340
+ Statement = [
341
+ {
342
+ Effect = "Allow"
343
+ Action = [
344
+ "s3:PutObject",
345
+ "s3:GetObject",
346
+ ]
347
+ Resource = "${aws_s3_bucket.routine_output.arn}/*"
348
+ },
349
+ {
350
+ Effect = "Allow"
351
+ Action = ["s3:ListBucket"]
352
+ Resource = aws_s3_bucket.routine_output.arn
353
+ },
354
+ ]
355
+ })
356
+ }
357
+
358
+ ################################################################################
359
+ # Outputs
360
+ ################################################################################
361
+
362
+ output "execution_role_arn" {
363
+ description = "ARN of the Step Functions execution role (assumed by all routine state machines)."
364
+ value = aws_iam_role.execution.arn
365
+ }
366
+
367
+ output "execution_role_name" {
368
+ description = "Name of the Step Functions execution role."
369
+ value = aws_iam_role.execution.name
370
+ }
371
+
372
+ output "log_group_arn" {
373
+ description = "CloudWatch log group ARN for state machine execution histories."
374
+ value = aws_cloudwatch_log_group.routines.arn
375
+ }
376
+
377
+ output "log_group_name" {
378
+ description = "CloudWatch log group name."
379
+ value = aws_cloudwatch_log_group.routines.name
380
+ }
381
+
382
+ output "output_bucket_name" {
383
+ description = "S3 bucket for python() recipe stdout/stderr offload."
384
+ value = aws_s3_bucket.routine_output.bucket
385
+ }
386
+
387
+ output "output_bucket_arn" {
388
+ description = "S3 bucket ARN for python() recipe stdout/stderr offload."
389
+ value = aws_s3_bucket.routine_output.arn
390
+ }
391
+
392
+ output "stage" {
393
+ description = "Echo of the stage variable (convenience for downstream modules)."
394
+ value = var.stage
395
+ }
396
+
397
+ ################################################################################
398
+ # EventBridge → routine-execution-callback (Phase B U9)
399
+ #
400
+ # AWS EventBridge fires `Step Functions Execution Status Change` events on
401
+ # every status transition (RUNNING/SUCCEEDED/FAILED/TIMED_OUT/ABORTED). The
402
+ # routine-execution-callback Lambda speaks both API Gateway POST shape and
403
+ # this EventBridge shape; the Lambda detects the EventBridge event by
404
+ # `source === "aws.states"` and adapts.
405
+ #
406
+ # The rule is gated on var.execution_callback_lambda_arn being set so the
407
+ # substrate module remains provisionable before the lambda-api module
408
+ # defines the function. The thinkwork composite module wires the ARN
409
+ # back in once both modules apply.
410
+ ################################################################################
411
+
412
+ resource "aws_cloudwatch_event_rule" "sfn_state_change" {
413
+ count = var.execution_callback_lambda_arn != "" ? 1 : 0
414
+ name = "thinkwork-${var.stage}-routines-sfn-state-change"
415
+ description = "Forward SFN execution-state-change events to routine-execution-callback so routine_executions tracks lifecycle status."
416
+
417
+ event_pattern = jsonencode({
418
+ source = ["aws.states"]
419
+ "detail-type" = ["Step Functions Execution Status Change"]
420
+ detail = {
421
+ # Constrain to state machines provisioned by createRoutine — the
422
+ # alias-pointing state machine ARNs all live under the
423
+ # `thinkwork-${stage}-routine-` prefix per the publish flow's naming
424
+ # contract (Phase B U7).
425
+ stateMachineArn = [
426
+ {
427
+ prefix = "arn:aws:states:${var.region}:${var.account_id}:stateMachine:thinkwork-${var.stage}-routine-"
428
+ },
429
+ ]
430
+ }
431
+ })
432
+
433
+ tags = {
434
+ Name = "thinkwork-${var.stage}-routines-sfn-state-change"
435
+ Stage = var.stage
436
+ }
437
+ }
438
+
439
+ resource "aws_cloudwatch_event_target" "sfn_state_change" {
440
+ count = var.execution_callback_lambda_arn != "" ? 1 : 0
441
+ rule = aws_cloudwatch_event_rule.sfn_state_change[0].name
442
+ target_id = "routine-execution-callback"
443
+ arn = var.execution_callback_lambda_arn
444
+ }
445
+
446
+ resource "aws_lambda_permission" "sfn_state_change" {
447
+ count = var.execution_callback_lambda_arn != "" ? 1 : 0
448
+ statement_id = "AllowEventBridgeInvokeRoutineExecutionCallback"
449
+ action = "lambda:InvokeFunction"
450
+ function_name = var.execution_callback_lambda_arn
451
+ principal = "events.amazonaws.com"
452
+ source_arn = aws_cloudwatch_event_rule.sfn_state_change[0].arn
453
+ }
@@ -0,0 +1,66 @@
1
+ # `sandbox-log-scrubber` — R13 CloudWatch backstop
2
+
3
+ **Secondary** scrubber for the AgentCore Code Interpreter sandbox. Pattern-
4
+ redacts known-shape OAuth tokens in AgentCore `APPLICATION_LOGS` before they
5
+ land in the long-term CloudWatch tier. See plan Unit 12 +
6
+ `docs/brainstorms/2026-04-22-agentcore-code-sandbox-requirements.md` R13.
7
+
8
+ ## Relationship to the primary scrubber
9
+
10
+ The primary layer is the base-image `sitecustomize.py` stdio wrapper shipped
11
+ by [`terraform/modules/app/agentcore-code-interpreter`](../agentcore-code-interpreter/)
12
+ (plan Unit 4). That layer redacts by **value** — session-scoped token
13
+ strings registered by the preamble — and can therefore catch any token the
14
+ agent's own preamble has handled.
15
+
16
+ This backstop redacts by **pattern**: it has no access to session values,
17
+ only to the bytes that reach CloudWatch. It exists to mitigate *stdio-bypass
18
+ classes* named in R13's residual list:
19
+
20
+ - `subprocess.run(['env'])`, `subprocess.run(['cat', '/proc/self/environ'])`
21
+ - `os.write(fd, ...)` at the file-descriptor level
22
+ - C-extension writes to fd 1 directly
23
+ - `multiprocessing` workers with fresh Python interpreters where the
24
+ session token set hasn't been populated
25
+ - Adversarial split-writes that fragment a token across more bytes than the
26
+ rolling-buffer window
27
+
28
+ When those bytes carry a token whose *shape* is recognizable, this backstop
29
+ catches it. When they don't (bespoke token formats, high-entropy opaque
30
+ strings with no prefix), only the v2 in-process credential proxy can help.
31
+
32
+ ## Pattern set
33
+
34
+ Defined in `packages/lambda/sandbox-log-scrubber.ts`; add to it before
35
+ relying on new token shapes in production:
36
+
37
+ - `Authorization:\s*Bearer\s+<opaque-run>`
38
+ - JWT three-dotted base64url (16 chars min per segment)
39
+ - `gh[oprsu]_<A-Za-z0-9>{20,}` — GitHub PATs, OAuth, server-to-server
40
+ - `xox[abep]-<A-Za-z0-9-]{10,}` — Slack bot / app / user / export
41
+ - `ya29.<A-Za-z0-9_->{20,}` — Google short-lived OAuth
42
+
43
+ ## What it creates
44
+
45
+ - `/thinkwork/{stage}/sandbox/scrubbed` CloudWatch log group (90-day retention)
46
+ - IAM execution role `thinkwork-{stage}-sandbox-log-scrubber`
47
+ - Lambda function `thinkwork-{stage}-sandbox-log-scrubber` (node20, 256MB, 30s)
48
+ - Subscription filter on the source log group — delivers every event (no filter pattern)
49
+ - Invoke permission from CloudWatch Logs to the Lambda
50
+
51
+ ## Failure tolerance
52
+
53
+ If the scrubber Lambda fails — OOM, bug, cold-start timeout — source events
54
+ remain in the original CloudWatch log group. S3 export of the scrubbed group
55
+ is delayed, but no data is lost. The source group retains for whatever its
56
+ own retention policy specifies (typically 90 days).
57
+
58
+ ## Build + wire
59
+
60
+ 1. `bash scripts/build-lambdas.sh sandbox-log-scrubber` — produces
61
+ `dist/lambdas/sandbox-log-scrubber.zip`.
62
+ 2. In the caller module (e.g., `terraform/examples/greenfield`), pass
63
+ `lambda_zip_path` and `lambda_zip_hash` (typically `filebase64sha256()`
64
+ of the zip).
65
+ 3. Pass `source_log_group_name` pointing at the AgentCore runtime group
66
+ emitting APPLICATION_LOGS for the stage.