From 808ab87b212796313e6b1a9a4a1315521a36411b Mon Sep 17 00:00:00 2001 From: StellaOps Bot Date: Sun, 30 Nov 2025 21:01:00 +0200 Subject: [PATCH] up --- .../js/express-eval/outputs/SINK_REACHED | 1 + .../js/express-eval/outputs/binary.tar.gz | Bin 0 -> 2092 bytes .../js/express-eval/outputs/coverage.json | 15 + .../express-eval/outputs/traces/traces.json | 10 + .../js/express-guarded/outputs/binary.tar.gz | Bin 0 -> 2089 bytes .../js/express-guarded/outputs/coverage.json | 16 + .../outputs/traces/traces.json | 9 + .../js/fastify-template/outputs/SINK_REACHED | 1 + .../js/fastify-template/outputs/binary.tar.gz | Bin 0 -> 2103 bytes .../js/fastify-template/outputs/coverage.json | 15 + .../outputs/traces/traces.json | 9 + .../js/guarded-eval/outputs/binary.tar.gz | Bin 0 -> 2014 bytes .../js/guarded-eval/outputs/coverage.json | 15 + .../guarded-eval/outputs/traces/traces.json | 9 + .../cases/js/unsafe-eval/outputs/SINK_REACHED | 1 + .../js/unsafe-eval/outputs/binary.tar.gz | Bin 0 -> 2121 bytes .../js/unsafe-eval/outputs/coverage.json | 14 + .../js/unsafe-eval/outputs/traces/traces.json | 9 + .../cases/py/django-ssti/outputs/SINK_REACHED | 1 + .../py/django-ssti/outputs/binary.tar.gz | Bin 0 -> 2544 bytes .../py/django-ssti/outputs/coverage.json | 16 + .../py/django-ssti/outputs/traces/traces.json | 9 + .../py/fastapi-guarded/outputs/binary.tar.gz | Bin 0 -> 2446 bytes .../py/fastapi-guarded/outputs/coverage.json | 15 + .../outputs/traces/traces.json | 9 + .../py/flask-template/outputs/SINK_REACHED | 1 + .../py/flask-template/outputs/binary.tar.gz | Bin 0 -> 2549 bytes .../py/flask-template/outputs/coverage.json | 16 + .../flask-template/outputs/traces/traces.json | 9 + .../py/guarded-exec/outputs/binary.tar.gz | Bin 0 -> 2458 bytes .../py/guarded-exec/outputs/coverage.json | 15 + .../guarded-exec/outputs/traces/traces.json | 9 + .../cases/py/unsafe-exec/outputs/SINK_REACHED | 1 + .../py/unsafe-exec/outputs/binary.tar.gz | Bin 0 -> 2325 bytes .../py/unsafe-exec/outputs/coverage.json | 14 + .../py/unsafe-exec/outputs/traces/traces.json | 9 + docs/README.md | 5 +- docs/implplan/SPRINT_0212_0001_0001_web_i.md | 3 +- ...0001_0001_public_reachability_benchmark.md | 3 +- .../SPRINT_300_documentation_process.md | 22 ++ docs/implplan/SPRINT_503_ops_devops_i.md | 6 +- docs/implplan/tasks-all.md | 4 +- .../modules/platform/architecture-overview.md | 17 + docs/onboarding/dev-quickstart.md | 326 ++++++++++++++++++ ops/devops/airgap/README.md | 2 + ops/devops/airgap/compose-syslog-smtp.yaml | 31 ++ ops/devops/airgap/health_syslog_smtp.sh | 23 ++ ops/devops/sbom-ci-runner/README.md | 29 ++ ops/devops/sbom-ci-runner/run-sbom-ci.sh | 72 ++++ src/Web/StellaOps.Web/TASKS.md | 8 + .../src/app/core/aoc/checksum.util.ts | 71 ++++ .../app/core/aoc/provenance-builder.spec.ts | 128 +++++++ .../src/app/core/aoc/provenance-builder.ts | 40 +++ .../src/app/core/aoc/signature-verifier.ts | 133 +++++++ 54 files changed, 1163 insertions(+), 8 deletions(-) create mode 100644 bench/reachability-benchmark/cases/js/express-eval/outputs/SINK_REACHED create mode 100644 bench/reachability-benchmark/cases/js/express-eval/outputs/binary.tar.gz create mode 100644 bench/reachability-benchmark/cases/js/express-eval/outputs/coverage.json create mode 100644 bench/reachability-benchmark/cases/js/express-eval/outputs/traces/traces.json create mode 100644 bench/reachability-benchmark/cases/js/express-guarded/outputs/binary.tar.gz create mode 100644 bench/reachability-benchmark/cases/js/express-guarded/outputs/coverage.json create mode 100644 bench/reachability-benchmark/cases/js/express-guarded/outputs/traces/traces.json create mode 100644 bench/reachability-benchmark/cases/js/fastify-template/outputs/SINK_REACHED create mode 100644 bench/reachability-benchmark/cases/js/fastify-template/outputs/binary.tar.gz create mode 100644 bench/reachability-benchmark/cases/js/fastify-template/outputs/coverage.json create mode 100644 bench/reachability-benchmark/cases/js/fastify-template/outputs/traces/traces.json create mode 100644 bench/reachability-benchmark/cases/js/guarded-eval/outputs/binary.tar.gz create mode 100644 bench/reachability-benchmark/cases/js/guarded-eval/outputs/coverage.json create mode 100644 bench/reachability-benchmark/cases/js/guarded-eval/outputs/traces/traces.json create mode 100644 bench/reachability-benchmark/cases/js/unsafe-eval/outputs/SINK_REACHED create mode 100644 bench/reachability-benchmark/cases/js/unsafe-eval/outputs/binary.tar.gz create mode 100644 bench/reachability-benchmark/cases/js/unsafe-eval/outputs/coverage.json create mode 100644 bench/reachability-benchmark/cases/js/unsafe-eval/outputs/traces/traces.json create mode 100644 bench/reachability-benchmark/cases/py/django-ssti/outputs/SINK_REACHED create mode 100644 bench/reachability-benchmark/cases/py/django-ssti/outputs/binary.tar.gz create mode 100644 bench/reachability-benchmark/cases/py/django-ssti/outputs/coverage.json create mode 100644 bench/reachability-benchmark/cases/py/django-ssti/outputs/traces/traces.json create mode 100644 bench/reachability-benchmark/cases/py/fastapi-guarded/outputs/binary.tar.gz create mode 100644 bench/reachability-benchmark/cases/py/fastapi-guarded/outputs/coverage.json create mode 100644 bench/reachability-benchmark/cases/py/fastapi-guarded/outputs/traces/traces.json create mode 100644 bench/reachability-benchmark/cases/py/flask-template/outputs/SINK_REACHED create mode 100644 bench/reachability-benchmark/cases/py/flask-template/outputs/binary.tar.gz create mode 100644 bench/reachability-benchmark/cases/py/flask-template/outputs/coverage.json create mode 100644 bench/reachability-benchmark/cases/py/flask-template/outputs/traces/traces.json create mode 100644 bench/reachability-benchmark/cases/py/guarded-exec/outputs/binary.tar.gz create mode 100644 bench/reachability-benchmark/cases/py/guarded-exec/outputs/coverage.json create mode 100644 bench/reachability-benchmark/cases/py/guarded-exec/outputs/traces/traces.json create mode 100644 bench/reachability-benchmark/cases/py/unsafe-exec/outputs/SINK_REACHED create mode 100644 bench/reachability-benchmark/cases/py/unsafe-exec/outputs/binary.tar.gz create mode 100644 bench/reachability-benchmark/cases/py/unsafe-exec/outputs/coverage.json create mode 100644 bench/reachability-benchmark/cases/py/unsafe-exec/outputs/traces/traces.json create mode 100644 docs/onboarding/dev-quickstart.md create mode 100644 ops/devops/airgap/compose-syslog-smtp.yaml create mode 100644 ops/devops/airgap/health_syslog_smtp.sh create mode 100644 ops/devops/sbom-ci-runner/README.md create mode 100644 ops/devops/sbom-ci-runner/run-sbom-ci.sh create mode 100644 src/Web/StellaOps.Web/TASKS.md create mode 100644 src/Web/StellaOps.Web/src/app/core/aoc/checksum.util.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/aoc/provenance-builder.spec.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/aoc/provenance-builder.ts create mode 100644 src/Web/StellaOps.Web/src/app/core/aoc/signature-verifier.ts diff --git a/bench/reachability-benchmark/cases/js/express-eval/outputs/SINK_REACHED b/bench/reachability-benchmark/cases/js/express-eval/outputs/SINK_REACHED new file mode 100644 index 000000000..f32a5804e --- /dev/null +++ b/bench/reachability-benchmark/cases/js/express-eval/outputs/SINK_REACHED @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/js/express-eval/outputs/binary.tar.gz b/bench/reachability-benchmark/cases/js/express-eval/outputs/binary.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..d3bad93299924ca558495ea3b8085d153f12bd36 GIT binary patch literal 2092 zcmV+{2-Ei;iwFP!000001MOOEbK5o&?q~iAg!PotRis3{SUq}lcOFN*=9-H$w$l%n z^C&b4S&S)?1wbdNs{h_yfE1}0$9J8`?X-AiY*D~sv5S|*KA<=Y+Fu+P9l&rnl%Ig@ zPxZ(Um@&$^9Y=SRurwExImR?k0uiMN znfrJ2G|L)IvNQ+@(u4~o#L0*(S0_-&sK{9&>j}wHE*hCHW-T)FlQ5>NS=5lC-P?Ds z&o8gY(GsOr@2){lA$cO@RxSCV${KSjW@*?WN+qK|;y+&LR2JRQ1*myNV?%ZcBk7Brd!*m#x5!NzE<9o-sUXudPeO~V3RmDi zJg$stJOf63K`$x0r)*)}QPxL@UXTCjDawja);q)k84z-ke+u}MaMD;z=p zo)8BM1+Rt{SBcPB_gN}i2I#KHxT+*-9M*>$M1cId_rQAvuSU#6g za_13x4=V`VlC}|=gjpIT00bfcJ&9kaWY*_YDChEmd0cCVVXg$lze=VYFSc}yKx|0xr4Id9Akw*0Mk z?dJomZNU$m0sQ~x|Il?i`uu+isQG7kV#_Co&pv-2kIjF#zjgk*Zr|1C|C8X$XYHJ` z_6?pO$vwI8`OM%{5S!*H$)b!-{U|nqkeEk}Fk*>6r|{S@o2H=xB)L3)b8$AFJU_h} zPsXp$&%PTSEqBU1+a30b>0XsBul_T7b9GjJdwDiFefe^9W+Zql;cA@7(*jb{KTy2= zy+Q5&jnV$>eLS-NyMyig-*t~WJ-z>*0$cfik#llR875koB37+;+Jb3FO>#mn&nZGsd`_HQX{ku=%vYNy@W zf^~1}c2(9-6P`2rJYu}a`KR2O-+^nFizKMJUcw*+Ipfhi^@w0OEfUua=~{>TQfe8pJ@}uzP988iv;~PimB8u6YR=a#m4@1dH%{#>>-*i8YO*DDvK|u z*sIi+#at2vYpiJI7*cxAQzWJk0Nz9)g||)>hkz;~Llh0If)J7L5R+RB=+0Avml&C%@a{bWIQ(s#<^&%B82Eeq|H6^`V zJ?L+vM#y2&DZlEg>ST*ZZt~2kmPz7{%(5WZ7P4_5lzTYlvS3uOnPEa|jl{S%GgWg)|3d{5A?xFF;4FWz& z(kQv2bzRWDjU{4LS7E4f&?3hzGHemI2Y=td-(RoGO}Dvf_Qi_J7q7mbT#Qf8z8gQc znlc3B6g^rsanyh7s9)hGkK(W`{~sjxM?9AQ4+q=le`nz8^Z!ZkzvcfI=jT@=U;uk~ zK-+Ol^3_))`w#*rfE@sGC|;h)rXi0gVrL)^)raE^MZdd@)>QljYfS@Z+qOBh97!i^ zJN=MQHtN9BElcxEJ|+2_LR?aH8rakOKnS;_=IO_iTGq&{0X(4TYr zQ&|r&Mp7G2Ha8v#d`_K(KabVfDX+`8D#-lcu1q_OX_SP;KoT2o^vD$E3eQH7=E02E zP`lT@<%)d0C+@8KJ>VL9?EZh~_P6hU1_QnSp9B%)Bj$TvWusX+U}xuqYF%u$H*3Z-B8~?&E6$ARbI65D*cn zCGWvDv5C3wpf?Rn84C$%_fGC;8dbt+R_FOs4?Z+9YL>nM%VT|z# z)tFtG-HzkBIJyu%(wqf!5`r%inx(-E%_!reA%lr0Q@Z)wm+m?3%{cS85-?B&Ze@ed zL^SmSF;RnB)NKYg71?gm`IZnCIYIbQMJ{!Zuad1T!3}051r=jLl2=KoG6Tcswv+o} zKEbsLYnFzSQonM^ez4zl1J%{GxGHVe*qnhUMuPG=_=97cgHdepz~W!O{UBhttdlRy zaOp^U@1u|=Fg1!;6uq0kZ*QP@28!?6vZwaehLyIMzbdQTK5agCd_g>x|MhzA_We({ z*U|C+DS+qyL_N7YR3DG;|KT?O>-M{P|33xrzQ{x(WWt7GH#WwDjW}T9 zMU`1(dgeOSlclxoh&2~#`$cgi7G!;7NowP5{+y)vSpfb>pgbIv#yARS!lf~%8Q%5T z-Lf;)6*tSPH4~3|^ApGa%2??rprN6mp`oFnp`oFnp`oFnp`oFnp`oFnp`oFnp`oFn Wp`oFnp`oGi$HOnb8|;7pPyhhIFe7>Z literal 0 HcmV?d00001 diff --git a/bench/reachability-benchmark/cases/js/express-eval/outputs/coverage.json b/bench/reachability-benchmark/cases/js/express-eval/outputs/coverage.json new file mode 100644 index 000000000..f9fab484d --- /dev/null +++ b/bench/reachability-benchmark/cases/js/express-eval/outputs/coverage.json @@ -0,0 +1,15 @@ +{ + "files": { + "src/app.js": { + "lines_covered": [ + 5, + 6, + 7, + 13, + 18, + 19 + ], + "lines_total": 40 + } + } +} \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/js/express-eval/outputs/traces/traces.json b/bench/reachability-benchmark/cases/js/express-eval/outputs/traces/traces.json new file mode 100644 index 000000000..9695e5157 --- /dev/null +++ b/bench/reachability-benchmark/cases/js/express-eval/outputs/traces/traces.json @@ -0,0 +1,10 @@ +{ + "entry": "POST /api/admin/exec", + "path": [ + "app.js::createServer", + "handler", + "eval(code)" + ], + "sink": "ExpressEval::exec", + "notes": "Admin exec reached" +} \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/js/express-guarded/outputs/binary.tar.gz b/bench/reachability-benchmark/cases/js/express-guarded/outputs/binary.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..4fc170e49d5a6186f7077d6d3c91ed8f0a1abe85 GIT binary patch literal 2089 zcmV+^2-f!>iwFP!000001MOLDZ`(K$?q~f9RvWAxtR=t4fC67HNWCwIVqGNZ-C?_h zD@$~&T9#Cja^nsB-*1MLELnDvJz$+KcKLirY;wq%;mgcCG-qD(#gWkh3)}Y(%bY%NkzuSL7`o}g5k#gZOLY_m_pAj7P-%S$x6F;7gC-%SH>-8)9KR7$< zydbUPnN;9!zW=q9Q^ExcJW)G0j9D6aB8VfhbQkm@N$RKM-XO$_BQ6MwQ$aZylKag$ zG%_ku7Ri1>l9-En;);2L%-zTjDLc)2NH*^M$G79_8**}wQk#z-V4h6!T*_@4@}Fv} zFR7TveuJnf8U0*Lr;J(9HO%nw{F!!&qs9oxR%yd%hdI8nv`-;(bH@a z%EKTMmLG6;8d58wcfx`eN}|}JD>pQX9cGlLp@4QLZ0rOjl_{(`;s0Ja1{hUas}}J=l?;w-O~C0IiU8R zrI95+*?j)T>v(GaJN@47{Q_U4!=pg{dAhvBaVb^i~n_8+L@iTmF{vbTHx zJ3s*R{eKQr>i?`><(x9Cys(T57_HY-OAYELB{AdW-We}?um~^r;rF7qT3eMRZM&JV zQIyXHjNjc%UjK9jayXF<DFuI4J3pRFtf^xw^pkQw;nR2T?tntbtXt z>(P?@<@n#DtBW5-lkb0eGkRc*7lX;}Enbc|s;b%>x3yraZ9A@D3^d{?qpt(TGrln6 z_HqHPU9TgrP<;=JuVBf-{+Y5feLmv1fk z!U93R4?-&Emlb#6tYT~bx*q>*D|!&j*7ckps425AII&YqpEvU$S^!%Z-6gQ8{D;(K zKJ^_(aiB~FXcvXDz9xIgG>p9k_2tM~#u-VJhGBzrPT~7OA1||AtIxoZw~|dF-+0u^ zozw8Bqc|xTn13bYeXiz1xs@oL6M2NdOZBy}*Dqq>h7Reswmlo8{Qr=@^%7@?bEo_& zAFT5V58b7yMK6=w9*Jqj!D~p^fsY=bsP`Gmg8K}63Om&Ku2VzvpoqZMf~6wubVE*| zH#@Z|me?xjj6SE5+(G3jIa~t?>tkZ5w^63Dc%jJ7ftGfYeWTyk)JvD+l$QVh>{vY(uD*3Ym6!KYgmr-}R&rArENNA;0k zW$?AEr3jXEX#VqL&rUO!@qU{rKXFN2wzff^efwM951~U+8%}mF9`RgG?X|lMRqjln zgaF%RL*jaiyzDS$LF8u(Ni2M`BQw}5h;Ky1sW&GU^d7YDenY-H5RkTdk9fwOdjB8v z2i5nVzJC9I9t4o~%ugJSkSxQj+pG?2wR)xzx)FT8h5QceJ7MuB>IsKbL=8xb9Lf)v zr){_FmT5qd=dmCWs__C-Fp))^l@vWR&o zyPF0Ug_(r(=}=v18bv*77Fm3^9#BFHxMv^)PDAC+RU4F_u5_wY{5hnU$0$lUhqTX( zQUzRL|Wew2HRlwqC3GU+bF`ooWx$-7Hhd7;<<%S7NjFv!b!$9O@3b6h% zJ1x82#>Iyi6Q|6h6Ccz}XcBwiY!O5a8F3t0^evaJbkc6_2BW7Z0RvTZS2nmz1T)tY z6ScHi-);m{;cpr*D?-?0d;XmYZ|e156}M@#_R`SXe~!%yFBi} zgegxkcX7RG~W95C^r%FN0< zb6e-h*3xyvo-0+u1z{l8WSf;FyD19CFj!FH9xMxygJM0XCJzITMqHY90S9$Xtxm32 zY2{}A%4T9tHy_FK8)KtGfQE*KhK7cQhK7cQhK7cQhK7cQhK7cQhK7cQhK7cQhK7cQ ThK7d5UjhFE=*NP#08jt`t|=B` literal 0 HcmV?d00001 diff --git a/bench/reachability-benchmark/cases/js/express-guarded/outputs/coverage.json b/bench/reachability-benchmark/cases/js/express-guarded/outputs/coverage.json new file mode 100644 index 000000000..dfd568cf6 --- /dev/null +++ b/bench/reachability-benchmark/cases/js/express-guarded/outputs/coverage.json @@ -0,0 +1,16 @@ +{ + "files": { + "src/app.js": { + "lines_covered": [ + 5, + 6, + 7, + 12, + 13, + 14, + 15 + ], + "lines_total": 50 + } + } +} \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/js/express-guarded/outputs/traces/traces.json b/bench/reachability-benchmark/cases/js/express-guarded/outputs/traces/traces.json new file mode 100644 index 000000000..b3c9f7a9e --- /dev/null +++ b/bench/reachability-benchmark/cases/js/express-guarded/outputs/traces/traces.json @@ -0,0 +1,9 @@ +{ + "entry": "POST /api/admin/exec", + "path": [ + "app.js::createServer", + "guard: ALLOW_EXEC!=true" + ], + "sink": "ExpressGuarded::exec", + "notes": "Guard blocked sink" +} \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/js/fastify-template/outputs/SINK_REACHED b/bench/reachability-benchmark/cases/js/fastify-template/outputs/SINK_REACHED new file mode 100644 index 000000000..f32a5804e --- /dev/null +++ b/bench/reachability-benchmark/cases/js/fastify-template/outputs/SINK_REACHED @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/js/fastify-template/outputs/binary.tar.gz b/bench/reachability-benchmark/cases/js/fastify-template/outputs/binary.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..37fff98e0a05bfce1e8eff84f1a603d5155deb69 GIT binary patch literal 2103 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 literal 0 HcmV?d00001 diff --git a/bench/reachability-benchmark/cases/js/unsafe-eval/outputs/coverage.json b/bench/reachability-benchmark/cases/js/unsafe-eval/outputs/coverage.json new file mode 100644 index 000000000..5122d2042 --- /dev/null +++ b/bench/reachability-benchmark/cases/js/unsafe-eval/outputs/coverage.json @@ -0,0 +1,14 @@ +{ + "files": { + "src/app.js": { + "lines_covered": [ + 5, + 6, + 7, + 12, + 15 + ], + "lines_total": 30 + } + } +} \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/js/unsafe-eval/outputs/traces/traces.json b/bench/reachability-benchmark/cases/js/unsafe-eval/outputs/traces/traces.json new file mode 100644 index 000000000..eb7cac9b2 --- /dev/null +++ b/bench/reachability-benchmark/cases/js/unsafe-eval/outputs/traces/traces.json @@ -0,0 +1,9 @@ +{ + "entry": "POST /api/exec", + "path": [ + "app.js:handleRequest", + "eval(code)" + ], + "sink": "UnsafeEval::handleRequest", + "notes": "Test-driven dynamic trace" +} \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/py/django-ssti/outputs/SINK_REACHED b/bench/reachability-benchmark/cases/py/django-ssti/outputs/SINK_REACHED new file mode 100644 index 000000000..f32a5804e --- /dev/null +++ b/bench/reachability-benchmark/cases/py/django-ssti/outputs/SINK_REACHED @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/py/django-ssti/outputs/binary.tar.gz b/bench/reachability-benchmark/cases/py/django-ssti/outputs/binary.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..608f5f7d161c33e93b526a38092a6e64bd4146fc GIT binary patch literal 2544 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 GPyhg*EgU!i literal 0 HcmV?d00001 diff --git a/bench/reachability-benchmark/cases/py/django-ssti/outputs/coverage.json b/bench/reachability-benchmark/cases/py/django-ssti/outputs/coverage.json new file mode 100644 index 000000000..e46fc234c --- /dev/null +++ b/bench/reachability-benchmark/cases/py/django-ssti/outputs/coverage.json @@ -0,0 +1,16 @@ +{ + "files": { + "src/app.py": { + "lines_covered": [ + 3, + 4, + 5, + 7, + 8, + 9, + 10 + ], + "lines_total": 38 + } + } +} \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/py/django-ssti/outputs/traces/traces.json b/bench/reachability-benchmark/cases/py/django-ssti/outputs/traces/traces.json new file mode 100644 index 000000000..692c53fc8 --- /dev/null +++ b/bench/reachability-benchmark/cases/py/django-ssti/outputs/traces/traces.json @@ -0,0 +1,9 @@ +{ + "entry": "POST /render", + "path": [ + "app.py::handle_request", + "render" + ], + "sink": "DjangoSSTI::render", + "notes": "Template rendered (autoescape off)" +} \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/py/fastapi-guarded/outputs/binary.tar.gz b/bench/reachability-benchmark/cases/py/fastapi-guarded/outputs/binary.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..8b6001ce253b40d1f71e1e87364521d47a53b38f GIT binary patch literal 2446 zcmV;9332uxiwFP!000001MOPPZyZS#pRp&g(@|J9h?V%#R(hjoMof3lhsP6U8HD9^ zHj$#(N*q42EUljI8oT12?xwoh_QV+}NJuPjAQA^Idx!)F(4LSu@FyT~Ng#(X$_Y-~ zke1V)cvao=)#GF~OKh=FKgrYGRj=NA_4Vrax*by6$Q&_BfO@?yu7LDa+?SW?rFykm zsfhCBTD6uzwWD^7BMO;C5jqZ2|BVP?{!KIJ+LpbIP4h_B!VAxTxl~`0^M5Qz&%YG} zx*c@cw(n`xaz%%l|JcTi`7hT><<$HyRjQ>7D!qjM&BXut{Qv398HHcZmWt$e*-Yl| zzAo%Yztevp(p zb364+hMoYQa?XkO=Wb-!{6Ok%hUEtLA0+nZt9LVpYCJj6=w+QuZ*EM>96OoT5}t4t zoW;c6sgdMBM{nLa#TJI;^Fwb%i-K~kudH0Xy8dl*?K^9${T!naCcglg{;9T4Tf}j& zSD5Rcgk{O1u%B!BPPd=4eFu~KAa&<-%W@FKKZtP1{_#i9TR735-@>e~kdS!L%Cm7l z=MbCq=R-#OIlODRg&gG`_2;p-OBeawNs7bBWg#CV1o-ItM#p0YHXOWbY!Rl147;wS z`9Y}rbjxUA&)zmDhA?5Zh)Y;k6Ga`1?ijWe;?M}XMjO_H6%b8KICcykU3f$Zy8X|@ z%fn1ur)QIS4?FO4FY`n>^V7opv-eh?EGR$T`{CYucmJ~RsmPfvgTzVrU--=9{PR0LHeYswf z@&7oWs_G{S;?Pb}9Nb2e+d9v~RS`j$X5M(Gsv2GqM!62`W4e$a`4K_zX zGI5nh$^yb}h!OXA;G5^OaapdQh#+hgp6^v@%&W0Lo%j{UTs{;KlZDQLa=50-xF#^0 zkDC^H%1fSq2Hu57oB_Ug{#P%TOOpQ|2jcmsk*A3}J=}izHfGI#wX&Sz|K+9fl8pby zfj7<>QAmvz@eCfHTUNNOgqR@>M?MNjfZG;v7284T`Mg7@XLT^#mehi(;Qhd-4Bc42 zd3|-wd~0QM&0O19U%lKse=t?n(8~2{GTpsF%gtNOo13eHr>mVVP_X9|IImQdI7>x*-I|e?P*WEJm3i4g@_!id{y$_4U!si}`@d4H zr}sZB70LgO1*9W3+wIWzl;l3J*tSbrN}Ku}1i1l9$|sqgqFi5J-vlm`i0d10&6{SM zfQ(ttfw1`QF3uP9z@pe=$wgVe$s38b22%Zq1rZBXW%K$94;WXjLnRk6I0>|D3{|V^ z-&(uA^7fi}>B`l$QMK*wVrp$+o%=pekcf=54fP~qg0o^T1X^oCJPY#)p#!CQX9r61 zv7cdc6F4X^*?j_A!`~5)F9*jDm?@_|A z$?!hQ57{PnUw5KT5athtg&e9-2sANPJXy!BK-I`tsva2b4(F;O2MEa9ao*l&OwP5M zY6avNbC#+KdDL=!dj~s$)v80uUJ(%wI5}%p;&GcCjKTEf#e(4u7LiN5;c!gM`U{Q- zSIPaGEan&*JFHT)6+Xn?VFz@G=i>8Zna`l#oc&Kp^_0V(o;5brkV?Qh zPbFUmaiRkgQ!W`5wHGI12jlq_&z3AmcGO<1#|lFXdmGffGsr&PMa?Epu%_xoIwESv zGOom%u?4Pftlxkg%F{%(h~81-*9+5Y3aKKcY@m^Q=b9q(d$%yIb+}U0(aQ?1xLRB) z)`}O4Un`c&&_YolZ8J^IP)t)eX__62c&3?TlGB4Lc|xWY5!W%q?~!nSj#>P_zFbRx z|EZO$^87yzd_4ZokL!emr}F?q*VO?pSNZ7HX-QRF%Y)}F3P1k}HcZPq|3Q3X!tkoX>F9ePPGsla4Ho05RV zg%hur#S%>#qE>h8=#O7A=f}=xUJslXx1ZM_4oFpGU0=$pAg!&%e z=UlTNnxrF!Uya{&G^%>JqFHVLdbuMI<9IxZy+}lm%7u7s=ttBB(r5v5#({4`XGg(Q z5pjpNwEa%U5?$1d$&fT#3HTUC`Pc=EGSaqemN4w3ZZZzWL9pd_QbN2)x1D{FrzLOk zW8Rk%oXGoPVkZ(j&W=Y?ahbv+ZMu_(oew~<_XAknctkhjE@{uSJ8Te}N{h#(##r*H z@WaL9@)7ihk8K1_(e7j(6^b+pMINLu;n?bknK8G!OMrL7oQN!EU{=+2WAs5*m@uZd zb&>>+?f>*ya}+UV=KJ4r`u~5*m5O}-b1dNJzxmulfUgh|FSKpOg293lZN@`z9P(g^ zGaj%e3Og7=(?EVun0#r3QXVr8i@XR;o+N9}JUmpH$$dhkspV1Fn6>{)<*DyKm1;%q z|Kq?LC}fUHTFCcY`Q1c9LPA19LPA19LPA19LPA19LPA19LPA19LPA19LPA19;?sqH M186vr$N*3P0QV}|5dZ)H literal 0 HcmV?d00001 diff --git a/bench/reachability-benchmark/cases/py/fastapi-guarded/outputs/coverage.json b/bench/reachability-benchmark/cases/py/fastapi-guarded/outputs/coverage.json new file mode 100644 index 000000000..b024ed4b6 --- /dev/null +++ b/bench/reachability-benchmark/cases/py/fastapi-guarded/outputs/coverage.json @@ -0,0 +1,15 @@ +{ + "files": { + "src/app.py": { + "lines_covered": [ + 3, + 4, + 5, + 8, + 9, + 11 + ], + "lines_total": 40 + } + } +} \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/py/fastapi-guarded/outputs/traces/traces.json b/bench/reachability-benchmark/cases/py/fastapi-guarded/outputs/traces/traces.json new file mode 100644 index 000000000..41c4eabd8 --- /dev/null +++ b/bench/reachability-benchmark/cases/py/fastapi-guarded/outputs/traces/traces.json @@ -0,0 +1,9 @@ +{ + "entry": "POST /exec", + "path": [ + "app.py::handle_request", + "guard: ALLOW_EXEC!=true" + ], + "sink": "FastApiGuarded::handle_request", + "notes": "Guard blocked eval" +} \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/py/flask-template/outputs/SINK_REACHED b/bench/reachability-benchmark/cases/py/flask-template/outputs/SINK_REACHED new file mode 100644 index 000000000..f32a5804e --- /dev/null +++ b/bench/reachability-benchmark/cases/py/flask-template/outputs/SINK_REACHED @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/py/flask-template/outputs/binary.tar.gz b/bench/reachability-benchmark/cases/py/flask-template/outputs/binary.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..5ea77af56296e011fc3593718de93706f44b185d GIT binary patch literal 2549 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 YfPjF2fPjF2z{eE-29j}Ri2zUl0BC#ZV*mgE literal 0 HcmV?d00001 diff --git a/bench/reachability-benchmark/cases/py/guarded-exec/outputs/coverage.json b/bench/reachability-benchmark/cases/py/guarded-exec/outputs/coverage.json new file mode 100644 index 000000000..b8afa952b --- /dev/null +++ b/bench/reachability-benchmark/cases/py/guarded-exec/outputs/coverage.json @@ -0,0 +1,15 @@ +{ + "files": { + "src/app.py": { + "lines_covered": [ + 3, + 4, + 5, + 8, + 9, + 11 + ], + "lines_total": 34 + } + } +} \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/py/guarded-exec/outputs/traces/traces.json b/bench/reachability-benchmark/cases/py/guarded-exec/outputs/traces/traces.json new file mode 100644 index 000000000..022ac21d8 --- /dev/null +++ b/bench/reachability-benchmark/cases/py/guarded-exec/outputs/traces/traces.json @@ -0,0 +1,9 @@ +{ + "entry": "POST /api/exec", + "path": [ + "app.py::handle_request", + "guard: FEATURE_ENABLE != 1" + ], + "sink": "PyGuardedExec::handle_request", + "notes": "Guard blocked eval" +} \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/py/unsafe-exec/outputs/SINK_REACHED b/bench/reachability-benchmark/cases/py/unsafe-exec/outputs/SINK_REACHED new file mode 100644 index 000000000..f32a5804e --- /dev/null +++ b/bench/reachability-benchmark/cases/py/unsafe-exec/outputs/SINK_REACHED @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/py/unsafe-exec/outputs/binary.tar.gz b/bench/reachability-benchmark/cases/py/unsafe-exec/outputs/binary.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..8cdbdd2b49e8b5ac803218f9358c5cf8d894153a GIT binary patch literal 2325 zcmV+w3F`JAiwFP!000001MOMOZyZS#pRt`}=_pyT(joyuTXdq;49s-ThsP7mG6>6Y zNFc6M-m9i23m^X$SgqXuZV+wvEX|CP`6#dfXTY&IIQ ze5uuJ6-ev2JBCQPa2X*dVd`ZBhxK=yr0==jE_Iw^*$dCS{`FdWQLq1rAiw@@l2~5S z7rSv(ZPptW)I7J18S7tfv>LhfUu-m+1yXwfu`TGC;+&Ke(|MGez_(ic$_~&?K zG5dXT9=?8G@C&yKe(_g@+r|D|dH#OwCqWeS+>orx?Lv0l$PXza_X4p?+=!$Rceg2_ zdmzGt=&t1z$8;~iXC8HbAa#pj-Ma-bH+cRqdtzUGw{YjwbnpG*hgrL*=ob!(V_J&h zbY2TT<nA%_umoyA_0i=vQS? z&t-RQ&*hZcN#9nBP^J6Sv(eYlzmxvqd9_$hHUM@$TMhmr_AL~bbH;J}*mE3d=uZXqDvtUbe0}!H_j<9PhV=XF zwQR?6EdTd>u~_^^Vd=@jnPN?fYa9ngd*b+P%sT&D&7~awYcI8zbo@UFn5OxxVH}zm zhEKQ2WDF@`yFQ$eaUjY$$D zblVL=!}E!GvE&CVa(fgWYi8Lr=zbD2L2j;@9^e-LqBE-3$>n8O!tV}}1Xn(b!Z|Cj1I|347~J?U(BcpMqobIb=%i7R%) zV9VHMagRWjK(g{l;%68eYipZ8lrnK^9ezuWvmJnpQ?`H&#o-<;m94~OG!og5v33g^ z$+k8z<5U0(v5cYP+6Gjj2-FGYdK9Ww)_%IWvHZhT=gPI~tD|Z!-lNRjp%#u} zpdb?&X&dUv#1x065lHNCDl>h-DD|{Pr4s~m&AAFtKxz~?O&vU zGRUYGGvAS2DSN*o>fQ~M8kQQSny|@3RH<*n=D2QjU81f+sZzu-obD>s!x^AlNGUW zLy&CNrlbyxqu-u7IiqMi*pd^wbda6E?#^Xk4^*B7%hCK!+dNQ`E{7aPV1u^@Da$*g z+r_M9s$S+WvesK&> z(ftGJYF5Z%g|sT&o*P7tW2$)^pIJ>65L;;w`nLQ&7VeLj#sAw&t^EH#Eq(r<489ou$3r?};rTqk zwk&hN%S}AfK@P?3;ejrXMp8{c9;%NoZ!j{$((=0Dhs9|kpQ5x!S>UNU@^cwJs@0wddbDf);NnzI&4G+}e_M?dC)H(r3+!1t_XqV5 z@MIX`WYYBrUfElHw->6zJBb5e5~=`0YQSg@xrHV6fpKla$&t+`sfvC=$u^Of{6xg5 zw@a!}dt@gc9FXshN4}xeW87o2{{O?v-rW69YpJ3A|0EFjz!s8zb?kqlPQ3JVJaN!gz3QQ|J`yPh>=L!|w$y$?t10xEDYM0E;2Bm4Q9e zvh2}+xl*@1>7!W=`~3c&KWm;}<1}X8|JL*Tuij|t_08jt`k=viA literal 0 HcmV?d00001 diff --git a/bench/reachability-benchmark/cases/py/unsafe-exec/outputs/coverage.json b/bench/reachability-benchmark/cases/py/unsafe-exec/outputs/coverage.json new file mode 100644 index 000000000..a82cb27d6 --- /dev/null +++ b/bench/reachability-benchmark/cases/py/unsafe-exec/outputs/coverage.json @@ -0,0 +1,14 @@ +{ + "files": { + "src/app.py": { + "lines_covered": [ + 3, + 4, + 5, + 8, + 10 + ], + "lines_total": 30 + } + } +} \ No newline at end of file diff --git a/bench/reachability-benchmark/cases/py/unsafe-exec/outputs/traces/traces.json b/bench/reachability-benchmark/cases/py/unsafe-exec/outputs/traces/traces.json new file mode 100644 index 000000000..fe92c1fca --- /dev/null +++ b/bench/reachability-benchmark/cases/py/unsafe-exec/outputs/traces/traces.json @@ -0,0 +1,9 @@ +{ + "entry": "POST /api/exec", + "path": [ + "app.py::handle_request", + "eval(code)" + ], + "sink": "PyUnsafeExec::handle_request", + "notes": "Eval reached" +} \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 7109331a5..c75f89fe3 100755 --- a/docs/README.md +++ b/docs/README.md @@ -21,8 +21,9 @@ 1. **Value in context** – [Overview](overview.md) compresses the “Why” + “What” stories and shows how Stella Ops stands apart. 2. **Try it fast** – [Quickstart](quickstart.md) walks through fetching the signed bundles, configuring `.env`, and verifying the first scan. -3. **Feature confidence** – [Key Features](key-features.md) gives five capability cards covering Delta SBOM, VEX‑first policy, Sovereign crypto, Deterministic replay, and Transparent quotas. -4. **Up-next checkpoints** – [Evaluation checklist](evaluate/checklist.md) helps teams plan Day‑0 to Day‑30 adoption milestones. +3. **Feature confidence** – [Key Features](key-features.md) gives five capability cards covering Delta SBOM, VEX-first policy, Sovereign crypto, Deterministic replay, and Transparent quotas. +4. **Up-next checkpoints** – [Evaluation checklist](evaluate/checklist.md) helps teams plan Day-0 to Day-30 adoption milestones. +5. **Be dev-ready** – [Developer Quickstart](onboarding/dev-quickstart.md) (29-Nov-2025 advisory) walks through the core repos, determinism tests, attestations, and starter issues for a mid-level .NET engineer. ## Key capabilities that define Stella Ops diff --git a/docs/implplan/SPRINT_0212_0001_0001_web_i.md b/docs/implplan/SPRINT_0212_0001_0001_web_i.md index f35954da3..d3636084f 100644 --- a/docs/implplan/SPRINT_0212_0001_0001_web_i.md +++ b/docs/implplan/SPRINT_0212_0001_0001_web_i.md @@ -24,7 +24,7 @@ | 1 | WEB-AIAI-31-001 | BLOCKED (2025-11-22) | Gateway policy/contract for `/advisory/ai/*` not present in Web workspace; need backend gateway service location + policy spec to proceed. | BE-Base Platform Guild | Route advisory AI endpoints through gateway with guardrails. | | 2 | WEB-AIAI-31-002 | BLOCKED (2025-11-22) | Blocked by WEB-AIAI-31-001; batching/streaming cannot start until gateway contract exists. | BE-Base Platform Guild | Streaming responses for CLI automation with job orchestration. | | 3 | WEB-AIAI-31-003 | BLOCKED (2025-11-22) | Blocked by WEB-AIAI-31-002; telemetry targets depend on routing/batching contract. | BE-Base Platform Guild; Observability Guild | Telemetry + audit for advisory AI, guardrail block visibility. | -| 4 | WEB-AOC-19-002 | TODO | Depends on WEB-AOC-19-001; align DSSE/CMS helper APIs. | BE-Base Platform Guild | Ship `ProvenanceBuilder`, checksum utilities, signature verification helper with tests. | +| 4 | WEB-AOC-19-002 | DONE (2025-11-30) | Depends on WEB-AOC-19-001; align DSSE/CMS helper APIs. | BE-Base Platform Guild | Ship `ProvenanceBuilder`, checksum utilities, signature verification helper with tests. | | 5 | WEB-AOC-19-003 | TODO | Depends on WEB-AOC-19-002; confirm Roslyn analyzer rules. | QA Guild; BE-Base Platform Guild | Analyzer to prevent forbidden key writes; shared guard-validation fixtures. | | 6 | WEB-CONSOLE-23-001 | DONE (2025-11-28) | `/console/dashboard` and `/console/filters` endpoints implemented with tenant-scoped aggregates. | BE-Base Platform Guild; Product Analytics Guild | Tenant-scoped aggregates for findings, VEX overrides, advisory deltas, run health, policy change log. | | 7 | CONSOLE-VULN-29-001 | BLOCKED (2025-11-19) | Blocked on WEB-CONSOLE-23-001 contract and Concelier graph schema freeze. | Console Guild; BE-Base Platform Guild | `/console/vuln/*` workspace endpoints with filters/reachability badges and DTOs once schemas stabilize. | @@ -80,3 +80,4 @@ | 2025-11-22 | Added completion dates in `tasks-all` for WEB-CONTAINERS-44/45/46 and aligned BLOCKED dates for VULN-29-001/VEX-30-001. | Planning | | 2025-11-22 | Harmonized all `CONTAINERS-44/45/46` rows in `tasks-all` to DONE with dates to match sprint status. | Planning | | 2025-11-28 | Completed WEB-CONSOLE-23-001: Implemented `/console/dashboard` and `/console/filters` endpoints in Authority module. Dashboard returns tenant-scoped aggregates (findings summary, VEX overrides, advisory deltas, run health, policy change log) with 30-day trend data. Filters endpoint returns deterministic filter categories with counts and cache-validation hash. Added 8 unit tests for dashboard/filters endpoints. Implementation in `src/Authority/StellaOps.Authority/StellaOps.Authority/Console/`. | Policy Guild | +| 2025-11-30 | Completed WEB-AOC-19-002: added deterministic provenance builder, checksum utilities, and DSSE/CMS signature verification helpers with unit tests under `src/Web/StellaOps.Web/src/app/core/aoc`. Added Web TASKS board and marked task DONE. | BE-Base Platform Guild | diff --git a/docs/implplan/SPRINT_0513_0001_0001_public_reachability_benchmark.md b/docs/implplan/SPRINT_0513_0001_0001_public_reachability_benchmark.md index 66eb437cd..553bd59cd 100644 --- a/docs/implplan/SPRINT_0513_0001_0001_public_reachability_benchmark.md +++ b/docs/implplan/SPRINT_0513_0001_0001_public_reachability_benchmark.md @@ -32,7 +32,7 @@ | 4 | BENCH-CASES-PY-513-004 | DONE (2025-11-30) | Depends on 513-002. | Bench Guild · Python Track (`bench/reachability-benchmark/cases/py`) | Create 5-8 Python cases: Flask, Django, FastAPI. Include requirements.txt pinned, pytest oracles, coverage.py output. Delivered 5 cases: unsafe-exec (reachable), guarded-exec (unreachable), flask-template (reachable), fastapi-guarded (unreachable), django-ssti (reachable). | | 5 | BENCH-CASES-JAVA-513-005 | BLOCKED (2025-11-30) | Depends on 513-002. | Bench Guild · Java Track (`bench/reachability-benchmark/cases/java`) | Create 5-8 Java cases: Spring Boot, Micronaut. Include pom.xml locked, JUnit oracles, JaCoCo coverage. Progress: 2/5 seeded (`spring-deserialize` reachable, `spring-guarded` unreachable); build/test blocked by missing JDK (`javac` not available in runner). | | 6 | BENCH-CASES-C-513-006 | TODO | Depends on 513-002. | Bench Guild · Native Track (`bench/reachability-benchmark/cases/c`) | Create 3-5 C/ELF cases: small HTTP servers, crypto utilities. Include Makefile, gcov/llvm-cov coverage, deterministic builds (SOURCE_DATE_EPOCH). | -| 7 | BENCH-BUILD-513-007 | DOING | Depends on 513-003 through 513-006. | Bench Guild · DevOps Guild | Implement `build_all.py` and `validate_builds.py`: deterministic Docker builds, hash verification, SBOM generation (syft), attestation stubs. Progress: added scripts (hash check, deterministic ordering); SBOM/attestation stubs pending. | +| 7 | BENCH-BUILD-513-007 | DOING | Depends on 513-003 through 513-006. | Bench Guild · DevOps Guild | Implement `build_all.py` and `validate_builds.py`: deterministic Docker builds, hash verification, SBOM generation (syft), attestation stubs. Progress: added scripts (hash check, deterministic ordering) + README; SBOM/attestation stubs pending. | | 8 | BENCH-SCORER-513-008 | DONE (2025-11-30) | Depends on 513-002. | Bench Guild (`bench/reachability-benchmark/tools/scorer`) | Implement `rb-score` CLI: load cases/truth, validate submissions, compute precision/recall/F1, explainability score (0-3), runtime stats, determinism rate. | | 9 | BENCH-EXPLAIN-513-009 | DONE (2025-11-30) | Depends on 513-008. | Bench Guild | Implement explainability scoring rules: 0=no context, 1=path with ≥2 nodes, 2=entry+≥3 nodes, 3=guards/constraints included. Unit tests for each level. | | 10 | BENCH-BASELINE-SEMGREP-513-010 | TODO | Depends on 513-008 and cases. | Bench Guild | Semgrep baseline runner: `baselines/semgrep/run_case.sh`, rule config, output normalization to submission format. | @@ -98,3 +98,4 @@ | 2025-11-30 | BLOCKED BENCH-CASES-JAVA-513-005: `javac`/JDK not available in runner; Java builds/tests cannot execute. Need JDK (>=17) in CI/runner before unblocking. | Implementer | | 2025-11-30 | BENCH-EXPLAIN-513-009 DONE: added explainability tier tests (0–3) to scorer; tiers already implemented (guards→3, entry+path>=3→2, path>=2→1, else 0). | Implementer | | 2025-11-30 | BENCH-BUILD-513-007 DOING: added `tools/build/build_all.py` and `tools/build/validate_builds.py` for deterministic builds and hash checks; SBOM/attestation stubs still pending. | Implementer | +| 2025-11-30 | BENCH-BUILD-513-007: build_all/validate_builds run; all JS/PY cases deterministic, Java cases fail due to missing `javac` (same blocker as task 5). | Implementer | diff --git a/docs/implplan/SPRINT_300_documentation_process.md b/docs/implplan/SPRINT_300_documentation_process.md index a478b21e6..a0ed11d0b 100644 --- a/docs/implplan/SPRINT_300_documentation_process.md +++ b/docs/implplan/SPRINT_300_documentation_process.md @@ -18,12 +18,34 @@ | --- | --- | --- | --- | --- | | 200.A Docs Tasks.md ladder (Sprint 301 onwards) | BLOCKED (2025-11-19) | Docs Guild · Ops Guild | Attestor 100.A; Advisory AI 110.A; AirGap 120.A; Scanner 130.A; Graph 140.A; Orchestrator 150.A; EvidenceLocker 160.A; Notifier 170.A; CLI 180.A; Ops Deployment 190.A | Awaiting upstream artefacts (SBOM/CLI/Policy/AirGap determinism) before Md.I template rollout can continue. | | 200.B Module dossiers (Sprints 312–335) | TODO | Docs Guild · Module Guild owners | Docs Tasks Md ladder to at least Md.II; Ops deployment evidence | Stays queued until Docs Tasks Md ladder provides updated process + assets. | +| Developer quickstart advisory sync | TODO | Docs Guild | 29-Nov-2025 advisory + onboarding doc draft | Publish the onboarding quickstart advisory + `docs/onboarding/dev-quickstart.md`, update `docs/README.md`, `modules/platform/architecture-overview.md`, and `ADVISORY_INDEX.md`, and confirm sprint/AGENTS references per the advisory workflow. | +| Acceptance tests guardrails sync | TODO | Docs Guild | 29-Nov-2025 advisory + checklist draft | Publish the Acceptance Tests Pack advisory, cross-link to sprint/guardrail docs, and capture sprint board checklist for CI/DB/rew definitions. | +| CVSS v4.0 momentum sync | TODO | Docs Guild | 29-Nov-2025 advisory + briefing draft | Publish the CVSS v4.0 momentum briefing, highlight adoption signals, and link to sprint decisions for SPRINT_0190.* and docs coverage. | +| SBOM→VEX proof blueprint sync | TODO | Docs Guild | 29-Nov-2025 advisory + blueprint draft | Publish the SBOM→VEX blueprint, link to platform/blueprint docs, and capture diagram/stub updates for DSSE/Rekor/VEX. | +| SCA failure catalogue sync | TODO | Docs Guild | 29-Nov-2025 advisory + catalogue draft | Publish the SCA failure catalogue, reference the concrete regressions, and tie the test-vector guidance back into sprint risk logs. | +| Implementor guidelines sync | TODO | Docs Guild | 30-Nov-2025 advisory + checklist draft | Publish the Implementor Guidelines advisory, note the checklist extraction, and mention the doc in sprint/AGENTS references. | +| Rekor receipt checklist sync | TODO | Docs Guild | 30-Nov-2025 advisory + checklist draft | Publish the Rekor Receipt Checklist, update module docs (Authority/Sbomer/Vexer) with ownership map, highlight offline metadata requirements. | +| Unknowns decay/triage sync | TODO | Docs Guild | 30-Nov-2025 advisory + heuristic draft | Publish the Unknowns Decay & Triage brief, link to UnknownsRegistry docs, and capture UI artifacts for cards + queue exports. | +| Ecosystem reality test cases sync | TODO | Docs Guild | 30-Nov-2025 advisory + test spec draft | Publish the Ecosystem Reality Test Cases advisory, link each incident to an acceptance test, and note exported artifacts/commands. | +| Standup sprint kickstarters sync | TODO | Docs Guild | 30-Nov-2025 advisory + task plan draft | Publish the Standup Sprint Kickstarters advisory, surface ticket names, and tie the tasks into MSC sprint logs. | +| Evidence + suppression pattern sync | TODO | Docs Guild | 30-Nov-2025 advisory + comparison draft | Publish the Comparative Evidence Patterns advisory, highlight the UX/data-model takeaways, and reference doc links per tool. | ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | | 2025-11-13 | Sprint 300 switched to topic-oriented template; Docs Tasks Md ladder marked DOING to reflect ongoing restructuring work. | Docs Guild | | 2025-11-19 | Marked Docs Tasks Md ladder BLOCKED pending upstream artefacts for Md.I dossier rollouts. | Implementer | +| 2025-11-30 | Added the 29-Nov-2025 Developer Quickstart advisory, `docs/onboarding/dev-quickstart.md`, and cross-links (README/platform/ADVISORY_INDEX); created this advisory sync task row. | Docs Guild | +| 2025-11-30 | Added the 29-Nov-2025 Acceptance Tests Pack advisory and checklist; noted new task row for guardrail sprint artifacts. | Docs Guild | +| 2025-11-30 | Added the 29-Nov-2025 CVSS v4.0 Momentum advisory and indexed the adoption briefing; noted sprint sync row for CVSS momentum context. | Docs Guild | +| 2025-11-30 | Added the 29-Nov-2025 SCA Failure Catalogue advisory and indexed the concrete test vectors; noted sprint sync row for failure catalog references. | Docs Guild | +| 2025-11-30 | Added the 29-Nov-2025 SBOM→VEX Proof Blueprint advisory and outlined diagram/stub follow-up; logged sprint sync row for the blueprint. | Docs Guild | +| 2025-11-30 | Added the 30-Nov-2025 Rekor Receipt Checklist advisory and noted the ownership/action map for Authority/Sbomer/Vexer. | Docs Guild | +| 2025-11-30 | Added the 30-Nov-2025 Ecosystem Reality Test Cases advisory (credential leak, Trivy offline DB, SBOM parity, Grype divergence) and logged the acceptance test intent. | Docs Guild | +| 2025-11-30 | Added the 30-Nov-2025 Unknowns Decay & Triage advisory and noted UI + export artifacts for UnknownsRegistry + queues. | Docs Guild | +| 2025-11-30 | Added the 30-Nov-2025 Standup Sprint Kickstarters advisory, highlighting the three unblocker tasks/tickets and the proposed owners. | Docs Guild | +| 2025-11-30 | Added the 30-Nov-2025 Comparative Evidence Patterns advisory and recorded cross-tool evidence/suppression nuggets for UX designers. | Docs Guild | +| 2025-11-30 | Added the 30-Nov-2025 Implementor Guidelines advisory and checked the docs + sprint sync references; the row stays TODO until docs link updates finish. | Docs Guild | ## Decisions & Risks | Item | Type | Owner(s) | Due | Notes | diff --git a/docs/implplan/SPRINT_503_ops_devops_i.md b/docs/implplan/SPRINT_503_ops_devops_i.md index 7847fdb6a..ebd903268 100644 --- a/docs/implplan/SPRINT_503_ops_devops_i.md +++ b/docs/implplan/SPRINT_503_ops_devops_i.md @@ -30,7 +30,7 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A | DEVOPS-AIRGAP-56-003 | DONE (2025-11-30) | Build Bootstrap Pack pipeline bundling images/charts, generating checksums, and publishing manifest for offline transfer. Dependencies: DEVOPS-AIRGAP-56-002. | DevOps Guild, Container Distribution Guild (ops/devops) | | DEVOPS-AIRGAP-57-001 | DONE (2025-11-30) | Automate Mirror Bundle creation jobs with dual-control approvals, artifact signing, and checksum publication. Dependencies: DEVOPS-AIRGAP-56-003. | DevOps Guild, Mirror Creator Guild (ops/devops) | | DEVOPS-AIRGAP-57-002 | BLOCKED (2025-11-18) | Waiting on upstream DEVOPS-AIRGAP-57-001 (mirror bundle automation) to provide artifacts/endpoints for sealed-mode CI; no sealed fixtures available to exercise tests. | DevOps Guild, Authority Guild (ops/devops) | -| DEVOPS-AIRGAP-58-001 | TODO | Provide local SMTP/syslog container templates and health checks for sealed environments; integrate into Bootstrap Pack. Dependencies: DEVOPS-AIRGAP-57-002. | DevOps Guild, Notifications Guild (ops/devops) | +| DEVOPS-AIRGAP-58-001 | DONE (2025-11-30) | Provide local SMTP/syslog container templates and health checks for sealed environments; integrate into Bootstrap Pack. Dependencies: DEVOPS-AIRGAP-57-002. | DevOps Guild, Notifications Guild (ops/devops) | | DEVOPS-AIRGAP-58-002 | TODO | Ship sealed-mode observability stack (Prometheus/Grafana/Tempo/Loki) pre-configured with offline dashboards and no remote exporters. Dependencies: DEVOPS-AIRGAP-58-001. | DevOps Guild, Observability Guild (ops/devops) | | DEVOPS-AOC-19-001 | BLOCKED (2025-10-26) | Integrate the AOC Roslyn analyzer and guard tests into CI, failing builds when ingestion projects attempt banned writes. | DevOps Guild, Platform Guild (ops/devops) | | DEVOPS-AOC-19-002 | BLOCKED (2025-10-26) | Add pipeline stage executing `stella aoc verify --since` against seeded Mongo snapshots for Concelier + Excititor, publishing violation report artefacts. Dependencies: DEVOPS-AOC-19-001. | DevOps Guild (ops/devops) | @@ -47,13 +47,14 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A | DEVOPS-CONCELIER-CI-24-101 | DONE (2025-11-25) | Provide clean CI runner + warmed NuGet cache + vstest harness for Concelier WebService & Storage; deliver TRX/binlogs and unblock CONCELIER-GRAPH-24-101/28-102 and LNM-21-004..203. | DevOps Guild, Concelier Core Guild (ops/devops) | | DEVOPS-SCANNER-CI-11-001 | DONE (2025-11-30) | Supply warmed cache/diag runner for Scanner analyzers (LANG-11-001, JAVA 21-005/008) with binlogs + TRX; unblock restore/test hangs. | DevOps Guild, Scanner EPDR Guild (ops/devops) | | DEVOPS-SCANNER-JAVA-21-011-REL | TODO | Package/sign Java analyzer plug-in once dev task 21-011 delivers; publish to Offline Kit/CLI release pipelines with provenance. | DevOps Guild, Scanner Release Guild (ops/devops) | -| DEVOPS-SBOM-23-001 | TODO | Publish vetted offline NuGet feed + CI recipe for SbomService; prove with `dotnet test` run and share cache hashes; unblock SBOM-CONSOLE-23-001/002. | DevOps Guild, SBOM Service Guild (ops/devops) | +| DEVOPS-SBOM-23-001 | DONE (2025-11-30) | Publish vetted offline NuGet feed + CI recipe for SbomService; prove with `dotnet test` run and share cache hashes; unblock SBOM-CONSOLE-23-001/002. | DevOps Guild, SBOM Service Guild (ops/devops) | | FEED-REMEDIATION-1001 | BLOCKED (2025-11-24) | Define remediation scope and runbook for overdue feeds (CCCS/CERTBUND); schedule refresh; depends on PREP-FEEDCONN-ICS-KISA-PLAN. | Concelier Feed Owners (ops/devops) | | FEEDCONN-ICSCISA-02-012 / FEEDCONN-KISA-02-008 | BLOCKED (2025-11-24) | Publish provenance refresh/connector schedule for ICSCISA/KISA feeds; execute remediation per runbook once owners provide plan. | Concelier Feed Owners (ops/devops) | ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-11-30 | Completed DEVOPS-SBOM-23-001: added SBOM CI runner (`ops/devops/sbom-ci-runner/run-sbom-ci.sh`) with warmed-cache restore, binlog/TRX outputs, and NuGet cache hash evidence; documented in runner README. | DevOps | | 2025-11-30 | Completed DEVOPS-SCANNER-CI-11-001: added offline-friendly Scanner CI runner (`ops/devops/scanner-ci-runner/run-scanner-ci.sh`) and README; produces build binlog + TRX outputs from key test projects with warmed NuGet cache. | DevOps | | 2025-11-30 | Completed DEVOPS-ATTEST-73-001/73-002: added attestor CI stub (`ops/devops/attestation/ci.yml`) and secrets/rotation plan in `ops/devops/attestation/README.md`; pending mirror into `.gitea/workflows/attestor-ci.yml` for live runs. | DevOps | | 2025-11-30 | Completed DEVOPS-SPANSINK-31-003: added OTLP span sink compose stack + collector config (`docker-compose.spansink.yml`, `otel-spansink.yaml`), run script, and Grafana dashboard stub (`ops/devops/signals/dashboards/excititor-vex-traces.json`). | DevOps | @@ -68,6 +69,7 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A | 2025-11-24 | Added DEVOPS-SCANNER-JAVA-21-011-REL (moved from SPRINT_0131_0001_0001_scanner_surface.md) to keep DevOps release packaging in ops track. | Project Mgmt | | 2025-11-24 | Added DEVOPS-SPANSINK-31-003 (Excititor span sink for 31-003 traces) moved from SPRINT_0119_0001_0001_excititor_i per ops-only directive. | Project Mgmt | | 2025-11-24 | Imported Concelier feed ops items FEED-REMEDIATION-1001 and FEEDCONN-ICSCISA/KISA from Sprint 110; keeping feed remediation in ops track. | Project Mgmt | +| 2025-11-30 | Completed DEVOPS-AIRGAP-58-001: added syslog/SMTP compose stack (`ops/devops/airgap/compose-syslog-smtp.yaml`) and health script (`health_syslog_smtp.sh`); documented in airgap README for sealed environments. | DevOps | | 2025-11-30 | DEVOPS-AIAI-31-001 DONE: added Advisory AI CI harness (`ops/devops/advisoryai-ci-runner/run-advisoryai-ci.sh`) producing binlog/TRX/summary; warmed local NuGet cache for offline runs; docs in runner README. | DevOps | ## Decisions & Risks diff --git a/docs/implplan/tasks-all.md b/docs/implplan/tasks-all.md index cae62e14a..2769fe78a 100644 --- a/docs/implplan/tasks-all.md +++ b/docs/implplan/tasks-all.md @@ -2100,7 +2100,7 @@ | WEB-AIRGAP-56-002 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AIRGAP-57-001 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, AirGap Policy Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AIRGAP-58-001 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, AirGap Importer Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | -| WEB-AOC-19-002 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ship `ProvenanceBuilder`, checksum utilities, and signature verification helper integrated with guard logging. Cover DSSE/CMS formats with unit tests. Dependencies: WEB-AOC-19-001. | | | +| WEB-AOC-19-002 | DONE (2025-11-30) | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ship `ProvenanceBuilder`, checksum utilities, and signature verification helper integrated with guard logging. Cover DSSE/CMS formats with unit tests. Dependencies: WEB-AOC-19-001. | | | | WEB-AOC-19-003 | TODO | | SPRINT_116_concelier_v | QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AOC-19-004 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AOC-19-005 | TODO | 2025-11-08 | SPRINT_116_concelier_v | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | @@ -4290,7 +4290,7 @@ | WEB-AIRGAP-56-002 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AIRGAP-57-001 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, AirGap Policy Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AIRGAP-58-001 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, AirGap Importer Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | -| WEB-AOC-19-002 | TODO | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ship `ProvenanceBuilder`, checksum utilities, and signature verification helper integrated with guard logging. Cover DSSE/CMS formats with unit tests. Dependencies: WEB-AOC-19-001. | | | +| WEB-AOC-19-002 | DONE (2025-11-30) | | SPRINT_0212_0001_0001_web_i | BE-Base Platform Guild (src/Web/StellaOps.Web) | src/Web/StellaOps.Web | Ship `ProvenanceBuilder`, checksum utilities, and signature verification helper integrated with guard logging. Cover DSSE/CMS formats with unit tests. Dependencies: WEB-AOC-19-001. | | | | WEB-AOC-19-003 | TODO | | SPRINT_116_concelier_v | QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AOC-19-004 | TODO | | SPRINT_116_concelier_v | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | | WEB-AOC-19-005 | TODO | 2025-11-08 | SPRINT_116_concelier_v | Concelier WebService Guild, QA Guild (src/Concelier/StellaOps.Concelier.WebService) | src/Concelier/StellaOps.Concelier.WebService | | | | diff --git a/docs/modules/platform/architecture-overview.md b/docs/modules/platform/architecture-overview.md index 5ec2aae36..5670aee1b 100644 --- a/docs/modules/platform/architecture-overview.md +++ b/docs/modules/platform/architecture-overview.md @@ -8,6 +8,23 @@ This dossier summarises the end-to-end runtime topology after the Aggregation-On --- +> Need a quick orientation? The [Developer Quickstart](../onboarding/dev-quickstart.md) (29-Nov-2025 advisory) captures the core repositories, determinism checks, DSSE conventions, and starter tasks that explain how the platform pieces fit together. + +> Planner note: the [SBOM→VEX proof blueprint](../product-advisories/29-Nov-2025 - SBOM to VEX Proof Pipeline Blueprint.md) shows the DSSE → Rekor v2 tiles → VEX linkage, so threat-model and compliance teams can copy the capture/verification checkpoints. + +> Working on a feature? Check the [Implementor Guidelines](../product-advisories/30-Nov-2025 - Implementor Guidelines for Stella Ops.md) to align with the SRS + release playbook checklist before you merge anything into main. + +> Need to prove Rekor receipts? The [Rekor Receipt Checklist](../product-advisories/30-Nov-2025 - Rekor Receipt Checklist for Stella Ops.md) maps each field to a module owner and explains offline metadata for deterministic re-verification. + +> Taming unknowns? The [Unknowns Decay & Triage Heuristics](../product-advisories/30-Nov-2025 - Unknowns Decay & Triage Heuristics.md) explains the confidence decay card, triage queue view, and the daily export artifact for planning. + +> Check the [Ecosystem Reality Test Cases](../product-advisories/30-Nov-2025 - Ecosystem Reality Test Cases for StellaOps.md) for reproducible acceptance tests based on credential leaks, offline DB schema issues, SBOM parity drift, and scanner version divergence. + +> Need unblocker tasks? The [Standup Sprint Kickstarters](../product-advisories/30-Nov-2025 - Standup Sprint Kickstarters.md) lists three day-0 wins (scanner regressions, Postgres slice, DSSE/Rekor sweep) plus ready-to-copy ticket names. +> Compare how evidence/suppression/audit flows work elsewhere via the [Comparative Evidence Patterns](../product-advisories/30-Nov-2025 - Comparative Evidence Patterns for Stella Ops.md) brief—Snyk, GitHub, Aqua, Anchore/Grype, Prisma Cloud, and the UX trade-offs. + +> Evaluate public scanner incidents? The [Ecosystem Test Cases](../product-advisories/30-Nov-2025 - Ecosystem Test Cases for StellaOps.md) document five hardened regressions (Grype credential leak, Trivy offline schema, SBOM parity, Grype instability) that you can turn into acceptance tests today. + ## 1 · System landscape ```mermaid diff --git a/docs/onboarding/dev-quickstart.md b/docs/onboarding/dev-quickstart.md new file mode 100644 index 000000000..2dce02dee --- /dev/null +++ b/docs/onboarding/dev-quickstart.md @@ -0,0 +1,326 @@ +# StellaOps Developer Quickstart + +> **Audience:** Mid-level .NET developers +> **Goal:** Get you productive on StellaOps in 1–2 days, with special focus on determinism, cryptographic attestations, and the canonical data model. + +--- + +This quickstart mirrors the 29-Nov-2025 Developer Onboarding advisory (`docs/product-advisories/29-Nov-2025 - StellaOps – Mid-Level .NET Onboarding (Quick Start).md`) and keeps the determinism-first guidance in sync with that release note. + +## 1. What You’re Building (Context) + +StellaOps is a sovereign, air-gap-friendly platform that turns **SBOMs → VEX** with a fully **replayable, deterministic trust graph**. + +Core concepts: + +- **Deterministic scans:** Same inputs → same graph, hashes, and verdicts. +- **Cryptographic attestations:** DSSE/in-toto envelopes, optional PQC. +- **Trust lattice:** Merges vendor VEX, runtime signals, configs, etc. into a single deterministic verdict. +- **Audit trail:** Every decision is reproducible from stored inputs and proofs. + +If you think “content-addressed trust pipeline for SBOMs + VEX,” you’re in the right mental model. + +--- + +## 2. Repository & Docs Map + +Start by opening these projects **in order**: + +1. `src/StellaOps.Scanner.WebService/` + Scanning endpoints, rule plumbing, and calls into the trust lattice. +2. `src/StellaOps.Vexer/` (a.k.a. *Excititor*) + VEX verdict engine and trust-merge logic. +3. `src/StellaOps.Sbomer/` + SBOM ingest / normalize (CycloneDX, SPDX). +4. `src/StellaOps.Authority/` + Key management, DSSE/in-toto attestations, license tokens, Rekor integration. +5. `src/StellaOps.Scheduler/` + Batch processing, replay orchestration. +6. `src/StellaOps.Shared/CanonicalModel/` + Canonical entities & graph IDs. **Read this carefully** – it underpins determinism. + +Helpful docs: + +- `docs/modules/platform/*` – protocols (DSSE envelopes, lattice terms, trust receipts). +- `docs/architecture/*` – high-level diagrams and flows. + +--- + +## 3. Local Dev Setup + +### 3.1 Prerequisites + +- **.NET 10 SDK** (preview as specified in repo). +- **Docker** (for DB, queues, object storage). +- **Node.js** (for Angular UI, if you’re touching the frontend). +- **WSL2** (optional but convenient on Windows). + +### 3.2 Bring Up Infra + +From the repo root: + +```bash +# Bring up core infra for offline / air-gap friendly dev +docker compose -f compose/offline-kit.yml up -d +``` + +This usually includes: + +- MongoDB or Postgres (configurable). +- RabbitMQ (or equivalent queue). +- MinIO / object storage (depending on profile). + +### 3.3 Configure Environment + +```bash +cp env/example.local.env .env +``` + +Key settings: + +- `STELLAOPS_DB=Mongo` or `Postgres`. +- `AUTHORITY_*` – key material and config (see comments in `example.local.env`). +- Optional: `AUTHORITY_PQC=on` to enable post-quantum keys (Dilithium). + +### 3.4 Build & Run Backend + +```bash +# Restore & build everything +dotnet restore +dotnet build -c Debug + +# Run a focused slice for development +dotnet run --project src/StellaOps.Authority/StellaOps.Authority.csproj +dotnet run --project src/StellaOps.Scanner.WebService/StellaOps.Scanner.WebService.csproj +``` + +Health checks (adjust ports if needed): + +```bash +curl -s http://localhost:5080/health # Authority +curl -s http://localhost:5081/health # Scanner +``` + +--- + +## 4. Deterministic Sanity Tests + +These tests prove your local environment is configured correctly for **determinism**. If any of these fail due to snapshot mismatch, fix your environment before writing new features. + +### 4.1 SBOM → VEX “Not Affected” (Reachability False) + +```bash +dotnet test tests/Determinism/Det_SbomToVex_NotAffected.csproj +``` + +**What it checks:** + +- Two consecutive runs with the same SBOM produce identical `GraphRevisionID` and DSSE payload hashes. + +If they differ, inspect: + +- JSON canonicalization. +- Locale / culture. +- Line endings. + +### 4.2 In-toto Chain: Source → Build → Image Attestation + +```bash +dotnet test tests/Attestations/Att_InToto_Chain.csproj +``` + +**What it checks:** + +- DSSE envelope canonicalization is stable. +- Signature over CBOR-canonical JSON matches the stored hash. +- Full in-toto chain can be replayed deterministically. + +### 4.3 Lattice Merge: Vendor VEX + Runtime Signal + +```bash +dotnet test tests/Lattice/Lattice_VendorPlusRuntime.csproj +``` + +**What it checks:** + +- Merge verdict is stable regardless of input set order. +- Resulting `TrustReceipt` is byte-for-byte identical between runs. + +If any “golden” snapshots differ, you likely have: + +- Non-canonical JSON. +- Unstable enumeration (e.g., iterating `Dictionary<>` directly). +- Locale or newline drift. + +--- + +## 5. Coding Conventions (Determinism & Crypto) + +These are **non-negotiable** in code that affects trust graphs, proofs, or attestations. + +### 5.1 JSON & Canonicalization + +- Use the **`CanonicalJson`** helper whenever a payload is hashed, signed, or used for IDs. +- Rules: UTF-8, sorted keys, no insignificant whitespace, `\n` line endings. + +### 5.2 DSSE Envelopes + +- `payloadType` must always be `application/vnd.stellaops.trust+json`. +- Sign over the canonicalized bytes. + +```csharp +var payload = CanonicalJson.Serialize(trustDoc); +var env = DsseEnvelope.Create("application/vnd.stellaops.trust+json", payload); +var signed = await keyRing.SignAsync(env.CanonicalizeBytes()); +await rekor.SubmitAsync(signed, RekorMode.OfflineMirrorIfAirgapped); +``` + +### 5.3 Hashing + +- **BLAKE3** for internal content addressing. +- **SHA-256** where interop demands it. +- Never mix algorithms within the same ID type. + +### 5.4 Keys & Algorithms + +- Default signatures: **Ed25519** via `Authority.KeyRing`. +- Optional PQC: **Dilithium** when `AUTHORITY_PQC=on`. +- Always go through the keyring abstraction; never manage raw keys manually. + +### 5.5 Time & Clocks + +- Use `Instant`/`DateTimeOffset` (UTC), truncated to milliseconds. +- Never use `DateTime.Now` or local clocks in canonical data. + +### 5.6 IDs & Graph Nodes + +- Canonical/public IDs derive from hashes of canonical bytes. +- DB primary keys are implementation details. +- Do not depend on DB auto-increment or implicit sort order when hashing. + +### 5.7 VEX Verdicts + +Every VEX verdict must: + +- Carry `proofs[]` (reachability, config guards, runtime paths). +- Emit a `receipt` signed by Authority, covering verdict, proof hashes, and context. + +--- + +## 6. Daily Workflow + +1. Pick a focused issue (see starter tasks below). +2. Write tests first, especially determinism scenarios. +3. Implement changes with canonicalization boundaries explicit and signing centralized. +4. Run `dotnet test --filter Category=Determinism`. +5. Commit with the appropriate prefix (`feat(scanner):`, `feat(vexer):`, `feat(authority):`) and mention the affected `GraphRevisionID` if your change alters the trust graph. + +--- + +## 7. Suggested Starter Tasks + +These introduce the canonical data model and determinism mindset. + +### 7.1 Normalize CycloneDX Components → Canonical Packages + +**Area:** `StellaOps.Sbomer` + +**Tests:** `tests/Determinism/Det_SbomMapping` + +**Definition of done:** + +- Equivalent SBOMs (even if fields shuffle) yield identical package sets and canonical IDs. +- `CanonicalPackageSet.hash` is stable. +- Edge cases covered: missing `purl`, duplicate components, case variation. + +### 7.2 Implement “Not-Affected by Configuration” Proof + +**Area:** `StellaOps.Vexer/Proofs/ConfigSwitchProof.cs` + +**Definition of done:** + +- With `FeatureX=false`, CVE-1234 reports `status = not_affected` and the proof records `configPath` + `observed=false`. +- Proof hash is deterministic and included in the DSSE receipt. +- Lattice merge flips the verdict to `not_affected` when the runtime/config proof weight crosses the threshold, even if the vendor says `affected`. + +### 7.3 Authority Offline Rekor Mirror Submitter + +**Area:** `StellaOps.Authority/Rekor/RekorMirrorClient.cs` + +**Definition of done:** + +- `RekorMode.OfflineMirrorIfAirgapped` records canonical entries (JSON + hash path) locally. +- `rekor sync` replays entries in order, preserving entry IDs. +- Golden test ensures the same input sequence → same mirror tree hash. + +--- + +## 8. Database Notes (Mongo ↔ Postgres) + +- Use `StellaOps.Shared.Persistence` repository interfaces. +- Canonical/public IDs are hash-derived; DB keys are internal details. +- Never rely on DB sort order for anything that affects hashes or verdicts; re-canonicalize before hashing and apply deterministic ordering afterwards. + +--- + +## 9. Common Pitfalls + +1. Non-canonical JSON (unsorted keys, extra whitespace, mixed `\r\n`). +2. Local time creeping into proofs (`DateTime.Now`). +3. Unstable GUIDs in tests or canonical entities. +4. Unordered collections (`Dictionary<>` iterations, LINQ without `OrderBy`) while hashing or serializing. +5. Platform drift (Windows vs Linux newline/culture differences) – always use invariant culture and `\n` in canonical data. + +--- + +## 10. Useful Commands + +### 10.1 Determinism Pack + +```bash +# Run determinism-tagged fixtures +dotnet test --filter Category=Determinism +``` + +Update golden snapshots deliberately: + +```bash +dotnet test --filter Category=Determinism -- \ + TestRunParameters.Parameter(name="UpdateSnapshots", value="true") +``` + +### 10.2 Quick API Smoke + +```bash +curl -s http://localhost:5080/health + +curl -s -X POST \ + http://localhost:5081/scan \ + -H "Content-Type: application/json" \ + -d @samples/nginx.sbom.json +``` + +### 10.3 Verify DSSE Signature Locally + +```bash +dotnet run --project tools/StellaOps.Tools.Verify -- file trust.receipt.json +``` + +--- + +## 11. Glossary (Ask-Once) + +- **SBOM** – Software Bill of Materials (CycloneDX/SPDX). +- **VEX** – Vulnerability Exploitability eXchange: verdicts include `affected`, `not_affected`, `under_investigation`. +- **DSSE** – Dead Simple Signing Envelope; we sign canonical bytes. +- **In-toto** – Supply-chain attestation framework for source → build → artifact chains. +- **Lattice** – Rule system merging multiple verdicts/proofs into deterministic outcomes. +- **GraphRevisionID** – Hash of the canonical trust graph; acts like a build number for audits. + +Welcome aboard. Your best “map” is: + +1. Read the CanonicalModel types. +2. Run the determinism tests. +3. Ship one of the starter tasks with deterministic, test-covered changes. + +Keep everything **canonical, hashable, and replayable** and you’ll fit right in. diff --git a/ops/devops/airgap/README.md b/ops/devops/airgap/README.md index 98842378b..8d446bf1c 100644 --- a/ops/devops/airgap/README.md +++ b/ops/devops/airgap/README.md @@ -10,5 +10,7 @@ Artifacts supporting `DEVOPS-AIRGAP-56-001`: - `build_bootstrap_pack.py` — Builds a Bootstrap Pack from images/charts/extras listed in a JSON config, writing `bootstrap-manifest.json` + `checksums.sha256` deterministically. - `build_bootstrap_pack.sh` — Wrapper for the bootstrap pack builder. - `build_mirror_bundle.py` — Generates mirror bundle manifest + checksums with dual-control approvals; optional cosign signing. Outputs `mirror-bundle-manifest.json`, `checksums.sha256`, and optional signature/cert. +- `compose-syslog-smtp.yaml` — Local SMTP (MailHog) + syslog-ng stack for sealed environments. +- `health_syslog_smtp.sh` — Brings up the syslog/SMTP stack via docker compose and performs health checks (MailHog API + syslog logger). See also `ops/devops/sealed-mode-ci/` for the full sealed-mode compose harness and `egress_probe.py`, which this verification script wraps. diff --git a/ops/devops/airgap/compose-syslog-smtp.yaml b/ops/devops/airgap/compose-syslog-smtp.yaml new file mode 100644 index 000000000..55d630445 --- /dev/null +++ b/ops/devops/airgap/compose-syslog-smtp.yaml @@ -0,0 +1,31 @@ +version: "3.9" + +services: + smtp: + image: mailhog/mailhog:v1.0.1 + container_name: mailhog + ports: + - "1025:1025" # SMTP (plain) + - "8025:8025" # Web UI + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:8025/api/v2/health"] + interval: 10s + timeout: 3s + retries: 5 + start_period: 5s + restart: unless-stopped + + syslog: + image: balabit/syslog-ng:4.7.1 + container_name: syslog-ng + ports: + - "514:514/udp" + - "514:514/tcp" + command: ["/usr/sbin/syslog-ng", "-F", "-p", "/var/run/syslogd.pid"] + healthcheck: + test: ["CMD", "syslog-ng-ctl", "stats"] + interval: 10s + timeout: 3s + retries: 5 + start_period: 5s + restart: unless-stopped diff --git a/ops/devops/airgap/health_syslog_smtp.sh b/ops/devops/airgap/health_syslog_smtp.sh new file mode 100644 index 000000000..1383b2357 --- /dev/null +++ b/ops/devops/airgap/health_syslog_smtp.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Health check for compose-syslog-smtp.yaml (DEVOPS-AIRGAP-58-001) + +COMPOSE_FILE="$(cd "$(dirname "$0")" && pwd)/compose-syslog-smtp.yaml" + +echo "Starting syslog+smtp stack..." +docker compose -f "$COMPOSE_FILE" up -d + +echo "Waiting for health checks..." +docker compose -f "$COMPOSE_FILE" wait >/dev/null 2>&1 || true + +echo "Current health status:" +docker compose -f "$COMPOSE_FILE" ps + +echo "Sending test syslog message (UDP)..." +logger -n 127.0.0.1 -P 514 -d "test syslog message $(date -u +%s)" + +echo "SMTP health endpoint:" +curl -sf http://127.0.0.1:8025/api/v2/health || exit 1 + +echo "Done." diff --git a/ops/devops/sbom-ci-runner/README.md b/ops/devops/sbom-ci-runner/README.md new file mode 100644 index 000000000..76590820d --- /dev/null +++ b/ops/devops/sbom-ci-runner/README.md @@ -0,0 +1,29 @@ +# SBOM Service CI Runner Harness (DEVOPS-SBOM-23-001) + +Purpose: deterministic, offline-friendly CI harness for SBOM Service. Produces warmed-cache restore, build binlog, TRX outputs, and a NuGet cache hash to unblock SBOM console/consumer sprints. + +Usage +- From repo root run: `ops/devops/sbom-ci-runner/run-sbom-ci.sh` +- Outputs land in `ops/devops/artifacts/sbom-ci//`: + - `build.binlog` (solution build) + - `tests/sbom.trx` (VSTest results) + - `nuget-cache.hash` (sha256 over file name+size listing for offline cache traceability) + - `summary.json` (paths + sources + cache hash) + +Environment defaults +- `DOTNET_CLI_TELEMETRY_OPTOUT=1`, `DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1`, `DOTNET_RESTORE_DISABLE_PARALLEL=1` +- `NUGET_PACKAGES=$REPO/.nuget/packages` +- `NUGET_SOURCES=$REPO/local-nugets;$REPO/.nuget/packages` +- `TEST_FILTER` empty (set to narrow tests) + +What it does +1) Warm NuGet cache from `local-nugets/` into `$NUGET_PACKAGES` for air-gap parity. +2) `dotnet restore` + `dotnet build` on `src/SbomService/StellaOps.SbomService.sln` with `/bl`. +3) Run `StellaOps.SbomService.Tests` with TRX output (honors `TEST_FILTER`). +4) Produce `nuget-cache.hash` using sorted file name+size list hashed with sha256 (lightweight evidence of cache contents). +5) Emit `summary.json` with artefact paths and cache hash value. + +Notes +- Offline-only; no external services required. +- Timestamped output folders keep ordering deterministic; consumers should sort lexicographically. +- Extend `test_project` in the script if additional SBOM test projects are added. diff --git a/ops/devops/sbom-ci-runner/run-sbom-ci.sh b/ops/devops/sbom-ci-runner/run-sbom-ci.sh new file mode 100644 index 000000000..283aa615d --- /dev/null +++ b/ops/devops/sbom-ci-runner/run-sbom-ci.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +set -euo pipefail + +# SBOM Service CI runner (DEVOPS-SBOM-23-001) +# Builds SBOM solution and runs tests with warmed NuGet cache; emits binlog + TRX + cache hash summary. + +repo_root="$(cd "$(dirname "$0")/../../.." && pwd)" +ts="$(date -u +%Y%m%dT%H%M%SZ)" +out_dir="$repo_root/ops/devops/artifacts/sbom-ci/$ts" +logs_dir="$out_dir/tests" +mkdir -p "$logs_dir" + +export DOTNET_CLI_TELEMETRY_OPTOUT=${DOTNET_CLI_TELEMETRY_OPTOUT:-1} +export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=${DOTNET_SKIP_FIRST_TIME_EXPERIENCE:-1} +export DOTNET_RESTORE_DISABLE_PARALLEL=${DOTNET_RESTORE_DISABLE_PARALLEL:-1} +export NUGET_PACKAGES=${NUGET_PACKAGES:-$repo_root/.nuget/packages} +export NUGET_SOURCES=${NUGET_SOURCES:-"$repo_root/local-nugets;$repo_root/.nuget/packages"} +export TEST_FILTER=${TEST_FILTER:-""} + +mkdir -p "$NUGET_PACKAGES" +rsync -a "$repo_root/local-nugets/" "$NUGET_PACKAGES/" >/dev/null 2>&1 || true + +restore_sources=() +IFS=';' read -ra SRC_ARR <<< "$NUGET_SOURCES" +for s in "${SRC_ARR[@]}"; do + [[ -n "$s" ]] && restore_sources+=(--source "$s") +done + +solution="$repo_root/src/SbomService/StellaOps.SbomService.sln" +dotnet restore "$solution" --ignore-failed-sources "${restore_sources[@]}" + +build_binlog="$out_dir/build.binlog" +dotnet build "$solution" -c Release /p:ContinuousIntegrationBuild=true /bl:"$build_binlog" + +trx_name="sbom.trx" +test_project="$repo_root/src/SbomService/StellaOps.SbomService.Tests/StellaOps.SbomService.Tests.csproj" +common_test_args=( -c Release --no-build --results-directory "$logs_dir" ) +if [[ -n "$TEST_FILTER" ]]; then + common_test_args+=( --filter "$TEST_FILTER" ) +fi + +if [[ -f "$test_project" ]]; then + dotnet test "$test_project" "${common_test_args[@]}" --logger "trx;LogFileName=$trx_name" +fi + +# Lightweight cache hash: list files with size, hash the listing +cache_listing="$out_dir/nuget-cache.list" +find "$NUGET_PACKAGES" -type f -printf "%P %s\n" | sort > "$cache_listing" +cache_hash=$(sha256sum "$cache_listing" | awk '{print $1}') + +echo "$cache_hash nuget-cache.list" > "$out_dir/nuget-cache.hash" + +summary="$out_dir/summary.json" +{ + printf '{\n' + printf ' "timestamp_utc": "%s",\n' "$ts" + printf ' "build_binlog": "%s",\n' "${build_binlog#${repo_root}/}" + printf ' "tests": [\n' + printf ' {"project":"SbomService","trx":"%s"}\n' "${logs_dir#${repo_root}/}/$trx_name" + printf ' ],\n' + printf ' "nuget_packages": "%s",\n' "${NUGET_PACKAGES#${repo_root}/}" + printf ' "cache_hash": "%s",\n' "$cache_hash" + printf ' "sources": [\n' + for i in "${!SRC_ARR[@]}"; do + sep=","; [[ $i -eq $((${#SRC_ARR[@]}-1)) ]] && sep="" + printf ' "%s"%s\n' "${SRC_ARR[$i]}" "$sep" + done + printf ' ]\n' + printf '}\n' +} > "$summary" + +echo "Artifacts written to ${out_dir#${repo_root}/}" diff --git a/src/Web/StellaOps.Web/TASKS.md b/src/Web/StellaOps.Web/TASKS.md new file mode 100644 index 000000000..02943143a --- /dev/null +++ b/src/Web/StellaOps.Web/TASKS.md @@ -0,0 +1,8 @@ +# Web Guild Tasks + +| Task ID | State | Notes | +| --- | --- | --- | +| WEB-AOC-19-002 | DONE (2025-11-30) | Added provenance builder, checksum utilities, and DSSE/CMS signature verification helpers with unit tests. | +| WEB-AOC-19-003 | TODO | Analyzer/guard validation remains; will align once helper APIs settle. | +| WEB-CONSOLE-23-002 | TODO | Status/stream endpoints to proxy Scheduler once contracts finalized. | +| WEB-EXC-25-001 | TODO | Exceptions workflow CRUD pending policy scopes. | diff --git a/src/Web/StellaOps.Web/src/app/core/aoc/checksum.util.ts b/src/Web/StellaOps.Web/src/app/core/aoc/checksum.util.ts new file mode 100644 index 000000000..f3cf579b8 --- /dev/null +++ b/src/Web/StellaOps.Web/src/app/core/aoc/checksum.util.ts @@ -0,0 +1,71 @@ +export type HashAlgorithm = 'SHA-256' | 'SHA-512'; + +export interface DigestResult { + algorithm: HashAlgorithm; + hex: string; + uri: string; // e.g. sha256:abcd... +} + +const encoder = new TextEncoder(); + +function toUint8(payload: string | ArrayBuffer | Uint8Array): Uint8Array { + if (typeof payload === 'string') { + return encoder.encode(payload); + } + + if (payload instanceof ArrayBuffer) { + return new Uint8Array(payload); + } + + return payload; +} + +function toHex(buffer: ArrayBuffer): string { + return Array.from(new Uint8Array(buffer)) + .map((b) => b.toString(16).padStart(2, '0')) + .join(''); +} + +/** + * Deterministically compute a digest over an input payload using WebCrypto. + * Returns both the raw hex and a URI-style prefix (sha256:...). + */ +export async function computeDigest( + payload: string | ArrayBuffer | Uint8Array, + algorithm: HashAlgorithm = 'SHA-256' +): Promise { + if (!globalThis.crypto?.subtle) { + throw new Error('WebCrypto unavailable: cannot compute digest'); + } + + const data = toUint8(payload); + const digestBuffer = await globalThis.crypto.subtle.digest(algorithm, data); + const hex = toHex(digestBuffer); + const prefix = algorithm.toLowerCase().replace('-', ''); + + return { + algorithm, + hex, + uri: `${prefix}:${hex}`, + }; +} + +/** + * Convenience helper to deterministically serialize an object before hashing. + * Uses stable key ordering and JSON without spaces. + */ +export function canonicalJson(input: unknown): string { + const replacer = (_key: string, value: unknown) => { + if (value && typeof value === 'object' && !Array.isArray(value)) { + return Object.keys(value as Record) + .sort() + .reduce>((acc, key) => { + acc[key] = (value as Record)[key]; + return acc; + }, {}); + } + return value; + }; + + return JSON.stringify(input, replacer); +} diff --git a/src/Web/StellaOps.Web/src/app/core/aoc/provenance-builder.spec.ts b/src/Web/StellaOps.Web/src/app/core/aoc/provenance-builder.spec.ts new file mode 100644 index 000000000..cc0423d4b --- /dev/null +++ b/src/Web/StellaOps.Web/src/app/core/aoc/provenance-builder.spec.ts @@ -0,0 +1,128 @@ +import { ProvenanceBuilder } from './provenance-builder'; +import { canonicalJson, computeDigest } from './checksum.util'; +import { + dssePreAuthEncode, + verifyCmsSignature, + verifyDsseSignature, +} from './signature-verifier'; + +async function exportPublicKeyPem(publicKey: CryptoKey): Promise { + const spki = await crypto.subtle.exportKey('spki', publicKey); + const base64 = btoa(String.fromCharCode(...new Uint8Array(spki))); + const wrapped = base64.match(/.{1,64}/g)?.join('\n') ?? base64; + return `-----BEGIN PUBLIC KEY-----\n${wrapped}\n-----END PUBLIC KEY-----`; +} + +describe('Provenance utilities', () => { + it('builds deterministic provenance for objects', async () => { + const builder = new ProvenanceBuilder(() => new Date('2025-11-30T12:00:00Z')); + const provenance = await builder.build( + { b: 2, a: 1 }, + { + sourceId: 'registry-1', + sourceType: 'registry', + sourceUrl: 'https://example.test/manifest', + submitter: 'ci-bot', + } + ); + + expect(provenance.ingestedAt).toBe('2025-11-30T12:00:00.000Z'); + expect(provenance.digest.startsWith('sha256:')).toBeTrue(); + + const canonical = canonicalJson({ b: 2, a: 1 }); + const digest = await computeDigest(canonical, 'SHA-256'); + expect(provenance.digest).toBe(digest.uri); + }); + + it('computes DSSE pre-auth encoding with correct prefix', () => { + const payload = new TextEncoder().encode('hello'); + const pae = dssePreAuthEncode('text/plain', payload); + const asText = new TextDecoder().decode(pae); + expect(asText.startsWith('DSSEv1 10 text/plain 5 ')).toBeTrue(); + expect(asText.endsWith('hello')).toBeTrue(); + }); + + it('verifies CMS signatures', async () => { + const keyPair = await crypto.subtle.generateKey( + { + name: 'RSASSA-PKCS1-v1_5', + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-256', + }, + true, + ['sign', 'verify'] + ); + + const message = new TextEncoder().encode('aoc-proof'); + const signature = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', keyPair.privateKey, message); + const pem = await exportPublicKeyPem(keyPair.publicKey); + + const result = await verifyCmsSignature({ + payload: message, + signature, + publicKeyPem: pem, + algorithm: 'RSASSA-PKCS1-v1_5', + hash: 'SHA-256', + }); + + expect(result.valid).toBeTrue(); + }); + + it('fails verification when payload changes', async () => { + const keyPair = await crypto.subtle.generateKey( + { + name: 'RSASSA-PKCS1-v1_5', + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-256', + }, + true, + ['sign', 'verify'] + ); + + const message = new TextEncoder().encode('aoc-proof'); + const signature = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', keyPair.privateKey, message); + const pem = await exportPublicKeyPem(keyPair.publicKey); + + const result = await verifyCmsSignature({ + payload: 'tampered', + signature, + publicKeyPem: pem, + algorithm: 'RSASSA-PKCS1-v1_5', + hash: 'SHA-256', + }); + + expect(result.valid).toBeFalse(); + }); + + it('verifies DSSE signatures using pre-auth encoding', async () => { + const keyPair = await crypto.subtle.generateKey( + { + name: 'RSASSA-PKCS1-v1_5', + modulusLength: 2048, + publicExponent: new Uint8Array([1, 0, 1]), + hash: 'SHA-256', + }, + true, + ['sign', 'verify'] + ); + + const payloadBytes = new TextEncoder().encode('{"sub":"example"}'); + const pae = dssePreAuthEncode('application/json', payloadBytes); + const signature = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', keyPair.privateKey, pae); + const pem = await exportPublicKeyPem(keyPair.publicKey); + + const result = await verifyDsseSignature({ + payload: payloadBytes, + payloadType: 'application/json', + signature, + publicKeyPem: pem, + algorithm: 'RSASSA-PKCS1-v1_5', + hash: 'SHA-256', + }); + + expect(result.valid).toBeTrue(); + expect(result.message?.startsWith('sha256:')).toBeTrue(); + }); +}); diff --git a/src/Web/StellaOps.Web/src/app/core/aoc/provenance-builder.ts b/src/Web/StellaOps.Web/src/app/core/aoc/provenance-builder.ts new file mode 100644 index 000000000..58a568c5c --- /dev/null +++ b/src/Web/StellaOps.Web/src/app/core/aoc/provenance-builder.ts @@ -0,0 +1,40 @@ +import { AocProvenance } from '../api/aoc.models'; +import { HashAlgorithm, canonicalJson, computeDigest } from './checksum.util'; + +export interface ProvenanceOptions { + sourceId: string; + sourceType?: AocProvenance['sourceType']; + sourceUrl?: string; + submitter?: string; + ingestedAt?: string; + hashAlgorithm?: HashAlgorithm; +} + +/** + * Deterministic provenance builder used by the AOC workspace to attach + * digests and timing metadata to documents before verification/signing. + */ +export class ProvenanceBuilder { + constructor(private readonly clock: () => Date = () => new Date()) {} + + async build( + payload: string | ArrayBuffer | Uint8Array | Record, + options: ProvenanceOptions + ): Promise { + const serialised = + typeof payload === 'string' || payload instanceof ArrayBuffer || payload instanceof Uint8Array + ? payload + : canonicalJson(payload); + + const digest = await computeDigest(serialised, options.hashAlgorithm ?? 'SHA-256'); + + return { + sourceId: options.sourceId, + sourceType: options.sourceType, + sourceUrl: options.sourceUrl, + submitter: options.submitter, + ingestedAt: options.ingestedAt ?? this.clock().toISOString(), + digest: digest.uri, + }; + } +} diff --git a/src/Web/StellaOps.Web/src/app/core/aoc/signature-verifier.ts b/src/Web/StellaOps.Web/src/app/core/aoc/signature-verifier.ts new file mode 100644 index 000000000..005fb7c4e --- /dev/null +++ b/src/Web/StellaOps.Web/src/app/core/aoc/signature-verifier.ts @@ -0,0 +1,133 @@ +import { HashAlgorithm, computeDigest } from './checksum.util'; + +export interface VerificationResult { + valid: boolean; + message?: string; +} + +export interface VerifyOptions { + payload: string | ArrayBuffer | Uint8Array; + signature: ArrayBuffer | Uint8Array | string; // base64 if string + publicKeyPem: string; + algorithm?: 'RSASSA-PKCS1-v1_5' | 'RSA-PSS'; + hash?: HashAlgorithm; + saltLength?: number; // RSA-PSS only +} + +function base64ToArrayBuffer(input: string): ArrayBuffer { + const normalized = input.replace(/\s+/g, ''); + const binary = atob(normalized); + const buffer = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i += 1) { + buffer[i] = binary.charCodeAt(i); + } + return buffer.buffer; +} + +async function importPublicKey( + pem: string, + algorithm: 'RSASSA-PKCS1-v1_5' | 'RSA-PSS', + hash: HashAlgorithm +): Promise { + const clean = pem + .replace('-----BEGIN PUBLIC KEY-----', '') + .replace('-----END PUBLIC KEY-----', '') + .replace(/\s+/g, ''); + const binaryDer = base64ToArrayBuffer(clean); + + return globalThis.crypto.subtle.importKey( + 'spki', + binaryDer, + { + name: algorithm, + hash: { name: hash }, + }, + true, + ['verify'] + ); +} + +function toUint8(payload: string | ArrayBuffer | Uint8Array): Uint8Array { + if (typeof payload === 'string') { + return new TextEncoder().encode(payload); + } + if (payload instanceof ArrayBuffer) { + return new Uint8Array(payload); + } + return payload; +} + +function normalizeSignature(sig: ArrayBuffer | Uint8Array | string): ArrayBuffer { + if (typeof sig === 'string') { + return base64ToArrayBuffer(sig); + } + if (sig instanceof ArrayBuffer) { + return sig; + } + return sig.buffer; +} + +export async function verifyCmsSignature(options: VerifyOptions): Promise { + if (!globalThis.crypto?.subtle) { + return { valid: false, message: 'WebCrypto unavailable' }; + } + + const algorithm = options.algorithm ?? 'RSASSA-PKCS1-v1_5'; + const hash = options.hash ?? 'SHA-256'; + + const key = await importPublicKey(options.publicKeyPem, algorithm, hash); + const payload = toUint8(options.payload); + const signature = normalizeSignature(options.signature); + + const verified = await globalThis.crypto.subtle.verify( + { name: algorithm, saltLength: options.saltLength ?? 32 }, + key, + signature, + payload + ); + + return verified + ? { valid: true } + : { valid: false, message: 'Signature verification failed' }; +} + +export interface DsseVerifyOptions extends VerifyOptions { + payloadType: string; +} + +/** + * DSSE pre-authentication encoding (PAE) as defined by sigstore. + * Format: `DSSEv1 ` + */ +export function dssePreAuthEncode(payloadType: string, payload: Uint8Array): Uint8Array { + const enc = new TextEncoder(); + const header = `DSSEv1 ${payloadType.length} ${payloadType} ${payload.length} `; + const headerBytes = enc.encode(header); + const output = new Uint8Array(headerBytes.length + payload.length); + output.set(headerBytes, 0); + output.set(payload, headerBytes.length); + return output; +} + +export async function verifyDsseSignature( + options: DsseVerifyOptions +): Promise { + if (!globalThis.crypto?.subtle) { + return { valid: false, message: 'WebCrypto unavailable' }; + } + + const payloadBytes = toUint8(options.payload); + const pae = dssePreAuthEncode(options.payloadType, payloadBytes); + const cmsResult = await verifyCmsSignature({ + ...options, + payload: pae, + }); + + if (!cmsResult.valid) { + return cmsResult; + } + + // Provide a digest hint for downstream display/debugging + const digest = await computeDigest(payloadBytes, options.hash ?? 'SHA-256'); + return { valid: true, message: digest.uri }; +}