Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/146-fileserver-verification-security-audit-probes.txt
T
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.

Follows the import procedure in README.md.

  source/     the originating repository verbatim — 78 documents, 28 SVGs,
              8 manifests, plus .source-revision recording the commit
  final/      the SSOT
    document.md   729 lines written from the 29 experiment documents, not
                  concatenated: what was predicted, what was measured, and
                  where the measurement itself was wrong
    evidence/raw    125 outputs, flattened to <experiment>__<file> because
                    the originals collided (01-baseline.txt appeared three
                    times) and the audit only globs the top level
    evidence/meta   one per raw file; command and exitCode are null and the
                    README says why rather than inventing them
    evidence/browser  22 captures
    assets/       three diagrams through techviz
    .techviz/     their VizSpecs

A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.

Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.

verify-pipeline.py passes. audit-records.py reports no issues.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 22:51:59 +09:00

154 lines
13 KiB
Plaintext

Sub-scope 05 (fileserver) platform/local failure·probe·health·orphan + verification + security + audit
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
generatedAt=2026-08-30T01:27:59+00:00
=== OWNED FILES (24 main + 5 test) ===
9e61e441e81fd868a99c09b8afd1c4fa9a9e9172 platform/local/FilesystemFailureClassifier.java 51
67318269844b61a021bf3ed964ffb92b0abe7b30 platform/local/FilesystemOutcome.java 23
62e9aac34c4ae1696ac86c36b7caed19380ac075 platform/local/AmbiguousFilesystemOperationDetector.java 91
04b3b101d17e0b2d4148219c24466d1a1a27e797 platform/local/LocalStorageFailures.java 48
53e89c47a91b09940d297ea204b1a76fd70f9af7 platform/local/LocalStorageCapabilityProbe.java 302
7b803247f076a9bc79ec2fa084c6bac3b0e580f5 platform/local/LocalStorageProbeResult.java 53
ee5121e14abe0da88103aa3e713954c52a8c7b14 platform/local/LocalStorageHealthAdapter.java 70
636b22ca38c666258c5888a667234d20e4212cce platform/local/LocalStorageUsageProbe.java 46
527677720cbc2381fb41fd3b50c61285d25d5bfd platform/local/LocalReconciliationContentProbe.java 84
2bc58d8cd0dd9c54dbf756cbf832c05a1c04efff platform/local/LocalOrphanScanAdapter.java 239
cae4bf09a9d405aa927e4442007ca778aa1aa8a6 platform/verification/FilenamePolicyVerifier.java 38
30fc5c76d7c0f2ab45df7f6f4297566a5c4166f3 platform/verification/LengthVerifier.java 41
d24d5bc45303f4cd062266af4cf3d32b4eecc320 platform/verification/LocalVerificationContentReader.java 68
995e30a163712f4afc85e0f6ceb1a89470cf890a platform/verification/MediaTypeVerifier.java 108
3e2a1958457d413e71e581d2a2057fc53a004eae platform/verification/Sha256Verifier.java 32
ff6d5cdf0f6379464f4dc43c3c53d035fdab1bc6 platform/verification/ScriptableContentPolicy.java 57
6b592a50a5e46d8f110100248fef5e360295e692 platform/verification/VerificationContentReader.java 16
84602304e188c7942841d193650872e8b8c34246 platform/verification/VerificationCoordinator.java 67
0aec00fdd700990879833c03f48a653daad5a587 platform/verification/VerificationPolicyCombiner.java 43
3739210eadfbd16afede10a169a60bd1ffe5885d platform/verification/VerificationVerdictPrecedence.java 11
815473a43331d452ecc23ae7ed10b1a08bece58c platform/security/RoleBasedFileAccessPolicy.java 98
3f4c47032cea3783ec230e834d1ca385c71d74df platform/security/UnenforcedFileAccessPolicy.java 27
afec1144e343f5817ebb1e848770032ebf420c42 platform/audit/StructuredAdminAuditAdapter.java 35
4e9094c7a1a194345bae9f9208ed9f6aa05aa256 platform/audit/StructuredFileserverAuditAdapter.java 30
=== 8.1 resolving the sub-scope 01 forward reference: are these real implementations? ===
$ grep -n 'implements ' adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/audit/*.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/security/*.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/local/LocalStorageHealthAdapter.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/local/LocalStorageUsageProbe.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/local/LocalOrphanScanAdapter.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/local/LocalReconciliationContentProbe.java
adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/audit/StructuredAdminAuditAdapter.java:19:public final class StructuredAdminAuditAdapter implements AdminAuditPort {
adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/audit/StructuredFileserverAuditAdapter.java:15:public final class StructuredFileserverAuditAdapter implements FileserverAuditPort {
adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/security/RoleBasedFileAccessPolicy.java:27:public final class RoleBasedFileAccessPolicy implements FileAccessPolicy {
adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/security/UnenforcedFileAccessPolicy.java:20:public final class UnenforcedFileAccessPolicy implements FileAccessPolicy {
adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/local/LocalStorageHealthAdapter.java:24:public final class LocalStorageHealthAdapter implements StorageHealthPort {
adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/local/LocalStorageUsageProbe.java:21:public final class LocalStorageUsageProbe implements StorageUsageProbe {
adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/local/LocalOrphanScanAdapter.java:45:public final class LocalOrphanScanAdapter implements OrphanScanPort {
adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/local/LocalReconciliationContentProbe.java:22:public final class LocalReconciliationContentProbe implements ReconciliationContentProbe {
exit=0
$ git grep -rn 'new StructuredAdminAuditAdapter\|new StructuredFileserverAuditAdapter\|new LocalStorageHealthAdapter\|new LocalStorageUsageProbe\|new LocalOrphanScanAdapter\|new LocalReconciliationContentProbe\|new RoleBasedFileAccessPolicy\|new UnenforcedFileAccessPolicy' -- app-bootstrap/src/main
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java:51: new RoleBasedFileAccessPolicy(
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java:71: return new StructuredAdminAuditAdapter();
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java:77: return new StructuredFileserverAuditAdapter();
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java:90: return new UnenforcedFileAccessPolicy();
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java:179: return new LocalStorageHealthAdapter(properties, probeResult, store);
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java:189: return new LocalOrphanScanAdapter(
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java:196: return new LocalStorageUsageProbe(properties.root());
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java:203: return new LocalReconciliationContentProbe(store, stagingLocator);
exit=0
$ grep -rn 'not implemented' adapter/outbound/fileserver/README.md
103:`shared-mounted`/NFS and SFTP providers are not implemented. Cross-node producer fencing,
105:tracing, and audit are also not implemented. No setting or bean for those capabilities is exposed.
exit=0
=== 8.2 the scriptable-content marker: anchored at the prefix start ===
$ grep -n 'SCRIPTABLE_MARKERS\|stripLeading\|startsWith' -B 2 -A 2 adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/verification/ScriptableContentPolicy.java
21- private static final int PREFIX_BYTES = 1024;
22-
23: private static final List<String> SCRIPTABLE_MARKERS =
24- List.of("<!doctype html", "<html", "<script", "<svg", "<?xml", "<!entity");
25-
--
42- byte[] prefix = contentReader.readPrefix(request, PREFIX_BYTES);
43- String text =
44: new String(prefix, StandardCharsets.UTF_8).toLowerCase(Locale.ROOT).stripLeading();
45: boolean scriptable = SCRIPTABLE_MARKERS.stream().anyMatch(text::startsWith);
46-
47- if (!scriptable) {
exit=0
$ grep -n 'Detection is on content' -A 3 adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/verification/ScriptableContentPolicy.java
15: * <p>Detection is on content, not on the claimed type or the extension, because both are attacker
16- * controlled. Unless an explicit safe profile is enabled, scriptable content is quarantined rather
17- * than published.
18- */
exit=0
=== EXECUTION PROBE: what ScriptableContentPolicy actually quarantines ===
One temporary test class was added, run, and removed:
src/test/.../platform/verification/Ss05ScriptableProbe.java
inlineSafeProfile=false (the enforcing profile); the reader returns each candidate verbatim.
PROBE plain <script> -> QUARANTINE / SCRIPTABLE_CONTENT
PROBE plain <html> -> QUARANTINE / SCRIPTABLE_CONTENT
PROBE leading whitespace + <html> -> QUARANTINE / SCRIPTABLE_CONTENT
PROBE UTF-8 BOM + <html> -> ACCEPT / NO_SCRIPTABLE_CONTENT
PROBE HTML comment then <script> -> ACCEPT / NO_SCRIPTABLE_CONTENT
PROBE uppercase <SCRIPT> -> QUARANTINE / SCRIPTABLE_CONTENT
PROBE <svg onload> -> QUARANTINE / SCRIPTABLE_CONTENT
PROBE NUL byte then <html> -> ACCEPT / NO_SCRIPTABLE_CONTENT
PROBE plain text -> ACCEPT / NO_SCRIPTABLE_CONTENT
Reading: the check is text.stripLeading().startsWith(marker). String.stripLeading removes
only Character.isWhitespace, so a UTF-8 BOM (U+FEFF) or a NUL byte before the markup
defeats it, and any leading HTML comment does too. A browser renders all three as HTML.
=== 8.2b the sibling verifier decides on content the same way; does it anchor? ===
$ grep -n 'signature.matches(prefix)\|prefix\[index\]' -B 2 -A 2 adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/verification/MediaTypeVerifier.java
70- private static Optional<String> detect(byte[] prefix) {
71- for (Signature signature : SIGNATURES) {
72: if (signature.matches(prefix)) {
73- return Optional.of(signature.mediaType());
74- }
--
99- }
100- for (int index = 0; index < magic.size(); index++) {
101: if ((prefix[index] & 0xFF) != magic.get(index)) {
102- return false;
103- }
exit=0
=== 8.3 fail-closed composition around the verifiers ===
$ grep -n 'VERIFIER_TIMEOUT\|VERIFIER_UNAVAILABLE\|VERIFIER_INTERRUPTED\|NO_VERIFIER_ANSWERED' adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/verification/VerificationCoordinator.java adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/verification/VerificationPolicyCombiner.java
adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/verification/VerificationCoordinator.java:59: return VerificationResult.retry("VERIFIER_TIMEOUT");
adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/verification/VerificationCoordinator.java:62: return VerificationResult.retry("VERIFIER_INTERRUPTED");
adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/verification/VerificationCoordinator.java:64: return VerificationResult.retry("VERIFIER_UNAVAILABLE");
adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/verification/VerificationPolicyCombiner.java:21: return VerificationResult.retry("NO_VERIFIER_ANSWERED");
exit=0
$ grep -n 'REJECT > QUARANTINE > RETRY > ACCEPT' -B 2 -A 2 adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/verification/VerificationPolicyCombiner.java
9- * Reduces per-verifier answers to one verdict.
10- *
11: * <p>Precedence is {@code REJECT > QUARANTINE > RETRY > ACCEPT}. Because {@code RETRY} outranks
12- * {@code ACCEPT}, a scanner that timed out can never be silently overridden by the verifiers that
13- * did answer.
exit=0
=== 8.4 message hygiene: does any failure or audit line carry a path? ===
$ grep -rn 'exception.getMessage()\|root\b' adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/local/LocalStorageFailures.java
16: * <p>No message carries a path, a mount, or a storage root: transports map on the error code alone.
exit=0
$ grep -rn 'record.subjectId()\|actorFingerprint' adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/audit/*.java
adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/audit/StructuredAdminAuditAdapter.java:30: record.actorFingerprint(),
adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/audit/StructuredAdminAuditAdapter.java:31: record.subjectId(),
adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/audit/StructuredFileserverAuditAdapter.java:26: event.actorFingerprint(),
exit=0
$ grep -n 'names neither the required role' -A 3 adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/security/RoleBasedFileAccessPolicy.java
86: * <p>The message names neither the required role nor the subject's roles: a denial that reported
87- * what was missing would turn every 403 into a readable description of the role model.
88- */
89- private static FileAccessDeniedException denied(FileserverErrorCode code, String message) {
exit=0
$ git status --short | wc -l
0
exit=0