↑ ↓ 移动 · Enter 打开 · Esc 关闭点按一个结果

架构
skill · build 类 · sonnet

transport-security-setup

决定 TLS 在哪里终止,在 edge 还是嵌入式 Tomcat,并写出相应配置和证明它的测试。

skill · buildsonneteffort low7 允许的路径随生成的项目导出
§ skill 的定义

transport-security-setup,逐节说明

skill 自己的 SKILL.md 的各个章节,节选。悬停或点击一个章节,查看它引用的项目规则、模板和 references/ 文件。章节也是标签:点击即可跳转。

nerviz/.claude/skills/transport-security-setup/SKILL.md

## Why this is a skill (Form 1) and not Form 2

Form 1: project-bootstrap and arch-adopt chain it by name, and a skill with disable-model-invocation: true cannot be called through the Skill tool — the reason sonarqube-setup stays model-invocable. Axis 2 of the interview — two callers, one of which runs inside a project where project-bootstrap does not exist — made it a skill of its own; the closest rejected form was a transport block inside security-architect, a design skill that writes docs only and runs per use case, so a project with no protected endpoint would never decide transport. Record: @.claude/decisions/0122-transport-security-setup-and-secrets.md.

Runs on sonnet with effort: low: one interview with fixed options, then edits per answer (@.claude/decisions/0080-procedural-skills-on-sonnet.md).

## Procedure

Project directory. When the caller's one-line context names project: <absolute path>, that directory is the project root, not the session's: read and write every path of this procedure under it, and run every shell command as (cd "<path>" && …). When this skill chains docker-architect, pass project: <path> on in its context.

### 1 · Detect

grep -n "^\*\*Transport:" CLAUDE.md 2>/dev/null
grep -rnE "server\.ssl|ssl:|bundle:|forward-headers-strategy" --include='application*.yml' --include='application*.properties' . 2>/dev/null | grep -v /target/
grep -nE "spring-boot-starter-(security|jetty)" pom.xml build.gradle */pom.xml */build.gradle 2>/dev/null
  • **Transport: found → entry rule: report and stop.
  • pom.xml at the root → maven; build.gradle → gradle. The main module is the one with
  • spring-boot-starter-security present → the security filter chain owns HSTS, and step 4 is
  • spring-boot-starter-jetty present → topology B and C are not offered: Jetty does not reload

### 2 · Interview — two `AskUserQuestion` calls

When a caller already passed an answer in its one-line context, don't ask it again.

First call — the topology, with one line of what each costs:

OptionMeans
*A · Edge (Recommended)*An ingress, load balancer or reverse proxy terminates TLS and renews the certificate; the application holds no key and trusts X-Forwarded-* from the edge's network only
*B · Direct*The embedded Tomcat terminates TLS on 8443 with a certificate mounted at run time and reloaded on renewal; the application owns the key
*C · Mutual TLS*B, and every caller presents a client certificate from a private CA; a missing one fails the handshake, never a 401
*C′ · Service mesh*The mesh sidecar terminates TLS; the application is configured as in A

Second call, depending on the first:

  • A or C′ — local edge: "Run a local Caddy edge in docker-compose, so HTTPS, HTTP/2 and HSTS
  • B or C — two questions in the same call:

### 3 · Write the configuration

Merge into the main module's application.yml — into its existing roots, never a second server: or spring: key in the same document:

TopologyExemplarAdjust
A, C′templates/application-tls-edge.yml.examplenothing
Btemplates/application-tls-direct.yml.examplethe first block into the existing roots, the --- document appended at the end. Local profile *No* → the expression becomes `"!docker & (!testtls)"`
Csame as Balso uncomment the bundle's truststore and server.ssl.client-auth: need

server.http2.enabled: true goes in for every topology.

### 4 · HSTS — only when step 1 found no Spring Security

Write templates/HstsHeaderFilter.java.example into the blueprint's configuration package plus .web (config.web, infrastructure.config.web); in a blueprint with no configuration package (hexagonal, onion, custom), into a web subpackage of the inbound REST adapter. Base package and package names come from the blueprint's packages.map, never from the exemplar.

With Spring Security present, nothing is written: its default header writer already sends the same value, and the filter would only override it.

### 5 · The test

TopologyExemplarWhere
A, C′templates/ForwardedHeadersIT.java.examplesrc/test/java/<base package>/ of the main module
B, Ctemplates/TransportSecurityIT.java.examplesame

For B and C, every @SpringBootTest must run with the test profile, or its context now starts with TLS on and fails reading /etc/tls (@.claude/rules/testing.md § Slices and context requires it anyway):

grep -rL '@ActiveProfiles("test")' $(grep -rl "@SpringBootTest" --include='*.java' src/test/ 2>/dev/null) 2>/dev/null

Add @ActiveProfiles("test") to each file listed. The generator's own *ApplicationTests is usually the only one.

A datasource in the context. When the project has a Testcontainers configuration —

grep -rl "class TestcontainersConfiguration" --include='*.java' src/test/ */src/test/ 2>/dev/null

… 此处截断,其余见文件

### 6 · Container — through `docker-architect`

Invoke docker-architect through the Skill tool with a one-line context, and only in these two cases. It owns every service block and the Dockerfile; don't write either here, not even when it looks like a copy.

  • A or C′ with a local edge → "service edge (Caddy) and docker/caddy/Caddyfile, chained from
  • B or C → "Dockerfile: the image also listens on 8443 (EXPOSE 8080 8443), chained from

No docker-compose.yml or Dockerfile at the root → docker-architect stops on its own entry rule; say so in the report.

### 7 · Record the decision

Write the matching paragraph of templates/CLAUDE-transport.md.example into the root CLAUDE.md, placeholders filled, right below the **Bounded context: paragraph (or below the first heading when there is none). That template owns the wording.

### 8 · Verify

When chained by project-bootstrap, skip: its own Verify runs next and covers the test this skill wrote. Otherwise run the project's full build — ./mvnw -B verify or ./gradlew build. A red test is reported with its first failing assertion, never "fixed" by loosening the configuration.

## Report

✅ Transport decided — topology <A edge | B direct | C mutual TLS | C′ mesh>

Configuration . <application.yml path> — <forwarded headers + trusted proxies | SSL bundle `webserver`, reload, 8443, TLS on except <profiles>> · HTTP/2 on
HSTS .......... <Spring Security's default writer | HstsHeaderFilter in <package>>
Test .......... <ForwardedHeadersIT | TransportSecurityIT> — <green in the build | runs in project-bootstrap's Verify> · @ActiveProfiles("test") added to <n files | none needed>
Container ..... <edge (Caddy) added by docker-architect | Dockerfile EXPOSE 8443 by docker-architect | not touched — <reason>>
CLAUDE.md ..... Transport paragraph written

Then the lines the person needs on day one, by topology:

  • A / C′ — in each environment, set TRUSTED_PROXIES to the edge's addresses (CIDR list);
  • B / C — mount the certificate and key (PEM, PKCS#8) as files and set TLS_CERTIFICATE and
  • C — the bundle's truststore holds only the private CA that issues client certificates, from
在 GitHub 上打开完整文件 →
§ 行为

它读什么、写什么、拒绝什么

skill 自己的契约,取自 SKILL.md,保留原文。

Class

build — the territory is skill_classes.build's override for this skill in @.claude/schemas/extensions.json: the main module's application*.yml, the HstsHeaderFilter.java file, src/test/ (the IT, and the test profile on existing @SpringBootTest classes), and the root CLAUDE.md. ArchHook.java guard enforces it. docker-compose.yml, docker/ and the Dockerfile are deliberately outside it.

Reads

the root CLAUDE.md, the build file, the active blueprint's packages.map, and the main module's application*.yml.

Writes

the transport configuration, the HSTS filter when no security chain exists, one transport IT, and the **Transport: paragraph — the project fact security-architect, http-client-architect and docker-architect read.

Never writes

a certificate, a key, a keystore or a password, in any file.

Delegates

the edge service, the Caddyfile and the Dockerfile port to docker-architect, the single owner of both files. Outbound TLS — a client certificate or a private CA for a call this service makes — is http-client-architect's, never this skill's. The client-certificate authentication mechanism of topology C is security-architect's, designed with the first use case that needs it.

Chained by

project-bootstrap (step 7.5, before Verify) and arch-adopt (step 7.5), and invocable by hand. Travels into the generated project (export.skills.include): arch-adopt runs inside the project, where project-bootstrap does not exist, and needs this skill there.

来源:SKILL.md § Contract(nerviz e4cae27)。

§ guard 与 hook

guard 如何处理它

build 是该 skill 的类,类中每个 skill 都有自己的领地。由谁调用决定适用哪个领地:在 project-initializer 之下用 agent 的;从主线程调用则根本不放行。右侧文字是 hook 自己的输出。

transport-security-setup 类的 write_allow

  • ✓src/main/resources/application*.yml
  • ✓**/src/main/resources/application*.yml
  • ✓src/main/java/**/HstsHeaderFilter.java
  • ✓**/src/main/java/**/HstsHeaderFilter.java
  • ✓src/test/**
  • ✓**/src/test/**
  • ✓CLAUDE.md
  • 当 skill 自己打开阶段时,此列表之外的任何路径
  • /init-project 打开时来自主线程的调用

拒绝以退出码 2 结束,消息回到模型。

由 project-initializer 串联时,它的写入带着该 agent 的 agent_type,按 agent_classes.driver(**)判定。此列表仅在 skill 由自己的提示运行时适用。

PreToolUse  Skill(transport-security-setup)
# agent_type: project-initializer (class driver)
  guard call                       exit 0
# a classed agent's call leaves the phase as it was
PreToolUse  Write(../bank-app/src/main/resources/application.yml)
  guard write                      exit 0
# judged by agent_classes.driver, write_allow: **
§ 编排

它在轨道上的位置

每张卡片是 /init-project 的一步。访谈在主会话中进行;着色的步骤在 project-initializer 内运行;git-publish 在 audit genesis 之后回到主会话。虚线步骤仅在适用时运行。

← 返回链路