返回 DeepSeek-Reasonix
main.go
1 package main
2
3 import (
4 "crypto/sha256"
5 "encoding/binary"
6 "errors"
7 "fmt"
8 "io"
9 "os"
10 "path/filepath"
11 "sort"
12 "strings"
13
14 "gopkg.in/yaml.v3"
15 )
16
17 const (
18 contractPath = ".signpath/contracts/release-signing.yml"
19 projectSlug = "DeepSeek-Reasonix"
20 policySlug = "release-signing"
21 repository = "https://github.com/esengine/DeepSeek-Reasonix.git"
22 )
23
24 var expectedBranches = []string{"main-v2"}
25
26 type releaseSigningContract struct {
27 Version int `yaml:"version"`
28 ProjectSlug string `yaml:"project_slug"`
29 SigningPolicySlug string `yaml:"signing_policy_slug"`
30 RepositoryURL string `yaml:"repository_url"`
31 AllowedBranchNames []string `yaml:"allowed_branch_names"`
32 AllowedBuildDefinitions []string `yaml:"allowed_build_definitions"`
33 FingerprintFiles []string `yaml:"fingerprint_files"`
34 }
35
36 type workflowInfo struct {
37 externallyTriggered bool
38 directSigning bool
39 calls []string
40 }
41
42 func main() {
43 if len(os.Args) < 2 || len(os.Args) > 3 {
44 fatalf("usage: signpath-contract <validate|fingerprint> [repository-root]")
45 }
46 root := "."
47 if len(os.Args) == 3 {
48 root = os.Args[2]
49 }
50 contract, err := loadAndValidate(root)
51 if err != nil {
52 fatalf("%v", err)
53 }
54
55 switch os.Args[1] {
56 case "validate":
57 fmt.Println("SignPath release signing contract is valid")
58 case "fingerprint":
59 fingerprint, err := contractFingerprint(root, contract)
60 if err != nil {
61 fatalf("fingerprint contract: %v", err)
62 }
63 fmt.Printf("v1:%x\n", fingerprint)
64 default:
65 fatalf("unknown command %q", os.Args[1])
66 }
67 }
68
69 func fatalf(format string, args ...any) {
70 fmt.Fprintf(os.Stderr, "signpath-contract: "+format+"\n", args...)
71 os.Exit(1)
72 }
73
74 func loadAndValidate(root string) (releaseSigningContract, error) {
75 data, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(contractPath)))
76 if err != nil {
77 return releaseSigningContract{}, fmt.Errorf("read %s: %w", contractPath, err)
78 }
79 var contract releaseSigningContract
80 if err := yaml.Unmarshal(data, &contract); err != nil {
81 return releaseSigningContract{}, fmt.Errorf("parse %s: %w", contractPath, err)
82 }
83 if err := validateContract(root, contract); err != nil {
84 return releaseSigningContract{}, err
85 }
86 return contract, nil
87 }
88
89 func validateContract(root string, contract releaseSigningContract) error {
90 if contract.Version != 1 {
91 return fmt.Errorf("%s version is %d, want 1", contractPath, contract.Version)
92 }
93 if contract.ProjectSlug != projectSlug {
94 return fmt.Errorf("project_slug is %q, want %q", contract.ProjectSlug, projectSlug)
95 }
96 if contract.SigningPolicySlug != policySlug {
97 return fmt.Errorf("signing_policy_slug is %q, want %q", contract.SigningPolicySlug, policySlug)
98 }
99 if contract.RepositoryURL != repository {
100 return fmt.Errorf("repository_url is %q, want %q", contract.RepositoryURL, repository)
101 }
102 if err := requireExactSet("allowed_branch_names", contract.AllowedBranchNames, expectedBranches); err != nil {
103 return err
104 }
105 if len(contract.AllowedBuildDefinitions) == 0 {
106 return errors.New("allowed_build_definitions must not be empty")
107 }
108 for _, value := range append(append([]string{}, contract.AllowedBranchNames...), contract.AllowedBuildDefinitions...) {
109 if strings.ContainsAny(value, "*?[") {
110 return fmt.Errorf("SignPath allowlists must not contain wildcard %q", value)
111 }
112 }
113 for _, name := range contract.AllowedBuildDefinitions {
114 if err := validateRepositoryPath(name); err != nil {
115 return fmt.Errorf("invalid allowed build definition %q: %w", name, err)
116 }
117 if !strings.HasPrefix(name, ".github/workflows/") {
118 return fmt.Errorf("allowed build definition %q is outside .github/workflows", name)
119 }
120 }
121 if err := requireUnique("allowed_build_definitions", contract.AllowedBuildDefinitions); err != nil {
122 return err
123 }
124 if len(contract.FingerprintFiles) == 0 {
125 return errors.New("fingerprint_files must not be empty")
126 }
127 seen := make(map[string]bool, len(contract.FingerprintFiles))
128 for _, name := range contract.FingerprintFiles {
129 if err := validateRepositoryPath(name); err != nil {
130 return fmt.Errorf("invalid fingerprint file %q: %w", name, err)
131 }
132 if seen[name] {
133 return fmt.Errorf("duplicate fingerprint file %q", name)
134 }
135 seen[name] = true
136 info, err := os.Stat(filepath.Join(root, filepath.FromSlash(name)))
137 if err != nil {
138 return fmt.Errorf("fingerprint file %q: %w", name, err)
139 }
140 if !info.Mode().IsRegular() {
141 return fmt.Errorf("fingerprint file %q is not a regular file", name)
142 }
143 }
144
145 reachable, err := discoverTopLevelSigningWorkflows(root)
146 if err != nil {
147 return err
148 }
149 if err := requireExactSet("top-level workflows that reach SignPath", reachable, contract.AllowedBuildDefinitions); err != nil {
150 return fmt.Errorf("SignPath build-definition drift: %w", err)
151 }
152 return nil
153 }
154
155 func requireExactSet(name string, got, want []string) error {
156 gotCopy := append([]string(nil), got...)
157 wantCopy := append([]string(nil), want...)
158 sort.Strings(gotCopy)
159 sort.Strings(wantCopy)
160 if len(gotCopy) != len(wantCopy) {
161 return fmt.Errorf("%s is %v, want %v", name, got, want)
162 }
163 for i := range gotCopy {
164 if gotCopy[i] != wantCopy[i] {
165 return fmt.Errorf("%s is %v, want %v", name, got, want)
166 }
167 if i > 0 && gotCopy[i] == gotCopy[i-1] {
168 return fmt.Errorf("%s contains duplicate %q", name, gotCopy[i])
169 }
170 }
171 return nil
172 }
173
174 func requireUnique(name string, values []string) error {
175 seen := make(map[string]bool, len(values))
176 for _, value := range values {
177 if seen[value] {
178 return fmt.Errorf("%s contains duplicate %q", name, value)
179 }
180 seen[value] = true
181 }
182 return nil
183 }
184
185 func validateRepositoryPath(name string) error {
186 if name == "" || filepath.IsAbs(filepath.FromSlash(name)) {
187 return errors.New("path must be non-empty and repository-relative")
188 }
189 clean := filepath.Clean(filepath.FromSlash(name))
190 if clean == "." || clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) {
191 return errors.New("path escapes the repository")
192 }
193 if filepath.ToSlash(clean) != name {
194 return errors.New("path must be normalized with forward slashes")
195 }
196 return nil
197 }
198
199 func discoverTopLevelSigningWorkflows(root string) ([]string, error) {
200 paths, err := filepath.Glob(filepath.Join(root, ".github", "workflows", "*.y*ml"))
201 if err != nil {
202 return nil, fmt.Errorf("list workflows: %w", err)
203 }
204 workflows := make(map[string]workflowInfo, len(paths))
205 for _, path := range paths {
206 data, err := os.ReadFile(path)
207 if err != nil {
208 return nil, fmt.Errorf("read workflow %s: %w", path, err)
209 }
210 name, err := filepath.Rel(root, path)
211 if err != nil {
212 return nil, err
213 }
214 name = filepath.ToSlash(name)
215 info, err := parseWorkflow(data)
216 if err != nil {
217 return nil, fmt.Errorf("parse workflow %s: %w", name, err)
218 }
219 workflows[name] = info
220 }
221
222 state := make(map[string]uint8, len(workflows))
223 memo := make(map[string]bool, len(workflows))
224 var reachesSigning func(string) (bool, error)
225 reachesSigning = func(name string) (bool, error) {
226 if state[name] == 2 {
227 return memo[name], nil
228 }
229 if state[name] == 1 {
230 return false, fmt.Errorf("reusable workflow cycle includes %s", name)
231 }
232 info, ok := workflows[name]
233 if !ok {
234 return false, fmt.Errorf("workflow calls missing local workflow %s", name)
235 }
236 state[name] = 1
237 reachable := info.directSigning
238 for _, called := range info.calls {
239 calledReachable, err := reachesSigning(called)
240 if err != nil {
241 return false, err
242 }
243 reachable = reachable || calledReachable
244 }
245 state[name] = 2
246 memo[name] = reachable
247 return reachable, nil
248 }
249
250 var result []string
251 for name, info := range workflows {
252 if !info.externallyTriggered {
253 continue
254 }
255 reachable, err := reachesSigning(name)
256 if err != nil {
257 return nil, err
258 }
259 if reachable {
260 result = append(result, name)
261 }
262 }
263 sort.Strings(result)
264 return result, nil
265 }
266
267 func parseWorkflow(data []byte) (workflowInfo, error) {
268 var document yaml.Node
269 if err := yaml.Unmarshal(data, &document); err != nil {
270 return workflowInfo{}, err
271 }
272 if len(document.Content) != 1 || document.Content[0].Kind != yaml.MappingNode {
273 return workflowInfo{}, errors.New("workflow root must be a mapping")
274 }
275 root := document.Content[0]
276 on := mappingValue(root, "on")
277 jobs := mappingValue(root, "jobs")
278 if on == nil || jobs == nil || jobs.Kind != yaml.MappingNode {
279 return workflowInfo{}, errors.New("workflow must contain on and jobs mappings")
280 }
281
282 info := workflowInfo{externallyTriggered: hasExternalTrigger(on)}
283 for i := 1; i < len(jobs.Content); i += 2 {
284 job := jobs.Content[i]
285 if job.Kind != yaml.MappingNode {
286 continue
287 }
288 if uses := mappingScalar(job, "uses"); strings.HasPrefix(uses, "./.github/workflows/") {
289 info.calls = append(info.calls, strings.TrimPrefix(uses, "./"))
290 }
291 if nodeContains(job, "secrets.SIGNPATH_API_TOKEN") {
292 info.directSigning = true
293 }
294 steps := mappingValue(job, "steps")
295 if steps == nil || steps.Kind != yaml.SequenceNode {
296 continue
297 }
298 for _, step := range steps.Content {
299 if step.Kind != yaml.MappingNode {
300 continue
301 }
302 if strings.HasPrefix(mappingScalar(step, "uses"), "signpath/github-action-submit-signing-request@") {
303 info.directSigning = true
304 }
305 }
306 }
307 sort.Strings(info.calls)
308 return info, nil
309 }
310
311 func nodeContains(node *yaml.Node, text string) bool {
312 if node == nil {
313 return false
314 }
315 if node.Kind == yaml.ScalarNode && strings.Contains(node.Value, text) {
316 return true
317 }
318 for _, child := range node.Content {
319 if nodeContains(child, text) {
320 return true
321 }
322 }
323 return false
324 }
325
326 func hasExternalTrigger(node *yaml.Node) bool {
327 switch node.Kind {
328 case yaml.ScalarNode:
329 return node.Value != "workflow_call"
330 case yaml.SequenceNode:
331 for _, child := range node.Content {
332 if child.Value != "workflow_call" {
333 return true
334 }
335 }
336 case yaml.MappingNode:
337 for i := 0; i < len(node.Content); i += 2 {
338 if node.Content[i].Value != "workflow_call" {
339 return true
340 }
341 }
342 }
343 return false
344 }
345
346 func mappingValue(node *yaml.Node, key string) *yaml.Node {
347 if node == nil || node.Kind != yaml.MappingNode {
348 return nil
349 }
350 for i := 0; i < len(node.Content); i += 2 {
351 if node.Content[i].Value == key {
352 return node.Content[i+1]
353 }
354 }
355 return nil
356 }
357
358 func mappingScalar(node *yaml.Node, key string) string {
359 value := mappingValue(node, key)
360 if value == nil || value.Kind != yaml.ScalarNode {
361 return ""
362 }
363 return value.Value
364 }
365
366 func contractFingerprint(root string, contract releaseSigningContract) ([sha256.Size]byte, error) {
367 names := append([]string{contractPath}, contract.FingerprintFiles...)
368 sort.Strings(names)
369 hash := sha256.New()
370 _, _ = io.WriteString(hash, "reasonix-signpath-release-contract-v1\x00")
371 for _, name := range names {
372 data, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(name)))
373 if err != nil {
374 return [sha256.Size]byte{}, fmt.Errorf("read %s: %w", name, err)
375 }
376 writeLengthPrefixed(hash, []byte(name))
377 writeLengthPrefixed(hash, data)
378 }
379 var result [sha256.Size]byte
380 copy(result[:], hash.Sum(nil))
381 return result, nil
382 }
383
384 func writeLengthPrefixed(writer io.Writer, data []byte) {
385 var length [8]byte
386 binary.BigEndian.PutUint64(length[:], uint64(len(data)))
387 _, _ = writer.Write(length[:])
388 _, _ = writer.Write(data)
389 }
390
390 lines GO