thinkwork-cli 0.13.1 → 1.0.0
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.
- package/README.md +3 -14
- package/dist/{api-client-4DZ266J5.js → api-client-4P4JZOBX.js} +3 -1
- package/dist/bowser-6Z6JFAIR.js +2822 -0
- package/dist/{chunk-Y2WHJECR.js → chunk-4OMDWYZQ.js} +5 -44
- package/dist/chunk-AD2NIWB5.js +6516 -0
- package/dist/chunk-DV3MRBR5.js +43 -0
- package/dist/chunk-EI5ZX7CN.js +522 -0
- package/dist/chunk-EUJ7X6YD.js +126 -0
- package/dist/chunk-GG5WKBOX.js +436 -0
- package/dist/chunk-I64EZSU5.js +358 -0
- package/dist/chunk-KMIJ5KGT.js +128 -0
- package/dist/chunk-MWQ7RO76.js +137 -0
- package/dist/chunk-OCPHU34C.js +996 -0
- package/dist/chunk-OLUQ4U2Z.js +1728 -0
- package/dist/chunk-XYM7PE2A.js +2264 -0
- package/dist/chunk-YQCYD6NE.js +3299 -0
- package/dist/cli.js +49156 -31257
- package/dist/commands/enterprise/templates/deploy-repo/scripts/apply-release.mjs +0 -1
- package/dist/commands/enterprise/templates/deploy-repo/scripts/smoke.mjs +0 -1
- package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +0 -14
- package/dist/dist-es-2OTNTL4N.js +315 -0
- package/dist/dist-es-2XVAH4BQ.js +14726 -0
- package/dist/dist-es-3DPDUJAH.js +2000 -0
- package/dist/dist-es-BNZTBBVC.js +176 -0
- package/dist/dist-es-CD2RBDAC.js +88 -0
- package/dist/dist-es-I4FW57PS.js +88 -0
- package/dist/dist-es-KL7Y5NB2.js +377 -0
- package/dist/dist-es-KVFWJNDY.js +318 -0
- package/dist/dist-es-PS73DWNL.js +70 -0
- package/dist/dist-es-S3NZQFGT.js +170 -0
- package/dist/dist-es-SGCA4P32.js +48 -0
- package/dist/dist-es-WBM6J4RA.js +70 -0
- package/dist/dist-es-ZGVADYIF.js +485 -0
- package/dist/dist-es-ZKC2QKD2.js +377 -0
- package/dist/drizzle/0076_scheduled_jobs_marco_backfill.sql +2 -1
- package/dist/drizzle/0079_seed_tenant_customize_catalog.sql +1 -0
- package/dist/drizzle/0090_brain_schema_extraction.sql +10 -0
- package/dist/drizzle/0132_drop_computer_tables.sql +3 -0
- package/dist/drizzle/0160_compliance_event_types_plugins.sql +2 -0
- package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +1 -0
- package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +1 -0
- package/dist/drizzle/0227_analyst_reader_role.sql +17 -20
- package/dist/drizzle/0230_analyst_rls.sql +28 -36
- package/dist/drizzle/0250_kg_schema_extraction.sql +27 -0
- package/dist/drizzle/0251_brain_stragglers.sql +21 -0
- package/dist/drizzle/0253_drop_brain_substrate.sql +2 -0
- package/dist/drizzle/0257_agents_runtime_allow_harness.sql +24 -0
- package/dist/drizzle/0261_native_auth_control_plane.sql +332 -0
- package/dist/drizzle/0262_auth_subscription_tickets.sql +59 -0
- package/dist/drizzle/0263_agentcore_runtime_identifier.sql +69 -0
- package/dist/drizzle/0263_drop_workos_auth_runtime.sql +236 -0
- package/dist/drizzle/0264_auth_enrollment_attempt_limits.sql +15 -0
- package/dist/drizzle/0265_eval_runs_requester_identity.sql +28 -0
- package/dist/drizzle/0265_require_fully_valid_auth_publication.sql +27 -0
- package/dist/drizzle/0266_auth_identity_recovery_grants.sql +9 -0
- package/dist/drizzle/0266_eval_profiles_runtime_type.sql +12 -0
- package/dist/drizzle/0267_ontology_candidate_rejections.sql +53 -0
- package/dist/drizzle/0268_identity_crosswalk_routing.sql +182 -0
- package/dist/drizzle/0269_ontology_system_map.sql +31 -0
- package/dist/drizzle/0270_tool_execution_ledger.sql +115 -0
- package/dist/drizzle/0271_ontology_twin_declarations.sql +47 -0
- package/dist/drizzle/0272_identity_graph_projection_cursor.sql +23 -0
- package/dist/drizzle/0273_twin_materialization_suggestions.sql +29 -0
- package/dist/drizzle/0274_tenant_mcp_twin_keys.sql +59 -0
- package/dist/drizzle/0275_identity_bulk_rebuild_fence.sql +30 -0
- package/dist/drizzle/0276_tool_execution_ledger_allow_cascade_delete.sql +18 -0
- package/dist/drizzle/0277_knowledge_base_sources.sql +107 -0
- package/dist/drizzle/0278_kb_page_transcription.sql +55 -0
- package/dist/drizzle/0279_mcp_servers_off_plugin_provenance.sql +93 -0
- package/dist/drizzle/0280_work_item_external_refs_drop_plane_provider.sql +46 -0
- package/dist/drizzle/0281_tenant_mcp_twin_keys_expiry_suffix.sql +9 -0
- package/dist/drizzle/0282_tenant_mcp_twin_keys_grants.sql +24 -0
- package/dist/drizzle/0283_agents_agentcore_runtime_dispatch.sql +13 -0
- package/dist/drizzle/0284_user_brain_claims.sql +69 -0
- package/dist/drizzle/0285_tenant_mcp_twin_keys_trusted_subsystem.sql +30 -0
- package/dist/drizzle/0286_brain_analytics_key.sql +26 -0
- package/dist/drizzle/0287_brain_subject.sql +25 -0
- package/dist/drizzle/0288_multi_lane_identities.sql +25 -0
- package/dist/event-streams-7JNLJEEL.js +894 -0
- package/dist/event-streams-KDF5RWR6.js +1515 -0
- package/dist/loadSso-ETCXTR4I.js +576 -0
- package/dist/loadSso-UBD7KUHE.js +583 -0
- package/dist/plugins/README.md +2 -2
- package/dist/plugins/catalog/package.json +1 -5
- package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +6 -4
- package/dist/plugins/catalog/src/__tests__/catalog.test.ts +21 -21
- package/dist/plugins/catalog/src/__tests__/contracts.test.ts +102 -94
- package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +7 -8
- package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -20
- package/dist/plugins/catalog/src/contracts.ts +17 -179
- package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -24
- package/dist/plugins/n8n/test/integrated-app-contract.test.ts +1 -1
- package/dist/plugins/twenty/package.json +2 -1
- package/dist/plugins/twenty/scripts/LAMBDA_DEPLOY.md +114 -0
- package/dist/plugins/twenty/scripts/build-lambda.sh +29 -0
- package/dist/plugins/twenty/scripts/configure-product-views.ts +195 -0
- package/dist/plugins/twenty/scripts/lambda-handler.ts +49 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/load-attachments.test.ts +38 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/load-records.test.ts +145 -2
- package/dist/plugins/twenty/scripts/lib/__tests__/mappers.test.ts +397 -151
- package/dist/plugins/twenty/scripts/lib/__tests__/members-ensure.test.ts +28 -129
- package/dist/plugins/twenty/scripts/lib/__tests__/provision-members-db.test.ts +10 -0
- package/dist/plugins/twenty/scripts/lib/__tests__/schema-ensure.test.ts +13 -8
- package/dist/plugins/twenty/scripts/lib/lastmile-reader.ts +222 -2
- package/dist/plugins/twenty/scripts/lib/load-attachments.ts +29 -0
- package/dist/plugins/twenty/scripts/lib/load-records.ts +160 -43
- package/dist/plugins/twenty/scripts/lib/mappers.ts +368 -152
- package/dist/plugins/twenty/scripts/lib/members-ensure.ts +43 -268
- package/dist/plugins/twenty/scripts/lib/provision-members-db.ts +57 -0
- package/dist/plugins/twenty/scripts/lib/schema-ensure.ts +136 -31
- package/dist/plugins/twenty/scripts/migrate-lastmile.ts +254 -152
- package/dist/plugins/twenty/scripts/provision-twenty-members.ts +44 -1
- package/dist/plugins/twenty/scripts/purge-lastmile-import.ts +178 -0
- package/dist/plugins/twenty/scripts/restructure-products.ts +312 -0
- package/dist/plugins/twenty/scripts/retag-stage-values.ts +362 -0
- package/dist/plugins/twenty/test/manifest.test.ts +2 -4
- package/dist/scripts/post-deploy.sh +79 -1
- package/dist/signin-5QZNRSOO.js +821 -0
- package/dist/sso-oidc-XK673P54.js +837 -0
- package/dist/sts-OXI2UALF.js +1113 -0
- package/dist/sts-SQW4NBK3.js +1117 -0
- package/dist/terraform/examples/greenfield/main.tf +391 -176
- package/dist/terraform/examples/greenfield/terraform.tfvars.example +26 -8
- package/dist/terraform/modules/app/agentcore-admin/main.tf +47 -0
- package/dist/terraform/modules/app/agentcore-code-interpreter/README.md +39 -47
- package/dist/terraform/modules/app/agentcore-code-interpreter/main.tf +13 -66
- package/dist/terraform/modules/app/agentcore-identity/main.tf +253 -0
- package/dist/terraform/modules/app/agentcore-identity/outputs.tf +51 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/bootstrap_twenty_oauth_client.sh +239 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/delete_identity.sh +22 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/delete_twenty_identity.sh +16 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/delete_workload_identity.sh +19 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/ensure_workload_identity.sh +29 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/read_identity.sh +55 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_identity.sh +73 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_identity.sh +45 -0
- package/dist/terraform/modules/app/agentcore-identity/scripts/reconcile_twenty_provider.mjs +123 -0
- package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/after.tf +15 -0
- package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/before.tf +7 -0
- package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/identity-after.tf +8 -0
- package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/identity-before.tf +9 -0
- package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/thinkwork-after.tf +3 -0
- package/dist/terraform/modules/app/agentcore-identity/tests/tainted-marker/thinkwork-before.tf +3 -0
- package/dist/terraform/modules/app/agentcore-identity/variables.tf +60 -0
- package/dist/terraform/modules/app/agentcore-memory/README.md +46 -9
- package/dist/terraform/modules/app/agentcore-memory/main.tf +27 -10
- package/dist/terraform/modules/app/agentcore-memory/scripts/create_or_find_memory.sh +156 -24
- package/dist/terraform/modules/app/agentcore-pi/README.md +1 -3
- package/dist/terraform/modules/app/agentcore-pi/main.tf +42 -52
- package/dist/terraform/modules/app/agentcore-pi/outputs.tf +3 -3
- package/dist/terraform/modules/app/agentcore-pi/scripts/reconcile_pi_runtime.mjs +175 -0
- package/dist/terraform/modules/app/agentcore-pi/variables.tf +35 -28
- package/dist/terraform/modules/app/agentcore-platform/main.tf +68 -0
- package/dist/terraform/modules/app/agentcore-runtime/main.tf +4 -20
- package/dist/terraform/modules/app/appsync-subscriptions/main.tf +127 -21
- package/dist/terraform/modules/app/appsync-subscriptions/outputs.tf +0 -6
- package/dist/terraform/modules/app/appsync-subscriptions/variables.tf +5 -0
- package/dist/terraform/modules/app/capability-broker/main.tf +2 -2
- package/dist/terraform/modules/app/capability-broker/variables.tf +11 -0
- package/dist/terraform/modules/app/deployment-control-plane/README.md +264 -0
- package/dist/terraform/modules/app/deployment-control-plane/buildspec.yml +4 -1
- package/dist/terraform/modules/app/deployment-control-plane/main.tf +94 -0
- package/dist/terraform/modules/app/deployment-control-plane/outputs.tf +5 -0
- package/dist/terraform/modules/app/deployment-control-plane/runner.py +3689 -233
- package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +2579 -140
- package/dist/terraform/modules/app/deployment-control-plane/variables.tf +33 -0
- package/dist/terraform/modules/app/lambda-api/auth-state.tf +72 -0
- package/dist/terraform/modules/app/lambda-api/chat-latency-observability.tf +466 -0
- package/dist/terraform/modules/app/lambda-api/dispatch-dlq.tf +55 -0
- package/dist/terraform/modules/app/lambda-api/handlers.tf +531 -619
- package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +350 -241
- package/dist/terraform/modules/app/lambda-api/main.tf +75 -13
- package/dist/terraform/modules/app/lambda-api/memory-alarms.tf +3 -28
- package/dist/terraform/modules/app/lambda-api/oauth-secrets.tf +1 -2
- package/dist/terraform/modules/app/lambda-api/outputs.tf +17 -10
- package/dist/terraform/modules/app/lambda-api/runtime-config.tf +3 -21
- package/dist/terraform/modules/app/lambda-api/turn-assertion.tf +45 -0
- package/dist/terraform/modules/app/lambda-api/variables.tf +307 -121
- package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/README.md +1 -1
- package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/main.tf +18 -18
- package/dist/terraform/modules/app/sandbox-log-scrubber/README.md +11 -10
- package/dist/terraform/modules/app/sandbox-log-scrubber/main.tf +5 -5
- package/dist/terraform/modules/app/static-site/main.tf +1 -1
- package/dist/terraform/modules/app/www-dns/main.tf +9 -27
- package/dist/terraform/modules/app/www-dns/variables.tf +4 -10
- package/dist/terraform/modules/data/aurora-postgres/README.md +34 -0
- package/dist/terraform/modules/data/aurora-postgres/alarms.tf +82 -0
- package/dist/terraform/modules/data/aurora-postgres/main.tf +27 -57
- package/dist/terraform/modules/data/aurora-postgres/outputs.tf +0 -9
- package/dist/terraform/modules/data/aurora-postgres/variables.tf +41 -0
- package/dist/terraform/modules/data/s3-buckets/main.tf +5 -6
- package/dist/terraform/modules/foundation/cognito/main.tf +367 -72
- package/dist/terraform/modules/foundation/cognito/outputs.tf +47 -0
- package/dist/terraform/modules/foundation/cognito/variables.tf +131 -9
- package/dist/terraform/modules/thinkwork/README.md +5 -1
- package/dist/terraform/modules/thinkwork/main.tf +459 -234
- package/dist/terraform/modules/thinkwork/outputs.tf +44 -62
- package/dist/terraform/modules/thinkwork/variables.tf +353 -89
- package/dist/terraform/schema.graphql +69 -36
- package/dist/workspace-defaults/files/INSTRUCTIONS.md +9 -11
- package/dist/workspace-defaults/files/MEMORY_GUIDE.md +38 -35
- package/dist/workspace-defaults/files/ROUTER.md +1 -1
- package/dist/workspace-defaults/files/TOOLS.md +9 -39
- package/dist/workspace-defaults/files/USER.md +1 -0
- package/dist/workspace-defaults/files/agents/analyst/INSTRUCTIONS.md +6 -3
- package/dist/workspace-defaults/files/catalog-skills/brain-brief-builder/SKILL.md +165 -0
- package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/SKILL.md +75 -0
- package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/mcp-tooling.md +40 -0
- package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/validation-and-handoff.md +40 -0
- package/dist/workspace-defaults/files/catalog-skills/n8n-workflow-operator/references/workflow-authoring.md +46 -0
- package/dist/workspace-defaults/files/skills/artifact-builder/references/crm-dashboard.md +1 -1
- package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +22 -3
- package/package.json +3 -2
- package/dist/drizzle/0261_thread_turns_origin_turn_index.sql +0 -11
- package/dist/plugins/company-data/README.md +0 -49
- package/dist/plugins/company-data/package.json +0 -21
- package/dist/plugins/company-data/src/index.ts +0 -30
- package/dist/plugins/company-data/src/manifest.ts +0 -26
- package/dist/plugins/company-data/test/manifest.test.ts +0 -116
- package/dist/plugins/company-data/tsconfig.json +0 -8
- package/dist/plugins/company-etl/README.md +0 -46
- package/dist/plugins/company-etl/package.json +0 -21
- package/dist/plugins/company-etl/src/index.ts +0 -29
- package/dist/plugins/company-etl/src/manifest.ts +0 -26
- package/dist/plugins/company-etl/test/manifest.test.ts +0 -108
- package/dist/plugins/company-etl/tsconfig.json +0 -8
- package/dist/plugins/lastmile/README.md +0 -50
- package/dist/plugins/lastmile/package.json +0 -23
- package/dist/plugins/lastmile/smoke/lastmile-plugin-smoke.mjs +0 -661
- package/dist/plugins/lastmile/src/api/tasks-adapter.ts +0 -572
- package/dist/plugins/lastmile/src/discovery.fixture.ts +0 -64
- package/dist/plugins/lastmile/src/index.ts +0 -46
- package/dist/plugins/lastmile/src/manifest.ts +0 -139
- package/dist/plugins/lastmile/test/api/tasks-adapter.test.ts +0 -266
- package/dist/plugins/lastmile/test/discovery.test.ts +0 -88
- package/dist/plugins/lastmile/tsconfig.json +0 -8
- package/dist/plugins/n8n/smoke/n8n-managed-app-smoke.mjs +0 -409
- package/dist/plugins/twenty/smoke/twenty-managed-app-smoke.mjs +0 -359
- package/dist/plugins/workos-auth/package.json +0 -22
- package/dist/plugins/workos-auth/src/index.ts +0 -30
- package/dist/plugins/workos-auth/src/manifest.ts +0 -40
- package/dist/plugins/workos-auth/src/provider-contract.ts +0 -39
- package/dist/plugins/workos-auth/test/manifest.test.ts +0 -94
- package/dist/plugins/workos-auth/tsconfig.json +0 -8
- package/dist/terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base +0 -61
- package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/sitecustomize.py +0 -227
- package/dist/terraform/modules/app/agentcore-code-interpreter/sandbox/test_sitecustomize.py +0 -277
- package/dist/terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh +0 -70
- package/dist/terraform/modules/app/agentcore-harness/main.tf +0 -101
- package/dist/terraform/modules/app/agentcore-harness/outputs.tf +0 -9
- package/dist/terraform/modules/app/agentcore-harness/variables.tf +0 -25
- package/dist/terraform/modules/app/hindsight-memory/README.md +0 -89
- package/dist/terraform/modules/app/hindsight-memory/main.tf +0 -411
- package/dist/terraform/modules/data/bedrock-knowledge-base/main.tf +0 -102
- /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/outputs.tf +0 -0
- /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/scripts/sync-database.py +0 -0
- /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/scripts/test_sync_database.py +0 -0
- /package/dist/{plugins/n8n/terraform → terraform/modules/app}/n8n/variables.tf +0 -0
- /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/README.md +0 -0
- /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/main.tf +0 -0
- /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/outputs.tf +0 -0
- /package/dist/{plugins/twenty/terraform → terraform/modules/app}/twenty/variables.tf +0 -0
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
"""sitecustomize — primary R13 token redactor for the AgentCore sandbox base image.
|
|
2
|
-
|
|
3
|
-
Python auto-imports any module named ``sitecustomize`` during interpreter
|
|
4
|
-
startup (before user code, before the preamble, before framework tracebacks on
|
|
5
|
-
cold start). This file installs a stdio write-wrapper keyed on a session-scoped
|
|
6
|
-
set of token values so no token leaks to stdout / stderr via Python-stdio
|
|
7
|
-
writes.
|
|
8
|
-
|
|
9
|
-
The invariant is honestly scoped per the brainstorm's R13 (as aligned in
|
|
10
|
-
docs/brainstorms/2026-04-22-agentcore-code-sandbox-requirements.md):
|
|
11
|
-
|
|
12
|
-
"no token value reaches a persisted log ... via Python-stdio-mediated writes"
|
|
13
|
-
|
|
14
|
-
Bytes that bypass Python stdio (``os.write(fd, ...)``, subprocesses inheriting
|
|
15
|
-
fds, C extensions, multiprocessing children) are the named residual class
|
|
16
|
-
covered by the Unit 12 CloudWatch subscription-filter backstop for known
|
|
17
|
-
OAuth prefixes, not by this module.
|
|
18
|
-
|
|
19
|
-
Public API, callable from the sandbox preamble:
|
|
20
|
-
register_token(value: str) -> None
|
|
21
|
-
register_tokens(values: Iterable[str]) -> None
|
|
22
|
-
clear_tokens() -> None
|
|
23
|
-
installed() -> bool
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
from __future__ import annotations
|
|
27
|
-
|
|
28
|
-
import base64
|
|
29
|
-
import binascii
|
|
30
|
-
import os
|
|
31
|
-
import sys
|
|
32
|
-
import urllib.parse
|
|
33
|
-
from collections.abc import Iterable
|
|
34
|
-
from typing import Any
|
|
35
|
-
|
|
36
|
-
# ---------------------------------------------------------------------------
|
|
37
|
-
# Module state
|
|
38
|
-
# ---------------------------------------------------------------------------
|
|
39
|
-
|
|
40
|
-
# Every unique byte-sequence we redact. Populated by ``register_token`` — each
|
|
41
|
-
# registered token expands into its cleartext, standard base64, URL-safe
|
|
42
|
-
# base64, URL-encoded, and hex forms (only the forms whose length is at least
|
|
43
|
-
# MIN_TOKEN_LEN, to avoid redacting short accidental matches like "AQ").
|
|
44
|
-
_redactions: set[bytes] = set()
|
|
45
|
-
|
|
46
|
-
# Redaction placeholder. Length unimportant — stream layout survives because
|
|
47
|
-
# writes stay synchronous.
|
|
48
|
-
_REDACTION = b"<redacted>"
|
|
49
|
-
|
|
50
|
-
# Forms shorter than this are rejected — redacting 3-character strings is the
|
|
51
|
-
# shape of a wildcard bug, not a security control.
|
|
52
|
-
MIN_TOKEN_LEN = 12
|
|
53
|
-
|
|
54
|
-
# Rolling-suffix buffer size. Set at ``register_token`` time to
|
|
55
|
-
# max(existing, 2 * len(new_token_form)). The wrapper keeps this many bytes
|
|
56
|
-
# from the previous write and prepends them to the current write before
|
|
57
|
-
# searching, so a token split across two ``write()`` calls is caught as long
|
|
58
|
-
# as neither split half exceeds the buffer. Adversarial fragmentation at
|
|
59
|
-
# arbitrary positions is a named residual (see R13).
|
|
60
|
-
_rolling_buffer_size: int = 0
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def _expand(value: str) -> list[bytes]:
|
|
64
|
-
"""Return the encoded forms of a token value we treat as equivalent.
|
|
65
|
-
|
|
66
|
-
Each form is added to the redaction set so ``print(base64.b64encode(token))``
|
|
67
|
-
and the cleartext ``print(token)`` are both scrubbed.
|
|
68
|
-
"""
|
|
69
|
-
if not value:
|
|
70
|
-
return []
|
|
71
|
-
raw = value.encode("utf-8")
|
|
72
|
-
forms: list[bytes] = []
|
|
73
|
-
|
|
74
|
-
def maybe_add(candidate: bytes) -> None:
|
|
75
|
-
if len(candidate) >= MIN_TOKEN_LEN:
|
|
76
|
-
forms.append(candidate)
|
|
77
|
-
|
|
78
|
-
maybe_add(raw)
|
|
79
|
-
maybe_add(base64.b64encode(raw).rstrip(b"="))
|
|
80
|
-
maybe_add(base64.b64encode(raw))
|
|
81
|
-
maybe_add(base64.urlsafe_b64encode(raw).rstrip(b"="))
|
|
82
|
-
maybe_add(base64.urlsafe_b64encode(raw))
|
|
83
|
-
maybe_add(urllib.parse.quote(value, safe="").encode("ascii"))
|
|
84
|
-
maybe_add(binascii.hexlify(raw))
|
|
85
|
-
return forms
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def register_token(value: str) -> None:
|
|
89
|
-
"""Add a token (and its derived encodings) to the redaction set.
|
|
90
|
-
|
|
91
|
-
Safe to call multiple times with the same value. No-op for empty values.
|
|
92
|
-
"""
|
|
93
|
-
global _rolling_buffer_size
|
|
94
|
-
for form in _expand(value):
|
|
95
|
-
_redactions.add(form)
|
|
96
|
-
required = 2 * len(form)
|
|
97
|
-
if required > _rolling_buffer_size:
|
|
98
|
-
_rolling_buffer_size = required
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def register_tokens(values: Iterable[str]) -> None:
|
|
102
|
-
for v in values:
|
|
103
|
-
register_token(v)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def clear_tokens() -> None:
|
|
107
|
-
"""Drop every registered token. Called at session teardown by the container."""
|
|
108
|
-
global _rolling_buffer_size
|
|
109
|
-
_redactions.clear()
|
|
110
|
-
_rolling_buffer_size = 0
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
# ---------------------------------------------------------------------------
|
|
114
|
-
# Stdio wrapper
|
|
115
|
-
# ---------------------------------------------------------------------------
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def _redact(chunk: bytes, tail_buffer: bytearray) -> tuple[bytes, bytearray]:
|
|
119
|
-
"""Return (scrubbed_output_chunk, new_tail_buffer).
|
|
120
|
-
|
|
121
|
-
The tail buffer holds the trailing N bytes of the previous write so we can
|
|
122
|
-
catch tokens split across write boundaries. The output we emit is the part
|
|
123
|
-
of ``tail_buffer + chunk`` that is safely past the trailing window; the
|
|
124
|
-
suffix stays in the new tail for the next call.
|
|
125
|
-
"""
|
|
126
|
-
if not _redactions:
|
|
127
|
-
return chunk, tail_buffer
|
|
128
|
-
|
|
129
|
-
combined = bytes(tail_buffer) + chunk
|
|
130
|
-
for form in _redactions:
|
|
131
|
-
combined = combined.replace(form, _REDACTION)
|
|
132
|
-
|
|
133
|
-
if _rolling_buffer_size and len(combined) > _rolling_buffer_size:
|
|
134
|
-
emit = combined[:-_rolling_buffer_size]
|
|
135
|
-
new_tail = bytearray(combined[-_rolling_buffer_size:])
|
|
136
|
-
else:
|
|
137
|
-
emit = b""
|
|
138
|
-
new_tail = bytearray(combined)
|
|
139
|
-
|
|
140
|
-
return emit, new_tail
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
class _RedactingStream:
|
|
144
|
-
"""Wraps sys.stdout / sys.stderr's underlying buffer with a redactor.
|
|
145
|
-
|
|
146
|
-
Intercepts both ``write(str)`` (TextIOBase) and ``.buffer.write(bytes)``
|
|
147
|
-
paths so ``print(...)``, ``sys.stdout.write(...)``, and
|
|
148
|
-
``sys.stdout.buffer.write(...)`` all flow through the scrubber.
|
|
149
|
-
"""
|
|
150
|
-
|
|
151
|
-
def __init__(self, wrapped: Any, name: str) -> None:
|
|
152
|
-
self._wrapped = wrapped
|
|
153
|
-
self._name = name
|
|
154
|
-
self._tail = bytearray()
|
|
155
|
-
# AgentCore writes to stdout/stderr line-buffered; we flush our tail
|
|
156
|
-
# on every newline-terminated write so output appears promptly.
|
|
157
|
-
|
|
158
|
-
def write(self, data: Any) -> int:
|
|
159
|
-
if isinstance(data, str):
|
|
160
|
-
payload = data.encode("utf-8", errors="replace")
|
|
161
|
-
wrote_str = True
|
|
162
|
-
elif isinstance(data, (bytes, bytearray, memoryview)):
|
|
163
|
-
payload = bytes(data)
|
|
164
|
-
wrote_str = False
|
|
165
|
-
else:
|
|
166
|
-
# Defer unknown types to the wrapped object — let it raise.
|
|
167
|
-
return self._wrapped.write(data)
|
|
168
|
-
|
|
169
|
-
emit, self._tail = _redact(payload, self._tail)
|
|
170
|
-
# A newline forces a tail flush so output doesn't lag arbitrarily when
|
|
171
|
-
# no new writes arrive. This is a UX concession, not a security one —
|
|
172
|
-
# redaction is complete whether we flush or not.
|
|
173
|
-
if b"\n" in payload:
|
|
174
|
-
emit = emit + bytes(self._tail)
|
|
175
|
-
self._tail = bytearray()
|
|
176
|
-
|
|
177
|
-
if not emit:
|
|
178
|
-
return len(data) if wrote_str else len(payload)
|
|
179
|
-
|
|
180
|
-
if wrote_str:
|
|
181
|
-
self._wrapped.write(emit.decode("utf-8", errors="replace"))
|
|
182
|
-
else:
|
|
183
|
-
# TextIOWrapper.write raises on bytes; prefer the underlying buffer.
|
|
184
|
-
buf = getattr(self._wrapped, "buffer", None)
|
|
185
|
-
if buf is not None:
|
|
186
|
-
buf.write(emit)
|
|
187
|
-
else:
|
|
188
|
-
self._wrapped.write(emit)
|
|
189
|
-
return len(data) if wrote_str else len(payload)
|
|
190
|
-
|
|
191
|
-
def flush(self) -> None:
|
|
192
|
-
if self._tail:
|
|
193
|
-
data = bytes(self._tail)
|
|
194
|
-
self._tail = bytearray()
|
|
195
|
-
buf = getattr(self._wrapped, "buffer", None)
|
|
196
|
-
if buf is not None:
|
|
197
|
-
buf.write(data)
|
|
198
|
-
else:
|
|
199
|
-
self._wrapped.write(data.decode("utf-8", errors="replace"))
|
|
200
|
-
self._wrapped.flush()
|
|
201
|
-
|
|
202
|
-
# Forward any other attribute access (fileno, isatty, encoding, etc.) so
|
|
203
|
-
# libraries that introspect the stream still see what they expect.
|
|
204
|
-
def __getattr__(self, item: str) -> Any:
|
|
205
|
-
return getattr(self._wrapped, item)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
_installed = False
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
def installed() -> bool:
|
|
212
|
-
return _installed
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def _install() -> None:
|
|
216
|
-
global _installed
|
|
217
|
-
if _installed:
|
|
218
|
-
return
|
|
219
|
-
sys.stdout = _RedactingStream(sys.stdout, "stdout")
|
|
220
|
-
sys.stderr = _RedactingStream(sys.stderr, "stderr")
|
|
221
|
-
_installed = True
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
# Install unconditionally on import. Disable only for a pytest diagnostic
|
|
225
|
-
# marker the test suite can opt out of (set THINKWORK_SANDBOX_SCRUBBER=off).
|
|
226
|
-
if os.environ.get("THINKWORK_SANDBOX_SCRUBBER", "on") != "off":
|
|
227
|
-
_install()
|
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
"""Unit tests for the AgentCore sandbox base-image stdio token redactor.
|
|
2
|
-
|
|
3
|
-
Tests hit ``_RedactingStream`` directly with a test-owned stream instead of
|
|
4
|
-
mutating ``sys.stdout`` — that side-steps pytest's capture machinery, which
|
|
5
|
-
owns sys.stdout at test time and would otherwise see writes before the
|
|
6
|
-
wrapper does. Correctness of the sys.stdout replacement path is proven by
|
|
7
|
-
a separate end-to-end test that runs the module as a subprocess with its
|
|
8
|
-
auto-install path active.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
import base64
|
|
14
|
-
import binascii
|
|
15
|
-
import importlib.util
|
|
16
|
-
import io
|
|
17
|
-
import os
|
|
18
|
-
import subprocess
|
|
19
|
-
import sys
|
|
20
|
-
import urllib.parse
|
|
21
|
-
|
|
22
|
-
import pytest
|
|
23
|
-
|
|
24
|
-
os.environ["THINKWORK_SANDBOX_SCRUBBER"] = "off"
|
|
25
|
-
|
|
26
|
-
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
27
|
-
_SPEC = importlib.util.spec_from_file_location(
|
|
28
|
-
"sandbox_base_sitecustomize",
|
|
29
|
-
os.path.join(_HERE, "sitecustomize.py"),
|
|
30
|
-
)
|
|
31
|
-
assert _SPEC is not None and _SPEC.loader is not None
|
|
32
|
-
sc = importlib.util.module_from_spec(_SPEC)
|
|
33
|
-
_SPEC.loader.exec_module(sc)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
class _StringSink:
|
|
37
|
-
"""Minimal sys.stdout-shaped target: the wrapper writes strings into us,
|
|
38
|
-
we collect them as UTF-8 bytes. Also exposes ``.buffer`` so bytes-path
|
|
39
|
-
writes have a path through the wrapper."""
|
|
40
|
-
|
|
41
|
-
def __init__(self) -> None:
|
|
42
|
-
self.str_bytes = bytearray()
|
|
43
|
-
self.buffer = io.BytesIO()
|
|
44
|
-
self.name = "test-sink"
|
|
45
|
-
self.encoding = "utf-8"
|
|
46
|
-
self.errors = "strict"
|
|
47
|
-
|
|
48
|
-
def write(self, s: str) -> int:
|
|
49
|
-
self.str_bytes.extend(s.encode("utf-8", errors="replace"))
|
|
50
|
-
return len(s)
|
|
51
|
-
|
|
52
|
-
def flush(self) -> None:
|
|
53
|
-
pass
|
|
54
|
-
|
|
55
|
-
def captured(self) -> str:
|
|
56
|
-
combined = bytes(self.str_bytes) + self.buffer.getvalue()
|
|
57
|
-
return combined.decode("utf-8", errors="replace")
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@pytest.fixture
|
|
61
|
-
def wrapper() -> tuple:
|
|
62
|
-
"""A fresh redactor wrapping a new sink, with an empty token set."""
|
|
63
|
-
sc.clear_tokens()
|
|
64
|
-
sink = _StringSink()
|
|
65
|
-
stream = sc._RedactingStream(sink, "test-stdout") # type: ignore[attr-defined]
|
|
66
|
-
yield stream, sink
|
|
67
|
-
stream.flush()
|
|
68
|
-
sc.clear_tokens()
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def test_cleartext_is_redacted(wrapper):
|
|
72
|
-
stream, sink = wrapper
|
|
73
|
-
token = "ghp_abc123abc123abc123"
|
|
74
|
-
sc.register_token(token)
|
|
75
|
-
stream.write(f"the token was {token} and not noticeable\n")
|
|
76
|
-
stream.flush()
|
|
77
|
-
text = sink.captured()
|
|
78
|
-
assert token not in text
|
|
79
|
-
assert "<redacted>" in text
|
|
80
|
-
assert "the token was" in text
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
def test_base64_form_is_redacted(wrapper):
|
|
84
|
-
stream, sink = wrapper
|
|
85
|
-
token = "ghp_abc123abc123abc123"
|
|
86
|
-
sc.register_token(token)
|
|
87
|
-
encoded = base64.b64encode(token.encode()).decode()
|
|
88
|
-
stream.write(f"encoded={encoded}\n")
|
|
89
|
-
stream.flush()
|
|
90
|
-
text = sink.captured()
|
|
91
|
-
assert encoded not in text
|
|
92
|
-
assert "<redacted>" in text
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def test_urlsafe_base64_form_is_redacted(wrapper):
|
|
96
|
-
stream, sink = wrapper
|
|
97
|
-
token = "some_opaque_secret_for_urlsafe"
|
|
98
|
-
sc.register_token(token)
|
|
99
|
-
encoded = base64.urlsafe_b64encode(token.encode()).decode()
|
|
100
|
-
stream.write(f"encoded={encoded}\n")
|
|
101
|
-
stream.flush()
|
|
102
|
-
text = sink.captured()
|
|
103
|
-
assert encoded not in text
|
|
104
|
-
assert "<redacted>" in text
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def test_url_encoded_form_is_redacted(wrapper):
|
|
108
|
-
stream, sink = wrapper
|
|
109
|
-
token = "token with spaces & special=chars"
|
|
110
|
-
sc.register_token(token)
|
|
111
|
-
encoded = urllib.parse.quote(token, safe="")
|
|
112
|
-
stream.write(f"in_url={encoded}\n")
|
|
113
|
-
stream.flush()
|
|
114
|
-
text = sink.captured()
|
|
115
|
-
assert encoded not in text
|
|
116
|
-
assert "<redacted>" in text
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def test_hex_form_is_redacted(wrapper):
|
|
120
|
-
stream, sink = wrapper
|
|
121
|
-
token = "0123456789abcdef01234567"
|
|
122
|
-
sc.register_token(token)
|
|
123
|
-
encoded = binascii.hexlify(token.encode()).decode()
|
|
124
|
-
stream.write(f"hex={encoded}\n")
|
|
125
|
-
stream.flush()
|
|
126
|
-
text = sink.captured()
|
|
127
|
-
assert encoded not in text
|
|
128
|
-
assert "<redacted>" in text
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def test_register_after_first_write_still_redacts_subsequent(wrapper):
|
|
132
|
-
stream, sink = wrapper
|
|
133
|
-
stream.write("before registration\n")
|
|
134
|
-
token = "late_registered_token_value"
|
|
135
|
-
sc.register_token(token)
|
|
136
|
-
stream.write(f"after {token} registration\n")
|
|
137
|
-
stream.flush()
|
|
138
|
-
text = sink.captured()
|
|
139
|
-
assert "before registration" in text
|
|
140
|
-
assert token not in text
|
|
141
|
-
assert "<redacted>" in text
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def test_split_write_across_boundary_is_caught(wrapper):
|
|
145
|
-
stream, sink = wrapper
|
|
146
|
-
token = "ghp_splitfragmenttokenvalue"
|
|
147
|
-
sc.register_token(token)
|
|
148
|
-
stream.write(token[:10])
|
|
149
|
-
stream.write(token[10:])
|
|
150
|
-
stream.write("\n")
|
|
151
|
-
stream.flush()
|
|
152
|
-
text = sink.captured()
|
|
153
|
-
assert token not in text
|
|
154
|
-
assert "<redacted>" in text
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def test_short_token_below_min_len_is_ignored(wrapper):
|
|
158
|
-
"""Redacting 3-char strings would produce wildcard false-positives."""
|
|
159
|
-
stream, sink = wrapper
|
|
160
|
-
sc.register_token("AQ")
|
|
161
|
-
stream.write("AQ should appear unchanged because AQ is too short to redact\n")
|
|
162
|
-
stream.flush()
|
|
163
|
-
text = sink.captured()
|
|
164
|
-
assert "AQ" in text
|
|
165
|
-
assert "<redacted>" not in text
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def test_bytes_path_through_buffer(wrapper):
|
|
169
|
-
stream, sink = wrapper
|
|
170
|
-
token = "some_token_value_we_registered"
|
|
171
|
-
sc.register_token(token)
|
|
172
|
-
# Write bytes through the wrapper — it routes into sink.buffer.
|
|
173
|
-
stream.write(b"\x00\x01\x02binary with " + token.encode() + b" embedded\n")
|
|
174
|
-
stream.flush()
|
|
175
|
-
text = sink.captured()
|
|
176
|
-
assert token not in text
|
|
177
|
-
assert "<redacted>" in text
|
|
178
|
-
assert "binary with" in text
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def test_clear_tokens_disables_redaction_for_later_writes(wrapper):
|
|
182
|
-
stream, sink = wrapper
|
|
183
|
-
token = "a_token_that_will_be_cleared_soon"
|
|
184
|
-
sc.register_token(token)
|
|
185
|
-
stream.write(f"before clear: {token}\n")
|
|
186
|
-
sc.clear_tokens()
|
|
187
|
-
stream.write(f"after clear: {token}\n")
|
|
188
|
-
stream.flush()
|
|
189
|
-
text = sink.captured()
|
|
190
|
-
assert text.count("<redacted>") == 1
|
|
191
|
-
assert text.count(token) == 1
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def test_register_tokens_plural_is_equivalent(wrapper):
|
|
195
|
-
stream, sink = wrapper
|
|
196
|
-
sc.register_tokens(["first_registered_token_abc", "second_registered_token_def"])
|
|
197
|
-
stream.write("first_registered_token_abc / second_registered_token_def\n")
|
|
198
|
-
stream.flush()
|
|
199
|
-
text = sink.captured()
|
|
200
|
-
assert "first_registered_token_abc" not in text
|
|
201
|
-
assert "second_registered_token_def" not in text
|
|
202
|
-
assert text.count("<redacted>") >= 2
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def test_empty_token_is_ignored(wrapper):
|
|
206
|
-
stream, sink = wrapper
|
|
207
|
-
sc.register_token("")
|
|
208
|
-
stream.write("nothing to redact here\n")
|
|
209
|
-
stream.flush()
|
|
210
|
-
text = sink.captured()
|
|
211
|
-
assert "nothing to redact here" in text
|
|
212
|
-
assert "<redacted>" not in text
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def test_no_crash_on_raw_bytes_without_matching_token(wrapper):
|
|
216
|
-
stream, sink = wrapper
|
|
217
|
-
sc.register_token("something_registered_but_not_present")
|
|
218
|
-
stream.write(b"\x00\x01\x02binary bytes\n")
|
|
219
|
-
stream.flush()
|
|
220
|
-
text = sink.captured()
|
|
221
|
-
assert "binary bytes" in text
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
# --- End-to-end: the auto-install path ---------------------------------------
|
|
225
|
-
#
|
|
226
|
-
# Tests above exercise _RedactingStream directly, which is fine for the
|
|
227
|
-
# redaction logic itself but bypasses the sitecustomize auto-install step
|
|
228
|
-
# that replaces sys.stdout on interpreter startup. This subprocess test
|
|
229
|
-
# proves that step works — a fresh Python process, with PYTHONPATH pointed
|
|
230
|
-
# at the module directory and THINKWORK_SANDBOX_SCRUBBER unset, installs
|
|
231
|
-
# the wrapper on its own and redacts a registered token before stdout is
|
|
232
|
-
# flushed.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def test_auto_install_redacts_on_fresh_interpreter(tmp_path):
|
|
236
|
-
script = tmp_path / "run.py"
|
|
237
|
-
script.write_text(
|
|
238
|
-
"""
|
|
239
|
-
import sys, sitecustomize
|
|
240
|
-
sitecustomize.register_token("ghp_autoInstallValueTokenX")
|
|
241
|
-
print("auto-install redacts ghp_autoInstallValueTokenX mid-line here")
|
|
242
|
-
""".strip()
|
|
243
|
-
)
|
|
244
|
-
env = os.environ.copy()
|
|
245
|
-
# Strip our opt-out so sitecustomize installs on import.
|
|
246
|
-
env.pop("THINKWORK_SANDBOX_SCRUBBER", None)
|
|
247
|
-
# Prepend the module dir so Python's startup hook finds our
|
|
248
|
-
# sitecustomize.py before any installed one.
|
|
249
|
-
env["PYTHONPATH"] = _HERE + os.pathsep + env.get("PYTHONPATH", "")
|
|
250
|
-
out = subprocess.run(
|
|
251
|
-
[sys.executable, str(script)],
|
|
252
|
-
env=env,
|
|
253
|
-
check=True,
|
|
254
|
-
capture_output=True,
|
|
255
|
-
text=True,
|
|
256
|
-
)
|
|
257
|
-
assert "ghp_autoInstallValueTokenX" not in out.stdout
|
|
258
|
-
assert "<redacted>" in out.stdout
|
|
259
|
-
assert "auto-install redacts" in out.stdout
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
def test_installed_reports_true_after_auto_install(tmp_path):
|
|
263
|
-
"""Probe ``installed()`` from a fresh interpreter — confirms the startup
|
|
264
|
-
hook ran (and set the flag)."""
|
|
265
|
-
script = tmp_path / "run.py"
|
|
266
|
-
script.write_text("import sitecustomize; print(sitecustomize.installed())")
|
|
267
|
-
env = os.environ.copy()
|
|
268
|
-
env.pop("THINKWORK_SANDBOX_SCRUBBER", None)
|
|
269
|
-
env["PYTHONPATH"] = _HERE + os.pathsep + env.get("PYTHONPATH", "")
|
|
270
|
-
out = subprocess.run(
|
|
271
|
-
[sys.executable, str(script)],
|
|
272
|
-
env=env,
|
|
273
|
-
check=True,
|
|
274
|
-
capture_output=True,
|
|
275
|
-
text=True,
|
|
276
|
-
)
|
|
277
|
-
assert out.stdout.strip() == "True"
|
package/dist/terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
################################################################################
|
|
3
|
-
# build_and_push_sandbox_base.sh — build + push the AgentCore sandbox base
|
|
4
|
-
# image to the stage's ECR repo. Invoked by CI; also safe to run locally
|
|
5
|
-
# once ``thinkwork doctor`` reports green.
|
|
6
|
-
#
|
|
7
|
-
# Usage (from repo root):
|
|
8
|
-
# bash terraform/modules/app/agentcore-code-interpreter/scripts/build_and_push_sandbox_base.sh \
|
|
9
|
-
# --stage dev --region us-east-1
|
|
10
|
-
#
|
|
11
|
-
# The script:
|
|
12
|
-
# 1. Reads the ECR repo URL from ``terraform output -raw`` (must be run
|
|
13
|
-
# after ``terraform apply`` lands the agentcore-code-interpreter
|
|
14
|
-
# module for the target stage).
|
|
15
|
-
# 2. docker build the Dockerfile.sandbox-base with the repo root as
|
|
16
|
-
# context (so the COPY can reach packages/).
|
|
17
|
-
# 3. Tags with the current git SHA (immutable tag per the ECR repo
|
|
18
|
-
# config) and ``:latest``.
|
|
19
|
-
# 4. docker push both tags after ``aws ecr get-login-password``.
|
|
20
|
-
################################################################################
|
|
21
|
-
set -euo pipefail
|
|
22
|
-
|
|
23
|
-
STAGE=""
|
|
24
|
-
REGION=""
|
|
25
|
-
|
|
26
|
-
while [[ $# -gt 0 ]]; do
|
|
27
|
-
case "$1" in
|
|
28
|
-
--stage) STAGE="$2"; shift 2 ;;
|
|
29
|
-
--region) REGION="$2"; shift 2 ;;
|
|
30
|
-
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
|
31
|
-
esac
|
|
32
|
-
done
|
|
33
|
-
|
|
34
|
-
: "${STAGE:?--stage is required}"
|
|
35
|
-
: "${REGION:?--region is required}"
|
|
36
|
-
|
|
37
|
-
# Resolve the repo URL from terraform state. Tolerant: if the caller runs
|
|
38
|
-
# this before the module applies, fall back to the deterministic name.
|
|
39
|
-
REPO_URL="$(
|
|
40
|
-
terraform -chdir="terraform/examples/greenfield" output -raw \
|
|
41
|
-
sandbox_base_ecr_repository_url 2>/dev/null \
|
|
42
|
-
|| echo ""
|
|
43
|
-
)"
|
|
44
|
-
if [[ -z "$REPO_URL" ]]; then
|
|
45
|
-
ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
|
|
46
|
-
REPO_URL="${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/thinkwork-${STAGE}-sandbox-base"
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
GIT_SHA="$(git rev-parse --short HEAD)"
|
|
50
|
-
IMAGE_TAG="${GIT_SHA}"
|
|
51
|
-
|
|
52
|
-
echo "[sandbox-base] repo: ${REPO_URL}"
|
|
53
|
-
echo "[sandbox-base] tag: ${IMAGE_TAG}"
|
|
54
|
-
|
|
55
|
-
# ECR login
|
|
56
|
-
aws ecr get-login-password --region "${REGION}" \
|
|
57
|
-
| docker login --username AWS --password-stdin "${REPO_URL%/*}"
|
|
58
|
-
|
|
59
|
-
# Build with repo root as the context so COPY can reach packages/.
|
|
60
|
-
docker build \
|
|
61
|
-
--platform=linux/amd64 \
|
|
62
|
-
-f terraform/modules/app/agentcore-code-interpreter/Dockerfile.sandbox-base \
|
|
63
|
-
-t "${REPO_URL}:${IMAGE_TAG}" \
|
|
64
|
-
-t "${REPO_URL}:latest" \
|
|
65
|
-
.
|
|
66
|
-
|
|
67
|
-
docker push "${REPO_URL}:${IMAGE_TAG}"
|
|
68
|
-
docker push "${REPO_URL}:latest"
|
|
69
|
-
|
|
70
|
-
echo "[sandbox-base] pushed ${REPO_URL}:${IMAGE_TAG}"
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
################################################################################
|
|
2
|
-
# AgentCore Harness — App Module (THINK-311 U4, ship-inert)
|
|
3
|
-
#
|
|
4
|
-
# IAM surface for the AWS Bedrock AgentCore Harness trial: the execution role
|
|
5
|
-
# Harness microVMs assume, plus (via outputs consumed by lambda-api's grouped
|
|
6
|
-
# policies) the invoker grants the shared api Lambda role needs to create and
|
|
7
|
-
# invoke Harness resources. Nothing reads or invokes any of this until the
|
|
8
|
-
# harness-runner handler lands in U5 — this module is pure IAM.
|
|
9
|
-
#
|
|
10
|
-
# Trust-policy note: sibling agentcore roles (agentcore-pi, agentcore-runtime)
|
|
11
|
-
# trust the bare `bedrock-agentcore.amazonaws.com` service principal with no
|
|
12
|
-
# account/source-arn conditions; this role mirrors that repo pattern.
|
|
13
|
-
################################################################################
|
|
14
|
-
|
|
15
|
-
################################################################################
|
|
16
|
-
# Harness execution role — assumed by Harness microVMs
|
|
17
|
-
################################################################################
|
|
18
|
-
|
|
19
|
-
resource "aws_iam_role" "harness_execution" {
|
|
20
|
-
count = var.enabled ? 1 : 0
|
|
21
|
-
|
|
22
|
-
name = "thinkwork-${var.stage}-agentcore-harness-role"
|
|
23
|
-
|
|
24
|
-
assume_role_policy = jsonencode({
|
|
25
|
-
Version = "2012-10-17"
|
|
26
|
-
Statement = [{
|
|
27
|
-
Effect = "Allow"
|
|
28
|
-
Principal = { Service = "bedrock-agentcore.amazonaws.com" }
|
|
29
|
-
Action = "sts:AssumeRole"
|
|
30
|
-
}]
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
tags = {
|
|
34
|
-
Name = "thinkwork-${var.stage}-agentcore-harness-role"
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
resource "aws_iam_role_policy" "harness_execution" {
|
|
39
|
-
count = var.enabled ? 1 : 0
|
|
40
|
-
|
|
41
|
-
name = "agentcore-harness-permissions"
|
|
42
|
-
role = aws_iam_role.harness_execution[0].id
|
|
43
|
-
|
|
44
|
-
policy = jsonencode({
|
|
45
|
-
Version = "2012-10-17"
|
|
46
|
-
Statement = [
|
|
47
|
-
{
|
|
48
|
-
# Cross-region inference profiles (us.anthropic.claude-*) require
|
|
49
|
-
# `bedrock:InvokeModel` on the *inference-profile* ARN AND on the
|
|
50
|
-
# underlying foundation-model ARN in every region the profile can
|
|
51
|
-
# route to — same resource list the api Lambda's grouped ai policy
|
|
52
|
-
# uses (see ../lambda-api/iam-grouped.tf "bedrock-invoke").
|
|
53
|
-
Sid = "BedrockInvoke"
|
|
54
|
-
Effect = "Allow"
|
|
55
|
-
Action = ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"]
|
|
56
|
-
Resource = [
|
|
57
|
-
"arn:aws:bedrock:*::foundation-model/*",
|
|
58
|
-
"arn:aws:bedrock:*:${var.account_id}:inference-profile/*",
|
|
59
|
-
]
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
# Tenant skill catalog sources + materialized workspace skill folders
|
|
63
|
-
# live under tenants/* in the stage workspace bucket. Read-only —
|
|
64
|
-
# Harness microVMs never write back to the workspace bucket.
|
|
65
|
-
Sid = "WorkspaceSkillSourcesRead"
|
|
66
|
-
Effect = "Allow"
|
|
67
|
-
Action = ["s3:GetObject"]
|
|
68
|
-
Resource = "arn:aws:s3:::${var.bucket_name}/tenants/*"
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
Sid = "WorkspaceSkillSourcesList"
|
|
72
|
-
Effect = "Allow"
|
|
73
|
-
Action = ["s3:ListBucket"]
|
|
74
|
-
Resource = "arn:aws:s3:::${var.bucket_name}"
|
|
75
|
-
Condition = {
|
|
76
|
-
StringLike = {
|
|
77
|
-
"s3:prefix" = "tenants/*"
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
# Harness containers log to the service-managed bedrock-agentcore
|
|
83
|
-
# log-group namespace, mirroring the agentcore-pi role's runtimes
|
|
84
|
-
# grant.
|
|
85
|
-
Sid = "CloudWatchLogs"
|
|
86
|
-
Effect = "Allow"
|
|
87
|
-
Action = [
|
|
88
|
-
"logs:CreateLogGroup",
|
|
89
|
-
"logs:CreateLogStream",
|
|
90
|
-
"logs:DescribeLogGroups",
|
|
91
|
-
"logs:DescribeLogStreams",
|
|
92
|
-
"logs:PutLogEvents",
|
|
93
|
-
]
|
|
94
|
-
Resource = [
|
|
95
|
-
"arn:aws:logs:${var.region}:${var.account_id}:log-group:/aws/bedrock-agentcore/*",
|
|
96
|
-
"arn:aws:logs:${var.region}:${var.account_id}:log-group:/aws/bedrock-agentcore/*:*",
|
|
97
|
-
]
|
|
98
|
-
},
|
|
99
|
-
]
|
|
100
|
-
})
|
|
101
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
output "execution_role_arn" {
|
|
2
|
-
description = "ARN of the Harness execution role (CreateHarness executionRoleArn). Empty string when the module is disabled."
|
|
3
|
-
value = var.enabled ? aws_iam_role.harness_execution[0].arn : ""
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
output "execution_role_name" {
|
|
7
|
-
description = "Name of the Harness execution role. Empty string when the module is disabled."
|
|
8
|
-
value = var.enabled ? aws_iam_role.harness_execution[0].name : ""
|
|
9
|
-
}
|