Compare commits
665 Commits
v0.19.0
...
fix/cwe-61
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20537d7bd9 | ||
|
|
66b37b5a98 | ||
|
|
52f759cc00 | ||
|
|
cc3cb1da4b | ||
|
|
2c608bf684 | ||
|
|
a855ed0cf6 | ||
|
|
ad7e97e7df | ||
|
|
a2fea2b368 | ||
|
|
c428a5be57 | ||
|
|
22769977e3 | ||
|
|
50fb6659da | ||
|
|
e4f2606ea2 | ||
|
|
af5cdf48cf | ||
|
|
1940f7f55d | ||
|
|
c785c5165d | ||
|
|
eaf981f635 | ||
|
|
4284bcf0b6 | ||
|
|
586f7cfc98 | ||
|
|
15e9efeeae | ||
|
|
cd8bb2f501 | ||
|
|
fa42e79af3 | ||
|
|
859ddaef1f | ||
|
|
3b247cdd73 | ||
|
|
00aab022f5 | ||
|
|
a40764d7da | ||
|
|
87b3db7019 | ||
|
|
ded533d690 | ||
|
|
fc4ceafa20 | ||
|
|
5b02eebfe5 | ||
|
|
338c9a3eef | ||
|
|
68d21fc20b | ||
|
|
ea9ebdfdf2 | ||
|
|
1d09c793f6 | ||
|
|
856fd4097b | ||
|
|
bb14ae73cc | ||
|
|
44450ff88a | ||
|
|
3a80e032f4 | ||
|
|
6e2d89372f | ||
|
|
5bf7b54496 | ||
|
|
0bdcb2a091 | ||
|
|
b988179685 | ||
|
|
cbfe80809e | ||
|
|
9f826f764c | ||
|
|
262a805317 | ||
|
|
ec25165e54 | ||
|
|
7b34e2ecea | ||
|
|
ec9b8ac925 | ||
|
|
431d88c47c | ||
|
|
e08e1861d6 | ||
|
|
64d2d4d423 | ||
|
|
9f233a0128 | ||
|
|
6939c792bd | ||
|
|
853940b74a | ||
|
|
5aa8940af2 | ||
|
|
cd3f2a90b4 | ||
|
|
bf89c2603d | ||
|
|
19b388d865 | ||
|
|
25e40f164d | ||
|
|
5505f66c41 | ||
|
|
9a07619b89 | ||
|
|
faf2041a82 | ||
|
|
460834f8f3 | ||
|
|
75ae77a6bf | ||
|
|
73f2134caf | ||
|
|
c5efc30f43 | ||
|
|
3099d74b28 | ||
|
|
fcc9309f2e | ||
|
|
e581a9e7e7 | ||
|
|
ac72e6c3ac | ||
|
|
db824152ef | ||
|
|
1de29fe6fc | ||
|
|
ac2026159e | ||
|
|
cfb28055cf | ||
|
|
a2d8970b22 | ||
|
|
abadf9878a | ||
|
|
87590ac4e8 | ||
|
|
999a81dce7 | ||
|
|
031457406a | ||
|
|
3d9d183b77 | ||
|
|
379c664b5c | ||
|
|
4d8f09e279 | ||
|
|
8a0e91ac3b | ||
|
|
3bc798bc9d | ||
|
|
8b4e0afd43 | ||
|
|
c7c4fc8915 | ||
|
|
41c0252cf1 | ||
|
|
4c375ad86f | ||
|
|
459a8fef42 | ||
|
|
00a18704e8 | ||
|
|
dc9bbacc27 | ||
|
|
4da4e1a0d4 | ||
|
|
3318b4af80 | ||
|
|
c1aaa48ecb | ||
|
|
f82a892405 | ||
|
|
287e85d232 | ||
|
|
fa6fbc8ce9 | ||
|
|
61418fa9dd | ||
|
|
0df1126aa9 | ||
|
|
1c72469ad6 | ||
|
|
338f864f60 | ||
|
|
8b0011f6c6 | ||
|
|
e6a044c532 | ||
|
|
bb1e59ea93 | ||
|
|
b761d7d4f7 | ||
|
|
418fb7d17c | ||
|
|
5084483984 | ||
|
|
3c96810aa1 | ||
|
|
dcd1ec7e95 | ||
|
|
4f222b6308 | ||
|
|
071ae38d35 | ||
|
|
3385800f41 | ||
|
|
4fe538b37e | ||
|
|
2bdf4f8286 | ||
|
|
a96366957e | ||
|
|
c44642241c | ||
|
|
b5bf505ab9 | ||
|
|
51f59e5972 | ||
|
|
65d02e754e | ||
|
|
816c0595e1 | ||
|
|
9496001811 | ||
|
|
ec1b79c2b7 | ||
|
|
bab79f2349 | ||
|
|
edd7405313 | ||
|
|
79800871fa | ||
|
|
67dd87d3a9 | ||
|
|
dfc2beb8f3 | ||
|
|
5e5eae7422 | ||
|
|
78f216eaef | ||
|
|
34d5cca972 | ||
|
|
5d771381a1 | ||
|
|
95a65069c0 | ||
|
|
1e4b2d1d03 | ||
|
|
81f1dce887 | ||
|
|
3570c05805 | ||
|
|
b66cc34e1c | ||
|
|
5bafd92edf | ||
|
|
6e4294dce1 | ||
|
|
82b1c85b7c | ||
|
|
41ecb7122f | ||
|
|
2fa7608b9b | ||
|
|
285ee2cdda | ||
|
|
72598ed2ce | ||
|
|
8670cdfd2b | ||
|
|
f8e8440388 | ||
|
|
ab4dee5fcd | ||
|
|
04e87e87d5 | ||
|
|
cc96435db1 | ||
|
|
53af0a6866 | ||
|
|
3577ce6c56 | ||
|
|
0ce35f2d64 | ||
|
|
0e556433f7 | ||
|
|
4b170b69e0 | ||
|
|
fd58f9d99a | ||
|
|
f33ab83b7c | ||
|
|
6777f6e8ff | ||
|
|
1096b00b94 | ||
|
|
6180d53a93 | ||
|
|
fca1139c81 | ||
|
|
847b10322a | ||
|
|
59251c8f27 | ||
|
|
58b087bc63 | ||
|
|
8ab926dc8b | ||
|
|
85f258d9f6 | ||
|
|
042c5ec6e5 | ||
|
|
05d19c0471 | ||
|
|
48af524313 | ||
|
|
bad97102e1 | ||
|
|
98a4efcd82 | ||
|
|
f631dfc628 | ||
|
|
eb5b74cbe3 | ||
|
|
1785ccc39f | ||
|
|
4b896c2e3c | ||
|
|
88a9cdb0ff | ||
|
|
354ff0068a | ||
|
|
0c419d8f85 | ||
|
|
26be592f4d | ||
|
|
fb9b6cae76 | ||
|
|
5bb9b2a6fb | ||
|
|
593694a4b4 | ||
|
|
b207993299 | ||
|
|
a807288052 | ||
|
|
49b956f916 | ||
|
|
53227de55c | ||
|
|
58921556a1 | ||
|
|
442164cc5c | ||
|
|
8414004d8f | ||
|
|
7932188dae | ||
|
|
d4081d954f | ||
|
|
2e85a341c8 | ||
|
|
2969eb58e4 | ||
|
|
9d6ecd8f73 | ||
|
|
0c2a9d0ee8 | ||
|
|
c71e6fef30 | ||
|
|
3186676f94 | ||
|
|
b108f11bb4 | ||
|
|
d56e8a0f7f | ||
|
|
b76c1d7efc | ||
|
|
cbb2f42a2b | ||
|
|
fd056c05a7 | ||
|
|
2f76b4eadc | ||
|
|
fde59a94ae | ||
|
|
7409862140 | ||
|
|
065ac87815 | ||
|
|
d6d810f1a2 | ||
|
|
05c71988c0 | ||
|
|
3e32610ea1 | ||
|
|
be502b7533 | ||
|
|
4e81a982aa | ||
|
|
c977c6f9a4 | ||
|
|
7416229ba3 | ||
|
|
9000c1f4ba | ||
|
|
7423e64bc5 | ||
|
|
1d5f46980d | ||
|
|
e09efa42a8 | ||
|
|
e99be20bae | ||
|
|
6ce858e52e | ||
|
|
f41bd485e3 | ||
|
|
2fc5b10d3d | ||
|
|
f3d69b0116 | ||
|
|
13c5f8356c | ||
|
|
95c3adfa61 | ||
|
|
ef71f66029 | ||
|
|
317bff326b | ||
|
|
542d4ff3ee | ||
|
|
82a55da026 | ||
|
|
0535f50d89 | ||
|
|
fc5cb0eb88 | ||
|
|
524d363e27 | ||
|
|
e2ebdb37f0 | ||
|
|
539dd1bff4 | ||
|
|
f8ec567a35 | ||
|
|
c758c9d3ab | ||
|
|
bfe535d36a | ||
|
|
aaf52475ee | ||
|
|
424dc43652 | ||
|
|
cd35f6d8c7 | ||
|
|
85b0bb1f5e | ||
|
|
b0001e4d50 | ||
|
|
a77b6c5d3e | ||
|
|
3414c7c941 | ||
|
|
332872c7f5 | ||
|
|
c499c57296 | ||
|
|
912bb7c577 | ||
|
|
36d561bbb8 | ||
|
|
fccb1f06ac | ||
|
|
cf46ff0a3b | ||
|
|
6a37a906ce | ||
|
|
0f823956c6 | ||
|
|
703108051a | ||
|
|
795486e5b2 | ||
|
|
799ca8c5f9 | ||
|
|
9cc7393e7b | ||
|
|
791e812c3c | ||
|
|
187c3aea68 | ||
|
|
d7de28a040 | ||
|
|
d1baf6f1b0 | ||
|
|
3201830405 | ||
|
|
728a55f1d8 | ||
|
|
d3ef8d83b3 | ||
|
|
c4e8d6c8ae | ||
|
|
698ad86d17 | ||
|
|
2240c4c629 | ||
|
|
65b82a8e08 | ||
|
|
8032fb5b41 | ||
|
|
56fde3cbe1 | ||
|
|
bccbb708f1 | ||
|
|
80b1ed7fab | ||
|
|
e68035fe30 | ||
|
|
80ecb7de7f | ||
|
|
75cd0a4d9c | ||
|
|
2824a731f5 | ||
|
|
2dbb00036d | ||
|
|
0ad0c2f2c4 | ||
|
|
104f0eb6ee | ||
|
|
c144bb2b97 | ||
|
|
f50b05519b | ||
|
|
ca3c1085ac | ||
|
|
4cee4f01f3 | ||
|
|
82e2134333 | ||
|
|
6add11f1d2 | ||
|
|
744b6aeff5 | ||
|
|
92310a8b3e | ||
|
|
d74ea47e2c | ||
|
|
c665f62700 | ||
|
|
37471141e8 | ||
|
|
81497beb4b | ||
|
|
2d40f34ff0 | ||
|
|
801760add1 | ||
|
|
4ebf8d23fe | ||
|
|
77a7368c5d | ||
|
|
51a01c4f7b | ||
|
|
13d31dd922 | ||
|
|
c9bb303a7d | ||
|
|
6ebfd417e3 | ||
|
|
b527470e75 | ||
|
|
89b4d88eb1 | ||
|
|
a69f698440 | ||
|
|
ee224adcf1 | ||
|
|
5bbae48b6b | ||
|
|
abcfd62b21 | ||
|
|
10d952a22e | ||
|
|
635caf0f9a | ||
|
|
2266a8d051 | ||
|
|
b292a1b793 | ||
|
|
bf398a1cb2 | ||
|
|
e7c98e5526 | ||
|
|
99ff0a34e3 | ||
|
|
c42b7f5a5b | ||
|
|
ed89295012 | ||
|
|
834907cb5d | ||
|
|
e295a1f64c | ||
|
|
7cec4d7979 | ||
|
|
132bbbd657 | ||
|
|
833220f1cb | ||
|
|
e1e422bfc6 | ||
|
|
e4b6ce62cd | ||
|
|
396d01595e | ||
|
|
6a13e648ea | ||
|
|
5fa0cff274 | ||
|
|
bcb2748f89 | ||
|
|
e68a6039b9 | ||
|
|
0199f93994 | ||
|
|
f2cf5c3508 | ||
|
|
1d39756713 | ||
|
|
71455ef88f | ||
|
|
99b8ed875e | ||
|
|
8242666678 | ||
|
|
5aade0456e | ||
|
|
479f56f3e8 | ||
|
|
8c7a55eaa2 | ||
|
|
924b8227b5 | ||
|
|
c3fa29d13c | ||
|
|
e5dab58b42 | ||
|
|
22496a44a8 | ||
|
|
87e6762611 | ||
|
|
ddc79865bc | ||
|
|
6ee185c538 | ||
|
|
367943b543 | ||
|
|
08e7eb7525 | ||
|
|
35ca99866a | ||
|
|
2f83526966 | ||
|
|
5a58404e1b | ||
|
|
8ea907066b | ||
|
|
ffe5d951e0 | ||
|
|
e5af7d98d1 | ||
|
|
27c252600a | ||
|
|
c32cce2a88 | ||
|
|
c01c6c6225 | ||
|
|
a66659476d | ||
|
|
7a8b0343e4 | ||
|
|
cc3077d709 | ||
|
|
d1362a7fba | ||
|
|
4e9e1919a8 | ||
|
|
f19f53ed9a | ||
|
|
f062dc206e | ||
|
|
a97cb334a2 | ||
|
|
cf52a943b5 | ||
|
|
46d0ecc4fb | ||
|
|
348c5e5405 | ||
|
|
25dbe82360 | ||
|
|
fc404da455 | ||
|
|
ed27fb0da9 | ||
|
|
afbd50b43f | ||
|
|
ad2d30b525 | ||
|
|
a570a3327f | ||
|
|
0fd00575a2 | ||
|
|
a3d1ae3742 | ||
|
|
6f408f62ba | ||
|
|
e92e7edd70 | ||
|
|
4e4c4581ea | ||
|
|
3f12ca05a3 | ||
|
|
a681d6aa30 | ||
|
|
3632d0d88c | ||
|
|
a1a9ab2ece | ||
|
|
9c203914dd | ||
|
|
6cfe8ca9f2 | ||
|
|
938b170d98 | ||
|
|
9d6d2cbe53 | ||
|
|
136dd7ef62 | ||
|
|
f0c754cc52 | ||
|
|
28be62dee0 | ||
|
|
49bfbf3f76 | ||
|
|
2f90d936bf | ||
|
|
4a60400af9 | ||
|
|
18d0c235fa | ||
|
|
fe8225753b | ||
|
|
273fb3cf21 | ||
|
|
e3b6693402 | ||
|
|
ac915f14c7 | ||
|
|
5ee52dd4d6 | ||
|
|
b5fd5d5774 | ||
|
|
ae4f5936b3 | ||
|
|
5017fdf4c1 | ||
|
|
f0eda7c93c | ||
|
|
f60a99d0bd | ||
|
|
1440b2722e | ||
|
|
f58c96d29f | ||
|
|
3b92700b5b | ||
|
|
5c0a543669 | ||
|
|
317b695efb | ||
|
|
077e3c1d2b | ||
|
|
b5c5ab0bc3 | ||
|
|
a6188bf2f1 | ||
|
|
2ecd6dd9d4 | ||
|
|
16752f4bb1 | ||
|
|
a75dd2dcdd | ||
|
|
63e79664cc | ||
|
|
005b7bdf5b | ||
|
|
0f143af5bc | ||
|
|
76fb800922 | ||
|
|
58f5295652 | ||
|
|
0917a1ae95 | ||
|
|
409dc0526f | ||
|
|
10259146df | ||
|
|
8cbd907d82 | ||
|
|
ff5ef35a0f | ||
|
|
fbb86b1cc3 | ||
|
|
0f995edbd1 | ||
|
|
aaddb88488 | ||
|
|
f79f0218c5 | ||
|
|
d94c9ba623 | ||
|
|
0241de69f4 | ||
|
|
f20e789a16 | ||
|
|
6f5c8873f9 | ||
|
|
7a12ab7928 | ||
|
|
871adca270 | ||
|
|
dbff270d22 | ||
|
|
8e1b9d91e2 | ||
|
|
67bcef32e4 | ||
|
|
739104e029 | ||
|
|
2204b7bd35 | ||
|
|
fdbba5b838 | ||
|
|
4ff65c83be | ||
|
|
3409e204eb | ||
|
|
61bb19e6f3 | ||
|
|
3cc979f5b8 | ||
|
|
ef8f237233 | ||
|
|
43a63007a7 | ||
|
|
404aa92ea0 | ||
|
|
94356e7d4e | ||
|
|
63c9976e5f | ||
|
|
09ef4f579e | ||
|
|
fbd94a031e | ||
|
|
6483a25555 | ||
|
|
61b73bc57b | ||
|
|
d77d618de0 | ||
|
|
2cd19d8964 | ||
|
|
61d4e12c56 | ||
|
|
5c5c1eabfc | ||
|
|
d9cc0ead71 | ||
|
|
b78798b877 | ||
|
|
e90ad34c28 | ||
|
|
1a559e3c64 | ||
|
|
a83967daa3 | ||
|
|
e374d6f7d2 | ||
|
|
7723d291ce | ||
|
|
386fcd8276 | ||
|
|
10f5e5dd1d | ||
|
|
89281c4255 | ||
|
|
de7861abea | ||
|
|
25443d3319 | ||
|
|
be279ba864 | ||
|
|
5fe1cf9265 | ||
|
|
cdf7948575 | ||
|
|
b04b94e429 | ||
|
|
0ff19f66b6 | ||
|
|
bf583927c1 | ||
|
|
6ed8d8054f | ||
|
|
5c4a558486 | ||
|
|
2024ad1373 | ||
|
|
5c0185d5eb | ||
|
|
c9e4916d43 | ||
|
|
75d945f706 | ||
|
|
99ab2202a2 | ||
|
|
feaae052ac | ||
|
|
476e65e7dd | ||
|
|
24a5773637 | ||
|
|
0eb0e43d60 | ||
|
|
6f98962981 | ||
|
|
2b3b5c3ff2 | ||
|
|
eb5518092f | ||
|
|
1b10198d50 | ||
|
|
449d316174 | ||
|
|
9356756065 | ||
|
|
5b3e005f2b | ||
|
|
7654acc710 | ||
|
|
afb2901618 | ||
|
|
117fd51082 | ||
|
|
b66ba3ad4d | ||
|
|
cbe238b27d | ||
|
|
f814706fe2 | ||
|
|
fc508d01d7 | ||
|
|
ba880083be | ||
|
|
b657235870 | ||
|
|
132b78b317 | ||
|
|
25cb0528e2 | ||
|
|
e9acaa61cc | ||
|
|
218ce5658e | ||
|
|
08a17d7716 | ||
|
|
f9c43d50c6 | ||
|
|
e348b5b2a3 | ||
|
|
678b442f5e | ||
|
|
2470861c4a | ||
|
|
9e201126a9 | ||
|
|
5b67808d13 | ||
|
|
68e3bee684 | ||
|
|
4081003051 | ||
|
|
bd2b1bd8b7 | ||
|
|
5e033e4bef | ||
|
|
06ba9bc438 | ||
|
|
3339208e53 | ||
|
|
4fad52aef5 | ||
|
|
9664e379ea | ||
|
|
1e126996cb | ||
|
|
f4115a2977 | ||
|
|
c6fd201f90 | ||
|
|
6ed988dc5b | ||
|
|
f34a9c4f37 | ||
|
|
940c42f341 | ||
|
|
759cff5e7f | ||
|
|
5a626715d6 | ||
|
|
82d18f11a5 | ||
|
|
fb5fdb8c4e | ||
|
|
8ff3f305db | ||
|
|
06ceb9ef6f | ||
|
|
5a3b143127 | ||
|
|
d28add1a73 | ||
|
|
70d2465429 | ||
|
|
3cc5126267 | ||
|
|
26fde2d649 | ||
|
|
da2db85bfc | ||
|
|
ccdc719501 | ||
|
|
ac720f95df | ||
|
|
1913e9d739 | ||
|
|
a7be6c304d | ||
|
|
d89b86675c | ||
|
|
fb69f3da12 | ||
|
|
e1c0173e3d | ||
|
|
46fe59cf0a | ||
|
|
4a398185c2 | ||
|
|
122030269e | ||
|
|
5b436a883d | ||
|
|
a1c88de3c4 | ||
|
|
a6c6ce550e | ||
|
|
1af04987e0 | ||
|
|
ad31bacc1c | ||
|
|
bab8414666 | ||
|
|
0deffd37e7 | ||
|
|
a98c9ed311 | ||
|
|
12a04b4744 | ||
|
|
d97c08bada | ||
|
|
ce335ff342 | ||
|
|
cb16ac05a2 | ||
|
|
0917edb863 | ||
|
|
4d0df36e5e | ||
|
|
7b1861f5a9 | ||
|
|
29f6664ab0 | ||
|
|
690480e181 | ||
|
|
c156183666 | ||
|
|
d8e6d8d9a9 | ||
|
|
7591d2cda8 | ||
|
|
aa2e7a1685 | ||
|
|
9a683c3231 | ||
|
|
e5cebc091d | ||
|
|
15cdaa8294 | ||
|
|
32f2d25d58 | ||
|
|
a9dcc007e5 | ||
|
|
bf53712b7c | ||
|
|
2b4f60615f | ||
|
|
bbaad17e97 | ||
|
|
bc4c7c1406 | ||
|
|
e13b49cfd2 | ||
|
|
4d4a5d3adb | ||
|
|
7983de9f2a | ||
|
|
0034968919 | ||
|
|
6cec0a67eb | ||
|
|
f56fa41301 | ||
|
|
b1a1a7a238 | ||
|
|
8381790b0b | ||
|
|
65228c5ee8 | ||
|
|
b531a840e8 | ||
|
|
5a2e11878b | ||
|
|
fcc60a0aa3 | ||
|
|
fdbf1a66cd | ||
|
|
e8a513541f | ||
|
|
bc9f2cf882 | ||
|
|
1329b00ed5 | ||
|
|
a9c5b5b2d8 | ||
|
|
4b9508a9be | ||
|
|
dc1426ae31 | ||
|
|
72bfca2dc3 | ||
|
|
09f9f7eb3d | ||
|
|
9e71dd218b | ||
|
|
ee5350d675 | ||
|
|
9424aca5e2 | ||
|
|
8fa0950138 | ||
|
|
1315d7a3ef | ||
|
|
63d7c5c0c4 | ||
|
|
79c8e660f5 | ||
|
|
7b640cc0af | ||
|
|
1f2b4c7d5e | ||
|
|
441c3dc947 | ||
|
|
735b9fdd0e | ||
|
|
45458df1bf | ||
|
|
4004c6bc08 | ||
|
|
427babd3c1 | ||
|
|
2486dc24a1 | ||
|
|
3fa1074ea9 | ||
|
|
51d997c6fb | ||
|
|
b15cfbb706 | ||
|
|
4d9fafdd9a | ||
|
|
cdcd1b6639 | ||
|
|
9634eb65ad | ||
|
|
a52ba29f02 | ||
|
|
f5db7ad0e4 | ||
|
|
7497cbecd0 | ||
|
|
b14f6f040f | ||
|
|
89a1768496 | ||
|
|
57e7aa3e81 | ||
|
|
ff88ae9fd8 | ||
|
|
cddec19862 | ||
|
|
1bbd71cac3 | ||
|
|
a21351cd0f | ||
|
|
783956cb78 | ||
|
|
9094d3b99b | ||
|
|
718358314f | ||
|
|
f11cd689a5 | ||
|
|
3a3c06a5ff | ||
|
|
c48ced8c03 | ||
|
|
4ea22c11b3 | ||
|
|
a558c36853 | ||
|
|
1e14dcd59c | ||
|
|
1d909afe41 | ||
|
|
0d9ca68a94 | ||
|
|
105338ef67 | ||
|
|
8e88d9feae | ||
|
|
1309189523 | ||
|
|
a278ae1287 | ||
|
|
12dd09b32b | ||
|
|
0dfbb74c3c | ||
|
|
5429d85e8a | ||
|
|
82c1737d4b | ||
|
|
1a477f90f4 | ||
|
|
efbbf46a7a | ||
|
|
6b03ffc4bc | ||
|
|
7f53c27344 | ||
|
|
127a81a748 | ||
|
|
8f4298951a | ||
|
|
c68804d37e | ||
|
|
1189fa59b6 | ||
|
|
7070ea6f44 | ||
|
|
a3cdc70453 | ||
|
|
3e2df57fd1 | ||
|
|
2944cd6bed | ||
|
|
72c4dee12f | ||
|
|
2e85325d08 | ||
|
|
e2e3cc3dcf | ||
|
|
5ee3ce8b0d | ||
|
|
f4ef79def3 | ||
|
|
745d3afab5 | ||
|
|
9a4b4632c0 | ||
|
|
28e32d5aee | ||
|
|
c484e7d6d3 | ||
|
|
7845602907 | ||
|
|
b9c1a106d5 |
@@ -1,297 +0,0 @@
|
||||
/**
|
||||
* Security Dashboard Mobile Responsive E2E Tests
|
||||
* Test IDs: MR-01 through MR-10
|
||||
*
|
||||
* Tests mobile viewport (375x667), tablet viewport (768x1024),
|
||||
* touch targets, scrolling, and layout responsiveness.
|
||||
*/
|
||||
import { test, expect } from '@bgotink/playwright-coverage'
|
||||
|
||||
const base = process.env.CHARON_BASE_URL || 'http://localhost:8080'
|
||||
|
||||
test.describe('Security Dashboard Mobile (375x667)', () => {
|
||||
test.use({ viewport: { width: 375, height: 667 } })
|
||||
|
||||
test('MR-01: cards stack vertically on mobile', async ({ page }) => {
|
||||
await page.goto(`${base}/security`)
|
||||
|
||||
// Wait for page to load
|
||||
await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 })
|
||||
|
||||
// On mobile, grid should be single column
|
||||
const grid = page.locator('.grid.grid-cols-1')
|
||||
await expect(grid).toBeVisible()
|
||||
|
||||
// Get the computed grid-template-columns
|
||||
const cardsContainer = page.locator('.grid').first()
|
||||
const gridStyle = await cardsContainer.evaluate((el) => {
|
||||
const style = window.getComputedStyle(el)
|
||||
return style.gridTemplateColumns
|
||||
})
|
||||
|
||||
// Single column should have just one value (not multiple columns like "repeat(4, ...)")
|
||||
const columns = gridStyle.split(' ').filter((s) => s.trim().length > 0)
|
||||
expect(columns.length).toBeLessThanOrEqual(2) // Single column or flexible
|
||||
})
|
||||
|
||||
test('MR-04: toggle switches have accessible touch targets', async ({ page }) => {
|
||||
await page.goto(`${base}/security`)
|
||||
await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 })
|
||||
|
||||
// Check CrowdSec toggle
|
||||
const crowdsecToggle = page.getByTestId('toggle-crowdsec')
|
||||
const crowdsecBox = await crowdsecToggle.boundingBox()
|
||||
|
||||
// Touch target should be at least 24px (component) + padding
|
||||
// Most switches have a reasonable touch target
|
||||
expect(crowdsecBox).not.toBeNull()
|
||||
if (crowdsecBox) {
|
||||
expect(crowdsecBox.height).toBeGreaterThanOrEqual(20)
|
||||
expect(crowdsecBox.width).toBeGreaterThanOrEqual(35)
|
||||
}
|
||||
|
||||
// Check WAF toggle
|
||||
const wafToggle = page.getByTestId('toggle-waf')
|
||||
const wafBox = await wafToggle.boundingBox()
|
||||
expect(wafBox).not.toBeNull()
|
||||
if (wafBox) {
|
||||
expect(wafBox.height).toBeGreaterThanOrEqual(20)
|
||||
}
|
||||
})
|
||||
|
||||
test('MR-05: config buttons are tappable on mobile', async ({ page }) => {
|
||||
await page.goto(`${base}/security`)
|
||||
await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 })
|
||||
|
||||
// Find config/configure buttons
|
||||
const configButtons = page.locator('button:has-text("Config"), button:has-text("Configure")')
|
||||
const buttonCount = await configButtons.count()
|
||||
|
||||
expect(buttonCount).toBeGreaterThan(0)
|
||||
|
||||
// Check first config button has reasonable size
|
||||
const firstButton = configButtons.first()
|
||||
const box = await firstButton.boundingBox()
|
||||
expect(box).not.toBeNull()
|
||||
if (box) {
|
||||
expect(box.height).toBeGreaterThanOrEqual(28) // Minimum tap height
|
||||
}
|
||||
})
|
||||
|
||||
test('MR-06: page content is scrollable on mobile', async ({ page }) => {
|
||||
await page.goto(`${base}/security`)
|
||||
await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 })
|
||||
|
||||
// Check if page is scrollable (content height > viewport)
|
||||
const bodyHeight = await page.evaluate(() => document.body.scrollHeight)
|
||||
const viewportHeight = 667
|
||||
|
||||
// If content is taller than viewport, page should scroll
|
||||
if (bodyHeight > viewportHeight) {
|
||||
// Attempt to scroll down
|
||||
await page.evaluate(() => window.scrollBy(0, 200))
|
||||
const scrollY = await page.evaluate(() => window.scrollY)
|
||||
expect(scrollY).toBeGreaterThan(0)
|
||||
}
|
||||
})
|
||||
|
||||
test('MR-10: navigation is accessible on mobile', async ({ page }) => {
|
||||
await page.goto(`${base}/security`)
|
||||
await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 })
|
||||
|
||||
// On mobile, there should be some form of navigation
|
||||
// Check if sidebar or mobile menu toggle exists
|
||||
const sidebar = page.locator('nav, aside, [role="navigation"]')
|
||||
const sidebarCount = await sidebar.count()
|
||||
|
||||
// Navigation should exist in some form
|
||||
expect(sidebarCount).toBeGreaterThanOrEqual(0) // May be hidden on mobile
|
||||
})
|
||||
|
||||
test('MR-06b: overlay renders correctly on mobile', async ({ page }) => {
|
||||
await page.goto(`${base}/security`)
|
||||
await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 })
|
||||
|
||||
// Skip if Cerberus is disabled (toggles would be disabled)
|
||||
const cerberusDisabled = await page.locator('text=Cerberus Disabled').isVisible()
|
||||
if (cerberusDisabled) {
|
||||
test.skip()
|
||||
return
|
||||
}
|
||||
|
||||
// Trigger loading state by clicking a toggle
|
||||
const wafToggle = page.getByTestId('toggle-waf')
|
||||
const isDisabled = await wafToggle.isDisabled()
|
||||
|
||||
if (!isDisabled) {
|
||||
await wafToggle.click()
|
||||
|
||||
// Check for overlay (may appear briefly)
|
||||
// Use a short timeout since it might disappear quickly
|
||||
try {
|
||||
const overlay = page.locator('.fixed.inset-0')
|
||||
await overlay.waitFor({ state: 'visible', timeout: 2000 })
|
||||
|
||||
// If overlay appeared, verify it fits screen
|
||||
const box = await overlay.boundingBox()
|
||||
if (box) {
|
||||
expect(box.width).toBeLessThanOrEqual(375 + 10) // Allow small margin
|
||||
}
|
||||
} catch {
|
||||
// Overlay might have disappeared before we could check
|
||||
// This is acceptable for a fast operation
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Security Dashboard Tablet (768x1024)', () => {
|
||||
test.use({ viewport: { width: 768, height: 1024 } })
|
||||
|
||||
test('MR-02: cards show 2 columns on tablet', async ({ page }) => {
|
||||
await page.goto(`${base}/security`)
|
||||
await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 })
|
||||
|
||||
// On tablet (md breakpoint), should have md:grid-cols-2
|
||||
const grid = page.locator('.grid').first()
|
||||
await expect(grid).toBeVisible()
|
||||
|
||||
// Get computed style
|
||||
const gridStyle = await grid.evaluate((el) => {
|
||||
const style = window.getComputedStyle(el)
|
||||
return style.gridTemplateColumns
|
||||
})
|
||||
|
||||
// Should have 2 columns at md breakpoint
|
||||
const columns = gridStyle.split(' ').filter((s) => s.trim().length > 0 && s !== 'none')
|
||||
expect(columns.length).toBeGreaterThanOrEqual(2)
|
||||
})
|
||||
|
||||
test('MR-08: cards have proper spacing on tablet', async ({ page }) => {
|
||||
await page.goto(`${base}/security`)
|
||||
await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 })
|
||||
|
||||
// Check gap between cards
|
||||
const grid = page.locator('.grid.gap-6').first()
|
||||
const hasGap = await grid.isVisible()
|
||||
expect(hasGap).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Security Dashboard Desktop (1920x1080)', () => {
|
||||
test.use({ viewport: { width: 1920, height: 1080 } })
|
||||
|
||||
test('MR-03: cards show 4 columns on desktop', async ({ page }) => {
|
||||
await page.goto(`${base}/security`)
|
||||
await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 })
|
||||
|
||||
// On desktop (lg breakpoint), should have lg:grid-cols-4
|
||||
const grid = page.locator('.grid').first()
|
||||
await expect(grid).toBeVisible()
|
||||
|
||||
// Get computed style
|
||||
const gridStyle = await grid.evaluate((el) => {
|
||||
const style = window.getComputedStyle(el)
|
||||
return style.gridTemplateColumns
|
||||
})
|
||||
|
||||
// Should have 4 columns at lg breakpoint
|
||||
const columns = gridStyle.split(' ').filter((s) => s.trim().length > 0 && s !== 'none')
|
||||
expect(columns.length).toBeGreaterThanOrEqual(4)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Security Dashboard Layout Tests', () => {
|
||||
test('cards maintain correct order across viewports', async ({ page }) => {
|
||||
// Test on mobile
|
||||
await page.setViewportSize({ width: 375, height: 667 })
|
||||
await page.goto(`${base}/security`)
|
||||
await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 })
|
||||
|
||||
// Get card headings
|
||||
const getCardOrder = async () => {
|
||||
const headings = await page.locator('h3').allTextContents()
|
||||
return headings.filter((h) => ['CrowdSec', 'Access Control', 'Coraza', 'Rate Limiting'].includes(h))
|
||||
}
|
||||
|
||||
const mobileOrder = await getCardOrder()
|
||||
|
||||
// Test on tablet
|
||||
await page.setViewportSize({ width: 768, height: 1024 })
|
||||
await page.waitForTimeout(100) // Allow reflow
|
||||
const tabletOrder = await getCardOrder()
|
||||
|
||||
// Test on desktop
|
||||
await page.setViewportSize({ width: 1920, height: 1080 })
|
||||
await page.waitForTimeout(100) // Allow reflow
|
||||
const desktopOrder = await getCardOrder()
|
||||
|
||||
// Order should be consistent
|
||||
expect(mobileOrder).toEqual(tabletOrder)
|
||||
expect(tabletOrder).toEqual(desktopOrder)
|
||||
expect(desktopOrder).toEqual(['CrowdSec', 'Access Control', 'Coraza', 'Rate Limiting'])
|
||||
})
|
||||
|
||||
test('MR-09: all security cards are visible on scroll', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 375, height: 667 })
|
||||
await page.goto(`${base}/security`)
|
||||
await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 })
|
||||
|
||||
// Scroll to each card type
|
||||
const cardTypes = ['CrowdSec', 'Access Control', 'Coraza', 'Rate Limiting']
|
||||
|
||||
for (const cardType of cardTypes) {
|
||||
const card = page.locator(`h3:has-text("${cardType}")`)
|
||||
await card.scrollIntoViewIfNeeded()
|
||||
await expect(card).toBeVisible()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Security Dashboard Interaction Tests', () => {
|
||||
test.use({ viewport: { width: 375, height: 667 } })
|
||||
|
||||
test('MR-07: config buttons navigate correctly on mobile', async ({ page }) => {
|
||||
await page.goto(`${base}/security`)
|
||||
await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 })
|
||||
|
||||
// Skip if Cerberus disabled
|
||||
const cerberusDisabled = await page.locator('text=Cerberus Disabled').isVisible()
|
||||
if (cerberusDisabled) {
|
||||
test.skip()
|
||||
return
|
||||
}
|
||||
|
||||
// Find and click WAF Configure button
|
||||
const configureButton = page.locator('button:has-text("Configure")').first()
|
||||
|
||||
if (await configureButton.isVisible()) {
|
||||
await configureButton.click()
|
||||
|
||||
// Should navigate to a config page
|
||||
await page.waitForTimeout(500)
|
||||
const url = page.url()
|
||||
|
||||
// URL should include security/waf or security/rate-limiting etc
|
||||
expect(url).toMatch(/security\/(waf|rate-limiting|access-lists|crowdsec)/i)
|
||||
}
|
||||
})
|
||||
|
||||
test('documentation button works on mobile', async ({ page }) => {
|
||||
await page.goto(`${base}/security`)
|
||||
await page.waitForSelector('[data-testid="toggle-crowdsec"]', { timeout: 10000 })
|
||||
|
||||
// Find documentation button
|
||||
const docButton = page.locator('button:has-text("Documentation"), a:has-text("Documentation")').first()
|
||||
|
||||
if (await docButton.isVisible()) {
|
||||
// Check it has correct external link behavior
|
||||
const href = await docButton.getAttribute('href')
|
||||
|
||||
// Should open external docs
|
||||
if (href) {
|
||||
expect(href).toContain('wikid82.github.io')
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,34 +0,0 @@
|
||||
import { test, expect } from '@bgotink/playwright-coverage'
|
||||
|
||||
const base = process.env.CHARON_BASE_URL || 'http://localhost:8080'
|
||||
|
||||
// Hit an API route inside /api/v1 to ensure Cerberus middleware executes.
|
||||
const targetPath = '/api/v1/system/my-ip'
|
||||
|
||||
test.describe('WAF blocking and monitoring', () => {
|
||||
test('blocks malicious query when mode=block', async ({ request }) => {
|
||||
// Use literal '<script>' to trigger naive WAF check
|
||||
const res = await request.get(`${base}${targetPath}?<script>=x`)
|
||||
expect([400, 401]).toContain(res.status())
|
||||
// When WAF runs before auth, expect 400; if auth runs first, we still validate that the server rejects
|
||||
if (res.status() === 400) {
|
||||
const body = await res.json()
|
||||
expect(body?.error).toMatch(/WAF: suspicious payload/i)
|
||||
}
|
||||
})
|
||||
|
||||
test('does not block when mode=monitor (returns 401 due to auth)', async ({ request }) => {
|
||||
const res = await request.get(`${base}${targetPath}?safe=yes`)
|
||||
// Unauthenticated → expect 401, not 400; proves WAF did not block
|
||||
expect([401, 403]).toContain(res.status())
|
||||
})
|
||||
|
||||
test('metrics endpoint exposes Prometheus counters', async ({ request }) => {
|
||||
const res = await request.get(`${base}/metrics`)
|
||||
expect(res.status()).toBe(200)
|
||||
const text = await res.text()
|
||||
expect(text).toContain('charon_waf_requests_total')
|
||||
expect(text).toContain('charon_waf_blocked_total')
|
||||
expect(text).toContain('charon_waf_monitored_total')
|
||||
})
|
||||
})
|
||||
@@ -1,26 +0,0 @@
|
||||
import { test, expect } from '@bgotink/playwright-coverage'
|
||||
|
||||
test.describe('Login - smoke', () => {
|
||||
test('renders and has no console errors on load', async ({ page }) => {
|
||||
const consoleErrors: string[] = []
|
||||
|
||||
page.on('console', msg => {
|
||||
if (msg.type() === 'error') {
|
||||
consoleErrors.push(msg.text())
|
||||
}
|
||||
})
|
||||
|
||||
await page.goto('/login', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
await expect(page).toHaveURL(/\/login(?:\?|$)/)
|
||||
|
||||
const emailInput = page.getByRole('textbox', { name: /email/i })
|
||||
const passwordInput = page.getByLabel(/password/i)
|
||||
|
||||
await expect(emailInput).toBeVisible()
|
||||
await expect(passwordInput).toBeVisible()
|
||||
await expect(page.getByRole('button', { name: /sign in/i })).toBeVisible()
|
||||
|
||||
expect(consoleErrors, 'Console errors during /login load').toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -94,7 +94,7 @@ Configure the application via `docker-compose.yml`:
|
||||
| `CHARON_ENV` | `production` | Set to `development` for verbose logging (`CPM_ENV` supported for backward compatibility). |
|
||||
| `CHARON_HTTP_PORT` | `8080` | Port for the Web UI (`CPM_HTTP_PORT` supported for backward compatibility). |
|
||||
| `CHARON_DB_PATH` | `/app/data/charon.db` | Path to the SQLite database (`CPM_DB_PATH` supported for backward compatibility). |
|
||||
| `CHARON_CADDY_ADMIN_API` | `http://localhost:2019` | Internal URL for Caddy API (`CPM_CADDY_ADMIN_API` supported for backward compatibility). |
|
||||
| `CHARON_CADDY_ADMIN_API` | `http://localhost:2019` | Internal URL for Caddy API (`CPM_CADDY_ADMIN_API` supported for backward compatibility). Must resolve to an internal allowlisted host on port `2019`. |
|
||||
| `CHARON_CADDY_CONFIG_ROOT` | `/config` | Path to Caddy autosave configuration directory. |
|
||||
| `CHARON_CADDY_LOG_DIR` | `/var/log/caddy` | Directory for Caddy access logs. |
|
||||
| `CHARON_CROWDSEC_LOG_DIR` | `/var/log/crowdsec` | Directory for CrowdSec logs. |
|
||||
@@ -218,6 +218,8 @@ environment:
|
||||
- CPM_CADDY_ADMIN_API=http://your-caddy-host:2019
|
||||
```
|
||||
|
||||
If using a non-localhost internal hostname, add it to `CHARON_SSRF_INTERNAL_HOST_ALLOWLIST`.
|
||||
|
||||
**Warning**: Charon will replace Caddy's entire configuration. Backup first!
|
||||
|
||||
## Performance Tuning
|
||||
|
||||
@@ -32,6 +32,8 @@ services:
|
||||
#- CPM_SECURITY_RATELIMIT_ENABLED=false
|
||||
#- CPM_SECURITY_ACL_ENABLED=false
|
||||
- FEATURE_CERBERUS_ENABLED=true
|
||||
# Docker socket group access: copy docker-compose.override.example.yml
|
||||
# to docker-compose.override.yml and set your host's docker GID.
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro # For local container discovery
|
||||
- crowdsec_data:/app/data/crowdsec
|
||||
|
||||
@@ -27,6 +27,8 @@ services:
|
||||
- FEATURE_CERBERUS_ENABLED=true
|
||||
# Emergency "break-glass" token for security reset when ACL blocks access
|
||||
- CHARON_EMERGENCY_TOKEN=03e4682c1164f0c1cb8e17c99bd1a2d9156b59824dde41af3bb67c513e5c5e92
|
||||
# Docker socket group access: copy docker-compose.override.example.yml
|
||||
# to docker-compose.override.yml and set your host's docker GID.
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
cap_add:
|
||||
@@ -45,7 +47,7 @@ services:
|
||||
# - <PATH_TO_YOUR_CADDYFILE>:/import/Caddyfile:ro
|
||||
# - <PATH_TO_YOUR_SITES_DIR>:/import/sites:ro # If your Caddyfile imports other files
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://localhost:8080/api/v1/health || exit 1"]
|
||||
test: ["CMD-SHELL", "wget -qO /dev/null http://localhost:8080/api/v1/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
26
.docker/compose/docker-compose.override.example.yml
Normal file
26
.docker/compose/docker-compose.override.example.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
# Docker Compose override — copy to docker-compose.override.yml to activate.
|
||||
#
|
||||
# Use case: grant the container access to the host Docker socket so that
|
||||
# Charon can discover running containers.
|
||||
#
|
||||
# 1. cp docker-compose.override.example.yml docker-compose.override.yml
|
||||
# 2. Uncomment the service that matches your compose file:
|
||||
# - "charon" for docker-compose.local.yml
|
||||
# - "app" for docker-compose.dev.yml
|
||||
# 3. Replace <GID> with the output of: stat -c '%g' /var/run/docker.sock
|
||||
# 4. docker compose up -d
|
||||
|
||||
services:
|
||||
# Uncomment for docker-compose.local.yml
|
||||
charon:
|
||||
group_add:
|
||||
- "<GID>" # e.g. "988" — run: stat -c '%g' /var/run/docker.sock
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
|
||||
# Uncomment for docker-compose.dev.yml
|
||||
app:
|
||||
group_add:
|
||||
- "<GID>" # e.g. "988" — run: stat -c '%g' /var/run/docker.sock
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
@@ -85,8 +85,9 @@ services:
|
||||
- playwright_data:/app/data
|
||||
- playwright_caddy_data:/data
|
||||
- playwright_caddy_config:/config
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro # For container discovery in tests
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://localhost:8080/api/v1/health"]
|
||||
test: ["CMD-SHELL", "wget -qO /dev/null http://localhost:8080/api/v1/health || exit 1"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 12
|
||||
@@ -111,6 +112,7 @@ services:
|
||||
volumes:
|
||||
- playwright_crowdsec_data:/var/lib/crowdsec/data
|
||||
- playwright_crowdsec_config:/etc/crowdsec
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro # For container discovery in tests
|
||||
healthcheck:
|
||||
test: ["CMD", "cscli", "version"]
|
||||
interval: 10s
|
||||
|
||||
@@ -49,8 +49,10 @@ services:
|
||||
# True tmpfs for E2E test data - fresh on every run, in-memory only
|
||||
# mode=1777 allows any user to write (container runs as non-root)
|
||||
- /app/data:size=100M,mode=1777
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro # For container discovery in tests
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://localhost:8080/api/v1/health || exit 1"]
|
||||
test: ["CMD-SHELL", "wget -qO /dev/null http://localhost:8080/api/v1/health || exit 1"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
@@ -52,7 +52,7 @@ services:
|
||||
# - ./my-existing-Caddyfile:/import/Caddyfile:ro
|
||||
# - ./sites:/import/sites:ro # If your Caddyfile imports other files
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://localhost:8080/api/v1/health || exit 1"]
|
||||
test: ["CMD-SHELL", "wget -qO /dev/null http://localhost:8080/api/v1/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
@@ -27,30 +27,24 @@ get_group_by_gid() {
|
||||
}
|
||||
|
||||
create_group_with_gid() {
|
||||
local gid="$1"
|
||||
local name="$2"
|
||||
|
||||
if command -v addgroup >/dev/null 2>&1; then
|
||||
addgroup -g "$gid" "$name" 2>/dev/null || true
|
||||
addgroup -g "$1" "$2" 2>/dev/null || true
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v groupadd >/dev/null 2>&1; then
|
||||
groupadd -g "$gid" "$name" 2>/dev/null || true
|
||||
groupadd -g "$1" "$2" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
add_user_to_group() {
|
||||
local user="$1"
|
||||
local group="$2"
|
||||
|
||||
if command -v addgroup >/dev/null 2>&1; then
|
||||
addgroup "$user" "$group" 2>/dev/null || true
|
||||
addgroup "$1" "$2" 2>/dev/null || true
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v usermod >/dev/null 2>&1; then
|
||||
usermod -aG "$group" "$user" 2>/dev/null || true
|
||||
usermod -aG "$2" "$1" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -142,8 +136,15 @@ if [ -S "/var/run/docker.sock" ] && is_root; then
|
||||
fi
|
||||
fi
|
||||
elif [ -S "/var/run/docker.sock" ]; then
|
||||
echo "Note: Docker socket mounted but container is running non-root; skipping docker.sock group setup."
|
||||
echo " If Docker discovery is needed, run with matching group permissions (e.g., --group-add)"
|
||||
DOCKER_SOCK_GID=$(stat -c '%g' /var/run/docker.sock 2>/dev/null || echo "unknown")
|
||||
echo "Note: Docker socket mounted (GID=$DOCKER_SOCK_GID) but container is running non-root; skipping docker.sock group setup."
|
||||
echo " If Docker discovery is needed, add 'group_add: [\"$DOCKER_SOCK_GID\"]' to your compose service."
|
||||
if [ "$DOCKER_SOCK_GID" = "0" ]; then
|
||||
if [ "${ALLOW_DOCKER_SOCK_GID_0:-false}" != "true" ]; then
|
||||
echo "⚠️ WARNING: Docker socket GID is 0 (root group). group_add: [\"0\"] grants root-group access."
|
||||
echo " Set ALLOW_DOCKER_SOCK_GID_0=true to acknowledge this risk."
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Note: Docker socket not found. Docker container discovery will be unavailable."
|
||||
fi
|
||||
@@ -191,7 +192,7 @@ if command -v cscli >/dev/null; then
|
||||
echo "Initializing persistent CrowdSec configuration..."
|
||||
|
||||
# Check if .dist has content
|
||||
if [ -d "/etc/crowdsec.dist" ] && [ -n "$(ls -A /etc/crowdsec.dist 2>/dev/null)" ]; then
|
||||
if [ -d "/etc/crowdsec.dist" ] && find /etc/crowdsec.dist -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null | grep -q .; then
|
||||
echo "Copying config from /etc/crowdsec.dist..."
|
||||
if ! cp -r /etc/crowdsec.dist/* "$CS_CONFIG_DIR/"; then
|
||||
echo "ERROR: Failed to copy config from /etc/crowdsec.dist"
|
||||
@@ -208,7 +209,7 @@ if command -v cscli >/dev/null; then
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Successfully initialized config from .dist directory"
|
||||
elif [ -d "/etc/crowdsec" ] && [ ! -L "/etc/crowdsec" ] && [ -n "$(ls -A /etc/crowdsec 2>/dev/null)" ]; then
|
||||
elif [ -d "/etc/crowdsec" ] && [ ! -L "/etc/crowdsec" ] && find /etc/crowdsec -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null | grep -q .; then
|
||||
echo "Copying config from /etc/crowdsec (fallback)..."
|
||||
if ! cp -r /etc/crowdsec/* "$CS_CONFIG_DIR/"; then
|
||||
echo "ERROR: Failed to copy config from /etc/crowdsec (fallback)"
|
||||
@@ -248,7 +249,7 @@ if command -v cscli >/dev/null; then
|
||||
echo "Expected: /etc/crowdsec -> /app/data/crowdsec/config"
|
||||
echo "This indicates a critical build-time issue. Symlink must be created at build time as root."
|
||||
echo "DEBUG: Directory check:"
|
||||
ls -la /etc/ | grep crowdsec || echo " (no crowdsec entry found)"
|
||||
find /etc -mindepth 1 -maxdepth 1 -name '*crowdsec*' -exec ls -ld {} \; 2>/dev/null || echo " (no crowdsec entry found)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -364,7 +365,7 @@ echo "Caddy started (PID: $CADDY_PID)"
|
||||
echo "Waiting for Caddy admin API..."
|
||||
i=1
|
||||
while [ "$i" -le 30 ]; do
|
||||
if curl -sf http://127.0.0.1:2019/config/ > /dev/null 2>&1; then
|
||||
if wget -qO /dev/null http://127.0.0.1:2019/config/ 2>/dev/null; then
|
||||
echo "Caddy is ready!"
|
||||
break
|
||||
fi
|
||||
|
||||
@@ -9,13 +9,12 @@
|
||||
.git/
|
||||
.gitignore
|
||||
.github/
|
||||
.pre-commit-config.yaml
|
||||
codecov.yml
|
||||
.goreleaser.yaml
|
||||
.sourcery.yml
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Python (pre-commit, tooling)
|
||||
# Python (tooling)
|
||||
# -----------------------------------------------------------------------------
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
23
.github/agents/Backend_Dev.agent.md
vendored
23
.github/agents/Backend_Dev.agent.md
vendored
@@ -2,9 +2,10 @@
|
||||
name: 'Backend Dev'
|
||||
description: 'Senior Go Engineer focused on high-performance, secure backend implementation.'
|
||||
argument-hint: 'The specific backend task from the Plan (e.g., "Implement ProxyHost CRUD endpoints")'
|
||||
tools: vscode/extensions, vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/openSimpleBrowser, vscode/runCommand, vscode/askQuestions, vscode/vscodeAPI, execute, read, agent, 'github/*', 'github/*', 'io.github.goreleaser/mcp/*', 'trivy-mcp/*', edit, search, web, 'github/*', 'playwright/*', 'pylance-mcp-server/*', todo, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment, 'gopls/*'
|
||||
tools: vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/runCommand, vscode/vscodeAPI, vscode/extensions, vscode/askQuestions, execute, read, edit, search, web, browser, github/add_comment_to_pending_review, github/add_issue_comment, github/add_reply_to_pull_request_comment, github/assign_copilot_to_issue, github/create_branch, github/create_or_update_file, github/create_pull_request, github/create_pull_request_with_copilot, github/create_repository, github/delete_file, github/fork_repository, github/get_commit, github/get_copilot_job_status, github/get_file_contents, github/get_label, github/get_latest_release, github/get_me, github/get_release_by_tag, github/get_tag, github/get_team_members, github/get_teams, github/issue_read, github/issue_write, github/list_branches, github/list_commits, github/list_issue_types, github/list_issues, github/list_pull_requests, github/list_releases, github/list_tags, github/merge_pull_request, github/pull_request_read, github/pull_request_review_write, github/push_files, github/request_copilot_review, github/search_code, github/search_issues, github/search_pull_requests, github/search_repositories, github/search_users, github/sub_issue_write, github/update_pull_request, github/update_pull_request_branch, playwright/*, github/*, io.github.goreleaser/mcp/*, mcp-refactor-typescript/*, microsoftdocs/mcp/*, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/pullRequestStatusChecks, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment, todo
|
||||
|
||||
|
||||
|
||||
model: GPT-5.3-Codex (copilot)
|
||||
target: vscode
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
@@ -15,6 +16,9 @@ Your priority is writing code that is clean, tested, and secure by default.
|
||||
|
||||
<context>
|
||||
|
||||
- **Governance**: When this agent file conflicts with canonical instruction
|
||||
files (`.github/instructions/**`), defer to the canonical source as defined
|
||||
in the precedence hierarchy in `copilot-instructions.md`.
|
||||
- **MANDATORY**: Read all relevant instructions in `.github/instructions/` for the specific task before starting.
|
||||
- **Project**: Charon (Self-hosted Reverse Proxy)
|
||||
- **Stack**: Go 1.22+, Gin, GORM, SQLite.
|
||||
@@ -41,14 +45,21 @@ Your priority is writing code that is clean, tested, and secure by default.
|
||||
- Define the structs in `internal/models` to fix compilation errors.
|
||||
- **Step 3 (The Logic)**:
|
||||
- Implement the handler in `internal/api/handlers`.
|
||||
- **Step 4 (The Green Light)**:
|
||||
- **Step 4 (Lint and Format)**:
|
||||
- Run `lefthook run pre-commit` to ensure code quality.
|
||||
- **Step 5 (The Green Light)**:
|
||||
- Run `go test ./...`.
|
||||
- **CRITICAL**: If it fails, fix the *Code*, NOT the *Test* (unless the test was wrong about the contract).
|
||||
|
||||
3. **Verification (Definition of Done)**:
|
||||
- Run `go mod tidy`.
|
||||
- Run `go fmt ./...`.
|
||||
- Run `go test ./...` to ensure no regressions.
|
||||
- Run `go test ./...` to ensure no regressions.
|
||||
- **Conditional GORM Gate**: If task changes include model/database-related
|
||||
files (`backend/internal/models/**`, GORM query logic, migrations), run
|
||||
GORM scanner in check mode and treat CRITICAL/HIGH findings as blocking:
|
||||
- Run: `lefthook run pre-commit` (which includes manual gorm-security-scan) OR `./scripts/scan-gorm-security.sh --check`
|
||||
- Policy: Process-blocking gate even while automation is manual stage
|
||||
- **Local Patch Coverage Preflight (MANDATORY)**: Run VS Code task `Test: Local Patch Report` or `bash scripts/local-patch-report.sh` before backend coverage runs.
|
||||
- Ensure artifacts exist: `test-results/local-patch-report.md` and `test-results/local-patch-report.json`.
|
||||
- Use the file-level coverage gap list to target tests before final coverage validation.
|
||||
@@ -58,9 +69,9 @@ Your priority is writing code that is clean, tested, and secure by default.
|
||||
- **Manual Script**: Execute `/projects/Charon/scripts/go-test-coverage.sh` from the root directory
|
||||
- **Minimum**: 85% coverage (configured via `CHARON_MIN_COVERAGE` or `CPM_MIN_COVERAGE`)
|
||||
- **Critical**: If coverage drops below threshold, write additional tests immediately. Do not skip this step.
|
||||
- **Why**: Coverage tests are in manual stage of pre-commit for performance. You MUST run them via VS Code tasks or scripts before completing your task.
|
||||
- **Why**: Coverage tests are in manual stage of lefthook for performance. You MUST run them via VS Code tasks or scripts before completing your task.
|
||||
- Ensure coverage goals are met as well as all tests pass. Just because Tests pass does not mean you are done. Goal Coverage Needs to be met even if the tests to get us there are outside the scope of your task. At this point, your task is to maintain coverage goal and all tests pass because we cannot commit changes if they fail.
|
||||
- Run `pre-commit run --all-files` as final check (this runs fast hooks only; coverage was verified above).
|
||||
- Run `lefthook run pre-commit` as final check (this runs fast hooks only; coverage was verified above).
|
||||
</workflow>
|
||||
|
||||
<constraints>
|
||||
|
||||
4
.github/agents/DevOps.agent.md
vendored
4
.github/agents/DevOps.agent.md
vendored
@@ -2,9 +2,9 @@
|
||||
name: 'DevOps'
|
||||
description: 'DevOps specialist for CI/CD pipelines, deployment debugging, and GitOps workflows focused on making deployments boring and reliable'
|
||||
argument-hint: 'The CI/CD or infrastructure task (e.g., "Debug failing GitHub Action workflow")'
|
||||
tools: vscode/extensions, vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/openSimpleBrowser, vscode/runCommand, vscode/askQuestions, vscode/vscodeAPI, execute, read, agent, 'github/*', 'github/*', 'io.github.goreleaser/mcp/*', 'trivy-mcp/*', edit, search, web, 'github/*', 'playwright/*', 'pylance-mcp-server/*', todo, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment, 'gopls/*'
|
||||
tools: vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/runCommand, vscode/vscodeAPI, vscode/extensions, vscode/askQuestions, execute, read, edit, search, web, browser, github/add_comment_to_pending_review, github/add_issue_comment, github/add_reply_to_pull_request_comment, github/assign_copilot_to_issue, github/create_branch, github/create_or_update_file, github/create_pull_request, github/create_pull_request_with_copilot, github/create_repository, github/delete_file, github/fork_repository, github/get_commit, github/get_copilot_job_status, github/get_file_contents, github/get_label, github/get_latest_release, github/get_me, github/get_release_by_tag, github/get_tag, github/get_team_members, github/get_teams, github/issue_read, github/issue_write, github/list_branches, github/list_commits, github/list_issue_types, github/list_issues, github/list_pull_requests, github/list_releases, github/list_tags, github/merge_pull_request, github/pull_request_read, github/pull_request_review_write, github/push_files, github/request_copilot_review, github/search_code, github/search_issues, github/search_pull_requests, github/search_repositories, github/search_users, github/sub_issue_write, github/update_pull_request, github/update_pull_request_branch, playwright/*, github/*, io.github.goreleaser/mcp/*, mcp-refactor-typescript/*, microsoftdocs/mcp/*, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/pullRequestStatusChecks, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment, todo
|
||||
|
||||
|
||||
model: GPT-5.3-Codex (copilot)
|
||||
target: vscode
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
|
||||
4
.github/agents/Doc_Writer.agent.md
vendored
4
.github/agents/Doc_Writer.agent.md
vendored
@@ -2,9 +2,9 @@
|
||||
name: 'Docs Writer'
|
||||
description: 'User Advocate and Writer focused on creating simple, layman-friendly documentation.'
|
||||
argument-hint: 'The feature to document (e.g., "Write the guide for the new Real-Time Logs")'
|
||||
tools: vscode/extensions, vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/openSimpleBrowser, vscode/runCommand, vscode/askQuestions, vscode/vscodeAPI, execute, read, agent, 'github/*', 'github/*', 'io.github.goreleaser/mcp/*', 'trivy-mcp/*', edit, search, web, 'github/*', 'playwright/*', 'pylance-mcp-server/*', todo, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment, 'gopls/*'
|
||||
tools: vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/runCommand, vscode/vscodeAPI, vscode/extensions, vscode/askQuestions, execute, read, edit, search, web, browser, github/add_comment_to_pending_review, github/add_issue_comment, github/add_reply_to_pull_request_comment, github/assign_copilot_to_issue, github/create_branch, github/create_or_update_file, github/create_pull_request, github/create_pull_request_with_copilot, github/create_repository, github/delete_file, github/fork_repository, github/get_commit, github/get_copilot_job_status, github/get_file_contents, github/get_label, github/get_latest_release, github/get_me, github/get_release_by_tag, github/get_tag, github/get_team_members, github/get_teams, github/issue_read, github/issue_write, github/list_branches, github/list_commits, github/list_issue_types, github/list_issues, github/list_pull_requests, github/list_releases, github/list_tags, github/merge_pull_request, github/pull_request_read, github/pull_request_review_write, github/push_files, github/request_copilot_review, github/search_code, github/search_issues, github/search_pull_requests, github/search_repositories, github/search_users, github/sub_issue_write, github/update_pull_request, github/update_pull_request_branch, playwright/*, github/*, io.github.goreleaser/mcp/*, mcp-refactor-typescript/*, microsoftdocs/mcp/*, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/pullRequestStatusChecks, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment, todo
|
||||
|
||||
|
||||
model: GPT-5.3-Codex (copilot)
|
||||
target: vscode
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
|
||||
8
.github/agents/Frontend_Dev.agent.md
vendored
8
.github/agents/Frontend_Dev.agent.md
vendored
@@ -2,9 +2,10 @@
|
||||
name: 'Frontend Dev'
|
||||
description: 'Senior React/TypeScript Engineer for frontend implementation.'
|
||||
argument-hint: 'The frontend feature or component to implement (e.g., "Implement the Real-Time Logs dashboard component")'
|
||||
tools: vscode/extensions, vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/openSimpleBrowser, vscode/runCommand, vscode/askQuestions, vscode/vscodeAPI, execute, read, agent, 'github/*', 'github/*', 'io.github.goreleaser/mcp/*', 'trivy-mcp/*', edit, search, web, 'github/*', 'playwright/*', 'pylance-mcp-server/*', todo, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment, 'gopls/*'
|
||||
tools: vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/runCommand, vscode/vscodeAPI, vscode/extensions, vscode/askQuestions, execute, read, edit, search, web, browser, github/add_comment_to_pending_review, github/add_issue_comment, github/add_reply_to_pull_request_comment, github/assign_copilot_to_issue, github/create_branch, github/create_or_update_file, github/create_pull_request, github/create_pull_request_with_copilot, github/create_repository, github/delete_file, github/fork_repository, github/get_commit, github/get_copilot_job_status, github/get_file_contents, github/get_label, github/get_latest_release, github/get_me, github/get_release_by_tag, github/get_tag, github/get_team_members, github/get_teams, github/issue_read, github/issue_write, github/list_branches, github/list_commits, github/list_issue_types, github/list_issues, github/list_pull_requests, github/list_releases, github/list_tags, github/merge_pull_request, github/pull_request_read, github/pull_request_review_write, github/push_files, github/request_copilot_review, github/search_code, github/search_issues, github/search_pull_requests, github/search_repositories, github/search_users, github/sub_issue_write, github/update_pull_request, github/update_pull_request_branch, playwright/*, github/*, io.github.goreleaser/mcp/*, mcp-refactor-typescript/*, microsoftdocs/mcp/*, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/pullRequestStatusChecks, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment, todo
|
||||
|
||||
|
||||
|
||||
model: GPT-5.3-Codex (copilot)
|
||||
target: vscode
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
@@ -48,8 +49,7 @@ You are a SENIOR REACT/TYPESCRIPT ENGINEER with deep expertise in:
|
||||
- Run tests with `npm test` in `frontend/` directory
|
||||
|
||||
4. **Quality Checks**:
|
||||
- Run `npm run lint` to check for linting issues
|
||||
- Run `npm run typecheck` for TypeScript errors
|
||||
- Run `lefthook run pre-commit` to ensure linting and formatting
|
||||
- Ensure accessibility with proper ARIA attributes
|
||||
</workflow>
|
||||
|
||||
|
||||
43
.github/agents/Management.agent.md
vendored
43
.github/agents/Management.agent.md
vendored
@@ -3,9 +3,9 @@ name: 'Management'
|
||||
description: 'Engineering Director. Delegates ALL research and execution. DO NOT ask it to debug code directly.'
|
||||
argument-hint: 'The high-level goal (e.g., "Build the new Proxy Host Dashboard widget")'
|
||||
|
||||
tools: vscode/extensions, vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/openIntegratedBrowser, vscode/runCommand, vscode/askQuestions, vscode/vscodeAPI, execute, read, agent, 'github/*', 'github/*', 'io.github.goreleaser/mcp/*', 'trivy-mcp/*', edit, search, web, 'github/*', 'gopls/*', 'playwright/*', 'pylance-mcp-server/*', todo, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment
|
||||
tools: vscode/extensions, vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/runCommand, vscode/vscodeAPI, vscode/askQuestions, execute, read, agent, edit, search, web, 'github/*', 'playwright/*', 'github/*', 'github/*', 'io.github.goreleaser/mcp/*', 'mcp-refactor-typescript/*', 'microsoftdocs/mcp/*', browser, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/pullRequestStatusChecks, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment, todo
|
||||
|
||||
|
||||
model: GPT-5.3-Codex (copilot)
|
||||
target: vscode
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
@@ -18,19 +18,22 @@ You are "lazy" in the smartest way possible. You never do what a subordinate can
|
||||
|
||||
1. **Initialize**: ALWAYS read `.github/instructions/copilot-instructions.md` first to load global project rules.
|
||||
2. **MANDATORY**: Read all relevant instructions in `.github/instructions/**` for the specific task before starting.
|
||||
3. **Team Roster**:
|
||||
3. **Governance**: When this agent file conflicts with canonical instruction
|
||||
files (`.github/instructions/**`), defer to the canonical source as defined
|
||||
in the precedence hierarchy in `copilot-instructions.md`.
|
||||
4. **Team Roster**:
|
||||
- `Planning`: The Architect. (Delegate research & planning here).
|
||||
- `Supervisor`: The Senior Advisor. (Delegate plan review here).
|
||||
- `Backend_Dev`: The Engineer. (Delegate Go implementation here).
|
||||
- `Frontend_Dev`: The Designer. (Delegate React implementation here).
|
||||
- `QA_Security`: The Auditor. (Delegate verification and testing here).
|
||||
- `Docs_Writer`: The Scribe. (Delegate docs here).
|
||||
- `Backend Dev`: The Engineer. (Delegate Go implementation here).
|
||||
- `Frontend Dev`: The Designer. (Delegate React implementation here).
|
||||
- `QA Security`: The Auditor. (Delegate verification and testing here).
|
||||
- `Docs Writer`: The Scribe. (Delegate docs here).
|
||||
- `DevOps`: The Packager. (Delegate CI/CD and infrastructure here).
|
||||
- `Playwright_Dev`: The E2E Specialist. (Delegate Playwright test creation and maintenance here).
|
||||
4. **Parallel Execution**:
|
||||
- `Playwright Dev`: The E2E Specialist. (Delegate Playwright test creation and maintenance here).
|
||||
5. **Parallel Execution**:
|
||||
- You may delegate to `runSubagent` multiple times in parallel if tasks are independent. The only exception is `QA_Security`, which must run last as this validates the entire codebase after all changes.
|
||||
5. **Implementation Choices**:
|
||||
- When faced with multiple implementation options, ALWAYS choose the "Prroper" fix over a "Quick" fix. This ensures long-term maintainability and saves double work. The "Quick" fix will only cause more work later when the "Proper" fix is eventually needed.
|
||||
6. **Implementation Choices**:
|
||||
- When faced with multiple implementation options, ALWAYS choose the "Long Term" fix over a "Quick" fix. This ensures long-term maintainability and saves double work. The "Quick" fix will only cause more work later when the "Long Term" fix is eventually needed.
|
||||
</global_context>
|
||||
|
||||
<workflow>
|
||||
@@ -40,7 +43,7 @@ You are "lazy" in the smartest way possible. You never do what a subordinate can
|
||||
- **Identify Goal**: Understand the user's request.
|
||||
- **STOP**: Do not look at the code. Do not run `list_dir`. No code is to be changed or implemented until there is a fundamentally sound plan of action that has been approved by the user.
|
||||
- **Action**: Immediately call `Planning` subagent.
|
||||
- *Prompt*: "Research the necessary files for '{user_request}' and write a comprehensive plan detailing as many specifics as possible to `docs/plans/current_spec.md`. Be an artist with directions and discriptions. Include file names, function names, and component names wherever possible. Break the plan into phases based on the least amount of requests. Include a PR Slicing Strategy section that decides whether to split work into multiple PRs and, when split, defines PR-1/PR-2/PR-3 scope, dependencies, and acceptance criteria. Review and suggest updaetes to `.gitignore`, `codecov.yml`, `.dockerignore`, and `Dockerfile` if necessary. Return only when the plan is complete."
|
||||
- *Prompt*: "Research the necessary files for '{user_request}' and write a comprehensive plan detailing as many specifics as possible to `docs/plans/current_spec.md`. Be an artist with directions and discriptions. Include file names, function names, and component names wherever possible. Break the plan into phases based on the least amount of requests. Include a Commit Slicing Strategy section that decides whether to split work into multiple PRs and, when split, defines PR-1/PR-2/PR-3 scope, dependencies, and acceptance criteria. Review and suggest updaetes to `.gitignore`, `codecov.yml`, `.dockerignore`, and `Dockerfile` if necessary. Return only when the plan is complete."
|
||||
- **Task Specifics**:
|
||||
- If the task is to just run tests or audits, there is no need for a plan. Directly call `QA_Security` to perform the tests and write the report. If issues are found, return to `Planning` for a remediation plan and delegate the fixes to the corresponding subagents.
|
||||
|
||||
@@ -56,7 +59,7 @@ You are "lazy" in the smartest way possible. You never do what a subordinate can
|
||||
- **Ask**: "Plan created. Shall I authorize the construction?"
|
||||
|
||||
4. **Phase 4: Execution (Waterfall)**:
|
||||
- **Single-PR or Multi-PR Decision**: Read the PR Slicing Strategy in `docs/plans/current_spec.md`.
|
||||
- **Single-PR or Multi-PR Decision**: Read the Commit Slicing Strategy in `docs/plans/current_spec.md`.
|
||||
- **If single PR**:
|
||||
- **Backend**: Call `Backend_Dev` with the plan file.
|
||||
- **Frontend**: Call `Frontend_Dev` with the plan file.
|
||||
@@ -64,12 +67,14 @@ You are "lazy" in the smartest way possible. You never do what a subordinate can
|
||||
- Execute in PR slices, one slice at a time, in dependency order.
|
||||
- Require each slice to pass review + QA gates before starting the next slice.
|
||||
- Keep every slice deployable and independently testable.
|
||||
- **MANDATORY**: Implementation agents must perform linting and type checks locally before declaring their slice "DONE". This is a critical step that must not be skipped to avoid broken commits and security issues.
|
||||
|
||||
5. **Phase 5: Review**:
|
||||
- **Supervisor**: Call `Supervisor` to review the implementation against the plan. Provide feedback and ensure alignment with best practices.
|
||||
|
||||
6. **Phase 6: Audit**:
|
||||
- **QA**: Call `QA_Security` to meticulously test current implementation as well as regression test. Run all linting, security tasks, and manual pre-commit checks. Write a report to `docs/reports/qa_report.md`. Start back at Phase 1 if issues are found.
|
||||
- Review Security: Read `security.md.instrutctions.md` and `SECURITY.md` to understand the security requirements and best practices for Charon. Ensure that any open concerns or issues are addressed in the QA Audit and `SECURITY.md` is updated accordingly.
|
||||
- **QA**: Call `QA_Security` to meticulously test current implementation as well as regression test. Run all linting, security tasks, and manual lefthook checks. Write a report to `docs/reports/qa_report.md`. Start back at Phase 1 if issues are found.
|
||||
|
||||
7. **Phase 7: Closure**:
|
||||
- **Docs**: Call `Docs_Writer`.
|
||||
@@ -145,6 +150,16 @@ The task is not complete until ALL of the following pass with zero issues:
|
||||
```
|
||||
This ensures the container has latest code and proper environment variables (emergency token, encryption key from `.env`).
|
||||
- **Run**: `npx playwright test --project=chromium --project=firefox --project=webkit` from project root
|
||||
|
||||
1.5. **GORM Security Scan (Conditional Gate)**:
|
||||
- **Delegation Verification:** If implementation touched backend models
|
||||
(`backend/internal/models/**`) or database-interaction paths
|
||||
(GORM services, migrations), confirm `QA_Security` (or responsible
|
||||
subagent) ran the GORM scanner using check mode (`--check`) and resolved
|
||||
all CRITICAL/HIGH findings before accepting task completion
|
||||
- **Manual Stage Clarification:** Scanner execution is manual
|
||||
(not automated pre-commit), but enforcement is process-blocking for DoD
|
||||
when triggered
|
||||
- **No Truncation**: Never pipe output through `head`, `tail`, or other truncating commands. Playwright requires user input to quit when piped, causing hangs.
|
||||
- **Why First**: If the app is broken at E2E level, unit tests may need updates. Catch integration issues early.
|
||||
- **Scope**: Run tests relevant to modified features (e.g., `tests/manual-dns-provider.spec.ts`)
|
||||
|
||||
7
.github/agents/Planning.agent.md
vendored
7
.github/agents/Planning.agent.md
vendored
@@ -2,9 +2,10 @@
|
||||
name: 'Planning'
|
||||
description: 'Principal Architect for technical planning and design decisions.'
|
||||
argument-hint: 'The feature or system to plan (e.g., "Design the architecture for Real-Time Logs")'
|
||||
tools: vscode/extensions, vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/openSimpleBrowser, vscode/runCommand, vscode/askQuestions, vscode/vscodeAPI, execute, read, agent, 'github/*', 'github/*', 'io.github.goreleaser/mcp/*', 'trivy-mcp/*', edit, search, web, 'github/*', 'playwright/*', 'pylance-mcp-server/*', todo, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment , 'gopls/*'
|
||||
tools: vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/runCommand, vscode/vscodeAPI, vscode/extensions, vscode/askQuestions, execute, read, edit, search, web, browser, github/add_comment_to_pending_review, github/add_issue_comment, github/add_reply_to_pull_request_comment, github/assign_copilot_to_issue, github/create_branch, github/create_or_update_file, github/create_pull_request, github/create_pull_request_with_copilot, github/create_repository, github/delete_file, github/fork_repository, github/get_commit, github/get_copilot_job_status, github/get_file_contents, github/get_label, github/get_latest_release, github/get_me, github/get_release_by_tag, github/get_tag, github/get_team_members, github/get_teams, github/issue_read, github/issue_write, github/list_branches, github/list_commits, github/list_issue_types, github/list_issues, github/list_pull_requests, github/list_releases, github/list_tags, github/merge_pull_request, github/pull_request_read, github/pull_request_review_write, github/push_files, github/request_copilot_review, github/search_code, github/search_issues, github/search_pull_requests, github/search_repositories, github/search_users, github/sub_issue_write, github/update_pull_request, github/update_pull_request_branch, playwright/*, github/*, io.github.goreleaser/mcp/*, mcp-refactor-typescript/*, microsoftdocs/mcp/*, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/pullRequestStatusChecks, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment, todo
|
||||
|
||||
|
||||
|
||||
model: GPT-5.3-Codex (copilot)
|
||||
target: vscode
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
@@ -44,7 +45,7 @@ You are a PRINCIPAL ARCHITECT responsible for technical planning and system desi
|
||||
- Include acceptance criteria
|
||||
- Break down into implementable tasks using examples, diagrams, and tables
|
||||
- Estimate complexity for each component
|
||||
- Add a **PR Slicing Strategy** section with:
|
||||
- Add a **Commit Slicing Strategy** section with:
|
||||
- Decision: single PR or multiple PRs
|
||||
- Trigger reasons (scope, risk, cross-domain changes, review size)
|
||||
- Ordered PR slices (`PR-1`, `PR-2`, ...), each with scope, files, dependencies, and validation gates
|
||||
|
||||
5
.github/agents/Playwright_Dev.agent.md
vendored
5
.github/agents/Playwright_Dev.agent.md
vendored
@@ -3,9 +3,10 @@ name: 'Playwright Dev'
|
||||
description: 'E2E Testing Specialist for Playwright test automation.'
|
||||
argument-hint: 'The feature or flow to test (e.g., "Write E2E tests for the login flow")'
|
||||
|
||||
tools: vscode/extensions, vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/openIntegratedBrowser, vscode/runCommand, vscode/askQuestions, vscode/vscodeAPI, execute, read, agent, 'github/*', 'github/*', 'io.github.goreleaser/mcp/*', 'trivy-mcp/*', edit, search, web, 'github/*', 'gopls/*', 'playwright/*', 'pylance-mcp-server/*', todo, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment
|
||||
tools: vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/runCommand, vscode/vscodeAPI, vscode/extensions, vscode/askQuestions, execute, read, edit, search, web, browser, github/add_comment_to_pending_review, github/add_issue_comment, github/add_reply_to_pull_request_comment, github/assign_copilot_to_issue, github/create_branch, github/create_or_update_file, github/create_pull_request, github/create_pull_request_with_copilot, github/create_repository, github/delete_file, github/fork_repository, github/get_commit, github/get_copilot_job_status, github/get_file_contents, github/get_label, github/get_latest_release, github/get_me, github/get_release_by_tag, github/get_tag, github/get_team_members, github/get_teams, github/issue_read, github/issue_write, github/list_branches, github/list_commits, github/list_issue_types, github/list_issues, github/list_pull_requests, github/list_releases, github/list_tags, github/merge_pull_request, github/pull_request_read, github/pull_request_review_write, github/push_files, github/request_copilot_review, github/search_code, github/search_issues, github/search_pull_requests, github/search_repositories, github/search_users, github/sub_issue_write, github/update_pull_request, github/update_pull_request_branch, playwright/*, github/*, io.github.goreleaser/mcp/*, mcp-refactor-typescript/*, microsoftdocs/mcp/*, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/pullRequestStatusChecks, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment, todo
|
||||
|
||||
|
||||
|
||||
model: GPT-5.3-Codex (copilot)
|
||||
target: vscode
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
|
||||
22
.github/agents/QA_Security.agent.md
vendored
22
.github/agents/QA_Security.agent.md
vendored
@@ -2,9 +2,10 @@
|
||||
name: 'QA Security'
|
||||
description: 'Quality Assurance and Security Engineer for testing and vulnerability assessment.'
|
||||
argument-hint: 'The component or feature to test (e.g., "Run security scan on authentication endpoints")'
|
||||
tools: vscode/extensions, vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/openSimpleBrowser, vscode/runCommand, vscode/askQuestions, vscode/vscodeAPI, execute, read, agent, 'github/*', 'github/*', 'io.github.goreleaser/mcp/*', 'trivy-mcp/*', edit, search, web, 'github/*', 'playwright/*', 'pylance-mcp-server/*', todo, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment, 'gopls/*'
|
||||
tools: vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/runCommand, vscode/vscodeAPI, vscode/extensions, vscode/askQuestions, execute, read, edit, search, web, browser, github/add_comment_to_pending_review, github/add_issue_comment, github/add_reply_to_pull_request_comment, github/assign_copilot_to_issue, github/create_branch, github/create_or_update_file, github/create_pull_request, github/create_pull_request_with_copilot, github/create_repository, github/delete_file, github/fork_repository, github/get_commit, github/get_copilot_job_status, github/get_file_contents, github/get_label, github/get_latest_release, github/get_me, github/get_release_by_tag, github/get_tag, github/get_team_members, github/get_teams, github/issue_read, github/issue_write, github/list_branches, github/list_commits, github/list_issue_types, github/list_issues, github/list_pull_requests, github/list_releases, github/list_tags, github/merge_pull_request, github/pull_request_read, github/pull_request_review_write, github/push_files, github/request_copilot_review, github/search_code, github/search_issues, github/search_pull_requests, github/search_repositories, github/search_users, github/sub_issue_write, github/update_pull_request, github/update_pull_request_branch, playwright/*, github/*, io.github.goreleaser/mcp/*, mcp-refactor-typescript/*, microsoftdocs/mcp/*, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/pullRequestStatusChecks, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment, todo
|
||||
|
||||
|
||||
|
||||
model: GPT-5.3-Codex (copilot)
|
||||
target: vscode
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
@@ -13,7 +14,11 @@ You are a QA AND SECURITY ENGINEER responsible for testing and vulnerability ass
|
||||
|
||||
<context>
|
||||
|
||||
- **Governance**: When this agent file conflicts with canonical instruction
|
||||
files (`.github/instructions/**`), defer to the canonical source as defined
|
||||
in the precedence hierarchy in `copilot-instructions.md`.
|
||||
- **MANDATORY**: Read all relevant instructions in `.github/instructions/**` for the specific task before starting.
|
||||
- **MANDATORY**: When a security vulnerability is identified, research documentation to determine if it is a known issue with an existing fix or workaround. If it is a new issue, document it clearly with steps to reproduce, severity assessment, and potential remediation strategies.
|
||||
- Charon is a self-hosted reverse proxy management tool
|
||||
- Backend tests: `.github/skills/test-backend-unit.SKILL.md`
|
||||
- Frontend tests: `.github/skills/test-frontend-react.SKILL.md`
|
||||
@@ -40,6 +45,19 @@ You are a QA AND SECURITY ENGINEER responsible for testing and vulnerability ass
|
||||
- Review test failure outputs with `test_failure` tool
|
||||
|
||||
4. **Security Scanning**:
|
||||
- - Review Security: Read `security.md.instrutctions.md` and `SECURITY.md` to understand the security requirements and best practices for Charon. Ensure that any open concerns or issues are addressed in the QA Audit and `SECURITY.md` is updated accordingly.
|
||||
- **Conditional GORM Scan**: When backend model/database-related changes are
|
||||
in scope (`backend/internal/models/**`, GORM services, migrations), run
|
||||
GORM scanner in check mode and report pass/fail as DoD gate:
|
||||
- Run: VS Code task `Lint: GORM Security Scan` OR
|
||||
`./scripts/scan-gorm-security.sh --check`
|
||||
- Block approval on unresolved CRITICAL/HIGH findings
|
||||
- **Gotify Token Review**: Verify no Gotify tokens appear in:
|
||||
- Logs, test artifacts, screenshots
|
||||
- API examples, report output
|
||||
- Tokenized URL query strings (e.g., `?token=...`)
|
||||
- Verify URL query parameters are redacted in
|
||||
diagnostics/examples/log artifacts
|
||||
- Run Trivy scans on filesystem and container images
|
||||
- Analyze vulnerabilities with `mcp_trivy_mcp_findings_list`
|
||||
- Prioritize by severity (CRITICAL > HIGH > MEDIUM > LOW)
|
||||
|
||||
6
.github/agents/Supervisor.agent.md
vendored
6
.github/agents/Supervisor.agent.md
vendored
@@ -2,9 +2,9 @@
|
||||
name: 'Supervisor'
|
||||
description: 'Code Review Lead for quality assurance and PR review.'
|
||||
argument-hint: 'The PR or code change to review (e.g., "Review PR #123 for security issues")'
|
||||
tools: vscode/extensions, vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/openSimpleBrowser, vscode/runCommand, vscode/askQuestions, vscode/vscodeAPI, execute, read, agent, 'github/*', 'github/*', 'io.github.goreleaser/mcp/*', 'trivy-mcp/*', edit, search, web, 'github/*', 'playwright/*', 'pylance-mcp-server/*', todo, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment, 'gopls/*'
|
||||
tools: vscode/getProjectSetupInfo, vscode/installExtension, vscode/memory, vscode/runCommand, vscode/vscodeAPI, vscode/extensions, vscode/askQuestions, execute, read, edit, search, web, browser, github/add_comment_to_pending_review, github/add_issue_comment, github/add_reply_to_pull_request_comment, github/assign_copilot_to_issue, github/create_branch, github/create_or_update_file, github/create_pull_request, github/create_pull_request_with_copilot, github/create_repository, github/delete_file, github/fork_repository, github/get_commit, github/get_copilot_job_status, github/get_file_contents, github/get_label, github/get_latest_release, github/get_me, github/get_release_by_tag, github/get_tag, github/get_team_members, github/get_teams, github/issue_read, github/issue_write, github/list_branches, github/list_commits, github/list_issue_types, github/list_issues, github/list_pull_requests, github/list_releases, github/list_tags, github/merge_pull_request, github/pull_request_read, github/pull_request_review_write, github/push_files, github/request_copilot_review, github/search_code, github/search_issues, github/search_pull_requests, github/search_repositories, github/search_users, github/sub_issue_write, github/update_pull_request, github/update_pull_request_branch, playwright/*, github/*, io.github.goreleaser/mcp/*, mcp-refactor-typescript/*, microsoftdocs/mcp/*, vscode.mermaid-chat-features/renderMermaidDiagram, github.vscode-pull-request-github/issue_fetch, github.vscode-pull-request-github/labels_fetch, github.vscode-pull-request-github/notification_fetch, github.vscode-pull-request-github/doSearch, github.vscode-pull-request-github/activePullRequest, github.vscode-pull-request-github/pullRequestStatusChecks, github.vscode-pull-request-github/openPullRequest, ms-azuretools.vscode-containers/containerToolsConfig, ms-python.python/getPythonEnvironmentInfo, ms-python.python/getPythonExecutableCommand, ms-python.python/installPythonPackage, ms-python.python/configurePythonEnvironment, todo
|
||||
|
||||
|
||||
model: GPT-5.3-Codex (copilot)
|
||||
target: vscode
|
||||
user-invocable: true
|
||||
disable-model-invocation: false
|
||||
@@ -15,8 +15,10 @@ You are a CODE REVIEW LEAD responsible for quality assurance and maintaining cod
|
||||
|
||||
- **MANDATORY**: Read all relevant instructions in `.github/instructions/` for the specific task before starting.
|
||||
- Charon is a self-hosted reverse proxy management tool
|
||||
- The codebase includes Go for backend and TypeScript for frontend
|
||||
- Code style: Go follows `gofmt`, TypeScript follows ESLint config
|
||||
- Review guidelines: `.github/instructions/code-review-generic.instructions.md`
|
||||
- Think "mature Saas product codebase with security-sensitive features and a high standard for code quality" over "open source project with varying contribution quality"
|
||||
- Security guidelines: `.github/instructions/security-and-owasp.instructions.md`
|
||||
</context>
|
||||
|
||||
|
||||
7
.github/badges/ghcr-downloads.json
vendored
7
.github/badges/ghcr-downloads.json
vendored
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "GHCR pulls",
|
||||
"message": "0",
|
||||
"color": "blue",
|
||||
"cacheSeconds": 3600
|
||||
}
|
||||
@@ -126,11 +126,11 @@ graph TB
|
||||
| **HTTP Framework** | Gin | Latest | Routing, middleware, HTTP handling |
|
||||
| **Database** | SQLite | 3.x | Embedded database |
|
||||
| **ORM** | GORM | Latest | Database abstraction layer |
|
||||
| **Reverse Proxy** | Caddy Server | 2.11.0-beta.2 | Embedded HTTP/HTTPS proxy |
|
||||
| **Reverse Proxy** | Caddy Server | 2.11.2 | Embedded HTTP/HTTPS proxy |
|
||||
| **WebSocket** | gorilla/websocket | Latest | Real-time log streaming |
|
||||
| **Crypto** | golang.org/x/crypto | Latest | Password hashing, encryption |
|
||||
| **Metrics** | Prometheus Client | Latest | Application metrics |
|
||||
| **Notifications** | Shoutrrr | Latest | Multi-platform alerts |
|
||||
| **Notifications** | Notify | Latest | Multi-platform alerts |
|
||||
| **Docker Client** | Docker SDK | Latest | Container discovery |
|
||||
| **Logging** | Logrus + Lumberjack | Latest | Structured logging with rotation |
|
||||
|
||||
@@ -1263,8 +1263,8 @@ docker exec charon /app/scripts/restore-backup.sh \
|
||||
- Future: Dynamic plugin loading for custom providers
|
||||
|
||||
2. **Notification Channels:**
|
||||
- Shoutrrr provides 40+ channels (Discord, Slack, Email, etc.)
|
||||
- Custom channels via Shoutrrr service URLs
|
||||
- Notify provides multi-platform channels (Discord, Slack, Gotify, etc.)
|
||||
- Provider-based configuration with per-channel feature flags
|
||||
|
||||
3. **Authentication Providers:**
|
||||
- Current: Local database authentication
|
||||
|
||||
44
.github/instructions/copilot-instructions.md
vendored
44
.github/instructions/copilot-instructions.md
vendored
@@ -17,6 +17,23 @@ Every session should improve the codebase, not just add to it. Actively refactor
|
||||
- **READABLE**: Maintain comments and clear naming for complex logic. Favor clarity over cleverness.
|
||||
- **CONVENTIONAL COMMITS**: Write commit messages using `feat:`, `fix:`, `chore:`, `refactor:`, or `docs:` prefixes.
|
||||
|
||||
## Governance & Precedence
|
||||
|
||||
When policy statements conflict across documentation sources, resolve using this precedence hierarchy:
|
||||
|
||||
1. **Highest Precedence**: `.github/instructions/**` files (canonical source of truth)
|
||||
2. **Agent Overrides**: `.github/agents/**` files (agent-specific customizations)
|
||||
3. **Operator Documentation**: `SECURITY.md`, `docs/security.md`,
|
||||
`docs/features/notifications.md` (user-facing guidance)
|
||||
|
||||
**Reconciliation Rule**: When conflicts arise, the stricter security requirement
|
||||
wins. Update downstream documentation to match canonical text in
|
||||
`.github/instructions/**`.
|
||||
|
||||
**Example**: If `.github/instructions/security.instructions.md` mandates token
|
||||
redaction but operator docs suggest logging is acceptable, token redaction
|
||||
requirement takes precedence and operator docs must be updated.
|
||||
|
||||
## 🚨 CRITICAL ARCHITECTURE RULES 🚨
|
||||
|
||||
- **Single Frontend Source**: All frontend code MUST reside in `frontend/`. NEVER create `backend/frontend/` or any other nested frontend directory.
|
||||
@@ -50,7 +67,7 @@ Before proposing ANY code change or fix, you must build a mental map of the feat
|
||||
|
||||
- **Run**: `cd backend && go run ./cmd/api`.
|
||||
- **Test**: `go test ./...`.
|
||||
- **Static Analysis (BLOCKING)**: Fast linters run automatically on every commit via pre-commit hooks.
|
||||
- **Static Analysis (BLOCKING)**: Fast linters run automatically on every commit via lefthook pre-commit-phase hooks.
|
||||
- **Staticcheck errors MUST be fixed** - commits are BLOCKED until resolved
|
||||
- Manual run: `make lint-fast` or VS Code task "Lint: Staticcheck (Fast)"
|
||||
- Staticcheck-only: `make lint-staticcheck-only`
|
||||
@@ -62,7 +79,7 @@ Before proposing ANY code change or fix, you must build a mental map of the feat
|
||||
- **Security**: Sanitize all file paths using `filepath.Clean`. Use `fmt.Errorf("context: %w", err)` for error wrapping.
|
||||
- **Graceful Shutdown**: Long-running work must respect `server.Run(ctx)`.
|
||||
|
||||
### Troubleshooting Pre-Commit Staticcheck Failures
|
||||
### Troubleshooting Lefthook Staticcheck Failures
|
||||
|
||||
**Common Issues:**
|
||||
|
||||
@@ -150,6 +167,21 @@ Before marking an implementation task as complete, perform the following in orde
|
||||
- **Base URL**: Uses `PLAYWRIGHT_BASE_URL` or default from `playwright.config.js`
|
||||
- All E2E tests must pass before proceeding to unit tests
|
||||
|
||||
1.5. **GORM Security Scan** (CONDITIONAL, BLOCKING):
|
||||
- **Trigger Condition**: Execute this gate when changes include backend models or database interaction logic:
|
||||
- `backend/internal/models/**`
|
||||
- GORM query/service layers
|
||||
- Database migrations or seeding logic
|
||||
- **Exclusions**: Skip this gate for docs-only (`**/*.md`) or frontend-only (`frontend/**`) changes
|
||||
- **Run One Of**:
|
||||
- VS Code task: `Lint: GORM Security Scan`
|
||||
- Lefthook: `lefthook run pre-commit` (includes gorm-security-scan)
|
||||
- Direct: `./scripts/scan-gorm-security.sh --check`
|
||||
- **Gate Enforcement**: DoD is process-blocking until scanner reports zero
|
||||
CRITICAL/HIGH findings, even while automation remains in manual stage
|
||||
- **Check Mode Required**: Gate decisions must use check mode semantics
|
||||
(`--check` flag or equivalent task wiring) for pass/fail determination
|
||||
|
||||
2. **Local Patch Coverage Preflight** (MANDATORY - Run Before Unit/Coverage Tests):
|
||||
- **Run**: VS Code task `Test: Local Patch Report` or `bash scripts/local-patch-report.sh` from repo root.
|
||||
- **Purpose**: Surface exact changed files and uncovered changed lines before adding/refining unit tests.
|
||||
@@ -157,15 +189,15 @@ Before marking an implementation task as complete, perform the following in orde
|
||||
- **Expected Behavior**: Report may warn (non-blocking rollout), but artifact generation is mandatory.
|
||||
|
||||
3. **Security Scans** (MANDATORY - Zero Tolerance):
|
||||
- **CodeQL Go Scan**: Run VS Code task "Security: CodeQL Go Scan (CI-Aligned)" OR `pre-commit run codeql-go-scan --all-files`
|
||||
- **CodeQL Go Scan**: Run VS Code task "Security: CodeQL Go Scan (CI-Aligned)" OR `lefthook run pre-commit`
|
||||
- Must use `security-and-quality` suite (CI-aligned)
|
||||
- **Zero high/critical (error-level) findings allowed**
|
||||
- Medium/low findings should be documented and triaged
|
||||
- **CodeQL JS Scan**: Run VS Code task "Security: CodeQL JS Scan (CI-Aligned)" OR `pre-commit run codeql-js-scan --all-files`
|
||||
- **CodeQL JS Scan**: Run VS Code task "Security: CodeQL JS Scan (CI-Aligned)" OR `lefthook run pre-commit`
|
||||
- Must use `security-and-quality` suite (CI-aligned)
|
||||
- **Zero high/critical (error-level) findings allowed**
|
||||
- Medium/low findings should be documented and triaged
|
||||
- **Validate Findings**: Run `pre-commit run codeql-check-findings --all-files` to check for HIGH/CRITICAL issues
|
||||
- **Validate Findings**: Run `lefthook run pre-commit` to check for HIGH/CRITICAL issues
|
||||
- **Trivy Container Scan**: Run VS Code task "Security: Trivy Scan" for container/dependency vulnerabilities
|
||||
- **Results Viewing**:
|
||||
- Primary: VS Code SARIF Viewer extension (`MS-SarifVSCode.sarif-viewer`)
|
||||
@@ -178,7 +210,7 @@ Before marking an implementation task as complete, perform the following in orde
|
||||
- Database creation: `--threads=0 --overwrite`
|
||||
- Analysis: `--sarif-add-baseline-file-info`
|
||||
|
||||
4. **Pre-Commit Triage**: Run `pre-commit run --all-files`.
|
||||
4. **Lefthook Triage**: Run `lefthook run pre-commit`.
|
||||
- If errors occur, **fix them immediately**.
|
||||
- If logic errors occur, analyze and propose a fix.
|
||||
- Do not output code that violates pre-commit standards.
|
||||
|
||||
2
.github/instructions/go.instructions.md
vendored
2
.github/instructions/go.instructions.md
vendored
@@ -353,7 +353,7 @@ Follow idiomatic Go practices and community standards when writing Go code. Thes
|
||||
### Development Practices
|
||||
|
||||
- Run tests before committing
|
||||
- Use pre-commit hooks for formatting and linting
|
||||
- Use lefthook pre-commit-phase hooks for formatting and linting
|
||||
- Keep commits focused and atomic
|
||||
- Write meaningful commit messages
|
||||
- Review diffs before committing
|
||||
|
||||
@@ -49,3 +49,26 @@ Your primary directive is to ensure all code you generate, review, or refactor i
|
||||
## General Guidelines
|
||||
- **Be Explicit About Security:** When you suggest a piece of code that mitigates a security risk, explicitly state what you are protecting against (e.g., "Using a parameterized query here to prevent SQL injection.").
|
||||
- **Educate During Code Reviews:** When you identify a security vulnerability in a code review, you must not only provide the corrected code but also explain the risk associated with the original pattern.
|
||||
|
||||
### Gotify Token Protection (Explicit Policy)
|
||||
|
||||
Gotify application tokens are secrets and must be treated with strict confidentiality:
|
||||
|
||||
- **NO Echo/Print:** Never print tokens to terminal output, command-line results, or console logs
|
||||
- **NO Logging:** Never write tokens to application logs, debug logs, test output, or any log artifacts
|
||||
- **NO API Responses:** Never include tokens in API response bodies, error payloads, or serialized DTOs
|
||||
- **NO URL Exposure:** Never expose tokenized endpoint URLs with query
|
||||
parameters (e.g., `https://gotify.example.com/message?token=...`) in:
|
||||
- Documentation examples
|
||||
- Diagnostic output
|
||||
- Screenshots or reports
|
||||
- Log files
|
||||
- **Redact Query Parameters:** Always redact URL query parameters in
|
||||
diagnostics, examples, and log output before display or storage
|
||||
- **Validation Without Revelation:** For token validation or health checks:
|
||||
- Return only non-sensitive status indicators (`valid`/`invalid` + reason category)
|
||||
- Use token length/prefix-independent masking in UX and diagnostics
|
||||
- Never reveal raw token values in validation feedback
|
||||
- **Storage:** Store and process tokens as secrets only (environment variables
|
||||
or secret management service)
|
||||
- **Rotation:** Rotate tokens immediately on suspected exposure
|
||||
|
||||
204
.github/instructions/security.md.instructions.md
vendored
Normal file
204
.github/instructions/security.md.instructions.md
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
---
|
||||
applyTo: SECURITY.md
|
||||
---
|
||||
|
||||
# Instructions: Maintaining `SECURITY.md`
|
||||
|
||||
`SECURITY.md` is the project's living security record. It serves two audiences simultaneously: users who need to know what risks exist right now, and the broader community who need confidence that vulnerabilities are being tracked and remediated with discipline. Treat it like a changelog, but for security events — every known issue gets an entry, every resolved issue keeps its entry.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
`SECURITY.md` must always contain the following top-level sections, in this order:
|
||||
|
||||
1. A brief project security policy preamble (responsible disclosure contact, response SLA)
|
||||
2. **`## Known Vulnerabilities`** — active, unpatched issues
|
||||
3. **`## Patched Vulnerabilities`** — resolved issues, retained permanently for audit trail
|
||||
|
||||
No other top-level sections are required. Do not collapse or remove sections even when they are empty — use the explicit empty-state placeholder defined below.
|
||||
|
||||
---
|
||||
|
||||
## Section 1: Known Vulnerabilities
|
||||
|
||||
This section lists every vulnerability that is currently unpatched or only partially mitigated. Entries must be sorted with the highest severity first, then by discovery date descending within the same severity tier.
|
||||
|
||||
### Entry Format
|
||||
|
||||
Each entry is an H3 heading followed by a structured block:
|
||||
|
||||
```markdown
|
||||
### [SEVERITY] CVE-XXXX-XXXXX · Short Title
|
||||
|
||||
| Field | Value |
|
||||
|--------------|-------|
|
||||
| **ID** | CVE-XXXX-XXXXX (or `CHARON-YYYY-NNN` if no CVE assigned yet) |
|
||||
| **Severity** | Critical / High / Medium / Low · CVSS v3.1 score if known (e.g. `8.1 · High`) |
|
||||
| **Status** | Investigating / Fix In Progress / Awaiting Upstream / Mitigated (partial) |
|
||||
|
||||
**What**
|
||||
One to three sentences describing the vulnerability class and its impact.
|
||||
Be specific: name the weakness type (e.g. SQL injection, path traversal, SSRF).
|
||||
|
||||
**Who**
|
||||
- Discovered by: [Reporter name or handle, or "Internal audit", or "Automated scan (tool name)"]
|
||||
- Reported: YYYY-MM-DD
|
||||
- Affects: [User roles, API consumers, unauthenticated users, etc.]
|
||||
|
||||
**Where**
|
||||
- Component: [Module or service name]
|
||||
- File(s): `path/to/affected/file.go`, `path/to/other/file.ts`
|
||||
- Versions affected: `>= X.Y.Z` (or "all versions" / "prior to X.Y.Z")
|
||||
|
||||
**When**
|
||||
- Discovered: YYYY-MM-DD
|
||||
- Disclosed (if public): YYYY-MM-DD (or "Not yet publicly disclosed")
|
||||
- Target fix: YYYY-MM-DD (or sprint/milestone reference)
|
||||
|
||||
**How**
|
||||
A concise technical description of the attack vector, prerequisites, and exploitation
|
||||
method. Omit proof-of-concept code. Reference CVE advisories or upstream issue
|
||||
trackers where appropriate.
|
||||
|
||||
**Planned Remediation**
|
||||
Describe the fix strategy: library upgrade, logic refactor, config change, etc.
|
||||
If a workaround is available in the meantime, document it here.
|
||||
Link to the tracking issue: [#NNN](https://github.com/owner/repo/issues/NNN)
|
||||
```
|
||||
|
||||
### Empty State
|
||||
|
||||
When there are no known vulnerabilities:
|
||||
|
||||
```markdown
|
||||
## Known Vulnerabilities
|
||||
|
||||
No known unpatched vulnerabilities at this time.
|
||||
Last reviewed: YYYY-MM-DD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Section 2: Patched Vulnerabilities
|
||||
|
||||
This section is a permanent, append-only ledger. Entries are never deleted. Sort newest-patched first. This section builds community trust by demonstrating that issues are resolved promptly and transparently.
|
||||
|
||||
### Entry Format
|
||||
|
||||
```markdown
|
||||
### ✅ [SEVERITY] CVE-XXXX-XXXXX · Short Title
|
||||
|
||||
| Field | Value |
|
||||
|--------------|-------|
|
||||
| **ID** | CVE-XXXX-XXXXX (or internal ID) |
|
||||
| **Severity** | Critical / High / Medium / Low · CVSS v3.1 score |
|
||||
| **Patched** | YYYY-MM-DD in `vX.Y.Z` |
|
||||
|
||||
**What**
|
||||
Same description carried over from the Known Vulnerabilities entry.
|
||||
|
||||
**Who**
|
||||
- Discovered by: [Reporter or method]
|
||||
- Reported: YYYY-MM-DD
|
||||
|
||||
**Where**
|
||||
- Component: [Module or service name]
|
||||
- File(s): `path/to/affected/file.go`
|
||||
- Versions affected: `< X.Y.Z`
|
||||
|
||||
**When**
|
||||
- Discovered: YYYY-MM-DD
|
||||
- Patched: YYYY-MM-DD
|
||||
- Time to patch: N days
|
||||
|
||||
**How**
|
||||
Same technical description as the original entry.
|
||||
|
||||
**Resolution**
|
||||
Describe exactly what was changed to fix the issue.
|
||||
- Commit: [`abc1234`](https://github.com/owner/repo/commit/abc1234)
|
||||
- PR: [#NNN](https://github.com/owner/repo/pull/NNN)
|
||||
- Release: [`vX.Y.Z`](https://github.com/owner/repo/releases/tag/vX.Y.Z)
|
||||
|
||||
**Credit**
|
||||
[Optional] Thank the reporter if they consented to attribution.
|
||||
```
|
||||
|
||||
### Empty State
|
||||
|
||||
```markdown
|
||||
## Patched Vulnerabilities
|
||||
|
||||
No patched vulnerabilities on record yet.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Lifecycle: Moving an Entry from Known → Patched
|
||||
|
||||
When a fix ships:
|
||||
|
||||
1. Remove the entry from `## Known Vulnerabilities` entirely.
|
||||
2. Add a new entry to the **top** of `## Patched Vulnerabilities` using the patched format above.
|
||||
3. Carry forward all original fields verbatim — do not rewrite the history of the issue.
|
||||
4. Add the `**Resolution**` and `**Credit**` blocks with patch details.
|
||||
5. Update the `Last reviewed` date on the Known Vulnerabilities section if it is now empty.
|
||||
|
||||
Do not edit or backfill existing Patched entries once they are committed.
|
||||
|
||||
---
|
||||
|
||||
## Severity Classification
|
||||
|
||||
Use the following definitions consistently:
|
||||
|
||||
| Severity | CVSS Range | Meaning |
|
||||
|----------|------------|---------|
|
||||
| **Critical** | 9.0–10.0 | Remote code execution, auth bypass, full data exposure |
|
||||
| **High** | 7.0–8.9 | Significant data exposure, privilege escalation, DoS |
|
||||
| **Medium** | 4.0–6.9 | Limited data exposure, requires user interaction or auth |
|
||||
| **Low** | 0.1–3.9 | Minimal impact, difficult to exploit, defense-in-depth |
|
||||
|
||||
When a CVE CVSS score is not yet available, assign a preliminary severity based on these definitions and note it as `(preliminary)` until confirmed.
|
||||
|
||||
---
|
||||
|
||||
## Internal IDs
|
||||
|
||||
If a vulnerability has no CVE assigned, use the format `CHARON-YYYY-NNN` where `YYYY` is the year and `NNN` is a zero-padded sequence number starting at `001` for each year. Example: `CHARON-2025-003`. Assign a CVE ID in the entry retroactively if one is issued later, and add the internal ID as an alias in parentheses.
|
||||
|
||||
---
|
||||
|
||||
## Responsible Disclosure Preamble
|
||||
|
||||
The preamble at the top of `SECURITY.md` (before the vulnerability sections) must include:
|
||||
|
||||
- The preferred contact method for reporting vulnerabilities (e.g. a GitHub private advisory link, a security email address, or both)
|
||||
- An acknowledgment-first response commitment: confirm receipt within 48 hours, even if the full investigation takes longer
|
||||
- A statement that reporters will not be penalized or publicly named without consent
|
||||
- A link to the full disclosure policy if one exists
|
||||
|
||||
Example:
|
||||
|
||||
```markdown
|
||||
## Reporting a Vulnerability
|
||||
|
||||
To report a security issue, please use
|
||||
[GitHub Private Security Advisories](https://github.com/owner/repo/security/advisories/new)
|
||||
or email `security@example.com`.
|
||||
|
||||
We will acknowledge your report within **48 hours** and provide a remediation
|
||||
timeline within **7 days**. Reporters are credited with their consent.
|
||||
We do not pursue legal action against good-faith security researchers.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Maintenance Rules
|
||||
|
||||
- **Review cadence**: Update the `Last reviewed` date in the Known Vulnerabilities section at least once per release cycle, even if no entries changed.
|
||||
- **No silent patches**: Every security fix — no matter how minor — must produce an entry in `## Patched Vulnerabilities` before or alongside the release.
|
||||
- **No redaction**: Do not redact or soften historical entries. Accuracy builds trust; minimizing past issues destroys it.
|
||||
- **Dependency vulnerabilities**: Transitive dependency CVEs that affect Charon's exposed attack surface must be tracked here the same as first-party vulnerabilities. Pure dev-dependency CVEs with no runtime impact may be omitted at maintainer discretion, but must still be noted in the relevant dependency update PR.
|
||||
- **Partial mitigations**: If a workaround is deployed but the root cause is not fixed, the entry stays in `## Known Vulnerabilities` with `Status: Mitigated (partial)` and the workaround documented in `**Planned Remediation**`.
|
||||
@@ -9,7 +9,7 @@ description: 'Repository structure guidelines to maintain organized file placeme
|
||||
|
||||
The repository root should contain ONLY:
|
||||
|
||||
- Essential config files (`.gitignore`, `.pre-commit-config.yaml`, `Makefile`, etc.)
|
||||
- Essential config files (`.gitignore`, `Makefile`, etc.)
|
||||
- Standard project files (`README.md`, `CONTRIBUTING.md`, `LICENSE`, `CHANGELOG.md`)
|
||||
- Go workspace files (`go.work`, `go.work.sum`)
|
||||
- VS Code workspace (`Chiron.code-workspace`)
|
||||
|
||||
@@ -28,7 +28,7 @@ runSubagent({
|
||||
- Parallel: run `QA and Security`, `DevOps` and `Doc Writer` in parallel for CI / QA checks and documentation.
|
||||
- Return: a JSON summary with `subagent_results`, `overall_status`, and aggregated artifacts.
|
||||
|
||||
2.1) Multi-PR Slicing Protocol
|
||||
2.1) Multi-Commit Slicing Protocol
|
||||
|
||||
- If a task is large or high-risk, split into PR slices and execute in order.
|
||||
- Each slice must have:
|
||||
|
||||
39
.github/instructions/testing.instructions.md
vendored
39
.github/instructions/testing.instructions.md
vendored
@@ -4,6 +4,10 @@ description: 'Strict protocols for test execution, debugging, and coverage valid
|
||||
---
|
||||
# Testing Protocols
|
||||
|
||||
**Governance Note**: This file is subject to the precedence hierarchy defined in
|
||||
`.github/instructions/copilot-instructions.md`. When conflicts arise, canonical
|
||||
instruction files take precedence over agent files and operator documentation.
|
||||
|
||||
## 0. E2E Verification First (Playwright)
|
||||
|
||||
**MANDATORY**: Before running unit tests, verify the application UI/UX functions correctly end-to-end.
|
||||
@@ -170,16 +174,39 @@ Before pushing code, verify E2E coverage:
|
||||
* **Threshold Compliance:** You must compare the final coverage percentage against the project's threshold (Default: 85% unless specified otherwise). If coverage drops, you must identify the "uncovered lines" and add targeted tests.
|
||||
* **Patch Coverage (Suggestion):** Codecov reports patch coverage as an indicator. While developers should aim for 100% coverage of modified lines, patch coverage is **not a hard requirement** and will not block PR approval. If patch coverage is low, consider adding targeted tests to improve the metric.
|
||||
* **Review Patch Coverage:** When reviewing patch coverage reports, assess whether missing lines represent genuine gaps or are acceptable (e.g., error handling branches, deprecated code paths). Use the report to inform testing decisions, not as an absolute gate.
|
||||
|
||||
## 4. GORM Security Validation (Manual Stage)
|
||||
|
||||
**Requirement:** All backend changes involving GORM models or database interactions must pass the GORM Security Scanner.
|
||||
**Requirement:** For any change that touches backend models or
|
||||
database-related logic, the GORM Security Scanner is a mandatory local DoD gate
|
||||
and must pass with zero CRITICAL/HIGH findings.
|
||||
|
||||
### When to Run
|
||||
**Policy vs. Automation Reconciliation:** "Manual stage" describes execution
|
||||
mechanism only (not automated pre-commit hook); policy enforcement remains
|
||||
process-blocking for DoD. Gate decisions must use check semantics
|
||||
(`./scripts/scan-gorm-security.sh --check` or equivalent task wiring).
|
||||
|
||||
* **Before Committing:** When modifying GORM models (files in `backend/internal/models/`)
|
||||
* **Before Opening PR:** Verify no security issues introduced
|
||||
* **After Code Review:** If model-related changes were requested
|
||||
* **Definition of Done:** Scanner must pass with zero CRITICAL/HIGH issues
|
||||
### When to Run (Conditional Trigger Matrix)
|
||||
|
||||
**Mandatory Trigger Paths (Include):**
|
||||
- `backend/internal/models/**` — GORM model definitions
|
||||
- Backend services/repositories with GORM query logic
|
||||
- Database migrations or seeding logic affecting model persistence behavior
|
||||
|
||||
**Explicit Exclusions:**
|
||||
- Docs-only changes (`**/*.md`, governance documentation)
|
||||
- Frontend-only changes (`frontend/**`)
|
||||
|
||||
**Gate Decision Rule:** IF any Include path matches, THEN scanner execution in
|
||||
check mode is mandatory DoD gate. IF only Exclude paths match, THEN GORM gate
|
||||
is not required for that change set.
|
||||
|
||||
### Definition of Done
|
||||
- **Before Committing:** When modifying trigger paths listed above
|
||||
- **Before Opening PR:** Verify no security issues introduced
|
||||
- **After Code Review:** If model-related changes were requested
|
||||
- **Blocking Gate:** Scanner must pass with zero CRITICAL/HIGH issues before
|
||||
task completion
|
||||
|
||||
### Running the Scanner
|
||||
|
||||
|
||||
87
.github/renovate.json
vendored
87
.github/renovate.json
vendored
@@ -27,7 +27,10 @@
|
||||
"rebaseWhen": "auto",
|
||||
|
||||
"vulnerabilityAlerts": {
|
||||
"enabled": true
|
||||
"enabled": true,
|
||||
"dependencyDashboardApproval": false,
|
||||
"automerge": false,
|
||||
"labels": ["security", "vulnerability"]
|
||||
},
|
||||
|
||||
"rangeStrategy": "bump",
|
||||
@@ -36,6 +39,19 @@
|
||||
"platformAutomerge": true,
|
||||
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Track caddy-security plugin version in Dockerfile",
|
||||
"managerFilePatterns": [
|
||||
"/^Dockerfile$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"ARG CADDY_SECURITY_VERSION=(?<currentValue>[^\\s]+)"
|
||||
],
|
||||
"depNameTemplate": "github.com/greenpau/caddy-security",
|
||||
"datasourceTemplate": "go",
|
||||
"versioningTemplate": "semver"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Track Go dependencies patched in Dockerfile for Caddy CVE fixes",
|
||||
@@ -53,12 +69,45 @@
|
||||
"description": "Track Alpine base image digest in Dockerfile for security updates",
|
||||
"managerFilePatterns": ["/^Dockerfile$/"],
|
||||
"matchStrings": [
|
||||
"#\\s*renovate:\\s*datasource=docker\\s+depName=alpine.*\\nARG CADDY_IMAGE=alpine:(?<currentValue>[^\\s@]+@sha256:[a-f0-9]+)"
|
||||
"#\\s*renovate:\\s*datasource=docker\\s+depName=alpine.*\\nARG ALPINE_IMAGE=alpine:(?<currentValue>[^@\\s]+)@(?<currentDigest>sha256:[a-f0-9]+)"
|
||||
],
|
||||
"depNameTemplate": "alpine",
|
||||
"datasourceTemplate": "docker",
|
||||
"versioningTemplate": "docker"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Track Go toolchain version ARG in Dockerfile",
|
||||
"managerFilePatterns": ["/^Dockerfile$/"],
|
||||
"matchStrings": [
|
||||
"#\\s*renovate:\\s*datasource=docker\\s+depName=golang.*\\nARG GO_VERSION=(?<currentValue>[^\\s]+)"
|
||||
],
|
||||
"depNameTemplate": "golang",
|
||||
"datasourceTemplate": "docker",
|
||||
"versioningTemplate": "docker"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Track expr-lang version ARG in Dockerfile",
|
||||
"managerFilePatterns": ["/^Dockerfile$/"],
|
||||
"matchStrings": [
|
||||
"#\\s*renovate:\\s*datasource=go\\s+depName=github\\.com/expr-lang/expr.*\\nARG EXPR_LANG_VERSION=(?<currentValue>[^\\s]+)"
|
||||
],
|
||||
"depNameTemplate": "github.com/expr-lang/expr",
|
||||
"datasourceTemplate": "go",
|
||||
"versioningTemplate": "semver"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Track golang.org/x/net version ARG in Dockerfile",
|
||||
"managerFilePatterns": ["/^Dockerfile$/"],
|
||||
"matchStrings": [
|
||||
"#\\s*renovate:\\s*datasource=go\\s+depName=golang\\.org/x/net.*\\nARG XNET_VERSION=(?<currentValue>[^\\s]+)"
|
||||
],
|
||||
"depNameTemplate": "golang.org/x/net",
|
||||
"datasourceTemplate": "go",
|
||||
"versioningTemplate": "semver"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Track Delve version in Dockerfile",
|
||||
@@ -117,13 +166,45 @@
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Track GO_VERSION in Actions workflows",
|
||||
"fileMatch": ["^\\.github/workflows/.*\\.yml$"],
|
||||
"managerFilePatterns": ["/^\\.github/workflows/.*\\.yml$/"],
|
||||
"matchStrings": [
|
||||
"GO_VERSION: ['\"]?(?<currentValue>[\\d\\.]+)['\"]?"
|
||||
],
|
||||
"depNameTemplate": "golang/go",
|
||||
"datasourceTemplate": "golang-version",
|
||||
"versioningTemplate": "semver"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Track Syft version in workflows and scripts",
|
||||
"managerFilePatterns": [
|
||||
"/^\\.github/workflows/nightly-build\\.yml$/",
|
||||
"/^\\.github/skills/security-scan-docker-image-scripts/run\\.sh$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"SYFT_VERSION=\\\"v(?<currentValue>[^\\\"\\s]+)\\\"",
|
||||
"set_default_env \\\"SYFT_VERSION\\\" \\\"v(?<currentValue>[^\\\"]+)\\\""
|
||||
],
|
||||
"depNameTemplate": "anchore/syft",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"versioningTemplate": "semver",
|
||||
"extractVersionTemplate": "^v(?<version>.*)$"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Track Grype version in workflows and scripts",
|
||||
"managerFilePatterns": [
|
||||
"/^\\.github/workflows/supply-chain-pr\\.yml$/",
|
||||
"/^\\.github/skills/security-scan-docker-image-scripts/run\\.sh$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"anchore/grype/main/install\\.sh \\| sh -s -- -b /usr/local/bin v(?<currentValue>[0-9]+\\.[0-9]+\\.[0-9]+)",
|
||||
"set_default_env \\\"GRYPE_VERSION\\\" \\\"v(?<currentValue>[^\\\"]+)\\\""
|
||||
],
|
||||
"depNameTemplate": "anchore/grype",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"versioningTemplate": "semver",
|
||||
"extractVersionTemplate": "^v(?<version>.*)$"
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
55
.github/security-severity-policy.yml
vendored
Normal file
55
.github/security-severity-policy.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
version: 1
|
||||
effective_date: 2026-02-25
|
||||
scope:
|
||||
- local pre-commit manual security hooks
|
||||
- github actions security workflows
|
||||
|
||||
defaults:
|
||||
blocking:
|
||||
- critical
|
||||
- high
|
||||
medium:
|
||||
mode: risk-based
|
||||
default_action: report
|
||||
require_sla: true
|
||||
default_sla_days: 14
|
||||
escalation:
|
||||
trigger: high-signal class or repeated finding
|
||||
action: require issue + owner + due date
|
||||
low:
|
||||
action: report
|
||||
|
||||
codeql:
|
||||
severity_mapping:
|
||||
error: high_or_critical
|
||||
warning: medium_or_lower
|
||||
note: informational
|
||||
blocking_levels:
|
||||
- error
|
||||
warning_policy:
|
||||
default_action: report
|
||||
escalation_high_signal_rule_ids:
|
||||
- go/request-forgery
|
||||
- js/missing-rate-limiting
|
||||
- js/insecure-randomness
|
||||
|
||||
trivy:
|
||||
blocking_severities:
|
||||
- CRITICAL
|
||||
- HIGH
|
||||
medium_policy:
|
||||
action: report
|
||||
escalation: issue-with-sla
|
||||
|
||||
grype:
|
||||
blocking_severities:
|
||||
- Critical
|
||||
- High
|
||||
medium_policy:
|
||||
action: report
|
||||
escalation: issue-with-sla
|
||||
|
||||
enforcement_contract:
|
||||
codeql_local_vs_ci: "local and ci block on codeql error-level findings only"
|
||||
supply_chain_medium: "medium vulnerabilities are non-blocking by default and require explicit triage"
|
||||
auth_regression_guard: "state-changing routes must remain protected by auth middleware"
|
||||
2
.github/skills/README.md
vendored
2
.github/skills/README.md
vendored
@@ -63,7 +63,7 @@ Agent Skills are self-documenting, AI-discoverable task definitions that combine
|
||||
|
||||
| Skill Name | Category | Description | Status |
|
||||
|------------|----------|-------------|--------|
|
||||
| [qa-precommit-all](./qa-precommit-all.SKILL.md) | qa | Run all pre-commit hooks on entire codebase | ✅ Active |
|
||||
| [qa-lefthook-all](./qa-lefthook-all.SKILL.md) | qa | Run all lefthook pre-commit‑phase hooks on entire codebase | ✅ Active |
|
||||
|
||||
### Utility Skills
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.23'
|
||||
go-version: "1.26.1"
|
||||
|
||||
- name: Run GORM Security Scanner
|
||||
id: gorm-scan
|
||||
|
||||
349
.github/skills/qa-lefthook-all.SKILL.md
vendored
Normal file
349
.github/skills/qa-lefthook-all.SKILL.md
vendored
Normal file
@@ -0,0 +1,349 @@
|
||||
---
|
||||
# agentskills.io specification v1.0
|
||||
name: "qa-lefthook-all"
|
||||
version: "1.0.0"
|
||||
description: "Run all lefthook pre-commit-phase hooks for comprehensive code quality validation"
|
||||
author: "Charon Project"
|
||||
license: "MIT"
|
||||
tags:
|
||||
- "qa"
|
||||
- "quality"
|
||||
- "pre-commit"
|
||||
- "linting"
|
||||
- "validation"
|
||||
compatibility:
|
||||
os:
|
||||
- "linux"
|
||||
- "darwin"
|
||||
shells:
|
||||
- "bash"
|
||||
requirements:
|
||||
- name: "python3"
|
||||
version: ">=3.8"
|
||||
optional: false
|
||||
- name: "lefthook"
|
||||
version: ">=0.14"
|
||||
optional: false
|
||||
environment_variables:
|
||||
- name: "SKIP"
|
||||
description: "Comma-separated list of hook IDs to skip"
|
||||
default: ""
|
||||
required: false
|
||||
parameters:
|
||||
- name: "files"
|
||||
type: "string"
|
||||
description: "Specific files to check (default: all staged files)"
|
||||
default: "--all-files"
|
||||
required: false
|
||||
outputs:
|
||||
- name: "validation_report"
|
||||
type: "stdout"
|
||||
description: "Results of all pre-commit hook executions"
|
||||
- name: "exit_code"
|
||||
type: "number"
|
||||
description: "0 if all hooks pass, non-zero if any fail"
|
||||
metadata:
|
||||
category: "qa"
|
||||
subcategory: "quality"
|
||||
execution_time: "medium"
|
||||
risk_level: "low"
|
||||
ci_cd_safe: true
|
||||
requires_network: false
|
||||
idempotent: true
|
||||
---
|
||||
|
||||
# QA Pre-commit All
|
||||
|
||||
## Overview
|
||||
|
||||
Executes all configured lefthook pre-commit-phase hooks to validate code quality, formatting, security, and best practices across the entire codebase. This skill runs checks for Python, Go, JavaScript/TypeScript, Markdown, YAML, and more.
|
||||
|
||||
This skill is designed for CI/CD pipelines and local quality validation before committing code.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Python 3.8 or higher installed and in PATH
|
||||
- Python virtual environment activated (`.venv`)
|
||||
- Pre-commit installed in virtual environment: `pip install pre-commit`
|
||||
- Pre-commit hooks installed: `pre-commit install`
|
||||
- All language-specific tools installed (Go, Node.js, etc.)
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Run all pre-commit-phase hooks on all files:
|
||||
|
||||
```bash
|
||||
cd /path/to/charon
|
||||
lefthook run pre-commit
|
||||
```
|
||||
|
||||
### Staged Files Only
|
||||
|
||||
Run lefthook on staged files only (faster):
|
||||
|
||||
```bash
|
||||
lefthook run pre-commit --staged
|
||||
```
|
||||
|
||||
### Specific Hook
|
||||
|
||||
Run only a specific hook by ID:
|
||||
|
||||
```bash
|
||||
lefthook run pre-commit --hooks=trailing-whitespace
|
||||
```
|
||||
|
||||
### Skip Specific Hooks
|
||||
|
||||
Skip certain hooks during execution:
|
||||
|
||||
```bash
|
||||
SKIP=prettier,eslint .github/skills/scripts/skill-runner.sh qa-precommit-all
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
|-----------|------|----------|---------|-------------|
|
||||
| files | string | No | --all-files | File selection mode (--all-files or staged) |
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|----------|----------|---------|-------------|
|
||||
| SKIP | No | "" | Comma-separated hook IDs to skip |
|
||||
| PRE_COMMIT_HOME | No | ~/.cache/pre-commit | Pre-commit cache directory |
|
||||
|
||||
## Outputs
|
||||
|
||||
- **Success Exit Code**: 0 (all hooks passed)
|
||||
- **Error Exit Codes**: Non-zero (one or more hooks failed)
|
||||
- **Output**: Detailed results from each hook
|
||||
|
||||
## Pre-commit Hooks Included
|
||||
|
||||
The following hooks are configured in `.pre-commit-config.yaml`:
|
||||
|
||||
### General Hooks
|
||||
- **trailing-whitespace**: Remove trailing whitespace
|
||||
- **end-of-file-fixer**: Ensure files end with newline
|
||||
- **check-yaml**: Validate YAML syntax
|
||||
- **check-json**: Validate JSON syntax
|
||||
- **check-merge-conflict**: Detect merge conflict markers
|
||||
- **check-added-large-files**: Prevent committing large files
|
||||
|
||||
### Python Hooks
|
||||
- **black**: Code formatting
|
||||
- **isort**: Import sorting
|
||||
- **flake8**: Linting
|
||||
- **mypy**: Type checking
|
||||
|
||||
### Go Hooks
|
||||
- **gofmt**: Code formatting
|
||||
- **go-vet**: Static analysis
|
||||
- **golangci-lint**: Comprehensive linting
|
||||
|
||||
### JavaScript/TypeScript Hooks
|
||||
- **prettier**: Code formatting
|
||||
- **eslint**: Linting and code quality
|
||||
|
||||
### Markdown Hooks
|
||||
- **markdownlint**: Markdown linting and formatting
|
||||
|
||||
### Security Hooks
|
||||
- **detect-private-key**: Prevent committing private keys
|
||||
- **detect-aws-credentials**: Prevent committing AWS credentials
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Full Quality Check
|
||||
|
||||
```bash
|
||||
# Run all hooks on all files
|
||||
source .venv/bin/activate
|
||||
.github/skills/scripts/skill-runner.sh qa-precommit-all
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
Trim Trailing Whitespace.....................................Passed
|
||||
Fix End of Files.............................................Passed
|
||||
Check Yaml...................................................Passed
|
||||
Check JSON...................................................Passed
|
||||
Check for merge conflicts....................................Passed
|
||||
Check for added large files..................................Passed
|
||||
black........................................................Passed
|
||||
isort........................................................Passed
|
||||
prettier.....................................................Passed
|
||||
eslint.......................................................Passed
|
||||
markdownlint.................................................Passed
|
||||
```
|
||||
|
||||
### Example 2: Quick Staged Files Check
|
||||
|
||||
```bash
|
||||
# Run only on staged files (faster for pre-commit)
|
||||
.github/skills/scripts/skill-runner.sh qa-precommit-all staged
|
||||
```
|
||||
|
||||
### Example 3: Skip Slow Hooks
|
||||
|
||||
```bash
|
||||
# Skip time-consuming hooks for quick validation
|
||||
SKIP=golangci-lint,mypy .github/skills/scripts/skill-runner.sh qa-precommit-all
|
||||
```
|
||||
|
||||
### Example 4: CI/CD Pipeline Integration
|
||||
|
||||
```yaml
|
||||
# GitHub Actions example
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install pre-commit
|
||||
run: pip install pre-commit
|
||||
|
||||
- name: Run QA Pre-commit Checks
|
||||
run: .github/skills/scripts/skill-runner.sh qa-precommit-all
|
||||
```
|
||||
|
||||
### Example 5: Auto-fix Mode
|
||||
|
||||
```bash
|
||||
# Some hooks can auto-fix issues
|
||||
# Run twice: first to fix, second to validate
|
||||
.github/skills/scripts/skill-runner.sh qa-precommit-all || \
|
||||
.github/skills/scripts/skill-runner.sh qa-precommit-all
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Virtual environment not activated**:
|
||||
```bash
|
||||
Error: pre-commit not found
|
||||
Solution: source .venv/bin/activate
|
||||
```
|
||||
|
||||
**Pre-commit not installed**:
|
||||
```bash
|
||||
Error: pre-commit command not available
|
||||
Solution: pip install pre-commit
|
||||
```
|
||||
|
||||
**Hooks not installed**:
|
||||
```bash
|
||||
Error: Run 'pre-commit install'
|
||||
Solution: pre-commit install
|
||||
```
|
||||
|
||||
**Hook execution failed**:
|
||||
```bash
|
||||
Hook X failed
|
||||
Solution: Review error output and fix reported issues
|
||||
```
|
||||
|
||||
**Language tool missing**:
|
||||
```bash
|
||||
Error: golangci-lint not found
|
||||
Solution: Install required language tools
|
||||
```
|
||||
|
||||
## Exit Codes
|
||||
|
||||
- **0**: All hooks passed
|
||||
- **1**: One or more hooks failed
|
||||
- **Other**: Hook execution error
|
||||
|
||||
## Hook Fixing Strategies
|
||||
|
||||
### Auto-fixable Issues
|
||||
These hooks automatically fix issues:
|
||||
- `trailing-whitespace`
|
||||
- `end-of-file-fixer`
|
||||
- `black`
|
||||
- `isort`
|
||||
- `prettier`
|
||||
- `gofmt`
|
||||
|
||||
**Workflow**: Run pre-commit, review changes, commit fixed files
|
||||
|
||||
### Manual Fixes Required
|
||||
These hooks only report issues:
|
||||
- `check-yaml`
|
||||
- `check-json`
|
||||
- `flake8`
|
||||
- `eslint`
|
||||
- `markdownlint`
|
||||
- `go-vet`
|
||||
- `golangci-lint`
|
||||
|
||||
**Workflow**: Review errors, manually fix code, re-run pre-commit
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [test-backend-coverage](./test-backend-coverage.SKILL.md) - Backend test coverage
|
||||
- [test-frontend-coverage](./test-frontend-coverage.SKILL.md) - Frontend test coverage
|
||||
- [security-scan-trivy](./security-scan-trivy.SKILL.md) - Security scanning
|
||||
|
||||
## Notes
|
||||
|
||||
- Pre-commit hooks cache their environments for faster execution
|
||||
- First run may be slow while environments are set up
|
||||
- Subsequent runs are much faster (seconds vs minutes)
|
||||
- Hooks run in parallel where possible
|
||||
- Failed hooks stop execution (fail-fast behavior)
|
||||
- Use `SKIP` to bypass specific hooks temporarily
|
||||
- Recommended to run before every commit
|
||||
- Can be integrated into Git pre-commit hook for automatic checks
|
||||
- Cache location: `~/.cache/pre-commit` (configurable)
|
||||
|
||||
## Performance Tips
|
||||
|
||||
- **Initial Setup**: First run takes longer (installing hook environments)
|
||||
- **Incremental**: Run on staged files only for faster feedback
|
||||
- **Parallel**: Pre-commit runs compatible hooks in parallel
|
||||
- **Cache**: Hook environments are cached and reused
|
||||
- **Skip**: Use `SKIP` to bypass slow hooks during development
|
||||
|
||||
## Integration with Git
|
||||
|
||||
To automatically run on every commit:
|
||||
|
||||
```bash
|
||||
# Install Git pre-commit hook
|
||||
pre-commit install
|
||||
|
||||
# Now pre-commit runs automatically on git commit
|
||||
git commit -m "Your commit message"
|
||||
```
|
||||
|
||||
To bypass pre-commit hook temporarily:
|
||||
|
||||
```bash
|
||||
git commit --no-verify -m "Emergency commit"
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Pre-commit configuration is in `.pre-commit-config.yaml`. To update hooks:
|
||||
|
||||
```bash
|
||||
# Update to latest versions
|
||||
pre-commit autoupdate
|
||||
|
||||
# Clean cache and re-install
|
||||
pre-commit clean
|
||||
pre-commit install --install-hooks
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-12-20
|
||||
**Maintained by**: Charon Project
|
||||
**Source**: `pre-commit run --all-files`
|
||||
26
.github/skills/qa-precommit-all.SKILL.md
vendored
26
.github/skills/qa-precommit-all.SKILL.md
vendored
@@ -1,8 +1,8 @@
|
||||
---
|
||||
# agentskills.io specification v1.0
|
||||
name: "qa-precommit-all"
|
||||
name: "qa-lefthook-all"
|
||||
version: "1.0.0"
|
||||
description: "Run all pre-commit hooks for comprehensive code quality validation"
|
||||
description: "Run all lefthook pre-commit-phase hooks for comprehensive code quality validation"
|
||||
author: "Charon Project"
|
||||
license: "MIT"
|
||||
tags:
|
||||
@@ -21,15 +21,11 @@ requirements:
|
||||
- name: "python3"
|
||||
version: ">=3.8"
|
||||
optional: false
|
||||
- name: "pre-commit"
|
||||
version: ">=2.0"
|
||||
- name: "lefthook"
|
||||
version: ">=0.14"
|
||||
optional: false
|
||||
environment_variables:
|
||||
- name: "PRE_COMMIT_HOME"
|
||||
description: "Pre-commit cache directory"
|
||||
default: "~/.cache/pre-commit"
|
||||
required: false
|
||||
- name: "SKIP"
|
||||
- name: "SKIP"
|
||||
description: "Comma-separated list of hook IDs to skip"
|
||||
default: ""
|
||||
required: false
|
||||
@@ -60,7 +56,7 @@ metadata:
|
||||
|
||||
## Overview
|
||||
|
||||
Executes all configured pre-commit hooks to validate code quality, formatting, security, and best practices across the entire codebase. This skill runs checks for Python, Go, JavaScript/TypeScript, Markdown, YAML, and more.
|
||||
Executes all configured lefthook pre-commit-phase hooks to validate code quality, formatting, security, and best practices across the entire codebase. This skill runs checks for Python, Go, JavaScript/TypeScript, Markdown, YAML, and more.
|
||||
|
||||
This skill is designed for CI/CD pipelines and local quality validation before committing code.
|
||||
|
||||
@@ -76,19 +72,19 @@ This skill is designed for CI/CD pipelines and local quality validation before c
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Run all hooks on all files:
|
||||
Run all pre-commit-phase hooks on all files:
|
||||
|
||||
```bash
|
||||
cd /path/to/charon
|
||||
.github/skills/scripts/skill-runner.sh qa-precommit-all
|
||||
lefthook run pre-commit
|
||||
```
|
||||
|
||||
### Staged Files Only
|
||||
|
||||
Run hooks on staged files only (faster):
|
||||
Run lefthook on staged files only (faster):
|
||||
|
||||
```bash
|
||||
.github/skills/scripts/skill-runner.sh qa-precommit-all staged
|
||||
lefthook run pre-commit --staged
|
||||
```
|
||||
|
||||
### Specific Hook
|
||||
@@ -96,7 +92,7 @@ Run hooks on staged files only (faster):
|
||||
Run only a specific hook by ID:
|
||||
|
||||
```bash
|
||||
SKIP="" .github/skills/scripts/skill-runner.sh qa-precommit-all trailing-whitespace
|
||||
lefthook run pre-commit --hooks=trailing-whitespace
|
||||
```
|
||||
|
||||
### Skip Specific Hooks
|
||||
|
||||
96
.github/skills/scripts/_environment_helpers.sh
vendored
96
.github/skills/scripts/_environment_helpers.sh
vendored
@@ -192,6 +192,101 @@ get_project_root() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# ensure_charon_encryption_key: Ensure CHARON_ENCRYPTION_KEY is present and valid
|
||||
# for backend tests. Generates an ephemeral base64-encoded 32-byte key when
|
||||
# missing or invalid.
|
||||
ensure_charon_encryption_key() {
|
||||
local key_source="existing"
|
||||
local decoded_key_hex=""
|
||||
local decoded_key_bytes=0
|
||||
|
||||
generate_key() {
|
||||
if command -v openssl >/dev/null 2>&1; then
|
||||
openssl rand -base64 32 | tr -d '\n'
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
python3 - <<'PY'
|
||||
import base64
|
||||
import os
|
||||
|
||||
print(base64.b64encode(os.urandom(32)).decode())
|
||||
PY
|
||||
return
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
if [[ -z "${CHARON_ENCRYPTION_KEY:-}" ]]; then
|
||||
key_source="generated"
|
||||
CHARON_ENCRYPTION_KEY="$(generate_key)"
|
||||
fi
|
||||
|
||||
if [[ -z "${CHARON_ENCRYPTION_KEY:-}" ]]; then
|
||||
if declare -f log_error >/dev/null 2>&1; then
|
||||
log_error "Could not auto-provision CHARON_ENCRYPTION_KEY (requires openssl or python3)"
|
||||
else
|
||||
echo "[ERROR] Could not auto-provision CHARON_ENCRYPTION_KEY (requires openssl or python3)" >&2
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! decoded_key_hex=$(printf '%s' "$CHARON_ENCRYPTION_KEY" | base64 --decode 2>/dev/null | od -An -tx1 -v | tr -d ' \n'); then
|
||||
key_source="regenerated"
|
||||
CHARON_ENCRYPTION_KEY="$(generate_key)"
|
||||
if ! decoded_key_hex=$(printf '%s' "$CHARON_ENCRYPTION_KEY" | base64 --decode 2>/dev/null | od -An -tx1 -v | tr -d ' \n'); then
|
||||
if declare -f log_error >/dev/null 2>&1; then
|
||||
log_error "CHARON_ENCRYPTION_KEY is invalid and regeneration failed"
|
||||
else
|
||||
echo "[ERROR] CHARON_ENCRYPTION_KEY is invalid and regeneration failed" >&2
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
decoded_key_bytes=$(( ${#decoded_key_hex} / 2 ))
|
||||
if [[ "$decoded_key_bytes" -ne 32 ]]; then
|
||||
key_source="regenerated"
|
||||
CHARON_ENCRYPTION_KEY="$(generate_key)"
|
||||
if ! decoded_key_hex=$(printf '%s' "$CHARON_ENCRYPTION_KEY" | base64 --decode 2>/dev/null | od -An -tx1 -v | tr -d ' \n'); then
|
||||
if declare -f log_error >/dev/null 2>&1; then
|
||||
log_error "CHARON_ENCRYPTION_KEY has invalid length and regeneration failed"
|
||||
else
|
||||
echo "[ERROR] CHARON_ENCRYPTION_KEY has invalid length and regeneration failed" >&2
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
decoded_key_bytes=$(( ${#decoded_key_hex} / 2 ))
|
||||
if [[ "$decoded_key_bytes" -ne 32 ]]; then
|
||||
if declare -f log_error >/dev/null 2>&1; then
|
||||
log_error "Could not provision a valid 32-byte CHARON_ENCRYPTION_KEY"
|
||||
else
|
||||
echo "[ERROR] Could not provision a valid 32-byte CHARON_ENCRYPTION_KEY" >&2
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
export CHARON_ENCRYPTION_KEY
|
||||
|
||||
if [[ "$key_source" == "generated" ]]; then
|
||||
if declare -f log_info >/dev/null 2>&1; then
|
||||
log_info "CHARON_ENCRYPTION_KEY not set; generated ephemeral test key"
|
||||
fi
|
||||
elif [[ "$key_source" == "regenerated" ]]; then
|
||||
if declare -f log_warn >/dev/null 2>&1; then
|
||||
log_warn "CHARON_ENCRYPTION_KEY invalid; generated ephemeral test key"
|
||||
elif declare -f log_info >/dev/null 2>&1; then
|
||||
log_info "CHARON_ENCRYPTION_KEY invalid; generated ephemeral test key"
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Export functions
|
||||
export -f validate_go_environment
|
||||
export -f validate_python_environment
|
||||
@@ -200,3 +295,4 @@ export -f validate_docker_environment
|
||||
export -f set_default_env
|
||||
export -f validate_project_structure
|
||||
export -f get_project_root
|
||||
export -f ensure_charon_encryption_key
|
||||
|
||||
@@ -95,6 +95,7 @@ run_codeql_scan() {
|
||||
local source_root=$2
|
||||
local db_name="codeql-db-${lang}"
|
||||
local sarif_file="codeql-results-${lang}.sarif"
|
||||
local suite=""
|
||||
local build_mode_args=()
|
||||
local codescanning_config="${PROJECT_ROOT}/.github/codeql/codeql-config.yml"
|
||||
|
||||
@@ -107,6 +108,9 @@ run_codeql_scan() {
|
||||
|
||||
if [[ "${lang}" == "javascript" ]]; then
|
||||
build_mode_args=(--build-mode=none)
|
||||
suite="codeql/javascript-queries:codeql-suites/javascript-security-and-quality.qls"
|
||||
else
|
||||
suite="codeql/go-queries:codeql-suites/go-security-and-quality.qls"
|
||||
fi
|
||||
|
||||
log_step "CODEQL" "Scanning ${lang} code in ${source_root}/"
|
||||
@@ -135,8 +139,9 @@ run_codeql_scan() {
|
||||
fi
|
||||
|
||||
# Run analysis
|
||||
log_info "Analyzing with Code Scanning config (CI-aligned query filters)..."
|
||||
log_info "Analyzing with CI-aligned suite: ${suite}"
|
||||
if ! codeql database analyze "${db_name}" \
|
||||
"${suite}" \
|
||||
--format=sarif-latest \
|
||||
--output="${sarif_file}" \
|
||||
--sarif-add-baseline-file-info \
|
||||
|
||||
9
.github/skills/security-scan-codeql.SKILL.md
vendored
9
.github/skills/security-scan-codeql.SKILL.md
vendored
@@ -136,8 +136,8 @@ This skill uses the **security-and-quality** suite to match CI:
|
||||
|
||||
| Language | Suite | Queries | Coverage |
|
||||
|----------|-------|---------|----------|
|
||||
| Go | go-security-and-quality.qls | 61 | Security + quality issues |
|
||||
| JavaScript | javascript-security-and-quality.qls | 204 | Security + quality issues |
|
||||
| Go | go-security-and-quality.qls | version-dependent | Security + quality issues |
|
||||
| JavaScript | javascript-security-and-quality.qls | version-dependent | Security + quality issues |
|
||||
|
||||
**Note:** This matches GitHub Actions CodeQL default configuration exactly.
|
||||
|
||||
@@ -251,7 +251,7 @@ Solution: Verify source-root points to correct directory
|
||||
|
||||
- [security-scan-trivy](./security-scan-trivy.SKILL.md) - Container/dependency vulnerabilities
|
||||
- [security-scan-go-vuln](./security-scan-go-vuln.SKILL.md) - Go-specific CVE checking
|
||||
- [qa-precommit-all](./qa-precommit-all.SKILL.md) - Pre-commit quality checks
|
||||
- [qa-lefthook-all](./qa-lefthook-all.SKILL.md) - Lefthook pre-commit-phase quality checks
|
||||
|
||||
## CI Alignment
|
||||
|
||||
@@ -260,8 +260,7 @@ This skill is specifically designed to match GitHub Actions CodeQL workflow:
|
||||
| Parameter | Local | CI | Aligned |
|
||||
|-----------|-------|-----|---------|
|
||||
| Query Suite | security-and-quality | security-and-quality | ✅ |
|
||||
| Go Queries | 61 | 61 | ✅ |
|
||||
| JS Queries | 204 | 204 | ✅ |
|
||||
| Query Expansion | version-dependent | version-dependent | ✅ (when versions match) |
|
||||
| Threading | auto | auto | ✅ |
|
||||
| Baseline Info | enabled | enabled | ✅ |
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ fi
|
||||
# Check Grype
|
||||
if ! command -v grype >/dev/null 2>&1; then
|
||||
log_error "Grype not found - install from: https://github.com/anchore/grype"
|
||||
log_error "Installation: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin v0.107.0"
|
||||
log_error "Installation: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin v0.110.0"
|
||||
error_exit "Grype is required for vulnerability scanning" 2
|
||||
fi
|
||||
|
||||
@@ -50,8 +50,8 @@ SYFT_INSTALLED_VERSION=$(syft version | grep -oP 'Version:\s*\Kv?[0-9]+\.[0-9]+\
|
||||
GRYPE_INSTALLED_VERSION=$(grype version | grep -oP 'Version:\s*\Kv?[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "unknown")
|
||||
|
||||
# Set defaults matching CI workflow
|
||||
set_default_env "SYFT_VERSION" "v1.17.0"
|
||||
set_default_env "GRYPE_VERSION" "v0.107.0"
|
||||
set_default_env "SYFT_VERSION" "v1.42.3"
|
||||
set_default_env "GRYPE_VERSION" "v0.110.0"
|
||||
set_default_env "IMAGE_TAG" "charon:local"
|
||||
set_default_env "FAIL_ON_SEVERITY" "Critical,High"
|
||||
|
||||
|
||||
2
.github/skills/security-scan-gorm.SKILL.md
vendored
2
.github/skills/security-scan-gorm.SKILL.md
vendored
@@ -545,7 +545,7 @@ Solution: Add suppression comment: // gorm-scanner:ignore [reason]
|
||||
|
||||
- [security-scan-trivy](./security-scan-trivy.SKILL.md) - Container vulnerability scanning
|
||||
- [security-scan-codeql](./security-scan-codeql.SKILL.md) - Static analysis for Go/JS
|
||||
- [qa-precommit-all](./qa-precommit-all.SKILL.md) - Pre-commit quality checks
|
||||
- [qa-lefthook-all](./qa-lefthook-all.SKILL.md) - Lefthook pre-commit-phase quality checks
|
||||
|
||||
## Best Practices
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ validate_docker_environment || error_exit "Docker is required but not available"
|
||||
# Set defaults
|
||||
set_default_env "TRIVY_SEVERITY" "CRITICAL,HIGH,MEDIUM"
|
||||
set_default_env "TRIVY_TIMEOUT" "10m"
|
||||
set_default_env "TRIVY_DOCKER_RM" "true"
|
||||
|
||||
# Parse arguments
|
||||
# Default scanners exclude misconfig to avoid non-actionable policy bundle issues
|
||||
@@ -88,8 +89,19 @@ for d in "${SKIP_DIRS[@]}"; do
|
||||
SKIP_DIR_FLAGS+=("--skip-dirs" "/app/${d}")
|
||||
done
|
||||
|
||||
log_step "PREPARE" "Pulling latest Trivy Docker image"
|
||||
if ! docker pull aquasec/trivy:latest >/dev/null; then
|
||||
log_error "Failed to pull Docker image aquasec/trivy:latest"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run Trivy via Docker
|
||||
if docker run --rm \
|
||||
DOCKER_RUN_ARGS=(run)
|
||||
if [[ "${TRIVY_DOCKER_RM}" == "true" ]]; then
|
||||
DOCKER_RUN_ARGS+=(--rm)
|
||||
fi
|
||||
|
||||
if docker "${DOCKER_RUN_ARGS[@]}" \
|
||||
-v "$(pwd):/app:ro" \
|
||||
-e "TRIVY_SEVERITY=${TRIVY_SEVERITY}" \
|
||||
-e "TRIVY_TIMEOUT=${TRIVY_TIMEOUT}" \
|
||||
|
||||
2
.github/skills/security-scan-trivy.SKILL.md
vendored
2
.github/skills/security-scan-trivy.SKILL.md
vendored
@@ -227,7 +227,7 @@ Solution: Review and remediate reported vulnerabilities
|
||||
## Related Skills
|
||||
|
||||
- [security-scan-go-vuln](./security-scan-go-vuln.SKILL.md) - Go-specific vulnerability checking
|
||||
- [qa-precommit-all](./qa-precommit-all.SKILL.md) - Pre-commit quality checks
|
||||
- [qa-lefthook-all](./qa-lefthook-all.SKILL.md) - Lefthook pre-commit-phase quality checks
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ cd "${PROJECT_ROOT}"
|
||||
validate_project_structure "backend" "scripts/go-test-coverage.sh" || error_exit "Invalid project structure"
|
||||
|
||||
# Set default environment variables
|
||||
set_default_env "CHARON_MIN_COVERAGE" "85"
|
||||
set_default_env "CHARON_MIN_COVERAGE" "87"
|
||||
set_default_env "PERF_MAX_MS_GETSTATUS_P95" "25ms"
|
||||
set_default_env "PERF_MAX_MS_GETSTATUS_P95_PARALLEL" "50ms"
|
||||
set_default_env "PERF_MAX_MS_LISTDECISIONS_P95" "75ms"
|
||||
|
||||
@@ -25,6 +25,10 @@ requirements:
|
||||
version: ">=3.8"
|
||||
optional: false
|
||||
environment_variables:
|
||||
- name: "CHARON_ENCRYPTION_KEY"
|
||||
description: "Encryption key for backend test runtime. Auto-generated ephemerally by the script if missing/invalid."
|
||||
default: "(auto-generated for test run)"
|
||||
required: false
|
||||
- name: "CHARON_MIN_COVERAGE"
|
||||
description: "Minimum coverage percentage required (overrides default)"
|
||||
default: "85"
|
||||
@@ -125,6 +129,7 @@ For use in GitHub Actions or other CI/CD pipelines:
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|----------|----------|---------|-------------|
|
||||
| CHARON_ENCRYPTION_KEY | No | auto-generated for test run | Backend test encryption key. If missing/invalid, an ephemeral 32-byte base64 key is generated for the run. |
|
||||
| CHARON_MIN_COVERAGE | No | 85 | Minimum coverage percentage required for success |
|
||||
| CPM_MIN_COVERAGE | No | 85 | Legacy name for minimum coverage (fallback) |
|
||||
| PERF_MAX_MS_GETSTATUS_P95 | No | 25ms | Max P95 latency for GetStatus endpoint |
|
||||
|
||||
@@ -11,10 +11,13 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# Helper scripts are in .github/skills/scripts/
|
||||
SKILLS_SCRIPTS_DIR="$(cd "${SCRIPT_DIR}/../scripts" && pwd)"
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
# shellcheck source=../scripts/_logging_helpers.sh
|
||||
source "${SKILLS_SCRIPTS_DIR}/_logging_helpers.sh"
|
||||
# shellcheck disable=SC1091
|
||||
# shellcheck source=../scripts/_error_handling_helpers.sh
|
||||
source "${SKILLS_SCRIPTS_DIR}/_error_handling_helpers.sh"
|
||||
# shellcheck disable=SC1091
|
||||
# shellcheck source=../scripts/_environment_helpers.sh
|
||||
source "${SKILLS_SCRIPTS_DIR}/_environment_helpers.sh"
|
||||
|
||||
@@ -24,6 +27,7 @@ PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
|
||||
# Validate environment
|
||||
log_step "ENVIRONMENT" "Validating prerequisites"
|
||||
validate_go_environment "1.23" || error_exit "Go 1.23+ is required"
|
||||
ensure_charon_encryption_key || error_exit "Failed to provision CHARON_ENCRYPTION_KEY for backend tests"
|
||||
|
||||
# Validate project structure
|
||||
log_step "VALIDATION" "Checking project structure"
|
||||
|
||||
10
.github/skills/test-backend-unit.SKILL.md
vendored
10
.github/skills/test-backend-unit.SKILL.md
vendored
@@ -21,7 +21,11 @@ requirements:
|
||||
- name: "go"
|
||||
version: ">=1.23"
|
||||
optional: false
|
||||
environment_variables: []
|
||||
environment_variables:
|
||||
- name: "CHARON_ENCRYPTION_KEY"
|
||||
description: "Encryption key for backend test runtime. Auto-generated ephemerally if missing/invalid."
|
||||
default: "(auto-generated for test run)"
|
||||
required: false
|
||||
parameters:
|
||||
- name: "verbose"
|
||||
type: "boolean"
|
||||
@@ -106,7 +110,9 @@ For use in GitHub Actions or other CI/CD pipelines:
|
||||
|
||||
## Environment Variables
|
||||
|
||||
No environment variables are required for this skill.
|
||||
| Variable | Required | Default | Description |
|
||||
|----------|----------|---------|-------------|
|
||||
| CHARON_ENCRYPTION_KEY | No | auto-generated for test run | Backend test encryption key. If missing/invalid, an ephemeral 32-byte base64 key is generated for the run. |
|
||||
|
||||
## Outputs
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ cd "${PROJECT_ROOT}"
|
||||
validate_project_structure "frontend" "scripts/frontend-test-coverage.sh" || error_exit "Invalid project structure"
|
||||
|
||||
# Set default environment variables
|
||||
set_default_env "CHARON_MIN_COVERAGE" "85"
|
||||
set_default_env "CHARON_MIN_COVERAGE" "87"
|
||||
|
||||
# Execute the legacy script
|
||||
log_step "EXECUTION" "Running frontend tests with coverage"
|
||||
|
||||
2
.github/workflows/auto-changelog.yml
vendored
2
.github/workflows/auto-changelog.yml
vendored
@@ -21,6 +21,6 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
- name: Draft Release
|
||||
uses: release-drafter/release-drafter@6db134d15f3909ccc9eefd369f02bd1e9cffdf97 # v6
|
||||
uses: release-drafter/release-drafter@139054aeaa9adc52ab36ddf67437541f039b88e2 # v7
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
4
.github/workflows/auto-versioning.yml
vendored
4
.github/workflows/auto-versioning.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
- name: Calculate Semantic Version
|
||||
id: semver
|
||||
uses: paulhatch/semantic-version@f29500c9d60a99ed5168e39ee367e0976884c46e # v6.0.1
|
||||
uses: paulhatch/semantic-version@9f72830310d5ed81233b641ee59253644cd8a8fc # v6.0.2
|
||||
with:
|
||||
# The prefix to use to create tags
|
||||
tag_prefix: "v"
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
|
||||
- name: Create GitHub Release (creates tag via API)
|
||||
if: ${{ steps.semver.outputs.changed == 'true' && steps.check_release.outputs.exists == 'false' }}
|
||||
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
|
||||
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2
|
||||
with:
|
||||
tag_name: ${{ steps.determine_tag.outputs.tag }}
|
||||
name: Release ${{ steps.determine_tag.outputs.tag }}
|
||||
|
||||
54
.github/workflows/badge-ghcr-downloads.yml
vendored
54
.github/workflows/badge-ghcr-downloads.yml
vendored
@@ -1,54 +0,0 @@
|
||||
name: "Badge: GHCR downloads"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Update periodically (GitHub schedules may be delayed)
|
||||
- cron: '17 * * * *'
|
||||
workflow_dispatch: {}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: read
|
||||
|
||||
concurrency:
|
||||
group: ghcr-downloads-badge
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout (main)
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
with:
|
||||
node-version: 24.13.1
|
||||
|
||||
- name: Update GHCR downloads badge
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GHCR_OWNER: ${{ github.repository_owner }}
|
||||
GHCR_PACKAGE: charon
|
||||
BADGE_OUTPUT: .github/badges/ghcr-downloads.json
|
||||
run: node scripts/update-ghcr-downloads-badge.mjs
|
||||
|
||||
- name: Commit and push (if changed)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if git diff --quiet; then
|
||||
echo "No changes."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git add .github/badges/ghcr-downloads.json
|
||||
git commit -m "chore(badges): update GHCR downloads [skip ci]"
|
||||
git push origin HEAD:main
|
||||
7
.github/workflows/benchmark.yml
vendored
7
.github/workflows/benchmark.yml
vendored
@@ -3,6 +3,8 @@ name: Go Benchmark
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
@@ -10,7 +12,7 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.26.0'
|
||||
GO_VERSION: '1.26.1'
|
||||
GOTOOLCHAIN: auto
|
||||
|
||||
# Minimal permissions at workflow level; write permissions granted at job level for push only
|
||||
@@ -33,9 +35,10 @@ jobs:
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
cache-dependency-path: backend/go.sum
|
||||
|
||||
- name: Run Benchmark
|
||||
|
||||
2
.github/workflows/cerberus-integration.yml
vendored
2
.github/workflows/cerberus-integration.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Build Docker image (Local)
|
||||
run: |
|
||||
echo "Building image locally for integration tests..."
|
||||
docker build -t charon:local .
|
||||
docker build -t charon:local --build-arg CI="${CI:-false}" .
|
||||
echo "✅ Successfully built charon:local"
|
||||
|
||||
- name: Run Cerberus integration tests
|
||||
|
||||
15
.github/workflows/codecov-upload.yml
vendored
15
.github/workflows/codecov-upload.yml
vendored
@@ -3,6 +3,8 @@ name: Upload Coverage to Codecov
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
run_backend:
|
||||
@@ -17,11 +19,11 @@ on:
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.run_id }}
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.26.0'
|
||||
GO_VERSION: '1.26.1'
|
||||
NODE_VERSION: '24.12.0'
|
||||
GOTOOLCHAIN: auto
|
||||
|
||||
@@ -43,9 +45,10 @@ jobs:
|
||||
ref: ${{ github.sha }}
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
cache-dependency-path: backend/go.sum
|
||||
|
||||
# SECURITY: Keep pull_request (not pull_request_target) for secret-bearing backend tests.
|
||||
@@ -132,7 +135,7 @@ jobs:
|
||||
exit "${PIPESTATUS[0]}"
|
||||
|
||||
- name: Upload backend coverage to Codecov
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./backend/coverage.txt
|
||||
@@ -152,7 +155,7 @@ jobs:
|
||||
ref: ${{ github.sha }}
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
@@ -169,7 +172,7 @@ jobs:
|
||||
exit "${PIPESTATUS[0]}"
|
||||
|
||||
- name: Upload frontend coverage to Codecov
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
|
||||
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
directory: ./frontend/coverage
|
||||
|
||||
89
.github/workflows/codeql.yml
vendored
89
.github/workflows/codeql.yml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
pull_request:
|
||||
branches: [main, nightly, development]
|
||||
push:
|
||||
branches: [main, nightly, development, 'feature/**', 'fix/**']
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 3 * * 1' # Mondays 03:00 UTC
|
||||
@@ -15,6 +15,7 @@ concurrency:
|
||||
|
||||
env:
|
||||
GOTOOLCHAIN: auto
|
||||
GO_VERSION: '1.26.1'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -39,14 +40,19 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
# Use github.ref (full ref path) instead of github.ref_name:
|
||||
# - push/schedule: resolves to refs/heads/<branch>, checking out latest HEAD
|
||||
# - pull_request: resolves to refs/pull/<n>/merge, the correct PR merge ref
|
||||
# github.ref_name fails for PRs because it yields "<n>/merge" which checkout
|
||||
# interprets as a branch name (refs/heads/<n>/merge) that does not exist.
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
- name: Verify CodeQL parity guard
|
||||
if: matrix.language == 'go'
|
||||
run: bash scripts/ci/check-codeql-parity.sh
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4
|
||||
uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: security-and-quality
|
||||
@@ -57,9 +63,9 @@ jobs:
|
||||
|
||||
- name: Setup Go
|
||||
if: matrix.language == 'go'
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
with:
|
||||
go-version: 1.26.0
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache-dependency-path: backend/go.sum
|
||||
|
||||
- name: Verify Go toolchain and build
|
||||
@@ -86,10 +92,10 @@ jobs:
|
||||
run: mkdir -p sarif-results
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@9e907b5e64f6b83e7804b09294d44122997950d6 # v4
|
||||
uses: github/codeql-action/autobuild@38697555549f1db7851b81482ff19f1fa5c4fedc # v4
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v4
|
||||
uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
output: sarif-results/${{ matrix.language }}
|
||||
@@ -122,10 +128,28 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2016
|
||||
EFFECTIVE_LEVELS_JQ='[
|
||||
.runs[] as $run
|
||||
| $run.results[]
|
||||
| . as $result
|
||||
| ($run.tool.driver.rules // []) as $rules
|
||||
| ((
|
||||
$result.level
|
||||
// (if (($result.ruleIndex | type) == "number") then ($rules[$result.ruleIndex].defaultConfiguration.level // empty) else empty end)
|
||||
// ([
|
||||
$rules[]?
|
||||
| select((.id // "") == ($result.ruleId // ""))
|
||||
| (.defaultConfiguration.level // empty)
|
||||
][0] // empty)
|
||||
// ""
|
||||
) | ascii_downcase)
|
||||
]'
|
||||
|
||||
echo "Found SARIF file: $SARIF_FILE"
|
||||
ERROR_COUNT=$(jq '[.runs[].results[] | select(.level == "error")] | length' "$SARIF_FILE")
|
||||
WARNING_COUNT=$(jq '[.runs[].results[] | select(.level == "warning")] | length' "$SARIF_FILE")
|
||||
NOTE_COUNT=$(jq '[.runs[].results[] | select(.level == "note")] | length' "$SARIF_FILE")
|
||||
ERROR_COUNT=$(jq -r "${EFFECTIVE_LEVELS_JQ} | map(select(. == \"error\")) | length" "$SARIF_FILE")
|
||||
WARNING_COUNT=$(jq -r "${EFFECTIVE_LEVELS_JQ} | map(select(. == \"warning\")) | length" "$SARIF_FILE")
|
||||
NOTE_COUNT=$(jq -r "${EFFECTIVE_LEVELS_JQ} | map(select(. == \"note\")) | length" "$SARIF_FILE")
|
||||
|
||||
{
|
||||
echo "**Findings:**"
|
||||
@@ -135,14 +159,32 @@ jobs:
|
||||
echo ""
|
||||
|
||||
if [ "$ERROR_COUNT" -gt 0 ]; then
|
||||
echo "❌ **CRITICAL:** High-severity security issues found!"
|
||||
echo "❌ **BLOCKING:** CodeQL error-level security issues found"
|
||||
echo ""
|
||||
echo "### Top Issues:"
|
||||
echo '```'
|
||||
jq -r '.runs[].results[] | select(.level == "error") | "\(.ruleId): \(.message.text)"' "$SARIF_FILE" | head -5
|
||||
# shellcheck disable=SC2016
|
||||
jq -r '
|
||||
.runs[] as $run
|
||||
| $run.results[]
|
||||
| . as $result
|
||||
| ($run.tool.driver.rules // []) as $rules
|
||||
| ((
|
||||
$result.level
|
||||
// (if (($result.ruleIndex | type) == "number") then ($rules[$result.ruleIndex].defaultConfiguration.level // empty) else empty end)
|
||||
// ([
|
||||
$rules[]?
|
||||
| select((.id // "") == ($result.ruleId // ""))
|
||||
| (.defaultConfiguration.level // empty)
|
||||
][0] // empty)
|
||||
// ""
|
||||
) | ascii_downcase) as $effectiveLevel
|
||||
| select($effectiveLevel == "error")
|
||||
| "\($effectiveLevel): \($result.ruleId // \"<unknown-rule>\"): \($result.message.text)"
|
||||
' "$SARIF_FILE" | head -5
|
||||
echo '```'
|
||||
else
|
||||
echo "✅ No high-severity issues found"
|
||||
echo "✅ No blocking CodeQL issues found"
|
||||
fi
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
@@ -169,9 +211,26 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ERROR_COUNT=$(jq '[.runs[].results[] | select(.level == "error")] | length' "$SARIF_FILE")
|
||||
# shellcheck disable=SC2016
|
||||
ERROR_COUNT=$(jq -r '[
|
||||
.runs[] as $run
|
||||
| $run.results[]
|
||||
| . as $result
|
||||
| ($run.tool.driver.rules // []) as $rules
|
||||
| ((
|
||||
$result.level
|
||||
// (if (($result.ruleIndex | type) == "number") then ($rules[$result.ruleIndex].defaultConfiguration.level // empty) else empty end)
|
||||
// ([
|
||||
$rules[]?
|
||||
| select((.id // "") == ($result.ruleId // ""))
|
||||
| (.defaultConfiguration.level // empty)
|
||||
][0] // empty)
|
||||
// ""
|
||||
) | ascii_downcase) as $effectiveLevel
|
||||
| select($effectiveLevel == "error")
|
||||
] | length' "$SARIF_FILE")
|
||||
|
||||
if [ "$ERROR_COUNT" -gt 0 ]; then
|
||||
echo "::error::CodeQL found $ERROR_COUNT high-severity security issues. Fix before merging."
|
||||
echo "::error::CodeQL found $ERROR_COUNT blocking findings (effective-level=error). Fix before merging. Policy: .github/security-severity-policy.yml"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
186
.github/workflows/container-prune.yml
vendored
186
.github/workflows/container-prune.yml
vendored
@@ -5,10 +5,6 @@ on:
|
||||
- cron: '0 3 * * 0' # Weekly: Sundays at 03:00 UTC
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
registries:
|
||||
description: 'Comma-separated registries to prune (ghcr,dockerhub)'
|
||||
required: false
|
||||
default: 'ghcr,dockerhub'
|
||||
keep_days:
|
||||
description: 'Number of days to retain images (unprotected)'
|
||||
required: false
|
||||
@@ -27,16 +23,17 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
prune:
|
||||
prune-ghcr:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
OWNER: ${{ github.repository_owner }}
|
||||
IMAGE_NAME: charon
|
||||
REGISTRIES: ${{ github.event.inputs.registries || 'ghcr,dockerhub' }}
|
||||
KEEP_DAYS: ${{ github.event.inputs.keep_days || '30' }}
|
||||
KEEP_LAST_N: ${{ github.event.inputs.keep_last_n || '30' }}
|
||||
DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
|
||||
PROTECTED_REGEX: '["^v","^latest$","^main$","^develop$"]'
|
||||
DRY_RUN: ${{ github.event_name == 'pull_request' && 'true' || github.event.inputs.dry_run || 'false' }}
|
||||
PROTECTED_REGEX: '["^v?[0-9]+\\.[0-9]+\\.[0-9]+$","^latest$","^main$","^develop$"]'
|
||||
PRUNE_UNTAGGED: 'true'
|
||||
PRUNE_SBOM_TAGS: 'true'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
@@ -45,21 +42,19 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y jq curl
|
||||
|
||||
- name: Run container prune
|
||||
- name: Run GHCR prune
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
run: |
|
||||
chmod +x scripts/prune-container-images.sh
|
||||
./scripts/prune-container-images.sh 2>&1 | tee prune-${{ github.run_id }}.log
|
||||
chmod +x scripts/prune-ghcr.sh
|
||||
./scripts/prune-ghcr.sh 2>&1 | tee prune-ghcr-${{ github.run_id }}.log
|
||||
|
||||
- name: Summarize prune results (space reclaimed)
|
||||
if: ${{ always() }}
|
||||
- name: Summarize GHCR results
|
||||
if: always()
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SUMMARY_FILE=prune-summary.env
|
||||
LOG_FILE=prune-${{ github.run_id }}.log
|
||||
SUMMARY_FILE=prune-summary-ghcr.env
|
||||
LOG_FILE=prune-ghcr-${{ github.run_id }}.log
|
||||
|
||||
human() {
|
||||
local bytes=${1:-0}
|
||||
@@ -67,7 +62,7 @@ jobs:
|
||||
echo "0 B"
|
||||
return
|
||||
fi
|
||||
awk -v b="$bytes" 'function human(x){ split("B KiB MiB GiB TiB",u," "); i=0; while(x>1024){x/=1024;i++} printf "%0.2f %s", x, u[i+1]} END{human(b)}'
|
||||
awk -v b="$bytes" 'BEGIN { split("B KiB MiB GiB TiB",u," "); i=0; x=b; while(x>1024){x/=1024;i++} printf "%0.2f %s", x, u[i+1] }'
|
||||
}
|
||||
|
||||
if [ -f "$SUMMARY_FILE" ]; then
|
||||
@@ -77,34 +72,155 @@ jobs:
|
||||
TOTAL_DELETED_BYTES=$(grep -E '^TOTAL_DELETED_BYTES=' "$SUMMARY_FILE" | cut -d= -f2 || echo 0)
|
||||
|
||||
{
|
||||
echo "## Container prune summary"
|
||||
echo "## GHCR prune summary"
|
||||
echo "- candidates: ${TOTAL_CANDIDATES} (≈ $(human "${TOTAL_CANDIDATES_BYTES}"))"
|
||||
echo "- deleted: ${TOTAL_DELETED} (≈ $(human "${TOTAL_DELETED_BYTES}"))"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
printf 'PRUNE_SUMMARY: candidates=%s candidates_bytes=%s deleted=%s deleted_bytes=%s\n' \
|
||||
"${TOTAL_CANDIDATES}" "${TOTAL_CANDIDATES_BYTES}" "${TOTAL_DELETED}" "${TOTAL_DELETED_BYTES}"
|
||||
echo "Deleted approximately: $(human "${TOTAL_DELETED_BYTES}")"
|
||||
echo "space_saved=$(human "${TOTAL_DELETED_BYTES}")" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
deleted_bytes=$(grep -oE '\( *approx +[0-9]+ bytes\)' "$LOG_FILE" | sed -E 's/.*approx +([0-9]+) bytes.*/\1/' | awk '{s+=$1} END {print s+0}' || true)
|
||||
deleted_count=$(grep -cE 'deleting |DRY RUN: would delete' "$LOG_FILE" || true)
|
||||
|
||||
{
|
||||
echo "## Container prune summary"
|
||||
echo "## GHCR prune summary"
|
||||
echo "- deleted (approx): ${deleted_count} (≈ $(human "${deleted_bytes}"))"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
printf 'PRUNE_SUMMARY: deleted_approx=%s deleted_bytes=%s\n' "${deleted_count}" "${deleted_bytes}"
|
||||
echo "Deleted approximately: $(human "${deleted_bytes}")"
|
||||
echo "space_saved=$(human "${deleted_bytes}")" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Upload prune artifacts
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
- name: Upload GHCR prune artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: prune-log-${{ github.run_id }}
|
||||
name: prune-ghcr-log-${{ github.run_id }}
|
||||
path: |
|
||||
prune-${{ github.run_id }}.log
|
||||
prune-summary.env
|
||||
prune-ghcr-${{ github.run_id }}.log
|
||||
prune-summary-ghcr.env
|
||||
|
||||
prune-dockerhub:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
OWNER: ${{ github.repository_owner }}
|
||||
IMAGE_NAME: charon
|
||||
KEEP_DAYS: ${{ github.event.inputs.keep_days || '30' }}
|
||||
KEEP_LAST_N: ${{ github.event.inputs.keep_last_n || '30' }}
|
||||
DRY_RUN: ${{ github.event_name == 'pull_request' && 'true' || github.event.inputs.dry_run || 'false' }}
|
||||
PROTECTED_REGEX: '["^v?[0-9]+\\.[0-9]+\\.[0-9]+$","^latest$","^main$","^develop$"]'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y jq curl
|
||||
|
||||
- name: Run Docker Hub prune
|
||||
env:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
run: |
|
||||
chmod +x scripts/prune-dockerhub.sh
|
||||
./scripts/prune-dockerhub.sh 2>&1 | tee prune-dockerhub-${{ github.run_id }}.log
|
||||
|
||||
- name: Summarize Docker Hub results
|
||||
if: always()
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SUMMARY_FILE=prune-summary-dockerhub.env
|
||||
LOG_FILE=prune-dockerhub-${{ github.run_id }}.log
|
||||
|
||||
human() {
|
||||
local bytes=${1:-0}
|
||||
if [ -z "$bytes" ] || [ "$bytes" -eq 0 ]; then
|
||||
echo "0 B"
|
||||
return
|
||||
fi
|
||||
awk -v b="$bytes" 'BEGIN { split("B KiB MiB GiB TiB",u," "); i=0; x=b; while(x>1024){x/=1024;i++} printf "%0.2f %s", x, u[i+1] }'
|
||||
}
|
||||
|
||||
if [ -f "$SUMMARY_FILE" ]; then
|
||||
TOTAL_CANDIDATES=$(grep -E '^TOTAL_CANDIDATES=' "$SUMMARY_FILE" | cut -d= -f2 || echo 0)
|
||||
TOTAL_CANDIDATES_BYTES=$(grep -E '^TOTAL_CANDIDATES_BYTES=' "$SUMMARY_FILE" | cut -d= -f2 || echo 0)
|
||||
TOTAL_DELETED=$(grep -E '^TOTAL_DELETED=' "$SUMMARY_FILE" | cut -d= -f2 || echo 0)
|
||||
TOTAL_DELETED_BYTES=$(grep -E '^TOTAL_DELETED_BYTES=' "$SUMMARY_FILE" | cut -d= -f2 || echo 0)
|
||||
|
||||
{
|
||||
echo "## Docker Hub prune summary"
|
||||
echo "- candidates: ${TOTAL_CANDIDATES} (≈ $(human "${TOTAL_CANDIDATES_BYTES}"))"
|
||||
echo "- deleted: ${TOTAL_DELETED} (≈ $(human "${TOTAL_DELETED_BYTES}"))"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
deleted_bytes=$(grep -oE '\( *approx +[0-9]+ bytes\)' "$LOG_FILE" | sed -E 's/.*approx +([0-9]+) bytes.*/\1/' | awk '{s+=$1} END {print s+0}' || true)
|
||||
deleted_count=$(grep -cE 'deleting |DRY RUN: would delete' "$LOG_FILE" || true)
|
||||
|
||||
{
|
||||
echo "## Docker Hub prune summary"
|
||||
echo "- deleted (approx): ${deleted_count} (≈ $(human "${deleted_bytes}"))"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
- name: Upload Docker Hub prune artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: prune-dockerhub-log-${{ github.run_id }}
|
||||
path: |
|
||||
prune-dockerhub-${{ github.run_id }}.log
|
||||
prune-summary-dockerhub.env
|
||||
|
||||
summarize:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [prune-ghcr, prune-dockerhub]
|
||||
if: always()
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
pattern: prune-*-log-${{ github.run_id }}
|
||||
merge-multiple: true
|
||||
|
||||
- name: Combined summary
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
human() {
|
||||
local bytes=${1:-0}
|
||||
if [ -z "$bytes" ] || [ "$bytes" -eq 0 ]; then
|
||||
echo "0 B"
|
||||
return
|
||||
fi
|
||||
awk -v b="$bytes" 'BEGIN { split("B KiB MiB GiB TiB",u," "); i=0; x=b; while(x>1024){x/=1024;i++} printf "%0.2f %s", x, u[i+1] }'
|
||||
}
|
||||
|
||||
GHCR_CANDIDATES=0 GHCR_CANDIDATES_BYTES=0 GHCR_DELETED=0 GHCR_DELETED_BYTES=0
|
||||
if [ -f prune-summary-ghcr.env ]; then
|
||||
GHCR_CANDIDATES=$(grep -E '^TOTAL_CANDIDATES=' prune-summary-ghcr.env | cut -d= -f2 || echo 0)
|
||||
GHCR_CANDIDATES_BYTES=$(grep -E '^TOTAL_CANDIDATES_BYTES=' prune-summary-ghcr.env | cut -d= -f2 || echo 0)
|
||||
GHCR_DELETED=$(grep -E '^TOTAL_DELETED=' prune-summary-ghcr.env | cut -d= -f2 || echo 0)
|
||||
GHCR_DELETED_BYTES=$(grep -E '^TOTAL_DELETED_BYTES=' prune-summary-ghcr.env | cut -d= -f2 || echo 0)
|
||||
fi
|
||||
|
||||
HUB_CANDIDATES=0 HUB_CANDIDATES_BYTES=0 HUB_DELETED=0 HUB_DELETED_BYTES=0
|
||||
if [ -f prune-summary-dockerhub.env ]; then
|
||||
HUB_CANDIDATES=$(grep -E '^TOTAL_CANDIDATES=' prune-summary-dockerhub.env | cut -d= -f2 || echo 0)
|
||||
HUB_CANDIDATES_BYTES=$(grep -E '^TOTAL_CANDIDATES_BYTES=' prune-summary-dockerhub.env | cut -d= -f2 || echo 0)
|
||||
HUB_DELETED=$(grep -E '^TOTAL_DELETED=' prune-summary-dockerhub.env | cut -d= -f2 || echo 0)
|
||||
HUB_DELETED_BYTES=$(grep -E '^TOTAL_DELETED_BYTES=' prune-summary-dockerhub.env | cut -d= -f2 || echo 0)
|
||||
fi
|
||||
|
||||
TOTAL_CANDIDATES=$((GHCR_CANDIDATES + HUB_CANDIDATES))
|
||||
TOTAL_CANDIDATES_BYTES=$((GHCR_CANDIDATES_BYTES + HUB_CANDIDATES_BYTES))
|
||||
TOTAL_DELETED=$((GHCR_DELETED + HUB_DELETED))
|
||||
TOTAL_DELETED_BYTES=$((GHCR_DELETED_BYTES + HUB_DELETED_BYTES))
|
||||
|
||||
{
|
||||
echo "## Combined container prune summary"
|
||||
echo ""
|
||||
echo "| Registry | Candidates | Deleted | Space Reclaimed |"
|
||||
echo "|----------|------------|---------|-----------------|"
|
||||
echo "| GHCR | ${GHCR_CANDIDATES} | ${GHCR_DELETED} | $(human "${GHCR_DELETED_BYTES}") |"
|
||||
echo "| Docker Hub | ${HUB_CANDIDATES} | ${HUB_DELETED} | $(human "${HUB_DELETED_BYTES}") |"
|
||||
echo "| **Total** | **${TOTAL_CANDIDATES}** | **${TOTAL_DELETED}** | **$(human "${TOTAL_DELETED_BYTES}")** |"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
printf 'PRUNE_SUMMARY: candidates=%s candidates_bytes=%s deleted=%s deleted_bytes=%s\n' \
|
||||
"${TOTAL_CANDIDATES}" "${TOTAL_CANDIDATES_BYTES}" "${TOTAL_DELETED}" "${TOTAL_DELETED_BYTES}"
|
||||
echo "Total space reclaimed: $(human "${TOTAL_DELETED_BYTES}")"
|
||||
|
||||
2
.github/workflows/crowdsec-integration.yml
vendored
2
.github/workflows/crowdsec-integration.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Build Docker image (Local)
|
||||
run: |
|
||||
echo "Building image locally for integration tests..."
|
||||
docker build -t charon:local .
|
||||
docker build -t charon:local --build-arg CI="${CI:-false}" .
|
||||
echo "✅ Successfully built charon:local"
|
||||
|
||||
- name: Run CrowdSec integration tests
|
||||
|
||||
90
.github/workflows/docker-build.yml
vendored
90
.github/workflows/docker-build.yml
vendored
@@ -23,7 +23,11 @@ name: Docker Build, Publish & Test
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
workflow_run:
|
||||
workflows: ["Docker Lint"]
|
||||
types: [completed]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.head_ref || github.ref_name }}
|
||||
@@ -38,7 +42,7 @@ env:
|
||||
TRIGGER_HEAD_SHA: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
|
||||
TRIGGER_REF: ${{ github.event_name == 'workflow_run' && format('refs/heads/{0}', github.event.workflow_run.head_branch) || github.ref }}
|
||||
TRIGGER_HEAD_REF: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.head_ref }}
|
||||
TRIGGER_PR_NUMBER: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number || github.event.pull_request.number }}
|
||||
TRIGGER_PR_NUMBER: ${{ github.event_name == 'workflow_run' && join(github.event.workflow_run.pull_requests.*.number, '') || github.event.pull_request.number }}
|
||||
TRIGGER_ACTOR: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.actor.login || github.actor }}
|
||||
|
||||
jobs:
|
||||
@@ -111,21 +115,22 @@ jobs:
|
||||
|
||||
- name: Set up QEMU
|
||||
if: steps.skip.outputs.skip_build != 'true'
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.skip.outputs.skip_build != 'true'
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
- name: Resolve Alpine base image digest
|
||||
if: steps.skip.outputs.skip_build != 'true'
|
||||
id: caddy
|
||||
id: alpine
|
||||
run: |
|
||||
docker pull alpine:3.23.3
|
||||
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' alpine:3.23.3)
|
||||
ALPINE_TAG=$(grep -m1 'ARG ALPINE_IMAGE=' Dockerfile | sed 's/ARG ALPINE_IMAGE=alpine://' | cut -d'@' -f1)
|
||||
docker pull "alpine:${ALPINE_TAG}"
|
||||
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "alpine:${ALPINE_TAG}")
|
||||
echo "image=$DIGEST" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
if: steps.skip.outputs.skip_build != 'true'
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ${{ env.GHCR_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -133,7 +138,7 @@ jobs:
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: steps.skip.outputs.skip_build != 'true' && env.HAS_DOCKERHUB_TOKEN == 'true'
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
@@ -195,7 +200,7 @@ jobs:
|
||||
|
||||
- name: Generate Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
with:
|
||||
images: |
|
||||
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
@@ -229,7 +234,7 @@ jobs:
|
||||
- name: Build and push Docker image (with retry)
|
||||
if: steps.skip.outputs.skip_build != 'true'
|
||||
id: build-and-push
|
||||
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
|
||||
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
|
||||
with:
|
||||
timeout_minutes: 25
|
||||
max_attempts: 3
|
||||
@@ -267,7 +272,7 @@ jobs:
|
||||
--build-arg "VERSION=${{ steps.meta.outputs.version }}"
|
||||
--build-arg "BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}"
|
||||
--build-arg "VCS_REF=${{ env.TRIGGER_HEAD_SHA }}"
|
||||
--build-arg "CADDY_IMAGE=${{ steps.caddy.outputs.image }}"
|
||||
--build-arg "ALPINE_IMAGE=${{ steps.alpine.outputs.image }}"
|
||||
--iidfile /tmp/image-digest.txt
|
||||
.
|
||||
)
|
||||
@@ -339,7 +344,7 @@ jobs:
|
||||
|
||||
- name: Upload Image Artifact
|
||||
if: success() && steps.skip.outputs.skip_build != 'true' && env.TRIGGER_EVENT == 'pull_request'
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: ${{ env.TRIGGER_EVENT == 'pull_request' && format('pr-image-{0}', env.TRIGGER_PR_NUMBER) || 'push-image' }}
|
||||
path: /tmp/charon-pr-image.tar
|
||||
@@ -527,23 +532,25 @@ jobs:
|
||||
|
||||
- name: Run Trivy scan (table output)
|
||||
if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.skip.outputs.is_feature_push != 'true'
|
||||
uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # 0.34.0
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
|
||||
with:
|
||||
image-ref: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}
|
||||
format: 'table'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
exit-code: '0'
|
||||
version: 'v0.69.3'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run Trivy vulnerability scanner (SARIF)
|
||||
if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.skip.outputs.is_feature_push != 'true'
|
||||
id: trivy
|
||||
uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # 0.34.0
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
|
||||
with:
|
||||
image-ref: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}
|
||||
format: 'sarif'
|
||||
output: 'trivy-results.sarif'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
version: 'v0.69.3'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Check Trivy SARIF exists
|
||||
@@ -558,15 +565,16 @@ jobs:
|
||||
|
||||
- name: Upload Trivy results
|
||||
if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.trivy-check.outputs.exists == 'true'
|
||||
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||
uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
category: '.github/workflows/docker-build.yml:build-and-push'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Generate SBOM (Software Bill of Materials) for supply chain security
|
||||
# Only for production builds (main/development) - feature branches use downstream supply-chain-pr.yml
|
||||
- name: Generate SBOM
|
||||
uses: anchore/sbom-action@28d71544de8eaf1b958d335707167c5f783590ad # v0.22.2
|
||||
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
|
||||
if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.skip.outputs.is_feature_push != 'true'
|
||||
with:
|
||||
image: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-and-push.outputs.digest }}
|
||||
@@ -575,7 +583,7 @@ jobs:
|
||||
|
||||
# Create verifiable attestation for the SBOM
|
||||
- name: Attest SBOM
|
||||
uses: actions/attest-sbom@4651f806c01d8637787e274ac3bdf724ef169f34 # v3.0.0
|
||||
uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0
|
||||
if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.skip.outputs.is_feature_push != 'true'
|
||||
with:
|
||||
subject-name: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
@@ -586,7 +594,7 @@ jobs:
|
||||
# Install Cosign for keyless signing
|
||||
- name: Install Cosign
|
||||
if: env.TRIGGER_EVENT != 'pull_request' && steps.skip.outputs.skip_build != 'true' && steps.skip.outputs.is_feature_push != 'true'
|
||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||
uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0
|
||||
|
||||
# Sign GHCR image with keyless signing (Sigstore/Fulcio)
|
||||
- name: Sign GHCR Image
|
||||
@@ -652,7 +660,7 @@ jobs:
|
||||
echo "image_ref=${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:${PR_TAG}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ${{ env.GHCR_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -684,31 +692,67 @@ jobs:
|
||||
echo "✅ Image freshness validated"
|
||||
|
||||
- name: Run Trivy scan on PR image (table output)
|
||||
uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # 0.34.0
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
|
||||
with:
|
||||
image-ref: ${{ steps.pr-image.outputs.image_ref }}
|
||||
format: 'table'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
exit-code: '0'
|
||||
version: 'v0.69.3'
|
||||
|
||||
- name: Run Trivy scan on PR image (SARIF - blocking)
|
||||
id: trivy-scan
|
||||
uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # 0.34.0
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
|
||||
with:
|
||||
image-ref: ${{ steps.pr-image.outputs.image_ref }}
|
||||
format: 'sarif'
|
||||
output: 'trivy-pr-results.sarif'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
exit-code: '1' # Intended to block, but continued on error for now
|
||||
version: 'v0.69.3'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload Trivy scan results
|
||||
- name: Check Trivy PR SARIF exists
|
||||
if: always()
|
||||
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||
id: trivy-pr-check
|
||||
run: |
|
||||
if [ -f trivy-pr-results.sarif ]; then
|
||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Upload Trivy scan results
|
||||
if: always() && steps.trivy-pr-check.outputs.exists == 'true'
|
||||
uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
with:
|
||||
sarif_file: 'trivy-pr-results.sarif'
|
||||
category: 'docker-pr-image'
|
||||
|
||||
- name: Upload Trivy compatibility results (docker-build category)
|
||||
if: always() && steps.trivy-pr-check.outputs.exists == 'true'
|
||||
uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
with:
|
||||
sarif_file: 'trivy-pr-results.sarif'
|
||||
category: '.github/workflows/docker-build.yml:build-and-push'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload Trivy compatibility results (docker-publish alias)
|
||||
if: always() && steps.trivy-pr-check.outputs.exists == 'true'
|
||||
uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
with:
|
||||
sarif_file: 'trivy-pr-results.sarif'
|
||||
category: '.github/workflows/docker-publish.yml:build-and-push'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload Trivy compatibility results (nightly alias)
|
||||
if: always() && steps.trivy-pr-check.outputs.exists == 'true'
|
||||
uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
with:
|
||||
sarif_file: 'trivy-pr-results.sarif'
|
||||
category: 'trivy-nightly'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Create scan summary
|
||||
if: always()
|
||||
run: |
|
||||
|
||||
2
.github/workflows/docs-to-issues.yml
vendored
2
.github/workflows/docs-to-issues.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
|
||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
# Step 2: Set up Node.js (for building any JS-based doc tools)
|
||||
- name: 🔧 Set up Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
|
||||
222
.github/workflows/e2e-tests-split.yml
vendored
222
.github/workflows/e2e-tests-split.yml
vendored
@@ -80,11 +80,10 @@ on:
|
||||
default: false
|
||||
type: boolean
|
||||
pull_request:
|
||||
push:
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
GO_VERSION: '1.26.0'
|
||||
GO_VERSION: '1.26.1'
|
||||
GOTOOLCHAIN: auto
|
||||
DOCKERHUB_REGISTRY: docker.io
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/charon
|
||||
@@ -96,7 +95,7 @@ env:
|
||||
CI_LOG_LEVEL: 'verbose'
|
||||
|
||||
concurrency:
|
||||
group: e2e-split-${{ github.workflow }}-${{ github.ref }}-${{ github.event.pull_request.head.sha || github.sha }}
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
@@ -143,22 +142,23 @@ jobs:
|
||||
|
||||
- name: Set up Go
|
||||
if: steps.resolve-image.outputs.image_source == 'build'
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
cache: true
|
||||
cache-dependency-path: backend/go.sum
|
||||
|
||||
- name: Set up Node.js
|
||||
if: steps.resolve-image.outputs.image_source == 'build'
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Cache npm dependencies
|
||||
if: steps.resolve-image.outputs.image_source == 'build'
|
||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
@@ -170,12 +170,12 @@ jobs:
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.resolve-image.outputs.image_source == 'build'
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
|
||||
- name: Build Docker image
|
||||
id: build-image
|
||||
if: steps.resolve-image.outputs.image_source == 'build'
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
@@ -191,7 +191,7 @@ jobs:
|
||||
|
||||
- name: Upload Docker image artifact
|
||||
if: steps.resolve-image.outputs.image_source == 'build'
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: docker-image
|
||||
path: charon-e2e-image.tar
|
||||
@@ -225,14 +225,15 @@ jobs:
|
||||
ref: ${{ github.sha }}
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: needs.build.outputs.image_source == 'registry'
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ${{ env.DOCKERHUB_REGISTRY }}
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
@@ -247,7 +248,7 @@ jobs:
|
||||
|
||||
- name: Download Docker image artifact
|
||||
if: needs.build.outputs.image_source == 'build'
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: docker-image
|
||||
|
||||
@@ -347,7 +348,7 @@ jobs:
|
||||
|
||||
- name: Upload HTML report (Chromium Security)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: playwright-report-chromium-security
|
||||
path: playwright-report/
|
||||
@@ -355,7 +356,7 @@ jobs:
|
||||
|
||||
- name: Upload Chromium Security coverage (if enabled)
|
||||
if: always() && (inputs.playwright_coverage == 'true' || vars.PLAYWRIGHT_COVERAGE == '1')
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: e2e-coverage-chromium-security
|
||||
path: coverage/e2e/
|
||||
@@ -363,7 +364,7 @@ jobs:
|
||||
|
||||
- name: Upload test traces on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: traces-chromium-security
|
||||
path: test-results/**/*.zip
|
||||
@@ -382,7 +383,7 @@ jobs:
|
||||
|
||||
- name: Upload diagnostics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: e2e-diagnostics-chromium-security
|
||||
path: diagnostics/
|
||||
@@ -395,7 +396,7 @@ jobs:
|
||||
|
||||
- name: Upload Docker logs on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: docker-logs-chromium-security
|
||||
path: docker-logs-chromium-security.txt
|
||||
@@ -426,14 +427,15 @@ jobs:
|
||||
ref: ${{ github.sha }}
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: needs.build.outputs.image_source == 'registry'
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ${{ env.DOCKERHUB_REGISTRY }}
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
@@ -448,7 +450,7 @@ jobs:
|
||||
|
||||
- name: Download Docker image artifact
|
||||
if: needs.build.outputs.image_source == 'build'
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: docker-image
|
||||
|
||||
@@ -556,7 +558,7 @@ jobs:
|
||||
|
||||
- name: Upload HTML report (Firefox Security)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: playwright-report-firefox-security
|
||||
path: playwright-report/
|
||||
@@ -564,7 +566,7 @@ jobs:
|
||||
|
||||
- name: Upload Firefox Security coverage (if enabled)
|
||||
if: always() && (inputs.playwright_coverage == 'true' || vars.PLAYWRIGHT_COVERAGE == '1')
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: e2e-coverage-firefox-security
|
||||
path: coverage/e2e/
|
||||
@@ -572,7 +574,7 @@ jobs:
|
||||
|
||||
- name: Upload test traces on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: traces-firefox-security
|
||||
path: test-results/**/*.zip
|
||||
@@ -591,7 +593,7 @@ jobs:
|
||||
|
||||
- name: Upload diagnostics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: e2e-diagnostics-firefox-security
|
||||
path: diagnostics/
|
||||
@@ -604,7 +606,7 @@ jobs:
|
||||
|
||||
- name: Upload Docker logs on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: docker-logs-firefox-security
|
||||
path: docker-logs-firefox-security.txt
|
||||
@@ -635,14 +637,15 @@ jobs:
|
||||
ref: ${{ github.sha }}
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: needs.build.outputs.image_source == 'registry'
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ${{ env.DOCKERHUB_REGISTRY }}
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
@@ -657,7 +660,7 @@ jobs:
|
||||
|
||||
- name: Download Docker image artifact
|
||||
if: needs.build.outputs.image_source == 'build'
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: docker-image
|
||||
|
||||
@@ -765,7 +768,7 @@ jobs:
|
||||
|
||||
- name: Upload HTML report (WebKit Security)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: playwright-report-webkit-security
|
||||
path: playwright-report/
|
||||
@@ -773,7 +776,7 @@ jobs:
|
||||
|
||||
- name: Upload WebKit Security coverage (if enabled)
|
||||
if: always() && (inputs.playwright_coverage == 'true' || vars.PLAYWRIGHT_COVERAGE == '1')
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: e2e-coverage-webkit-security
|
||||
path: coverage/e2e/
|
||||
@@ -781,7 +784,7 @@ jobs:
|
||||
|
||||
- name: Upload test traces on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: traces-webkit-security
|
||||
path: test-results/**/*.zip
|
||||
@@ -800,7 +803,7 @@ jobs:
|
||||
|
||||
- name: Upload diagnostics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: e2e-diagnostics-webkit-security
|
||||
path: diagnostics/
|
||||
@@ -813,7 +816,7 @@ jobs:
|
||||
|
||||
- name: Upload Docker logs on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: docker-logs-webkit-security
|
||||
path: docker-logs-webkit-security.txt
|
||||
@@ -856,14 +859,47 @@ jobs:
|
||||
ref: ${{ github.sha }}
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Preflight disk diagnostics (before cleanup)
|
||||
run: |
|
||||
echo "Disk usage before cleanup"
|
||||
df -h
|
||||
docker system df || true
|
||||
|
||||
- name: Preflight cleanup (best effort)
|
||||
run: |
|
||||
echo "Best-effort cleanup for CI runner"
|
||||
docker system prune -af || true
|
||||
rm -rf playwright-report playwright-output coverage/e2e test-results diagnostics || true
|
||||
rm -f docker-logs-*.txt charon-e2e-image.tar || true
|
||||
|
||||
- name: Preflight disk diagnostics and threshold gate
|
||||
run: |
|
||||
set -euo pipefail
|
||||
MIN_FREE_BYTES=$((5 * 1024 * 1024 * 1024))
|
||||
echo "Disk usage after cleanup"
|
||||
df -h
|
||||
docker system df || true
|
||||
|
||||
WORKSPACE_PATH="${GITHUB_WORKSPACE:-$PWD}"
|
||||
FREE_ROOT_BYTES=$(df -PB1 / | awk 'NR==2 {print $4}')
|
||||
FREE_WORKSPACE_BYTES=$(df -PB1 "$WORKSPACE_PATH" | awk 'NR==2 {print $4}')
|
||||
|
||||
echo "Free bytes on /: $FREE_ROOT_BYTES"
|
||||
echo "Free bytes on workspace ($WORKSPACE_PATH): $FREE_WORKSPACE_BYTES"
|
||||
|
||||
if [ "$FREE_ROOT_BYTES" -lt "$MIN_FREE_BYTES" ] || [ "$FREE_WORKSPACE_BYTES" -lt "$MIN_FREE_BYTES" ]; then
|
||||
echo "::error::[CI_DISK_PRESSURE] Insufficient free disk after cleanup. Required >= 5GiB on both / and workspace. root=${FREE_ROOT_BYTES}B workspace=${FREE_WORKSPACE_BYTES}B"
|
||||
exit 42
|
||||
fi
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: needs.build.outputs.image_source == 'registry'
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ${{ env.DOCKERHUB_REGISTRY }}
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
@@ -878,7 +914,7 @@ jobs:
|
||||
|
||||
- name: Download Docker image artifact
|
||||
if: needs.build.outputs.image_source == 'build'
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: docker-image
|
||||
|
||||
@@ -968,7 +1004,7 @@ jobs:
|
||||
|
||||
- name: Upload HTML report (Chromium shard ${{ matrix.shard }})
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: playwright-report-chromium-shard-${{ matrix.shard }}
|
||||
path: playwright-report/
|
||||
@@ -976,7 +1012,7 @@ jobs:
|
||||
|
||||
- name: Upload Playwright output (Chromium shard ${{ matrix.shard }})
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: playwright-output-chromium-shard-${{ matrix.shard }}
|
||||
path: playwright-output/chromium-shard-${{ matrix.shard }}/
|
||||
@@ -984,7 +1020,7 @@ jobs:
|
||||
|
||||
- name: Upload Chromium coverage (if enabled)
|
||||
if: always() && (inputs.playwright_coverage == 'true' || vars.PLAYWRIGHT_COVERAGE == '1')
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: e2e-coverage-chromium-shard-${{ matrix.shard }}
|
||||
path: coverage/e2e/
|
||||
@@ -992,7 +1028,7 @@ jobs:
|
||||
|
||||
- name: Upload test traces on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: traces-chromium-shard-${{ matrix.shard }}
|
||||
path: test-results/**/*.zip
|
||||
@@ -1011,7 +1047,7 @@ jobs:
|
||||
|
||||
- name: Upload diagnostics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: e2e-diagnostics-chromium-shard-${{ matrix.shard }}
|
||||
path: diagnostics/
|
||||
@@ -1024,7 +1060,7 @@ jobs:
|
||||
|
||||
- name: Upload Docker logs on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: docker-logs-chromium-shard-${{ matrix.shard }}
|
||||
path: docker-logs-chromium-shard-${{ matrix.shard }}.txt
|
||||
@@ -1060,14 +1096,47 @@ jobs:
|
||||
ref: ${{ github.sha }}
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Preflight disk diagnostics (before cleanup)
|
||||
run: |
|
||||
echo "Disk usage before cleanup"
|
||||
df -h
|
||||
docker system df || true
|
||||
|
||||
- name: Preflight cleanup (best effort)
|
||||
run: |
|
||||
echo "Best-effort cleanup for CI runner"
|
||||
docker system prune -af || true
|
||||
rm -rf playwright-report playwright-output coverage/e2e test-results diagnostics || true
|
||||
rm -f docker-logs-*.txt charon-e2e-image.tar || true
|
||||
|
||||
- name: Preflight disk diagnostics and threshold gate
|
||||
run: |
|
||||
set -euo pipefail
|
||||
MIN_FREE_BYTES=$((5 * 1024 * 1024 * 1024))
|
||||
echo "Disk usage after cleanup"
|
||||
df -h
|
||||
docker system df || true
|
||||
|
||||
WORKSPACE_PATH="${GITHUB_WORKSPACE:-$PWD}"
|
||||
FREE_ROOT_BYTES=$(df -PB1 / | awk 'NR==2 {print $4}')
|
||||
FREE_WORKSPACE_BYTES=$(df -PB1 "$WORKSPACE_PATH" | awk 'NR==2 {print $4}')
|
||||
|
||||
echo "Free bytes on /: $FREE_ROOT_BYTES"
|
||||
echo "Free bytes on workspace ($WORKSPACE_PATH): $FREE_WORKSPACE_BYTES"
|
||||
|
||||
if [ "$FREE_ROOT_BYTES" -lt "$MIN_FREE_BYTES" ] || [ "$FREE_WORKSPACE_BYTES" -lt "$MIN_FREE_BYTES" ]; then
|
||||
echo "::error::[CI_DISK_PRESSURE] Insufficient free disk after cleanup. Required >= 5GiB on both / and workspace. root=${FREE_ROOT_BYTES}B workspace=${FREE_WORKSPACE_BYTES}B"
|
||||
exit 42
|
||||
fi
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: needs.build.outputs.image_source == 'registry'
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ${{ env.DOCKERHUB_REGISTRY }}
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
@@ -1082,7 +1151,7 @@ jobs:
|
||||
|
||||
- name: Download Docker image artifact
|
||||
if: needs.build.outputs.image_source == 'build'
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: docker-image
|
||||
|
||||
@@ -1180,7 +1249,7 @@ jobs:
|
||||
|
||||
- name: Upload HTML report (Firefox shard ${{ matrix.shard }})
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: playwright-report-firefox-shard-${{ matrix.shard }}
|
||||
path: playwright-report/
|
||||
@@ -1188,7 +1257,7 @@ jobs:
|
||||
|
||||
- name: Upload Playwright output (Firefox shard ${{ matrix.shard }})
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: playwright-output-firefox-shard-${{ matrix.shard }}
|
||||
path: playwright-output/firefox-shard-${{ matrix.shard }}/
|
||||
@@ -1196,7 +1265,7 @@ jobs:
|
||||
|
||||
- name: Upload Firefox coverage (if enabled)
|
||||
if: always() && (inputs.playwright_coverage == 'true' || vars.PLAYWRIGHT_COVERAGE == '1')
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: e2e-coverage-firefox-shard-${{ matrix.shard }}
|
||||
path: coverage/e2e/
|
||||
@@ -1204,7 +1273,7 @@ jobs:
|
||||
|
||||
- name: Upload test traces on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: traces-firefox-shard-${{ matrix.shard }}
|
||||
path: test-results/**/*.zip
|
||||
@@ -1223,7 +1292,7 @@ jobs:
|
||||
|
||||
- name: Upload diagnostics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: e2e-diagnostics-firefox-shard-${{ matrix.shard }}
|
||||
path: diagnostics/
|
||||
@@ -1236,7 +1305,7 @@ jobs:
|
||||
|
||||
- name: Upload Docker logs on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: docker-logs-firefox-shard-${{ matrix.shard }}
|
||||
path: docker-logs-firefox-shard-${{ matrix.shard }}.txt
|
||||
@@ -1272,14 +1341,47 @@ jobs:
|
||||
ref: ${{ github.sha }}
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Preflight disk diagnostics (before cleanup)
|
||||
run: |
|
||||
echo "Disk usage before cleanup"
|
||||
df -h
|
||||
docker system df || true
|
||||
|
||||
- name: Preflight cleanup (best effort)
|
||||
run: |
|
||||
echo "Best-effort cleanup for CI runner"
|
||||
docker system prune -af || true
|
||||
rm -rf playwright-report playwright-output coverage/e2e test-results diagnostics || true
|
||||
rm -f docker-logs-*.txt charon-e2e-image.tar || true
|
||||
|
||||
- name: Preflight disk diagnostics and threshold gate
|
||||
run: |
|
||||
set -euo pipefail
|
||||
MIN_FREE_BYTES=$((5 * 1024 * 1024 * 1024))
|
||||
echo "Disk usage after cleanup"
|
||||
df -h
|
||||
docker system df || true
|
||||
|
||||
WORKSPACE_PATH="${GITHUB_WORKSPACE:-$PWD}"
|
||||
FREE_ROOT_BYTES=$(df -PB1 / | awk 'NR==2 {print $4}')
|
||||
FREE_WORKSPACE_BYTES=$(df -PB1 "$WORKSPACE_PATH" | awk 'NR==2 {print $4}')
|
||||
|
||||
echo "Free bytes on /: $FREE_ROOT_BYTES"
|
||||
echo "Free bytes on workspace ($WORKSPACE_PATH): $FREE_WORKSPACE_BYTES"
|
||||
|
||||
if [ "$FREE_ROOT_BYTES" -lt "$MIN_FREE_BYTES" ] || [ "$FREE_WORKSPACE_BYTES" -lt "$MIN_FREE_BYTES" ]; then
|
||||
echo "::error::[CI_DISK_PRESSURE] Insufficient free disk after cleanup. Required >= 5GiB on both / and workspace. root=${FREE_ROOT_BYTES}B workspace=${FREE_WORKSPACE_BYTES}B"
|
||||
exit 42
|
||||
fi
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: needs.build.outputs.image_source == 'registry'
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ${{ env.DOCKERHUB_REGISTRY }}
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
@@ -1294,7 +1396,7 @@ jobs:
|
||||
|
||||
- name: Download Docker image artifact
|
||||
if: needs.build.outputs.image_source == 'build'
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: docker-image
|
||||
|
||||
@@ -1392,7 +1494,7 @@ jobs:
|
||||
|
||||
- name: Upload HTML report (WebKit shard ${{ matrix.shard }})
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: playwright-report-webkit-shard-${{ matrix.shard }}
|
||||
path: playwright-report/
|
||||
@@ -1400,7 +1502,7 @@ jobs:
|
||||
|
||||
- name: Upload Playwright output (WebKit shard ${{ matrix.shard }})
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: playwright-output-webkit-shard-${{ matrix.shard }}
|
||||
path: playwright-output/webkit-shard-${{ matrix.shard }}/
|
||||
@@ -1408,7 +1510,7 @@ jobs:
|
||||
|
||||
- name: Upload WebKit coverage (if enabled)
|
||||
if: always() && (inputs.playwright_coverage == 'true' || vars.PLAYWRIGHT_COVERAGE == '1')
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: e2e-coverage-webkit-shard-${{ matrix.shard }}
|
||||
path: coverage/e2e/
|
||||
@@ -1416,7 +1518,7 @@ jobs:
|
||||
|
||||
- name: Upload test traces on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: traces-webkit-shard-${{ matrix.shard }}
|
||||
path: test-results/**/*.zip
|
||||
@@ -1435,7 +1537,7 @@ jobs:
|
||||
|
||||
- name: Upload diagnostics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: e2e-diagnostics-webkit-shard-${{ matrix.shard }}
|
||||
path: diagnostics/
|
||||
@@ -1448,7 +1550,7 @@ jobs:
|
||||
|
||||
- name: Upload Docker logs on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: docker-logs-webkit-shard-${{ matrix.shard }}
|
||||
path: docker-logs-webkit-shard-${{ matrix.shard }}.txt
|
||||
|
||||
1170
.github/workflows/e2e-tests-split.yml.backup
vendored
1170
.github/workflows/e2e-tests-split.yml.backup
vendored
File diff suppressed because it is too large
Load Diff
632
.github/workflows/e2e-tests.yml.backup
vendored
632
.github/workflows/e2e-tests.yml.backup
vendored
@@ -1,632 +0,0 @@
|
||||
# E2E Tests Workflow
|
||||
# Runs Playwright E2E tests with sharding for faster execution
|
||||
# and collects frontend code coverage via @bgotink/playwright-coverage
|
||||
#
|
||||
# Test Execution Architecture:
|
||||
# - Parallel Sharding: Tests split across 4 shards for speed
|
||||
# - Per-Shard HTML Reports: Each shard generates its own HTML report
|
||||
# - No Merging Needed: Smaller reports are easier to debug
|
||||
# - Trace Collection: Failure traces captured for debugging
|
||||
#
|
||||
# Coverage Architecture:
|
||||
# - Backend: Docker container at localhost:8080 (API)
|
||||
# - Frontend: Vite dev server at localhost:3000 (serves source files)
|
||||
# - Tests hit Vite, which proxies API calls to Docker
|
||||
# - V8 coverage maps directly to source files for accurate reporting
|
||||
# - Coverage disabled by default (requires PLAYWRIGHT_COVERAGE=1)
|
||||
#
|
||||
# Triggers:
|
||||
# - Pull requests to main/develop (with path filters)
|
||||
# - Push to main branch
|
||||
# - Manual dispatch with browser selection
|
||||
#
|
||||
# Jobs:
|
||||
# 1. build: Build Docker image and upload as artifact
|
||||
# 2. e2e-tests: Run tests in parallel shards, upload per-shard HTML reports
|
||||
# 3. test-summary: Generate summary with links to shard reports
|
||||
# 4. comment-results: Post test results as PR comment
|
||||
# 5. upload-coverage: Merge and upload E2E coverage to Codecov (if enabled)
|
||||
# 6. e2e-results: Status check to block merge on failure
|
||||
|
||||
name: E2E Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- development
|
||||
- 'feature/**'
|
||||
paths:
|
||||
- 'frontend/**'
|
||||
- 'backend/**'
|
||||
- 'tests/**'
|
||||
- 'playwright.config.js'
|
||||
- '.github/workflows/e2e-tests.yml'
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
browser:
|
||||
description: 'Browser to test'
|
||||
required: false
|
||||
default: 'chromium'
|
||||
type: choice
|
||||
options:
|
||||
- chromium
|
||||
- firefox
|
||||
- webkit
|
||||
- all
|
||||
|
||||
env:
|
||||
NODE_VERSION: '20'
|
||||
GO_VERSION: '1.25.6'
|
||||
GOTOOLCHAIN: auto
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/charon
|
||||
PLAYWRIGHT_COVERAGE: ${{ vars.PLAYWRIGHT_COVERAGE || '0' }}
|
||||
# Enhanced debugging environment variables
|
||||
DEBUG: 'charon:*,charon-test:*'
|
||||
PLAYWRIGHT_DEBUG: '1'
|
||||
CI_LOG_LEVEL: 'verbose'
|
||||
|
||||
concurrency:
|
||||
group: e2e-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Build application once, share across test shards
|
||||
build:
|
||||
name: Build Application
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
image_digest: ${{ steps.build-image.outputs.digest }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
cache-dependency-path: backend/go.sum
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Cache npm dependencies
|
||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: npm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
|
||||
|
||||
- name: Build Docker image
|
||||
id: build-image
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: false
|
||||
load: true
|
||||
tags: charon:e2e-test
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Save Docker image
|
||||
run: docker save charon:e2e-test -o charon-e2e-image.tar
|
||||
|
||||
- name: Upload Docker image artifact
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: docker-image
|
||||
path: charon-e2e-image.tar
|
||||
retention-days: 1
|
||||
|
||||
# Run tests in parallel shards
|
||||
e2e-tests:
|
||||
name: E2E ${{ matrix.browser }} (Shard ${{ matrix.shard }}/${{ matrix.total-shards }})
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
# Required for security teardown (emergency reset fallback when ACL blocks API)
|
||||
CHARON_EMERGENCY_TOKEN: ${{ secrets.CHARON_EMERGENCY_TOKEN }}
|
||||
# Enable security-focused endpoints and test gating
|
||||
CHARON_EMERGENCY_SERVER_ENABLED: "true"
|
||||
CHARON_SECURITY_TESTS_ENABLED: "true"
|
||||
CHARON_E2E_IMAGE_TAG: charon:e2e-test
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4]
|
||||
total-shards: [4]
|
||||
browser: [chromium, firefox, webkit]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Download Docker image
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
name: docker-image
|
||||
|
||||
- name: Validate Emergency Token Configuration
|
||||
run: |
|
||||
echo "🔐 Validating emergency token configuration..."
|
||||
|
||||
if [ -z "$CHARON_EMERGENCY_TOKEN" ]; then
|
||||
echo "::error title=Missing Secret::CHARON_EMERGENCY_TOKEN secret not configured in repository settings"
|
||||
echo "::error::Navigate to: Repository Settings → Secrets and Variables → Actions"
|
||||
echo "::error::Create secret: CHARON_EMERGENCY_TOKEN"
|
||||
echo "::error::Generate value with: openssl rand -hex 32"
|
||||
echo "::error::See docs/github-setup.md for detailed instructions"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TOKEN_LENGTH=${#CHARON_EMERGENCY_TOKEN}
|
||||
if [ $TOKEN_LENGTH -lt 64 ]; then
|
||||
echo "::error title=Invalid Token Length::CHARON_EMERGENCY_TOKEN must be at least 64 characters (current: $TOKEN_LENGTH)"
|
||||
echo "::error::Generate new token with: openssl rand -hex 32"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Mask token in output (show first 8 chars only)
|
||||
MASKED_TOKEN="${CHARON_EMERGENCY_TOKEN:0:8}...${CHARON_EMERGENCY_TOKEN: -4}"
|
||||
echo "::notice::Emergency token validated (length: $TOKEN_LENGTH, preview: $MASKED_TOKEN)"
|
||||
env:
|
||||
CHARON_EMERGENCY_TOKEN: ${{ secrets.CHARON_EMERGENCY_TOKEN }}
|
||||
|
||||
- name: Load Docker image
|
||||
run: |
|
||||
docker load -i charon-e2e-image.tar
|
||||
docker images | grep charon
|
||||
|
||||
- name: Generate ephemeral encryption key
|
||||
run: |
|
||||
# Generate a unique, ephemeral encryption key for this CI run
|
||||
# Key is 32 bytes, base64-encoded as required by CHARON_ENCRYPTION_KEY
|
||||
echo "CHARON_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> $GITHUB_ENV
|
||||
echo "✅ Generated ephemeral encryption key for E2E tests"
|
||||
|
||||
- name: Start test environment
|
||||
run: |
|
||||
# Use docker-compose.playwright-ci.yml for CI (no .env file, uses GitHub Secrets)
|
||||
# Note: Using pre-built image loaded from artifact - no rebuild needed
|
||||
docker compose -f .docker/compose/docker-compose.playwright-ci.yml --profile security-tests up -d
|
||||
echo "✅ Container started via docker-compose.playwright-ci.yml"
|
||||
|
||||
- name: Wait for service health
|
||||
run: |
|
||||
echo "⏳ Waiting for Charon to be healthy..."
|
||||
MAX_ATTEMPTS=30
|
||||
ATTEMPT=0
|
||||
|
||||
while [[ ${ATTEMPT} -lt ${MAX_ATTEMPTS} ]]; do
|
||||
ATTEMPT=$((ATTEMPT + 1))
|
||||
echo "Attempt ${ATTEMPT}/${MAX_ATTEMPTS}..."
|
||||
|
||||
if curl -sf http://localhost:8080/api/v1/health > /dev/null 2>&1; then
|
||||
echo "✅ Charon is healthy!"
|
||||
curl -s http://localhost:8080/api/v1/health | jq .
|
||||
exit 0
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "❌ Health check failed"
|
||||
docker compose -f .docker/compose/docker-compose.playwright-ci.yml logs
|
||||
exit 1
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Clean Playwright browser cache
|
||||
run: rm -rf ~/.cache/ms-playwright
|
||||
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
id: playwright-cache
|
||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
# Use exact match only - no restore-keys fallback
|
||||
# This ensures we don't restore stale browsers when Playwright version changes
|
||||
key: playwright-${{ matrix.browser }}-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
- name: Install & verify Playwright browsers
|
||||
run: |
|
||||
npx playwright install --with-deps --force
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "🎯 Playwright CLI version"
|
||||
npx playwright --version || true
|
||||
|
||||
echo "🔍 Showing Playwright cache root (if present)"
|
||||
ls -la ~/.cache/ms-playwright || true
|
||||
|
||||
echo "📥 Install or verify browser: ${{ matrix.browser }}"
|
||||
|
||||
# Install when cache miss, otherwise verify the expected executables exist
|
||||
if [[ "${{ steps.playwright-cache.outputs.cache-hit }}" != "true" ]]; then
|
||||
echo "📥 Cache miss - downloading ${{ matrix.browser }} browser..."
|
||||
npx playwright install --with-deps ${{ matrix.browser }}
|
||||
else
|
||||
echo "✅ Cache hit - verifying ${{ matrix.browser }} browser files..."
|
||||
fi
|
||||
|
||||
# Look for the browser-specific headless shell executable(s)
|
||||
case "${{ matrix.browser }}" in
|
||||
chromium)
|
||||
EXPECTED_PATTERN="chrome-headless-shell*"
|
||||
;;
|
||||
firefox)
|
||||
EXPECTED_PATTERN="firefox*"
|
||||
;;
|
||||
webkit)
|
||||
EXPECTED_PATTERN="webkit*"
|
||||
;;
|
||||
*)
|
||||
EXPECTED_PATTERN="*"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Searching for expected files (pattern=$EXPECTED_PATTERN)..."
|
||||
find ~/.cache/ms-playwright -maxdepth 4 -type f -name "$EXPECTED_PATTERN" -print || true
|
||||
|
||||
# Attempt to derive the exact executable path Playwright will use
|
||||
echo "Attempting to resolve Playwright's executable path via Node API (best-effort)"
|
||||
node -e "try{ const pw = require('playwright'); const b = pw['${{ matrix.browser }}']; console.log('exePath:', b.executablePath ? b.executablePath() : 'n/a'); }catch(e){ console.error('node-check-failed', e.message); process.exit(0); }" || true
|
||||
|
||||
# If the expected binary is missing, force reinstall
|
||||
MISSING_COUNT=$(find ~/.cache/ms-playwright -maxdepth 4 -type f -name "$EXPECTED_PATTERN" | wc -l || true)
|
||||
if [[ "$MISSING_COUNT" -lt 1 ]]; then
|
||||
echo "⚠️ Expected Playwright browser executable not found (count=$MISSING_COUNT). Forcing reinstall..."
|
||||
npx playwright install --with-deps ${{ matrix.browser }} --force
|
||||
fi
|
||||
|
||||
echo "Post-install: show cache contents (top 5 lines)"
|
||||
find ~/.cache/ms-playwright -maxdepth 3 -printf '%p\n' | head -40 || true
|
||||
|
||||
# Final sanity check: try a headless launch via a tiny Node script (browser-specific args, retry without args)
|
||||
echo "🔁 Verifying browser can be launched (headless)"
|
||||
node -e "(async()=>{ try{ const pw=require('playwright'); const name='${{ matrix.browser }}'; const browser = pw[name]; const argsMap = { chromium: ['--no-sandbox'], firefox: ['--no-sandbox'], webkit: [] }; const args = argsMap[name] || [];
|
||||
// First attempt: launch with recommended args for this browser
|
||||
try {
|
||||
console.log('attempt-launch', name, 'args', JSON.stringify(args));
|
||||
const b = await browser.launch({ headless: true, args });
|
||||
await b.close();
|
||||
console.log('launch-ok', 'argsUsed', JSON.stringify(args));
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
console.warn('launch-with-args-failed', err && err.message);
|
||||
if (args.length) {
|
||||
// Retry without args (some browsers reject unknown flags)
|
||||
console.log('retrying-without-args');
|
||||
const b2 = await browser.launch({ headless: true });
|
||||
await b2.close();
|
||||
console.log('launch-ok-no-args');
|
||||
process.exit(0);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
} catch (e) { console.error('launch-failed', e && e.message); process.exit(2); } })()" || (echo '❌ Browser launch verification failed' && exit 1)
|
||||
|
||||
echo "✅ Playwright ${{ matrix.browser }} ready and verified"
|
||||
|
||||
- name: Run E2E tests (Shard ${{ matrix.shard }}/${{ matrix.total-shards }})
|
||||
run: |
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
echo "E2E Test Shard ${{ matrix.shard }}/${{ matrix.total-shards }}"
|
||||
echo "Browser: ${{ matrix.browser }}"
|
||||
echo "Start Time: $(date -u +'%Y-%m-%dT%H:%M:%SZ')"
|
||||
echo ""
|
||||
echo "Reporter: HTML (per-shard reports)"
|
||||
echo "Output: playwright-report/ directory"
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
|
||||
# Capture start time for performance budget tracking
|
||||
SHARD_START=$(date +%s)
|
||||
echo "SHARD_START=$SHARD_START" >> $GITHUB_ENV
|
||||
|
||||
npx playwright test \
|
||||
--project=${{ matrix.browser }} \
|
||||
--shard=${{ matrix.shard }}/${{ matrix.total-shards }}
|
||||
|
||||
# Capture end time for performance budget tracking
|
||||
SHARD_END=$(date +%s)
|
||||
echo "SHARD_END=$SHARD_END" >> $GITHUB_ENV
|
||||
|
||||
SHARD_DURATION=$((SHARD_END - SHARD_START))
|
||||
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
echo "Shard ${{ matrix.shard }} Complete | Duration: ${SHARD_DURATION}s"
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
env:
|
||||
# Test directly against Docker container (no coverage)
|
||||
PLAYWRIGHT_BASE_URL: http://localhost:8080
|
||||
CI: true
|
||||
TEST_WORKER_INDEX: ${{ matrix.shard }}
|
||||
|
||||
- name: Verify shard performance budget
|
||||
if: always()
|
||||
run: |
|
||||
# Calculate shard execution time
|
||||
SHARD_DURATION=$((SHARD_END - SHARD_START))
|
||||
MAX_DURATION=900 # 15 minutes
|
||||
|
||||
echo "📊 Performance Budget Check"
|
||||
echo " Shard Duration: ${SHARD_DURATION}s"
|
||||
echo " Budget Limit: ${MAX_DURATION}s"
|
||||
echo " Utilization: $((SHARD_DURATION * 100 / MAX_DURATION))%"
|
||||
|
||||
# Fail if shard exceeded performance budget
|
||||
if [[ $SHARD_DURATION -gt $MAX_DURATION ]]; then
|
||||
echo "::error::Shard exceeded performance budget: ${SHARD_DURATION}s > ${MAX_DURATION}s"
|
||||
echo "::error::This likely indicates feature flag polling regression or API bottleneck"
|
||||
echo "::error::Review test logs and consider optimizing wait helpers or API calls"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Shard completed within budget: ${SHARD_DURATION}s"
|
||||
|
||||
- name: Upload HTML report (per-shard)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: playwright-report-${{ matrix.browser }}-shard-${{ matrix.shard }}
|
||||
path: playwright-report/
|
||||
retention-days: 14
|
||||
|
||||
- name: Upload test traces on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: traces-${{ matrix.browser }}-shard-${{ matrix.shard }}
|
||||
path: test-results/**/*.zip
|
||||
retention-days: 7
|
||||
|
||||
- name: Collect Docker logs on failure
|
||||
if: failure()
|
||||
run: |
|
||||
echo "📋 Container logs:"
|
||||
docker compose -f .docker/compose/docker-compose.playwright-ci.yml logs > docker-logs-${{ matrix.browser }}-shard-${{ matrix.shard }}.txt 2>&1
|
||||
|
||||
- name: Upload Docker logs on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: docker-logs-${{ matrix.browser }}-shard-${{ matrix.shard }}
|
||||
path: docker-logs-${{ matrix.browser }}-shard-${{ matrix.shard }}.txt
|
||||
retention-days: 7
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: |
|
||||
docker compose -f .docker/compose/docker-compose.playwright-ci.yml down -v 2>/dev/null || true
|
||||
|
||||
# Summarize test results from all shards (no merging needed)
|
||||
test-summary:
|
||||
name: E2E Test Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: e2e-tests
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Generate job summary with per-shard links
|
||||
run: |
|
||||
echo "## 📊 E2E Test Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Per-Shard HTML Reports" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Each shard generates its own HTML report for easier debugging:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Browser | Shards | HTML Reports | Traces (on failure) |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|---------|--------|--------------|---------------------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Chromium | 1-4 | \`playwright-report-chromium-shard-{1..4}\` | \`traces-chromium-shard-{1..4}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Firefox | 1-4 | \`playwright-report-firefox-shard-{1..4}\` | \`traces-firefox-shard-{1..4}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| WebKit | 1-4 | \`playwright-report-webkit-shard-{1..4}\` | \`traces-webkit-shard-{1..4}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### How to View Reports" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "1. Download the shard HTML report artifact (zip file)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "2. Extract and open \`index.html\` in your browser" >> $GITHUB_STEP_SUMMARY
|
||||
echo "3. Or run: \`npx playwright show-report path/to/extracted-folder\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Debugging Tips" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Failed tests?** Download the shard report that failed. Each shard has a focused subset of tests." >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Traces**: Available in trace artifacts (only on failure)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Docker Logs**: Backend errors available in docker-logs-shard-N artifacts" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Local repro**: \`npx playwright test --grep=\"test name\"\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Comment on PR with results
|
||||
comment-results:
|
||||
name: Comment Test Results
|
||||
runs-on: ubuntu-latest
|
||||
needs: [e2e-tests, test-summary]
|
||||
if: github.event_name == 'pull_request' && always()
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Determine test status
|
||||
id: status
|
||||
run: |
|
||||
if [[ "${{ needs.e2e-tests.result }}" == "success" ]]; then
|
||||
echo "emoji=✅" >> $GITHUB_OUTPUT
|
||||
echo "status=PASSED" >> $GITHUB_OUTPUT
|
||||
echo "message=All E2E tests passed!" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ needs.e2e-tests.result }}" == "failure" ]]; then
|
||||
echo "emoji=❌" >> $GITHUB_OUTPUT
|
||||
echo "status=FAILED" >> $GITHUB_OUTPUT
|
||||
echo "message=Some E2E tests failed. Check artifacts for per-shard reports." >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "emoji=⚠️" >> $GITHUB_OUTPUT
|
||||
echo "status=UNKNOWN" >> $GITHUB_OUTPUT
|
||||
echo "message=E2E tests did not complete successfully." >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Comment on PR
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
const emoji = '${{ steps.status.outputs.emoji }}';
|
||||
const status = '${{ steps.status.outputs.status }}';
|
||||
const message = '${{ steps.status.outputs.message }}';
|
||||
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
|
||||
|
||||
const body = `## ${emoji} E2E Test Results: ${status}
|
||||
|
||||
${message}
|
||||
|
||||
| Metric | Result |
|
||||
|--------|--------|
|
||||
| Browsers | Chromium, Firefox, WebKit |
|
||||
| Shards per Browser | 4 |
|
||||
| Total Jobs | 12 |
|
||||
| Status | ${status} |
|
||||
|
||||
**Per-Shard HTML Reports** (easier to debug):
|
||||
- \`playwright-report-{browser}-shard-{1..4}\` (12 total artifacts)
|
||||
- Trace artifacts: \`traces-{browser}-shard-{N}\`
|
||||
|
||||
[📊 View workflow run & download reports](${runUrl})
|
||||
|
||||
---
|
||||
<sub>🤖 This comment was automatically generated by the E2E Tests workflow.</sub>`;
|
||||
|
||||
// Find existing comment
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
});
|
||||
|
||||
const botComment = comments.find(comment =>
|
||||
comment.user.type === 'Bot' &&
|
||||
comment.body.includes('E2E Test Results')
|
||||
);
|
||||
|
||||
if (botComment) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: botComment.id,
|
||||
body: body
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: body
|
||||
});
|
||||
}
|
||||
|
||||
# Upload merged E2E coverage to Codecov
|
||||
upload-coverage:
|
||||
name: Upload E2E Coverage
|
||||
runs-on: ubuntu-latest
|
||||
needs: e2e-tests
|
||||
# Coverage is only produced when PLAYWRIGHT_COVERAGE=1 (requires Vite dev server)
|
||||
if: vars.PLAYWRIGHT_COVERAGE == '1'
|
||||
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
pattern: e2e-coverage-*
|
||||
path: all-coverage
|
||||
merge-multiple: false
|
||||
|
||||
- name: Merge LCOV coverage files
|
||||
run: |
|
||||
# Install lcov for merging
|
||||
sudo apt-get update && sudo apt-get install -y lcov
|
||||
|
||||
# Create merged coverage directory
|
||||
mkdir -p coverage/e2e-merged
|
||||
|
||||
# Find all lcov.info files and merge them
|
||||
LCOV_FILES=$(find all-coverage -name "lcov.info" -type f)
|
||||
|
||||
if [[ -n "$LCOV_FILES" ]]; then
|
||||
# Build merge command
|
||||
MERGE_ARGS=""
|
||||
for file in $LCOV_FILES; do
|
||||
MERGE_ARGS="$MERGE_ARGS -a $file"
|
||||
done
|
||||
|
||||
lcov $MERGE_ARGS -o coverage/e2e-merged/lcov.info
|
||||
echo "✅ Merged $(echo "$LCOV_FILES" | wc -w) coverage files"
|
||||
else
|
||||
echo "⚠️ No coverage files found to merge"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
- name: Upload E2E coverage to Codecov
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./coverage/e2e-merged/lcov.info
|
||||
flags: e2e
|
||||
name: e2e-coverage
|
||||
fail_ci_if_error: false
|
||||
|
||||
- name: Upload merged coverage artifact
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: e2e-coverage-merged
|
||||
path: coverage/e2e-merged/
|
||||
retention-days: 30
|
||||
|
||||
# Final status check - blocks merge if tests fail
|
||||
e2e-results:
|
||||
name: E2E Test Results
|
||||
runs-on: ubuntu-latest
|
||||
needs: e2e-tests
|
||||
if: always()
|
||||
|
||||
steps:
|
||||
- name: Check test results
|
||||
run: |
|
||||
if [[ "${{ needs.e2e-tests.result }}" == "success" ]]; then
|
||||
echo "✅ All E2E tests passed"
|
||||
exit 0
|
||||
elif [[ "${{ needs.e2e-tests.result }}" == "skipped" ]]; then
|
||||
echo "⏭️ E2E tests were skipped"
|
||||
exit 0
|
||||
else
|
||||
echo "❌ E2E tests failed or were cancelled"
|
||||
echo "Result: ${{ needs.e2e-tests.result }}"
|
||||
exit 1
|
||||
fi
|
||||
334
.github/workflows/nightly-build.yml
vendored
334
.github/workflows/nightly-build.yml
vendored
@@ -15,7 +15,7 @@ on:
|
||||
default: "false"
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.26.0'
|
||||
GO_VERSION: '1.26.1'
|
||||
NODE_VERSION: '24.12.0'
|
||||
GOTOOLCHAIN: auto
|
||||
GHCR_REGISTRY: ghcr.io
|
||||
@@ -103,11 +103,12 @@ jobs:
|
||||
const workflows = [
|
||||
{ id: 'e2e-tests-split.yml' },
|
||||
{ id: 'codecov-upload.yml', inputs: { run_backend: 'true', run_frontend: 'true' } },
|
||||
{ id: 'security-pr.yml' },
|
||||
{ id: 'supply-chain-verify.yml' },
|
||||
{ id: 'codeql.yml' },
|
||||
];
|
||||
|
||||
core.info('Skipping security-pr.yml: PR-only workflow intentionally excluded from nightly non-PR dispatch');
|
||||
|
||||
for (const workflow of workflows) {
|
||||
const { data: workflowRuns } = await github.rest.actions.listWorkflowRuns({
|
||||
owner,
|
||||
@@ -147,27 +148,37 @@ jobs:
|
||||
id-token: write
|
||||
outputs:
|
||||
version: ${{ steps.meta.outputs.version }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
digest: ${{ steps.resolve_digest.outputs.digest }}
|
||||
|
||||
steps:
|
||||
- name: Checkout nightly branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: nightly
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || 'nightly' }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set lowercase image name
|
||||
run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Resolve Alpine base image digest
|
||||
id: alpine
|
||||
run: |
|
||||
ALPINE_IMAGE_REF=$(grep -m1 'ARG ALPINE_IMAGE=' Dockerfile | cut -d'=' -f2-)
|
||||
if [[ -z "$ALPINE_IMAGE_REF" ]]; then
|
||||
echo "::error::Failed to parse ALPINE_IMAGE from Dockerfile"
|
||||
exit 1
|
||||
fi
|
||||
echo "Resolved Alpine image: ${ALPINE_IMAGE_REF}"
|
||||
echo "image=${ALPINE_IMAGE_REF}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ${{ env.GHCR_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -175,7 +186,7 @@ jobs:
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: env.HAS_DOCKERHUB_TOKEN == 'true'
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
@@ -183,7 +194,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
with:
|
||||
images: |
|
||||
${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
@@ -198,7 +209,7 @@ jobs:
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: build
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
@@ -209,25 +220,112 @@ jobs:
|
||||
VERSION=nightly-${{ github.sha }}
|
||||
VCS_REF=${{ github.sha }}
|
||||
BUILD_DATE=${{ github.event.repository.pushed_at }}
|
||||
ALPINE_IMAGE=${{ steps.alpine.outputs.image }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
provenance: true
|
||||
sbom: true
|
||||
|
||||
- name: Resolve and export image digest
|
||||
id: resolve_digest
|
||||
run: |
|
||||
set -euo pipefail
|
||||
DIGEST="${{ steps.build.outputs.digest }}"
|
||||
|
||||
if [[ -z "$DIGEST" ]]; then
|
||||
echo "Build action digest empty; querying GHCR registry API..."
|
||||
GHCR_TOKEN=$(curl -sf \
|
||||
-u "${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}" \
|
||||
"https://ghcr.io/token?scope=repository:${{ env.IMAGE_NAME }}:pull&service=ghcr.io" \
|
||||
| jq -r '.token')
|
||||
DIGEST=$(curl -sfI \
|
||||
-H "Authorization: Bearer ${GHCR_TOKEN}" \
|
||||
-H "Accept: application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.oci.image.manifest.v1+json" \
|
||||
"https://ghcr.io/v2/${{ env.IMAGE_NAME }}/manifests/nightly" \
|
||||
| grep -i '^docker-content-digest:' | awk '{print $2}' | tr -d '\r' || true)
|
||||
[[ -n "$DIGEST" ]] && echo "Resolved from GHCR API: ${DIGEST}"
|
||||
fi
|
||||
|
||||
if [[ -z "$DIGEST" ]]; then
|
||||
echo "::error::Could not determine image digest from step output or GHCR registry API"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "RESOLVED_DIGEST=${DIGEST}" >> "$GITHUB_ENV"
|
||||
echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
|
||||
echo "Exported digest: ${DIGEST}"
|
||||
|
||||
- name: Record nightly image digest
|
||||
run: |
|
||||
echo "## 🧾 Nightly Image Digest" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ steps.build.outputs.digest }}" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "- ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ steps.resolve_digest.outputs.digest }}" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Generate SBOM
|
||||
uses: anchore/sbom-action@28d71544de8eaf1b958d335707167c5f783590ad # v0.22.2
|
||||
id: sbom_primary
|
||||
continue-on-error: true
|
||||
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
|
||||
with:
|
||||
image: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ steps.build.outputs.digest }}
|
||||
image: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.resolve_digest.outputs.digest }}
|
||||
format: cyclonedx-json
|
||||
output-file: sbom-nightly.json
|
||||
syft-version: v1.42.1
|
||||
|
||||
- name: Generate SBOM fallback with pinned Syft
|
||||
if: always()
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${{ steps.sbom_primary.outcome }}" == "success" ]] && [[ -s sbom-nightly.json ]] && jq -e . sbom-nightly.json >/dev/null 2>&1; then
|
||||
echo "Primary SBOM generation succeeded with valid JSON; skipping fallback"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Primary SBOM generation failed or produced missing/invalid output; using deterministic Syft fallback"
|
||||
|
||||
SYFT_VERSION="v1.42.3"
|
||||
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
ARCH="$(uname -m)"
|
||||
case "$ARCH" in
|
||||
x86_64) ARCH="amd64" ;;
|
||||
aarch64|arm64) ARCH="arm64" ;;
|
||||
*) echo "Unsupported architecture: $ARCH"; exit 1 ;;
|
||||
esac
|
||||
|
||||
TARBALL="syft_${SYFT_VERSION#v}_${OS}_${ARCH}.tar.gz"
|
||||
BASE_URL="https://github.com/anchore/syft/releases/download/${SYFT_VERSION}"
|
||||
|
||||
curl -fsSLo "$TARBALL" "${BASE_URL}/${TARBALL}"
|
||||
curl -fsSLo checksums.txt "${BASE_URL}/syft_${SYFT_VERSION#v}_checksums.txt"
|
||||
|
||||
grep " ${TARBALL}$" checksums.txt > checksum_line.txt
|
||||
sha256sum -c checksum_line.txt
|
||||
|
||||
tar -xzf "$TARBALL" syft
|
||||
chmod +x syft
|
||||
|
||||
DIGEST="${{ steps.resolve_digest.outputs.digest }}"
|
||||
if [[ -z "$DIGEST" ]]; then
|
||||
echo "::error::Digest from resolve_digest step is empty; the digest-resolution step did not complete successfully"
|
||||
exit 1
|
||||
fi
|
||||
./syft "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${DIGEST}" -o cyclonedx-json=sbom-nightly.json
|
||||
|
||||
- name: Verify SBOM artifact
|
||||
if: always()
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -s sbom-nightly.json
|
||||
jq -e . sbom-nightly.json >/dev/null
|
||||
jq -e '
|
||||
.bomFormat == "CycloneDX"
|
||||
and (.specVersion | type == "string" and length > 0)
|
||||
and has("version")
|
||||
and has("metadata")
|
||||
and (.components | type == "array")
|
||||
' sbom-nightly.json >/dev/null
|
||||
|
||||
- name: Upload SBOM artifact
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: sbom-nightly
|
||||
path: sbom-nightly.json
|
||||
@@ -235,13 +333,13 @@ jobs:
|
||||
|
||||
# Install Cosign for keyless signing
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||
uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0
|
||||
|
||||
# Sign GHCR image with keyless signing (Sigstore/Fulcio)
|
||||
- name: Sign GHCR Image
|
||||
run: |
|
||||
echo "Signing GHCR nightly image with keyless signing..."
|
||||
cosign sign --yes "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}"
|
||||
cosign sign --yes "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.resolve_digest.outputs.digest }}"
|
||||
echo "✅ GHCR nightly image signed successfully"
|
||||
|
||||
# Sign Docker Hub image with keyless signing (Sigstore/Fulcio)
|
||||
@@ -249,7 +347,7 @@ jobs:
|
||||
if: env.HAS_DOCKERHUB_TOKEN == 'true'
|
||||
run: |
|
||||
echo "Signing Docker Hub nightly image with keyless signing..."
|
||||
cosign sign --yes "${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}"
|
||||
cosign sign --yes "${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.resolve_digest.outputs.digest }}"
|
||||
echo "✅ Docker Hub nightly image signed successfully"
|
||||
|
||||
# Attach SBOM to Docker Hub image
|
||||
@@ -257,7 +355,7 @@ jobs:
|
||||
if: env.HAS_DOCKERHUB_TOKEN == 'true'
|
||||
run: |
|
||||
echo "Attaching SBOM to Docker Hub nightly image..."
|
||||
cosign attach sbom --sbom sbom-nightly.json "${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}"
|
||||
cosign attach sbom --sbom sbom-nightly.json "${{ env.DOCKERHUB_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.resolve_digest.outputs.digest }}"
|
||||
echo "✅ SBOM attached to Docker Hub nightly image"
|
||||
|
||||
test-nightly-image:
|
||||
@@ -271,13 +369,13 @@ jobs:
|
||||
- name: Checkout nightly branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: nightly
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || 'nightly' }}
|
||||
|
||||
- name: Set lowercase image name
|
||||
run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ${{ env.GHCR_REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -288,9 +386,10 @@ jobs:
|
||||
|
||||
- name: Run container smoke test
|
||||
run: |
|
||||
IMAGE_REF="${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ needs.build-and-push-nightly.outputs.digest }}"
|
||||
docker run --name charon-nightly -d \
|
||||
-p 8080:8080 \
|
||||
"${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ needs.build-and-push-nightly.outputs.digest }}"
|
||||
"${IMAGE_REF}"
|
||||
|
||||
# Wait for container to start
|
||||
sleep 10
|
||||
@@ -325,40 +424,211 @@ jobs:
|
||||
- name: Checkout nightly branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: nightly
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && github.ref || 'nightly' }}
|
||||
|
||||
- name: Set lowercase image name
|
||||
run: echo "IMAGE_NAME_LC=${IMAGE_NAME,,}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Download SBOM
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: sbom-nightly
|
||||
|
||||
- name: Scan with Grype
|
||||
uses: anchore/scan-action@7037fa011853d5a11690026fb85feee79f4c946c # v7.3.2
|
||||
uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0
|
||||
with:
|
||||
sbom: sbom-nightly.json
|
||||
fail-build: false
|
||||
severity-cutoff: high
|
||||
|
||||
- name: Scan with Trivy
|
||||
uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # 0.34.0
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
|
||||
with:
|
||||
image-ref: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ needs.build-and-push-nightly.outputs.digest }}
|
||||
image-ref: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}:nightly@${{ needs.build-and-push-nightly.outputs.digest }}
|
||||
format: 'sarif'
|
||||
output: 'trivy-nightly.sarif'
|
||||
version: 'v0.69.3'
|
||||
trivyignores: '.trivyignore'
|
||||
|
||||
- name: Upload Trivy results
|
||||
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||
uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
with:
|
||||
sarif_file: 'trivy-nightly.sarif'
|
||||
category: 'trivy-nightly'
|
||||
|
||||
- name: Check for critical CVEs
|
||||
- name: Security severity policy summary
|
||||
run: |
|
||||
if grep -q "CRITICAL" trivy-nightly.sarif; then
|
||||
echo "❌ Critical vulnerabilities found in nightly build"
|
||||
{
|
||||
echo "## 🔐 Nightly Supply Chain Severity Policy"
|
||||
echo ""
|
||||
echo "- Blocking: Critical, High"
|
||||
echo "- Medium: non-blocking by default (report + triage SLA)"
|
||||
echo "- Policy file: .github/security-severity-policy.yml"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Check for Critical/High CVEs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
jq -e . trivy-nightly.sarif >/dev/null
|
||||
|
||||
CRITICAL_COUNT=$(jq -r '
|
||||
[
|
||||
.runs[] as $run
|
||||
| ($run.tool.driver.rules // []) as $rules
|
||||
| $run.results[]?
|
||||
| . as $result
|
||||
| (
|
||||
(
|
||||
if (($result.ruleIndex | type) == "number") then
|
||||
($rules[$result.ruleIndex].properties["security-severity"] // empty)
|
||||
else
|
||||
empty
|
||||
end
|
||||
)
|
||||
// ([
|
||||
$rules[]?
|
||||
| select((.id // "") == ($result.ruleId // ""))
|
||||
| .properties["security-severity"]
|
||||
][0] // empty)
|
||||
// empty
|
||||
) as $securitySeverity
|
||||
| (try ($securitySeverity | tonumber) catch empty) as $score
|
||||
| select($score != null and $score >= 9.0)
|
||||
] | length
|
||||
' trivy-nightly.sarif)
|
||||
|
||||
HIGH_COUNT=$(jq -r '
|
||||
[
|
||||
.runs[] as $run
|
||||
| ($run.tool.driver.rules // []) as $rules
|
||||
| $run.results[]?
|
||||
| . as $result
|
||||
| (
|
||||
(
|
||||
if (($result.ruleIndex | type) == "number") then
|
||||
($rules[$result.ruleIndex].properties["security-severity"] // empty)
|
||||
else
|
||||
empty
|
||||
end
|
||||
)
|
||||
// ([
|
||||
$rules[]?
|
||||
| select((.id // "") == ($result.ruleId // ""))
|
||||
| .properties["security-severity"]
|
||||
][0] // empty)
|
||||
// empty
|
||||
) as $securitySeverity
|
||||
| (try ($securitySeverity | tonumber) catch empty) as $score
|
||||
| select($score != null and $score >= 7.0 and $score < 9.0)
|
||||
] | length
|
||||
' trivy-nightly.sarif)
|
||||
|
||||
MEDIUM_COUNT=$(jq -r '
|
||||
[
|
||||
.runs[] as $run
|
||||
| ($run.tool.driver.rules // []) as $rules
|
||||
| $run.results[]?
|
||||
| . as $result
|
||||
| (
|
||||
(
|
||||
if (($result.ruleIndex | type) == "number") then
|
||||
($rules[$result.ruleIndex].properties["security-severity"] // empty)
|
||||
else
|
||||
empty
|
||||
end
|
||||
)
|
||||
// ([
|
||||
$rules[]?
|
||||
| select((.id // "") == ($result.ruleId // ""))
|
||||
| .properties["security-severity"]
|
||||
][0] // empty)
|
||||
// empty
|
||||
) as $securitySeverity
|
||||
| (try ($securitySeverity | tonumber) catch empty) as $score
|
||||
| select($score != null and $score >= 4.0 and $score < 7.0)
|
||||
] | length
|
||||
' trivy-nightly.sarif)
|
||||
|
||||
{
|
||||
echo "- Structured SARIF counts: CRITICAL=${CRITICAL_COUNT}, HIGH=${HIGH_COUNT}, MEDIUM=${MEDIUM_COUNT}"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
# List all Critical/High/Medium findings with details for triage
|
||||
# shellcheck disable=SC2016
|
||||
LIST_FINDINGS='
|
||||
.runs[] as $run
|
||||
| ($run.tool.driver.rules // []) as $rules
|
||||
| $run.results[]?
|
||||
| . as $result
|
||||
| (
|
||||
(
|
||||
if (($result.ruleIndex | type) == "number") then
|
||||
($rules[$result.ruleIndex] // {})
|
||||
else
|
||||
{}
|
||||
end
|
||||
) as $ruleByIndex
|
||||
| (
|
||||
[$rules[]? | select((.id // "") == ($result.ruleId // ""))][0] // {}
|
||||
) as $ruleById
|
||||
| ($ruleByIndex // $ruleById) as $rule
|
||||
| ($rule.properties["security-severity"] // null) as $sev
|
||||
| (try ($sev | tonumber) catch null) as $score
|
||||
| select($score != null and $score >= 4.0)
|
||||
| {
|
||||
id: ($result.ruleId // "unknown"),
|
||||
score: $score,
|
||||
severity: (
|
||||
if $score >= 9.0 then "CRITICAL"
|
||||
elif $score >= 7.0 then "HIGH"
|
||||
else "MEDIUM"
|
||||
end
|
||||
),
|
||||
message: ($result.message.text // $rule.shortDescription.text // "no description")[0:120]
|
||||
}
|
||||
)
|
||||
'
|
||||
|
||||
echo ""
|
||||
echo "=== Vulnerability Details ==="
|
||||
jq -r "[ ${LIST_FINDINGS} ] | sort_by(-.score) | .[] | \"\\(.severity) (\\(.score)): \\(.id) — \\(.message)\"" trivy-nightly.sarif || true
|
||||
echo "============================="
|
||||
echo ""
|
||||
|
||||
if [ "$CRITICAL_COUNT" -gt 0 ]; then
|
||||
echo "❌ Critical vulnerabilities found in nightly build (${CRITICAL_COUNT})"
|
||||
{
|
||||
echo ""
|
||||
echo "### ❌ Critical CVEs blocking nightly"
|
||||
echo '```'
|
||||
jq -r "[ ${LIST_FINDINGS} | select(.severity == \"CRITICAL\") ] | sort_by(-.score) | .[] | \"\\(.id) (score: \\(.score)): \\(.message)\"" trivy-nightly.sarif || true
|
||||
echo '```'
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ No critical vulnerabilities found"
|
||||
|
||||
if [ "$HIGH_COUNT" -gt 0 ]; then
|
||||
echo "❌ High vulnerabilities found in nightly build (${HIGH_COUNT})"
|
||||
{
|
||||
echo ""
|
||||
echo "### ❌ High CVEs blocking nightly"
|
||||
echo '```'
|
||||
jq -r "[ ${LIST_FINDINGS} | select(.severity == \"HIGH\") ] | sort_by(-.score) | .[] | \"\\(.id) (score: \\(.score)): \\(.message)\"" trivy-nightly.sarif || true
|
||||
echo '```'
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$MEDIUM_COUNT" -gt 0 ]; then
|
||||
echo "::warning::Medium vulnerabilities found in nightly build (${MEDIUM_COUNT}). Non-blocking by policy; triage with SLA per .github/security-severity-policy.yml"
|
||||
{
|
||||
echo ""
|
||||
echo "### ⚠️ Medium CVEs (non-blocking)"
|
||||
echo '```'
|
||||
jq -r "[ ${LIST_FINDINGS} | select(.severity == \"MEDIUM\") ] | sort_by(-.score) | .[] | \"\\(.id) (score: \\(.score)): \\(.message)\"" trivy-nightly.sarif || true
|
||||
echo '```'
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
echo "✅ No Critical/High vulnerabilities found"
|
||||
|
||||
2
.github/workflows/propagate-changes.yml
vendored
2
.github/workflows/propagate-changes.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
(github.event.workflow_run.head_branch == 'main' || github.event.workflow_run.head_branch == 'development')
|
||||
steps:
|
||||
- name: Set up Node (for github-script)
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
|
||||
43
.github/workflows/quality-checks.yml
vendored
43
.github/workflows/quality-checks.yml
vendored
@@ -3,6 +3,9 @@ name: Quality Checks
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- nightly
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -13,11 +16,33 @@ permissions:
|
||||
checks: write
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.26.0'
|
||||
GO_VERSION: '1.26.1'
|
||||
NODE_VERSION: '24.12.0'
|
||||
GOTOOLCHAIN: auto
|
||||
|
||||
jobs:
|
||||
auth-route-protection-contract:
|
||||
name: Auth Route Protection Contract
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.sha }}
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
cache-dependency-path: backend/go.sum
|
||||
|
||||
- name: Run auth protection contract tests
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cd backend
|
||||
go test ./internal/api/routes -run 'TestRegister_StateChangingRoutesRequireAuthentication|TestRegister_StateChangingRoutesDenyByDefaultWithExplicitAllowlist|TestRegister_AuthenticatedRoutes' -count=1 -v
|
||||
|
||||
codecov-trigger-parity-guard:
|
||||
name: Codecov Trigger/Comment Parity Guard
|
||||
runs-on: ubuntu-latest
|
||||
@@ -113,9 +138,10 @@ jobs:
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
cache-dependency-path: backend/go.sum
|
||||
|
||||
- name: Repo health check
|
||||
@@ -128,8 +154,7 @@ jobs:
|
||||
env:
|
||||
CGO_ENABLED: 1
|
||||
run: |
|
||||
bash "scripts/go-test-coverage.sh" 2>&1 | tee backend/test-output.txt
|
||||
exit "${PIPESTATUS[0]}"
|
||||
bash "scripts/go-test-coverage.sh" 2>&1 | tee backend/test-output.txt; exit "${PIPESTATUS[0]}"
|
||||
|
||||
- name: Go Test Summary
|
||||
if: always()
|
||||
@@ -206,11 +231,12 @@ jobs:
|
||||
PERF_MAX_MS_GETSTATUS_P95_PARALLEL: 1500ms
|
||||
PERF_MAX_MS_LISTDECISIONS_P95: 2000ms
|
||||
run: |
|
||||
go test -run TestPerf -v ./internal/api/handlers -count=1 2>&1 | tee perf-output.txt; PERF_STATUS="${PIPESTATUS[0]}"
|
||||
{
|
||||
echo "## 🔍 Running performance assertions (TestPerf)"
|
||||
go test -run TestPerf -v ./internal/api/handlers -count=1 | tee perf-output.txt
|
||||
cat perf-output.txt
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
exit "${PIPESTATUS[0]}"
|
||||
exit "$PERF_STATUS"
|
||||
|
||||
frontend-quality:
|
||||
name: Frontend (React)
|
||||
@@ -225,7 +251,7 @@ jobs:
|
||||
bash "scripts/repo_health_check.sh"
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
cache: 'npm'
|
||||
@@ -272,8 +298,7 @@ jobs:
|
||||
id: frontend-tests
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
bash scripts/frontend-test-coverage.sh 2>&1 | tee frontend/test-output.txt
|
||||
exit "${PIPESTATUS[0]}"
|
||||
bash scripts/frontend-test-coverage.sh 2>&1 | tee frontend/test-output.txt; exit "${PIPESTATUS[0]}"
|
||||
|
||||
- name: Frontend Test Summary
|
||||
if: always()
|
||||
|
||||
4
.github/workflows/rate-limit-integration.yml
vendored
4
.github/workflows/rate-limit-integration.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Build Docker image (Local)
|
||||
run: |
|
||||
echo "Building image locally for integration tests..."
|
||||
docker build -t charon:local .
|
||||
docker build -t charon:local --build-arg CI="${CI:-false}" .
|
||||
echo "✅ Successfully built charon:local"
|
||||
|
||||
- name: Run rate limit integration tests
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
|
||||
echo "### Caddy Admin Config (rate_limit handlers)"
|
||||
echo '```json'
|
||||
curl -s http://localhost:2119/config 2>/dev/null | grep -A 20 '"handler":"rate_limit"' | head -30 || echo "Could not retrieve Caddy config"
|
||||
curl -s http://localhost:2119/config/ 2>/dev/null | grep -A 20 '"handler":"rate_limit"' | head -30 || echo "Could not retrieve Caddy config"
|
||||
echo '```'
|
||||
echo ""
|
||||
|
||||
|
||||
22
.github/workflows/release-goreleaser.yml
vendored
22
.github/workflows/release-goreleaser.yml
vendored
@@ -10,7 +10,7 @@ concurrency:
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.26.0'
|
||||
GO_VERSION: '1.26.1'
|
||||
NODE_VERSION: '24.12.0'
|
||||
GOTOOLCHAIN: auto
|
||||
|
||||
@@ -20,6 +20,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
if: ${{ !contains(github.ref_name, '-candidate') && !contains(github.ref_name, '-rc') }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
# Use the built-in GITHUB_TOKEN by default for GitHub API operations.
|
||||
@@ -32,13 +33,26 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Enforce PR-2 release promotion guard
|
||||
env:
|
||||
REPO_VARS_JSON: ${{ toJSON(vars) }}
|
||||
run: |
|
||||
PR2_GATE_STATUS="$(printf '%s' "$REPO_VARS_JSON" | jq -r '.CHARON_PR2_GATES_PASSED // "false"')"
|
||||
if [[ "$PR2_GATE_STATUS" != "true" ]]; then
|
||||
echo "::error::Releasable tag promotion is blocked until PR-2 security/retirement gates pass."
|
||||
echo "::error::Set repository variable CHARON_PR2_GATES_PASSED=true only after PR-2 approval."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6
|
||||
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
|
||||
cache-dependency-path: backend/go.sum
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
@@ -61,7 +75,7 @@ jobs:
|
||||
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6
|
||||
uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: '~> v2.5'
|
||||
|
||||
2
.github/workflows/renovate.yml
vendored
2
.github/workflows/renovate.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Renovate
|
||||
uses: renovatebot/github-action@d65ef9e20512193cc070238b49c3873a361cd50c # v46.1.1
|
||||
uses: renovatebot/github-action@abd08c7549b2a864af5df4a2e369c43f035a6a9d # v46.1.5
|
||||
with:
|
||||
configurationFile: .github/renovate.json
|
||||
token: ${{ secrets.RENOVATE_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
|
||||
2
.github/workflows/repo-health.yml
vendored
2
.github/workflows/repo-health.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
- name: Upload health output
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: repo-health-output
|
||||
path: |
|
||||
|
||||
339
.github/workflows/security-pr.yml
vendored
339
.github/workflows/security-pr.yml
vendored
@@ -4,18 +4,22 @@
|
||||
name: Security Scan (PR)
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Docker Build, Publish & Test"]
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'PR number to scan (optional)'
|
||||
required: false
|
||||
description: 'PR number to scan'
|
||||
required: true
|
||||
type: string
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
|
||||
concurrency:
|
||||
group: security-pr-${{ github.event.workflow_run.event || github.event_name }}-${{ github.event.workflow_run.head_branch || github.ref }}
|
||||
group: security-pr-${{ github.event_name == 'workflow_run' && github.event.workflow_run.event || github.event_name }}-${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
@@ -23,16 +27,18 @@ jobs:
|
||||
name: Trivy Binary Scan
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
# Run for: manual dispatch, PR builds, or any push builds from docker-build
|
||||
# Run for manual dispatch, direct PR/push, or successful upstream workflow_run
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
github.event_name == 'pull_request' ||
|
||||
((github.event.workflow_run.event == 'push' || github.event.workflow_run.pull_requests[0].number != null) &&
|
||||
(github.event.workflow_run.status != 'completed' || github.event.workflow_run.conclusion == 'success'))
|
||||
github.event_name == 'push' ||
|
||||
(github.event_name == 'workflow_run' &&
|
||||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.status == 'completed' &&
|
||||
github.event.workflow_run.conclusion == 'success')
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
security-events: write
|
||||
actions: read
|
||||
|
||||
@@ -41,27 +47,65 @@ jobs:
|
||||
# actions/checkout v4.2.2
|
||||
uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }}
|
||||
|
||||
- name: Extract PR number from workflow_run
|
||||
id: pr-info
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
# Manual dispatch - use input or fail gracefully
|
||||
if [[ -n "${{ inputs.pr_number }}" ]]; then
|
||||
echo "pr_number=${{ inputs.pr_number }}" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ Using manually provided PR number: ${{ inputs.pr_number }}"
|
||||
else
|
||||
echo "⚠️ No PR number provided for manual dispatch"
|
||||
echo "pr_number=" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
if [[ "${{ github.event_name }}" == "push" ]]; then
|
||||
echo "pr_number=" >> "$GITHUB_OUTPUT"
|
||||
echo "is_push=true" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ Push event detected; using local image path"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
echo "pr_number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT"
|
||||
echo "is_push=false" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ Pull request event detected: PR #${{ github.event.pull_request.number }}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
INPUT_PR_NUMBER="${{ inputs.pr_number }}"
|
||||
if [[ -z "${INPUT_PR_NUMBER}" ]]; then
|
||||
echo "❌ workflow_dispatch requires inputs.pr_number"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! "${INPUT_PR_NUMBER}" =~ ^[0-9]+$ ]]; then
|
||||
echo "❌ reason_category=invalid_input"
|
||||
echo "reason=workflow_dispatch pr_number must be digits-only"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PR_NUMBER="${INPUT_PR_NUMBER}"
|
||||
echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT"
|
||||
echo "is_push=false" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ Using manually provided PR number: ${PR_NUMBER}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${{ github.event_name }}" == "workflow_run" ]]; then
|
||||
if [[ "${{ github.event.workflow_run.event }}" != "pull_request" ]]; then
|
||||
# Explicit contract validation happens in the dedicated guard step.
|
||||
echo "pr_number=" >> "$GITHUB_OUTPUT"
|
||||
echo "is_push=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -n "${{ github.event.workflow_run.pull_requests[0].number || '' }}" ]]; then
|
||||
echo "pr_number=${{ github.event.workflow_run.pull_requests[0].number }}" >> "$GITHUB_OUTPUT"
|
||||
echo "is_push=false" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ Found PR number from workflow_run payload: ${{ github.event.workflow_run.pull_requests[0].number }}"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Extract PR number from context
|
||||
HEAD_SHA="${{ github.event.workflow_run.head_sha || github.event.pull_request.head.sha || github.sha }}"
|
||||
HEAD_SHA="${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.event.pull_request.head.sha || github.sha }}"
|
||||
echo "🔍 Looking for PR with head SHA: ${HEAD_SHA}"
|
||||
|
||||
# Query GitHub API for PR associated with this commit
|
||||
@@ -73,21 +117,38 @@ jobs:
|
||||
|
||||
if [[ -n "${PR_NUMBER}" ]]; then
|
||||
echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT"
|
||||
echo "is_push=false" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ Found PR number: ${PR_NUMBER}"
|
||||
else
|
||||
echo "⚠️ Could not find PR number for SHA: ${HEAD_SHA}"
|
||||
echo "pr_number=" >> "$GITHUB_OUTPUT"
|
||||
echo "❌ Could not determine PR number for workflow_run SHA: ${HEAD_SHA}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if this is a push event (not a PR)
|
||||
if [[ "${{ github.event_name }}" == "push" || "${{ github.event.workflow_run.event }}" == "push" || -z "${PR_NUMBER}" ]]; then
|
||||
HEAD_BRANCH="${{ github.event.workflow_run.head_branch || github.ref_name }}"
|
||||
echo "is_push=true" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ Detected push build from branch: ${HEAD_BRANCH}"
|
||||
else
|
||||
echo "is_push=false" >> "$GITHUB_OUTPUT"
|
||||
- name: Validate workflow_run trust boundary and event contract
|
||||
if: github.event_name == 'workflow_run'
|
||||
run: |
|
||||
if [[ "${{ github.event.workflow_run.name }}" != "Docker Build, Publish & Test" ]]; then
|
||||
echo "❌ reason_category=unexpected_upstream_workflow"
|
||||
echo "workflow_name=${{ github.event.workflow_run.name }}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${{ github.event.workflow_run.event }}" != "pull_request" ]]; then
|
||||
echo "❌ reason_category=unsupported_upstream_event"
|
||||
echo "upstream_event=${{ github.event.workflow_run.event }}"
|
||||
echo "run_id=${{ github.event.workflow_run.id }}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${{ github.event.workflow_run.head_repository.full_name }}" != "${{ github.repository }}" ]]; then
|
||||
echo "❌ reason_category=untrusted_upstream_repository"
|
||||
echo "upstream_head_repository=${{ github.event.workflow_run.head_repository.full_name }}"
|
||||
echo "expected_repository=${{ github.repository }}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ workflow_run trust boundary and event contract validated"
|
||||
|
||||
- name: Build Docker image (Local)
|
||||
if: github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
run: |
|
||||
@@ -97,95 +158,149 @@ jobs:
|
||||
|
||||
- name: Check for PR image artifact
|
||||
id: check-artifact
|
||||
if: (steps.pr-info.outputs.pr_number != '' || steps.pr-info.outputs.is_push == 'true') && github.event_name != 'push' && github.event_name != 'pull_request'
|
||||
if: github.event_name == 'workflow_run' || github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# Determine artifact name based on event type
|
||||
if [[ "${{ steps.pr-info.outputs.is_push }}" == "true" ]]; then
|
||||
ARTIFACT_NAME="push-image"
|
||||
else
|
||||
PR_NUMBER="${{ steps.pr-info.outputs.pr_number }}"
|
||||
ARTIFACT_NAME="pr-image-${PR_NUMBER}"
|
||||
PR_NUMBER="${{ steps.pr-info.outputs.pr_number }}"
|
||||
if [[ ! "${PR_NUMBER}" =~ ^[0-9]+$ ]]; then
|
||||
echo "❌ reason_category=invalid_input"
|
||||
echo "reason=Resolved PR number must be digits-only"
|
||||
exit 1
|
||||
fi
|
||||
RUN_ID="${{ github.event.workflow_run.id }}"
|
||||
|
||||
ARTIFACT_NAME="pr-image-${PR_NUMBER}"
|
||||
RUN_ID="${{ github.event_name == 'workflow_run' && github.event.workflow_run.id || '' }}"
|
||||
|
||||
echo "🔍 Checking for artifact: ${ARTIFACT_NAME}"
|
||||
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
# For manual dispatch, find the most recent workflow run with this artifact
|
||||
RUN_ID=$(gh api \
|
||||
# Manual replay path: find latest successful docker-build pull_request run for this PR.
|
||||
RUNS_JSON=$(gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/repos/${{ github.repository }}/actions/workflows/docker-build.yml/runs?status=success&per_page=10" \
|
||||
--jq '.workflow_runs[0].id // empty' 2>/dev/null || echo "")
|
||||
"/repos/${{ github.repository }}/actions/workflows/docker-build.yml/runs?event=pull_request&status=success&per_page=100" 2>&1)
|
||||
RUNS_STATUS=$?
|
||||
|
||||
if [[ ${RUNS_STATUS} -ne 0 ]]; then
|
||||
echo "❌ reason_category=api_error"
|
||||
echo "reason=Failed to query workflow runs for PR lookup"
|
||||
echo "upstream_run_id=unknown"
|
||||
echo "artifact_name=${ARTIFACT_NAME}"
|
||||
echo "api_output=${RUNS_JSON}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RUN_ID=$(printf '%s' "${RUNS_JSON}" | jq -r --argjson pr "${PR_NUMBER}" '.workflow_runs[] | select((.pull_requests // []) | any(.number == $pr)) | .id' | head -n 1)
|
||||
|
||||
if [[ -z "${RUN_ID}" ]]; then
|
||||
echo "⚠️ No successful workflow runs found"
|
||||
echo "artifact_exists=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
echo "❌ reason_category=not_found"
|
||||
echo "reason=No successful docker-build pull_request run found for PR #${PR_NUMBER}"
|
||||
echo "upstream_run_id=unknown"
|
||||
echo "artifact_name=${ARTIFACT_NAME}"
|
||||
exit 1
|
||||
fi
|
||||
elif [[ -z "${RUN_ID}" ]]; then
|
||||
# If triggered by push/pull_request, RUN_ID is empty. Find recent run for this commit.
|
||||
HEAD_SHA="${{ github.event.workflow_run.head_sha || github.event.pull_request.head.sha || github.sha }}"
|
||||
echo "🔍 Searching for workflow run for SHA: ${HEAD_SHA}"
|
||||
# Retry a few times as the run might be just starting or finishing
|
||||
for i in {1..3}; do
|
||||
RUN_ID=$(gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/repos/${{ github.repository }}/actions/workflows/docker-build.yml/runs?head_sha=${HEAD_SHA}&status=success&per_page=1" \
|
||||
--jq '.workflow_runs[0].id // empty' 2>/dev/null || echo "")
|
||||
if [[ -n "${RUN_ID}" ]]; then break; fi
|
||||
echo "⏳ Waiting for workflow run to appear/complete... ($i/3)"
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
|
||||
echo "run_id=${RUN_ID}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Check if the artifact exists in the workflow run
|
||||
ARTIFACT_ID=$(gh api \
|
||||
ARTIFACTS_JSON=$(gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/repos/${{ github.repository }}/actions/runs/${RUN_ID}/artifacts" \
|
||||
--jq ".artifacts[] | select(.name == \"${ARTIFACT_NAME}\") | .id" 2>/dev/null || echo "")
|
||||
"/repos/${{ github.repository }}/actions/runs/${RUN_ID}/artifacts" 2>&1)
|
||||
ARTIFACTS_STATUS=$?
|
||||
|
||||
if [[ -n "${ARTIFACT_ID}" ]]; then
|
||||
echo "artifact_exists=true" >> "$GITHUB_OUTPUT"
|
||||
echo "artifact_id=${ARTIFACT_ID}" >> "$GITHUB_OUTPUT"
|
||||
echo "✅ Found artifact: ${ARTIFACT_NAME} (ID: ${ARTIFACT_ID})"
|
||||
else
|
||||
echo "artifact_exists=false" >> "$GITHUB_OUTPUT"
|
||||
echo "⚠️ Artifact not found: ${ARTIFACT_NAME}"
|
||||
echo "ℹ️ This is expected for non-PR builds or if the image was not uploaded"
|
||||
if [[ ${ARTIFACTS_STATUS} -ne 0 ]]; then
|
||||
echo "❌ reason_category=api_error"
|
||||
echo "reason=Failed to query artifacts for upstream run"
|
||||
echo "upstream_run_id=${RUN_ID}"
|
||||
echo "artifact_name=${ARTIFACT_NAME}"
|
||||
echo "api_output=${ARTIFACTS_JSON}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Skip if no artifact
|
||||
if: ((steps.pr-info.outputs.pr_number == '' && steps.pr-info.outputs.is_push != 'true') || steps.check-artifact.outputs.artifact_exists != 'true') && github.event_name != 'push' && github.event_name != 'pull_request'
|
||||
run: |
|
||||
echo "ℹ️ Skipping security scan - no PR image artifact available"
|
||||
echo "This is expected for:"
|
||||
echo " - Pushes to main/release branches"
|
||||
echo " - PRs where Docker build failed"
|
||||
echo " - Manual dispatch without PR number"
|
||||
exit 0
|
||||
ARTIFACT_ID=$(printf '%s' "${ARTIFACTS_JSON}" | jq -r --arg name "${ARTIFACT_NAME}" '.artifacts[] | select(.name == $name) | .id' | head -n 1)
|
||||
|
||||
if [[ -z "${ARTIFACT_ID}" ]]; then
|
||||
echo "❌ reason_category=not_found"
|
||||
echo "reason=Required artifact was not found"
|
||||
echo "upstream_run_id=${RUN_ID}"
|
||||
echo "artifact_name=${ARTIFACT_NAME}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
{
|
||||
echo "artifact_exists=true"
|
||||
echo "artifact_id=${ARTIFACT_ID}"
|
||||
echo "artifact_name=${ARTIFACT_NAME}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
echo "✅ Found artifact: ${ARTIFACT_NAME} (ID: ${ARTIFACT_ID})"
|
||||
|
||||
- name: Download PR image artifact
|
||||
if: steps.check-artifact.outputs.artifact_exists == 'true'
|
||||
if: github.event_name == 'workflow_run' || github.event_name == 'workflow_dispatch'
|
||||
# actions/download-artifact v4.1.8
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
|
||||
uses: actions/download-artifact@484a0b528fb4d7bd804637ccb632e47a0e638317
|
||||
with:
|
||||
name: ${{ steps.pr-info.outputs.is_push == 'true' && 'push-image' || format('pr-image-{0}', steps.pr-info.outputs.pr_number) }}
|
||||
name: ${{ steps.check-artifact.outputs.artifact_name }}
|
||||
run-id: ${{ steps.check-artifact.outputs.run_id }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Load Docker image
|
||||
if: steps.check-artifact.outputs.artifact_exists == 'true'
|
||||
if: github.event_name == 'workflow_run' || github.event_name == 'workflow_dispatch'
|
||||
id: load-image
|
||||
run: |
|
||||
echo "📦 Loading Docker image..."
|
||||
docker load < charon-pr-image.tar
|
||||
echo "✅ Docker image loaded"
|
||||
|
||||
if [[ ! -r "charon-pr-image.tar" ]]; then
|
||||
echo "❌ ERROR: Artifact image tar is missing or unreadable"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MANIFEST_TAGS=""
|
||||
if tar -tf charon-pr-image.tar | grep -qx "manifest.json"; then
|
||||
MANIFEST_TAGS=$(tar -xOf charon-pr-image.tar manifest.json 2>/dev/null | jq -r '.[]?.RepoTags[]?' 2>/dev/null | sed '/^$/d' || true)
|
||||
else
|
||||
echo "⚠️ manifest.json not found in artifact tar; will try docker-load-image-id fallback"
|
||||
fi
|
||||
|
||||
LOAD_OUTPUT=$(docker load < charon-pr-image.tar 2>&1)
|
||||
echo "${LOAD_OUTPUT}"
|
||||
|
||||
SOURCE_IMAGE_REF=""
|
||||
SOURCE_RESOLUTION_MODE=""
|
||||
|
||||
while IFS= read -r tag; do
|
||||
[[ -z "${tag}" ]] && continue
|
||||
if docker image inspect "${tag}" >/dev/null 2>&1; then
|
||||
SOURCE_IMAGE_REF="${tag}"
|
||||
SOURCE_RESOLUTION_MODE="manifest_tag"
|
||||
break
|
||||
fi
|
||||
done <<< "${MANIFEST_TAGS}"
|
||||
|
||||
if [[ -z "${SOURCE_IMAGE_REF}" ]]; then
|
||||
LOAD_IMAGE_ID=$(printf '%s\n' "${LOAD_OUTPUT}" | sed -nE 's/^Loaded image ID: (sha256:[0-9a-f]+)$/\1/p' | head -n1)
|
||||
if [[ -n "${LOAD_IMAGE_ID}" ]] && docker image inspect "${LOAD_IMAGE_ID}" >/dev/null 2>&1; then
|
||||
SOURCE_IMAGE_REF="${LOAD_IMAGE_ID}"
|
||||
SOURCE_RESOLUTION_MODE="load_image_id"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "${SOURCE_IMAGE_REF}" ]]; then
|
||||
echo "❌ ERROR: Could not resolve a valid image reference from manifest tags or docker load image ID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker tag "${SOURCE_IMAGE_REF}" "charon:artifact"
|
||||
|
||||
{
|
||||
echo "source_image_ref=${SOURCE_IMAGE_REF}"
|
||||
echo "source_resolution_mode=${SOURCE_RESOLUTION_MODE}"
|
||||
echo "image_ref=charon:artifact"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "✅ Docker image resolved via ${SOURCE_RESOLUTION_MODE} and tagged as charon:artifact"
|
||||
docker images | grep charon
|
||||
|
||||
- name: Extract charon binary from container
|
||||
@@ -214,31 +329,10 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Normalize image name for reference
|
||||
IMAGE_NAME=$(echo "${{ github.repository_owner }}/charon" | tr '[:upper:]' '[:lower:]')
|
||||
if [[ "${{ steps.pr-info.outputs.is_push }}" == "true" ]]; then
|
||||
BRANCH_NAME="${{ github.event.workflow_run.head_branch }}"
|
||||
if [[ -z "${BRANCH_NAME}" ]]; then
|
||||
echo "❌ ERROR: Branch name is empty for push build"
|
||||
exit 1
|
||||
fi
|
||||
# Normalize branch name for Docker tag (replace / and other special chars with -)
|
||||
# This matches docker/metadata-action behavior: type=ref,event=branch
|
||||
TAG_SAFE_BRANCH="${BRANCH_NAME//\//-}"
|
||||
IMAGE_REF="ghcr.io/${IMAGE_NAME}:${TAG_SAFE_BRANCH}"
|
||||
elif [[ -n "${{ steps.pr-info.outputs.pr_number }}" ]]; then
|
||||
IMAGE_REF="ghcr.io/${IMAGE_NAME}:pr-${{ steps.pr-info.outputs.pr_number }}"
|
||||
else
|
||||
echo "❌ ERROR: Cannot determine image reference"
|
||||
echo " - is_push: ${{ steps.pr-info.outputs.is_push }}"
|
||||
echo " - pr_number: ${{ steps.pr-info.outputs.pr_number }}"
|
||||
echo " - branch: ${{ github.event.workflow_run.head_branch }}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate the image reference format
|
||||
if [[ ! "${IMAGE_REF}" =~ ^ghcr\.io/[a-z0-9_-]+/[a-z0-9_-]+:[a-zA-Z0-9._-]+$ ]]; then
|
||||
echo "❌ ERROR: Invalid image reference format: ${IMAGE_REF}"
|
||||
# For workflow_run artifact path, always use locally tagged image from loaded artifact.
|
||||
IMAGE_REF="${{ steps.load-image.outputs.image_ref }}"
|
||||
if [[ -z "${IMAGE_REF}" ]]; then
|
||||
echo "❌ ERROR: Loaded artifact image reference is empty"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -268,7 +362,7 @@ jobs:
|
||||
- name: Run Trivy filesystem scan (SARIF output)
|
||||
if: steps.check-artifact.outputs.artifact_exists == 'true' || github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
# aquasecurity/trivy-action v0.33.1
|
||||
uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1
|
||||
with:
|
||||
scan-type: 'fs'
|
||||
scan-ref: ${{ steps.extract.outputs.binary_path }}
|
||||
@@ -277,19 +371,30 @@ jobs:
|
||||
severity: 'CRITICAL,HIGH,MEDIUM'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Check Trivy SARIF output exists
|
||||
if: always() && (steps.check-artifact.outputs.artifact_exists == 'true' || github.event_name == 'push' || github.event_name == 'pull_request')
|
||||
id: trivy-sarif-check
|
||||
run: |
|
||||
if [[ -f trivy-binary-results.sarif ]]; then
|
||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||
echo "ℹ️ No Trivy SARIF output found; skipping SARIF/artifact upload steps"
|
||||
fi
|
||||
|
||||
- name: Upload Trivy SARIF to GitHub Security
|
||||
if: steps.check-artifact.outputs.artifact_exists == 'true' || github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
if: always() && steps.trivy-sarif-check.outputs.exists == 'true'
|
||||
# github/codeql-action v4
|
||||
uses: github/codeql-action/upload-sarif@5e7a52feb2a3dfb87f88be2af33b9e2275f48de6
|
||||
uses: github/codeql-action/upload-sarif@05b1a5d28f8763fd11e77388fe57846f1ba8e766
|
||||
with:
|
||||
sarif_file: 'trivy-binary-results.sarif'
|
||||
category: ${{ steps.pr-info.outputs.is_push == 'true' && format('security-scan-{0}', github.event.workflow_run.head_branch) || format('security-scan-pr-{0}', steps.pr-info.outputs.pr_number) }}
|
||||
category: ${{ steps.pr-info.outputs.is_push == 'true' && format('security-scan-{0}', github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.ref_name) || format('security-scan-pr-{0}', steps.pr-info.outputs.pr_number) }}
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run Trivy filesystem scan (fail on CRITICAL/HIGH)
|
||||
if: steps.check-artifact.outputs.artifact_exists == 'true' || github.event_name == 'push' || github.event_name == 'pull_request'
|
||||
# aquasecurity/trivy-action v0.33.1
|
||||
uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1
|
||||
with:
|
||||
scan-type: 'fs'
|
||||
scan-ref: ${{ steps.extract.outputs.binary_path }}
|
||||
@@ -298,11 +403,11 @@ jobs:
|
||||
exit-code: '1'
|
||||
|
||||
- name: Upload scan artifacts
|
||||
if: always() && (steps.check-artifact.outputs.artifact_exists == 'true' || github.event_name == 'push' || github.event_name == 'pull_request')
|
||||
if: always() && steps.trivy-sarif-check.outputs.exists == 'true'
|
||||
# actions/upload-artifact v4.4.3
|
||||
uses: actions/upload-artifact@47309c993abb98030a35d55ef7ff34b7fa1074b5
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: ${{ steps.pr-info.outputs.is_push == 'true' && format('security-scan-{0}', github.event.workflow_run.head_branch) || format('security-scan-pr-{0}', steps.pr-info.outputs.pr_number) }}
|
||||
name: ${{ steps.pr-info.outputs.is_push == 'true' && format('security-scan-{0}', github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.ref_name) || format('security-scan-pr-{0}', steps.pr-info.outputs.pr_number) }}
|
||||
path: |
|
||||
trivy-binary-results.sarif
|
||||
retention-days: 14
|
||||
@@ -312,7 +417,7 @@ jobs:
|
||||
run: |
|
||||
{
|
||||
if [[ "${{ steps.pr-info.outputs.is_push }}" == "true" ]]; then
|
||||
echo "## 🔒 Security Scan Results - Branch: ${{ github.event.workflow_run.head_branch }}"
|
||||
echo "## 🔒 Security Scan Results - Branch: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.ref_name }}"
|
||||
else
|
||||
echo "## 🔒 Security Scan Results - PR #${{ steps.pr-info.outputs.pr_number }}"
|
||||
fi
|
||||
|
||||
30
.github/workflows/security-weekly-rebuild.yml
vendored
30
.github/workflows/security-weekly-rebuild.yml
vendored
@@ -6,7 +6,7 @@ name: Weekly Security Rebuild
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * 0' # Sundays at 02:00 UTC
|
||||
- cron: '0 12 * * 2' # Tuesdays at 12:00 UTC
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
force_rebuild:
|
||||
@@ -36,16 +36,21 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
# Explicitly fetch the current HEAD of the ref at run time, not the
|
||||
# SHA that was frozen when this scheduled job was queued. Without this,
|
||||
# a queued job can run days later with stale code.
|
||||
ref: ${{ github.ref_name }}
|
||||
|
||||
- name: Normalize image name
|
||||
run: |
|
||||
echo "IMAGE_NAME=$(echo "${{ env.IMAGE_NAME }}" | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Resolve Debian base image digest
|
||||
id: base-image
|
||||
@@ -56,7 +61,7 @@ jobs:
|
||||
echo "Base image digest: $DIGEST"
|
||||
|
||||
- name: Log in to Container Registry
|
||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
@@ -64,7 +69,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
@@ -72,7 +77,7 @@ jobs:
|
||||
|
||||
- name: Build Docker image (NO CACHE)
|
||||
id: build
|
||||
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
@@ -88,38 +93,41 @@ jobs:
|
||||
BASE_IMAGE=${{ steps.base-image.outputs.digest }}
|
||||
|
||||
- name: Run Trivy vulnerability scanner (CRITICAL+HIGH)
|
||||
uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # 0.34.0
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
|
||||
with:
|
||||
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
|
||||
format: 'table'
|
||||
severity: 'CRITICAL,HIGH'
|
||||
exit-code: '1' # Fail workflow if vulnerabilities found
|
||||
version: 'v0.69.3'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run Trivy vulnerability scanner (SARIF)
|
||||
id: trivy-sarif
|
||||
uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # 0.34.0
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
|
||||
with:
|
||||
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
|
||||
format: 'sarif'
|
||||
output: 'trivy-weekly-results.sarif'
|
||||
severity: 'CRITICAL,HIGH,MEDIUM'
|
||||
version: 'v0.69.3'
|
||||
|
||||
- name: Upload Trivy results to GitHub Security
|
||||
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
|
||||
uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
|
||||
with:
|
||||
sarif_file: 'trivy-weekly-results.sarif'
|
||||
|
||||
- name: Run Trivy vulnerability scanner (JSON for artifact)
|
||||
uses: aquasecurity/trivy-action@c1824fd6edce30d7ab345a9989de00bbd46ef284 # 0.34.0
|
||||
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0
|
||||
with:
|
||||
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
|
||||
format: 'json'
|
||||
output: 'trivy-weekly-results.json'
|
||||
severity: 'CRITICAL,HIGH,MEDIUM,LOW'
|
||||
version: 'v0.69.3'
|
||||
|
||||
- name: Upload Trivy JSON results
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
name: trivy-weekly-scan-${{ github.run_number }}
|
||||
path: trivy-weekly-results.json
|
||||
|
||||
76
.github/workflows/supply-chain-pr.yml
vendored
76
.github/workflows/supply-chain-pr.yml
vendored
@@ -11,6 +11,8 @@ on:
|
||||
type: string
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency:
|
||||
group: supply-chain-pr-${{ github.event.workflow_run.event || github.event_name }}-${{ github.event.workflow_run.head_branch || github.ref }}
|
||||
@@ -264,7 +266,7 @@ jobs:
|
||||
# Generate SBOM using official Anchore action (auto-updated by Renovate)
|
||||
- name: Generate SBOM
|
||||
if: steps.set-target.outputs.image_name != ''
|
||||
uses: anchore/sbom-action@28d71544de8eaf1b958d335707167c5f783590ad # v0.22.2
|
||||
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
|
||||
id: sbom
|
||||
with:
|
||||
image: ${{ steps.set-target.outputs.image_name }}
|
||||
@@ -283,7 +285,7 @@ jobs:
|
||||
- name: Install Grype
|
||||
if: steps.set-target.outputs.image_name != ''
|
||||
run: |
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin v0.107.1
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin v0.110.0
|
||||
|
||||
- name: Scan for vulnerabilities
|
||||
if: steps.set-target.outputs.image_name != ''
|
||||
@@ -337,9 +339,30 @@ jobs:
|
||||
echo " Low: ${LOW_COUNT}"
|
||||
echo " Total: ${TOTAL_COUNT}"
|
||||
|
||||
- name: Security severity policy summary
|
||||
if: steps.set-target.outputs.image_name != ''
|
||||
run: |
|
||||
CRITICAL_COUNT="${{ steps.vuln-summary.outputs.critical_count }}"
|
||||
HIGH_COUNT="${{ steps.vuln-summary.outputs.high_count }}"
|
||||
MEDIUM_COUNT="${{ steps.vuln-summary.outputs.medium_count }}"
|
||||
|
||||
{
|
||||
echo "## 🔐 Supply Chain Severity Policy"
|
||||
echo ""
|
||||
echo "- Blocking: Critical, High"
|
||||
echo "- Medium: non-blocking by default (report + triage SLA)"
|
||||
echo "- Policy file: .github/security-severity-policy.yml"
|
||||
echo ""
|
||||
echo "Current scan counts: Critical=${CRITICAL_COUNT}, High=${HIGH_COUNT}, Medium=${MEDIUM_COUNT}"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
if [[ "${MEDIUM_COUNT}" -gt 0 ]]; then
|
||||
echo "::warning::${MEDIUM_COUNT} medium vulnerabilities found. Non-blocking by policy; create/maintain triage issue with SLA per .github/security-severity-policy.yml"
|
||||
fi
|
||||
|
||||
- name: Upload SARIF to GitHub Security
|
||||
if: steps.check-artifact.outputs.artifact_found == 'true'
|
||||
uses: github/codeql-action/upload-sarif@9e907b5e64f6b83e7804b09294d44122997950d6 # v4
|
||||
uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4
|
||||
continue-on-error: true
|
||||
with:
|
||||
sarif_file: grype-results.sarif
|
||||
@@ -348,7 +371,7 @@ jobs:
|
||||
- name: Upload supply chain artifacts
|
||||
if: steps.set-target.outputs.image_name != ''
|
||||
# actions/upload-artifact v4.6.0
|
||||
uses: actions/upload-artifact@47309c993abb98030a35d55ef7ff34b7fa1074b5
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: ${{ steps.pr-number.outputs.is_push == 'true' && format('supply-chain-{0}', steps.sanitize.outputs.branch) || format('supply-chain-pr-{0}', steps.pr-number.outputs.pr_number) }}
|
||||
path: |
|
||||
@@ -358,9 +381,12 @@ jobs:
|
||||
|
||||
- name: Comment on PR
|
||||
if: steps.set-target.outputs.image_name != '' && steps.pr-number.outputs.is_push != 'true' && steps.pr-number.outputs.pr_number != ''
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
PR_NUMBER="${{ steps.pr-number.outputs.pr_number }}"
|
||||
COMPONENT_COUNT="${{ steps.sbom-count.outputs.component_count }}"
|
||||
CRITICAL_COUNT="${{ steps.vuln-summary.outputs.critical_count }}"
|
||||
@@ -406,37 +432,47 @@ jobs:
|
||||
EOF
|
||||
)
|
||||
|
||||
# Find and update existing comment or create new one
|
||||
COMMENT_ID=$(gh api \
|
||||
# Fetch existing comments — skip gracefully on 403 / permission errors
|
||||
COMMENTS_JSON=""
|
||||
if ! COMMENTS_JSON=$(gh api \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
|
||||
--jq '.[] | select(.body | contains("Supply Chain Verification Results")) | .id' | head -1)
|
||||
"/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" 2>/dev/null); then
|
||||
echo "⚠️ Cannot access PR comments (likely token permissions / fork / event context). Skipping PR comment."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -n "${COMMENT_ID}" ]]; then
|
||||
COMMENT_ID=$(echo "${COMMENTS_JSON}" | jq -r '.[] | select(.body | contains("Supply Chain Verification Results")) | .id' | head -1)
|
||||
|
||||
if [[ -n "${COMMENT_ID:-}" && "${COMMENT_ID}" != "null" ]]; then
|
||||
echo "📝 Updating existing comment..."
|
||||
gh api \
|
||||
--method PATCH \
|
||||
if ! gh api --method PATCH \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" \
|
||||
-f body="${COMMENT_BODY}"
|
||||
-f body="${COMMENT_BODY}"; then
|
||||
echo "⚠️ Failed to update comment (permissions?). Skipping."
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo "📝 Creating new comment..."
|
||||
gh api \
|
||||
--method POST \
|
||||
if ! gh api --method POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
|
||||
-f body="${COMMENT_BODY}"
|
||||
-f body="${COMMENT_BODY}"; then
|
||||
echo "⚠️ Failed to create comment (permissions?). Skipping."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "✅ PR comment posted"
|
||||
|
||||
- name: Fail on critical vulnerabilities
|
||||
- name: Fail on Critical/High vulnerabilities
|
||||
if: steps.set-target.outputs.image_name != ''
|
||||
run: |
|
||||
CRITICAL_COUNT="${{ steps.vuln-summary.outputs.critical_count }}"
|
||||
HIGH_COUNT="${{ steps.vuln-summary.outputs.high_count }}"
|
||||
|
||||
if [[ "${CRITICAL_COUNT}" -gt 0 ]]; then
|
||||
echo "🚨 Found ${CRITICAL_COUNT} CRITICAL vulnerabilities!"
|
||||
@@ -444,4 +480,10 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ No critical vulnerabilities found"
|
||||
if [[ "${HIGH_COUNT}" -gt 0 ]]; then
|
||||
echo "🚨 Found ${HIGH_COUNT} HIGH vulnerabilities!"
|
||||
echo "Please review the vulnerability report and address high severity issues before merging."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ No Critical/High vulnerabilities found"
|
||||
|
||||
8
.github/workflows/supply-chain-verify.yml
vendored
8
.github/workflows/supply-chain-verify.yml
vendored
@@ -119,7 +119,7 @@ jobs:
|
||||
# Generate SBOM using official Anchore action (auto-updated by Renovate)
|
||||
- name: Generate and Verify SBOM
|
||||
if: steps.image-check.outputs.exists == 'true'
|
||||
uses: anchore/sbom-action@28d71544de8eaf1b958d335707167c5f783590ad # v0.22.2
|
||||
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0
|
||||
with:
|
||||
image: ghcr.io/${{ github.repository_owner }}/charon:${{ steps.tag.outputs.tag }}
|
||||
format: cyclonedx-json
|
||||
@@ -144,7 +144,7 @@ jobs:
|
||||
|
||||
- name: Upload SBOM Artifact
|
||||
if: steps.image-check.outputs.exists == 'true' && always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: sbom-${{ steps.tag.outputs.tag }}
|
||||
path: sbom-verify.cyclonedx.json
|
||||
@@ -233,7 +233,7 @@ jobs:
|
||||
# Scan for vulnerabilities using official Anchore action (auto-updated by Renovate)
|
||||
- name: Scan for Vulnerabilities
|
||||
if: steps.validate-sbom.outputs.valid == 'true'
|
||||
uses: anchore/scan-action@7037fa011853d5a11690026fb85feee79f4c946c # v7.3.2
|
||||
uses: anchore/scan-action@e1165082ffb1fe366ebaf02d8526e7c4989ea9d2 # v7.4.0
|
||||
id: scan
|
||||
with:
|
||||
sbom: sbom-verify.cyclonedx.json
|
||||
@@ -324,7 +324,7 @@ jobs:
|
||||
|
||||
- name: Upload Vulnerability Scan Artifact
|
||||
if: steps.validate-sbom.outputs.valid == 'true' && always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: vulnerability-scan-${{ steps.tag.outputs.tag }}
|
||||
path: |
|
||||
|
||||
2
.github/workflows/waf-integration.yml
vendored
2
.github/workflows/waf-integration.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
- name: Build Docker image (Local)
|
||||
run: |
|
||||
echo "Building image locally for integration tests..."
|
||||
docker build -t charon:local .
|
||||
docker build -t charon:local --build-arg CI="${CI:-false}" .
|
||||
echo "✅ Successfully built charon:local"
|
||||
|
||||
- name: Run WAF integration tests
|
||||
|
||||
@@ -5,9 +5,9 @@ name: Weekly Nightly to Main Promotion
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Every Monday at 10:30 UTC (5:30am EST / 6:30am EDT)
|
||||
# Every Monday at 12:00 UTC (7:00am EST / 8:00am EDT)
|
||||
# Offset from nightly sync (09:00 UTC) to avoid schedule race and allow validation completion.
|
||||
- cron: '30 10 * * 1'
|
||||
- cron: '0 12 * * 1'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
reason:
|
||||
@@ -200,8 +200,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.check-nightly-health.outputs.is_healthy == 'true'
|
||||
outputs:
|
||||
pr_number: ${{ steps.create-pr.outputs.pr_number }}
|
||||
pr_url: ${{ steps.create-pr.outputs.pr_url }}
|
||||
pr_number: ${{ steps.create-pr.outputs.pr_number || steps.existing-pr.outputs.pr_number }}
|
||||
pr_url: ${{ steps.create-pr.outputs.pr_url || steps.existing-pr.outputs.pr_url }}
|
||||
skipped: ${{ steps.check-diff.outputs.skipped }}
|
||||
|
||||
steps:
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -78,6 +78,11 @@ backend/node_modules/
|
||||
backend/package.json
|
||||
backend/package-lock.json
|
||||
|
||||
# Root-level artifact files (non-documentation)
|
||||
FIREFOX_E2E_FIXES_SUMMARY.md
|
||||
verify-security-state-for-ui-tests
|
||||
categories.txt
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Databases
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -297,6 +302,7 @@ docs/plans/current_spec_notes.md
|
||||
tests/etc/passwd
|
||||
trivy-image-report.json
|
||||
trivy-fs-report.json
|
||||
trivy-report.json
|
||||
backend/# Tools Configuration.md
|
||||
docs/plans/requirements.md
|
||||
docs/plans/design.md
|
||||
@@ -306,4 +312,5 @@ frontend/temp**
|
||||
playwright-output/**
|
||||
validation-evidence/**
|
||||
.github/agents/# Tools Configuration.md
|
||||
docs/plans/codecove_patch_report.md
|
||||
docs/reports/codecove_patch_report.md
|
||||
vuln-results.json
|
||||
|
||||
535
.grype.yaml
535
.grype.yaml
@@ -4,60 +4,517 @@
|
||||
# Documentation: https://github.com/anchore/grype#specifying-matches-to-ignore
|
||||
|
||||
ignore:
|
||||
# CVE-2026-22184: zlib Global Buffer Overflow in untgz utility
|
||||
# Severity: CRITICAL
|
||||
# Package: zlib 1.3.1-r2 (Alpine Linux base image)
|
||||
# Status: No upstream fix available as of 2026-01-16
|
||||
# GHSA-69x3-g4r3-p962 / CVE-2026-25793: Nebula ECDSA Signature Malleability
|
||||
# Severity: HIGH (CVSS 8.1)
|
||||
# Package: github.com/slackhq/nebula v1.9.7 (embedded in /usr/bin/caddy)
|
||||
# Status: Cannot upgrade — smallstep/certificates v0.30.0-rc2 still pins nebula v1.9.x
|
||||
#
|
||||
# Vulnerability Details:
|
||||
# - Global buffer overflow in TGZfname() function
|
||||
# - Unbounded strcpy() allows attacker-controlled archive names
|
||||
# - Can lead to memory corruption, DoS, potential RCE
|
||||
# - ECDSA signature malleability allows bypassing certificate blocklists
|
||||
# - Attacker can forge alternate valid P256 ECDSA signatures for revoked
|
||||
# certificates (CVSSv3: AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N)
|
||||
# - Only affects configurations using Nebula-based certificate authorities
|
||||
# (non-default and uncommon in Charon deployments)
|
||||
#
|
||||
# Root Cause (Compile-Time Dependency Lock):
|
||||
# - Caddy is built with caddy-security plugin, which transitively requires
|
||||
# github.com/smallstep/certificates. That package pins nebula v1.9.x.
|
||||
# - Checked: smallstep/certificates v0.27.5 → v0.30.0-rc2 all require nebula v1.9.4–v1.9.7.
|
||||
# The nebula v1.10 API removal breaks compilation in the
|
||||
# authority/provisioner package; xcaddy build fails with upgrade attempted.
|
||||
# - Dockerfile caddy-builder stage pins nebula@v1.9.7 (Renovate tracked) with
|
||||
# an inline comment explaining the constraint (Dockerfile line 247).
|
||||
# - Fix path: once smallstep/certificates releases a version requiring
|
||||
# nebula v1.10+, remove the pin and this suppression simultaneously.
|
||||
#
|
||||
# Risk Assessment: ACCEPTED (Low exploitability in Charon context)
|
||||
# - Charon does not use untgz utility directly
|
||||
# - No untrusted tar archive processing in application code
|
||||
# - Attack surface limited to OS-level utilities
|
||||
# - Multiple layers of containerization and isolation
|
||||
# - Charon uses standard ACME/Let's Encrypt TLS; Nebula VPN PKI is not
|
||||
# enabled by default and rarely configured in Charon deployments.
|
||||
# - Exploiting this requires a valid certificate sharing the same issuer as
|
||||
# a revoked one — an uncommon and targeted attack scenario.
|
||||
# - Container-level isolation reduces the attack surface further.
|
||||
#
|
||||
# Mitigation:
|
||||
# - Monitor Alpine Linux security feed daily for zlib patches
|
||||
# - Container runs with minimal privileges (no-new-privileges)
|
||||
# - Read-only filesystem where possible
|
||||
# - Network isolation via Docker networks
|
||||
# Mitigation (active while suppression is in effect):
|
||||
# - Monitor smallstep/certificates releases at https://github.com/smallstep/certificates/releases
|
||||
# - Weekly CI security rebuild flags any new CVEs in the full image.
|
||||
# - Renovate annotation in Dockerfile (datasource=go depName=github.com/slackhq/nebula)
|
||||
# will surface the pin for review when xcaddy build becomes compatible.
|
||||
#
|
||||
# Review:
|
||||
# - Daily checks for Alpine security updates
|
||||
# - Automatic re-scan via CI/CD on every commit
|
||||
# - Manual review scheduled for 2026-01-23 (7 days)
|
||||
# - Reviewed 2026-02-19: smallstep/certificates latest stable remains v0.27.5;
|
||||
# no release requiring nebula v1.10+ has shipped. Suppression extended 14 days.
|
||||
# - Reviewed 2026-03-13: smallstep/certificates stable still v0.27.5, extended 30 days.
|
||||
# - Next review: 2026-04-12. Remove suppression immediately once upstream fixes.
|
||||
#
|
||||
# Removal Criteria:
|
||||
# - Alpine releases zlib 1.3.1-r3 or higher with CVE fix
|
||||
# - OR upstream zlib project releases patched version
|
||||
# - Remove this suppression immediately after fix available
|
||||
# - smallstep/certificates releases a stable version requiring nebula v1.10+
|
||||
# - Update Dockerfile caddy-builder patch to use the new versions
|
||||
# - Rebuild image, run security scan, confirm suppression no longer needed
|
||||
# - Remove both this entry and the corresponding .trivyignore entry
|
||||
#
|
||||
# References:
|
||||
# - CVE: https://nvd.nist.gov/vuln/detail/CVE-2026-22184
|
||||
# - Alpine Security: https://security.alpinelinux.org/
|
||||
# - GitHub Issue: https://github.com/Wikid82/Charon/issues/TBD
|
||||
- vulnerability: CVE-2026-22184
|
||||
# - GHSA: https://github.com/advisories/GHSA-69x3-g4r3-p962
|
||||
# - CVE-2026-25793: https://nvd.nist.gov/vuln/detail/CVE-2026-25793
|
||||
# - smallstep/certificates: https://github.com/smallstep/certificates/releases
|
||||
# - Dockerfile pin: caddy-builder stage, line ~247 (go get nebula@v1.9.7)
|
||||
- vulnerability: GHSA-69x3-g4r3-p962
|
||||
package:
|
||||
name: zlib
|
||||
version: "1.3.1-r2"
|
||||
type: apk # Alpine package
|
||||
name: github.com/slackhq/nebula
|
||||
version: "v1.9.7"
|
||||
type: go-module
|
||||
reason: |
|
||||
CRITICAL buffer overflow in untgz utility. No fix available from Alpine
|
||||
as of 2026-01-16. Risk accepted: Charon does not directly use untgz or
|
||||
process untrusted tar archives. Attack surface limited to base OS utilities.
|
||||
Monitoring Alpine security feed for upstream patch.
|
||||
expiry: "2026-01-23" # Re-evaluate in 7 days
|
||||
HIGH — ECDSA signature malleability in nebula v1.9.7 embedded in /usr/bin/caddy.
|
||||
Cannot upgrade: smallstep/certificates v0.27.5 (latest stable as of 2026-03-13)
|
||||
still requires nebula v1.9.x (verified across v0.27.5–v0.30.0-rc2). Charon does
|
||||
not use Nebula VPN PKI by default. Risk accepted pending upstream smallstep fix.
|
||||
Reviewed 2026-03-13: smallstep/certificates stable still v0.27.5, extended 30 days.
|
||||
expiry: "2026-04-12" # Re-evaluated 2026-03-13: smallstep/certificates stable still v0.27.5, extended 30 days.
|
||||
|
||||
# Action items when this suppression expires:
|
||||
# 1. Check Alpine security feed: https://security.alpinelinux.org/
|
||||
# 2. Check zlib releases: https://github.com/madler/zlib/releases
|
||||
# 3. If fix available: Update Dockerfile, rebuild, remove suppression
|
||||
# 4. If no fix: Extend expiry by 7 days, document justification
|
||||
# 5. If extended 3+ times: Escalate to security team for review
|
||||
# 1. Check smallstep/certificates releases: https://github.com/smallstep/certificates/releases
|
||||
# 2. If a stable version requires nebula v1.10+:
|
||||
# a. Update Dockerfile caddy-builder: remove the `go get nebula@v1.9.7` pin
|
||||
# b. Optionally bump smallstep/certificates to the new version
|
||||
# c. Rebuild Docker image and verify no compile failures
|
||||
# d. Re-run local security-scan-docker-image and confirm clean result
|
||||
# e. Remove this suppression entry
|
||||
# 3. If no fix yet: Extend expiry by 14 days and document justification
|
||||
# 4. If extended 3+ times: Open upstream issue on smallstep/certificates
|
||||
|
||||
# CVE-2026-2673: OpenSSL TLS 1.3 server key exchange group downgrade
|
||||
# Severity: HIGH (CVSS 7.5)
|
||||
# Packages: libcrypto3 3.5.5-r0 and libssl3 3.5.5-r0 (Alpine apk)
|
||||
# Status: No upstream fix available — Alpine 3.23 still ships libcrypto3/libssl3 3.5.5-r0 as of 2026-03-18
|
||||
#
|
||||
# Vulnerability Details:
|
||||
# - When DEFAULT is in the TLS 1.3 group configuration, the OpenSSL server may select
|
||||
# a weaker key exchange group than preferred, enabling a limited key exchange downgrade.
|
||||
# - Only affects systems acting as a raw TLS 1.3 server using OpenSSL's server-side group negotiation.
|
||||
#
|
||||
# Root Cause (No Fix Available):
|
||||
# - Alpine upstream has not published a patched libcrypto3/libssl3 for Alpine 3.23.
|
||||
# - Checked: Alpine 3.23 still ships libcrypto3/libssl3 3.5.5-r0 as of 2026-03-18.
|
||||
# - Fix path: once Alpine publishes a patched libcrypto3/libssl3, rebuild the Docker image
|
||||
# and remove this suppression.
|
||||
#
|
||||
# Risk Assessment: ACCEPTED (No upstream fix; limited exposure in Charon context)
|
||||
# - Charon terminates TLS at the Caddy layer — the Go backend does not act as a raw TLS 1.3 server.
|
||||
# - The vulnerability requires the affected application to directly configure TLS 1.3 server
|
||||
# group negotiation via OpenSSL, which Charon does not do.
|
||||
# - Container-level isolation reduces the attack surface further.
|
||||
#
|
||||
# Mitigation (active while suppression is in effect):
|
||||
# - Monitor Alpine security advisories: https://security.alpinelinux.org/vuln/CVE-2026-2673
|
||||
# - Weekly CI security rebuild (security-weekly-rebuild.yml) flags any new CVEs in the full image.
|
||||
#
|
||||
# Review:
|
||||
# - Reviewed 2026-03-18 (initial suppression): no upstream fix available. Set 30-day review.
|
||||
# - Next review: 2026-04-18. Remove suppression immediately once upstream fixes.
|
||||
#
|
||||
# Removal Criteria:
|
||||
# - Alpine publishes a patched version of libcrypto3 and libssl3
|
||||
# - Rebuild Docker image and verify CVE-2026-2673 no longer appears in grype-results.json
|
||||
# - Remove both these entries and the corresponding .trivyignore entry simultaneously
|
||||
#
|
||||
# References:
|
||||
# - CVE-2026-2673: https://nvd.nist.gov/vuln/detail/CVE-2026-2673
|
||||
# - Alpine security tracker: https://security.alpinelinux.org/vuln/CVE-2026-2673
|
||||
- vulnerability: CVE-2026-2673
|
||||
package:
|
||||
name: libcrypto3
|
||||
version: "3.5.5-r0"
|
||||
type: apk
|
||||
reason: |
|
||||
HIGH — OpenSSL TLS 1.3 server key exchange group downgrade in libcrypto3 3.5.5-r0 (Alpine base image).
|
||||
No upstream fix: Alpine 3.23 still ships libcrypto3 3.5.5-r0 as of 2026-03-18. Charon
|
||||
terminates TLS at the Caddy layer; the Go backend does not act as a raw TLS 1.3 server.
|
||||
Risk accepted pending Alpine upstream patch.
|
||||
expiry: "2026-04-18" # Initial 30-day review period. Extend in 14–30 day increments with documented justification.
|
||||
|
||||
# Action items when this suppression expires:
|
||||
# 1. Check Alpine security tracker: https://security.alpinelinux.org/vuln/CVE-2026-2673
|
||||
# 2. If a patched Alpine package is now available:
|
||||
# a. Rebuild Docker image without suppression
|
||||
# b. Run local security-scan-docker-image and confirm CVE is resolved
|
||||
# c. Remove this suppression entry, the libssl3 entry below, and the .trivyignore entry
|
||||
# 3. If no fix yet: Extend expiry by 14–30 days and update the review comment above
|
||||
# 4. If extended 3+ times: Open an issue to track the upstream status formally
|
||||
|
||||
# CVE-2026-2673 (libssl3) — see full justification in the libcrypto3 entry above
|
||||
- vulnerability: CVE-2026-2673
|
||||
package:
|
||||
name: libssl3
|
||||
version: "3.5.5-r0"
|
||||
type: apk
|
||||
reason: |
|
||||
HIGH — OpenSSL TLS 1.3 server key exchange group downgrade in libssl3 3.5.5-r0 (Alpine base image).
|
||||
No upstream fix: Alpine 3.23 still ships libssl3 3.5.5-r0 as of 2026-03-18. Charon
|
||||
terminates TLS at the Caddy layer; the Go backend does not act as a raw TLS 1.3 server.
|
||||
Risk accepted pending Alpine upstream patch.
|
||||
expiry: "2026-04-18" # Initial 30-day review period. See libcrypto3 entry above for action items.
|
||||
|
||||
# CVE-2026-33186 / GHSA-p77j-4mvh-x3m3: gRPC-Go authorization bypass via missing leading slash
|
||||
# Severity: CRITICAL (CVSS 9.1)
|
||||
# Package: google.golang.org/grpc v1.74.2 (embedded in /usr/local/bin/crowdsec and /usr/local/bin/cscli)
|
||||
# Status: Fix available at v1.79.3 — waiting on CrowdSec upstream to release with patched grpc
|
||||
#
|
||||
# Vulnerability Details:
|
||||
# - gRPC-Go server path-based authorization (grpc/authz) fails to match deny rules when
|
||||
# the HTTP/2 :path pseudo-header is missing its leading slash (e.g., "Service/Method"
|
||||
# instead of "/Service/Method"), allowing a fallback allow-rule to grant access instead.
|
||||
# - CVSSv3: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
|
||||
#
|
||||
# Root Cause (Third-Party Binary):
|
||||
# - Charon's own grpc dependency is patched to v1.79.3 (updated 2026-03-19).
|
||||
# - CrowdSec ships grpc v1.74.2 compiled into its binary; Charon has no control over this.
|
||||
# - This is a server-side vulnerability. CrowdSec uses grpc as a server; Charon uses it
|
||||
# only as a client (via the Docker SDK). CrowdSec's internal grpc server is not exposed
|
||||
# to external traffic in a standard Charon deployment.
|
||||
# - Fix path: once CrowdSec releases a version built with grpc >= v1.79.3, rebuild the
|
||||
# Docker image (Renovate tracks the CrowdSec version) and remove this suppression.
|
||||
#
|
||||
# Risk Assessment: ACCEPTED (Constrained exploitability in Charon context)
|
||||
# - The vulnerable code path requires an attacker to reach CrowdSec's internal grpc server,
|
||||
# which is bound to localhost/internal interfaces in the Charon container network.
|
||||
# - Container-level isolation (no exposed grpc port) significantly limits exposure.
|
||||
# - Charon does not configure grpc/authz deny rules on CrowdSec's server.
|
||||
#
|
||||
# Mitigation (active while suppression is in effect):
|
||||
# - Monitor CrowdSec releases: https://github.com/crowdsecurity/crowdsec/releases
|
||||
# - Weekly CI security rebuild flags the moment a fixed CrowdSec image ships.
|
||||
#
|
||||
# Review:
|
||||
# - Reviewed 2026-03-19 (initial suppression): grpc v1.79.3 fix exists; CrowdSec has not
|
||||
# yet shipped an updated release. Suppression set for 14-day review given fix availability.
|
||||
# - Next review: 2026-04-02. Remove suppression once CrowdSec ships with grpc >= v1.79.3.
|
||||
#
|
||||
# Removal Criteria:
|
||||
# - CrowdSec releases a version built with google.golang.org/grpc >= v1.79.3
|
||||
# - Rebuild Docker image, run security-scan-docker-image, confirm finding is resolved
|
||||
# - Remove this entry and the corresponding .trivyignore entry simultaneously
|
||||
#
|
||||
# References:
|
||||
# - GHSA-p77j-4mvh-x3m3: https://github.com/advisories/GHSA-p77j-4mvh-x3m3
|
||||
# - CVE-2026-33186: https://nvd.nist.gov/vuln/detail/CVE-2026-33186
|
||||
# - grpc fix (v1.79.3): https://github.com/grpc/grpc-go/releases/tag/v1.79.3
|
||||
# - CrowdSec releases: https://github.com/crowdsecurity/crowdsec/releases
|
||||
- vulnerability: CVE-2026-33186
|
||||
package:
|
||||
name: google.golang.org/grpc
|
||||
version: "v1.74.2"
|
||||
type: go-module
|
||||
reason: |
|
||||
CRITICAL — gRPC-Go authorization bypass in grpc v1.74.2 embedded in /usr/local/bin/crowdsec
|
||||
and /usr/local/bin/cscli. Fix available at v1.79.3 (Charon's own dep is patched); waiting
|
||||
on CrowdSec upstream to release with patched grpc. CrowdSec's grpc server is not exposed
|
||||
externally in a standard Charon deployment. Risk accepted pending CrowdSec upstream fix.
|
||||
Reviewed 2026-03-19: CrowdSec has not yet released with grpc >= v1.79.3.
|
||||
expiry: "2026-04-02" # 14-day review: fix exists at v1.79.3; check CrowdSec releases.
|
||||
|
||||
# Action items when this suppression expires:
|
||||
# 1. Check CrowdSec releases: https://github.com/crowdsecurity/crowdsec/releases
|
||||
# 2. If CrowdSec ships with grpc >= v1.79.3:
|
||||
# a. Renovate should auto-PR the new CrowdSec version in the Dockerfile
|
||||
# b. Merge the Renovate PR, rebuild Docker image
|
||||
# c. Run local security-scan-docker-image and confirm grpc v1.74.2 is gone
|
||||
# d. Remove this suppression entry and the corresponding .trivyignore entry
|
||||
# 3. If no fix yet: Extend expiry by 14 days and document justification
|
||||
# 4. If extended 3+ times: Open an upstream issue on crowdsecurity/crowdsec
|
||||
|
||||
# CVE-2026-33186 (Caddy) — see full justification in the CrowdSec entry above
|
||||
# Package: google.golang.org/grpc v1.79.1 (embedded in /usr/bin/caddy)
|
||||
# Status: Fix available at v1.79.3 — waiting on a new Caddy release built with patched grpc
|
||||
- vulnerability: CVE-2026-33186
|
||||
package:
|
||||
name: google.golang.org/grpc
|
||||
version: "v1.79.1"
|
||||
type: go-module
|
||||
reason: |
|
||||
CRITICAL — gRPC-Go authorization bypass in grpc v1.79.1 embedded in /usr/bin/caddy.
|
||||
Fix available at v1.79.3; waiting on Caddy upstream to release a build with patched grpc.
|
||||
Caddy's grpc server is not exposed externally in a standard Charon deployment.
|
||||
Risk accepted pending Caddy upstream fix. Reviewed 2026-03-19: no Caddy release with grpc >= v1.79.3 yet.
|
||||
expiry: "2026-04-02" # 14-day review: fix exists at v1.79.3; check Caddy releases.
|
||||
|
||||
# Action items when this suppression expires:
|
||||
# 1. Check Caddy releases: https://github.com/caddyserver/caddy/releases
|
||||
# (or the custom caddy-builder in the Dockerfile for caddy-security plugin)
|
||||
# 2. If a new Caddy build ships with grpc >= v1.79.3:
|
||||
# a. Update the Caddy version pin in the Dockerfile caddy-builder stage
|
||||
# b. Rebuild Docker image and run local security-scan-docker-image
|
||||
# c. Remove this suppression entry and the corresponding .trivyignore entry
|
||||
# 3. If no fix yet: Extend expiry by 14 days and document justification
|
||||
# 4. If extended 3+ times: Open an issue on caddyserver/caddy
|
||||
|
||||
# GHSA-479m-364c-43vc: goxmldsig XML signature validation bypass (loop variable capture)
|
||||
# Severity: HIGH (CVSS 7.5)
|
||||
# Package: github.com/russellhaering/goxmldsig v1.5.0 (embedded in /usr/bin/caddy)
|
||||
# Status: Fix available at v1.6.0 — waiting on a new Caddy release built with patched goxmldsig
|
||||
#
|
||||
# Vulnerability Details:
|
||||
# - Loop variable capture in validateSignature causes the signature reference to always
|
||||
# point to the last element in SignedInfo.References; an attacker can substitute signed
|
||||
# element content and bypass XML signature integrity validation (CWE-347, CWE-682).
|
||||
# - CVSSv3: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
|
||||
#
|
||||
# Root Cause (Third-Party Binary):
|
||||
# - Charon does not use goxmldsig directly. The package is compiled into /usr/bin/caddy
|
||||
# via the caddy-security plugin's SAML/SSO support.
|
||||
# - Fix path: once Caddy (or the caddy-security plugin) releases a build with
|
||||
# goxmldsig >= v1.6.0, rebuild the Docker image and remove this suppression.
|
||||
#
|
||||
# Risk Assessment: ACCEPTED (Low exploitability in default Charon context)
|
||||
# - The vulnerability only affects SAML/XML signature validation workflows.
|
||||
# - Charon does not enable or configure SAML-based SSO in its default setup.
|
||||
# - Exploiting this requires an active SAML integration, which is non-default.
|
||||
#
|
||||
# Mitigation (active while suppression is in effect):
|
||||
# - Monitor caddy-security plugin releases: https://github.com/greenpau/caddy-security/releases
|
||||
# - Monitor Caddy releases: https://github.com/caddyserver/caddy/releases
|
||||
# - Weekly CI security rebuild flags the moment a fixed image ships.
|
||||
#
|
||||
# Review:
|
||||
# - Reviewed 2026-03-19 (initial suppression): goxmldsig v1.6.0 fix exists; Caddy has not
|
||||
# yet shipped with the updated dep. Set 14-day review given fix availability.
|
||||
# - Next review: 2026-04-02. Remove suppression once Caddy ships with goxmldsig >= v1.6.0.
|
||||
#
|
||||
# Removal Criteria:
|
||||
# - Caddy (or caddy-security plugin) releases a build with goxmldsig >= v1.6.0
|
||||
# - Rebuild Docker image, run security-scan-docker-image, confirm finding is resolved
|
||||
# - Remove this entry and the corresponding .trivyignore entry simultaneously
|
||||
#
|
||||
# References:
|
||||
# - GHSA-479m-364c-43vc: https://github.com/advisories/GHSA-479m-364c-43vc
|
||||
# - goxmldsig v1.6.0 fix: https://github.com/russellhaering/goxmldsig/releases/tag/v1.6.0
|
||||
# - caddy-security plugin: https://github.com/greenpau/caddy-security/releases
|
||||
- vulnerability: GHSA-479m-364c-43vc
|
||||
package:
|
||||
name: github.com/russellhaering/goxmldsig
|
||||
version: "v1.5.0"
|
||||
type: go-module
|
||||
reason: |
|
||||
HIGH — XML signature validation bypass in goxmldsig v1.5.0 embedded in /usr/bin/caddy.
|
||||
Fix available at v1.6.0; waiting on Caddy upstream to release a build with patched goxmldsig.
|
||||
Charon does not configure SAML-based SSO by default; the vulnerable XML signature path
|
||||
is not reachable in a standard deployment. Risk accepted pending Caddy upstream fix.
|
||||
Reviewed 2026-03-19: no Caddy release with goxmldsig >= v1.6.0 yet.
|
||||
expiry: "2026-04-02" # 14-day review: fix exists at v1.6.0; check Caddy/caddy-security releases.
|
||||
|
||||
# Action items when this suppression expires:
|
||||
# 1. Check caddy-security releases: https://github.com/greenpau/caddy-security/releases
|
||||
# 2. If a new build ships with goxmldsig >= v1.6.0:
|
||||
# a. Update the Caddy version pin in the Dockerfile caddy-builder stage if needed
|
||||
# b. Rebuild Docker image and run local security-scan-docker-image
|
||||
# c. Remove this suppression entry and the corresponding .trivyignore entry
|
||||
# 3. If no fix yet: Extend expiry by 14 days and document justification
|
||||
|
||||
# GHSA-6g7g-w4f8-9c9x: buger/jsonparser Delete panic on malformed JSON (DoS)
|
||||
# Severity: HIGH (CVSS 7.5)
|
||||
# Package: github.com/buger/jsonparser v1.1.1 (embedded in /usr/local/bin/crowdsec and /usr/local/bin/cscli)
|
||||
# Status: NO upstream fix available — OSV marks "Last affected: v1.1.1" with no Fixed event
|
||||
#
|
||||
# Vulnerability Details:
|
||||
# - The Delete function fails to validate offsets on malformed JSON input, producing a
|
||||
# negative slice index and a runtime panic — denial of service (CWE-125).
|
||||
# - CVSSv3: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
|
||||
#
|
||||
# Root Cause (Third-Party Binary + No Upstream Fix):
|
||||
# - Charon does not use buger/jsonparser directly. It is compiled into CrowdSec binaries.
|
||||
# - The buger/jsonparser repository has no released fix as of 2026-03-19 (GitHub issue #275
|
||||
# and golang/vulndb #4514 are both open).
|
||||
# - Fix path: once buger/jsonparser releases a patched version and CrowdSec updates their
|
||||
# dependency, rebuild the Docker image and remove this suppression.
|
||||
#
|
||||
# Risk Assessment: ACCEPTED (Limited exploitability + no upstream fix)
|
||||
# - The DoS vector requires passing malformed JSON to the vulnerable Delete function within
|
||||
# CrowdSec's internal processing pipeline; this is not a direct attack surface in Charon.
|
||||
# - CrowdSec's exposed surface is its HTTP API (not raw JSON stream parsing via this path).
|
||||
#
|
||||
# Mitigation (active while suppression is in effect):
|
||||
# - Monitor buger/jsonparser: https://github.com/buger/jsonparser/issues/275
|
||||
# - Monitor CrowdSec releases: https://github.com/crowdsecurity/crowdsec/releases
|
||||
# - Weekly CI security rebuild flags the moment a fixed image ships.
|
||||
#
|
||||
# Review:
|
||||
# - Reviewed 2026-03-19 (initial suppression): no upstream fix exists. Set 30-day review.
|
||||
# - Next review: 2026-04-19. Remove suppression once buger/jsonparser ships a fix and
|
||||
# CrowdSec updates their dependency.
|
||||
#
|
||||
# Removal Criteria:
|
||||
# - buger/jsonparser releases a patched version (v1.1.2 or higher)
|
||||
# - CrowdSec releases a version built with the patched jsonparser
|
||||
# - Rebuild Docker image, run security-scan-docker-image, confirm finding is resolved
|
||||
# - Remove this entry and the corresponding .trivyignore entry simultaneously
|
||||
#
|
||||
# References:
|
||||
# - GHSA-6g7g-w4f8-9c9x: https://github.com/advisories/GHSA-6g7g-w4f8-9c9x
|
||||
# - Upstream issue: https://github.com/buger/jsonparser/issues/275
|
||||
# - golang/vulndb: https://github.com/golang/vulndb/issues/4514
|
||||
# - CrowdSec releases: https://github.com/crowdsecurity/crowdsec/releases
|
||||
- vulnerability: GHSA-6g7g-w4f8-9c9x
|
||||
package:
|
||||
name: github.com/buger/jsonparser
|
||||
version: "v1.1.1"
|
||||
type: go-module
|
||||
reason: |
|
||||
HIGH — DoS panic via malformed JSON in buger/jsonparser v1.1.1 embedded in CrowdSec binaries.
|
||||
No upstream fix: buger/jsonparser has no released patch as of 2026-03-19 (issue #275 open).
|
||||
Charon does not use this package directly; the vector requires reaching CrowdSec's internal
|
||||
JSON processing pipeline. Risk accepted; no remediation path until upstream ships a fix.
|
||||
Reviewed 2026-03-19: no patched release available.
|
||||
expiry: "2026-04-19" # 30-day review: no fix exists. Extend in 30-day increments with documented justification.
|
||||
|
||||
# Action items when this suppression expires:
|
||||
# 1. Check buger/jsonparser releases: https://github.com/buger/jsonparser/releases
|
||||
# and issue #275: https://github.com/buger/jsonparser/issues/275
|
||||
# 2. If a fix has shipped AND CrowdSec has updated their dependency:
|
||||
# a. Rebuild Docker image and run local security-scan-docker-image
|
||||
# b. Remove this suppression entry and the corresponding .trivyignore entry
|
||||
# 3. If no fix yet: Extend expiry by 30 days and update the review comment above
|
||||
# 4. If extended 3+ times with no progress: Consider opening an issue upstream or
|
||||
# evaluating whether CrowdSec can replace buger/jsonparser with a safe alternative
|
||||
|
||||
# GHSA-jqcq-xjh3-6g23: pgproto3/v2 DataRow.Decode panic on negative field length (DoS)
|
||||
# Severity: HIGH (CVSS 7.5)
|
||||
# Package: github.com/jackc/pgproto3/v2 v2.3.3 (embedded in /usr/local/bin/crowdsec and /usr/local/bin/cscli)
|
||||
# Status: NO fix in pgproto3/v2 (archived/EOL) — fix path requires CrowdSec to migrate to pgx/v5
|
||||
#
|
||||
# Vulnerability Details:
|
||||
# - DataRow.Decode does not validate field lengths; a malicious or compromised PostgreSQL server
|
||||
# can send a negative field length causing a slice-bounds panic — denial of service (CWE-129).
|
||||
# - CVSSv3: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
|
||||
#
|
||||
# Root Cause (EOL Module + Third-Party Binary):
|
||||
# - Charon does not use pgproto3/v2 directly nor communicate with PostgreSQL. The package
|
||||
# is compiled into CrowdSec binaries for their internal database communication.
|
||||
# - The pgproto3/v2 module is archived and EOL; no fix will be released. The fix path
|
||||
# is migration to pgx/v5, which embeds an updated pgproto3/v3.
|
||||
# - Fix path: once CrowdSec migrates to pgx/v5 and releases an updated binary, rebuild
|
||||
# the Docker image and remove this suppression.
|
||||
#
|
||||
# Risk Assessment: ACCEPTED (Non-exploitable in Charon context + no upstream fix path)
|
||||
# - The vulnerability requires a malicious PostgreSQL server response. Charon uses SQLite
|
||||
# internally and does not run PostgreSQL. CrowdSec's database path is not exposed to
|
||||
# external traffic in a standard Charon deployment.
|
||||
# - The attack requires a compromised database server, which would imply full host compromise.
|
||||
#
|
||||
# Mitigation (active while suppression is in effect):
|
||||
# - Monitor CrowdSec releases for pgx/v5 migration:
|
||||
# https://github.com/crowdsecurity/crowdsec/releases
|
||||
# - Weekly CI security rebuild flags the moment a fixed image ships.
|
||||
#
|
||||
# Review:
|
||||
# - Reviewed 2026-03-19 (initial suppression): pgproto3/v2 is EOL; no fix exists or will exist.
|
||||
# Waiting on CrowdSec to migrate to pgx/v5. Set 30-day review.
|
||||
# - Next review: 2026-04-19. Remove suppression once CrowdSec ships with pgx/v5.
|
||||
#
|
||||
# Removal Criteria:
|
||||
# - CrowdSec releases a version with pgx/v5 (pgproto3/v3) replacing pgproto3/v2
|
||||
# - Rebuild Docker image, run security-scan-docker-image, confirm finding is resolved
|
||||
# - Remove this entry and the corresponding .trivyignore entry simultaneously
|
||||
#
|
||||
# References:
|
||||
# - GHSA-jqcq-xjh3-6g23: https://github.com/advisories/GHSA-jqcq-xjh3-6g23
|
||||
# - pgproto3/v2 archive notice: https://github.com/jackc/pgproto3
|
||||
# - pgx/v5 (replacement): https://github.com/jackc/pgx
|
||||
# - CrowdSec releases: https://github.com/crowdsecurity/crowdsec/releases
|
||||
- vulnerability: GHSA-jqcq-xjh3-6g23
|
||||
package:
|
||||
name: github.com/jackc/pgproto3/v2
|
||||
version: "v2.3.3"
|
||||
type: go-module
|
||||
reason: |
|
||||
HIGH — DoS panic via negative field length in pgproto3/v2 v2.3.3 embedded in CrowdSec binaries.
|
||||
pgproto3/v2 is archived/EOL with no fix planned; fix path requires CrowdSec to migrate to pgx/v5.
|
||||
Charon uses SQLite, not PostgreSQL; this code path is not reachable in a standard deployment.
|
||||
Risk accepted; no remediation until CrowdSec ships with pgx/v5.
|
||||
Reviewed 2026-03-19: pgproto3/v2 EOL confirmed; CrowdSec has not migrated to pgx/v5 yet.
|
||||
expiry: "2026-04-19" # 30-day review: no fix path until CrowdSec migrates to pgx/v5.
|
||||
|
||||
# Action items when this suppression expires:
|
||||
# 1. Check CrowdSec releases for pgx/v5 migration:
|
||||
# https://github.com/crowdsecurity/crowdsec/releases
|
||||
# 2. Verify with: `go version -m /path/to/crowdsec | grep pgproto3`
|
||||
# Expected: pgproto3/v3 (or no pgproto3 reference if fully replaced)
|
||||
# 3. If CrowdSec has migrated:
|
||||
# a. Rebuild Docker image and run local security-scan-docker-image
|
||||
# b. Remove this suppression entry and the corresponding .trivyignore entry
|
||||
# 4. If not yet migrated: Extend expiry by 30 days and update the review comment above
|
||||
# 5. If extended 3+ times: Open an upstream issue on crowdsecurity/crowdsec requesting pgx/v5 migration
|
||||
|
||||
# GHSA-x6gf-mpr2-68h6 / CVE-2026-4427: pgproto3/v2 DataRow.Decode panic on negative field length (DoS)
|
||||
# Severity: HIGH (CVSS 7.5)
|
||||
# Package: github.com/jackc/pgproto3/v2 v2.3.3 (embedded in /usr/local/bin/crowdsec and /usr/local/bin/cscli)
|
||||
# Status: NO fix in pgproto3/v2 (archived/EOL) — fix path requires CrowdSec to migrate to pgx/v5
|
||||
# Note: This is the NVD/Red Hat advisory alias for the same underlying vulnerability as GHSA-jqcq-xjh3-6g23
|
||||
#
|
||||
# Vulnerability Details:
|
||||
# - DataRow.Decode does not validate field lengths; a malicious or compromised PostgreSQL server
|
||||
# can send a negative field length causing a slice-bounds panic — denial of service (CWE-129).
|
||||
# - CVSSv3: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (CVSS 7.5)
|
||||
#
|
||||
# Root Cause (EOL Module + Third-Party Binary):
|
||||
# - Same underlying vulnerability as GHSA-jqcq-xjh3-6g23; tracked separately by NVD/Red Hat as CVE-2026-4427.
|
||||
# - Charon does not use pgproto3/v2 directly nor communicate with PostgreSQL. The package
|
||||
# is compiled into CrowdSec binaries for their internal database communication.
|
||||
# - The pgproto3/v2 module is archived and EOL; no fix will be released. The fix path
|
||||
# is migration to pgx/v5, which embeds an updated pgproto3/v3.
|
||||
# - Fix path: once CrowdSec migrates to pgx/v5 and releases an updated binary, rebuild
|
||||
# the Docker image and remove this suppression.
|
||||
#
|
||||
# Risk Assessment: ACCEPTED (Non-exploitable in Charon context + no upstream fix path)
|
||||
# - The vulnerability requires a malicious PostgreSQL server response. Charon uses SQLite
|
||||
# internally and does not run PostgreSQL. CrowdSec's database path is not exposed to
|
||||
# external traffic in a standard Charon deployment.
|
||||
# - The attack requires a compromised database server, which would imply full host compromise.
|
||||
#
|
||||
# Mitigation (active while suppression is in effect):
|
||||
# - Monitor CrowdSec releases for pgx/v5 migration:
|
||||
# https://github.com/crowdsecurity/crowdsec/releases
|
||||
# - Weekly CI security rebuild flags the moment a fixed image ships.
|
||||
#
|
||||
# Review:
|
||||
# - Reviewed 2026-03-21 (initial suppression): pgproto3/v2 is EOL; no fix exists or will exist.
|
||||
# Waiting on CrowdSec to migrate to pgx/v5. Set 30-day review. Sibling GHSA-jqcq-xjh3-6g23
|
||||
# was already suppressed; this alias surfaced as a separate Grype match via NVD/Red Hat tracking.
|
||||
# - Next review: 2026-04-21. Remove suppression once CrowdSec ships with pgx/v5.
|
||||
#
|
||||
# Removal Criteria:
|
||||
# - Same as GHSA-jqcq-xjh3-6g23: CrowdSec releases a version with pgx/v5 replacing pgproto3/v2
|
||||
# - Rebuild Docker image, run security-scan-docker-image, confirm both advisories are resolved
|
||||
# - Remove this entry, GHSA-jqcq-xjh3-6g23 entry, and both .trivyignore entries simultaneously
|
||||
#
|
||||
# References:
|
||||
# - GHSA-x6gf-mpr2-68h6: https://github.com/advisories/GHSA-x6gf-mpr2-68h6
|
||||
# - CVE-2026-4427: https://nvd.nist.gov/vuln/detail/CVE-2026-4427
|
||||
# - Red Hat: https://access.redhat.com/security/cve/CVE-2026-4427
|
||||
# - pgproto3/v2 archive notice: https://github.com/jackc/pgproto3
|
||||
# - pgx/v5 (replacement): https://github.com/jackc/pgx
|
||||
# - CrowdSec releases: https://github.com/crowdsecurity/crowdsec/releases
|
||||
- vulnerability: GHSA-x6gf-mpr2-68h6
|
||||
package:
|
||||
name: github.com/jackc/pgproto3/v2
|
||||
version: "v2.3.3"
|
||||
type: go-module
|
||||
reason: |
|
||||
HIGH — DoS panic via negative field length in pgproto3/v2 v2.3.3 embedded in CrowdSec binaries.
|
||||
NVD/Red Hat alias (CVE-2026-4427) for the same underlying bug as GHSA-jqcq-xjh3-6g23.
|
||||
pgproto3/v2 is archived/EOL with no fix planned; fix path requires CrowdSec to migrate to pgx/v5.
|
||||
Charon uses SQLite, not PostgreSQL; this code path is not reachable in a standard deployment.
|
||||
Risk accepted; no remediation until CrowdSec ships with pgx/v5.
|
||||
Reviewed 2026-03-21: pgproto3/v2 EOL confirmed; CrowdSec has not migrated to pgx/v5 yet.
|
||||
expiry: "2026-04-21" # 30-day review: no fix path until CrowdSec migrates to pgx/v5.
|
||||
|
||||
# Action items when this suppression expires:
|
||||
# 1. Check CrowdSec releases for pgx/v5 migration:
|
||||
# https://github.com/crowdsecurity/crowdsec/releases
|
||||
# 2. Verify with: `go version -m /path/to/crowdsec | grep pgproto3`
|
||||
# Expected: pgproto3/v3 (or no pgproto3 reference if fully replaced)
|
||||
# 3. If CrowdSec has migrated:
|
||||
# a. Rebuild Docker image and run local security-scan-docker-image
|
||||
# b. Remove this entry, GHSA-jqcq-xjh3-6g23 entry, and both .trivyignore entries
|
||||
# 4. If not yet migrated: Extend expiry by 30 days and update the review comment above
|
||||
# 5. If extended 3+ times: Open an upstream issue on crowdsecurity/crowdsec requesting pgx/v5 migration
|
||||
|
||||
# Match exclusions (patterns to ignore during scanning)
|
||||
# Use sparingly - prefer specific CVE suppressions above
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
# NOTE: golangci-lint-fast now includes test files (_test.go) to catch security
|
||||
# issues earlier. The fast config uses gosec with critical-only checks (G101,
|
||||
# G110, G305, G401, G501, G502, G503) for acceptable performance.
|
||||
# Last updated: 2026-02-02
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
exclude: '^(frontend/(coverage|dist|node_modules|\.vite)/|.*\.tsbuildinfo$)'
|
||||
- id: trailing-whitespace
|
||||
exclude: '^(frontend/(coverage|dist|node_modules|\.vite)/|.*\.tsbuildinfo$)'
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
args: ['--maxkb=2500']
|
||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||
rev: v0.10.0.1
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
name: shellcheck
|
||||
exclude: '^(frontend/(coverage|dist|node_modules|\.vite)/|test-results|codeql-agent-results)/'
|
||||
args: ['--severity=error']
|
||||
- repo: https://github.com/rhysd/actionlint
|
||||
rev: v1.7.10
|
||||
hooks:
|
||||
- id: actionlint
|
||||
name: actionlint (GitHub Actions)
|
||||
files: '^\.github/workflows/.*\.ya?ml$'
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: dockerfile-check
|
||||
name: dockerfile validation
|
||||
entry: tools/dockerfile_check.sh
|
||||
language: script
|
||||
files: "Dockerfile.*"
|
||||
pass_filenames: true
|
||||
- id: go-test-coverage
|
||||
name: Go Test Coverage (Manual)
|
||||
entry: scripts/go-test-coverage.sh
|
||||
language: script
|
||||
files: '\.go$'
|
||||
pass_filenames: false
|
||||
verbose: true
|
||||
stages: [manual] # Only runs when explicitly called
|
||||
- id: go-vet
|
||||
name: Go Vet
|
||||
entry: bash -c 'cd backend && go vet ./...'
|
||||
language: system
|
||||
files: '\.go$'
|
||||
pass_filenames: false
|
||||
- id: golangci-lint-fast
|
||||
name: golangci-lint (Fast Linters - BLOCKING)
|
||||
entry: scripts/pre-commit-hooks/golangci-lint-fast.sh
|
||||
language: script
|
||||
files: '\.go$'
|
||||
# Test files are now included to catch security issues (gosec critical checks)
|
||||
pass_filenames: false
|
||||
description: "Runs fast, essential linters (staticcheck, govet, errcheck, ineffassign, unused, gosec critical) - BLOCKS commits on failure"
|
||||
- id: check-version-match
|
||||
name: Check .version matches latest Git tag
|
||||
entry: bash -c 'scripts/check-version-match-tag.sh'
|
||||
language: system
|
||||
files: '\.version$'
|
||||
pass_filenames: false
|
||||
- id: check-lfs-large-files
|
||||
name: Prevent large files that are not tracked by LFS
|
||||
entry: bash scripts/pre-commit-hooks/check-lfs-for-large-files.sh
|
||||
language: system
|
||||
pass_filenames: false
|
||||
verbose: true
|
||||
always_run: true
|
||||
- id: block-codeql-db-commits
|
||||
name: Prevent committing CodeQL DB artifacts
|
||||
entry: bash scripts/pre-commit-hooks/block-codeql-db-commits.sh
|
||||
language: system
|
||||
pass_filenames: false
|
||||
verbose: true
|
||||
always_run: true
|
||||
- id: block-data-backups-commit
|
||||
name: Prevent committing data/backups files
|
||||
entry: bash scripts/pre-commit-hooks/block-data-backups-commit.sh
|
||||
language: system
|
||||
pass_filenames: false
|
||||
verbose: true
|
||||
always_run: true
|
||||
|
||||
# === MANUAL/CI-ONLY HOOKS ===
|
||||
# These are slow and should only run on-demand or in CI
|
||||
# Run manually with: pre-commit run golangci-lint-full --all-files
|
||||
- id: go-test-race
|
||||
name: Go Test Race (Manual)
|
||||
entry: bash -c 'cd backend && go test -race ./...'
|
||||
language: system
|
||||
files: '\.go$'
|
||||
pass_filenames: false
|
||||
stages: [manual] # Only runs when explicitly called
|
||||
|
||||
- id: golangci-lint-full
|
||||
name: golangci-lint (Full - Manual)
|
||||
entry: scripts/pre-commit-hooks/golangci-lint-full.sh
|
||||
language: script
|
||||
files: '\.go$'
|
||||
pass_filenames: false
|
||||
stages: [manual] # Only runs when explicitly called
|
||||
|
||||
- id: hadolint
|
||||
name: Hadolint Dockerfile Check (Manual)
|
||||
entry: bash -c 'docker run --rm -i hadolint/hadolint < Dockerfile'
|
||||
language: system
|
||||
files: 'Dockerfile'
|
||||
pass_filenames: false
|
||||
stages: [manual] # Only runs when explicitly called
|
||||
- id: frontend-type-check
|
||||
name: Frontend TypeScript Check
|
||||
entry: bash -c 'cd frontend && npm run type-check'
|
||||
language: system
|
||||
files: '^frontend/.*\.(ts|tsx)$'
|
||||
pass_filenames: false
|
||||
- id: frontend-lint
|
||||
name: Frontend Lint (Fix)
|
||||
entry: bash -c 'cd frontend && npm run lint -- --fix'
|
||||
language: system
|
||||
files: '^frontend/.*\.(ts|tsx|js|jsx)$'
|
||||
pass_filenames: false
|
||||
|
||||
- id: frontend-test-coverage
|
||||
name: Frontend Test Coverage (Manual)
|
||||
entry: scripts/frontend-test-coverage.sh
|
||||
language: script
|
||||
files: '^frontend/.*\\.(ts|tsx|js|jsx)$'
|
||||
pass_filenames: false
|
||||
verbose: true
|
||||
stages: [manual]
|
||||
|
||||
- id: security-scan
|
||||
name: Security Vulnerability Scan (Manual)
|
||||
entry: scripts/security-scan.sh
|
||||
language: script
|
||||
files: '(\.go$|go\.mod$|go\.sum$)'
|
||||
pass_filenames: false
|
||||
verbose: true
|
||||
stages: [manual] # Only runs when explicitly called
|
||||
|
||||
- id: codeql-go-scan
|
||||
name: CodeQL Go Security Scan (Manual - Slow)
|
||||
entry: scripts/pre-commit-hooks/codeql-go-scan.sh
|
||||
language: script
|
||||
files: '\.go$'
|
||||
pass_filenames: false
|
||||
verbose: true
|
||||
stages: [manual] # Performance: 30-60s, only run on-demand
|
||||
|
||||
- id: codeql-js-scan
|
||||
name: CodeQL JavaScript/TypeScript Security Scan (Manual - Slow)
|
||||
entry: scripts/pre-commit-hooks/codeql-js-scan.sh
|
||||
language: script
|
||||
files: '^frontend/.*\.(ts|tsx|js|jsx)$'
|
||||
pass_filenames: false
|
||||
verbose: true
|
||||
stages: [manual] # Performance: 30-60s, only run on-demand
|
||||
|
||||
- id: codeql-check-findings
|
||||
name: Block HIGH/CRITICAL CodeQL Findings
|
||||
entry: scripts/pre-commit-hooks/codeql-check-findings.sh
|
||||
language: script
|
||||
pass_filenames: false
|
||||
verbose: true
|
||||
stages: [manual] # Only runs after CodeQL scans
|
||||
|
||||
- id: codeql-parity-check
|
||||
name: CodeQL Suite/Trigger Parity Guard (Manual)
|
||||
entry: scripts/ci/check-codeql-parity.sh
|
||||
language: script
|
||||
pass_filenames: false
|
||||
verbose: true
|
||||
stages: [manual]
|
||||
|
||||
- id: gorm-security-scan
|
||||
name: GORM Security Scanner (Manual)
|
||||
entry: scripts/pre-commit-hooks/gorm-security-check.sh
|
||||
language: script
|
||||
files: '\.go$'
|
||||
pass_filenames: false
|
||||
stages: [manual] # Manual stage initially (soft launch)
|
||||
verbose: true
|
||||
description: "Detects GORM ID leaks and common GORM security mistakes"
|
||||
|
||||
- id: semgrep-scan
|
||||
name: Semgrep Security Scan (Manual)
|
||||
entry: scripts/pre-commit-hooks/semgrep-scan.sh
|
||||
language: script
|
||||
pass_filenames: false
|
||||
verbose: true
|
||||
stages: [manual] # Manual stage initially (reversible rollout)
|
||||
|
||||
- id: gitleaks-tuned-scan
|
||||
name: Gitleaks Security Scan (Tuned, Manual)
|
||||
entry: scripts/pre-commit-hooks/gitleaks-tuned-scan.sh
|
||||
language: script
|
||||
pass_filenames: false
|
||||
verbose: true
|
||||
stages: [manual] # Manual stage initially (reversible rollout)
|
||||
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
rev: v0.47.0
|
||||
hooks:
|
||||
- id: markdownlint
|
||||
args: ["--fix"]
|
||||
exclude: '^(node_modules|\.venv|test-results|codeql-db|codeql-agent-results)/'
|
||||
stages: [manual]
|
||||
78
.trivyignore
78
.trivyignore
@@ -1,2 +1,80 @@
|
||||
.cache/
|
||||
playwright/.auth/
|
||||
|
||||
# GHSA-69x3-g4r3-p962 / CVE-2026-25793: Nebula ECDSA Signature Malleability
|
||||
# Severity: HIGH (CVSS 8.1) — Package: github.com/slackhq/nebula v1.9.7 in /usr/bin/caddy
|
||||
# Cannot upgrade: smallstep/certificates v0.27.5 (latest stable as of 2026-02-19) still pins nebula v1.9.x.
|
||||
# Charon does not use Nebula VPN PKI by default. Review by: 2026-03-05
|
||||
# See also: .grype.yaml for full justification
|
||||
CVE-2026-25793
|
||||
|
||||
# CVE-2026-22184: zlib Global Buffer Overflow in untgz utility
|
||||
# Severity: CRITICAL (CVSS 9.8) — Package: zlib 1.3.1-r2 in Alpine base image
|
||||
# No upstream fix available: Alpine 3.23 (including edge) still ships zlib 1.3.1-r2.
|
||||
# Charon does not use untgz or process untrusted tar archives. Review by: 2026-03-14
|
||||
# See also: .grype.yaml for full justification
|
||||
CVE-2026-22184
|
||||
|
||||
# CVE-2026-27171: zlib CPU spin via crc32_combine64 infinite loop (DoS)
|
||||
# Severity: MEDIUM (CVSS 5.5 NVD / 2.9 MITRE) — Package: zlib 1.3.1-r2 in Alpine base image
|
||||
# Fix requires zlib >= 1.3.2. No upstream fix available: Alpine 3.23 still ships zlib 1.3.1-r2.
|
||||
# Attack requires local access (AV:L); the vulnerable code path is not reachable via Charon's
|
||||
# network-facing surface. Non-blocking by CI policy (MEDIUM). Review by: 2026-04-21
|
||||
# exp: 2026-04-21
|
||||
CVE-2026-27171
|
||||
|
||||
# CVE-2026-2673: OpenSSL TLS 1.3 server key exchange group downgrade (libcrypto3/libssl3)
|
||||
# Severity: HIGH (CVSS 7.5) — Packages: libcrypto3 3.5.5-r0 and libssl3 3.5.5-r0 in Alpine base image
|
||||
# No upstream fix available: Alpine 3.23 still ships libcrypto3/libssl3 3.5.5-r0 as of 2026-03-18.
|
||||
# When DEFAULT is in TLS 1.3 group config, server may select a weaker key exchange group.
|
||||
# Charon terminates TLS at the Caddy layer — the Go backend does not act as a raw TLS 1.3 server.
|
||||
# Review by: 2026-04-18
|
||||
# See also: .grype.yaml for full justification
|
||||
# exp: 2026-04-18
|
||||
CVE-2026-2673
|
||||
|
||||
# CVE-2026-33186 / GHSA-p77j-4mvh-x3m3: gRPC-Go authorization bypass via missing leading slash
|
||||
# Severity: CRITICAL (CVSS 9.1) — Package: google.golang.org/grpc, embedded in CrowdSec (v1.74.2) and Caddy (v1.79.1)
|
||||
# Fix exists at v1.79.3 — Charon's own dep is patched. Waiting on CrowdSec and Caddy upstream releases.
|
||||
# CrowdSec's and Caddy's grpc servers are not exposed externally in a standard Charon deployment.
|
||||
# Review by: 2026-04-02
|
||||
# See also: .grype.yaml for full justification
|
||||
# exp: 2026-04-02
|
||||
CVE-2026-33186
|
||||
|
||||
# GHSA-479m-364c-43vc: goxmldsig XML signature validation bypass (loop variable capture)
|
||||
# Severity: HIGH (CVSS 7.5) — Package: github.com/russellhaering/goxmldsig v1.5.0, embedded in /usr/bin/caddy
|
||||
# Fix exists at v1.6.0 — waiting on Caddy upstream (or caddy-security plugin) to release with patched goxmldsig.
|
||||
# Charon does not configure SAML-based SSO by default; the vulnerable path is not reachable in a standard deployment.
|
||||
# Review by: 2026-04-02
|
||||
# See also: .grype.yaml for full justification
|
||||
# exp: 2026-04-02
|
||||
GHSA-479m-364c-43vc
|
||||
|
||||
# GHSA-6g7g-w4f8-9c9x: buger/jsonparser Delete panic on malformed JSON (DoS)
|
||||
# Severity: HIGH (CVSS 7.5) — Package: github.com/buger/jsonparser v1.1.1, embedded in CrowdSec binaries
|
||||
# No upstream fix available as of 2026-03-19 (issue #275 open, golang/vulndb #4514 open).
|
||||
# Charon does not use this package; the vector requires reaching CrowdSec's internal processing pipeline.
|
||||
# Review by: 2026-04-19
|
||||
# See also: .grype.yaml for full justification
|
||||
# exp: 2026-04-19
|
||||
GHSA-6g7g-w4f8-9c9x
|
||||
|
||||
# GHSA-jqcq-xjh3-6g23: pgproto3/v2 DataRow.Decode panic on negative field length (DoS)
|
||||
# Severity: HIGH (CVSS 7.5) — Package: github.com/jackc/pgproto3/v2 v2.3.3, embedded in CrowdSec binaries
|
||||
# pgproto3/v2 is archived/EOL — no fix will be released. Fix path requires CrowdSec to migrate to pgx/v5.
|
||||
# Charon uses SQLite; the PostgreSQL code path is not reachable in a standard deployment.
|
||||
# Review by: 2026-04-19
|
||||
# See also: .grype.yaml for full justification
|
||||
# exp: 2026-04-19
|
||||
GHSA-jqcq-xjh3-6g23
|
||||
|
||||
# GHSA-x6gf-mpr2-68h6 / CVE-2026-4427: pgproto3/v2 DataRow.Decode panic on negative field length (DoS)
|
||||
# Severity: HIGH (CVSS 7.5) — Package: github.com/jackc/pgproto3/v2 v2.3.3, embedded in CrowdSec binaries
|
||||
# NVD/Red Hat alias (CVE-2026-4427) for the same underlying bug as GHSA-jqcq-xjh3-6g23.
|
||||
# pgproto3/v2 is archived/EOL — no fix will be released. Fix path requires CrowdSec to migrate to pgx/v5.
|
||||
# Charon uses SQLite; the PostgreSQL code path is not reachable in a standard deployment.
|
||||
# Review by: 2026-04-21
|
||||
# See also: .grype.yaml for full justification
|
||||
# exp: 2026-04-21
|
||||
GHSA-x6gf-mpr2-68h6
|
||||
|
||||
8
.vscode/mcp.json
vendored
8
.vscode/mcp.json
vendored
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"inputs": [],
|
||||
"servers": {
|
||||
"microsoft/playwright-mcp": {
|
||||
"type": "stdio",
|
||||
@@ -8,11 +9,6 @@
|
||||
],
|
||||
"gallery": "https://api.mcp.github.com",
|
||||
"version": "0.0.1-seed"
|
||||
},
|
||||
"gopls": {
|
||||
"url": "http://localhost:8092",
|
||||
"type": "sse"
|
||||
}
|
||||
},
|
||||
"inputs": []
|
||||
}
|
||||
}
|
||||
|
||||
181
.vscode/tasks.json
vendored
181
.vscode/tasks.json
vendored
@@ -371,9 +371,9 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Lint: Pre-commit (All Files)",
|
||||
"label": "Lint: Lefthook Pre-commit (All Files)",
|
||||
"type": "shell",
|
||||
"command": ".github/skills/scripts/skill-runner.sh qa-precommit-all",
|
||||
"command": "lefthook run pre-commit",
|
||||
"group": "test",
|
||||
"problemMatcher": []
|
||||
},
|
||||
@@ -454,7 +454,7 @@
|
||||
{
|
||||
"label": "Security: Trivy Scan",
|
||||
"type": "shell",
|
||||
"command": ".github/skills/scripts/skill-runner.sh security-scan-trivy",
|
||||
"command": "TRIVY_DOCKER_RM=false .github/skills/scripts/skill-runner.sh security-scan-trivy",
|
||||
"group": "test",
|
||||
"problemMatcher": []
|
||||
},
|
||||
@@ -466,9 +466,9 @@
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Security: Semgrep Scan (Manual Hook)",
|
||||
"label": "Security: Semgrep Scan (Lefthook Pre-push)",
|
||||
"type": "shell",
|
||||
"command": "pre-commit run --hook-stage manual semgrep-scan --all-files",
|
||||
"command": "lefthook run pre-push",
|
||||
"group": "test",
|
||||
"problemMatcher": []
|
||||
},
|
||||
@@ -480,9 +480,9 @@
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Security: Gitleaks Scan (Tuned Manual Hook)",
|
||||
"label": "Security: Gitleaks Scan (Lefthook Pre-push)",
|
||||
"type": "shell",
|
||||
"command": "pre-commit run --hook-stage manual gitleaks-tuned-scan --all-files",
|
||||
"command": "lefthook run pre-push",
|
||||
"group": "test",
|
||||
"problemMatcher": []
|
||||
},
|
||||
@@ -501,14 +501,14 @@
|
||||
{
|
||||
"label": "Security: CodeQL Go Scan (DEPRECATED)",
|
||||
"type": "shell",
|
||||
"command": "codeql database create codeql-db-go --language=go --source-root=backend --overwrite && codeql database analyze codeql-db-go /projects/codeql/codeql/go/ql/src/codeql-suites/go-security-extended.qls --format=sarif-latest --output=codeql-results-go.sarif",
|
||||
"command": "bash scripts/pre-commit-hooks/codeql-go-scan.sh",
|
||||
"group": "test",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Security: CodeQL JS Scan (DEPRECATED)",
|
||||
"type": "shell",
|
||||
"command": "codeql database create codeql-db-js --language=javascript --source-root=frontend --overwrite && codeql database analyze codeql-db-js /projects/codeql/codeql/javascript/ql/src/codeql-suites/javascript-security-extended.qls --format=sarif-latest --output=codeql-results-js.sarif",
|
||||
"command": "bash scripts/pre-commit-hooks/codeql-js-scan.sh",
|
||||
"group": "test",
|
||||
"problemMatcher": []
|
||||
},
|
||||
@@ -724,6 +724,13 @@
|
||||
"group": "test",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Security: Caddy PR-1 Compatibility Matrix",
|
||||
"type": "shell",
|
||||
"command": "cd /projects/Charon && bash scripts/caddy-compat-matrix.sh --candidate-version 2.11.2 --patch-scenarios A,B,C --platforms linux/amd64,linux/arm64 --smoke-set boot_caddy,plugin_modules,config_validate,admin_api_health --output-dir test-results/caddy-compat --docs-report docs/reports/caddy-compatibility-matrix.md",
|
||||
"group": "test",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Test: E2E Playwright (Skill)",
|
||||
"type": "shell",
|
||||
@@ -808,6 +815,162 @@
|
||||
"close": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Test: E2E Playwright (Chromium) - Non-Security Shards 1/4-4/4",
|
||||
"type": "shell",
|
||||
"command": "cd /projects/Charon && if [ -f .env ]; then set -a; . ./.env; set +a; fi && : \"${CHARON_EMERGENCY_TOKEN:?CHARON_EMERGENCY_TOKEN is required (set it in /projects/Charon/.env)}\" && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=1 npx playwright test --project=chromium --shard=1/4 --output=playwright-output/chromium-shard-1 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks && cd /projects/Charon && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=2 npx playwright test --project=chromium --shard=2/4 --output=playwright-output/chromium-shard-2 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks && cd /projects/Charon && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=3 npx playwright test --project=chromium --shard=3/4 --output=playwright-output/chromium-shard-3 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks && cd /projects/Charon && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=4 npx playwright test --project=chromium --shard=4/4 --output=playwright-output/chromium-shard-4 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks",
|
||||
"group": "test",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"close": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Test: E2E Playwright (Chromium) - Non-Security Shard 1/4",
|
||||
"type": "shell",
|
||||
"command": "cd /projects/Charon && if [ -f .env ]; then set -a; . ./.env; set +a; fi && : \"${CHARON_EMERGENCY_TOKEN:?CHARON_EMERGENCY_TOKEN is required (set it in /projects/Charon/.env)}\" && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=1 npx playwright test --project=chromium --shard=1/4 --output=playwright-output/chromium-shard-1 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks",
|
||||
"group": "test",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"close": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Test: E2E Playwright (Chromium) - Non-Security Shard 2/4",
|
||||
"type": "shell",
|
||||
"command": "cd /projects/Charon && if [ -f .env ]; then set -a; . ./.env; set +a; fi && : \"${CHARON_EMERGENCY_TOKEN:?CHARON_EMERGENCY_TOKEN is required (set it in /projects/Charon/.env)}\" && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=2 npx playwright test --project=chromium --shard=2/4 --output=playwright-output/chromium-shard-2 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks",
|
||||
"group": "test",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"close": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Test: E2E Playwright (Chromium) - Non-Security Shard 3/4",
|
||||
"type": "shell",
|
||||
"command": "cd /projects/Charon && if [ -f .env ]; then set -a; . ./.env; set +a; fi && : \"${CHARON_EMERGENCY_TOKEN:?CHARON_EMERGENCY_TOKEN is required (set it in /projects/Charon/.env)}\" && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=3 npx playwright test --project=chromium --shard=3/4 --output=playwright-output/chromium-shard-3 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks",
|
||||
"group": "test",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"close": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Test: E2E Playwright (Chromium) - Non-Security Shard 4/4",
|
||||
"type": "shell",
|
||||
"command": "cd /projects/Charon && if [ -f .env ]; then set -a; . ./.env; set +a; fi && : \"${CHARON_EMERGENCY_TOKEN:?CHARON_EMERGENCY_TOKEN is required (set it in /projects/Charon/.env)}\" && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=4 npx playwright test --project=chromium --shard=4/4 --output=playwright-output/chromium-shard-4 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks",
|
||||
"group": "test",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"close": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Test: E2E Playwright (WebKit) - Non-Security Shards 1/4-4/4",
|
||||
"type": "shell",
|
||||
"command": "cd /projects/Charon && if [ -f .env ]; then set -a; . ./.env; set +a; fi && : \"${CHARON_EMERGENCY_TOKEN:?CHARON_EMERGENCY_TOKEN is required (set it in /projects/Charon/.env)}\" && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=1 npx playwright test --project=webkit --shard=1/4 --output=playwright-output/webkit-shard-1 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks && cd /projects/Charon && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=2 npx playwright test --project=webkit --shard=2/4 --output=playwright-output/webkit-shard-2 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks && cd /projects/Charon && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=3 npx playwright test --project=webkit --shard=3/4 --output=playwright-output/webkit-shard-3 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks && cd /projects/Charon && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=4 npx playwright test --project=webkit --shard=4/4 --output=playwright-output/webkit-shard-4 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks",
|
||||
"group": "test",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"close": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Test: E2E Playwright (WebKit) - Non-Security Shard 1/4",
|
||||
"type": "shell",
|
||||
"command": "cd /projects/Charon && if [ -f .env ]; then set -a; . ./.env; set +a; fi && : \"${CHARON_EMERGENCY_TOKEN:?CHARON_EMERGENCY_TOKEN is required (set it in /projects/Charon/.env)}\" && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=1 npx playwright test --project=webkit --shard=1/4 --output=playwright-output/webkit-shard-1 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks",
|
||||
"group": "test",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"close": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Test: E2E Playwright (WebKit) - Non-Security Shard 2/4",
|
||||
"type": "shell",
|
||||
"command": "cd /projects/Charon && if [ -f .env ]; then set -a; . ./.env; set +a; fi && : \"${CHARON_EMERGENCY_TOKEN:?CHARON_EMERGENCY_TOKEN is required (set it in /projects/Charon/.env)}\" && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=2 npx playwright test --project=webkit --shard=2/4 --output=playwright-output/webkit-shard-2 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks",
|
||||
"group": "test",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"close": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Test: E2E Playwright (WebKit) - Non-Security Shard 3/4",
|
||||
"type": "shell",
|
||||
"command": "cd /projects/Charon && if [ -f .env ]; then set -a; . ./.env; set +a; fi && : \"${CHARON_EMERGENCY_TOKEN:?CHARON_EMERGENCY_TOKEN is required (set it in /projects/Charon/.env)}\" && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=3 npx playwright test --project=webkit --shard=3/4 --output=playwright-output/webkit-shard-3 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks",
|
||||
"group": "test",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"close": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Test: E2E Playwright (WebKit) - Non-Security Shard 4/4",
|
||||
"type": "shell",
|
||||
"command": "cd /projects/Charon && if [ -f .env ]; then set -a; . ./.env; set +a; fi && : \"${CHARON_EMERGENCY_TOKEN:?CHARON_EMERGENCY_TOKEN is required (set it in /projects/Charon/.env)}\" && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=false PLAYWRIGHT_SKIP_SECURITY_DEPS=1 TEST_WORKER_INDEX=4 npx playwright test --project=webkit --shard=4/4 --output=playwright-output/webkit-shard-4 tests/core tests/dns-provider-crud.spec.ts tests/dns-provider-types.spec.ts tests/integration tests/manual-dns-provider.spec.ts tests/monitoring tests/settings tests/tasks",
|
||||
"group": "test",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"close": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Test: E2E Playwright (Chromium) - Security Suite",
|
||||
"type": "shell",
|
||||
"command": "cd /projects/Charon && if [ -f .env ]; then set -a; . ./.env; set +a; fi && : \"${CHARON_EMERGENCY_TOKEN:?CHARON_EMERGENCY_TOKEN is required (set it in /projects/Charon/.env)}\" && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=true PLAYWRIGHT_SKIP_SECURITY_DEPS=0 npx playwright test --project=security-tests --output=playwright-output/chromium-security tests/security",
|
||||
"group": "test",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"close": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Test: E2E Playwright (FireFox) - Security Suite",
|
||||
"type": "shell",
|
||||
"command": "cd /projects/Charon && if [ -f .env ]; then set -a; . ./.env; set +a; fi && : \"${CHARON_EMERGENCY_TOKEN:?CHARON_EMERGENCY_TOKEN is required (set it in /projects/Charon/.env)}\" && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=true PLAYWRIGHT_SKIP_SECURITY_DEPS=0 npx playwright test --project=firefox --output=playwright-output/firefox-security tests/security",
|
||||
"group": "test",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"close": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Test: E2E Playwright (WebKit) - Security Suite",
|
||||
"type": "shell",
|
||||
"command": "cd /projects/Charon && if [ -f .env ]; then set -a; . ./.env; set +a; fi && : \"${CHARON_EMERGENCY_TOKEN:?CHARON_EMERGENCY_TOKEN is required (set it in /projects/Charon/.env)}\" && CI=true PLAYWRIGHT_BASE_URL=http://127.0.0.1:8080 CHARON_SECURITY_TESTS_ENABLED=true PLAYWRIGHT_SKIP_SECURITY_DEPS=0 npx playwright test --project=webkit --output=playwright-output/webkit-security tests/security",
|
||||
"group": "test",
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "dedicated",
|
||||
"close": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Test: E2E Playwright with Coverage",
|
||||
"type": "shell",
|
||||
|
||||
@@ -126,11 +126,11 @@ graph TB
|
||||
| **HTTP Framework** | Gin | Latest | Routing, middleware, HTTP handling |
|
||||
| **Database** | SQLite | 3.x | Embedded database |
|
||||
| **ORM** | GORM | Latest | Database abstraction layer |
|
||||
| **Reverse Proxy** | Caddy Server | 2.11.0-beta.2 | Embedded HTTP/HTTPS proxy |
|
||||
| **Reverse Proxy** | Caddy Server | 2.11.2 | Embedded HTTP/HTTPS proxy |
|
||||
| **WebSocket** | gorilla/websocket | Latest | Real-time log streaming |
|
||||
| **Crypto** | golang.org/x/crypto | Latest | Password hashing, encryption |
|
||||
| **Metrics** | Prometheus Client | Latest | Application metrics |
|
||||
| **Notifications** | Shoutrrr | Latest | Multi-platform alerts |
|
||||
| **Notifications** | Notify (Discord-first) | Current | Discord notifications now; additional services in phased rollout |
|
||||
| **Docker Client** | Docker SDK | Latest | Container discovery |
|
||||
| **Logging** | Logrus + Lumberjack | Latest | Structured logging with rotation |
|
||||
|
||||
@@ -139,15 +139,15 @@ graph TB
|
||||
| Component | Technology | Version | Purpose |
|
||||
|-----------|-----------|---------|---------|
|
||||
| **Framework** | React | 19.2.3 | UI framework |
|
||||
| **Language** | TypeScript | 5.x | Type-safe JavaScript |
|
||||
| **Build Tool** | Vite | 6.1.9 | Fast bundler and dev server |
|
||||
| **CSS Framework** | Tailwind CSS | 3.x | Utility-first CSS |
|
||||
| **Language** | TypeScript | 6.x | Type-safe JavaScript |
|
||||
| **Build Tool** | Vite | 8.0.0-beta.18 | Fast bundler and dev server |
|
||||
| **CSS Framework** | Tailwind CSS | 4.2.1 | Utility-first CSS |
|
||||
| **Routing** | React Router | 7.x | Client-side routing |
|
||||
| **HTTP Client** | Fetch API | Native | API communication |
|
||||
| **State Management** | React Hooks + Context | Native | Global state |
|
||||
| **Internationalization** | i18next | Latest | 5 language support |
|
||||
| **Unit Testing** | Vitest | 2.x | Fast unit test runner |
|
||||
| **E2E Testing** | Playwright | 1.50.x | Browser automation |
|
||||
| **Unit Testing** | Vitest | 4.1.0-beta.6 | Fast unit test runner |
|
||||
| **E2E Testing** | Playwright | 1.58.2 | Browser automation |
|
||||
|
||||
### Infrastructure
|
||||
|
||||
@@ -218,7 +218,7 @@ graph TB
|
||||
│ │ └── main.tsx # Application entry point
|
||||
│ ├── public/ # Static assets
|
||||
│ ├── package.json # NPM dependencies
|
||||
│ └── vite.config.js # Vite configuration
|
||||
│ └── vite.config.ts # Vite configuration
|
||||
│
|
||||
├── .docker/ # Docker configuration
|
||||
│ ├── compose/ # Docker Compose files
|
||||
@@ -1259,6 +1259,14 @@ go test ./integration/...
|
||||
9. **Release Notes:** Generate changelog from commits
|
||||
10. **Notify:** Send release notification (Discord, email)
|
||||
|
||||
**Mandatory rollout gates (sign-off block):**
|
||||
|
||||
1. Digest freshness and index digest parity across GHCR and Docker Hub
|
||||
2. Per-arch digest parity across GHCR and Docker Hub
|
||||
3. SBOM and vulnerability scans against immutable refs (`image@sha256:...`)
|
||||
4. Artifact freshness timestamps after push
|
||||
5. Evidence block with required rollout verification fields
|
||||
|
||||
### Supply Chain Security
|
||||
|
||||
**Components:**
|
||||
@@ -1292,10 +1300,10 @@ cosign verify \
|
||||
wikid82/charon:latest
|
||||
|
||||
# Inspect SBOM
|
||||
syft wikid82/charon:latest -o json
|
||||
syft ghcr.io/wikid82/charon@sha256:<index-digest> -o json
|
||||
|
||||
# Scan for vulnerabilities
|
||||
grype wikid82/charon:latest
|
||||
grype ghcr.io/wikid82/charon@sha256:<index-digest>
|
||||
```
|
||||
|
||||
### Rollback Strategy
|
||||
@@ -1333,8 +1341,8 @@ docker exec charon /app/scripts/restore-backup.sh \
|
||||
- Future: Dynamic plugin loading for custom providers
|
||||
|
||||
2. **Notification Channels:**
|
||||
- Shoutrrr provides 40+ channels (Discord, Slack, Email, etc.)
|
||||
- Custom channels via Shoutrrr service URLs
|
||||
- Current rollout is Discord-only for notifications
|
||||
- Additional services are enabled later in validated phases
|
||||
|
||||
3. **Authentication Providers:**
|
||||
- Current: Local database authentication
|
||||
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- **Pushover Notification Provider**: Send push notifications to your devices via the Pushover app
|
||||
- Supports JSON templates (minimal, detailed, custom)
|
||||
- Application API Token stored securely — never exposed in API responses
|
||||
- User Key stored in the URL field, following the same pattern as Telegram
|
||||
- Feature flag: `feature.notifications.service.pushover.enabled` (on by default)
|
||||
- Emergency priority (2) is intentionally unsupported — deferred to a future release
|
||||
|
||||
- **Slack Notification Provider**: Send alerts to Slack channels via Incoming Webhooks
|
||||
- Supports JSON templates (minimal, detailed, custom) with Slack's native `text` format
|
||||
- Webhook URL stored securely — never exposed in API responses
|
||||
- Optional channel display name for easy identification in provider list
|
||||
- Feature flag: `feature.notifications.service.slack.enabled` (on by default)
|
||||
- See [Notification Guide](docs/features/notifications.md) for setup instructions
|
||||
|
||||
### CI/CD
|
||||
- **Supply Chain**: Optimized verification workflow to prevent redundant builds
|
||||
- Change: Removed direct Push/PR triggers; now waits for 'Docker Build' via `workflow_run`
|
||||
@@ -29,8 +45,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Prevents timeout errors in Firefox/WebKit caused by strict label matching
|
||||
|
||||
### Fixed
|
||||
- **TCP Monitor Creation**: Fixed misleading form UX that caused silent HTTP 500 errors when creating TCP monitors
|
||||
- Corrected URL placeholder to show `host:port` format instead of the incorrect `tcp://host:port` prefix
|
||||
- Added dynamic per-type placeholder and helper text (HTTP monitors show a full URL example; TCP monitors show `host:port`)
|
||||
- Added client-side validation that blocks form submission when a scheme prefix (e.g. `tcp://`) is detected, with an inline error message
|
||||
- Reordered form fields so the monitor type selector appears above the URL input, making the dynamic helper text immediately relevant
|
||||
- i18n: Added 5 new translation keys across en, de, fr, es, and zh locales
|
||||
- **CI: Rate Limit Integration Tests**: Hardened test script reliability — login now validates HTTP status, Caddy admin API readiness gated on `/config/` poll, security config failures are fatal with full diagnostics, and poll interval increased to 5s
|
||||
- **CI: Rate Limit Integration Tests**: Removed stale GeoIP database SHA256 checksum from Dockerfile non-CI path (hash was perpetually stale due to weekly upstream updates)
|
||||
- **CI: Rate Limit Integration Tests**: Fixed Caddy admin API debug dump URL to use canonical trailing slash in workflow
|
||||
- Fixed: Added robust validation and debug logging for Docker image tags to prevent invalid reference errors.
|
||||
- Fixed: Removed log masking for image references and added manifest validation to debug CI failures.
|
||||
- **Proxy Hosts**: Fixed ACL and Security Headers dropdown selections so create/edit saves now keep the selected values (including clearing to none) after submit and reload.
|
||||
- **CI**: Fixed Docker image reference output so integration jobs never pull an empty image ref
|
||||
- **E2E Test Reliability**: Resolved test timeout issues affecting CI/CD pipeline stability
|
||||
- Fixed config reload overlay blocking test interactions
|
||||
|
||||
@@ -33,7 +33,19 @@ This project follows a Code of Conduct that all contributors are expected to adh
|
||||
|
||||
### Development Tools
|
||||
|
||||
Install golangci-lint for pre-commit hooks (required for Go development):
|
||||
Install golangci-lint for lefthook pre-commit-phase hooks (required for Go development):
|
||||
|
||||
Also install lefthook itself so the git hooks work:
|
||||
|
||||
```bash
|
||||
# Option 1: Homebrew (macOS/Linux)
|
||||
brew install lefthook
|
||||
|
||||
# Option 2: Go install
|
||||
go install github.com/evilmartians/lefthook@latest
|
||||
```
|
||||
|
||||
|
||||
|
||||
```bash
|
||||
# Option 1: Homebrew (macOS/Linux)
|
||||
@@ -59,7 +71,7 @@ golangci-lint --version
|
||||
# Should output: golangci-lint has version 1.xx.x ...
|
||||
```
|
||||
|
||||
**Note:** Pre-commit hooks will **BLOCK commits** if golangci-lint finds issues. This is intentional - fix the issues before committing.
|
||||
**Note:** Lefthook pre-commit-phase hooks will **BLOCK commits** if golangci-lint finds issues. This is intentional - fix the issues before committing.
|
||||
|
||||
### CI/CD Go Version Management
|
||||
|
||||
@@ -84,7 +96,7 @@ When the project's Go version is updated (usually by Renovate):
|
||||
|
||||
3. **Rebuild your development tools**
|
||||
```bash
|
||||
# This fixes pre-commit hook errors and IDE issues
|
||||
# This fixes lefthook hook errors and IDE issues
|
||||
./scripts/rebuild-go-tools.sh
|
||||
```
|
||||
|
||||
@@ -104,7 +116,7 @@ Rebuilding tools with `./scripts/rebuild-go-tools.sh` fixes this by compiling th
|
||||
|
||||
**What if I forget?**
|
||||
|
||||
Don't worry! The pre-commit hook will detect the version mismatch and automatically rebuild tools for you. You'll see:
|
||||
Don't worry! The lefthook pre-commit hook will detect the version mismatch and automatically rebuild tools for you. You'll see:
|
||||
|
||||
```
|
||||
⚠️ golangci-lint Go version mismatch:
|
||||
|
||||
200
Dockerfile
200
Dockerfile
@@ -8,21 +8,49 @@ ARG VCS_REF
|
||||
# Set BUILD_DEBUG=1 to build with debug symbols (required for Delve debugging)
|
||||
ARG BUILD_DEBUG=0
|
||||
|
||||
# ---- Pinned Toolchain Versions ----
|
||||
# renovate: datasource=docker depName=golang versioning=docker
|
||||
ARG GO_VERSION=1.26.1
|
||||
|
||||
# renovate: datasource=docker depName=alpine versioning=docker
|
||||
ARG ALPINE_IMAGE=alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659
|
||||
|
||||
# ---- Shared CrowdSec Version ----
|
||||
# renovate: datasource=github-releases depName=crowdsecurity/crowdsec
|
||||
ARG CROWDSEC_VERSION=1.7.6
|
||||
# CrowdSec fallback tarball checksum (v${CROWDSEC_VERSION})
|
||||
ARG CROWDSEC_RELEASE_SHA256=704e37121e7ac215991441cef0d8732e33fa3b1a2b2b88b53a0bfe5e38f863bd
|
||||
|
||||
# ---- Shared Go Security Patches ----
|
||||
# renovate: datasource=go depName=github.com/expr-lang/expr
|
||||
ARG EXPR_LANG_VERSION=1.17.8
|
||||
# renovate: datasource=go depName=golang.org/x/net
|
||||
ARG XNET_VERSION=0.52.0
|
||||
# renovate: datasource=go depName=github.com/smallstep/certificates
|
||||
ARG SMALLSTEP_CERTIFICATES_VERSION=0.30.0
|
||||
# renovate: datasource=npm depName=npm
|
||||
ARG NPM_VERSION=11.11.1
|
||||
|
||||
# Allow pinning Caddy version - Renovate will update this
|
||||
# Build the most recent Caddy 2.x release (keeps major pinned under v3).
|
||||
# Setting this to '2' tells xcaddy to resolve the latest v2.x tag so we
|
||||
# avoid accidentally pulling a v3 major release. Renovate can still update
|
||||
# this ARG to a specific v2.x tag when desired.
|
||||
## Try to build the requested Caddy v2.x tag (Renovate can update this ARG).
|
||||
## If the requested tag isn't available, fall back to a known-good v2.11.0-beta.2 build.
|
||||
ARG CADDY_VERSION=2.11.0-beta.2
|
||||
## If the requested tag isn't available, fall back to a known-good v2.11.2 build.
|
||||
ARG CADDY_VERSION=2.11.2
|
||||
ARG CADDY_CANDIDATE_VERSION=2.11.2
|
||||
ARG CADDY_USE_CANDIDATE=0
|
||||
ARG CADDY_PATCH_SCENARIO=B
|
||||
# renovate: datasource=go depName=github.com/greenpau/caddy-security
|
||||
ARG CADDY_SECURITY_VERSION=1.1.50
|
||||
# renovate: datasource=go depName=github.com/corazawaf/coraza-caddy
|
||||
ARG CORAZA_CADDY_VERSION=2.2.0
|
||||
## When an official caddy image tag isn't available on the host, use a
|
||||
## plain Alpine base image and overwrite its caddy binary with our
|
||||
## xcaddy-built binary in the later COPY step. This avoids relying on
|
||||
## upstream caddy image tags while still shipping a pinned caddy binary.
|
||||
## Alpine 3.23 base to reduce glibc CVE exposure and image size.
|
||||
# renovate: datasource=docker depName=alpine versioning=docker
|
||||
ARG CADDY_IMAGE=alpine:3.23.3
|
||||
|
||||
# ---- Cross-Compilation Helpers ----
|
||||
# renovate: datasource=docker depName=tonistiigi/xx
|
||||
@@ -33,8 +61,7 @@ FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.9.0@sha256:c64defb9ed5a91eacb37f9
|
||||
# This fixes 22 HIGH/CRITICAL CVEs in stdlib embedded in Debian's gosu package
|
||||
# CVEs fixed: CVE-2023-24531, CVE-2023-24540, CVE-2023-29402, CVE-2023-29404,
|
||||
# CVE-2023-29405, CVE-2024-24790, CVE-2025-22871, and 15 more
|
||||
# renovate: datasource=docker depName=golang
|
||||
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS gosu-builder
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS gosu-builder
|
||||
COPY --from=xx / /
|
||||
|
||||
WORKDIR /tmp/gosu
|
||||
@@ -65,7 +92,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
# ---- Frontend Builder ----
|
||||
# Build the frontend using the BUILDPLATFORM to avoid arm64 musl Rollup native issues
|
||||
# renovate: datasource=docker depName=node
|
||||
FROM --platform=$BUILDPLATFORM node:24.13.1-alpine AS frontend-builder
|
||||
FROM --platform=$BUILDPLATFORM node:24.14.0-alpine@sha256:7fddd9ddeae8196abf4a3ef2de34e11f7b1a722119f91f28ddf1e99dcafdf114 AS frontend-builder
|
||||
WORKDIR /app/frontend
|
||||
|
||||
# Copy frontend package files
|
||||
@@ -76,9 +103,12 @@ ARG VERSION=dev
|
||||
# Make version available to Vite as VITE_APP_VERSION during the frontend build
|
||||
ENV VITE_APP_VERSION=${VERSION}
|
||||
|
||||
# Set environment to bypass native binary requirement for cross-arch builds
|
||||
ENV npm_config_rollup_skip_nodejs_native=1 \
|
||||
ROLLUP_SKIP_NODEJS_NATIVE=1
|
||||
# Vite 8: Rolldown native bindings auto-resolved per platform via optionalDependencies
|
||||
ARG NPM_VERSION
|
||||
# hadolint ignore=DL3017
|
||||
RUN apk upgrade --no-cache && \
|
||||
npm install -g npm@${NPM_VERSION} --no-fund --no-audit && \
|
||||
npm cache clean --force
|
||||
|
||||
RUN npm ci
|
||||
|
||||
@@ -88,8 +118,7 @@ RUN --mount=type=cache,target=/app/frontend/node_modules/.cache \
|
||||
npm run build
|
||||
|
||||
# ---- Backend Builder ----
|
||||
# renovate: datasource=docker depName=golang
|
||||
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS backend-builder
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS backend-builder
|
||||
# Copy xx helpers for cross-compilation
|
||||
COPY --from=xx / /
|
||||
|
||||
@@ -131,7 +160,7 @@ RUN set -eux; \
|
||||
# Note: xx-go install puts binaries in /go/bin/TARGETOS_TARGETARCH/dlv if cross-compiling.
|
||||
# We find it and move it to /go/bin/dlv so it's in a consistent location for the next stage.
|
||||
# renovate: datasource=go depName=github.com/go-delve/delve
|
||||
ARG DLV_VERSION=1.26.0
|
||||
ARG DLV_VERSION=1.26.1
|
||||
# hadolint ignore=DL3059,DL4006
|
||||
RUN CGO_ENABLED=0 xx-go install github.com/go-delve/delve/cmd/dlv@v${DLV_VERSION} && \
|
||||
DLV_PATH=$(find /go/bin -name dlv -type f | head -n 1) && \
|
||||
@@ -191,16 +220,23 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
# ---- Caddy Builder ----
|
||||
# Build Caddy from source to ensure we use the latest Go version and dependencies
|
||||
# This fixes vulnerabilities found in the pre-built Caddy images (e.g. CVE-2025-59530, stdlib issues)
|
||||
# renovate: datasource=docker depName=golang
|
||||
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS caddy-builder
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS caddy-builder
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG CADDY_VERSION
|
||||
ARG CADDY_CANDIDATE_VERSION
|
||||
ARG CADDY_USE_CANDIDATE
|
||||
ARG CADDY_PATCH_SCENARIO
|
||||
ARG CADDY_SECURITY_VERSION
|
||||
ARG CORAZA_CADDY_VERSION
|
||||
# renovate: datasource=go depName=github.com/caddyserver/xcaddy
|
||||
ARG XCADDY_VERSION=0.4.5
|
||||
ARG EXPR_LANG_VERSION
|
||||
ARG XNET_VERSION
|
||||
ARG SMALLSTEP_CERTIFICATES_VERSION
|
||||
|
||||
# hadolint ignore=DL3018
|
||||
RUN apk add --no-cache git
|
||||
RUN apk add --no-cache bash git
|
||||
# hadolint ignore=DL3062
|
||||
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
go install github.com/caddyserver/xcaddy/cmd/xcaddy@v${XCADDY_VERSION}
|
||||
@@ -212,13 +248,20 @@ RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
# hadolint ignore=SC2016
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
sh -c 'set -e; \
|
||||
bash -c 'set -e; \
|
||||
CADDY_TARGET_VERSION="${CADDY_VERSION}"; \
|
||||
if [ "${CADDY_USE_CANDIDATE}" = "1" ]; then \
|
||||
CADDY_TARGET_VERSION="${CADDY_CANDIDATE_VERSION}"; \
|
||||
fi; \
|
||||
echo "Using Caddy target version: v${CADDY_TARGET_VERSION}"; \
|
||||
echo "Using Caddy patch scenario: ${CADDY_PATCH_SCENARIO}"; \
|
||||
export XCADDY_SKIP_CLEANUP=1; \
|
||||
echo "Stage 1: Generate go.mod with xcaddy..."; \
|
||||
# Run xcaddy to generate the build directory and go.mod
|
||||
GOOS=$TARGETOS GOARCH=$TARGETARCH xcaddy build v${CADDY_VERSION} \
|
||||
--with github.com/greenpau/caddy-security \
|
||||
--with github.com/corazawaf/coraza-caddy/v2 \
|
||||
GOOS=$TARGETOS GOARCH=$TARGETARCH xcaddy build v${CADDY_TARGET_VERSION} \
|
||||
--with github.com/caddyserver/caddy/v2@v${CADDY_TARGET_VERSION} \
|
||||
--with github.com/greenpau/caddy-security@v${CADDY_SECURITY_VERSION} \
|
||||
--with github.com/corazawaf/coraza-caddy/v2@v${CORAZA_CADDY_VERSION} \
|
||||
--with github.com/hslatman/caddy-crowdsec-bouncer@v0.10.0 \
|
||||
--with github.com/zhangjiayin/caddy-geoip2 \
|
||||
--with github.com/mholt/caddy-ratelimit \
|
||||
@@ -235,16 +278,39 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
# Patch ALL dependencies BEFORE building the final binary
|
||||
# These patches fix CVEs in transitive dependencies
|
||||
# Renovate tracks these via regex manager in renovate.json
|
||||
# renovate: datasource=go depName=github.com/expr-lang/expr
|
||||
go get github.com/expr-lang/expr@v1.17.7; \
|
||||
go get github.com/expr-lang/expr@v${EXPR_LANG_VERSION}; \
|
||||
# renovate: datasource=go depName=github.com/hslatman/ipstore
|
||||
go get github.com/hslatman/ipstore@v0.4.0; \
|
||||
# NOTE: smallstep/certificates (pulled by caddy-security stack) currently
|
||||
# uses legacy nebula APIs removed in nebula v1.10+, which causes compile
|
||||
# failures in authority/provisioner. Keep this pinned to a known-compatible
|
||||
# v1.9.x release until upstream stack supports nebula v1.10+.
|
||||
# renovate: datasource=go depName=github.com/slackhq/nebula
|
||||
go get github.com/slackhq/nebula@v1.9.7; \
|
||||
go get golang.org/x/net@v${XNET_VERSION}; \
|
||||
# CVE-2026-33186 (GHSA-p77j-4mvh-x3m3): gRPC-Go auth bypass via missing leading slash
|
||||
# Fix available at v1.79.3. Pin here so the Caddy binary is patched immediately;
|
||||
# remove once Caddy ships a release built with grpc >= v1.79.3.
|
||||
# renovate: datasource=go depName=google.golang.org/grpc
|
||||
go get google.golang.org/grpc@v1.79.3; \
|
||||
# GHSA-479m-364c-43vc: goxmldsig XML signature validation bypass (loop variable capture)
|
||||
# Fix available at v1.6.0. Pin here so the Caddy binary is patched immediately;
|
||||
# remove once caddy-security ships a release built with goxmldsig >= v1.6.0.
|
||||
# renovate: datasource=go depName=github.com/russellhaering/goxmldsig
|
||||
go get github.com/russellhaering/goxmldsig@v1.6.0; \
|
||||
# CVE-2026-30836: smallstep/certificates 0.30.0-rc3 vulnerability
|
||||
# Fix available at v0.30.0. Pin here so the Caddy binary is patched immediately;
|
||||
# remove once caddy-security ships a release built with smallstep/certificates >= v0.30.0.
|
||||
go get github.com/smallstep/certificates@v${SMALLSTEP_CERTIFICATES_VERSION}; \
|
||||
if [ "${CADDY_PATCH_SCENARIO}" = "A" ]; then \
|
||||
# Rollback scenario: keep explicit nebula pin if upstream compatibility regresses.
|
||||
# NOTE: smallstep/certificates (pulled by caddy-security stack) currently
|
||||
# uses legacy nebula APIs removed in nebula v1.10+, which causes compile
|
||||
# failures in authority/provisioner. Keep this pinned to a known-compatible
|
||||
# v1.9.x release until upstream stack supports nebula v1.10+.
|
||||
# renovate: datasource=go depName=github.com/slackhq/nebula
|
||||
go get github.com/slackhq/nebula@v1.9.7; \
|
||||
elif [ "${CADDY_PATCH_SCENARIO}" = "B" ] || [ "${CADDY_PATCH_SCENARIO}" = "C" ]; then \
|
||||
# Default PR-2 posture: retire explicit nebula pin and use upstream resolution.
|
||||
echo "Skipping nebula pin for scenario ${CADDY_PATCH_SCENARIO}"; \
|
||||
else \
|
||||
echo "Unsupported CADDY_PATCH_SCENARIO=${CADDY_PATCH_SCENARIO}"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
# Clean up go.mod and ensure all dependencies are resolved
|
||||
go mod tidy; \
|
||||
echo "Dependencies patched successfully"; \
|
||||
@@ -263,10 +329,9 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
rm -rf /tmp/buildenv_* /tmp/caddy-initial'
|
||||
|
||||
# ---- CrowdSec Builder ----
|
||||
# Build CrowdSec from source to ensure we use Go 1.26.0+ and avoid stdlib vulnerabilities
|
||||
# Build CrowdSec from source to ensure we use Go 1.26.1+ and avoid stdlib vulnerabilities
|
||||
# (CVE-2025-58183, CVE-2025-58186, CVE-2025-58187, CVE-2025-61729)
|
||||
# renovate: datasource=docker depName=golang versioning=docker
|
||||
FROM --platform=$BUILDPLATFORM golang:1.26.0-alpine AS crowdsec-builder
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS crowdsec-builder
|
||||
COPY --from=xx / /
|
||||
|
||||
WORKDIR /tmp/crowdsec
|
||||
@@ -274,11 +339,10 @@ WORKDIR /tmp/crowdsec
|
||||
ARG TARGETPLATFORM
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
# CrowdSec version - Renovate can update this
|
||||
# renovate: datasource=github-releases depName=crowdsecurity/crowdsec
|
||||
ARG CROWDSEC_VERSION=1.7.6
|
||||
# CrowdSec fallback tarball checksum (v${CROWDSEC_VERSION})
|
||||
ARG CROWDSEC_RELEASE_SHA256=704e37121e7ac215991441cef0d8732e33fa3b1a2b2b88b53a0bfe5e38f863bd
|
||||
ARG CROWDSEC_VERSION
|
||||
ARG CROWDSEC_RELEASE_SHA256
|
||||
ARG EXPR_LANG_VERSION
|
||||
ARG XNET_VERSION
|
||||
|
||||
# hadolint ignore=DL3018
|
||||
RUN apk add --no-cache git clang lld
|
||||
@@ -292,10 +356,15 @@ RUN git clone --depth 1 --branch "v${CROWDSEC_VERSION}" https://github.com/crowd
|
||||
|
||||
# Patch dependencies to fix CVEs in transitive dependencies
|
||||
# This follows the same pattern as Caddy's dependency patches
|
||||
# renovate: datasource=go depName=github.com/expr-lang/expr
|
||||
# renovate: datasource=go depName=golang.org/x/crypto
|
||||
RUN go get github.com/expr-lang/expr@v1.17.7 && \
|
||||
RUN go get github.com/expr-lang/expr@v${EXPR_LANG_VERSION} && \
|
||||
go get golang.org/x/crypto@v0.46.0 && \
|
||||
go get golang.org/x/net@v${XNET_VERSION} && \
|
||||
# CVE-2026-33186 (GHSA-p77j-4mvh-x3m3): gRPC-Go auth bypass via missing leading slash
|
||||
# Fix available at v1.79.3. Pin here so the CrowdSec binary is patched immediately;
|
||||
# remove once CrowdSec ships a release built with grpc >= v1.79.3.
|
||||
# renovate: datasource=go depName=google.golang.org/grpc
|
||||
go get google.golang.org/grpc@v1.79.3 && \
|
||||
go mod tidy
|
||||
|
||||
# Fix compatibility issues with expr-lang v1.17.7
|
||||
@@ -325,18 +394,15 @@ RUN mkdir -p /crowdsec-out/config && \
|
||||
cp -r config/* /crowdsec-out/config/ || true
|
||||
|
||||
# ---- CrowdSec Fallback (for architectures where build fails) ----
|
||||
# renovate: datasource=docker depName=alpine versioning=docker
|
||||
FROM alpine:3.23.3 AS crowdsec-fallback
|
||||
FROM ${ALPINE_IMAGE} AS crowdsec-fallback
|
||||
|
||||
SHELL ["/bin/ash", "-o", "pipefail", "-c"]
|
||||
|
||||
WORKDIR /tmp/crowdsec
|
||||
|
||||
ARG TARGETARCH
|
||||
# CrowdSec version - Renovate can update this
|
||||
# renovate: datasource=github-releases depName=crowdsecurity/crowdsec
|
||||
ARG CROWDSEC_VERSION=1.7.6
|
||||
ARG CROWDSEC_RELEASE_SHA256=704e37121e7ac215991441cef0d8732e33fa3b1a2b2b88b53a0bfe5e38f863bd
|
||||
ARG CROWDSEC_VERSION
|
||||
ARG CROWDSEC_RELEASE_SHA256
|
||||
|
||||
# hadolint ignore=DL3018
|
||||
RUN apk add --no-cache curl ca-certificates
|
||||
@@ -365,17 +431,17 @@ RUN set -eux; \
|
||||
fi
|
||||
|
||||
# ---- Final Runtime with Caddy ----
|
||||
FROM ${CADDY_IMAGE}
|
||||
FROM ${ALPINE_IMAGE}
|
||||
WORKDIR /app
|
||||
|
||||
# Install runtime dependencies for Charon, including bash for maintenance scripts
|
||||
# Note: gosu is now built from source (see gosu-builder stage) to avoid CVEs from Debian's pre-compiled version
|
||||
# Explicitly upgrade packages to fix security vulnerabilities
|
||||
# binutils provides objdump for debug symbol detection in docker-entrypoint.sh
|
||||
# hadolint ignore=DL3018
|
||||
RUN apk add --no-cache \
|
||||
bash ca-certificates sqlite-libs sqlite tzdata curl gettext libcap libcap-utils \
|
||||
c-ares binutils libc-utils busybox-extras
|
||||
bash ca-certificates sqlite-libs sqlite tzdata gettext libcap libcap-utils \
|
||||
c-ares busybox-extras \
|
||||
&& apk upgrade --no-cache zlib
|
||||
|
||||
# Copy gosu binary from gosu-builder (built with Go 1.26+ to avoid stdlib CVEs)
|
||||
COPY --from=gosu-builder /gosu-out/gosu /usr/sbin/gosu
|
||||
@@ -392,12 +458,13 @@ SHELL ["/bin/ash", "-o", "pipefail", "-c"]
|
||||
# Note: In production, users should provide their own MaxMind license key
|
||||
# This uses the publicly available GeoLite2 database
|
||||
# In CI, timeout quickly rather than retrying to save build time
|
||||
ARG GEOLITE2_COUNTRY_SHA256=1cf82f09ce08a6e160d7426fc59fd6c12d56650e7408c832172b2eb9b62cf28d
|
||||
ARG GEOLITE2_COUNTRY_SHA256=aa154fc6bcd712644de232a4abcdd07dac1f801308c0b6f93dbc2b375443da7b
|
||||
RUN mkdir -p /app/data/geoip && \
|
||||
if [ -n "$CI" ]; then \
|
||||
if [ "$CI" = "true" ] || [ "$CI" = "1" ]; then \
|
||||
echo "⏱️ CI detected - quick download (10s timeout, no retries)"; \
|
||||
if curl -fSL -m 10 "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb" \
|
||||
-o /app/data/geoip/GeoLite2-Country.mmdb 2>/dev/null; then \
|
||||
if wget -qO /app/data/geoip/GeoLite2-Country.mmdb \
|
||||
-T 10 "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb" 2>/dev/null \
|
||||
&& [ -s /app/data/geoip/GeoLite2-Country.mmdb ]; then \
|
||||
echo "✅ GeoIP downloaded"; \
|
||||
else \
|
||||
echo "⚠️ GeoIP skipped"; \
|
||||
@@ -405,16 +472,12 @@ RUN mkdir -p /app/data/geoip && \
|
||||
fi; \
|
||||
else \
|
||||
echo "Local - full download (30s timeout, 3 retries)"; \
|
||||
if curl -fSL -m 30 --retry 3 "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb" \
|
||||
-o /app/data/geoip/GeoLite2-Country.mmdb; then \
|
||||
if echo "${GEOLITE2_COUNTRY_SHA256} /app/data/geoip/GeoLite2-Country.mmdb" | sha256sum -c -; then \
|
||||
echo "✅ GeoIP checksum verified"; \
|
||||
else \
|
||||
echo "⚠️ Checksum failed"; \
|
||||
touch /app/data/geoip/GeoLite2-Country.mmdb.placeholder; \
|
||||
fi; \
|
||||
if wget -qO /app/data/geoip/GeoLite2-Country.mmdb \
|
||||
-T 30 -t 4 "https://github.com/P3TERX/GeoLite.mmdb/raw/download/GeoLite2-Country.mmdb" \
|
||||
&& [ -s /app/data/geoip/GeoLite2-Country.mmdb ]; then \
|
||||
echo "✅ GeoIP downloaded"; \
|
||||
else \
|
||||
echo "⚠️ Download failed"; \
|
||||
echo "⚠️ GeoIP download failed or empty — skipping"; \
|
||||
touch /app/data/geoip/GeoLite2-Country.mmdb.placeholder; \
|
||||
fi; \
|
||||
fi
|
||||
@@ -425,7 +488,7 @@ COPY --from=caddy-builder /usr/bin/caddy /usr/bin/caddy
|
||||
# Allow non-root to bind privileged ports (80/443) securely
|
||||
RUN setcap 'cap_net_bind_service=+ep' /usr/bin/caddy
|
||||
|
||||
# Copy CrowdSec binaries from the crowdsec-builder stage (built with Go 1.26.0+)
|
||||
# Copy CrowdSec binaries from the crowdsec-builder stage (built with Go 1.26.1+)
|
||||
# This ensures we don't have stdlib vulnerabilities from older Go versions
|
||||
COPY --from=crowdsec-builder /crowdsec-out/crowdsec /usr/local/bin/crowdsec
|
||||
COPY --from=crowdsec-builder /crowdsec-out/cscli /usr/local/bin/cscli
|
||||
@@ -540,21 +603,16 @@ EXPOSE 80 443 443/udp 2019 8080
|
||||
|
||||
# Security: Add healthcheck to monitor container health
|
||||
# Verifies the Charon API is responding correctly
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
|
||||
CMD curl -f http://localhost:8080/api/v1/health || exit 1
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
|
||||
CMD wget -q -O /dev/null http://localhost:8080/api/v1/health || exit 1
|
||||
|
||||
# Create CrowdSec symlink as root before switching to non-root user
|
||||
# This symlink allows CrowdSec to use persistent storage at /app/data/crowdsec/config
|
||||
# while maintaining the expected /etc/crowdsec path for compatibility
|
||||
RUN ln -sf /app/data/crowdsec/config /etc/crowdsec
|
||||
|
||||
# Security: Container starts as root to handle Docker socket group permissions,
|
||||
# then the entrypoint script drops privileges to the charon user before starting
|
||||
# applications. This approach:
|
||||
# 1. Maintains CIS Docker Benchmark compliance (non-root execution)
|
||||
# 2. Enables Docker integration by dynamically adding charon to docker group
|
||||
# 3. Ensures proper ownership of mounted volumes
|
||||
# The entrypoint script uses gosu to securely drop privileges after setup.
|
||||
# Security: Run the container as non-root by default.
|
||||
USER charon
|
||||
|
||||
# Use custom entrypoint to start both Caddy and Charon
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
9
Makefile
9
Makefile
@@ -1,4 +1,4 @@
|
||||
.PHONY: help install test build run clean docker-build docker-run release go-check gopls-logs lint-fast lint-staticcheck-only
|
||||
.PHONY: help install test build run clean docker-build docker-run release go-check gopls-logs lint-fast lint-staticcheck-only security-local
|
||||
|
||||
# Default target
|
||||
help:
|
||||
@@ -22,6 +22,7 @@ help:
|
||||
@echo ""
|
||||
@echo "Security targets:"
|
||||
@echo " security-scan - Quick security scan (govulncheck on Go deps)"
|
||||
@echo " security-local - Run govulncheck + semgrep (p/golang) locally before push"
|
||||
@echo " security-scan-full - Full container scan with Trivy"
|
||||
@echo " security-scan-deps - Check for outdated Go dependencies"
|
||||
|
||||
@@ -145,6 +146,12 @@ security-scan:
|
||||
@echo "Running security scan (govulncheck)..."
|
||||
@./scripts/security-scan.sh
|
||||
|
||||
security-local: ## Run govulncheck + semgrep (p/golang) before push — fast local gate
|
||||
@echo "[1/2] Running govulncheck..."
|
||||
@./scripts/security-scan.sh
|
||||
@echo "[2/2] Running Semgrep (p/golang, ERROR+WARNING)..."
|
||||
@SEMGREP_CONFIG=p/golang ./scripts/pre-commit-hooks/semgrep-scan.sh
|
||||
|
||||
security-scan-full:
|
||||
@echo "Building local Docker image for security scan..."
|
||||
docker build --build-arg VCS_REF=$(shell git rev-parse HEAD) -t charon:local .
|
||||
|
||||
649
README.md
649
README.md
@@ -1,75 +1,132 @@
|
||||
<p align="center">
|
||||
<img src="frontend/public/banner.png" alt="Charon" width="600">
|
||||
<img src="https://raw.githubusercontent.com/Wikid82/Charon/refs/heads/main/frontend/public/banner.webp" alt="Charon" width="350">
|
||||
</p>
|
||||
|
||||
<h1 align="center">Charon</h1>
|
||||
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://www.repostatus.org/#active"><img src="https://www.repostatus.org/badges/latest/active.svg" alt="Project Status: Active – The project is being actively developed." /></a>
|
||||
<a href="https://hub.docker.com/r/wikid82/charon"><img src="https://img.shields.io/docker/pulls/wikid82/charon.svg" alt="Docker Pulls"></a>
|
||||
<a href="https://github.com/users/Wikid82/packages/container/package/charon"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Wikid82/Charon/main/.github/badges/ghcr-downloads.json" alt="GHCR Pulls"></a>
|
||||
<a href="https://github.com/Wikid82/charon/releases"><img src="https://img.shields.io/github/v/release/Wikid82/charon?include_prereleases" alt="Release"></a>
|
||||
<br>
|
||||
<a href="https://codecov.io/gh/Wikid82/Charon" ><img src="https://codecov.io/gh/Wikid82/Charon/branch/main/graph/badge.svg?token=RXSINLQTGE" alt="Code Coverage"/></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
|
||||
<a href="SECURITY.md"><img src="https://img.shields.io/badge/Security-Audited-brightgreen.svg" alt="Security: Audited"></a>
|
||||
<br>
|
||||
<a href="https://github.com/Wikid82/Charon/actions/workflows/e2e-tests-split.yml"><img src="https://github.com/Wikid82/Charon/actions/workflows/e2e-tests-split.yml/badge.svg" alt="E2E Tests"></a>
|
||||
<a href="https://github.com/Wikid82/Charon/actions/workflows/cerberus-integration.yml"><img src="https://github.com/Wikid82/Charon/actions/workflows/cerberus-integration.yml/badge.svg" alt="Cerberus Integration"></a><br>
|
||||
<a href="https://github.com/Wikid82/Charon/actions/workflows/crowdsec-integration.yml"><img src="https://github.com/Wikid82/Charon/actions/workflows/crowdsec-integration.yml/badge.svg" alt="CrowdSec Integration"></a>
|
||||
<a href="https://github.com/Wikid82/Charon/actions/workflows/waf-integration.yml"><img src="https://github.com/Wikid82/Charon/actions/workflows/waf-integration.yml/badge.svg" alt="WAF Integration"></a>
|
||||
<a href="https://github.com/Wikid82/Charon/actions/workflows/rate-limit-integration.yml"><img src="https://github.com/Wikid82/Charon/actions/workflows/rate-limit-integration.yml/badge.svg" alt="Rate Limit Integration"></a>
|
||||
<strong>Your server, your rules—without the headaches.</strong>
|
||||
</p>
|
||||
<br>
|
||||
<p align="center"><strong>Your server, your rules—without the headaches.</strong></p>
|
||||
|
||||
<p align="center">
|
||||
Simply manage multiple websites and self-hosted applications. Click, save, done. No code, no config files, no PhD required.
|
||||
Manage reverse proxies with a clean web interface.<br>
|
||||
No config files. No cryptic syntax. No networking degree required.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://hub.docker.com/r/wikid82/charon">
|
||||
<img src="https://img.shields.io/docker/pulls/wikid82/charon.svg" alt="Docker Pulls">
|
||||
</a>
|
||||
<a href="https://github.com/Wikid82/charon/releases">
|
||||
<img src="https://img.shields.io/github/v/release/Wikid82/charon?include_prereleases" alt="Latest Release">
|
||||
</a>
|
||||
<a href="LICENSE">
|
||||
<img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="MIT License">
|
||||
</a>
|
||||
<a href="https://discord.gg/Tvzg6BQx">
|
||||
<img src="https://img.shields.io/badge/Community-Discord-5865F2?logo=discord&logoColor=white">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## Why Charon?
|
||||
## 🚀 Why Charon?
|
||||
|
||||
You want your apps accessible online. You don't want to become a networking expert first.
|
||||
You want your apps online.
|
||||
|
||||
**The problem:** Managing reverse proxies usually means editing config files, memorizing cryptic syntax, and hoping you didn't break everything.
|
||||
You don’t want to edit config files or memorize reverse proxy syntax.
|
||||
|
||||
**Charon's answer:** A web interface where you click boxes and type domain names. That's it.
|
||||
Charon gives you:
|
||||
|
||||
- ✅ **Your blog** gets a green lock (HTTPS) automatically
|
||||
- ✅ **Your chat server** works without weird port numbers
|
||||
- ✅ **Your admin panel** blocks everyone except you
|
||||
- ✅ **Everything stays up** even when you make changes
|
||||
- ✅ Automatic HTTPS certificates
|
||||
- ✅ Clean domain routing
|
||||
- ✅ Built-in security protection
|
||||
- ✅ One-click Docker app discovery
|
||||
- ✅ Live updates without restarts
|
||||
- ✅ Zero external dependencies
|
||||
|
||||
If you can use a website, you can run Charon.
|
||||
|
||||
---
|
||||
|
||||
## 🐕 Cerberus Security Suite
|
||||
## 🛡 Built-In Security
|
||||
|
||||
### 🕵️♂️ **CrowdSec Integration**
|
||||
Charon includes security features that normally require multiple tools:
|
||||
|
||||
- Protects your applications from attacks using behavior-based detection and automated remediation.
|
||||
- Web Application Firewall (WAF)
|
||||
- CrowdSec intrusion detection
|
||||
- Access Control Lists (ACLs)
|
||||
- Rate limiting
|
||||
- Emergency recovery tools
|
||||
|
||||
### 🔐 **Access Control Lists (ACLs)**
|
||||
Secure by default. No extra containers required.
|
||||
|
||||
- Define fine-grained access rules for your applications, controlling who can access what and under which conditions.
|
||||
|
||||
### 🧱 **Web Application Firewall (WAF)**
|
||||
|
||||
- Protects your applications from common web vulnerabilities such as SQL injection, XSS, and more using Coraza.
|
||||
|
||||
### ⏱️ **Rate Limiting**
|
||||
|
||||
- Protect your applications from abuse by limiting the number of requests a user or IP can make within a certain timeframe.
|
||||
📖 [Learn more about security →](https://wikid82.github.io/charon/security)
|
||||
|
||||
---
|
||||
|
||||
## ✨ Top 10 Features
|
||||
## ⚡ Quick Start (5 Minutes)
|
||||
|
||||
### 1️⃣ Create `docker-compose.yml`
|
||||
|
||||
```yaml
|
||||
services:
|
||||
charon:
|
||||
image: wikid82/charon:latest
|
||||
container_name: charon
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp"
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./charon-data:/app/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
environment:
|
||||
- TZ=America/New_York
|
||||
# Generate with: openssl rand -base64 32
|
||||
- CHARON_ENCRYPTION_KEY=your-32-byte-base64-key
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -fsS http://localhost:8080/api/v1/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
```
|
||||
> **Docker Socket Access:** Charon runs as a non-root user. If you mount the Docker socket for container discovery, the container needs permission to read it. Find your socket's group ID and add it to the compose file:
|
||||
>
|
||||
> ```bash
|
||||
> stat -c '%g' /var/run/docker.sock
|
||||
> ```
|
||||
>
|
||||
> Then add `group_add: ["<gid>"]` under your service (replace `<gid>` with the number from the command above). For example, if the result is `998`:
|
||||
>
|
||||
> ```yaml
|
||||
> group_add:
|
||||
> - "998"
|
||||
> ```
|
||||
|
||||
### 2️⃣ Generate encryption key:
|
||||
```bash
|
||||
openssl rand -base64 32
|
||||
```
|
||||
### 3️⃣ Start Charon:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
### 4️⃣ Access the dashboard:
|
||||
Open your browser and navigate to `http://localhost:8080` to access the dashboard and create your admin account.
|
||||
```code
|
||||
http://localhost:8080
|
||||
```
|
||||
### Getting Started:
|
||||
Full setup instructions and documentation are available at [https://wikid82.github.io/Charon/docs/getting-started.html](https://wikid82.github.io/Charon/docs/getting-started.html).
|
||||
|
||||
|
||||
--- ## ✨ Top 10 Features
|
||||
|
||||
### 🎯 **Point & Click Management**
|
||||
|
||||
No config files. No terminal commands. Just click, type your domain name, and you're live. If you can use a website, you can run Charon.
|
||||
|
||||
### 🔐 **Automatic HTTPS Certificates**
|
||||
@@ -78,7 +135,7 @@ Free SSL certificates that request, install, and renew themselves. Your sites ge
|
||||
|
||||
### 🌐 **DNS Challenge for Wildcard Certificates**
|
||||
|
||||
Secure all your subdomains with a single `*.example.com` certificate. Supports 15+ DNS providers including Cloudflare, Route53, DigitalOcean, and Google Cloud DNS. Credentials are encrypted and automatically rotated.
|
||||
Secure all your subdomains with a single *.example.com certificate. Supports 15+ DNS providers including Cloudflare, Route53, DigitalOcean, and Google Cloud DNS. Credentials are encrypted and automatically rotated.
|
||||
|
||||
### 🛡️ **Enterprise-Grade Security Built In**
|
||||
|
||||
@@ -102,15 +159,13 @@ See exactly what's happening with live request logs, uptime monitoring, and inst
|
||||
|
||||
### 📥 **Migration Made Easy**
|
||||
|
||||
Import your existing configurations with one click:
|
||||
Already invested in another reverse proxy? Bring your work with you by importing your existing configurations with one click:
|
||||
- **Caddyfile** — Migrate from other Caddy setups
|
||||
- **Nginx** — Import from Nginx based configurations (Coming Soon)
|
||||
- **Traefik** - Import from Traefik based configurations (Coming Soon)
|
||||
- **CrowdSec** - Import from CrowdSec configurations (WIP)
|
||||
- **CrowdSec** - Import from CrowdSec configurations
|
||||
- **JSON Import** — Restore from Charon backups or generic JSON configs
|
||||
|
||||
Already invested in another reverse proxy? Bring your work with you.
|
||||
|
||||
### ⚡ **Live Configuration Changes**
|
||||
|
||||
Update domains, add security rules, or modify settings instantly—no container restarts needed.* Your sites stay up while you make changes.
|
||||
@@ -125,498 +180,22 @@ One Docker container. No databases to install. No external services required. No
|
||||
|
||||
### 💯 **100% Free & Open Source**
|
||||
|
||||
No premium tiers. No feature paywalls. No usage limits. Everything you see is yours to use, forever, backed by the MIT license.
|
||||
No premium tiers. No feature paywalls. No usage limits. Everything you see is yours to use, forever, backed by the MIT license. <sup>* Note: Initial security engine setup (CrowdSec) requires a one-time container restart to initialize the protection layer. All subsequent changes happen live.</sup> **
|
||||
|
||||
<sup>* Note: Initial security engine setup (CrowdSec) requires a one-time container restart to initialize the protection layer. All subsequent changes happen live.</sup>
|
||||
[Explore All Features →](https://github.com/Wikid82/Charon/blob/main/docs/features.md)**
|
||||
|
||||
**[Explore All Features →](https://wikid82.github.io/charon/features)**
|
||||
---
|
||||
💬 Support
|
||||
<p align="center"> <a href="https://github.com/Wikid82/Charon/issues">
|
||||
<img alt="GitHub issues"
|
||||
src="https://img.shields.io/github/issues/Wikid82/Charon"><a href="https://github.com/Wikid82/Charon/issues/new/choose"> <img src="https://img.shields.io/badge/Support-Open%20Issue-blue?logo=github"> </a> <a href="https://discord.gg/Tvzg6BQx"> <img src="https://img.shields.io/badge/Community-Discord-5865F2?logo=discord&logoColor=white"> </a> </p>
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
❤️ Free & Open Source
|
||||
|
||||
### Container Registries
|
||||
Charon is 100% free and open source under the MIT License.
|
||||
|
||||
Charon is available from two container registries:
|
||||
No premium tiers. No locked features. No usage limits.
|
||||
|
||||
**Docker Hub (Recommended):**
|
||||
|
||||
```bash
|
||||
docker pull wikid82/charon:latest
|
||||
```
|
||||
|
||||
**GitHub Container Registry:**
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/wikid82/charon:latest
|
||||
```
|
||||
|
||||
### Docker Compose (Recommended)
|
||||
|
||||
Save this as `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
charon:
|
||||
# Docker Hub (recommended)
|
||||
image: wikid82/charon:latest
|
||||
# Alternative: GitHub Container Registry
|
||||
# image: ghcr.io/wikid82/charon:latest
|
||||
container_name: charon
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp"
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./charon-data:/app/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
environment:
|
||||
- CHARON_ENV=production
|
||||
# Generate with: openssl rand -base64 32
|
||||
- CHARON_ENCRYPTION_KEY=your-32-byte-base64-key-here
|
||||
|
||||
```
|
||||
|
||||
**Using Nightly Builds:**
|
||||
|
||||
To test the latest nightly build (automated daily at 02:00 UTC):
|
||||
|
||||
```yaml
|
||||
services:
|
||||
charon:
|
||||
# Docker Hub
|
||||
image: wikid82/charon:nightly
|
||||
# Alternative: GitHub Container Registry
|
||||
# image: ghcr.io/wikid82/charon:nightly
|
||||
# ... rest of configuration
|
||||
```
|
||||
|
||||
> **Note:** Nightly builds are for testing and may contain experimental features. Use `latest` for production.
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Docker Run (One-Liner)
|
||||
|
||||
**Stable Release (Docker Hub):**
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name charon \
|
||||
-p 80:80 \
|
||||
-p 443:443 \
|
||||
-p 443:443/udp \
|
||||
-p 8080:8080 \
|
||||
-v ./charon-data:/app/data \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||
-e CHARON_ENV=production \
|
||||
-e CHARON_ENCRYPTION_KEY=your-32-byte-base64-key-here \
|
||||
wikid82/charon:latest
|
||||
```
|
||||
|
||||
**Stable Release (GitHub Container Registry):**
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name charon \
|
||||
-p 80:80 \
|
||||
-p 443:443 \
|
||||
-p 443:443/udp \
|
||||
-p 8080:8080 \
|
||||
-v ./charon-data:/app/data \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||
-e CHARON_ENV=production \
|
||||
-e CHARON_ENCRYPTION_KEY=your-32-byte-base64-key-here \
|
||||
ghcr.io/wikid82/charon:latest
|
||||
```
|
||||
|
||||
**Nightly Build (Testing - Docker Hub):**
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name charon \
|
||||
-p 80:80 \
|
||||
-p 443:443 \
|
||||
-p 443:443/udp \
|
||||
-p 8080:8080 \
|
||||
-v ./charon-data:/app/data \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock:ro \
|
||||
-e CHARON_ENV=production \
|
||||
-e CHARON_ENCRYPTION_KEY=your-32-byte-base64-key-here \
|
||||
wikid82/charon:nightly
|
||||
```
|
||||
|
||||
> **Note:** Nightly builds include the latest development features and are rebuilt daily at 02:00 UTC. Use for testing only. Also available via GHCR: `ghcr.io/wikid82/charon:nightly`
|
||||
|
||||
### What Just Happened?
|
||||
|
||||
1. Charon downloaded and started
|
||||
2. The web interface opened on port 8080
|
||||
3. Your websites will use ports 80 (HTTP) and 443 (HTTPS)
|
||||
|
||||
**Open <http://localhost:8080>** and start adding your websites!
|
||||
|
||||
### Requirements
|
||||
|
||||
**Server:**
|
||||
|
||||
- Docker 20.10+ or Docker Compose V2
|
||||
- Linux, macOS, or Windows with WSL2
|
||||
|
||||
**Browser:**
|
||||
|
||||
- Tested with React 19.2.3
|
||||
- Compatible with modern browsers:
|
||||
- Chrome/Edge 90+
|
||||
- Firefox 88+
|
||||
- Safari 14+
|
||||
- Opera 76+
|
||||
|
||||
> **Note:** If you encounter errors after upgrading, try a hard refresh (`Ctrl+Shift+R`) or clearing your browser cache. See [Troubleshooting Guide](docs/troubleshooting/react-production-errors.md) for details.
|
||||
|
||||
### Development Setup
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- **go 1.26.0+** — Download from [go.dev/dl](https://go.dev/dl/)
|
||||
- **Node.js 20+** and npm
|
||||
- Docker 20.10+
|
||||
|
||||
**Install golangci-lint** (for contributors): `go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest`
|
||||
|
||||
**GORM Security Scanner:** Charon includes an automated security scanner that detects GORM vulnerabilities (ID leaks, exposed secrets, DTO embedding issues). Runs automatically in CI on all PRs. Run locally via:
|
||||
|
||||
```bash
|
||||
# VS Code: Command Palette → "Lint: GORM Security Scan"
|
||||
# Or via pre-commit:
|
||||
pre-commit run --hook-stage manual gorm-security-scan --all-files
|
||||
# Or directly:
|
||||
./scripts/scan-gorm-security.sh --report
|
||||
```
|
||||
|
||||
See [GORM Security Scanner Documentation](docs/implementation/gorm_security_scanner_complete.md) for details.
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for complete development environment setup.
|
||||
|
||||
**Note:** GitHub Actions CI uses `GOTOOLCHAIN: auto` to automatically download and use go 1.26.0, even if your system has an older version installed. For local development, ensure you have go 1.26.0+ installed.
|
||||
|
||||
#### Keeping Go Tools Up-to-Date
|
||||
|
||||
After pulling a Go version update:
|
||||
|
||||
```bash
|
||||
# Rebuild all Go development tools
|
||||
./scripts/rebuild-go-tools.sh
|
||||
```
|
||||
|
||||
**Why?** Tools like golangci-lint are compiled programs. When Go upgrades, they need to be recompiled to work with the new version. This one command rebuilds all your tools automatically.
|
||||
|
||||
See [Go Version Upgrades Guide](docs/development/go_version_upgrades.md) for details.
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
Before running Charon or E2E tests, configure required environment variables:
|
||||
|
||||
1. **Copy the example environment file:**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. **Configure required secrets:**
|
||||
```bash
|
||||
# Generate encryption key (32 bytes, base64-encoded)
|
||||
openssl rand -base64 32
|
||||
|
||||
# Generate emergency token (64 characters hex)
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
3. **Add to `.env` file:**
|
||||
```bash
|
||||
CHARON_ENCRYPTION_KEY=<paste_encryption_key_here>
|
||||
CHARON_EMERGENCY_TOKEN=<paste_emergency_token_here>
|
||||
```
|
||||
|
||||
4. **Verify configuration:**
|
||||
```bash
|
||||
# Encryption key should be ~44 chars (base64)
|
||||
grep CHARON_ENCRYPTION_KEY .env | cut -d= -f2 | wc -c
|
||||
|
||||
# Emergency token should be 64 chars (hex)
|
||||
grep CHARON_EMERGENCY_TOKEN .env | cut -d= -f2 | wc -c
|
||||
```
|
||||
|
||||
⚠️ **Security:** Never commit actual secret values to the repository. The `.env` file is gitignored.
|
||||
|
||||
📖 **More Info:** See [Getting Started Guide](docs/getting-started.md) for detailed setup instructions.
|
||||
|
||||
### Upgrading? Run Migrations
|
||||
|
||||
If you're upgrading from a previous version with persistent data:
|
||||
|
||||
```bash
|
||||
docker exec charon /app/charon migrate
|
||||
docker restart charon
|
||||
```
|
||||
|
||||
This ensures security features (especially CrowdSec) work correctly.
|
||||
|
||||
**Important:** If you had CrowdSec enabled before the upgrade, it will **automatically restart** after migration. You don't need to manually re-enable it via the GUI. See [Migration Guide](https://wikid82.github.io/charon/migration-guide) for details.
|
||||
|
||||
---
|
||||
|
||||
## 🔔 Smart Notifications
|
||||
|
||||
Stay informed about your infrastructure with flexible notification support.
|
||||
|
||||
### Supported Services
|
||||
|
||||
Charon integrates with popular notification platforms using JSON templates for rich formatting:
|
||||
|
||||
- **Discord** — Rich embeds with colors, fields, and custom formatting
|
||||
- **Slack** — Block Kit messages with interactive elements
|
||||
- **Gotify** — Self-hosted push notifications with priority levels
|
||||
- **Telegram** — Instant messaging with Markdown support
|
||||
- **Generic Webhooks** — Connect to any service with custom JSON payloads
|
||||
|
||||
### JSON Template Examples
|
||||
|
||||
**Discord Rich Embed:**
|
||||
|
||||
```json
|
||||
{
|
||||
"embeds": [{
|
||||
"title": "🚨 {{.Title}}",
|
||||
"description": "{{.Message}}",
|
||||
"color": 15158332,
|
||||
"timestamp": "{{.Timestamp}}",
|
||||
"fields": [
|
||||
{"name": "Host", "value": "{{.HostName}}", "inline": true},
|
||||
{"name": "Event", "value": "{{.EventType}}", "inline": true}
|
||||
]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
**Slack Block Kit:**
|
||||
|
||||
```json
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"type": "header",
|
||||
"text": {"type": "plain_text", "text": "🔔 {{.Title}}"}
|
||||
},
|
||||
{
|
||||
"type": "section",
|
||||
"text": {"type": "mrkdwn", "text": "*Event:* {{.EventType}}\n*Message:* {{.Message}}"}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Available Template Variables
|
||||
|
||||
All JSON templates support these variables:
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `{{.Title}}` | Event title | "SSL Certificate Renewed" |
|
||||
| `{{.Message}}` | Event details | "Certificate for example.com renewed" |
|
||||
| `{{.EventType}}` | Type of event | "ssl_renewal", "uptime_down" |
|
||||
| `{{.Severity}}` | Severity level | "info", "warning", "error" |
|
||||
| `{{.HostName}}` | Affected host | "example.com" |
|
||||
| `{{.Timestamp}}` | ISO 8601 timestamp | "2025-12-24T10:30:00Z" |
|
||||
|
||||
**[📖 Complete Notification Guide →](docs/features/notifications.md)**
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Emergency Break Glass Access
|
||||
|
||||
Charon provides a **3-Tier Break Glass Protocol** for emergency lockout recovery when security modules (ACL, WAF, CrowdSec) block access to the admin interface.
|
||||
|
||||
### Emergency Recovery Quick Reference
|
||||
|
||||
**Tier 1 (Preferred):** Use emergency token via main endpoint
|
||||
|
||||
```bash
|
||||
curl -X POST https://charon.example.com/api/v1/emergency/security-reset \
|
||||
-H "X-Emergency-Token: $CHARON_EMERGENCY_TOKEN"
|
||||
```
|
||||
|
||||
**Tier 2 (If Tier 1 blocked):** Use emergency server via SSH tunnel
|
||||
|
||||
```bash
|
||||
ssh -L 2019:localhost:2019 admin@server
|
||||
curl -X POST http://localhost:2019/emergency/security-reset \
|
||||
-H "X-Emergency-Token: $CHARON_EMERGENCY_TOKEN" \
|
||||
-u admin:password
|
||||
```
|
||||
|
||||
**Tier 3 (Catastrophic):** Direct SSH access - see [Emergency Runbook](docs/runbooks/emergency-lockout-recovery.md)
|
||||
|
||||
### Tier 1: Emergency Token (Layer 7 Bypass)
|
||||
|
||||
**Use when:** The application is accessible but security middleware is blocking you.
|
||||
|
||||
```bash
|
||||
# Set emergency token (generate with: openssl rand -hex 32)
|
||||
export CHARON_EMERGENCY_TOKEN=your-64-char-hex-token
|
||||
|
||||
# Use token to disable security
|
||||
curl -X POST https://charon.example.com/api/v1/emergency/security-reset \
|
||||
-H "X-Emergency-Token: $CHARON_EMERGENCY_TOKEN"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "All security modules have been disabled",
|
||||
"disabled_modules": [
|
||||
"feature.cerberus.enabled",
|
||||
"security.acl.enabled",
|
||||
"security.waf.enabled",
|
||||
"security.rate_limit.enabled",
|
||||
"security.crowdsec.enabled"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Tier 2: Emergency Server (Sidecar Port)
|
||||
|
||||
**Use when:** Caddy/CrowdSec is blocking at the reverse proxy level, or you need a separate entry point.
|
||||
|
||||
**Prerequisites:**
|
||||
|
||||
- Emergency server enabled in configuration
|
||||
- SSH access to Docker host
|
||||
- Knowledge of Basic Auth credentials (if configured)
|
||||
|
||||
**Setup:**
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
environment:
|
||||
- CHARON_EMERGENCY_SERVER_ENABLED=true
|
||||
- CHARON_EMERGENCY_BIND=127.0.0.1:2019 # Localhost only
|
||||
- CHARON_EMERGENCY_USERNAME=admin
|
||||
- CHARON_EMERGENCY_PASSWORD=your-strong-password
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
|
||||
```bash
|
||||
# 1. SSH to server and create tunnel
|
||||
ssh -L 2019:localhost:2019 admin@server.example.com
|
||||
|
||||
# 2. Access emergency endpoint (from local machine)
|
||||
curl -X POST http://localhost:2019/emergency/security-reset \
|
||||
-H "X-Emergency-Token: $CHARON_EMERGENCY_TOKEN" \
|
||||
-u admin:your-strong-password
|
||||
```
|
||||
|
||||
### Tier 3: Direct System Access (Physical Key)
|
||||
|
||||
**Use when:** All application-level recovery methods have failed.
|
||||
|
||||
**Prerequisites:**
|
||||
|
||||
- SSH or console access to Docker host
|
||||
- Root or sudo privileges
|
||||
- Knowledge of container name
|
||||
|
||||
**Emergency Procedures:**
|
||||
|
||||
```bash
|
||||
# SSH to host
|
||||
ssh admin@docker-host.example.com
|
||||
|
||||
# Clear CrowdSec bans
|
||||
docker exec charon cscli decisions delete --all
|
||||
|
||||
# Disable security via database
|
||||
docker exec charon sqlite3 /app/data/charon.db \
|
||||
"UPDATE settings SET value='false' WHERE key LIKE 'security.%.enabled';"
|
||||
|
||||
# Restart container
|
||||
docker restart charon
|
||||
```
|
||||
|
||||
### When to Use Each Tier
|
||||
|
||||
| Scenario | Tier | Solution |
|
||||
|----------|------|----------|
|
||||
| ACL blocked your IP | Tier 1 | Emergency token via main port |
|
||||
| Caddy/CrowdSec blocking at Layer 7 | Tier 2 | Emergency server on separate port |
|
||||
| Complete system failure | Tier 3 | Direct SSH + database access |
|
||||
|
||||
### Security Considerations
|
||||
|
||||
**⚠️ Emergency Server Security:**
|
||||
|
||||
- The emergency server should **NEVER** be exposed to the public internet
|
||||
- Always bind to localhost (127.0.0.1) only
|
||||
- Use SSH tunneling or VPN access to reach the port
|
||||
- Optional Basic Auth provides defense in depth
|
||||
- Port 2019 should be blocked by firewall rules from public access
|
||||
|
||||
**🔐 Emergency Token Security:**
|
||||
|
||||
- Store token in secrets manager (Vault, AWS Secrets Manager, Azure Key Vault)
|
||||
- Rotate token every 90 days or after use
|
||||
- Never commit token to version control
|
||||
- Use HTTPS when calling emergency endpoint (HTTP leaks token)
|
||||
- Monitor audit logs for emergency token usage
|
||||
|
||||
**<2A> API Key & Credential Management:**
|
||||
|
||||
- **Never log sensitive credentials**: Charon automatically masks API keys in logs (e.g., `abcd...xyz9`)
|
||||
- **Secure storage**: CrowdSec API keys stored with 0600 permissions (owner read/write only)
|
||||
- **No HTTP exposure**: API keys never returned in API responses
|
||||
- **No cookie storage**: Keys never stored in browser cookies
|
||||
- **Regular rotation**: Rotate CrowdSec bouncer keys every 90 days (recommended)
|
||||
- **Environment variables**: Use `CHARON_SECURITY_CROWDSEC_API_KEY` for production deployments
|
||||
- **Compliance**: Implementation addresses CWE-312, CWE-315, CWE-359 (GDPR, PCI-DSS, SOC 2)
|
||||
|
||||
For detailed security practices, see:
|
||||
- 📘 [API Key Handling Guide](docs/security/api-key-handling.md)
|
||||
- 🛡️ [Security Best Practices](docs/SECURITY_PRACTICES.md)
|
||||
|
||||
**<2A>📍 Management Network Configuration:**
|
||||
|
||||
```yaml
|
||||
# Restrict emergency access to trusted networks only
|
||||
environment:
|
||||
- CHARON_MANAGEMENT_CIDRS=10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
|
||||
```
|
||||
|
||||
Default: RFC1918 private networks + localhost
|
||||
|
||||
### Complete Documentation
|
||||
|
||||
📖 **[Emergency Lockout Recovery Runbook](docs/runbooks/emergency-lockout-recovery.md)** — Complete procedures for all 3 tiers
|
||||
🔄 **[Emergency Token Rotation Guide](docs/runbooks/emergency-token-rotation.md)** — Token rotation procedures
|
||||
⚙️ **[Configuration Examples](docs/configuration/emergency-setup.md)** — Docker Compose and secrets manager integration
|
||||
🛡️ **[Security Documentation](docs/security.md)** — Break glass protocol architecture
|
||||
|
||||
---
|
||||
|
||||
## Getting Help
|
||||
|
||||
**[📖 Full Documentation](https://wikid82.github.io/charon/)** — Everything explained simply
|
||||
**[🚀 5-Minute Guide](https://wikid82.github.io/charon/getting-started)** — Your first website up and running
|
||||
**[🔐 Supply Chain Security](docs/guides/supply-chain-security-user-guide.md)** — Verify signatures and build provenance
|
||||
**[<EFBFBD> Maintenance](docs/maintenance/)** — Keeping Charon running smoothly
|
||||
**[<EFBFBD>🛠️ Troubleshooting](docs/troubleshooting/)** — Common issues and solutions
|
||||
**[💬 Ask Questions](https://github.com/Wikid82/charon/discussions)** — Friendly community help
|
||||
**[🐛 Report Problems](https://github.com/Wikid82/charon/issues)** — Something broken? Let us know
|
||||
|
||||
---
|
||||
Built for the self-hosting community.
|
||||
|
||||
779
SECURITY.md
779
SECURITY.md
@@ -11,61 +11,278 @@ We release security updates for the following versions:
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
We take security seriously. If you discover a security vulnerability in Charon, please report it responsibly.
|
||||
To report a security issue, use
|
||||
[GitHub Private Security Advisories](https://github.com/Wikid82/charon/security/advisories/new)
|
||||
or open a [GitHub Issue](https://github.com/Wikid82/Charon/issues) for non-sensitive disclosures.
|
||||
|
||||
### Where to Report
|
||||
Please include a description, reproduction steps, impact assessment, and a non-destructive proof of
|
||||
concept where possible.
|
||||
|
||||
**Preferred Method**: GitHub Security Advisory (Private)
|
||||
We will acknowledge your report within **48 hours** and provide a remediation timeline within
|
||||
**7 days**. Reporters are credited in release notes with their consent. We do not pursue legal
|
||||
action against good-faith security researchers. Please allow **90 days** from initial report before
|
||||
public disclosure.
|
||||
|
||||
1. Go to <https://github.com/Wikid82/charon/security/advisories/new>
|
||||
2. Fill out the advisory form with:
|
||||
- Vulnerability description
|
||||
- Steps to reproduce
|
||||
- Proof of concept (non-destructive)
|
||||
- Impact assessment
|
||||
- Suggested fix (if applicable)
|
||||
---
|
||||
|
||||
**Alternative Method**: Email
|
||||
## Known Vulnerabilities
|
||||
|
||||
- Send to: `security@charon.dev` (if configured)
|
||||
- Use PGP encryption (key available below, if applicable)
|
||||
- Include same information as GitHub advisory
|
||||
### [CRITICAL] CVE-2025-68121 · Go Stdlib Critical in CrowdSec Bundled Binaries
|
||||
|
||||
### What to Include
|
||||
| Field | Value |
|
||||
|--------------|-------|
|
||||
| **ID** | CVE-2025-68121 (see also CHARON-2025-001) |
|
||||
| **Severity** | Critical |
|
||||
| **Status** | Awaiting Upstream |
|
||||
|
||||
Please provide:
|
||||
**What**
|
||||
A critical Go standard library vulnerability affects CrowdSec binaries bundled in the Charon
|
||||
container image. The binaries were compiled against Go 1.25.6, which contains this flaw.
|
||||
Charon's own application code, compiled with Go 1.26.1, is unaffected.
|
||||
|
||||
1. **Description**: Clear explanation of the vulnerability
|
||||
2. **Reproduction Steps**: Detailed steps to reproduce the issue
|
||||
3. **Impact Assessment**: What an attacker could do with this vulnerability
|
||||
4. **Environment**: Charon version, deployment method, OS, etc.
|
||||
5. **Proof of Concept**: Code or commands demonstrating the vulnerability (non-destructive)
|
||||
6. **Suggested Fix**: If you have ideas for remediation
|
||||
**Who**
|
||||
- Discovered by: Automated scan (Grype)
|
||||
- Reported: 2026-03-20
|
||||
- Affects: CrowdSec Agent component within the container; not directly exposed through Charon's
|
||||
primary application interface
|
||||
|
||||
### What Happens Next
|
||||
**Where**
|
||||
- Component: CrowdSec Agent (bundled `cscli` and `crowdsec` binaries)
|
||||
- Versions affected: Charon container images with CrowdSec binaries compiled against Go < 1.25.7
|
||||
|
||||
1. **Acknowledgment**: We'll acknowledge your report within **48 hours**
|
||||
2. **Investigation**: We'll investigate and assess the severity
|
||||
3. **Updates**: We'll provide regular status updates (weekly minimum)
|
||||
4. **Fix Development**: We'll develop and test a fix
|
||||
5. **Disclosure**: Coordinated disclosure after fix is released
|
||||
6. **Credit**: We'll credit you in release notes (if desired)
|
||||
**When**
|
||||
- Discovered: 2026-03-20
|
||||
- Disclosed (if public): Not yet publicly disclosed
|
||||
- Target fix: When `golang:1.26.2-alpine` is published on Docker Hub
|
||||
|
||||
### Responsible Disclosure
|
||||
**How**
|
||||
The vulnerability resides entirely within CrowdSec's compiled binary artifacts. Exploitation
|
||||
is limited to the CrowdSec agent's internal execution paths, which are not externally exposed
|
||||
through Charon's API or network interface.
|
||||
|
||||
We ask that you:
|
||||
**Planned Remediation**
|
||||
`golang:1.26.2-alpine` is not yet available on Docker Hub. The `GO_VERSION` ARG has been
|
||||
reverted to `1.26.1` (the latest published image) until `1.26.2` is released. Once
|
||||
`golang:1.26.2-alpine` is available, bumping `GO_VERSION` to `1.26.2` and rebuilding the image
|
||||
will also resolve CVE-2026-25679 (High) and CVE-2025-61732 (High) tracked under CHARON-2025-001.
|
||||
|
||||
- ✅ Give us reasonable time to fix the issue before public disclosure (90 days preferred)
|
||||
- ✅ Avoid destructive testing or attacks on production systems
|
||||
- ✅ Not access, modify, or delete data that doesn't belong to you
|
||||
- ✅ Not perform actions that could degrade service for others
|
||||
---
|
||||
|
||||
We commit to:
|
||||
### [HIGH] CVE-2026-2673 · OpenSSL TLS 1.3 Key Exchange Group Downgrade
|
||||
|
||||
- ✅ Respond to your report within 48 hours
|
||||
- ✅ Provide regular status updates
|
||||
- ✅ Credit you in release notes (if desired)
|
||||
- ✅ Not pursue legal action for good-faith security research
|
||||
| Field | Value |
|
||||
|--------------|-------|
|
||||
| **ID** | CVE-2026-2673 (affects `libcrypto3` and `libssl3`) |
|
||||
| **Severity** | High · 7.5 |
|
||||
| **Status** | Awaiting Upstream |
|
||||
|
||||
**What**
|
||||
An OpenSSL TLS 1.3 server may fail to negotiate the intended key exchange group when the
|
||||
configuration includes the `DEFAULT` keyword, potentially allowing downgrade to weaker cipher
|
||||
suites. Affects Alpine 3.23.3 packages `libcrypto3` and `libssl3` at version 3.5.5-r0.
|
||||
|
||||
**Who**
|
||||
- Discovered by: Automated scan (Grype)
|
||||
- Reported: 2026-03-20
|
||||
- Affects: Container runtime environment; Caddy reverse proxy TLS negotiation could be affected
|
||||
if default key group configuration is used
|
||||
|
||||
**Where**
|
||||
- Component: Alpine 3.23.3 base image (`libcrypto3` 3.5.5-r0, `libssl3` 3.5.5-r0)
|
||||
- Versions affected: Alpine 3.23.3 prior to a patched `openssl` APK release
|
||||
|
||||
**When**
|
||||
- Discovered: 2026-03-20
|
||||
- Disclosed (if public): 2026-03-13 (OpenSSL advisory)
|
||||
- Target fix: When Alpine Security publishes a patched `openssl` APK
|
||||
|
||||
**How**
|
||||
When an OpenSSL TLS 1.3 server configuration uses the `DEFAULT` keyword for key exchange groups,
|
||||
the negotiation logic may select a weaker group than intended. Charon's Caddy TLS configuration
|
||||
does not use the `DEFAULT` keyword, which limits practical exploitability. The packages are
|
||||
present in the base image regardless of Caddy's configuration.
|
||||
|
||||
**Planned Remediation**
|
||||
Monitor https://security.alpinelinux.org/vuln/CVE-2026-2673 for a patched Alpine APK. Once
|
||||
available, update the pinned `ALPINE_IMAGE` digest in the Dockerfile, or add an explicit
|
||||
`RUN apk upgrade --no-cache libcrypto3 libssl3` to the runtime stage.
|
||||
|
||||
---
|
||||
|
||||
### [HIGH] CHARON-2025-001 · CrowdSec Bundled Binaries — Go Stdlib CVEs
|
||||
|
||||
| Field | Value |
|
||||
|--------------|-------|
|
||||
| **ID** | CHARON-2025-001 (aliases: CVE-2025-58183, CVE-2025-58186, CVE-2025-58187, CVE-2025-61729, CVE-2026-25679, CVE-2025-61732, CVE-2026-27142, CVE-2026-27139) |
|
||||
| **Severity** | High · (preliminary, CVSS scores pending upstream confirmation) |
|
||||
| **Status** | Awaiting Upstream |
|
||||
|
||||
**What**
|
||||
Multiple CVEs in Go standard library packages continue to accumulate in CrowdSec binaries bundled
|
||||
with Charon. The cluster originated when CrowdSec was compiled against Go 1.25.1; subsequent
|
||||
CrowdSec updates advanced the toolchain to Go 1.25.6/1.25.7, resolving earlier CVEs but
|
||||
introducing new ones. The cluster now includes a Critical-severity finding (CVE-2025-68121,
|
||||
tracked separately above). All issues resolve when CrowdSec is rebuilt against Go ≥ 1.26.2.
|
||||
Charon's own application code is unaffected.
|
||||
|
||||
**Who**
|
||||
- Discovered by: Automated scan (Trivy, Grype)
|
||||
- Reported: 2025-12-01 (original cluster); expanded 2026-03-20
|
||||
- Affects: CrowdSec Agent component within the container; not directly exposed through Charon's
|
||||
primary application interface
|
||||
|
||||
**Where**
|
||||
- Component: CrowdSec Agent (bundled `cscli` and `crowdsec` binaries)
|
||||
- Versions affected: All Charon versions shipping CrowdSec binaries compiled against Go < 1.26.2
|
||||
|
||||
**When**
|
||||
- Discovered: 2025-12-01
|
||||
- Disclosed (if public): Not yet publicly disclosed
|
||||
- Target fix: When `golang:1.26.2-alpine` is published on Docker Hub
|
||||
|
||||
**How**
|
||||
The CVEs reside entirely within CrowdSec's compiled binaries and cover HTTP/2, TLS, and archive
|
||||
processing paths that are not invoked by Charon's core application logic. The relevant network
|
||||
interfaces are not externally exposed via Charon's API surface.
|
||||
|
||||
**Planned Remediation**
|
||||
`golang:1.26.2-alpine` is not yet available on Docker Hub. The `GO_VERSION` ARG has been
|
||||
reverted to `1.26.1` (the latest published image) until `1.26.2` is released. Once available,
|
||||
bumping `GO_VERSION` to `1.26.2` and rebuilding the image will resolve the entire alias cluster.
|
||||
CVE-2025-68121 (Critical severity, same root cause) is tracked separately above.
|
||||
|
||||
---
|
||||
|
||||
### [MEDIUM] CVE-2026-27171 · zlib CPU Exhaustion via Infinite Loop in CRC Combine Functions
|
||||
|
||||
| Field | Value |
|
||||
|--------------|-------|
|
||||
| **ID** | CVE-2026-27171 |
|
||||
| **Severity** | Medium · 5.5 (NVD) / 2.9 (MITRE) |
|
||||
| **Status** | Awaiting Upstream |
|
||||
|
||||
**What**
|
||||
zlib before 1.3.2 allows unbounded CPU consumption (denial of service) via the `crc32_combine64`
|
||||
and `crc32_combine_gen64` functions. An internal helper `x2nmodp` performs right-shifts inside a
|
||||
loop with no termination condition when given a specially crafted input, causing a CPU spin
|
||||
(CWE-1284).
|
||||
|
||||
**Who**
|
||||
- Discovered by: 7aSecurity audit (commissioned by OSTIF)
|
||||
- Reported: 2026-02-17
|
||||
- Affects: Any component in the container that calls `crc32_combine`-family functions with
|
||||
attacker-controlled input; not directly exposed through Charon's application interface
|
||||
|
||||
**Where**
|
||||
- Component: Alpine 3.23.3 base image (`zlib` package, version 1.3.1-r2)
|
||||
- Versions affected: zlib < 1.3.2; all current Charon images using Alpine 3.23.3
|
||||
|
||||
**When**
|
||||
- Discovered: 2026-02-17 (NVD published 2026-02-17)
|
||||
- Disclosed (if public): 2026-02-17
|
||||
- Target fix: When Alpine 3.23 publishes a patched `zlib` APK (requires zlib 1.3.2)
|
||||
|
||||
**How**
|
||||
Exploitation requires local access (CVSS vector `AV:L`) and the ability to pass a crafted value
|
||||
to the `crc32_combine`-family functions. This code path is not invoked by Charon's reverse proxy
|
||||
or backend API. The vulnerability is non-blocking under the project's CI severity policy.
|
||||
|
||||
**Planned Remediation**
|
||||
Monitor https://security.alpinelinux.org/vuln/CVE-2026-27171 for a patched Alpine APK. Once
|
||||
available, update the pinned `ALPINE_IMAGE` digest in the Dockerfile, or add an explicit
|
||||
`RUN apk upgrade --no-cache zlib` to the runtime stage. Remove the `.trivyignore` entry at
|
||||
that time.
|
||||
|
||||
---
|
||||
|
||||
## Patched Vulnerabilities
|
||||
|
||||
### ✅ [HIGH] CHARON-2026-001 · Debian Base Image CVE Cluster
|
||||
|
||||
| Field | Value |
|
||||
|--------------|-------|
|
||||
| **ID** | CHARON-2026-001 (aliases: CVE-2026-0861, CVE-2025-15281, CVE-2026-0915, CVE-2025-13151, and 2 libtiff HIGH CVEs) |
|
||||
| **Severity** | High · 8.4 (highest per CVSS v3.1) |
|
||||
| **Patched** | 2026-03-20 (Alpine base image migration complete) |
|
||||
|
||||
**What**
|
||||
Seven HIGH-severity CVEs in Debian Trixie base image system libraries (`glibc`, `libtasn1-6`,
|
||||
`libtiff`). These vulnerabilities resided in the container's OS-level packages with no fixes
|
||||
available from the Debian Security Team.
|
||||
|
||||
**Who**
|
||||
- Discovered by: Automated scan (Trivy)
|
||||
- Reported: 2026-02-04
|
||||
|
||||
**Where**
|
||||
- Component: Debian Trixie base image (`libc6`, `libc-bin`, `libtasn1-6`, `libtiff`)
|
||||
- Versions affected: Charon container images built on Debian Trixie base (prior to Alpine migration)
|
||||
|
||||
**When**
|
||||
- Discovered: 2026-02-04
|
||||
- Patched: 2026-03-20
|
||||
- Time to patch: 44 days
|
||||
|
||||
**How**
|
||||
The affected packages were OS-level shared libraries bundled in the Debian Trixie container base
|
||||
image. Exploitation would have required local container access or a prior application-level
|
||||
compromise. Caddy reverse proxy ingress filtering and container isolation significantly reduced
|
||||
the effective attack surface throughout the exposure window.
|
||||
|
||||
**Resolution**
|
||||
Reverted to Alpine Linux base image (Alpine 3.23.3). Alpine's patch of CVE-2025-60876 (busybox
|
||||
heap overflow) removed the original blocker for the Alpine migration. Post-migration scan
|
||||
confirmed zero HIGH/CRITICAL CVEs from this cluster.
|
||||
|
||||
- Spec: [docs/plans/alpine_migration_spec.md](docs/plans/alpine_migration_spec.md)
|
||||
- Advisory: [docs/security/advisory_2026-02-04_debian_cves_temporary.md](docs/security/advisory_2026-02-04_debian_cves_temporary.md)
|
||||
|
||||
**Credit**
|
||||
Internal remediation; no external reporter.
|
||||
|
||||
---
|
||||
|
||||
### ✅ [HIGH] CVE-2025-68156 · expr-lang/expr ReDoS
|
||||
|
||||
| Field | Value |
|
||||
|--------------|-------|
|
||||
| **ID** | CVE-2025-68156 |
|
||||
| **Severity** | High · 7.5 |
|
||||
| **Patched** | 2026-01-11 |
|
||||
|
||||
**What**
|
||||
Regular Expression Denial of Service (ReDoS) vulnerability in the `expr-lang/expr` library used
|
||||
by CrowdSec for expression evaluation. Malicious regular expressions in CrowdSec scenarios or
|
||||
parsers could cause CPU exhaustion and service degradation through exponential backtracking.
|
||||
|
||||
**Who**
|
||||
- Discovered by: Automated scan (Trivy)
|
||||
- Reported: 2026-01-11
|
||||
|
||||
**Where**
|
||||
- Component: CrowdSec (via `expr-lang/expr` dependency)
|
||||
- Versions affected: CrowdSec versions using `expr-lang/expr` < v1.17.7
|
||||
|
||||
**When**
|
||||
- Discovered: 2026-01-11
|
||||
- Patched: 2026-01-11
|
||||
- Time to patch: 0 days
|
||||
|
||||
**How**
|
||||
Maliciously crafted regular expressions in CrowdSec scenario or parser rules could trigger
|
||||
exponential backtracking in `expr-lang/expr`'s evaluation engine, causing CPU exhaustion and
|
||||
denial of service. The vulnerability is in the upstream expression evaluation library, not in
|
||||
Charon's own code.
|
||||
|
||||
**Resolution**
|
||||
Upgraded CrowdSec to build from source with the patched `expr-lang/expr` v1.17.7. Verification
|
||||
confirmed via `go version -m ./cscli` showing the patched library version in compiled artifacts.
|
||||
Post-patch Trivy scan reports 0 HIGH/CRITICAL vulnerabilities in application code.
|
||||
|
||||
- Technical details: [docs/plans/crowdsec_source_build.md](docs/plans/crowdsec_source_build.md)
|
||||
|
||||
**Credit**
|
||||
Internal remediation; no external reporter.
|
||||
|
||||
---
|
||||
|
||||
@@ -73,7 +290,8 @@ We commit to:
|
||||
|
||||
### Server-Side Request Forgery (SSRF) Protection
|
||||
|
||||
Charon implements industry-leading **5-layer defense-in-depth** SSRF protection to prevent attackers from using the application to access internal resources or cloud metadata.
|
||||
Charon implements industry-leading **5-layer defense-in-depth** SSRF protection to prevent
|
||||
attackers from using the application to access internal resources or cloud metadata.
|
||||
|
||||
#### Protected Against
|
||||
|
||||
@@ -101,8 +319,6 @@ Charon implements industry-leading **5-layer defense-in-depth** SSRF protection
|
||||
|
||||
#### Learn More
|
||||
|
||||
For complete technical details, see:
|
||||
|
||||
- [SSRF Protection Guide](docs/security/ssrf-protection.md)
|
||||
- [Manual Test Plan](docs/issues/ssrf-manual-test-plan.md)
|
||||
- [QA Audit Report](docs/reports/qa_ssrf_remediation_report.md)
|
||||
@@ -125,6 +341,10 @@ For complete technical details, see:
|
||||
|
||||
### Infrastructure Security
|
||||
|
||||
- **Non-root by default**: Charon runs as an unprivileged user (`charon`, uid 1000) inside the
|
||||
container. Docker socket access is granted via a minimal supplemental group matching the host
|
||||
socket's GID — never by running as root. If the socket GID is `0` (root group), Charon requires
|
||||
explicit opt-in before granting access.
|
||||
- **Container isolation**: Docker-based deployment
|
||||
- **Minimal attack surface**: Alpine Linux base image
|
||||
- **Dependency scanning**: Regular Trivy and govulncheck scans
|
||||
@@ -139,6 +359,126 @@ For complete technical details, see:
|
||||
|
||||
---
|
||||
|
||||
## Supply Chain Security
|
||||
|
||||
Charon implements comprehensive supply chain security measures to ensure the integrity and
|
||||
authenticity of releases. Every release includes cryptographic signatures, SLSA provenance
|
||||
attestation, and a Software Bill of Materials (SBOM).
|
||||
|
||||
### Verification Commands
|
||||
|
||||
#### Verify Container Image Signature
|
||||
|
||||
All official Charon images are signed with Sigstore Cosign:
|
||||
|
||||
```bash
|
||||
cosign verify \
|
||||
--certificate-identity-regexp='https://github.com/Wikid82/charon' \
|
||||
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
|
||||
ghcr.io/wikid82/charon:latest
|
||||
```
|
||||
|
||||
Successful verification confirms the image was built by GitHub Actions from the official
|
||||
repository and has not been tampered with since signing.
|
||||
|
||||
#### Verify SLSA Provenance
|
||||
|
||||
```bash
|
||||
# Download provenance from release assets
|
||||
curl -LO https://github.com/Wikid82/charon/releases/latest/download/provenance.json
|
||||
|
||||
slsa-verifier verify-artifact \
|
||||
--provenance-path provenance.json \
|
||||
--source-uri github.com/Wikid82/charon \
|
||||
./backend/charon-binary
|
||||
```
|
||||
|
||||
#### Inspect the SBOM
|
||||
|
||||
```bash
|
||||
# Download SBOM from release assets
|
||||
curl -LO https://github.com/Wikid82/charon/releases/latest/download/sbom.spdx.json
|
||||
|
||||
# Scan for known vulnerabilities
|
||||
grype sbom:sbom.spdx.json
|
||||
```
|
||||
|
||||
### Transparency Log (Rekor)
|
||||
|
||||
All signatures are recorded in the public Sigstore Rekor transparency log:
|
||||
<https://search.sigstore.dev/>
|
||||
|
||||
### Digest Pinning Policy
|
||||
|
||||
**Scope (Required):**
|
||||
|
||||
- CI workflows: `.github/workflows/*.yml`
|
||||
- CI compose files: `.docker/compose/*.yml`
|
||||
- CI helper actions with container refs: `.github/actions/**/*.yml`
|
||||
|
||||
CI workflows and CI compose files MUST use digest-pinned images for third-party services.
|
||||
Tag+digest pairs are preferred for human-readable references with immutable resolution.
|
||||
Self-built images MUST propagate digests to downstream jobs and tests.
|
||||
|
||||
**Local Development Exceptions:**
|
||||
|
||||
Local-only overrides (e.g., `CHARON_E2E_IMAGE`, `CHARON_IMAGE`, `CHARON_DEV_IMAGE`) MAY use tags
|
||||
for developer iteration. Tag-only overrides MUST NOT be used in CI contexts.
|
||||
|
||||
**Documented Exceptions & Compensating Controls:**
|
||||
|
||||
1. **Go toolchain shim** (`golang.org/dl/goX.Y.Z@latest`) — Uses `@latest` to install the shim;
|
||||
compensated by the target toolchain version being pinned in `go.work` with Renovate tracking.
|
||||
2. **Unpinnable dependencies** — Require documented justification; prefer vendor checksums or
|
||||
signed releases; keep SBOM/vulnerability scans in CI.
|
||||
|
||||
### Learn More
|
||||
|
||||
- [User Guide](docs/guides/supply-chain-security-user-guide.md)
|
||||
- [Developer Guide](docs/guides/supply-chain-security-developer-guide.md)
|
||||
- [Sigstore Documentation](https://docs.sigstore.dev/)
|
||||
- [SLSA Framework](https://slsa.dev/)
|
||||
|
||||
---
|
||||
|
||||
## Security Audits & Scanning
|
||||
|
||||
### Automated Scanning
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| Trivy | Container image vulnerability scanning |
|
||||
| CodeQL | Static analysis for Go and JavaScript |
|
||||
| govulncheck | Go module vulnerability scanning |
|
||||
| golangci-lint (gosec) | Go code linting |
|
||||
| npm audit | Frontend dependency scanning |
|
||||
|
||||
### Scanning Workflows
|
||||
|
||||
**Docker Build & Scan** (`.github/workflows/docker-build.yml`) — runs on every commit to `main`,
|
||||
`development`, and `feature/beta-release`, and on all PRs targeting those branches. Performs Trivy
|
||||
scanning, generates an SBOM, creates SBOM attestations, and uploads SARIF results to the GitHub
|
||||
Security tab.
|
||||
|
||||
**Supply Chain Verification** (`.github/workflows/supply-chain-verify.yml`) — triggers
|
||||
automatically via `workflow_run` after a successful docker-build. Runs SBOM completeness checks,
|
||||
Grype vulnerability scans, and (on releases) Cosign signature and SLSA provenance validation.
|
||||
|
||||
**Weekly Security Rebuild** (`.github/workflows/security-weekly-rebuild.yml`) — runs every Sunday
|
||||
at 02:00 UTC. Performs a full no-cache rebuild, scans for all severity levels, and retains JSON
|
||||
artifacts for 90 days.
|
||||
|
||||
**PR-Specific Scanning** — extracts and scans only the Charon application binary on each pull
|
||||
request. Fails the PR if CRITICAL or HIGH vulnerabilities are found in application code.
|
||||
|
||||
### Manual Reviews
|
||||
|
||||
- Security code reviews for all major features
|
||||
- Peer review of security-sensitive changes
|
||||
- Third-party security audits (planned)
|
||||
|
||||
---
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### Deployment Recommendations
|
||||
@@ -153,30 +493,42 @@ For complete technical details, see:
|
||||
### Configuration Hardening
|
||||
|
||||
```yaml
|
||||
# Recommended docker-compose.yml settings
|
||||
services:
|
||||
charon:
|
||||
image: ghcr.io/wikid82/charon:latest
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- CHARON_ENV=production
|
||||
- LOG_LEVEL=info # Don't use debug in production
|
||||
- LOG_LEVEL=info
|
||||
volumes:
|
||||
- ./charon-data:/app/data:rw
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro # Read-only!
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
networks:
|
||||
- charon-internal # Isolated network
|
||||
- charon-internal
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE # Only if binding to ports < 1024
|
||||
- NET_BIND_SERVICE
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
read_only: true # If possible
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:noexec,nosuid,nodev
|
||||
```
|
||||
|
||||
### Gotify Token Hygiene
|
||||
|
||||
Gotify application tokens are secrets and must be handled with strict confidentiality.
|
||||
|
||||
- Never echo, print, log, or return token values in API responses or errors.
|
||||
- Never expose tokenized endpoint query strings (e.g., `...?token=...`) in logs, diagnostics,
|
||||
examples, screenshots, tickets, or reports.
|
||||
- Always redact query parameters in diagnostics and examples before display or storage.
|
||||
- Use write-only token inputs in operator workflows and UI forms.
|
||||
- Store tokens only in environment variables or a dedicated secret manager.
|
||||
- Validate Gotify endpoints over HTTPS only.
|
||||
- Rotate tokens immediately on suspected exposure.
|
||||
|
||||
### Network Security
|
||||
|
||||
- **Firewall Rules**: Only expose necessary ports (80, 443, 8080)
|
||||
@@ -186,318 +538,6 @@ services:
|
||||
|
||||
---
|
||||
|
||||
## Supply Chain Security
|
||||
|
||||
Charon implements comprehensive supply chain security measures to ensure the integrity and authenticity of releases. Every release includes cryptographic signatures, SLSA provenance attestation, and Software Bill of Materials (SBOM).
|
||||
|
||||
### Verification Commands
|
||||
|
||||
#### Verify Container Image Signature
|
||||
|
||||
All official Charon images are signed with Sigstore Cosign:
|
||||
|
||||
```bash
|
||||
# Install cosign (if not already installed)
|
||||
curl -LO https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
|
||||
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
|
||||
sudo chmod +x /usr/local/bin/cosign
|
||||
|
||||
# Verify image signature
|
||||
cosign verify \
|
||||
--certificate-identity-regexp='https://github.com/Wikid82/charon' \
|
||||
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
|
||||
ghcr.io/wikid82/charon:latest
|
||||
```
|
||||
|
||||
Successful verification output confirms:
|
||||
|
||||
- The image was built by GitHub Actions
|
||||
- The build came from the official Charon repository
|
||||
- The image has not been tampered with since signing
|
||||
|
||||
#### Verify SLSA Provenance
|
||||
|
||||
SLSA (Supply-chain Levels for Software Artifacts) provenance provides tamper-proof evidence of how the software was built:
|
||||
|
||||
```bash
|
||||
# Install slsa-verifier (if not already installed)
|
||||
curl -LO https://github.com/slsa-framework/slsa-verifier/releases/latest/download/slsa-verifier-linux-amd64
|
||||
sudo mv slsa-verifier-linux-amd64 /usr/local/bin/slsa-verifier
|
||||
sudo chmod +x /usr/local/bin/slsa-verifier
|
||||
|
||||
# Download provenance from release assets
|
||||
curl -LO https://github.com/Wikid82/charon/releases/latest/download/provenance.json
|
||||
|
||||
# Verify provenance
|
||||
slsa-verifier verify-artifact \
|
||||
--provenance-path provenance.json \
|
||||
--source-uri github.com/Wikid82/charon \
|
||||
./backend/charon-binary
|
||||
```
|
||||
|
||||
#### Inspect Software Bill of Materials (SBOM)
|
||||
|
||||
Every release includes a comprehensive SBOM in SPDX format:
|
||||
|
||||
```bash
|
||||
# Download SBOM from release assets
|
||||
curl -LO https://github.com/Wikid82/charon/releases/latest/download/sbom.spdx.json
|
||||
|
||||
# View SBOM contents
|
||||
cat sbom.spdx.json | jq .
|
||||
|
||||
# Check for known vulnerabilities (requires Grype)
|
||||
grype sbom:sbom.spdx.json
|
||||
```
|
||||
|
||||
### Transparency Log (Rekor)
|
||||
|
||||
All signatures are recorded in the public Sigstore Rekor transparency log, providing an immutable audit trail:
|
||||
|
||||
- **Search the log**: <https://search.sigstore.dev/>
|
||||
- **Query by image**: Search for `ghcr.io/wikid82/charon`
|
||||
- **View entry details**: Each entry includes commit SHA, workflow run, and signing timestamp
|
||||
|
||||
### Automated Verification in CI/CD
|
||||
|
||||
Integrate supply chain verification into your deployment pipeline:
|
||||
|
||||
```yaml
|
||||
# Example GitHub Actions workflow
|
||||
- name: Verify Charon Image
|
||||
run: |
|
||||
cosign verify \
|
||||
--certificate-identity-regexp='https://github.com/Wikid82/charon' \
|
||||
--certificate-oidc-issuer='https://token.actions.githubusercontent.com' \
|
||||
ghcr.io/wikid82/charon:${{ env.VERSION }}
|
||||
```
|
||||
|
||||
### What's Protected
|
||||
|
||||
- **Container Images**: All `ghcr.io/wikid82/charon:*` images are signed
|
||||
- **Release Binaries**: Backend binaries include provenance attestation
|
||||
- **Build Process**: SLSA Level 3 compliant build provenance
|
||||
- **Dependencies**: Complete SBOM including all direct and transitive dependencies
|
||||
|
||||
### Digest Pinning Policy
|
||||
|
||||
Charon uses digest pinning to reduce supply chain risk and ensure CI runs against immutable artifacts.
|
||||
|
||||
**Scope (Required):**
|
||||
|
||||
- **CI workflows**: `.github/workflows/*.yml`, `.github/workflows/*.yaml`
|
||||
- **CI compose files**: `.docker/compose/*.yml`, `.docker/compose/*.yaml`, `.docker/compose/docker-compose*.yml`, `.docker/compose/docker-compose*.yaml`
|
||||
- **CI helper actions with container refs**: `.github/actions/**/*.yml`, `.github/actions/**/*.yaml`
|
||||
- CI workflows and CI compose files MUST use digest-pinned images for third-party services.
|
||||
- Tag+digest pairs are preferred for human-readable references with immutable resolution.
|
||||
- Self-built images MUST propagate digests to downstream jobs and tests.
|
||||
|
||||
**Rationale:**
|
||||
|
||||
- Prevent tag drift and supply chain substitution in automated runs.
|
||||
- Ensure deterministic builds, reproducible scans, and stable SBOM generation.
|
||||
- Reduce rollback risk by guaranteeing CI uses immutable artifacts.
|
||||
|
||||
**Local Development Exceptions:**
|
||||
|
||||
- Local-only overrides (e.g., `CHARON_E2E_IMAGE`, `CHARON_IMAGE`, `CHARON_DEV_IMAGE`) MAY use tags for developer iteration.
|
||||
- Tag-only overrides MUST NOT be used in CI contexts.
|
||||
|
||||
**Documented Exceptions & Compensating Controls:**
|
||||
|
||||
1. **Go toolchain shim** (`golang.org/dl/goX.Y.Z@latest`)
|
||||
- **Exception:** Uses `@latest` to install the shim.
|
||||
- **Compensating controls:** The target toolchain version is pinned in `go.work`, and Renovate tracks the required version for updates.
|
||||
2. **Unpinnable dependencies** (no stable digest or checksum source)
|
||||
- **Exception:** Dependency cannot be pinned by digest.
|
||||
- **Compensating controls:** Require documented justification, prefer vendor-provided checksums or signed releases when available, and keep SBOM/vulnerability scans in CI.
|
||||
|
||||
### Learn More
|
||||
|
||||
- **[User Guide](docs/guides/supply-chain-security-user-guide.md)**: Step-by-step verification instructions
|
||||
- **[Developer Guide](docs/guides/supply-chain-security-developer-guide.md)**: Integration into development workflow
|
||||
- **[Sigstore Documentation](https://docs.sigstore.dev/)**: Technical details on signing and verification
|
||||
- **[SLSA Framework](https://slsa.dev/)**: Supply chain security framework overview
|
||||
|
||||
---
|
||||
|
||||
## Security Audits & Scanning
|
||||
|
||||
### Automated Scanning
|
||||
|
||||
We use the following tools:
|
||||
|
||||
- **Trivy**: Container image vulnerability scanning
|
||||
- **CodeQL**: Static code analysis for Go and JavaScript
|
||||
- **govulncheck**: Go module vulnerability scanning
|
||||
- **golangci-lint**: Go code linting (including gosec)
|
||||
- **npm audit**: Frontend dependency vulnerability scanning
|
||||
|
||||
### Security Scanning Workflows
|
||||
|
||||
Charon implements multiple layers of automated security scanning:
|
||||
|
||||
#### Docker Build & Scan (Per-Commit)
|
||||
|
||||
**Workflow**: `.github/workflows/docker-build.yml`
|
||||
|
||||
- Runs on every commit to `main`, `development`, and `feature/beta-release` branches
|
||||
- Runs on all pull requests targeting these branches
|
||||
- Performs Trivy vulnerability scanning on built images
|
||||
- Generates SBOM (Software Bill of Materials) for supply chain transparency
|
||||
- Creates SBOM attestations for verifiable build provenance
|
||||
- Verifies Caddy security patches (CVE-2025-68156)
|
||||
- Uploads SARIF results to GitHub Security tab
|
||||
|
||||
**Note**: This workflow replaced the previous `docker-publish.yml` (deleted Dec 21, 2025) with enhanced security features.
|
||||
|
||||
#### Supply Chain Verification
|
||||
|
||||
**Workflow**: `.github/workflows/supply-chain-verify.yml`
|
||||
|
||||
**Trigger Timing**: Runs automatically after `docker-build.yml` completes successfully via `workflow_run` trigger.
|
||||
|
||||
**Branch Coverage**: Triggers on **ALL branches** where docker-build completes, including:
|
||||
|
||||
- `main` (default branch)
|
||||
- `development`
|
||||
- `feature/*` branches (including `feature/beta-release`)
|
||||
- Pull request branches
|
||||
|
||||
**Why No Branch Filter**: GitHub Actions has a platform limitation where `branches` filters in `workflow_run` triggers only match the default branch. To ensure comprehensive supply chain verification across all branches and PRs, we intentionally omit the branch filter. The workflow file must exist on the branch to execute, preventing untrusted code execution.
|
||||
|
||||
**Verification Steps**:
|
||||
|
||||
1. SBOM completeness verification
|
||||
2. Vulnerability scanning with Grype
|
||||
3. Results uploaded as workflow artifacts
|
||||
4. PR comments with vulnerability summary (when applicable)
|
||||
5. For releases: Cosign signature verification and SLSA provenance validation
|
||||
|
||||
**Additional Triggers**:
|
||||
|
||||
- Runs on all published releases
|
||||
- Scheduled weekly on Mondays at 00:00 UTC
|
||||
- Can be triggered manually via `workflow_dispatch`
|
||||
|
||||
#### Weekly Security Rebuild
|
||||
|
||||
**Workflow**: `.github/workflows/security-weekly-rebuild.yml`
|
||||
|
||||
- Runs every Sunday at 02:00 UTC
|
||||
- Performs full rebuild with no cache to ensure latest base images
|
||||
- Scans with Trivy for CRITICAL, HIGH, MEDIUM, and LOW vulnerabilities
|
||||
- Uploads results to GitHub Security tab
|
||||
- Stores JSON artifacts for 90-day retention
|
||||
- Checks Alpine package versions for security updates
|
||||
|
||||
#### PR-Specific Scanning
|
||||
|
||||
**Workflow**: `.github/workflows/docker-build.yml` (trivy-pr-app-only job)
|
||||
|
||||
- Runs on all pull requests
|
||||
- Extracts and scans only the Charon application binary
|
||||
- Fails PR if CRITICAL or HIGH vulnerabilities found in application code
|
||||
- Faster feedback loop for developers during code review
|
||||
|
||||
### Workflow Orchestration
|
||||
|
||||
The security scanning workflows use a coordinated orchestration pattern:
|
||||
|
||||
1. **Build Phase**: `docker-build.yml` builds the image and performs initial Trivy scan
|
||||
2. **Verification Phase**: `supply-chain-verify.yml` triggers automatically via `workflow_run` after successful build
|
||||
3. **Verification Timing**:
|
||||
- On feature branches: Runs after docker-build completes on push events
|
||||
- On pull requests: Runs after docker-build completes on PR synchronize events
|
||||
- No delay or gaps: verification starts immediately after build success
|
||||
4. **Weekly Maintenance**: `security-weekly-rebuild.yml` provides ongoing monitoring
|
||||
|
||||
This pattern ensures:
|
||||
|
||||
- Images are built before verification attempts to scan them
|
||||
- No race conditions between build and verification
|
||||
- Comprehensive coverage across all branches and PRs
|
||||
- Efficient resource usage (verification only runs after successful builds)
|
||||
|
||||
### Manual Reviews
|
||||
|
||||
- Security code reviews for all major features
|
||||
- Peer review of security-sensitive changes
|
||||
- Third-party security audits (planned)
|
||||
|
||||
### Continuous Monitoring
|
||||
|
||||
- GitHub Dependabot alerts
|
||||
- Weekly security scans in CI/CD
|
||||
- Community vulnerability reports
|
||||
- Automated supply chain verification on every build
|
||||
|
||||
---
|
||||
|
||||
## Recently Resolved Vulnerabilities
|
||||
|
||||
Charon maintains transparency about security issues and their resolution. Below is a comprehensive record of recently patched vulnerabilities.
|
||||
|
||||
### CVE-2025-68156 (expr-lang/expr ReDoS)
|
||||
|
||||
- **Severity**: HIGH (CVSS 7.5)
|
||||
- **Component**: expr-lang/expr (used by CrowdSec for expression evaluation)
|
||||
- **Vulnerability**: Regular Expression Denial of Service (ReDoS)
|
||||
- **Description**: Malicious regular expressions in CrowdSec scenarios or parsers could cause CPU exhaustion and service degradation through exponential backtracking in vulnerable regex patterns.
|
||||
- **Fixed Version**: expr-lang/expr v1.17.7
|
||||
- **Resolution Date**: January 11, 2026
|
||||
- **Remediation**: Upgraded CrowdSec to build from source with patched expr-lang/expr v1.17.7
|
||||
- **Verification**:
|
||||
- Binary inspection: `go version -m ./cscli` confirms v1.17.7 in compiled artifacts
|
||||
- Container scan: Trivy reports 0 HIGH/CRITICAL vulnerabilities in application code
|
||||
- Runtime testing: CrowdSec scenarios and parsers load successfully with patched library
|
||||
- **Impact**: No known exploits in Charon deployments; preventive upgrade completed
|
||||
- **Status**: ✅ **PATCHED** — Verified in all release artifacts
|
||||
- **Technical Details**: See [CrowdSec Source Build Documentation](docs/plans/crowdsec_source_build.md)
|
||||
|
||||
---
|
||||
|
||||
## Known Security Considerations
|
||||
|
||||
### Debian Base Image CVEs (2026-02-04) — TEMPORARY
|
||||
|
||||
**Status**: ⚠️ 7 HIGH severity CVEs in Debian Trixie base image. **Alpine migration in progress.**
|
||||
|
||||
**Background**: Migrated from Alpine → Debian due to CVE-2025-60876 (busybox heap overflow). Debian now has worse CVE posture with no fixes available. Reverting to Alpine as Alpine CVE-2025-60876 is now patched.
|
||||
|
||||
**Affected Packages**:
|
||||
- **libc6/libc-bin** (glibc): CVE-2026-0861 (CVSS 8.4), CVE-2025-15281, CVE-2026-0915
|
||||
- **libtasn1-6**: CVE-2025-13151 (CVSS 7.5)
|
||||
- **libtiff**: 2 additional HIGH CVEs
|
||||
|
||||
**Fix Status**: ❌ No fixes available from Debian Security Team
|
||||
|
||||
**Risk Assessment**: 🟢 **LOW actual risk**
|
||||
- CVEs affect system libraries, NOT Charon application code
|
||||
- Container isolation limits exploit surface area
|
||||
- No direct exploit paths identified in Charon's usage patterns
|
||||
- Network ingress filtered through Caddy proxy
|
||||
|
||||
**Mitigation**: Alpine base image migration
|
||||
- **Spec**: [`docs/plans/alpine_migration_spec.md`](docs/plans/alpine_migration_spec.md)
|
||||
- **Security Advisory**: [`docs/security/advisory_2026-02-04_debian_cves_temporary.md`](docs/security/advisory_2026-02-04_debian_cves_temporary.md)
|
||||
- **Timeline**: 2-3 weeks (target completion: March 5, 2026)
|
||||
- **Expected Outcome**: 100% CVE reduction (7 HIGH → 0)
|
||||
|
||||
**Review Date**: 2026-02-11 (Phase 1 Alpine CVE verification)
|
||||
|
||||
**Details**: See [VULNERABILITY_ACCEPTANCE.md](docs/security/VULNERABILITY_ACCEPTANCE.md) for complete risk assessment and monitoring plan.
|
||||
|
||||
### Third-Party Dependencies
|
||||
|
||||
**CrowdSec Binaries**: As of December 2025, CrowdSec binaries shipped with Charon contain 4 HIGH-severity CVEs in Go stdlib (CVE-2025-58183, CVE-2025-58186, CVE-2025-58187, CVE-2025-61729). These are upstream issues in Go 1.25.1 and will be resolved when CrowdSec releases binaries built with go 1.26.0+.
|
||||
|
||||
**Impact**: Low. These vulnerabilities are in CrowdSec's third-party binaries, not in Charon's application code. They affect HTTP/2, TLS certificate handling, and archive parsing—areas not directly exposed to attackers through Charon's interface.
|
||||
|
||||
**Mitigation**: Monitor CrowdSec releases for updated binaries. Charon's own application code has zero vulnerabilities.
|
||||
|
||||
---
|
||||
|
||||
## Security Hall of Fame
|
||||
|
||||
We recognize security researchers who help improve Charon:
|
||||
@@ -507,19 +547,4 @@ We recognize security researchers who help improve Charon:
|
||||
|
||||
---
|
||||
|
||||
## Security Contact
|
||||
|
||||
- **GitHub Security Advisories**: <https://github.com/Wikid82/charon/security/advisories>
|
||||
- **GitHub Discussions**: <https://github.com/Wikid82/charon/discussions>
|
||||
- **GitHub Issues** (non-security): <https://github.com/Wikid82/charon/issues>
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
This security policy is part of the Charon project, licensed under the MIT License.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: January 30, 2026
|
||||
**Version**: 1.2
|
||||
**Last Updated**: 2026-03-20
|
||||
|
||||
74
VERSION.md
74
VERSION.md
@@ -19,36 +19,76 @@ Example: `0.1.0-alpha`, `1.0.0-beta.1`, `2.0.0-rc.2`
|
||||
|
||||
## Creating a Release
|
||||
|
||||
### Automated Release Process
|
||||
### Canonical Release Process (Tag-Derived CI)
|
||||
|
||||
1. **Update version** in `.version` file:
|
||||
1. **Create and push a release tag**:
|
||||
|
||||
```bash
|
||||
echo "1.0.0" > .version
|
||||
git tag -a v1.0.0 -m "Release v1.0.0"
|
||||
git push origin v1.0.0
|
||||
```
|
||||
|
||||
2. **Commit version bump**:
|
||||
2. **GitHub Actions automatically**:
|
||||
- Runs release workflow from the pushed tag (`.github/workflows/release-goreleaser.yml`)
|
||||
- Builds and publishes release artifacts/images through CI (`.github/workflows/docker-build.yml`)
|
||||
- Creates/updates GitHub Release metadata
|
||||
|
||||
3. **Container tags are published**:
|
||||
- `v1.0.0` (exact version)
|
||||
- `1.0` (minor version)
|
||||
- `1` (major version)
|
||||
- `latest` (for non-prerelease on main branch)
|
||||
|
||||
### Legacy/Optional `.version` Path
|
||||
|
||||
The `.version` file is optional and not the canonical release trigger.
|
||||
|
||||
Use it only when you need local/version-file parity checks:
|
||||
|
||||
1. **Set `.version` locally (optional)**:
|
||||
|
||||
```bash
|
||||
git add .version
|
||||
git commit -m "chore: bump version to 1.0.0"
|
||||
echo "1.0.0" > .version
|
||||
```
|
||||
|
||||
3. **Create and push tag**:
|
||||
2. **Validate `.version` matches the latest tag**:
|
||||
|
||||
```bash
|
||||
git tag -a v1.0.0 -m "Release v1.0.0"
|
||||
git push origin v1.0.0
|
||||
bash scripts/check-version-match-tag.sh
|
||||
```
|
||||
|
||||
4. **GitHub Actions automatically**:
|
||||
- Creates GitHub Release with changelog
|
||||
- Builds multi-arch Docker images (amd64, arm64)
|
||||
- Publishes to GitHub Container Registry with tags:
|
||||
- `v1.0.0` (exact version)
|
||||
- `1.0` (minor version)
|
||||
- `1` (major version)
|
||||
- `latest` (for non-prerelease on main branch)
|
||||
### Deterministic Rollout Verification Gates (Mandatory)
|
||||
|
||||
Release sign-off is blocked until all items below pass in the same validation
|
||||
run.
|
||||
|
||||
Enforcement points:
|
||||
|
||||
- Release sign-off checklist/process (mandatory): All gates below remain required for release sign-off.
|
||||
- CI-supported checks (current): `.github/workflows/docker-build.yml` and `.github/workflows/supply-chain-verify.yml` enforce the subset currently implemented in workflows.
|
||||
- Manual validation required until CI parity: Validate any not-yet-implemented workflow gates via VS Code tasks `Security: Full Supply Chain Audit`, `Security: Verify SBOM`, `Security: Generate SLSA Provenance`, and `Security: Sign with Cosign`.
|
||||
- Optional version-file parity check: `Utility: Check Version Match Tag` (script: `scripts/check-version-match-tag.sh`).
|
||||
|
||||
- [ ] **Digest freshness/parity:** Capture pre-push and post-push index digests
|
||||
for the target tag in GHCR and Docker Hub, confirm expected freshness,
|
||||
and confirm cross-registry index digest parity.
|
||||
- [ ] **Per-arch parity:** Confirm per-platform (`linux/amd64`, `linux/arm64`,
|
||||
and any published platform) digest parity between GHCR and Docker Hub.
|
||||
- [ ] **Immutable digest scanning:** Run SBOM and vulnerability scans against
|
||||
immutable refs only, using `image@sha256:<index-digest>`.
|
||||
- [ ] **Artifact freshness:** Confirm scan artifacts are generated after the
|
||||
push timestamp and in the same validation run.
|
||||
- [ ] **Evidence block present:** Include the mandatory evidence block fields
|
||||
listed below.
|
||||
|
||||
#### Mandatory Evidence Block Fields
|
||||
|
||||
- Tag name
|
||||
- Index digest (`sha256:...`)
|
||||
- Per-arch digests (platform -> digest)
|
||||
- Scan tool versions
|
||||
- Push timestamp and scan timestamp(s)
|
||||
- Artifact file names generated in this run
|
||||
|
||||
## Container Image Tags
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ linters:
|
||||
- ineffassign # Ineffectual assignments
|
||||
- unused # Unused code detection
|
||||
- gosec # Security checks (critical issues only)
|
||||
linters-settings:
|
||||
settings:
|
||||
govet:
|
||||
enable:
|
||||
- shadow
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# golangci-lint configuration
|
||||
version: 2
|
||||
version: "2"
|
||||
run:
|
||||
timeout: 5m
|
||||
tests: true
|
||||
@@ -14,7 +14,7 @@ linters:
|
||||
- staticcheck
|
||||
- unused
|
||||
- errcheck
|
||||
linters-settings:
|
||||
settings:
|
||||
gocritic:
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
|
||||
@@ -260,7 +260,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Register import handler with config dependencies
|
||||
routes.RegisterImportHandler(router, db, cfg.CaddyBinary, cfg.ImportDir, cfg.ImportCaddyfile)
|
||||
routes.RegisterImportHandler(router, db, cfg, cfg.CaddyBinary, cfg.ImportDir, cfg.ImportCaddyfile)
|
||||
|
||||
// Check for mounted Caddyfile on startup
|
||||
if err := handlers.CheckMountedImport(db, cfg.ImportCaddyfile, cfg.CaddyBinary, cfg.ImportDir); err != nil {
|
||||
|
||||
@@ -40,7 +40,7 @@ func TestResetPasswordCommand_Succeeds(t *testing.T) {
|
||||
}
|
||||
|
||||
email := "user@example.com"
|
||||
user := models.User{UUID: "u-1", Email: email, Name: "User", Role: "admin", Enabled: true}
|
||||
user := models.User{UUID: "u-1", Email: email, Name: "User", Role: models.RoleAdmin, Enabled: true}
|
||||
user.PasswordHash = "$2a$10$example_hashed_password"
|
||||
if err = db.Create(&user).Error; err != nil {
|
||||
t.Fatalf("seed user: %v", err)
|
||||
@@ -257,7 +257,7 @@ func TestMain_ResetPasswordCommand_InProcess(t *testing.T) {
|
||||
}
|
||||
|
||||
email := "user@example.com"
|
||||
user := models.User{UUID: "u-1", Email: email, Name: "User", Role: "admin", Enabled: true}
|
||||
user := models.User{UUID: "u-1", Email: email, Name: "User", Role: models.RoleAdmin, Enabled: true}
|
||||
user.PasswordHash = "$2a$10$example_hashed_password"
|
||||
user.FailedLoginAttempts = 3
|
||||
if err = db.Create(&user).Error; err != nil {
|
||||
@@ -311,7 +311,8 @@ func TestMain_DefaultStartupGracefulShutdown_Subprocess(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("find free http port: %v", err)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(dbPath), 0o750); err != nil {
|
||||
err = os.MkdirAll(filepath.Dir(dbPath), 0o750)
|
||||
if err != nil {
|
||||
t.Fatalf("mkdir db dir: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -64,11 +64,13 @@ func main() {
|
||||
jsonOutPath := resolvePath(repoRoot, *jsonOutFlag)
|
||||
mdOutPath := resolvePath(repoRoot, *mdOutFlag)
|
||||
|
||||
if err := assertFileExists(backendCoveragePath, "backend coverage file"); err != nil {
|
||||
err = assertFileExists(backendCoveragePath, "backend coverage file")
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := assertFileExists(frontendCoveragePath, "frontend coverage file"); err != nil {
|
||||
err = assertFileExists(frontendCoveragePath, "frontend coverage file")
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -235,7 +235,8 @@ func TestGitDiffAndWriters(t *testing.T) {
|
||||
t.Fatalf("expected empty diff for HEAD...HEAD, got: %q", diffContent)
|
||||
}
|
||||
|
||||
if _, err := gitDiff(repoRoot, "bad-baseline"); err == nil {
|
||||
_, err = gitDiff(repoRoot, "bad-baseline")
|
||||
if err == nil {
|
||||
t.Fatal("expected gitDiff failure for invalid baseline")
|
||||
}
|
||||
|
||||
@@ -263,7 +264,8 @@ func TestGitDiffAndWriters(t *testing.T) {
|
||||
}
|
||||
|
||||
jsonPath := filepath.Join(t.TempDir(), "report.json")
|
||||
if err := writeJSON(jsonPath, report); err != nil {
|
||||
err = writeJSON(jsonPath, report)
|
||||
if err != nil {
|
||||
t.Fatalf("writeJSON should succeed: %v", err)
|
||||
}
|
||||
// #nosec G304 -- Test reads artifact path created by this test.
|
||||
@@ -276,7 +278,8 @@ func TestGitDiffAndWriters(t *testing.T) {
|
||||
}
|
||||
|
||||
markdownPath := filepath.Join(t.TempDir(), "report.md")
|
||||
if err := writeMarkdown(markdownPath, report, "backend/coverage.txt", "frontend/coverage/lcov.info"); err != nil {
|
||||
err = writeMarkdown(markdownPath, report, "backend/coverage.txt", "frontend/coverage/lcov.info")
|
||||
if err != nil {
|
||||
t.Fatalf("writeMarkdown should succeed: %v", err)
|
||||
}
|
||||
// #nosec G304 -- Test reads artifact path created by this test.
|
||||
|
||||
@@ -72,7 +72,7 @@ func TestSeedMain_ForceAdminUpdatesExistingUserPassword(t *testing.T) {
|
||||
UUID: "existing-user",
|
||||
Email: "admin@localhost",
|
||||
Name: "Old Name",
|
||||
Role: "viewer",
|
||||
Role: models.RolePassthrough,
|
||||
Enabled: false,
|
||||
PasswordHash: "$2a$10$example_hashed_password",
|
||||
}
|
||||
@@ -134,7 +134,7 @@ func TestSeedMain_ForceAdminWithoutPasswordUpdatesMetadata(t *testing.T) {
|
||||
UUID: "existing-user-no-pass",
|
||||
Email: "admin@localhost",
|
||||
Name: "Old Name",
|
||||
Role: "viewer",
|
||||
Role: models.RolePassthrough,
|
||||
Enabled: false,
|
||||
PasswordHash: "$2a$10$example_hashed_password",
|
||||
}
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
module github.com/Wikid82/charon/backend
|
||||
|
||||
go 1.26
|
||||
go 1.26.1
|
||||
|
||||
require (
|
||||
github.com/containrrr/shoutrrr v0.8.0
|
||||
github.com/docker/docker v28.5.2+incompatible
|
||||
github.com/gin-contrib/gzip v1.2.5
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/gin-gonic/gin v1.12.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/mattn/go-sqlite3 v1.14.34
|
||||
github.com/mattn/go-sqlite3 v1.14.37
|
||||
github.com/oschwald/geoip2-golang/v2 v2.1.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
github.com/stretchr/testify v1.11.1
|
||||
golang.org/x/crypto v0.48.0
|
||||
golang.org/x/net v0.50.0
|
||||
golang.org/x/text v0.34.0
|
||||
golang.org/x/time v0.14.0
|
||||
golang.org/x/crypto v0.49.0
|
||||
golang.org/x/net v0.52.0
|
||||
golang.org/x/text v0.35.0
|
||||
golang.org/x/time v0.15.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gorm v1.31.1
|
||||
@@ -29,9 +28,9 @@ require (
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.14.1 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/bytedance/gopkg v0.1.4 // indirect
|
||||
github.com/bytedance/sonic v1.15.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.5.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
@@ -42,34 +41,32 @@ require (
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.30.1 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/goccy/go-json v0.10.6 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/morikuni/aec v1.1.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/oschwald/maxminddb-golang/v2 v2.1.1 // indirect
|
||||
@@ -77,28 +74,30 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.59.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/arch v0.22.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
|
||||
go.opentelemetry.io/otel v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.42.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.42.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
golang.org/x/arch v0.25.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
google.golang.org/grpc v1.79.3 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gotest.tools/v3 v3.5.2 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/sqlite v1.23.1 // indirect
|
||||
modernc.org/libc v1.70.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.47.0 // indirect
|
||||
)
|
||||
|
||||
219
backend/go.sum
219
backend/go.sum
@@ -4,12 +4,12 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=
|
||||
github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
|
||||
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
|
||||
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
|
||||
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
|
||||
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
|
||||
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
@@ -22,8 +22,6 @@ github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151X
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/containrrr/shoutrrr v0.8.0 h1:mfG2ATzIS7NR2Ec6XL+xyoHzN97H8WPjir8aYzJUSec=
|
||||
github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5QePU8atnAdO2o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -37,20 +35,18 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/gin-contrib/gzip v1.2.5 h1:fIZs0S+l17pIu1P5XRJOo/YNqfIuPCrZZ3TWB7pjckI=
|
||||
github.com/gin-contrib/gzip v1.2.5/go.mod h1:aomRgR7ftdZV3uWY0gW/m8rChfxau0n8YVvwlOHONzw=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -66,29 +62,25 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
|
||||
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
|
||||
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
@@ -107,13 +99,10 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
|
||||
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.37 h1:3DOZp4cXis1cUIpCfXLtmlGolNLp2VEqhiB/PARNBIg=
|
||||
github.com/mattn/go-sqlite3 v1.14.37/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
@@ -127,14 +116,12 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ=
|
||||
github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
@@ -153,86 +140,92 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
|
||||
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
|
||||
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
|
||||
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
|
||||
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
||||
go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc=
|
||||
go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
|
||||
go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
|
||||
go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo=
|
||||
go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc=
|
||||
go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
|
||||
go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE=
|
||||
golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc=
|
||||
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
|
||||
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -249,11 +242,31 @@ gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw=
|
||||
modernc.org/ccgo/v4 v4.32.0/go.mod h1:6F08EBCx5uQc38kMGl+0Nm0oWczoo1c7cgpzEry7Uc0=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
|
||||
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.47.0 h1:R1XyaNpoW4Et9yly+I2EeX7pBza/w+pmYee/0HJDyKk=
|
||||
modernc.org/sqlite v1.47.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user