Feat v0.8.0 files sandbox module
All checks were successful
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-frontend (pull_request) Has been skipped
CI/CD / test-runners (pull_request) Has been skipped
CI/CD / e2e-smoke (pull_request) Has been skipped
CI/CD / test-go-pg (pull_request) Successful in 2m44s
CI/CD / test-sqlite (pull_request) Successful in 2m57s
CI/CD / build-and-deploy (pull_request) Successful in 1m25s

Bridge ObjectStore (PVC/S3) into the Starlark sandbox with extension-scoped
key namespacing. New files module with put/get/meta/list/delete/exists
builtins gated by files.read and files.write permissions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 00:14:28 +00:00
parent 3b74774077
commit 5b72ed254f
13 changed files with 1169 additions and 5 deletions

View File

@@ -33,6 +33,7 @@ import (
"armature/events"
"armature/metrics"
"armature/models"
"armature/storage"
"armature/store"
)
@@ -77,6 +78,7 @@ type Runner struct {
dbPostgres bool // true = use $N placeholders; false = use ?
allowPrivateIPs bool
bus *events.Bus
objectStore storage.ObjectStore // nil = files module unavailable
}
// NewRunner creates a runner with the given sandbox and dependencies.
@@ -116,6 +118,11 @@ func (r *Runner) SetBus(bus *events.Bus) {
r.bus = bus
}
// SetObjectStore attaches the blob storage backend for the files module.
func (r *Runner) SetObjectStore(s storage.ObjectStore) {
r.objectStore = s
}
// SetAllowPrivateIPs disables the SSRF check that blocks connections to
// private/loopback IPs. For self-hosted environments where extensions
// reach internal services. Controlled by EXT_ALLOW_PRIVATE_IPS env var.
@@ -336,8 +343,9 @@ func (r *Runner) buildModulesWithLibCtx(ctx context.Context, packageID string, m
modules := make(map[string]starlark.Value)
// Track db permission level: 0=none, 1=read, 2=write
// Track tiered permission levels: 0=none, 1=read, 2=write
dbLevel := 0
filesLevel := 0
hasBatchExec := false
for _, perm := range granted {
@@ -363,6 +371,14 @@ func (r *Runner) buildModulesWithLibCtx(ctx context.Context, packageID string, m
case models.ExtPermDBWrite:
dbLevel = 2
case models.ExtPermFilesRead:
if filesLevel < 1 {
filesLevel = 1
}
case models.ExtPermFilesWrite:
filesLevel = 2
case models.ExtPermWorkflowAccess:
modules["workflow"] = BuildWorkflowModule(ctx, r.stores)
@@ -391,6 +407,15 @@ func (r *Runner) buildModulesWithLibCtx(ctx context.Context, packageID string, m
})
}
// Wire files module at the highest granted level.
if filesLevel > 0 && r.objectStore != nil {
modules["files"] = BuildFilesModule(ctx, FilesModuleConfig{
PackageID: packageID,
CanWrite: filesLevel == 2,
Store: r.objectStore,
})
}
// A package reads its own admin + team + user settings.
userID := ""
teamID := ""