Novedades de 0.7.0¶
Publicada el 2026-10-05: SDK Python rayito 0.7.0, SDK TypeScript
rayito 0.7.0 y rayd 0.7.0.
0.7.0 trae los volúmenes EFS (un sistema de ficheros compartido en vivo
entre sandboxes, dentro de tu VPC), el dominio propio como función
experimental, un entorno para probar en local sin cuenta de AWS y
un endurecimiento de seguridad que recorre rayd, los SDK, las
plantillas de infraestructura y la release. Como siempre, todo lo que tiene
coste está apagado por defecto: sin las opciones nuevas, un sandbox no
hace ninguna llamada nueva a AWS.
De un vistazo¶
| Novedad | Python | TypeScript | Necesita | Coste en reposo | Guía |
|---|---|---|---|---|---|
| Volúmenes EFS (experimental) | volumes={"/mnt/x": vol}, VolumeStore, EfsVolumes |
volumes: { "/mnt/x": vol }, VolumeStore, EfsVolumes |
imagen opcional rayito-base-caps-efs con rayd 0.7.0, una VPC tuya y la pila efs-volumes |
$0 vacío; EFS por GB | Volúmenes EFS |
| Dominio propio (experimental) | CustomDomain(...) + extra rayito[custom-domain] |
new CustomDomain({...}) + peers de CloudFront |
un dominio, un certificado ACM y la pila custom-domain |
$0 en reposo | Dominio propio |
| Probar en local | — | — | Docker y un clon del repositorio | $0, sin cuenta de AWS | Probar en local |
| Endurecimiento de seguridad | sin código que cambiar | sin código que cambiar | rayd 0.7.0 para lo que corre en el sandbox |
— | Seguridad |
Unos pocos cambios de comportamiento piden revisar tu configuración antes de actualizar: están en Cómo actualizar desde 0.6.x.
Volúmenes EFS¶
Un sistema de ficheros NFS en tu cuenta que rayd monta dentro del
sandbox: el análogo de Volume de E2B. A diferencia de persist= (una
copia en S3 que se restaura al crear y se guarda al pausar), un volumen EFS
se comparte en vivo entre varios sandboxes. Cada volumen es un access
point de EFS con su propio directorio y el usuario POSIX 1000:1000.
Son experimentales por dos motivos: exigen una imagen opcional y viven dentro de tu VPC. El montaje en sí está aceptado en AWS real en Python y TypeScript: escritura, sólo lectura, pausa y reanudación (también tras caducar las credenciales del túnel) y el borrado completo (Medido en AWS real).
1. Comprueba la VPC y despliega la pila¶
EfsVolumes.check() revisa una VPC que ya existe sin crear nada (sólo
ec2:Describe*): subredes en AZs distintas, IPs libres, DNS y cómo saldría
a internet. deploy() vuelve a comprobarla y se niega si algún hallazgo es
FAIL; si pasa, crea sólo recursos nuevos dentro de ella (un sistema de
ficheros cifrado, un mount target por subred, dos grupos de seguridad, un
conector de egress dedicado y la política del execution role) y nunca toca
la VPC, sus subredes, tablas de rutas ni grupos existentes.
from rayito import EfsVolumes
efs = EfsVolumes(region="us-east-1") # no llama a AWS
report = efs.check(
vpc_id="vpc-0123456789abcdef0",
subnet_ids=["subnet-0123456789abcdef0"],
)
for finding in report.findings:
print(finding.level, finding.code, finding.message)
status = efs.deploy( # se niega si algún hallazgo es FAIL
vpc_id="vpc-0123456789abcdef0",
subnet_ids=["subnet-0123456789abcdef0"],
)
print(status.outputs["ConnectorArn"], status.outputs["CallerPolicyArn"])
vol = efs.volume_store().create("datos-agente-7")
import { EfsVolumes } from "rayito"; // + npm i @aws-sdk/client-ec2 @aws-sdk/client-efs
const efs = new EfsVolumes({ region: "us-east-1" }); // no llama a AWS
const report = await efs.check({
vpcId: "vpc-0123456789abcdef0",
subnetIds: ["subnet-0123456789abcdef0"],
});
for (const finding of report.findings) {
console.log(finding.level, finding.code, finding.message);
}
const status = await efs.deploy({
vpcId: "vpc-0123456789abcdef0",
subnetIds: ["subnet-0123456789abcdef0"],
});
console.log(status.outputs.ConnectorArn, status.outputs.CallerPolicyArn);
const vol = await (await efs.volumeStore()).create("datos-agente-7");
Adjunta la salida CallerPolicyArn al execution role con el que lanzarás
los sandboxes y guarda ConnectorArn.
2. Publica la imagen con amazon-efs-utils¶
Las imágenes por defecto no cambian. volumes= sólo monta en
rayito-base-caps-efs, una variante caps que añade el mount helper de
EFS (≈ 198 MB más de imagen; el snapshot de memoria no cambia) y que
publicas tú con --with-efs:
make image-publish-caps-efs BUCKET=amzn-s3-demo-bucket
# o, paso a paso:
rayito image zip image image/rayito-image-efs.zip --sidecar kernel-sidecar --with-efs
rayito image publish --artifact image/rayito-image-efs.zip --with-efs \
--os-capabilities ALL --bucket amzn-s3-demo-bucket --base-image-version 1
rayito image publish --with-efs comprueba, antes de llamar a AWS, que el
zip trae el marcador de la variante y que pides --os-capabilities ALL.
rayito-base-caps-efs cuenta además como variante caps para mounts= y
telemetry=.
3. Monta los volúmenes¶
from rayito import Sandbox, VolumeStore
store = VolumeStore(file_system_id="fs-0123abcd", region="us-east-1")
datos = store.create("datos-agente-7")
with Sandbox.create(
"rayito-base-caps-efs",
execution_role_arn="arn:aws:iam::123456789012:role/rayito-execution",
egress=["arn:aws:lambda:us-east-1:123456789012:network-connector:rayito-efs"],
volumes={"/mnt/datos": datos},
) as sbx:
sbx.commands.run("echo hola > /mnt/datos/saludo.txt")
print(sbx.volumes["/mnt/datos"].state) # "mounted"
import { Sandbox, VolumeStore } from "rayito";
const store = new VolumeStore({ fileSystemId: "fs-0123abcd", region: "us-east-1" });
const datos = await store.create("datos-agente-7");
await using sbx = await Sandbox.create({
template: "rayito-base-caps-efs",
executionRoleArn: "arn:aws:iam::123456789012:role/rayito-execution",
egress: ["arn:aws:lambda:us-east-1:123456789012:network-connector:rayito-efs"],
volumes: { "/mnt/datos": datos },
});
await sbx.commands.run("echo hola > /mnt/datos/saludo.txt");
console.log((await sbx.volumes()).get("/mnt/datos")?.state); // "mounted"
create() valida la petición antes de lanzar nada (de 1 a 4 volúmenes,
rutas bajo /mnt/ o /home/user/, execution_role_arn= y la variante
caps), monta los volúmenes en el mismo ConfigureSandbox que el resto de
opciones y sólo vuelve con todos mounted. Si uno falla, termina el
sandbox (salvo keep_on_failure) y lanza VolumeMountException
(TypeScript: VolumeMountError) con un code cerrado (network,
iam_denied, not_found, tls, helper_missing, timeout,
invalid_path). reincarnate() vuelve a montarlos en el sucesor. En el
shim de E2B, Volume hace CRUD real sobre E2B(volume_store=...) y
volume_mounts= monta con volume_connector_arn=
(Shim E2B).
Un solo conector de egress¶
Un MicroVM admite un único conector de egress, y el volumen necesita el
de tu VPC para llegar al mount target. Por eso volumes= sin egress=,
con INTERNET_EGRESS o con dos conectores es InvalidArgumentException
(TypeScript: InvalidArgumentError) antes de lanzar nada. Un sandbox con
volumen sólo tiene internet a través de tu VPC (una NAT o un transit
gateway y un conector que lo permita); el de la pila efs-volumes sólo
deja salir NFS. El hallazgo internet-egress de check() te lo recuerda
(Internet y volúmenes).
Sólo lectura: por IAM, no por la opción ro¶
EfsVolume(read_only=True) monta con ro, pero eso solo no impide
escribir desde el sandbox. Lo que hace de sólo lectura un volumen es la
política del execution role: despliega la pila con
read_only_access_point_arns (TypeScript: readOnlyAccessPointArns; CLI:
--param ReadOnlyAccessPointArns=...), que le deniega ClientWrite a esos
access points, o con allow_write=False si ninguno debe admitir escritura
(Sólo lectura de verdad).
Pausar con escrituras pendientes y el mount target caído pierde datos
Si al pausar hay escrituras que aún no llegaron a EFS y el mount target
no responde, rayd no puede vaciarlas a tiempo, AWS termina el
MicroVM y lo no vaciado se pierde (pause() lanza
SandboxNotFoundException). Si un dato importa, ciérralo con fsync o
sync antes de pause()
(Lo que hace rayd con cada volumen).
Borrarlo todo¶
rayito stack destroy efs-volumes borra el conector, los mount targets,
los grupos de seguridad y el IAM, pero siempre conserva el sistema de
ficheros y sus datos. Para borrarlos de verdad:
Guías completas: Volúmenes EFS y Volúmenes EFS en tu VPC.
Dominio propio (experimental)¶
Experimental: no verificado de punta a punta en AWS real
CustomDomain se publica para que puedas probarlo, y su API puede
cambiar en una minor. Verificado en AWS real: la CloudFront
Function de enrutado compila y enruta en el runtime real (token
correcto, sin token, token erróneo, ruta caducada), y el despliegue
crea el KeyValueStore y la Function, con un rollback y un borrado que
no dejan nada. Sin verificar: crear la distribución CloudFront y
servir tráfico por ella (HTTP/1.1, WebSocket, propagación de rutas,
renovación del JWE), porque la cuenta de pruebas del proyecto no
permite crear distribuciones. Nadie ha abierto todavía una URL de
dominio propio desplegada con esta plantilla.
Sin esta función, un puerto del sandbox sólo responde a quien manda las
cabeceras del proxy de AWS que devuelve get_host(): un navegador, un
webhook o un <iframe> no pueden. Con dominio propio, cada puerto
registrado tiene una URL HTTPS normal bajo tu dominio,
https://<puerto>-<sandbox_id>.sbx.example.com, servida por una
distribución CloudFront en tu cuenta cuya Function comprueba el token
de tráfico y pone ella misma las cabeceras del proxy.
import secrets
from rayito import CustomDomain, Sandbox
domain = CustomDomain(public_domain="sbx.example.com") # no llama a AWS
domain.deploy(certificate_arn="arn:aws:acm:us-east-1:<cuenta>:certificate/<id>")
with Sandbox.create("rayito-base", allowed_ports=[8000]) as sbx:
jwe = sbx.get_host(8000).headers["x-aws-proxy-auth"]
route = domain.register(
sbx.sandbox_id, 8000, endpoint=sbx.endpoint, jwe=jwe,
traffic_token=secrets.token_urlsafe(32), ttl_seconds=2400,
)
print(f"https://{route.host}")
domain.unregister(sbx.sandbox_id, 8000)
import { randomBytes } from "node:crypto";
import { CustomDomain, Sandbox } from "rayito";
const domain = new CustomDomain({ publicDomain: "sbx.example.com" }); // no llama a AWS
await domain.deploy({ certificateArn: "arn:aws:acm:us-east-1:<cuenta>:certificate/<id>" });
await using sbx = await Sandbox.create({ template: "rayito-base", allowedPorts: [8000] });
const jwe = (await sbx.getHost(8000)).headers["x-aws-proxy-auth"] ?? "";
const route = await domain.register(sbx.sandboxId, 8000, {
endpoint: sbx.endpoint,
jwe,
trafficToken: randomBytes(32).toString("base64url"),
ttlSeconds: 2400,
});
console.log(`https://${route.host}`);
await domain.unregister(sbx.sandboxId, 8000);
Lo que conviene saber:
register()exige un token de tráfico: una ruta sólo es pública conpublic=True(TypeScript:public: true) explícito, nunca por omisión.deploy(alternate_domain_names=[...])(--alternate-domain-name) sustituye el comodín*.sbx.example.compor hostnames exactos.- En Python necesitas el extra
pip install "rayito[custom-domain]"(el plano de datos del KeyValueStore firma con SigV4A); en TypeScript, los peers@aws-sdk/client-cloudformation,@aws-sdk/client-cloudfront-keyvaluestorey@aws-sdk/signature-v4a. Sin instanciarCustomDomainno se carga ninguno. Sandbox.create(domain=)sigue sin cablear: lanzaUnimplementedErrorsin llamar a AWS. Hoy registras la ruta tú mismo yroute.hostes el hostname final.
Si lo pruebas en una cuenta que permita crear distribuciones, cuéntanos
cómo te fue en un
issue de GitHub
con [custom-domain] en el título, usando siempre example.com y
123456789012 en lugar de tus datos reales. Guía:
Dominio propio.
Probar en local con Docker y Floci¶
Para quien contribuye o quiere revisar un cambio antes de gastar en AWS:
un entorno de Docker Compose en el repositorio corre los SDK de Python y
TypeScript contra un rayd de verdad (la imagen de producto, con
rayd como PID 1) y contra Floci, un
emulador de AWS. Sin cuenta, sin credenciales y sin coste.
make local-up # construye y arranca floci, runner y guest
make local-e2e # corre los tests `local` de Python y de TypeScript
make local-down # lo para todo y borra los volúmenes
Cubre comandos, ficheros, PTY, run_code, la máquina de estados de pausa
y reanudación, las pilas opcionales, secretos, el índice de metadatos,
eventos y webhooks, entre otros. Lo que depende de la plataforma real (el
proxy de AWS, IMDS, el egress, los montajes, el snapshot de memoria, el
coste) sigue necesitando AWS. Ningún contenedor es privilegiado, nada
publica puertos en tu máquina y el emulador no tiene salida a internet. El
workflow local-e2e lo corre en CI sin secretos. Guía:
Probar en local.
Endurecimiento de seguridad¶
0.7.0 incorpora una revisión de seguridad completa. La mayoría de los
cambios no piden nada de tu parte salvo actualizar; los que cambian un
comportamiento están en Cómo actualizar. El
modelo de amenazas y el detalle de cada control están en
Seguridad y en
SECURITY.md.
rayd (necesita una imagen con rayd 0.7.0)
- Los hooks de ciclo de vida comprueban quién abrió la conexión: el código del sandbox ya no puede terminar el sandbox, repetir la validación ni suplantar el arranque de la plataforma por esa vía, y cada intento queda contado como anomalía.
- Las operaciones de ficheros trabajan sobre descriptores sin seguir
enlaces simbólicos, y los procesos internos de
raydarrancan con un entorno mínimo. - Los puertos del agente tienen un tope de conexiones, la identidad de
los procesos del usuario tiene un rango cerrado y
raydcomprueba los procesos de kernel antes de señalarlos. persist=liga el sandbox a su prefijo de persistencia: con un prefijo por inquilino, un sandbox no alcanza elHOMEpersistido de otro aunque compartan execution role.- La pasarela de secretos valida las rutas con una lista de permitidos (también en el SDK, antes de cualquier llamada) y ya no devuelve la credencial inyectada en las cabeceras de la respuesta.
Dos de estos controles (el origen del arranque y el tope de conexiones) quedan pendientes de aceptación en AWS real.
rayito sandbox proxy: sólo reenvía peticiones cuyo Host y Origin
son los esperados (loopback, <id>.localhost o los que declares con
--allowed-host y --allow-origin), un solo mensaje por conexión y como
mucho --max-connections a la vez. Una web abierta en tu navegador ya no
puede usar el servicio del sandbox con tu token
(Proxy local).
Templates e imágenes: Template.build no sigue enlaces simbólicos,
aplica .dockerignore con la semántica de Docker y avisa si el contexto va
a empaquetar ficheros sensibles (.env*, .git/, claves). rayito image
zip y make image-publish rechazan enlaces simbólicos en el árbol de la
imagen (.dockerignore).
Credenciales de git: clone/push/pull con usuario y contraseña
restauran siempre la URL sin credenciales, corren sin hooks ni
credential helpers y se niegan si la configuración de git reescribe URLs.
Aun así, trata el token como revelado al código del sandbox: usa tokens
de vida corta y de un solo repositorio
(Git).
IAM y Lambdas de las pilas opcionales
infra/iam.yamlseparaSandboxLauncherPolicy(sólo lanzar) deImagePublisherPolicy(sólo publicar);CallerPolicysigue siendo la unión.RayitoTemplateBuildersólo leerayito/*del bucket, ysizes-guardniega también publicar imágenes.events-webhooks: el forwarder autentica cada línea antes de leerla, descarta eventos viejos, repetidos o que no hacen avanzar el ciclo de vida, limitapaused/resumedpor sandbox y no pierde el lote si falla una escritura. Un webhook roto o lento ya no bloquea a los demás, cada entrega lleva además la firma con marca de tiemporayito-signaturey un secreto rotado deja de usarse en 5 minutos. Cada Lambda escribe sólo sus filas y sus logs, y el llamante tiene una política por tarea (Qué política necesita cada llamada).- Los secretos de firma de webhooks quedan fuera de
secrets=, y el zip de cada pila opcional se sube arayito/stacks/<componente>/y se compara antes de reutilizarlo.
SDK y CLI: los errores de AWS se sanean en más caminos, la salida de
un comando o PTY se guarda con un tope de 64 MiB por descriptor
(Salida guardada), la CLI
neutraliza secuencias de escape de logs y mensajes, un access token propio
debe tener al menos 16 bytes y el JWE del proxy ya no aparece en repr ni
en console.log.
Cadena de suministro: firmar rayd y la imagen exige la aprobación del
mantenedor, los assets se construyen sin credenciales y no se pueden
reemplazar desde la release, la release no restaura cachés y fija sus
herramientas por versión y hash, y la receta de verificación liga la firma
al tag exacto que instalas (Verificar una release).
Cómo actualizar desde 0.6.x¶
-
Actualiza el SDK.
-
Republica tu imagen sobre
rayd0.7.0 (Configurar AWS). La serie de compatibilidad pasa a0.7:rayito doctorexige elrayddel tagrayd-v0.7.0para el SDK 0.7 (Compatibilidad), porquevolumes=y el endurecimiento del agente viven enrayd. Contra una imagen anterior,volumes=falla cerrado conUnimplementedErrory termina el sandbox que acaba de lanzar. - Redespliega las pilas que uses.
rayito events deploysube el código nuevo de las Lambdas deevents-webhooks; redespliega tambiéninfra/iam.yamlpara tener las políticas separadas (IAM).EventsOperatorPolicysigue existiendo en 0.7 pero queda obsoleta: vincula a cada identidadEventsLauncherPolicy,EventsReaderPolicyoEventsWebhookAdminPolicy. - Revisa estos cambios de comportamiento:
| Qué | Antes | Desde 0.7.0 |
|---|---|---|
rayito sandbox proxy --bind 0.0.0.0 |
bastaba --allow-remote |
exige --allowed-host; un Host distinto (p. ej. tras un proxy inverso) recibe 421 sin --allowed-host, un Origin ajeno 403, y una petición con delimitación ambigua 400 |
.dockerignore de Template.build |
* cruzaba / |
* y ? no cruzan /: *.pyc sólo excluye los de la raíz; usa **/*.pyc |
Access token propio (access_token=, RAYITO_ACCESS_TOKEN) |
cualquier longitud | al menos 16 bytes decodificados, o InvalidArgumentException / InvalidArgumentError |
Checkpoint o restore explícito hacia otra base desde un sandbox con persist= |
permitido | permission_denied |
Salida de commands.run y PTY |
sin tope | 64 MiB por descriptor (se conserva el final y truncated lo indica); ajustable con max_output_bytes / maxOutputBytes |
Ruta de allow en SecretGateway que rayd nunca dejaría pasar |
aceptada | InvalidArgumentException / InvalidArgumentError antes de llamar a AWS |
secrets= con un secreto rayito/webhooks/* |
aceptado | InvalidArgumentException / InvalidArgumentError |
register_webhook con una URL inalcanzable (puerto o host inválidos) |
aceptada | InvalidArgumentException / InvalidArgumentError |
git con credenciales y url.*.insteadOf en la configuración |
seguía | GitAuthException / GitAuthError |
Sin opciones nuevas, el resto de tu código sigue igual: no hay cambios incompatibles en la API pública de 0.6.x fuera de esta tabla.
Lo que no trae 0.7.0¶
Sandbox.create(domain=)y queget_host()/expose()devuelvan la URL del dominio propio: siguen en desarrollo.- Las operaciones de contenido del
Volumedel shim (read_file,write_file… sin sandbox): Rayito no tiene plano de datos fuera de un MicroVM, así que siguen lanzandoUnimplementedError.
Detalle completo, cambio a cambio: Changelog.