specky-sdd 3.4.0 → 3.5.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 CHANGED
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.5.0] - 2026-07-02
11
+
12
+ Enterprise mode — an **opt-in configuration profile** of the same 100%
13
+ open-source (MIT) package. Every control below ships in `specky-sdd` for
14
+ everyone, default-OFF, and the standard profile is byte-for-byte the same
15
+ experience as 3.4.0. See `docs/ENTERPRISE-DEPLOYMENT.md`.
16
+
17
+ ### Added
18
+
19
+ - **Enterprise profile.** `profile: enterprise` in `.specky/config.yml` — or `SPECKY_PROFILE=enterprise`, the `SPECKY_ENTERPRISE=1` shorthand, or `specky serve --profile=enterprise` (flag > env > file) — flips the *defaults* of `audit_enabled`, `rbac.enabled`, `rate_limit.enabled`, and the new `audit.fail_closed` to ON. Explicit config values always win, so any control can still be switched off individually. The server logs the resolved posture at startup.
20
+ - **Identity-based RBAC over HTTP.** `SDD_HTTP_TOKENS_FILE` points at a YAML token table (kept outside the workspace) mapping bearer tokens — plaintext or `token_sha256` — to a named `principal` and RBAC `role`. Tokens are constant-time compared; loading is fail-closed (a malformed table aborts startup instead of accepting everyone). Role precedence is now **authenticated token role > `SDD_ROLE` > `rbac.default_role`** — an authenticated request ignores `SDD_ROLE`, so a remote caller cannot out-vote its token. `sdd_check_access` and `access_denied` responses report the principal and role source. The legacy shared `SDD_HTTP_TOKEN` continues to work unchanged when no table is configured.
21
+ - **Tamper-evident audit trail.** With `SDD_AUDIT_HMAC_KEY` (or `SDD_AUDIT_HMAC_KEY_FILE`, key held outside the workspace) every audit entry is signed with HMAC-SHA256 over its serialized form — `previous_hash` included, so signatures chain. `sdd_verify_audit` now verifies both the hash chain and the signatures and reports `hmac_checked` / `signed_entries`; a workspace writer who rewrites the log and recomputes the plain chain is detected. Audit entries also record the authenticated `principal`. (Documented limit: tail truncation needs external anchoring of `current_hash`.)
22
+ - **Fail-closed auditing.** `audit.fail_closed: true` (enterprise default) refuses tool execution with `audit_unavailable` when the pre-execution audit entry cannot be written — no unaudited actions. Post-execution audit failures are surfaced on stderr without masking the tool result. Standard profile keeps the historical fail-open behavior.
23
+ - **`docs/ENTERPRISE-DEPLOYMENT.md`** — profiles, token table setup (with token/hash generation commands), HMAC audit + verification, hosted `serve --http` behind TLS (systemd/container examples), air-gapped tarball + private-registry installs, CI enforcement, the full enterprise env-var/config reference, and an honest out-of-scope list (no in-process TLS, no SSO yet, stdio has no auth layer).
24
+ - Tests: `config-profile` (profile precedence + explicit-wins), `audit-hmac` (signing, forged-rewrite detection, fail-closed, key resolution), `token-table` (load validation + identity resolution), and identity/fail-closed cases in `tool-enforcement`.
25
+
26
+ ### Changed
27
+
28
+ - `AuditLogger` accepts an options object (`{ exportFormat, maxFileSizeMb, hmacKey, failClosed }`); the old positional signature still works.
29
+ - RBAC denial responses and audit entries include the caller's `principal` when authenticated.
30
+ - README, SECURITY.md, CLI.md, INSTALL.md, and ENTERPRISE-CONTROLS.md updated for the profile, token table, and HMAC audit; API reference regenerated.
31
+
10
32
  ## [3.4.0] - 2026-07-02
11
33
 
12
34
  First stable `3.4.0` (supersedes the `3.4.0-rc.*` prereleases). Consolidates the
package/README.md CHANGED
@@ -57,7 +57,7 @@
57
57
  | **Enterprise** | [Compliance Frameworks](#compliance-frameworks) | HIPAA, SOC2, GDPR, PCI-DSS, ISO 27001 |
58
58
  | | [Enterprise Ready](#enterprise-ready) | Security, audit trail, quality gates |
59
59
  | **Platform** | [The SDD Platform](#the-spec-driven-development-platform) | Built on Spec-Kit, everything included |
60
- | | [Roadmap](#roadmap) | v3.4 current, v3.5+ planned |
60
+ | | [Roadmap](#roadmap) | v3.5 current, v3.6+ planned |
61
61
 
62
62
 
63
63
  ## What is Specky?
@@ -199,7 +199,7 @@ cd your-project
199
199
  specky install
200
200
  ```
201
201
 
202
- That's it. The CLI auto-detects Claude Code and/or GitHub Copilot and installs the 13 agents, 22 prompts, 8 skills, 16 hooks, the MCP server registration, and pre-authorizes all required tools.
202
+ That's it. The CLI auto-detects Claude Code and/or GitHub Copilot and installs the 13 agents, 22 prompts, 8 skills, 16 hooks, the MCP server registration, and pre-authorizes a least-privilege set of tools (the Specky MCP tools plus scoped `git`/`npm`/`npx` and file editing — not arbitrary shell or network).
203
203
 
204
204
  **Other install modes:**
205
205
 
@@ -887,13 +887,19 @@ Create `.specky/config.yml` in your project root to customize Specky:
887
887
 
888
888
  ```yaml
889
889
  # .specky/config.yml
890
+ profile: standard # standard | enterprise (flips security defaults ON)
890
891
  templates_path: ./my-templates # Override built-in templates
891
892
  default_framework: vitest # Default test framework
892
893
  compliance_frameworks: [hipaa, soc2] # Frameworks to check
893
894
  audit_enabled: true # Enable audit trail
895
+ rbac:
896
+ enabled: false # Role checks (viewer/contributor/admin)
897
+ default_role: contributor
898
+ rate_limit:
899
+ enabled: false # HTTP token bucket (60 rpm, burst 10)
894
900
  ```
895
901
 
896
- When `templates_path` is set, Specky uses your custom templates instead of the built-in ones. When `audit_enabled` is true, tool invocations are logged locally.
902
+ When `templates_path` is set, Specky uses your custom templates instead of the built-in ones. When `audit_enabled` is true, tool invocations are logged locally. `profile: enterprise` turns audit, RBAC, rate limiting, and fail-closed auditing on by default (explicit values win) — see [docs/ENTERPRISE-DEPLOYMENT.md](docs/ENTERPRISE-DEPLOYMENT.md).
897
903
 
898
904
 
899
905
  ## MCP Integration Architecture
@@ -982,6 +988,12 @@ From any [input](#input-methods-6-ways-to-start) to production -- fully automate
982
988
 
983
989
  Specky is built with enterprise adoption in mind.
984
990
 
991
+ ### Enterprise profile (opt-in)
992
+
993
+ Specky is 100% open source (MIT) — enterprise mode is just an opt-in configuration profile of the same package: `profile: enterprise` (or `SPECKY_PROFILE=enterprise`, or `specky serve --profile=enterprise`) flips the governance defaults ON — hash-chained **audit trail** (fail-closed), **RBAC**, and HTTP **rate limiting** — while explicit config values still win. Add `SDD_HTTP_TOKENS_FILE` for **identity-based roles** (each bearer token maps to a named principal + role; audit entries record who did what) and `SDD_AUDIT_HMAC_KEY[_FILE]` for a **tamper-evident audit log** signed with a key the workspace never sees. The standard profile is untouched — all of this stays off unless you opt in.
994
+
995
+ → Full guide: [docs/ENTERPRISE-DEPLOYMENT.md](docs/ENTERPRISE-DEPLOYMENT.md) (hosted HTTP, tokens, HMAC audit, air-gapped installs, containers, CI gates)
996
+
985
997
  ### Security Posture
986
998
 
987
999
  - **3 runtime dependencies** — minimal attack surface (`@modelcontextprotocol/sdk`, `zod`, `yaml`)
@@ -993,6 +1005,7 @@ Specky is built with enterprise adoption in mind.
993
1005
  - See [SECURITY.md](SECURITY.md) for full OWASP Top 10 coverage
994
1006
  - See [docs/SYSTEM-DESIGN.md](docs/SYSTEM-DESIGN.md) for complete security architecture
995
1007
  - See [docs/ENTERPRISE-CONTROLS.md](docs/ENTERPRISE-CONTROLS.md) for RBAC, audit trail, and tool-enforcement controls
1008
+ - See [docs/ENTERPRISE-DEPLOYMENT.md](docs/ENTERPRISE-DEPLOYMENT.md) for the enterprise profile, identity tokens, HMAC audit, and hosted/air-gapped deployment
996
1009
  - See [docs/DETERMINISM.md](docs/DETERMINISM.md) for reproducible-output guarantees
997
1010
  - See [docs/BRANCH-GOVERNANCE.md](docs/BRANCH-GOVERNANCE.md) for branch and release governance
998
1011
  - See [docs/EVIDENCE.md](docs/EVIDENCE.md) for the validation evidence pack
@@ -1004,7 +1017,7 @@ When using Specky, follow these practices to protect your data:
1004
1017
  | Practice | Why | How |
1005
1018
  |----------|-----|-----|
1006
1019
  | **Use stdio mode for local development** | No network exposure | `npx specky-sdd` (default) |
1007
- | **Never expose HTTP mode to public networks without TLS** | HTTP has optional bearer-token auth but no TLS | `--http` binds to `127.0.0.1` by default; set `SDD_HTTP_TOKEN` for bearer auth. For remote access, add a reverse proxy (nginx, Caddy) terminating TLS |
1020
+ | **Never expose HTTP mode to public networks without TLS** | HTTP has optional bearer-token auth but no TLS | `--http` binds to `127.0.0.1` by default; set `SDD_HTTP_TOKEN` (shared) or `SDD_HTTP_TOKENS_FILE` (per-user identity + role) for bearer auth. For remote access, add a reverse proxy (nginx, Caddy) terminating TLS |
1008
1021
  | **Protect the `.specs/` directory** | Contains your specification artifacts (architecture, API contracts, business logic) | Add `.specs/` to `.gitignore` if specs contain sensitive IP, or use a private repo |
1009
1022
  | **Protect checkpoints** | `.specs/{feature}/.checkpoints/` stores full artifact snapshots | Same as above — treat checkpoints like source code |
1010
1023
  | **Review auto-generated specs before committing** | Turnkey and auto-pipeline generate from natural language — may capture sensitive details | Review SPECIFICATION.md and DESIGN.md before `git add` |
@@ -1086,7 +1099,7 @@ curl http://localhost:3200/health
1086
1099
 
1087
1100
  ## Roadmap
1088
1101
 
1089
- ### v3.4 (current)
1102
+ ### v3.5 (current)
1090
1103
 
1091
1104
  | Capability | Status |
1092
1105
  |------------|--------|
@@ -1118,17 +1131,20 @@ curl http://localhost:3200/health
1118
1131
  | RBAC foundation (opt-in role-based access control) | Stable |
1119
1132
  | Rate limiting for HTTP transport (opt-in) | Stable |
1120
1133
  | HTTP transport: loopback bind by default, bearer-token auth (`SDD_HTTP_TOKEN`), DNS-rebinding protection | Stable |
1134
+ | Enterprise profile (`profile: enterprise` — audit/RBAC/rate-limit defaults ON, opt-in) | Stable |
1135
+ | Identity-based RBAC over HTTP (`SDD_HTTP_TOKENS_FILE`: token → principal + role) | Stable |
1136
+ | Tamper-evident audit trail (HMAC-signed entries, fail-closed mode, `sdd_verify_audit`) | Stable |
1121
1137
 
1122
- ### v3.5+ (planned)
1138
+ ### v3.6+ (planned)
1123
1139
 
1124
1140
  | Feature | Description |
1125
1141
  |---------|-------------|
1126
1142
  | Observability | OpenTelemetry metrics and structured logging |
1127
1143
  | Internationalization | Spec templates in PT-BR, ES, FR, DE, JA |
1128
1144
  | Automated shrinking | fast-check/Hypothesis shrinking feedback into spec refinement |
1129
- | Centralized audit log | SIEM-integrated tamper-evident audit trail |
1145
+ | Centralized audit log | SIEM export (syslog shipping, OTLP) of the tamper-evident audit trail |
1130
1146
  | Multi-tenant | Isolated workspaces for multiple teams |
1131
- | SSO / SAML | Federated identity for enterprise auth |
1147
+ | SSO / SAML | Federated identity for enterprise auth (beyond the static token table) |
1132
1148
 
1133
1149
  Have a feature request? [Open an issue](https://github.com/paulasilvatech/specky/issues).
1134
1150
 
package/SECURITY.md CHANGED
@@ -4,10 +4,10 @@
4
4
 
5
5
  | Version | Supported |
6
6
  | --- | --- |
7
- | 3.3.x | ✅ Active |
8
- | 3.0.x–3.2.x | ✅ Security fixes only |
9
- | 2.3.x | ❌ End of life |
10
- | 2.0.x | ❌ End of life |
7
+ | 3.5.x | ✅ Active |
8
+ | 3.4.x | ✅ Security fixes only |
9
+ | 3.0.x–3.3.x | ❌ End of life |
10
+ | 2.x | ❌ End of life |
11
11
  | 1.0.x | ❌ End of life |
12
12
 
13
13
  ## Reporting a Vulnerability
@@ -78,7 +78,7 @@ Runtime dependencies are kept intentionally small and are audited in CI.
78
78
  | A04 Insecure Design | State machine enforces phase ordering; thin tools / fat services separation |
79
79
  | A05 Security Misconfiguration | Minimal config surface; no default credentials; no admin endpoints |
80
80
  | A06 Vulnerable Components | 3 runtime deps only; Dependabot enabled; regular audits |
81
- | A07 Authentication Failures | stdio mode is process-isolated (no network). HTTP mode binds to `127.0.0.1` by default and supports optional bearer-token auth (`SDD_HTTP_TOKEN`, constant-time compared) plus DNS-rebinding protection |
81
+ | A07 Authentication Failures | stdio mode is process-isolated (no network). HTTP mode binds to `127.0.0.1` by default and supports optional bearer-token auth — a shared token (`SDD_HTTP_TOKEN`) or a named token table (`SDD_HTTP_TOKENS_FILE`, principal + RBAC role per token, sha256 storage supported) — all constant-time compared, plus DNS-rebinding protection |
82
82
  | A08 Data Integrity Failures | Atomic file writes via FileManager; Zod schema enforcement |
83
83
  | A09 Logging Failures | Structured stderr logging; no stdout pollution |
84
84
  | A10 SSRF | Zero outbound network requests |
@@ -101,8 +101,15 @@ We run `npm audit` in CI on every pull request. Any `high` or `critical` vulnera
101
101
  | --- | --- | --- |
102
102
  | `SDD_WORKSPACE` | Restricts file operations to this directory | Current working directory |
103
103
  | `PORT` | HTTP transport port (when using `--http` mode) | 3200 |
104
+ | `SPECKY_PROFILE` | `enterprise` flips audit/RBAC/rate-limit defaults to ON (explicit config wins) | `standard` |
105
+ | `SDD_HTTP_TOKEN` | Shared bearer token for `--http` (no identity) | unset (auth off) |
106
+ | `SDD_HTTP_TOKENS_FILE` | Named token table → principal + RBAC role (identity-based auth) | unset |
107
+ | `SDD_AUDIT_HMAC_KEY` / `SDD_AUDIT_HMAC_KEY_FILE` | Sign audit entries (tamper evidence); keep the key outside the workspace | unset (chain only) |
108
+ | `SDD_ROLE` | Local RBAC role for stdio use — ignored on authenticated HTTP requests | `rbac.default_role` |
104
109
 
105
- HTTP transport (`--http`) binds to `127.0.0.1` by default. Binding to a non-loopback address requires an explicit `--host` and prints a warning. Set `SDD_HTTP_TOKEN` to require an `Authorization: Bearer <token>` header on every `/mcp` request (the `/health` probe stays open). Even so, do not expose Specky to public networks without a TLS-terminating reverse proxy.
110
+ HTTP transport (`--http`) binds to `127.0.0.1` by default. Binding to a non-loopback address requires an explicit `--host` and prints a warning. Set `SDD_HTTP_TOKEN` (shared token) or `SDD_HTTP_TOKENS_FILE` (named tokens mapping to principal + role) to require an `Authorization: Bearer <token>` header on every `/mcp` request (the `/health` probe stays open). Even so, do not expose Specky to public networks without a TLS-terminating reverse proxy. Hosted, air-gapped, and container deployment patterns: [docs/ENTERPRISE-DEPLOYMENT.md](docs/ENTERPRISE-DEPLOYMENT.md).
111
+
112
+ The audit trail's plain hash chain detects corruption but not deliberate rewriting by a workspace writer; configure an HMAC key (held outside the workspace) to make entries tamper-evident, and anchor the `current_hash` from `sdd_verify_audit` externally to detect tail truncation.
106
113
 
107
114
  ## Secure Development Practices
108
115
 
@@ -119,7 +126,7 @@ HTTP transport (`--http`) binds to `127.0.0.1` by default. Binding to a non-loop
119
126
  | Practice | Details |
120
127
  | --- | --- |
121
128
  | **Use stdio mode by default** | `specky-sdd` (global install) — no network exposure, process-level isolation |
122
- | **Never expose HTTP mode publicly without TLS** | `--http` supports bearer-token auth (`SDD_HTTP_TOKEN`) and binds to `127.0.0.1` by default, but has no TLS. For remote access, set a token AND place it behind a reverse proxy (nginx, Caddy, Traefik) terminating TLS |
129
+ | **Never expose HTTP mode publicly without TLS** | `--http` supports bearer-token auth (`SDD_HTTP_TOKEN`, or `SDD_HTTP_TOKENS_FILE` for per-user identity + role) and binds to `127.0.0.1` by default, but has no TLS. For remote access, set tokens AND place it behind a reverse proxy (nginx, Caddy, Traefik) terminating TLS |
123
130
  | **Protect `.specs/` directory** | Contains architecture details, API contracts, security models. Add to `.gitignore` for sensitive projects, or use a private repository |
124
131
  | **Protect `.checkpoints/`** | Contains full copies of all spec artifacts. Treat like source code |
125
132
  | **Keep security-scan hook active** | `.claude/hooks/security-scan.sh` scans for hardcoded secrets and blocks commits (exit 2). Do not disable |
@@ -205,7 +212,7 @@ Running `npx -y specky-sdd@latest` without a pinned version downloads the latest
205
212
 
206
213
  ```bash
207
214
  # Install into a local vendor directory — no global write permissions needed
208
- npm install specky-sdd@3.4.0 --prefix ./vendor --ignore-scripts
215
+ npm install specky-sdd@3.5.0 --prefix ./vendor --ignore-scripts
209
216
  ./vendor/node_modules/.bin/specky install
210
217
  ./vendor/node_modules/.bin/specky doctor # integrity check
211
218
  ```
package/apm.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  name: specky-sdd
2
- version: 3.4.0
2
+ version: 3.5.0
3
3
  description: "Agentic Spec-Driven Development — 10-phase pipeline with 58 MCP tools, 13 agents, 22 prompts, 8 skills, 16 hooks, EARS notation, model routing, and enterprise security"
4
4
  author: Paula Silva
5
5
  license: MIT
package/config.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  audit_enabled: true
2
- version: "3.4.0"
2
+ version: "3.5.0"
3
3
  pipeline:
4
4
  phases: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
5
5
  phase_names:
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/serve.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAalE"}
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/serve.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAclE"}
@@ -7,8 +7,9 @@
7
7
  */
8
8
  export async function runServe(opts) {
9
9
  // We simply import the existing entry point which starts the MCP server.
10
- // Flags like --http are read from process.argv by the server module itself,
11
- // so we just set them back into argv for compatibility.
10
+ // Flags like --http, --host= and --profile= are read from process.argv by
11
+ // the server module itself, so we just set them back into argv for
12
+ // compatibility.
12
13
  const extra = [];
13
14
  if (opts.http)
14
15
  extra.push("--http");
@@ -1 +1 @@
1
- {"version":3,"file":"serve.js","sourceRoot":"","sources":["../../../src/cli/commands/serve.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAkB;IAC/C,yEAAyE;IACzE,4EAA4E;IAC5E,wDAAwD;IACxD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,IAAI,CAAC,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACpC,IAAI,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvD,6CAA6C;IAC7C,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;IAE5B,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAC/B,2CAA2C;IAC3C,OAAO,CAAC,CAAC;AACX,CAAC"}
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../../../src/cli/commands/serve.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAkB;IAC/C,yEAAyE;IACzE,0EAA0E;IAC1E,mEAAmE;IACnE,iBAAiB;IACjB,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,IAAI,CAAC,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACpC,IAAI,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvD,6CAA6C;IAC7C,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;IAE5B,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAC/B,2CAA2C;IAC3C,OAAO,CAAC,CAAC;AACX,CAAC"}
package/dist/cli/index.js CHANGED
@@ -8,7 +8,7 @@
8
8
  * specky status
9
9
  * specky upgrade
10
10
  * specky hooks <list|test|run NAME>
11
- * specky serve [--http] [--port=N]
11
+ * specky serve [--http] [--port=N] [--profile=enterprise]
12
12
  * specky --version | -v
13
13
  * specky --help | -h
14
14
  *
@@ -70,6 +70,8 @@ Commands:
70
70
  --port=<N> HTTP port (default 3200)
71
71
  --host=<addr> HTTP bind address (default 127.0.0.1;
72
72
  set 0.0.0.0 only behind an auth proxy)
73
+ --profile=<standard|enterprise> Config profile (enterprise defaults
74
+ audit/RBAC/rate-limit to ON)
73
75
 
74
76
  --version, -v Print version
75
77
  --help, -h Print this help
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
package/dist/config.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import { z } from "zod";
2
2
  declare const configSchema: z.ZodObject<{
3
+ profile: z.ZodDefault<z.ZodEnum<{
4
+ standard: "standard";
5
+ enterprise: "enterprise";
6
+ }>>;
3
7
  templates_path: z.ZodDefault<z.ZodString>;
4
8
  default_framework: z.ZodDefault<z.ZodString>;
5
9
  compliance_frameworks: z.ZodDefault<z.ZodArray<z.ZodString>>;
@@ -16,6 +20,7 @@ declare const configSchema: z.ZodObject<{
16
20
  otlp: "otlp";
17
21
  }>>;
18
22
  max_file_size_mb: z.ZodDefault<z.ZodNumber>;
23
+ fail_closed: z.ZodDefault<z.ZodBoolean>;
19
24
  }, z.core.$strip>>;
20
25
  rbac: z.ZodDefault<z.ZodObject<{
21
26
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -27,11 +32,26 @@ declare const configSchema: z.ZodObject<{
27
32
  }, z.core.$strip>>;
28
33
  }, z.core.$strip>;
29
34
  export type SpeckyConfig = z.infer<typeof configSchema>;
35
+ export type SpeckyProfile = SpeckyConfig["profile"];
36
+ /** Inputs that can force the profile from outside config.yml (flag > env). */
37
+ export interface ProfileOverrides {
38
+ argv?: readonly string[];
39
+ env?: Record<string, string | undefined>;
40
+ }
41
+ /**
42
+ * Resolve the active profile. Precedence: `--profile=` flag > `SPECKY_PROFILE`
43
+ * env > `SPECKY_ENTERPRISE=1` shorthand > config.yml `profile:` > standard.
44
+ * Unknown values are ignored with a stderr warning (never crash the server).
45
+ */
46
+ export declare function resolveProfile(configProfile: SpeckyProfile, overrides?: ProfileOverrides): SpeckyProfile;
30
47
  /**
31
48
  * Load `.specky/config.yml` from workspace root. Returns defaults if the file
32
49
  * is absent, unparseable, or fails validation (a bad value never crashes the
33
50
  * server — it falls back to the safe defaults, with a stderr warning).
51
+ *
52
+ * `overrides` lets the server entry point (and tests) inject argv/env for
53
+ * profile resolution instead of reading globals.
34
54
  */
35
- export declare function loadConfig(workspaceRoot: string): SpeckyConfig;
55
+ export declare function loadConfig(workspaceRoot: string, overrides?: ProfileOverrides): SpeckyConfig;
36
56
  export {};
37
57
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAcxB,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0BR,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAIxD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,YAAY,CAyB9D"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAoBA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAcxB,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4BR,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AACxD,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;AAIpD,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CAC1C;AAMD;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,aAAa,EAAE,aAAa,EAC5B,SAAS,GAAE,gBAAqB,GAC/B,aAAa,CAsBf;AAgCD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,GAAE,gBAAqB,GAAG,YAAY,CAyBhG"}
package/dist/config.js CHANGED
@@ -5,6 +5,15 @@
5
5
  * Parsing uses the `yaml` library (safe by default — no code execution) and a
6
6
  * Zod schema for validation, replacing a hand-rolled line parser that could not
7
7
  * handle nested lists/quoting and did no validation.
8
+ *
9
+ * Profiles: `standard` (default) keeps every enterprise control opt-in and
10
+ * off. `enterprise` flips the *defaults* of audit_enabled, rbac.enabled,
11
+ * rate_limit.enabled, and audit.fail_closed to true — an explicit value in
12
+ * config.yml always wins over the profile default, so an enterprise deployment
13
+ * can still switch an individual control off. The profile itself can come from
14
+ * config.yml (`profile: enterprise`), the `SPECKY_PROFILE` env var, the
15
+ * `SPECKY_ENTERPRISE=1` shorthand, or the server's `--profile=enterprise` flag
16
+ * (flag > env > config file).
8
17
  */
9
18
  import { readFileSync } from "node:fs";
10
19
  import { join } from "node:path";
@@ -19,6 +28,7 @@ const safeRelativePath = z.string().refine((p) => p === "" ||
19
28
  !p.includes("\0")), { message: "templates_path must be a workspace-relative path (no absolute paths, no '..')." });
20
29
  const configSchema = z
21
30
  .object({
31
+ profile: z.enum(["standard", "enterprise"]).default("standard"),
22
32
  templates_path: safeRelativePath.default(""),
23
33
  default_framework: z.string().default("vitest"),
24
34
  compliance_frameworks: z.array(z.string()).default(["general"]),
@@ -34,8 +44,9 @@ const configSchema = z
34
44
  .object({
35
45
  export_format: z.enum(["jsonl", "syslog", "otlp"]).default("jsonl"),
36
46
  max_file_size_mb: z.number().positive().default(10),
47
+ fail_closed: z.boolean().default(false),
37
48
  })
38
- .default({ export_format: "jsonl", max_file_size_mb: 10 }),
49
+ .default({ export_format: "jsonl", max_file_size_mb: 10, fail_closed: false }),
39
50
  rbac: z
40
51
  .object({
41
52
  enabled: z.boolean().default(false),
@@ -45,19 +56,79 @@ const configSchema = z
45
56
  })
46
57
  .strip();
47
58
  const DEFAULT_CONFIG = configSchema.parse({});
59
+ function isRecord(value) {
60
+ return typeof value === "object" && value !== null && !Array.isArray(value);
61
+ }
62
+ /**
63
+ * Resolve the active profile. Precedence: `--profile=` flag > `SPECKY_PROFILE`
64
+ * env > `SPECKY_ENTERPRISE=1` shorthand > config.yml `profile:` > standard.
65
+ * Unknown values are ignored with a stderr warning (never crash the server).
66
+ */
67
+ export function resolveProfile(configProfile, overrides = {}) {
68
+ const argv = overrides.argv ?? process.argv;
69
+ const env = overrides.env ?? process.env;
70
+ const flagValue = argv
71
+ .filter((a) => a.startsWith("--profile="))
72
+ .at(-1)
73
+ ?.slice("--profile=".length);
74
+ const candidates = [
75
+ flagValue,
76
+ env["SPECKY_PROFILE"],
77
+ env["SPECKY_ENTERPRISE"] === "1" ? "enterprise" : undefined,
78
+ ];
79
+ for (const candidate of candidates) {
80
+ if (candidate === undefined)
81
+ continue;
82
+ if (candidate === "standard" || candidate === "enterprise")
83
+ return candidate;
84
+ console.error(`[specky] Ignoring unknown profile "${candidate}" (valid: standard, enterprise).`);
85
+ }
86
+ return configProfile;
87
+ }
88
+ /**
89
+ * Enterprise profile flips the *defaults* of the security controls to ON.
90
+ * A value explicitly present in config.yml always wins, so `rawConfig` (the
91
+ * parsed YAML before Zod defaults) is consulted to tell "user said false"
92
+ * apart from "user said nothing".
93
+ */
94
+ function applyEnterpriseDefaults(config, rawConfig) {
95
+ const raw = isRecord(rawConfig) ? rawConfig : {};
96
+ const rawRbac = isRecord(raw["rbac"]) ? raw["rbac"] : {};
97
+ const rawRateLimit = isRecord(raw["rate_limit"]) ? raw["rate_limit"] : {};
98
+ const rawAudit = isRecord(raw["audit"]) ? raw["audit"] : {};
99
+ return {
100
+ ...config,
101
+ audit_enabled: "audit_enabled" in raw ? config.audit_enabled : true,
102
+ rbac: {
103
+ ...config.rbac,
104
+ enabled: "enabled" in rawRbac ? config.rbac.enabled : true,
105
+ },
106
+ rate_limit: {
107
+ ...config.rate_limit,
108
+ enabled: "enabled" in rawRateLimit ? config.rate_limit.enabled : true,
109
+ },
110
+ audit: {
111
+ ...config.audit,
112
+ fail_closed: "fail_closed" in rawAudit ? config.audit.fail_closed : true,
113
+ },
114
+ };
115
+ }
48
116
  /**
49
117
  * Load `.specky/config.yml` from workspace root. Returns defaults if the file
50
118
  * is absent, unparseable, or fails validation (a bad value never crashes the
51
119
  * server — it falls back to the safe defaults, with a stderr warning).
120
+ *
121
+ * `overrides` lets the server entry point (and tests) inject argv/env for
122
+ * profile resolution instead of reading globals.
52
123
  */
53
- export function loadConfig(workspaceRoot) {
124
+ export function loadConfig(workspaceRoot, overrides = {}) {
54
125
  const configPath = join(workspaceRoot, ".specky", "config.yml");
55
126
  let raw;
56
127
  try {
57
128
  raw = readFileSync(configPath, "utf-8");
58
129
  }
59
130
  catch {
60
- return { ...DEFAULT_CONFIG };
131
+ return finalizeConfig({ ...DEFAULT_CONFIG }, {}, overrides);
61
132
  }
62
133
  let parsed;
63
134
  try {
@@ -65,13 +136,18 @@ export function loadConfig(workspaceRoot) {
65
136
  }
66
137
  catch (err) {
67
138
  console.error(`[specky] Ignoring malformed .specky/config.yml: ${err.message}`);
68
- return { ...DEFAULT_CONFIG };
139
+ return finalizeConfig({ ...DEFAULT_CONFIG }, {}, overrides);
69
140
  }
70
141
  const result = configSchema.safeParse(parsed);
71
142
  if (!result.success) {
72
143
  console.error(`[specky] Ignoring invalid .specky/config.yml: ${result.error.issues.map((i) => i.message).join("; ")}`);
73
- return { ...DEFAULT_CONFIG };
144
+ return finalizeConfig({ ...DEFAULT_CONFIG }, {}, overrides);
74
145
  }
75
- return result.data;
146
+ return finalizeConfig(result.data, parsed, overrides);
147
+ }
148
+ function finalizeConfig(config, rawConfig, overrides) {
149
+ const profile = resolveProfile(config.profile, overrides);
150
+ const resolved = { ...config, profile };
151
+ return profile === "enterprise" ? applyEnterpriseDefaults(resolved, rawConfig) : resolved;
76
152
  }
77
153
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,kFAAkF;AAClF,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CACxC,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,KAAK,EAAE;IACR,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;QACjB,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;QACnB,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAChC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EACtB,EAAE,OAAO,EAAE,gFAAgF,EAAE,CAC9F,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC;KACnB,MAAM,CAAC;IACN,cAAc,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;IAC5C,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;IAC/C,qBAAqB,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC;IAC/D,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACzC,UAAU,EAAE,CAAC;SACV,MAAM,CAAC;QACN,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;QACnC,uBAAuB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;QAChE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;KAC/C,CAAC;SACD,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,uBAAuB,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACtE,KAAK,EAAE,CAAC;SACL,MAAM,CAAC;QACN,aAAa,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;QACnE,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;KACpD,CAAC;SACD,OAAO,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE,EAAE,CAAC;IAC5D,IAAI,EAAE,CAAC;SACJ,MAAM,CAAC;QACN,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;QACnC,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC;KAChF,CAAC;SACD,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC;CAC5D,CAAC;KACD,KAAK,EAAE,CAAC;AAIX,MAAM,cAAc,GAAiB,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAE5D;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,aAAqB;IAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;IAChE,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,GAAG,cAAc,EAAE,CAAC;IAC/B,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;IAC5B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,mDAAoD,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAC3F,OAAO,EAAE,GAAG,cAAc,EAAE,CAAC;IAC/B,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CACX,iDAAiD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACxG,CAAC;QACF,OAAO,EAAE,GAAG,cAAc,EAAE,CAAC;IAC/B,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -9,7 +9,6 @@
9
9
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
10
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
11
  import { randomUUID } from "node:crypto";
12
- import { isBearerAuthorized } from "./utils/http-auth.js";
13
12
  import { VERSION, SERVER_NAME, DEFAULT_HTTP_PORT } from "./constants.js";
14
13
  import { FileManager } from "./services/file-manager.js";
15
14
  import { StateMachine } from "./services/state-machine.js";
@@ -45,7 +44,8 @@ import { registerTurnkeyTools } from "./tools/turnkey.js";
45
44
  import { PbtGenerator } from "./services/pbt-generator.js";
46
45
  import { registerPbtTools } from "./tools/pbt.js";
47
46
  import { loadConfig } from "./config.js";
48
- import { AuditLogger } from "./services/audit-logger.js";
47
+ import { AuditLogger, resolveAuditHmacKey } from "./services/audit-logger.js";
48
+ import { loadTokenTable, resolveBearerIdentity, sha256Hex } from "./utils/token-table.js";
49
49
  import { MetricsGenerator } from "./services/metrics-generator.js";
50
50
  import { registerMetricsTools } from "./tools/metrics.js";
51
51
  import { ModelRoutingEngine } from "./services/model-routing-engine.js";
@@ -64,12 +64,26 @@ import { registerAuditTools } from "./tools/audit.js";
64
64
  // Resolve workspace root
65
65
  const workspaceRoot = process.env["SDD_WORKSPACE"] || process.cwd();
66
66
  console.error(`[specky] Workspace root: ${workspaceRoot}`);
67
- // Load optional project config (.specky/config.yml)
67
+ // Load optional project config (.specky/config.yml). The profile may be
68
+ // forced from outside the workspace: --profile=enterprise flag, SPECKY_PROFILE
69
+ // env, or SPECKY_ENTERPRISE=1 shorthand.
68
70
  const config = loadConfig(workspaceRoot);
71
+ const auditHmacKey = resolveAuditHmacKey();
69
72
  if (config.templates_path)
70
73
  console.error(`[specky] Custom templates: ${config.templates_path}`);
71
- if (config.audit_enabled)
72
- console.error(`[specky] Audit trail: enabled`);
74
+ if (config.profile === "enterprise") {
75
+ console.error(`[specky] Profile: enterprise — audit=${config.audit_enabled ? "on" : "off"}` +
76
+ ` (fail_closed=${config.audit.fail_closed ? "on" : "off"}, hmac=${auditHmacKey ? "on" : "off"})` +
77
+ `, rbac=${config.rbac.enabled ? "on" : "off"} (default_role=${config.rbac.default_role})` +
78
+ `, rate_limit=${config.rate_limit.enabled ? "on" : "off"}`);
79
+ if (config.audit_enabled && !auditHmacKey) {
80
+ console.error("[specky] WARNING: enterprise audit is hash-chained but NOT tamper-evident — " +
81
+ "set SDD_AUDIT_HMAC_KEY or SDD_AUDIT_HMAC_KEY_FILE (key stored outside the workspace) to sign entries.");
82
+ }
83
+ }
84
+ else if (config.audit_enabled) {
85
+ console.error(`[specky] Audit trail: enabled${auditHmacKey ? " (HMAC-signed)" : ""}`);
86
+ }
73
87
  // Initialize MCP server
74
88
  const server = new McpServer({
75
89
  name: SERVER_NAME,
@@ -109,7 +123,12 @@ const docGenerator = new DocGenerator(fileManager, stateMachine);
109
123
  const gitManager = new GitManager(fileManager);
110
124
  const testGenerator = new TestGenerator(fileManager);
111
125
  const pbtGenerator = new PbtGenerator(fileManager);
112
- const auditLogger = new AuditLogger(workspaceRoot, config.audit_enabled, config.audit.export_format, config.audit.max_file_size_mb);
126
+ const auditLogger = new AuditLogger(workspaceRoot, config.audit_enabled, {
127
+ exportFormat: config.audit.export_format,
128
+ maxFileSizeMb: config.audit.max_file_size_mb,
129
+ hmacKey: auditHmacKey,
130
+ failClosed: config.audit.fail_closed,
131
+ });
113
132
  const rbacEngine = new RbacEngine(config.rbac.enabled ?? false, config.rbac.default_role ?? "contributor");
114
133
  const metricsGenerator = new MetricsGenerator(fileManager);
115
134
  const modelRoutingEngine = new ModelRoutingEngine();
@@ -174,18 +193,36 @@ async function main() {
174
193
  const hostArg = args.find((a) => a.startsWith("--host="))?.slice("--host=".length);
175
194
  const host = hostArg || process.env["SDD_HTTP_HOST"] || "127.0.0.1";
176
195
  const isLoopback = host === "127.0.0.1" || host === "::1" || host === "localhost";
177
- // Optional bearer-token auth. When SDD_HTTP_TOKEN is set, every /mcp request
178
- // must present `Authorization: Bearer <token>`. Compared in constant time.
196
+ // Optional bearer-token auth, two flavors:
197
+ // - SDD_HTTP_TOKEN: one shared token (no identity RBAC falls back to
198
+ // SDD_ROLE / default_role).
199
+ // - SDD_HTTP_TOKENS_FILE: named tokens mapping to principal + role
200
+ // (identity-based RBAC). Fail-closed: a broken tokens file aborts startup.
179
201
  const authToken = process.env["SDD_HTTP_TOKEN"] || "";
180
- const requireAuth = authToken.length > 0;
181
- if (requireAuth) {
182
- console.error("[specky] HTTP bearer-token authentication enabled.");
202
+ const tokensFile = process.env["SDD_HTTP_TOKENS_FILE"] || "";
203
+ let tokenTable = [];
204
+ if (tokensFile) {
205
+ try {
206
+ tokenTable = loadTokenTable(tokensFile);
207
+ console.error(`[specky] HTTP token table loaded: ${tokenTable.length} principal(s) from ${tokensFile}`);
208
+ }
209
+ catch (err) {
210
+ console.error(`[specky] FATAL: ${err.message}`);
211
+ process.exit(1);
212
+ }
213
+ }
214
+ else if (authToken) {
215
+ console.error("[specky] HTTP bearer-token authentication enabled (shared token).");
183
216
  }
217
+ const requireAuth = tokenTable.length > 0 || authToken.length > 0;
184
218
  if (!isLoopback && !requireAuth) {
185
219
  console.error(`[specky] WARNING: HTTP transport bound to non-loopback host "${host}" WITHOUT authentication. ` +
186
- "Set SDD_HTTP_TOKEN and put it behind a TLS-terminating reverse proxy, or bind to 127.0.0.1.");
220
+ "Set SDD_HTTP_TOKEN or SDD_HTTP_TOKENS_FILE and put it behind a TLS-terminating reverse proxy, or bind to 127.0.0.1.");
221
+ }
222
+ if (config.profile === "enterprise" && config.rbac.enabled && tokenTable.length === 0) {
223
+ console.error("[specky] NOTE: RBAC is on but no SDD_HTTP_TOKENS_FILE is configured — roles come from " +
224
+ "SDD_ROLE/default_role (self-asserted). Configure a token table for identity-based roles.");
187
225
  }
188
- const isAuthorized = (header) => isBearerAuthorized(header, authToken);
189
226
  const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
190
227
  const http = await import("node:http");
191
228
  const transport = new StreamableHTTPServerTransport({
@@ -206,7 +243,8 @@ async function main() {
206
243
  const httpServer = http.createServer(async (req, res) => {
207
244
  if (req.url === "/mcp") {
208
245
  // Authenticate before doing any work.
209
- if (!isAuthorized(req.headers["authorization"])) {
246
+ const identity = resolveBearerIdentity(req.headers["authorization"], tokenTable, authToken);
247
+ if (!identity.authorized) {
210
248
  res.writeHead(401, {
211
249
  "Content-Type": "application/json",
212
250
  "WWW-Authenticate": "Bearer",
@@ -214,6 +252,17 @@ async function main() {
214
252
  res.end(JSON.stringify({ error: "Unauthorized" }));
215
253
  return;
216
254
  }
255
+ // Attach the authenticated identity so the transport propagates it to
256
+ // tool handlers as extra.authInfo (consumed by the RBAC enforcement
257
+ // wrapper). `token` carries a fingerprint, never the secret itself.
258
+ if (identity.principal) {
259
+ req.auth = {
260
+ token: sha256Hex(req.headers["authorization"] ?? ""),
261
+ clientId: identity.principal,
262
+ scopes: identity.role ? [`role:${identity.role}`] : [],
263
+ extra: { principal: identity.principal, role: identity.role },
264
+ };
265
+ }
217
266
  // Apply rate limiting before forwarding to MCP handler
218
267
  if (rateLimiter) {
219
268
  const clientId = req.socket.remoteAddress ?? "unknown";