File: design-document.md

package info (click to toggle)
gittuf 0.12.0-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 3,692 kB
  • sloc: python: 85; makefile: 58; sh: 1
file content (853 lines) | stat: -rw-r--r-- 42,063 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
# gittuf Design Document

Last Modified: May 29, 2025

## Introduction

This document describes gittuf, a security layer for Git repositories.
With gittuf, any developer who can pull from a Git repository can independently
verify that the repository's security policies were followed. gittuf's policy,
inspired by [The Update Framework (TUF)](https://theupdateframework.io/),
handles key management for all trusted developers in a repository, allows for
setting permissions for repository namespaces such as branches, tags, and files,
and provides protections against
[attacks targeting Git metadata](https://www.usenix.org/conference/usenixsecurity16/technical-sessions/presentation/torres-arias).
At the same time, gittuf is backwards compatible with existing source control
platforms ("forges") such as GitHub, GitLab, and Bitbucket. gittuf is currently
an incubating project at the
[Open Source Security Foundation (OpenSSF)](https://openssf.org/) as part of the
[Supply Chain Integrity Working Group](https://github.com/ossf/wg-supply-chain-integrity).
The core concepts of gittuf described in this document have been
[peer reviewed](https://ssl.engineering.nyu.edu/papers/yelgundhalli_gittuf_ndss_2025.pdf).

This document is scoped to describing how gittuf's write access control policies
are applied to Git repositories. Other additions to gittuf's featureset are
described in standalone [gittuf Augmentation Proposals (GAPs)](/docs/gaps/).

## Definitions

This document uses several terms or phrases in specific ways. These are defined
here.

### Git References (Refs) and Objects

A Git reference is a "simple name" that typically points to a particular Git
commit. Generally, development in Git repositories are centered in one or more
refs, and they're updated as commits are added to the ref under development. By
default, Git defines two of refs: branches ("heads") and tags. Git allows for
the creation of other arbitrary refs that users can store other information as
long as they are formatted using Git's object types.

Git employs a content addressed object store, with support for four types of
objects. An essential Git object is the "commit", which is a self-contained
representation of the whole repository. Each commit points to a "tree" object
that represents the state of the files in the root of the repository at that
commit. A tree object contains one or more entries that are either other tree
objects (representing subdirectories) or "blob" objects (representing files).
The final type of Git object is the "tag" object, used as a static pointer to
another Git object. While a tag object can point to any other Git object, it is
frequently used to point to a commit.

```
Repository
|
|-- refs
|   |
|   |-- heads
|   |   |-- main (refers to commit C)
|   |   |-- feature-x (refers to commit E)
|   |
|   |-- tags
|   |   |-- v1.0 (refers to tag v1.0)
|   |
|   |-- arbitrary
|       |-- custom-ref (formatted as Git object type)
|
|-- objects
    |-- A [Initial commit]
    |-- B [Version 1.0 release]
    |-- C [More changes on main]
    |-- D [Initial commit on feature-x]
    |-- E [More changes on feature-x]
    |-- v1.0 [Tag object referring to commit B]
```


### Actors and Authentication

In a Git repository, an "actor" is any party, human or bot, who makes changes to
the repository. These changes can involve any part of the repository, such as
modifying files, branches or tags. In gittuf, each actor is identified by a
unique signing key that they use to cryptographically sign their contributions.
gittuf uses cryptographic signatures to authenticate actors as these signatures
can be verified by anyone who has the corresponding public key, fundamental to
gittuf's mission to enable independent verification of repository actions. Note
that gittuf does not rely on Git commit metadata (e.g., author email, committer
email) to identify the actor who created it, as that may be trivially spoofed.

In practice, a gittuf policy allows an actor to make certain changes by granting
trust to the actor's signing key to make those changes. To maintain security,
all actions made in the repository, such as adding or modifying files, are
checked for authenticity. This is done by verifying the digital signature
attached to the action, which must match the trusted public key associated with
the actor who is supposed to have made the change.

### State

The term "state" refers to the latest values or conditions of the tracked
references (like branches and tags) in a Git repository.  These are determined
by the most recent entries in the
[reference state log](#reference-state-log-rsl). Note that when verifying
changes in the repository, a workflow may only verify specific references rather
than all state updates in the reference state log.

## Threat Model

The following threat model is taken from the
[peer reviewed publication](https://ssl.engineering.nyu.edu/papers/yelgundhalli_gittuf_ndss_2025.pdf)
describing gittuf.

We consider the standard scenario where a forge is used to manage a Git
repository on a centralized synchronization point. This forge can be a publicly
hosted solution (e.g., the github.com service), or self-hosted on premises by an
enterprise. Either option exposes the forge instance to both external attackers
and insider threats. External attackers may circumvent security measures and
compromise the version control system, manifesting themselves as advanced
persistent threats (APT) and making unnoticed changes to the system. Similarly,
insider threats may be posed by rogue employees with escalated privileges who
abuse their authority to make unnoticed changes.

To protect the integrity of the repository’s contents, the maintainers of the
repository define security controls such as which contributors can write to
different parts of the repository. gittuf is meant to protect against scenarios
where any party, individual developers, bots that make changes, or the forge
itself, may be compromised and act in an arbitrarily malicious way as seen in
prior incidents. This includes scenarios such as:

* T1: Modifying configured repository security policies, such as to weaken them
* T2: Tampering with the contents of the repository’s activity log, such as by
      reordering, dropping, or otherwise manipulating log entries
* T3: Subverting the enforcement of security policies, such as by accepting
      invalid changes instead of rejecting them

Note that we consider out of scope a freeze attack, where the forge serves stale
data, as development workflows involve a substantial amount of out-of-band
communication which prevents such attacks from going unnoticed. We similarly
consider weaknesses in cryptographic algorithms as out of scope.

## gittuf Design

gittuf records additional metadata describing the repository's policy and
activity in the repository itself. Effectively, gittuf treats security policies,
activity information, and policy decisions as a content tracking problem. To
avoid collisions with regular repository contents, gittuf stores its metadata in
custom references under `refs/gittuf/`.

### gittuf Policy

Note: This section assumes some prior knowledge of the
[TUF specification](https://theupdateframework.github.io/specification/).

The repository's policy metadata handles the distribution of the repository's
trusted keys (representing actors) as well as write access control rules. There
are two types of metadata used by gittuf, which are stored in a custom reference
`refs/gittuf/policy`.

#### Root of Trust

gittuf's policy metadata includes root of trust metadata, which
establishes why the policy must be trusted. The root of trust metadata (similar
to TUF's root metadata) declares the keys belonging to the repository owners as
well as a numerical threshold that indicates the minimum number of signatures
for the metadata to be considered valid. The root of trust metadata is signed by
a threshold of root keys, and the initial set of root keys for a repository must
be distributed using out-of-band mechanisms or rely on trust-on-first-use
(TOFU). Subsequent changes to the set of root keys are handled in-band, with a
new version of the root of trust metadata created. This new version must be
signed by a threshold of root keys trusted in the previous version.

#### Rule Files

The rules protecting the repository's namespaces are declared in one or more
rule files. A rule file is similar to TUF's targets metadata. It declares the
public keys for the trusted actors, as well as namespaced "delegations" which
specify protected namespaces within the repository and which actors are trusted
to write to them.

A threshold of trusted actors for any delegation (or rule) can extend this trust
to other actors by signing a new rule file with the same name as the delegation.
In this rule file, they can add the actors who must be trusted for the same (or
a subset) of namespaces.

All repositories must contain a primary rule file (typically called
"targets.json" to match TUF's behavior). This rule file may contain no rules,
signifying that no repository namespaces are protected. The primary rule file
derives its trust directly from the root of trust metadata; it must be signed by
a threshold of actors trusted to manage the repository's primary rule file. All
other rule files derive their trust directly or indirectly from the primary rule
file through delegations.

![Policy delegations](/docs/media/policy-delegations.png)
_In this example, the repository administrator grants write permissions to Carol 
for the main branch, to Alice for the alice-dev branch, and to Bob for the
/tests folder (under any of the existing branches)._

A significant difference between typical TUF metadata and those used by gittuf
is in the expectations of the policies. Typical TUF deployments are explicit
about the artifacts they are distributing. Any artifact not listed in TUF
metadata is rejected. In gittuf, policies are written only to express
_restrictions_. As such, when verifying changes to unprotected namespaces,
gittuf must allow any key to sign for these changes. This means that after all
explicit policies (expressed as delegations) are processed, and none apply to
the namespace being verified, an implicit `allow-rule` is applied, allowing
verification to succeed.

#### Example gittuf Policy

The following example is taken from the
[peer reviewed publication](https://ssl.engineering.nyu.edu/papers/yelgundhalli_gittuf_ndss_2025.pdf)
of gittuf's design. It shows a gittuf policy state with its root of trust and
three distinct rule files connected using delegations. The root of trust
declares the trusted signers for the next version of the root of trust as well
as the primary rule file. Signatures are omitted.

```
rootOfTrust:
keys: {R1, R2, R3, P1, P2, P3}
signers:
    rootOfTrust: (2, {R1, R2, R3})
    primary: (2, {P1, P2, P3})

ruleFile: primary
keys: {Alice, Bob, Carol, Helen, Ilda}
rules:
    protect-main-prod: {git:refs/heads/main,
                        git:refs/heads/prod}
        -> (2, {Alice, Bob, Carol})
    protect-ios-app: {file:ios/*}
        -> (1, {Alice})
    protect-android-app: {file:android/*}
        -> (1, {Bob})
    protect-core-libraries: {file:src/*}
        -> (2, {Carol, Helen, Ilda})

ruleFile: protect-ios-app
keys: {Dana, George}
rules:
    authorize-ios-team: {file:ios/*}
        -> (1, {Dana, George})

ruleFile: protect-android-app
keys: {Eric, Frank}
rules:
    authorize-android-team: {file:android/*}
        -> (1, {Eric, Frank})
```

### Tracking Repository Activity

gittuf leverages a "Reference State Log (RSL)" to track changes to the
repository's references. In addition, gittuf uses the
[in-toto Attestation Framework](https://github.com/in-toto/attestation) to
record other repository activity such as code review approvals.

#### Reference State Log (RSL)

Note: This document presents a summary of the RSL. For a full understanding of
the attacks mitigated by the RSL, please refer to the
[academic](https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_torres-arias.pdf)
[papers](https://ssl.engineering.nyu.edu/papers/yelgundhalli_gittuf_ndss_2025.pdf)
underpinning gittuf's design.

The Reference State Log contains a series of entries that each describe some
change to a Git ref. Such entries are known as RSL reference entries. Each entry
contains the ref being updated, the new location it points to, and a hash of the
parent RSL entry. The entry is signed by the actor making the change to the ref.

Additionally, the RSL supports annotation entries that refer to prior reference
entries. An annotation entry can be used to attach additional user-readable
messages to prior RSL entries or to mark those entries as revoked.

Given that each entry points to its parent entry using its hash, an RSL is a
hash chain. gittuf's implementation of the RSL uses Git's underlying Merkle
graph. Generally, gittuf is designed to ensure the RSL is linear but a
privileged attacker may be able to cause the RSL to branch, resulting in a fork*
attack where different actors are presented different versions of the RSL. The
feasibility and implications of such an attack are discussed later in this
document.

The RSL is tracked at `refs/gittuf/reference-state-log`, and is implemented as a
distinct commit graph. Each commit corresponds to one entry in the RSL, and
standard Git signing mechanisms are employed for the actor's signature on the
RSL entry. The latest entry is identified using the tip of the RSL Git ref.

Note that the RSL and liveness of the repository in Git remove the need for some
traditional TUF roles. As the RSL records changes to other Git refs in the
repository, it incorporates TUF's
[snapshot role](https://theupdateframework.github.io/specification/latest/#snapshot)
properties. At present, gittuf does not include an equivalent to TUF's
[timestamp role](https://theupdateframework.github.io/specification/latest/#timestamp)
to guarantee the freshness of the RSL. This is because the timestamp role in the
context of gittuf at most provides a non-repudiation guarantee for each claim of
the RSL's tip. The use of an online timestamp does not guarantee that actors
will receive the correct RSL tip. This may evolve in future versions of the
gittuf design.

##### RSL Reference Entries

These entries are the standard variety described above. They contain the name of
the reference they apply to and a target ID. As such, they have the following
structure.

```
RSL Reference Entry

ref: <ref name>
targetID: <target ID>
number: <number>
```

The `targetID` is typically the ID of a commit for references that are branches.
However, for entries that record the state of a Git tag, `targetID` is the ID of
the annotated tag object.

##### RSL Annotation Entries

Apart from regular entries, the RSL can include annotations that apply to prior
RSL entries. Annotations can be used to add more information as a message about
a prior entry, or to _explicitly_ mark one or more entries as ones to be
skipped. This semantic is necessary when accidental or possibly malicious RSL
entries are recorded. Since the RSL history cannot be overwritten, an annotation
entry must be used to communicate to gittuf clients to skip the corresponding
entries. Annotations have the following schema.

```
RSL Annotation Entry

entryID: <RSL entry ID 1>
entryID: <RSL entry ID 2>
...
skip: <true/false>
number: <number>
-----BEGIN MESSAGE-----
<message>
------END MESSAGE------
```

##### Example Entries

Here's a sample RSL, with the output taken from `gittuf rsl log`:

```
entry a5ea2c6ee7e8b577f6be6fcee5b06e6cac7166fa (skipped)

  Ref:    refs/heads/main
  Target: 6cb8e5c546eab3d0e1d245014de8003febb8e9b3
  Number: 5

    Annotation ID: cccfb6f27b2a71c33e9a55bc82f084e2445aa398
    Skip:          yes
    Number:        6
    Message:
      Skipping RSL entry

entry 40c82851f78c7018f4c360030a83923b0925c28d

  Ref:    refs/gittuf/policy
  Target: b7cf91ec9b5b6b17334ab1378dc85375236524f5
  Number: 4

entry 94c153bff6d684a956ed27f0abd70624e875657c

  Ref:    refs/gittuf/policy-staging
  Target: b7cf91ec9b5b6b17334ab1378dc85375236524f5
  Number: 3

entry fed977a5ca07e566af3a37808284dc7c5a67d6dc

  Ref:    refs/gittuf/policy-staging
  Target: dcbb536bd86a69e555692aec7b65c20de8257ee2
  Number: 2

entry e026a62f1c63c6db58bb357f9a85cafe05c64fb6

  Ref:    refs/gittuf/policy-staging
  Target: 603fc733218a0a1e54ccde47d1d9864f67e0bb75
  Number: 1
```

Specifically, the latest reference entry
`a5ea2c6ee7e8b577f6be6fcee5b06e6cac7166fa` has been skipped by an annotation
entry `cccfb6f27b2a71c33e9a55bc82f084e2445aa398`.

The commit object for the reference entry is as follows:

```bash
~/tmp/repo $ git cat-file -p a5ea2c6ee7e8b577f6be6fcee5b06e6cac7166fa
tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
parent 40c82851f78c7018f4c360030a83923b0925c28d
author Aditya Sirish A Yelgundhalli <ayelgundhall@bloomberg.net> 1729514863 -0400
committer Aditya Sirish A Yelgundhalli <ayelgundhall@bloomberg.net> 1729514863 -0400
gpgsig -----BEGIN SSH SIGNATURE-----
 U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAg8g2CmHSb7guzi6MUNgwHUQnxPN
 X1x8urScZyJrUB6MMAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
 AAAAQGQMSviwqF+cE/wgEo0U73vu86YHi4f5crzzFIctjyMGOOy2isYfHgGvSzs5bv6V2Q
 EtMumBSVbCxvnRqJpiFAs=
 -----END SSH SIGNATURE-----

RSL Reference Entry

ref: refs/heads/main
targetID: 6cb8e5c546eab3d0e1d245014de8003febb8e9b3
number: 5
```

Similarly, the commit object for the annotation entry is as follows:

```bash
~/tmp/repo $ git cat-file -p cccfb6f27b2a71c33e9a55bc82f084e2445aa398
tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
parent a5ea2c6ee7e8b577f6be6fcee5b06e6cac7166fa
author Aditya Sirish A Yelgundhalli <ayelgundhall@bloomberg.net> 1729514924 -0400
committer Aditya Sirish A Yelgundhalli <ayelgundhall@bloomberg.net> 1729514924 -0400
gpgsig -----BEGIN SSH SIGNATURE-----
 U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAg8g2CmHSb7guzi6MUNgwHUQnxPN
 X1x8urScZyJrUB6MMAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
 AAAAQNf32yJvhGfLIIeeStHgkSB7iuRGJl6LhbRTpX/q49lUu4TrEiCeGa3H8LMJ/5D1EE
 in3QAhlzdowYnmCKglTAw=
 -----END SSH SIGNATURE-----

RSL Annotation Entry

entryID: a5ea2c6ee7e8b577f6be6fcee5b06e6cac7166fa
skip: true
number: 6
-----BEGIN MESSAGE-----
U2tpcHBpbmcgUlNMIGVudHJ5
-----END MESSAGE-----
```

#### Attestations for Authorization Records

gittuf makes use of the signing capability provided by Git for commits and tags
significantly. However, it is sometimes necessary to attach more than a single
signature to a Git object or repository action. For example, a policy may
require more than one developer to sign off and approve a change such as merging
something to the `main` branch. To support these workflows (while also remaining
compatible with standard Git clients), gittuf uses the concept of "detached
authorizations", implemented using signed [in-toto
attestations](https://github.com/in-toto/attestation). Attestations are tracked
in the custom Git reference `refs/gittuf/attestations`. The gittuf design
currently supports the "reference authorization" type to represent code review
approvals. Other types may be added to this document or via [GAPs](/docs/gaps/)
in future.

A reference authorization is an attestation that accompanies an RSL reference
entry, allowing additional developers to issue signatures authorizing the change
to the Git reference in question. Its structure is similar to that of a
reference entry:

```
TargetRef    string
FromTargetID string
ToTargetID   string
```

The `TargetRef` is the Git reference the authorization is for, while
`FromTargetID` and `ToTargetID` record the change in the state of the reference
authorized by the attestation (as Git hashes). The information pertaining to the
prior state of the Git reference is explicitly recorded in the attestation
unlike a standard RSL reference entry. This is because, for a reference entry,
this information can be implicitly identified using the RSL by examining the
previous entry for the reference in question. If the authorization is for a
brand new reference (say a new branch or any tag), `FromTargetID` must be set to
zero. For a change to a branch, `ToTargetID` pre-computes the Git merge tree
resulting from the change being approved. Thus, when verifying the change to the
branch, it must be followed by an RSL reference entry that points to a commit
which has the same Git tree ID. For a tag, `ToTargetID` records the Git object
the tag object is expected to point to.

Reference authorizations are stored in a directory called
`reference-authorizations` in the attestations namespace. Each authorization
must have the in-toto predicate type:
`https://gittuf.dev/reference-authorization/v<VERSION>`.

## gittuf Workflows

gittuf introduces some new workflows that are gittuf-specific, such as the
creation of policies and their verification. In addition, gittuf interposes in
some Git workflows so as to capture repository activity information.

### Policy Initialization and Changes

When the policy is initialized or updated (this can be a change to the root of
trust metadata or one or more rule files), a new policy state is created that
contains the full set of gittuf policy metadata. This is recorded as a commit in
the custom ref used to track the policy metadata (typically
`refs/gittuf/policy`). In turn, the commit to the custom ref is recorded in the
RSL, indicating the policy state to use for subsequent changes in the
repository.

### Syncing gittuf References

As the RSL must be linear with no branches, gittuf employs a variation of the
`Secure_Fetch` and `Secure_Push` workflows described in the
[RSL academic paper](https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_torres-arias.pdf).

![Using gittuf with legacy servers](/docs/media/gittuf-with-legacy-servers.png)
_Note that gittuf can be used even if the synchronization point is not
gittuf-enabled. The repository can host the gittuf namespaces which other gittuf
clients can pull from for verification. In this example, a gittuf client with a
changeset to commit to the dev branch (step 1), creates in its local repository
a new commit object and the associated RSL entry (step 2). These changes are
pushed next to a remote Git repository (step 3), from where other gittuf or
legacy Git clients pull the changes (step 4)._

#### `RSLFetch`: Receiving Remote RSL Changes

Before local RSL changes can be made or pushed, it is necessary to verify that
they are compatible with the remote RSL state. If the remote RSL has entries
that are unavailable locally, entries made locally will be rejected by the
remote. For example, let the local RSL tip be entry A and the new entry be entry
C. If the remote has entry B after A with B being the tip, attempting to push C
which also comes right after A will fail. Instead, the local RSL must first
fetch entry B and then create entry C. This is because entries in the RSL must
be made serially. As each entry includes the ID of the previous entry, a local
entry that does not incorporate the latest RSL entry on the remote is invalid.
The workflow is as follows:

1. Fetch remote RSL to the local remote tracker
   `refs/remotes/origin/gittuf/reference-state-log`.
1. If the last entry in the remote RSL is the same as the local RSL, terminate
   successfully.
1. Perform the verification workflow for the new entries in the remote RSL,
   incorporating remote changes to the local policy namespace. The verification
   workflow is performed for each Git reference in the new entries, relative to
   the local state of each reference. If verification fails, abort and warn
   user. Note that the verification workflow must fetch each Git reference to
   its corresponding remote tracker, `refs/remotes/origin/<ref>`.
1. For each modified Git reference, update the local state. As all the refs have
   been successfully verified, each ref's remote state can be applied to the
   local repository, so `refs/heads/<ref>` matches `refs/remotes/origin/<ref>`.
1. Set local RSL to the remote RSL's tip.

NOTE: Some aspects of this workflow are under discussion and are subject to
change. The gittuf implementation does not implement precisely this workflow.
Specifically, the implementation does not verify new entries in the remote
automatically. Additionally, the RSL may contain entries for references a client
does not have, making verification of those entries unfeasible. See
https://github.com/gittuf/gittuf/issues/708.

#### `RSLPush`: Submitting Local RSL Changes

1. Execute `RSLFetch` repeatedly until there are no new RSL entries in the
   remote RSL. Every time there is a remote update, the user must be prompted to
   fetch and re-apply their changes to the RSL. This process could be automated
   but user intervention may be needed to resolve conflicts in the refs they
   modified. Changes to the gittuf policy must be fetched and applied locally.
1. Verify the validity of the RSL entries being submitted using locally
   available gittuf policies to ensure the user is authorized for the changes.
   If verification fails, abort and warn user.
1. Perform an atomic Git push to the remote of the RSL as well as the modified
   Git references. If the push fails, it is likely because another actor pushed
   their changes first. Restart the `RSLPush` workflow.

NOTE: Some aspects of this workflow are under discussion and are subject to
change. The gittuf implementation does not implement precisely this workflow.
This workflow is closely related to other push operations performed in the
repository, and therefore, this section may be incorporated with other
workflows. See https://github.com/gittuf/gittuf/issues/708.

### Regular Pushes

When an actor pushes a change to a remote repository, this update to the
corresponding ref (or refs) must be recorded in the RSL. For each ref being
pushed, the gittuf client creates a new RSL entry. Then, `RSLPush` is used to
submit these changes to the remote repository.

### Force Pushes

Due to the linear nature of the RSL, it is not possible to remove a reference
entry. A force push makes one or more prior reference entries for the pushed ref
invalid as the targets recorded in those entries may not be reachable any
longer. Thus, these entries must be marked as "skipped" in the RSL using an
annotation entry. After an annotation for these reference entries is created, a
reference entry is created recording the current state of the ref. Then,
`RSLPush` is used to submit these changes to the remote repository.

### Verification Workflow

There are several aspects to verification. First, the right policy state must be
identified by walking back RSL entries to find the last change to that
namespace. Next, authorized keys must be identified to verify that commit or RSL
entry signatures are valid.

#### Identifying Authorized Signers for Protected Namespaces

When verifying a commit or RSL entry, the first step is identifying the set of
keys authorized to sign a commit or RSL entry in their respective namespaces.
This is achieved by performing pre-ordered depth first search over the
delegations graph in a gittuf policy state. Assume the relevant policy state
entry is `P` and the namespace being checked is `N`. Then:

1. Validate `P`'s root metadata using the TUF workflow starting from the initial
   root of trust metadata, ignore expiration date checks (see
   https://github.com/gittuf/gittuf/issues/280).
1. Create empty set `K` to record authorized verifiers for `N`.
1. Create empty set `queue` to track the rules (or delegations) that must be
   checked.
1. Begin traversing the delegations graph rooted at the primary rule file
   metadata.
1. Verify the signatures of the primary rule file using the trusted keys in the
   root of trust. If a threshold of signatures cannot be verified, abort.
1. Populate `queue` with the rules in the primary rule file.
1. While `queue` is not empty:
   1. Set `rule` to the first item in `queue`, removing it from `queue`.
   1. If `rule` is the `allow-rule`:
      1. Proceed to the next iteration.
   1. If the patterns of `rule` match `N` (i.e., the rule applies to the
      namespace being verified):
      1. Create a verifier with the trusted keys in `rule` and the specified
         threshold.
      1. Add this verifier to `K`.
      1. If `P` contains a rule file with the same name as `rule` (i.e., a
         delegated rule file exists):
         1. Verify that the delegated rule file is signed by a threshold of
            valid signatures using the keys declared in delegating rule file.
            Abort if verification fails.
         1. Add the rules in `current` to the front of `queue` (ensuring the
            delegated rules are prioritized to match pre-order depth first
            search behavior).
1. Return `K`.

#### Verifying Changes Made

In gittuf, verifying the validity of changes is _relative_. Verification of a
new state depends on comparing it against some prior, verified state. For some
ref `X` that is currently at verified entry `S` in the RSL and its latest
available state entry is `D`:

1. Fetch all changes made to `X` between the commit recorded in `S` and that
   recorded in `D`, including the latest commit into a temporary branch.
1. Walk back from `S` until an RSL entry `P` is found that updated the gittuf
   policy namespace. This identifies the policy that was active for changes made
   immediately after `S`. If a policy entry is not found, abort.
1. Walk back from `S` until an RSL entry `A` is found that updated the gittuf
   attestations ref. This identifies the set of attestations applicable for the
   changes made immediately after `S`.
1. Validate `P`'s metadata using the TUF workflow, ignore expiration date
   checks (see https://github.com/gittuf/gittuf/issues/280).
1. Walk back from `D` until `S` and create an ordered list of all RSL updates
   that targeted either `X` or gittuf namespaces. Entries pertaining to other
   refs MAY be ignored. Annotation entries MUST be recorded.
1. The verification workflow has an ordered list of states
   `[I1, I2, ..., In, D]` that are to be verified.
1. Set trusted set for `X` to `S`.
1. For each set of consecutive states starting with `(S, I1)` to `(In, D)`:
   1. Check if an annotation exists for the second state. If it does, verify if
      the annotation indicates the state is to be skipped. It true, proceed to
      the next set of consecutive states.
   1. If second state changes gittuf policy:
      1. Validate new policy metadata using the TUF workflow and `P`'s contents
         to established authorized signers for new policy. Ignore expiration
         date checks (see https://github.com/gittuf/gittuf/issues/280). If
         verification passes, update `P` to new policy state.
   1. If second state is for attestations:
      1. Set `A` to the new attestations state.
   1. Verify the second state entry was signed by an authorized key as defined
      in `P` for the ref `X`. If the gittuf policy requires more than one
      signature, search for a reference authorization attestation for the same
      change. Verify the signatures on the attestation are issued by authorized
      keys to meet the threshold, ignoring any signatures from the same key as
      the one used to sign the entry.
   1. If `P` contains rules protecting files in the repository:
      1. Enumerate all commits between that recorded in trusted state and the
         second state with the signing key used for each commit.
      1. Identify the net or combined set of files modified between the commits
         in the first and second states as `F`.
      1. If all commits are signed by the same key, individual commits need not
         be validated. Instead, `F` can be used directly. For each path:
            1. Find the set of keys authorized to make changes to the path in
               `P`.
            1. Verify key used is in authorized set. If not, terminate
               verification workflow with an error.
      1. If not, iterate over each commit. For each commit:
         1. Identify the file paths modified by the commit. For each path:
            1. Find the set of keys authorized to make changes to the path in
               `P`.
            1. Verify key used is in authorized set. If not, check if path is
               present in `F`, as an unauthorized change may have been corrected
            subsequently. This merely acts as a hint as path may have been also
            changed subsequently by an authorized user, meaning it is in `F`. If
            path is not in `F`, continue with verification. Else, request user
            input, indicating potential policy violation.
      1. Set trusted state for `X` to second state of current iteration.
1. Return indicating successful verification.

NOTE: Some aspects of this workflow are under discussion and are subject to
change. The gittuf implementation does not implement precisely this workflow,
instead also including aspects of the recovery workflow to see if a change that
fails verification has already been recovered from. See
https://github.com/gittuf/gittuf/issues/708.

### Recovery

If every user were using gittuf and were performing each operation by
generating all of the correct metadata, following the specification, etc., then
the procedure for handling each situation is fairly straightforward. However,
an important property of gittuf is to ensure that a malicious or erroneous
party cannot make changes that impact the state of the repository in a negative
manner. To address this, this section discusses how to handle situations where
something has not gone according to protocol. The goal is to recover to a
"known good" situation which does match the metadata which a set of valid
gittuf clients would generate.

#### Recovery Mechanisms

When gittuf verification fails, the following recovery workflow must be
employed. This mechanism is utilized in scenarios where some change is rejected.
For example, one or more commits may have been pushed to a branch that do not
meet gittuf policy. The repository is updated such that these commits are
neutralized and all Git refs match their latest RSL entries. This can take two
forms:

1. The rejected commit is removed and the state of the repo is set to the prior
commit which is known to be good. This is used when all rejected commits are
together at the end of the commit graph, making it easy to remove all of them.

2. The rejected commit is _reverted_ where a new commit is introduced that
reverses all the changes made in the reverted commit. This is needed when "good"
commits that must be retained are interspersed with "bad" commits that must be
rejected.

In both cases, new RSL entries and annotations must be used to record the
incident and skip the invalid RSL entries corresponding to the rejected changes.

gittuf, by default, prefers the second option, with an explicit revert commit
that is tree-same as the last good commit. This ensures that a client can always
fast-forward to a fix rather than rewind. By resetting the affected branch to a
prior good commit, Git clients that have already pulled in the invalid commit
will not reset as well. Instead, they will assume they are ahead of the remote
in question and will continue to use the bad commit as the latest commit.

When the gittuf verification workflow encounters an RSL entry for some Git
reference that does not meet policy, it looks to see if a subsequent entry for
the same reference contains a fix that aligns with the last known good state.
Any intermediate entries between the original invalid entry and the fix for the
reference in question are also considered to be invalid. Therefore, in addition
to the fix RSL entry, gittuf also expects skip annotations for the original
invalid entry and intermediate entries for the reference.

#### Recovery Scenarios

These scenarios are some examples where recovery is necessary. This is not meant
to be an exhaustive set of gittuf's recovery scenarios.

##### An Incorrect RSL Entry is Added

There are several ways in which an RSL entry can be considered "incorrect". If
an entry is malformed (structurally), Git may catch it if it's not a valid
commit. In such instances, the push from a buggy client is rejected altogether,
meaning other users are not exposed to the malformed commit.

Invalid entries that are not rejected by Git must be caught by gittuf. Some
examples of such invalid entries are:

* RSL entry is for a non-existing Git reference
* Commit recorded in RSL entry does not exist
* Commit recorded in RSL entry does not match the tip of the corresponding Git
  reference
* RSL annotation contains references to RSL entries that do not exist or are not
  RSL entries (i.e. the annotation points to other commits in the repository)

Note that as invalid RSL entries are only created by buggy or malicious gittuf
clients, these entries cannot be detected prior to them being pushed to the
synchronization point.

As correctly implemented gittuf clients verify the validity of RSL entries when
they pull from the synchronization point, the user is warned if invalid entries
are encountered. Then, the user can then use the recovery workflow to invalidate
the incorrect entry. Other clients with the invalid entry only need to fetch the
latest RSL entries to recover. Additionally, the client that created the invalid
entries must switch to a correct implementation of gittuf before further
interactions with the main repository, but this is left to out-of-band
synchronization between the actors who notice the issue and the actor using a
buggy client.

##### A gittuf Access Control Policy is Violated

An actor, Bob, creates an RSL entry for a branch he's not authorized for by
gittuf policy. He pushes a change to that branch. Another actor, Alice, notices
this when her gittuf client indicates a failure in the verification workflow.
Alice creates an RSL annotation marking Bob's entry as one to be skipped. Alice
also reverses Bob's change, creating a new RSL entry reflecting that.

##### Attacker Modifies or Deletes Historical RSL Entry

Overwriting or deleting an historical RSL entry is a complicated proposition.
Git's content addressable properties mean that a SHA-1 collision is necessary to
overwrite an existing RSL entry in the Git object store. Further, the attacker
also needs more than push access to the repository as Git will not accept an
object it already has in its store. Similarly, deleting an entry from the object
store preserves the RSL structure cosmetically but verification workflows that
require the entry will fail. This ensures that such an attack is detected, at
which point the owners of the repository can restore the RSL state from their
local copies.

Also note that while Git uses SHA-1 for its object store, cryptographic 
signatures are generated and verified using stronger hash algorithms. Therefore,
a successful SHA-1 collision for an RSL entry will not go undetected as all
entries are signed.

##### Forge Attempts Fork* Attacks

An attacker who controls the forge may attempt a fork* attack where different
developers receive different RSL states. For example, the attacker may drop a
push from an actor, Alice, from the RSL. Other developers such as Bob and Carol
would continue adding their RSL entries, unaware of the dropped entry. However,
Alice will observe the divergence in the RSL as she cannot receive Bob's and
Carol's changes.

The attacker cannot simply reapply Bob's and Carol's changes over Alice's RSL
entry without also controlling Bob's and Carol's keys. The attacker may attempt
a freeze attack targeted against Alice, where she's always told her entry is the
latest in the RSL. However, any out-of-band communication between Alice and
either Bob or Carol (common during development workflows) will highlight the
attack.

##### An Authorized Key is Compromised

When a key authorized by gittuf policy is compromised, it must be revoked and
rotated so that an attacker cannot use it to sign repository objects. gittuf
policies that grant permissions to the key must be updated to revoke the key,
possibly adding the actor's new key in the process. Further, if a security
analysis shows that the key was used to make malicious changes, those changes
must be reverted and the corresponding RSL entries signed with the compromised
key must be skipped. This ensures that gittuf clients do not consider attacker
created RSL entries as valid states for the corresponding Git references.
Clients that have an older RSL from before the attack can skip past the
malicious entries altogether.

## Example of Using gittuf

Consider project `foo`'s Git repository maintained by Alice and Bob. Alice and
Bob are the only actors authorized to update the state of the main branch. This
is accomplished by defining a TUF delegation to Alice and Bob's keys for the
namespace corresponding to the main branch. All changes to the main branch's
state MUST have a corresponding entry in the repository's RSL signed by either
Alice or Bob.

Further, `foo` has another contributor, Clara, who does not have maintainer
privileges. This means that Clara is free to make changes to other Git branches
but only Alice or Bob may merge Clara's changes from other unprotected branches
into the main branch.

Over time, `foo` grows to incorporate several subprojects with other
contributors Dave and Ella. Alice and Bob take the decision to reorganize the
repository into a monorepo containing two projects, `bar` and `baz`. Clara and
Dave work exclusively on bar and Ella works on baz with Bob. In this situation,
Alice and Bob retain their privileges to merge changes to the main branch.
Further, they set up delegations for each subproject's path within the
repository. Clara and Dave are only authorized to work on files within `bar/*`
and Ella is restricted to `baz/*`. As Bob is a maintainer of foo, he is not
restricted to working only on `baz/*`.