11institutetext: Norwegian University of Science and Technology, Trondheim,
Norway

# Autonomous Vehicle Control: End-to-end Learning in Simulated Urban
Environments

Hege Haavaldsen These authors contributed equally to this work.
0000-0001-6372-1989 Max Aasbø* 0000-0003-3462-165X Frank Lindseth
Corresponding author. 0000-0002-4979-9218

###### Abstract

In recent years, considerable progress has been made towards a vehicle’s
ability to operate autonomously. An end-to-end approach attempts to achieve
autonomous driving using a single, comprehensive software component. Recent
breakthroughs in deep learning have significantly increased end-to-end
systems’ capabilities, and such systems are now considered a possible
alternative to the current state-of-the-art solutions.

This paper examines end-to-end learning for autonomous vehicles in simulated
urban environments containing other vehicles, traffic lights, and speed
limits. Furthermore, the paper explores end-to-end systems’ ability to execute
navigational commands and examines whether improved performance can be
achieved by utilizing temporal dependencies between subsequent visual cues.

Two end-to-end architectures are proposed: a traditional Convolutional Neural
Network and an extended design combining a Convolutional Neural Network with a
recurrent layer. The models are trained using expert driving data from a
simulated urban setting, and are evaluated by their driving performance in an
unseen simulated environment.

The results of this paper indicate that end-to-end systems can operate
autonomously in simple urban environments. Moreover, it is found that the
exploitation of temporal information in subsequent images enhances a system’s
ability to judge movement and distance.

###### Keywords:

End-to-end learning Imitation Learning Autonomous Vehicle Control Artificial
Intelligence Deep Learning

##  1 Introduction

We are currently at the brink of a new paradigm in human travel: the fully
autonomous, self-driving car. Only 50 years ago, cars were completely analog
devices with almost no mechanisms for assisting the driver. Over the decades,
additional features, controls, and technologies have been integrated, and cars
have evolved into exceedingly complex machines.

In recent years, substantial progress has been made towards a vehicle’s
ability to operate autonomously. Primarily, two different approaches have
emerged. The prevailing state of the art approach is to divide the problem
into a number of sub-problems and solve them by combining techniques from
computer vision, sensor fusion, localization, control theory, and path
planning. This approach requires expert knowledge in several domains and often
results in complex solutions, consisting of several cooperating modules.

Another approach is to develop an end-to-end solution, solving the problem
using a single, comprehensive component, e.g., a deep neural network. A
technique for training such a system is to employ imitation learning. This
entails studying expert decisions in different scenarios, to find a mapping
between the perceived environments and the executed actions. While some
believe that the black-box characteristics of such systems makes them
untrustworthy and unreliable, others point to recent years’ advances in deep-
learning and argue that end-to-end solutions show great potential.

However, end-to-end systems cannot make the correct navigational decision
solely based on a perceived environment. It is also necessary to incorporate a
user’s intent in situations that require a decision (e.g., when approaching an
intersection). Hence, an end-to-end system should be able to receive and adapt
to navigational commands.

The objective of this paper is to investigate end-to-end systems’ ability to
drive autonomously in simulated urban environments. Specifically, to study
their performance in environments containing other vehicles, traffic lights,
and speed limits; to examine their ability to oblige navigational commands in
intersections; and to explore if a system can improve its performance by
utilizing temporal dependencies between subsequent visual cues.

This paper seeks to combine different aspects from recent research in the
field of end-to-end learning for autonomous vehicles. Concretely, the use of
navigational commands as network input, and the exploitation of temporal
dependencies between subsequent images. There have been no attempts - to our
knowledge - to combine both techniques in one system. Hopefully, this can lead
to a more complete end-to-end system and improved driving quality.

The rest of this paper is organized as follows. Section 2 presents previous
related work, while Section 3 addresses the environment in which the data was
collected and the experiments were conducted. Section 4 reviews the collection
and preprocessing of the data. The model architectures are presented in
Section 5. Section 6 and 7 covers the experimental setup and results, while
section 8 discusses the results. Finally, Section 9 covers the conclusions.

##  2 Related Work

There have been several advances in end-to-end learning for autonomous
vehicles over the last decades. The first approach was seen already in 1989
when a fully connected neural network was used to control a vehicle [10]. In
2003 a proof-of-concept project called DAVE emerged [9], showing a radio
controlled vehicle being able to drive around in a junk-filled alley and
avoiding obstacles. DAVE truly showed the potential of an end-to-end approach.
Three years later NVIDIA developed DAVE-2 [1], a framework with the objective
to make real vehicles drive reliably on public roads. DAVE-2 is the basis for
most end-to-end approaches seen today [2, 7]. The project used a CNN to
predict a vehicle’s steering commands. Their model was able to operate on
roads with or without lane markings and other vehicles, as well as parking
lots and unpaved roads.

Codevilla et al. [2] further explored NVIDIA’s architecture by adding
navigational commands to incorporate the drivers intent into the system and
predicted both steering angle and acceleration. The authors proposed two
network architectures: a branched network and a command input network. The
branched network used the navigational input as a switch between a CNN and
three fully connected networks, each specialized to a single intersection
action, while the command input network concatenated the navigational command
with the output of the CNN, connected to a single fully connected network.

Hubschneider et al. [7] proposed using turn signals as control commands to
incorporate the steering commands into the network. Furthermore, they proposed
a modified network architecture to improve driving accuracy. They used a CNN
that receives an image and a turn indicator as input such that the model could
be controlled in real time. To handle sharp turns and obstacles along the road
the authors proposed using images recorded several meters back to obtain a
spatial history of the environment. Images captured 4 and 8 meters behind the
current position were added as an input to make up for the limited vision from
a single centered camera.

Eraqi, H.M. et. al.[4] tried to utilize the temporal dependencies by combining
a CNN with a Long Short-Term Memory Neural Network. Their results showed that
the C-LSTM improved the angle prediction accuracy by 35 % and stability by 87
%.

##  3 Environment

Training and testing models for autonomous driving in the physical world can
be expensive, impractical, and potentially dangerous. Gathering a sufficient
amount of training data requires both human resources and suitable hardware,
and it can be time consuming to capture, organize and label the desired
driving scenarios. Moreover, the cost of unexpected behavior while testing a
model may be considerable.

An alternative is to train and test models in a simulated environment. A
simulator can effectively provide a variety of corner cases needed for
training, validation, and testing; while removing safety risks and material
costs. Additionally, the labeling of the dataset can be automated, removing
the cost of manual labeling, as well as the potential of human error.

The drawback, however, is the loss of realism. A simulation is only an
imitation of a real-world system, and a model trained on only simulated data
may not be able to function reliably in the real world. Nonetheless, a
simulator can give a good indication of a model’s actual driving performance
and serves well for benchmarking different models. Once a model can perform
reliably in a simulated environment, the model can be fine-tuned for further
testing in real environments.

In this paper, the CARLA simulator [3] is used to gather training data and to
evaluate the proposed models. CARLA is an open source simulator built for
autonomous driving research and provides an urban driving environment
populated with buildings, vehicles, pedestrians, and intersections.

##  4 Data Generation

###  4.1 Data Collection

When performing imitation learning, the quality of the training data plays a
significant role in a model’s ability to perform reliably in different
conditions. However, a model trained only using expert data in ideal
environments may not learn how to recover from perturbations. To overcome
this, several types of driving data was captured. Expert driving was captured
using CARLA’s built-in autopilot, resulting in center-of-lane driving while
following speed-limits. To capture more volatile data, a randomly generated
noise value was added to the autopilot’s outgoing control signal. This
resulted in sudden shifts in the vehicle’s trajectory and speed, which the
autopilot subsequently tried to correct. To eliminate undesirable behavior in
the training set, only the autopilot’s response to the noise was collected,
not the noisy control signal. Finally, recovery from possible disaster states
was captured by manually steering the vehicle into undesired locations, e.g.,
the opposite lane, or the sidewalk; while recording the recovery.

For each recorded frame, images from three forward-facing cameras (positioned
at the left, center, and right side of the vehicle) were captured, along with
the vehicle’s control signal (i.e., steering angle, throttle, and brake
values), and additional information (i.e., speed, speed limit, traffic light
state, and High-Level Command). The High-Level Command (HLC) is the active
navigational command, labeling the data with the user’s current intent.
Possible HLCs are: follow lane, turn left at the next intersection, turn right
at the next intersection, and continue straight ahead at the next
intersection.

Two different datasets were gathered, one for training and one for testing.
The training set was captured in CARLA’s Town 1, while the test set was
captured in Town 2. Data were gathered in four different weather conditions:
Clear noon, cloudy noon, clear sunset, and cloudy sunset. The training set
contained driving data captured both with and without other vehicles. The test
set exclusively contained driving data alongside other vehicles. All data were
captured in environments without pedestrians. Table 1 summarizes the gathered
datasets. All expert data was captured driving 10 km/h below the speed limit
to match the velocity of other vehicles.

Table 1: The collected datasets. An observation contains the captured data from a single rendered frame in the simulator. Dataset | Number of Observations | Size [GB]
---|---|---
Training | 117 889 | 31.5
Testing | 23 173 | 8.32

###  4.2 Data Preparation

For each recorded observation, a data sample was created containing the center
image, the vehicle’s control signal, and the additional information. Moreover,
to simulate the recovery from drifting out of the lane, two new data samples
were generated using the observation’s left and right images. To counteract
the left and right images’ positional offset, the associated steering angle
was shifted by +0.1 and -0.1 respectively.

For each data sample, a new augmented sample was generated using one of the
desirable transformations picked at random. These included a random change in
brightness or contrast, the addition of Gaussian noise or blur, and the
addition of randomly generated dark polygons differing in position and shape.

When recording the datasets, the majority of the observations were captured
driving straight. To prevent an unbalanced dataset, data samples with a small
steering angle were downsampled (by removal), while data samples with a large
steering angle were upsampled (by duplication). Additionally, the data samples
corresponding to the different intersection decisions (i.e., turn left, turn
right, or straight ahead) were balanced by analyzing the distributions of HLC-
properties in the dataset and downsampling the over-represented choices.
Finally, some of the data samples where the vehicle was not moving (e.g.,
waiting for a red light) were downsampled.

##  5 Model Architectures

In this paper, two related end-to-end architectures are proposed. The first is
a Convolutional Neural Network (CNN) inspired by NVIDIA’s DAVE-2 [1] system,
while the second extends the CNN with Long-Short-Term-Memory (LSTM) units to
capture temporal dynamic behavior.

###  5.1 CNN Model

The CNN model consists of two connected modules: a feature extractor and a
prediction module. The former uses a CNN to extract useful features from the
input image, while the latter combines the detected features with the
additional inputs (i.e., current speed, speed limit, traffic light state, and
HLC) to predict a control signal (i.e., steering angle, throttle and brake
values).

The convolutional part of the model is inspired by the architecture used in
NVIDIA’s DAVE-2 system [1]. The modified network takes a 180x300x3 image as
input, followed by a cropping layer and a normalization layer. The cropping
layer removes the top 70 pixels from the image, while the normalization layer
scales the pixel values between -0.5 and 0.5. Next follows six convolutional
layers, all using a ReLU activation function. The first three layers use a 5x5
filter, while the last three use a 3x3 filter. The first four layers use a
stride of 2, while the last two use a stride of 1. The output of the last
convolutional layer is flattened resulting in a one-dimensional feature layer
containing 768 nodes.

The output from the convolutional layers is concatenated with the additional
input containing the speed, speed-limit, traffic-sign, and HLC values. The
concatenated layer serves as an input for the predictive part of the model
which consists of three dense layers containing 100, 50, 10 nodes
respectively. All the dense layers use a ReLU activation function. The last of
the dense layers are finally connected to an output layer, consisting of 3
nodes. The complete architecture is shown in Figure 1.

Figure 1: The architecture of the CNN model. The model accepts a single RGB
image and predicts a control signal.

###  5.2 CNN-LSTM Model

The CNN-LSTM model consists of two connected modules: a feature extractor and
a temporal prediction module. The former follows the same architecture as the
previous model, shown in Figure 1. The feature extractor is connected to an
LSTM layer with 5 hidden states. The model uses a sequence of feature
extractions over time to predict a control signal. This allows the model to
learn temporal dependencies between time steps.

For each time step, the output of the feature extractor is concatenated with
the additional input containing speed, speed limit, traffic light, and an HLC.
This is sent through a dense layer containing 100 nodes and fed into an LSTM
layer with 10 nodes. For each time step in the sequence, the LSTM layer sends
its output to itself. At the last time step, the output is sent through a
dense layer, consisting of three nodes. This is the final prediction. The
complete architecture is shown in Figure 2.

Figure 2: The architecture of the CNN-LSTM model.

##  6 Experimental Setup

###  6.1 Training

####  6.1.1 Training and validation

The dataset was split into a training set (70%) and a validation set (30%).

For the CNN-LSTM, the data samples were further structured into sequences of
length five, using a sampling interval of three. The sequence length
determines the number of time steps the LSTM layer is able to remember, while
the sampling interval decides the period between successive individual time
steps within the sequences. Figure 3 illustrates the structuring of sequences
from 15 data samples.

Figure 3: The structuring of sequences from an array of data samples. A
sequence length of five and a sampling interval of three is used.

####  6.1.2 Hyperparameters

Both models were trained using an Adam optimizer [8] and an Mean Squared Error
loss function. The models were trained for a 100 epochs, with a batch size of
32 data-samples. The models’ weights were recorded after each epoch along with
the associated validation error. After the training was complete, the weights
associated with the lowest validation error were chosen for further testing.

###  6.2 Testing

After training, each model’s predictive and real-time performance was
measured. The predictive performance was tested by exposing the models to the
unseen test set while calculating the average prediction error. The real-time
performance was tested by letting the models control a simulated vehicle in
CARLA’s second town. Each model was to drive through a predefined route ten
times. The test evaluator provided HLCs to the model before each intersection.
A model’s performance was measured in the number of route completions, the
average route completion percentage, and the number of failures. Model
failures were recorded according to severity. Touching a lane line was
considered a minor failure, while a low-speed rear-ending or an object
collision was considered a moderate failure. Object collisions without
recovery or an ignored HLC were considered a severe failure. Catastrophic
failures consisted of either entering the opposite lane, disregarding a red
traffic light, or colliding with oncoming traffic.

##  7 Experimental Results

###  7.1 Validation and Test Error

After 100 epochs of training, both models’ training and validation loss had
stabilized. The CNN and CNN-LSTM had their lowest validation error after epoch
66 and 81 respectively. The models were then evaluated on the unseen test set
from CARLA’s second town. On the test set, the CNN was able to predict a
control signal with an average error of 0.023, a 43% increase compared to its
validation error. The CNN-LSTM was able to predict a control signal with an
average error of 0.022, a 57% increase compared to its validation error.

Table 2: Test and validation loss during the training of the models. Model | Validation Loss | Test Loss
---|---|---
CNN | 0.016 | 0.023
CNN-LSTM | 0.014 | 0.022

###  7.2 Real-time Test in Simulated Environment

Both models were real-time tested in CARLA’s second town. To test their
ability to handle various driving scenarios in an urban environment, each
model drove a predefined route ten times. The results are described below. Two
videos were created to show a successful run and some common failures. One
demonstrates the CNN model [5] and the other demonstrates the CNN-LSTM model
[6].

Table 3:  Summary of test results. Each model attempted to drive a predefined route (Figure LABEL:figure:town2_map) ten times. A model’s performance were measured in the number of route completions and the average route completion percentage.  Model | Route Completions | Avg. Route Completion
---|---|---
CNN | 2 | $56\%\pm 39$
CNN-LSTM | 5 | $81\%\pm 9$
Table 4: Average failures per run. The models’ failures were recorded according to severity. Touching a lane line was considered a minor failure, while a low-speed rear-ending or an object collision was considered a moderate failure. Object collisions without recovery or an ignored HLC were considered a severe failure. A catastrophic failure consisted of either entering the opposite lane, disregarding a red traffic light, or colliding with oncoming traffic. Model | Minor | Moderate | Severe | Catastrophic
---|---|---|---|---
CNN | $2.10\pm 1.73$ | $0.65\pm 0.99$ | $0.25\pm 0.44$ | $0.23\pm 0.57$
CNN-LSTM | $2.90\pm 1.72$ | $0.10\pm 0.31$ | $0.05\pm 0.22$ | $0.17\pm 0.38$

####  7.2.1 CNN

Out of ten runs, the CNN model was able to complete the predefined route
twice, with an average completion percentage of 56% over all runs. The model
performed well on lane following and drove reliably in the center of the lane
most of the time. The model handled most intersections but had a tendency to
perform very sharp right turns. This resulted in 2.1 minor failures (i.e., the
vehicle touching the lane line) per run. It always tried to follow the
provided HLC, and never ignored a traffic light. It was able to handle complex
light conditions, such as direct sunlight and dark shadows in the road. The
model hit objects without recovery several times, leading to 0.5 severe
failures per run. The vehicle rarely hit objects outside of the lane, but
occasionally struggled to stop for other vehicles, leading to several low
speed rear-end collisions. In total, the model had 0.1 object collisions, and
1.2 rear-end collisions per run. Finally, 0.7 times per run it struggled to
find the lane after a turn, leading to a catastrophic failure. The model
usually held the speed limit but found it hard to slow down fast enough to a
speed limit when the speed was high. The results are summarized in Table 3 and
Table 4.

####  7.2.2 CNN-LSTM

The CNN-LSTM model was able to complete the predefined route five out of ten
times, with an average completion percentage of 81% over all runs. It drove
reliably in the center of the lane most of the time but tended to perform
sharp turns. This lead to 2.9 minor failures (i.e., lane line touches) per
run. The model always tried to follow the provided HLC, and never ignored a
traffic light. It managed to handle various light conditions, such as direct
sunlight and dark shadows in the road. The vehicle rarely struggled to stop
for other objects or vehicles, resulting in 0.1 object collisions and 0.1
rear-end collisions per run. Finally, 0.5 times per run it struggled to find
the lane after a turn, leading to a catastrophic failure. The model adapted
well to the different speed limits. The results are summarized in Table 3 and
Table 4.

##  8 Discussion

This paper proposed two architectures for an end-to-end system: a traditional
CNN inspired by NVIDIA’s DAVE-2 system [1], and an extended design combining
the CNN with an LSTM layer to facilitate learning of temporal relationships.
Both models were able to follow a lane consistently and reliably. Any
disturbances or shifts in the trajectory were quickly corrected, without being
overly sensitive. The CNN exhibited less volatile steering compared to the
CNN-LSTM in the high-speed stretch of the route, but the CNN-LSTM outperformed
the CNN in turns following high-speed stretches. Additionally, both models
obeyed all traffic lights and always tried to follow the provided HLC at
intersections.

When introducing other vehicles, the differences between the models became
more apparent. The CNN-LSTM adapted its speed according to traffic, and only
rear-ended another vehicle once throughout the whole experiment. In scenarios
where another vehicle blocked most of the view (e.g., when following another
vehicle closely in a turn), the CNN-LSTM seemed to be able to use past
predictions as a guide. The CNN, on the other hand, experienced more trouble
when driving alongside other vehicles. Although the model, to some degree,
adapted its speed according to traffic, it often failed to react upon sudden
changes. This led to frequent rear-endings throughout the experiment.

The difference between the models’ performance may be explained in their
architectural differences. The CNN-LSTM model used five subsequent
observations when making predictions. This allowed it, by all indication, to
learn some important temporal dependencies - acquiring some knowledge about
the relationships between movement, change in object size, and distance. The
CNN model, however, interpreted each observation independently, which
restrained its ability to understand motion. It still learned to brake when
approaching a vehicle, but was not able to differentiate between fast
approaching and slow approaching objects. Predictions related to distance were
solely dependent on the size of objects. Moreover, the CNN could not rely on
past predictions when faced with confusing input, which seemed to result in
more unreliable behavior.

It should be mentioned that although the CNN model had 28 % less minor
failures than the CNN-LSTM model, its completion rate was 32 % lower than the
CNN-LSTM. The reduction in minor failures was probably a result of the lower
average route completion, not an indication of better performance.

###  8.1 Consistency with Related Work

The implemented models in this paper are based on the architecture in [1]. The
authors were able to use a CNN to drive on trafficked roads with and without
lane markings, parking lots and unpaved roads. This complies with this paper’s
results. Even though the implemented models were not tested on unmarked roads
or parking lost, they were able to drive on roads with lane marking, both on
roads with and without pavements.

Codevilla et. al. [2] claimed that their command input network performed
inadequately when executing navigational commands. This does not comply with
the results of this paper. The proposed architecture takes the navigational
command as input after the CNN, in a similar matter to the command input
network, but was able to execute the given navigational commands with a high
degree of success.

In [7] the turn indicators of the car was used as the navigational commands,
which were sent as input to the network. The authors did not use an RNN, but
fed three subsequent images to three CNNs and concatenated the output. It was
able to perform lane following, avoid obstacles and change lanes. The
navigational commands in this paper were introduced to the network in a
similar way, and both approaches were able to execute the navigational
commands. The proposed system was not tested for lane changes, but seeing
achievements in similar approaches indicates that this should be possible.

The CNN model in this paper was extended with an LSTM to utilize temporal
dependencies. A similar approach was attempted in [4]. They showed that adding
temporal dependencies improved both the accuracy and stability of a model
using a single CNN. Similar results can be seen in this paper.

##  9 Conclusion

The results of the experiments indicates that end-to-end systems are able to
operate autonomously in simulated urban environments. The proposed systems
managed to follow lanes reliably in varying lighting conditions and were not
disrupted by disturbances or shifts in trajectory. They were able to abide by
traffic lights and speed limits and learned to execute different navigational
commands at intersections.

Both systems managed to adapt its speed according to traffic, but their
ability to respond to sudden changes varied. The CNN-LSTM were, by all
indication, able to acquire some insight into the relationships between
movement, distance, and change in the perceived size of objects. The regular
CNN, interpreting each observation independently, was not able to learn these
essential temporal dependencies. Hence, the results suggest that exploiting
temporal information in subsequent images improves an end-to-end systems
ability to drive reliably in an urban environment.

Even though the systems’ performed several mistakes during testing, their
achievements demonstrated great potential for using end-to-end systems to
accomplish fully autonomous driving in urban environments.

## References

  * [1] Bojarski, M., Testa, D.D., Dworakowski, D., Firner, B., Flepp, B., Goyal, P., Jackel, L.D., Monfort, M., Muller, U., Zhang, J., Zhang, X., Zhao, J., Zieba, K.: End to end learning for self-driving cars. CoRR abs/1604.07316 (2016), http://arxiv.org/abs/1604.07316
  * [2] Codevilla, F., Müller, M., Dosovitskiy, A., López, A., Koltun, V.: End-to-end driving via conditional imitation learning. CoRR abs/1710.02410 (2017), http://arxiv.org/abs/1710.02410
  * [3] Dosovitskiy, A., Ros, G., Codevilla, F., López, A., Koltun, V.: CARLA: an open urban driving simulator. CoRR abs/1711.03938 (2017), http://arxiv.org/abs/1711.03938
  * [4] Eraqi, H.M., Moustafa, M.N., Honer, J.: End-to-end deep learning for steering autonomous vehicles considering temporal dependencies. CoRR abs/1710.03804 (2017), http://arxiv.org/abs/1710.03804
  * [5] Haavaldsen, H., Aasbø, M.: Autonomous driving using a cnn: An example of end-to-end learning, https://youtu.be/Q37jTFZjK2s
  * [6] Haavaldsen, H., Aasbø, M.: Autonomous driving using a cnn-lstm: An example of end-to-end learning, https://youtu.be/ADzEHGmIDQQ
  * [7] Hubschneider, C., Bauer, A., Weber, M., Zöllner, J.M.: Adding navigation to the equation: Turning decisions for end-to-end vehicle control. In: 2017 IEEE 20th International Conference on Intelligent Transportation Systems (ITSC). pp. 1–8 (Oct 2017). https://doi.org/10.1109/ITSC.2017.8317923
  * [8] Kingma, D.P., Ba, J.: Adam: A method for stochastic optimization. In: 3rd International Conference on Learning Representations, ICLR 2015, San Diego, CA, USA, May 7-9, 2015, Conference Track Proceedings (2015), http://arxiv.org/abs/1412.6980
  * [9] Lecun, Y., Cosatto, E., Ben, J., Muller, U., Flepp, B.: Dave: Autonomous off-road vehicle control using end-to-end learning. Tech. Rep. DARPA-IPTO Final Report, Courant Institute/CBLL, http://www.cs.nyu.edu/~yann/research/dave/index.html (2004)
  * [10] Pomerleau, D.A.: Advances in neural information processing systems 1. chap. ALVINN: An Autonomous Land Vehicle in a Neural Network, pp. 305–313. Morgan Kaufmann Publishers Inc., San Francisco, CA, USA (1989), http://dl.acm.org/citation.cfm?id=89851.89891


# GoSurf: Identifying Software Supply Chain Attack Vectors in Go

Carmine Cesarano<EMAIL_ADDRESS>Università degli Studi di Napoli
Federico IINaplesItaly ,  Vivi Andersson<EMAIL_ADDRESS>KTH Royal Institute of
TechnologyStockholmSweden ,  Roberto Natella<EMAIL_ADDRESS>Università degli Studi di Napoli Federico IINaplesItaly and  Martin Monperrus
<EMAIL_ADDRESS>KTH Royal Institute of TechnologyStockholmSweden

###### Abstract.

In Go, the widespread adoption of open-source software has led to a
flourishing ecosystem of third-party dependencies, which are often integrated
into critical systems. However, the reuse of dependencies introduces
significant supply chain security risks, as a single compromised package can
have cascading impacts. Existing supply chain attack taxonomies overlook
language-specific features that can be exploited by attackers to hide
malicious code. In this paper, we propose a novel taxonomy of 12 distinct
attack vectors tailored for the Go language and its package lifecycle. Our
taxonomy identifies patterns in which language-specific Go features, intended
for benign purposes, can be misused to propagate malicious code stealthily
through supply chains. Additionally, we introduce _GoSurf_ , a static analysis
tool that analyzes the attack surface of Go packages according to our proposed
taxonomy. We evaluate _GoSurf_ on a corpus of widely used, real-world Go
packages. Our work provides preliminary insights for securing the open-source
software supply chain within the Go ecosystem, allowing developers and
security analysts to prioritize code audit efforts and uncover hidden
malicious behaviors.

††copyright: none

##  1\. Introduction

Software development today heavily relies on open-source software (OSS)
dependencies, leveraging the benefits of code reusability. These dependencies
enable developers to re-use pre-written, tested, and community-vetted code,
significantly reducing development time and effort. This phenomenon is
observed in all software ecosystems, including Java, Python and Go.

Despite its benefits, OSS also introduces security risks. A single compromised
dependency in the software supply chain can put numerous downstream
applications and services at risk. In recent years, there has been a growing
trend of such supply chain attacks ((2024), CyRC) where malicious actors
compromise OSS projects to propagate malicious code to downstream
applications. Real-world incidents, such as npm typo-squatted packages (Labs,
2022), malicious crypto miners uploaded to the Python Package Index (PyPI)
(Sonatype, 2024), and potential repo-jacking attacks affecting more than 15K
Go repositories (Baines, 2023), highlight the harmful impact of software
supply chain attacks across programming languages.

The Go programming language, known for its simplicity, concurrency, and
performance, is not immune to supply chain attacks. Its supply chain demands
specific attention due to its growing adoption in security-critical software
like Kubernetes (Kubernetes, 2024) and Go Ethereum (Ethereum, 2024). Indeed,
there have already been advanced persistent threats (APTs) reported to use Go
(Cimpanu, 2021), such as the second-stage malware backdoor Sunshuttle (Lindsay
Smith, 2021), connected to the 2020 SolarWinds supply chain attack (for Cyber
Security Denmark, 2021) targeting US government agencies and private
companies. Similarly, a Golang backdoor GateDoor was leveraged in a 2024
supply chain attack (HackerNews, 2024) affecting downstream users of a video
recording service.

In this paper, we study how attackers can hide and execute arbitrary code in
Go dependencies. This important dimension of software supply chain security
has been overlooked so far. On the one hand, existing attack taxonomies (Ohm
et al., 2020; Ladisa et al., 2023a, b) are generic and lack language-specific
insights. On the other hand, existing Go security tools (Packages, 2024d, e,
c, b, a) only cover a narrow range of possible software supply chain attack
vectors in Go.

To address these challenges, we propose a novel taxonomy categorizing 12
distinct attack vectors specifically tailored for the Go ecosystem. This is
the first systematic enumeration of stealthy ways to conceal malicious code in
Go dependencies for propagation downstream. We make this taxonomy actionable
with _GoSurf_ , a static analyzer for Go to identify the presence of these
attack vectors in Go open-source packages.

We evaluate the applicability of the proposed taxonomy on a set of 500 popular
Go modules by using _GoSurf_ to quantify occurrences of attack vectors. Our
analysis reveals that all 12 defined attack vectors can be successfully
identified in real-world, popular Go projects, and most of these projects
exhibit a large attack surface. In addition, we show a practical use case of
_GoSurf_ by analyzing and comparing different version releases of a Go module,
demonstrating how the attack surface evolves over time.

_GoSurf_ can play a crucial role in securing the supply chain of open-source
Go projects by scrutinizing the attack surface of dependencies before
integration, and continuously monitoring dependency updates for new attack
vectors. The high volume of occurrences identified by _GoSurf_ underscores its
potential as a foundational step towards more sophisticated analysis
techniques, such as automated analysis of sensitive operations invoked within
identified attack patterns, and prioritization.

The main contributions of this paper are:

  * •

A novel taxonomy with 12 code injection attack vectors specifically tailored
for the Go software stack, showcased by runnable proof of concepts.

  * •

The design and implementation of _GoSurf_ for statically analyzing the attack
surface of Go packages, following the proposed taxonomy.

  * •

An analysis of open-source, real-world Go packages to evaluate _GoSurf_. We
publicly release _GoSurf_ and the evaluation scripts
111https://anonymous.4open.science/r/SCORED24_GoSurf/.

##  2\. Background on Golang

The Go programming language (The Go Programming Language, 2024d), commonly
known as Golang, was created by Google in 2007. It is a systems language that
addresses shortcomings of other programming languages by blending their
distinctive characteristics (Pike, 2012), such as the static typing and
runtime efficiency of C, the readability and usability of Python, and the
high-performance multiprocessing features of Erlang. From a security
standpoint, Go offers features like garbage collection, bounds checking, and
type safety, which help prevent common vulnerabilities such as buffer
overflows and memory leaks. Since its public release, Go has gained widespread
adoption (Stack Overflow, 2023).

In Go, software is organized into units called Go modules, which are
collections of related Go packages that are versioned and distributed together
(The Go Programming Language, 2024c). A Go package consists of one or more
source files within the same directory. Projects can depend on entire modules
or only on single packages, according to the specific needs of their
application.

Open-source Go modules are supplied to dependents through a distributed
package management system. By publishing a Go module to a version control
system (VCS) such as GitHub, any developer can import it by referencing its
repository hosting location (The Go Programming Language, 2024c). An official
Go proxy (The Go Programming Language, 2024b) maintains mirrors for the
repositories, ensuring that modules are reliably available in the Go package
ecosystem.

The Go module system and toolchain simplify dependency management throughout
the phases shown in Figure 1, and described in the following.

Figure 1. Go Package Lifecycle

Lifecycle for a Go Package

#### 1\. Pre-Build

To create a Go module, developers can use the `’go mod init’` command, which
generates a manifest file named `’go.mod’` (shown in Listing 1). This file
lists all the required direct and indirect dependencies and ensures
reproducible builds by pinning dependencies to specific versions (The Go
Programming Language, 2024c). During this phase, dependencies can be
explicitly integrated into the Go module through the `’go get’` command, which
fetches and caches the required packages locally. After this, developers can
test integrated packages (`’go test’`) and utilize code generation tools (`’go
generate’`) to automate tasks like creating boilerplate code or generating
environment-specific artifacts, before the build phase.

Listing 1: Example of a go.mod manifest file.

⬇

1module github.com/ethereum/go-ethereum

2go 1.21

3require (github.com/Azure/.../azblob v1.2.0

4 github.com/Microsoft/go-winio v0.6.1 ...)

#### 2\. Build

During the compilation of a module, the `’go build’` tool parses the manifest
(`’go.mod’`) and automatically fetches any required dependencies not cached
locally. The Go compiler then compiles them into machine code (The Go
Programming Language, 2024g). The compiled code is linked with the Go runtime
to create an executable binary. Go toolchain is explicitly designed to avoid
code execution during this process, ensuring build-time security.

#### 3\. Initialization

When executing the installed binary, the Go Runtime first initializes
dependencies based on a dependency ordering (The Go Programming Language,
2024d). If package A imports package B, package B will be initialized before
package A. First, package-level global variables are initialized, in the order
they are declared. Then, the Go runtime executes any package-level init()
functions. Global variables initialization and init functions can contain
arbitrary code.

#### 4\. Execution

Finally, once all packages are initialized, the Go runtime will execute the
`’main’` function in the main package, as a goroutine (The Go Programming
Language, 2024d). A goroutine is a lightweight thread managed by the Go
runtime, allowing for concurrent execution with lighter overhead than with
traditional OS threads. The Go runtime also automatically manages memory
allocation and garbage collection.

##  3\. Supply Chain Attack Vectors in Go

Establishing a robust taxonomy of attack vectors is critical in the realm of
software supply chain security. Although previous efforts (Ladisa et al.,
2023a, b; Ohm et al., 2020) have yielded general taxonomies for supply chain
attack strategies, they are not tailored to the unique characteristics and
idiosyncrasies of specific programming languages and stacks. The Go
programming language includes features and design choices that both mitigate
and introduce distinct security challenges.

In this section, we propose a novel language-specific taxonomy for classifying
attack vectors in the supply chain of Go packages. With this taxonomy, we can
help researchers and security experts to effectively understand and identify
the unique risks linked to Go dependencies.

To develop a Go-specific taxonomy of supply chain attack vectors, we began by
thoroughly reviewing existing generic taxonomies (Ladisa et al., 2023a, b; Ohm
et al., 2020) and systematically questioning the meaning and relevance of each
attack vector in the context of Go. We then curated a dataset of runnable
proof-of-concept attacks written in Go (included in the project repository) to
assess their feasibility and associated risks.

We categorized the identified attack vectors according to three phases of the
package lifecycle: pre-build, initialization, and execution (see Sec. 2 for a
detailed explanation of these phases). For each of the identified attack
vectors, we provide a detailed explanation of the Go-specific feature and its
intended purpose as well as how it can be exploited to perform an attack on
the supply chain.

###  3.1. Malicious Code at Pre-Build Time

During the pre-build phase, third-party dependency integration encompasses
various operations. The Go toolchain provides two primary tools to streamline
these operations: `’go generate’` for automating code generation tasks, and
`’go test’` for testing the code. Attackers can exploit both operations to
hide and execute malicious code.

#### P1. Static Code Generation

The go generate tool enables developers to define and execute code generators,
which run commands and generate Go source code. Go generators are defined in
comments using the `’//go:generate’` directive, followed by a shell command to
be executed, e.g. `’//go:generate cmd’`. Then, when the `’go generate’` tool
is run, source files in the package are scanned for these directives,
executing the specified shell commands. It is important to note that `’go
generate’` is not part of the `’go build’` command by default and must be
explicitly run before building the project (Pike, 2014).

##### Attack

This feature poses two main security concerns. First, generators can execute
arbitrary shell commands, allowing attackers to insert malicious code into the
directive to be directly executed. Second, generators can generate additional
source code on the fly to attach to a Go project before building, thereby
enabling the injection of malicious code into the final executable during the
subsequent build.

Malicious code hidden in these directives can be unintentionally executed by a
developer. This can happen when imported dependencies include code generation
operations that need to be run before compilation.

In addition, CI/CD pipelines might automatically run the `’go generate’`
command when invoked from a build script. For example, as evidenced by real-
life Go projects such as Istio (Istio, 2024), Terraform (Hashicorp, 2024a),
and Vault (Hashicorp, 2024b), it is a common practice to run code generation
in Makefile scripts.

A real-world example (github user, 2024) of this pattern is shown in Listing
2. Although the directive does not directly execute any harmful commands, the
code fetched from an external source can be inherently malicious.

Listing 2: Usage of the go:generate directive.

⬇

1//go:generate bash -c "mkdir -p codegen && go run github.com/deepmap/oapi-
<EMAIL_ADDRESS>-generate types,server,spec -package codegen
api/casaos/openapi.yaml > codegen/casaos_api.go"

#### P2. Testing Functions

The Go Toolchain includes built-in features to facilitate robust and efficient
software testing. Two central components of the Go testing framework are the
`’go test’` tool and the standard library testing, which automates the
execution of test cases. Developers can create test files ending in
`’_test.go’`, and define functions starting with the prefix `’Example’`,
`’Test’`, `’Benchmark’` or `’Fuzz’` to design examples, tests, benchmarks, and
fuzzing, respectively. These functions are automatically executed by the `’go
test’` tool. This feature is intended to be used during the testing stage of
development or as part of the CI actions. Dependency consumers may also use it
to test imported code before integration and compilation.

##### Attack

Test suites create a low-visibility environment with potentially large amounts
of code, allowing attackers to hide malicious code for seamless execution
during build integration. This code might compromise either the developer
machine or the CI runners.

Testing functions are often overlooked in security reviews because of the
absence of business logic that ends up in production. However, the same
testing functions may be triggered during post-deployment smoke tests. This
provides attackers with the opportunity to execute arbitrary code on
production machines.

###  3.2. Malicious Code at Initialization Time

As mentioned in Sec. 2, the Go Runtime supports initialization operations
executed before the main logic by means of global variable initialization and
`’init’` functions. Although these mechanisms are useful for initializing
packages, arbitrary code can be injected and executed through them.

#### I1. Global Variable Initialization

In Go, any variable declared outside a function is referred to as a package-
level global variable. Global variables can be initialized using simple
expressions, such as a composite literal, or when more complex operations are
required by the return value of regular or anonymous function calls. From a
runtime perspective, functions (regular or anonymous) invoked on the right-
hand side of a global variable declaration statement, are executed promptly
during package initialization, before the importing program executes both the
`’main’` and any `’init’` functions.

##### Attack

The initialization logic creates a stealthy attack vector. Because global
variable initializations are executed before the `’main’` function, an
attacker can establish a foothold and potentially manipulate the application’s
behavior from the outset. Furthermore, this initialization logic is executed
silently whenever a compromised package is imported, either directly or
transitively, through nested dependencies, even if the package is not used
later. Examples of both regular and anonymous functions used for global
variable initialization are presented in Listing 3.

Listing 3: Global variable initialization with function calls.

⬇

1func UnsafeMethod() string {

2 // malicious code

3}

4var var1 string = unsafeMethod()

5var var2 string = func() string {

6 //malicious code

7}()

#### I2. Initialization Hooks

Initialization tasks can also be defined in `’init()’` functions. After
initializing all global variables in a package, the Go runtime runs the
`’init()’` functions of dependencies.

##### Attack

As for global variable initialization, an attacker can inject malicious code
into an `’init’` function to execute their payload automatically during the
import process, before the main program starts. Note that Go removes unused
dependencies such that if a package is imported but never directly used, its
`’init()’` function will not be executed. However, when importing a package
with an underscore prefix, Go prevents the dependency from being removed,
which ensures its `’init’` function always runs (The Go Programming Language,
2024a).

###  3.3. Malicious Code at Execution Time

In Go, certain constructs can mask and execute harmful code during the main
execution phase. These include hidden dynamic behavior and unsafe features.

#### E1. Constructor Methods

In Go, developers define custom functions that serve as constructors for
structures following a naming convention such as `’NewStructName’` (The Go
Programming Language, 2024e). If the variable being initialized is the primary
type for the given package, the constructor can be named `’New’` without
suffix.

##### Attack

Constructors are called to create a new instance of a structure, and any
malicious code embedded into constructors will execute during each
instantiation. This exploit is insidious, as constructors can be assumed to
perform benign and simple instantiation tasks, potentially leading to less
developer scrutiny. This makes hiding malicious code in constructors an
effective method for attackers to compromise a system repeatedly.

#### E2. Reflection

Reflection in Go enables dynamic inspection and runtime modification of types
and methods. This is accomplished through the reflect package. When a method
is invoked through reflection, the actual method called is determined at
runtime rather than at compile-time. Developers use reflection to build
generic libraries, implement serialization and deserialization mechanisms, and
create flexible APIs that can operate on various types without needing to know
their specifics at compile-time (The Go Programming Language, 2024h).

##### Attack

An attacker can exploit reflection to dynamically inject and execute arbitrary
code at runtime. For instance, consider the scenario shown in Listing 4;
reflection is used to call `’UnsafeMethod’` dynamically.

Listing 4: Indirect method invocation through reflection.

⬇

1type Mytype string

2func (t MyType) Safe() {

3 // benign code

4}

5func (t MyType) Unsafe() {

6 // malicious code

7}

8func main() {

9 var target MyType

10 var methodName string = "Safe"

11 methodName = "Unsafe" // hidden manipulation

12 v := reflect.ValueOf(target)

13 m := v.MethodByName(methodName)

14 m.Call(nil)

The method name to be called is stored in a variable that an attacker can
manipulate (lines 10-11) to execute arbitrary methods. This ability to invoke
methods dynamically makes it easier for attackers to hide malicious payloads
and evade static security analysis (Sayar et al., 2023).

#### E3. Interface Polymorphism

In Go, polymorphism is achieved through interfaces, which specify a set of
method signatures, i.e., behaviors. Interfaces in Go are structurally typed
rather than nominally typed. Any type is considered to implement an interface
if it is structurally equivalent to the interface (i.e., implements all its
methods) regardless of the explicit declaration. When an interface is used,
the actual invoked method is dynamically dispatched based on the method set of
the underlying type. Common use cases include dependency injection, creating
mocks for unit testing, handling events, and defining callbacks.

##### Attack

Interfaces in Go can be exploited to achieve polymorphic behavior when
malicious code replaces a benign implementation at runtime. Listing 5 shows a
scenario where an interface designed to call `’Execute’` on a safe type is
subverted. By converting the instance of `’SafeType’` with
`’UnsafeType’`(lines 15-16), the attacker ensures that the malicious
implementation of `’Execute’` is invoked (line 17). The insidious nature of
this attack lies in the ability to hide the malicious implementation behind an
innocuous-looking interface. In addition, the subtle replacement can leverage
dynamic or indirect assignment of types, potentially being concealed deep
within the code, making it difficult to detect during code reviews or static
analysis.

Listing 5: Indirect method invocation through interfaces.

⬇

1type SafeInterface interface {

2 Execute()

3}

4type SafeType struct{}

5func (b SafeType) Execute() {

6 // benign implementation

7}

8type UnsafeType struct{}

9func (m UnsafeType) Execute() {

10 // malicious implementation

11}

12func main() {

13 var safeVar SafeInterface = SafeType{}

14 safeVar.Execute() // Safe code exec

15 var unsafeVar SafeInterface = UnsafeType{}

16 safeVar = unsafeVar // hidden conversion

17 safeVar.Execute() // Unsafe code exec

#### E4. Unsafe Pointers

In Go, using safe abstractions such as arrays, slices, and maps ensures built-
in checks that prevent common memory violations such as buffer overflows, null
pointer dereferences, and out-of-bound memory accesses. However, to perform
low-level programming tasks or to optimize certain performance-sensitive
applications, the unsafe package can be utilized. This package allows for the
definition of the type `’unsafe.Pointer’`, which bypasses Go’s safety checks.
For example, developers can perform pointer conversions, pointer arithmetic,
and interpret the memory layout of complex data structures in ways that would
otherwise be restricted.

##### Attack

Safe usage of unsafe pointers is possible, however, several examples of
patterns that introduce security risks exist (Costa et al., 2022; Lauinger et
al., 2020). First, attackers can exploit unsafe pointers to perform
unauthorized memory operations. As shown in Listing 6, unsafe pointers can be
misused to create a function pointer variable and set its value to the address
of an arbitrary function (lines 7-8), thereby effectively allowing the
execution of any code at runtime. Similarly, Listing 7 shows how unsafe
pointers can be misused to access out-of-bound memory locations, potentially
leading to information disclosure or memory-corruption vulnerabilities.

Listing 6: Unsafe Pointers for Arbitrary Execution.

⬇

1func targetFunction() {

2 // malicious code

3}

4func main() {

5 type FuncType func()

6 targetFunc := targetFunction

7 var funcPtr FuncType

8 funcPtr = *(*FuncType)(unsafe.Pointer(&targetFunc))

9 funcPtr()

10}

Listing 7: Unsafe Pointers for Memory Access.

⬇

1data := [4]int{1, 2, 3, 4}

2ptr := unsafe.Pointer(uintptr(unsafe.Pointer(&data[0])) +
unsafe.Sizeof(data[0])*4)

3value := *(*int)(ptr)

4fmt.Printf("Out-of-bounds value: %d\n", value)

#### E5. CGO Static Code Linking

CGO is a foreign function interface allowing for static linking of C code
within Go packages. Developers can write C snippets in a preamble comment of a
Go file, directly defining C functions or importing external C header files.
Thus, by using a pseudo-package `’C’`, the defined C functions can be directly
invoked within the Go program (shown in Listing 8). When the Go compiler
parses an import of `’C’`, it invokes the C compiler on the defined C
functions and links the C and Go object files. The CGO feature is commonly
used to leverage existing C libraries to avoid error-prone re-implementations
and bring performance benefits in Go (Sorniotti et al., 2023).

##### Attack

From a security standpoint, CGO introduces risks, mainly concerning memory
safety. While Go has built-in protection (Pike, 2012) for common memory
management errors, such as buffer overflows, dangling pointers, and memory
leaks, C lacks these safeguards. Linking the C code in Go packages can
reintroduce these vulnerabilities, which can be exploited by attackers. For
example, memory violations can be used to gain unauthorized access or control
over a system. In addition, attackers can manipulate function pointers in C to
redirect execution to malicious code segments, bypassing Go’s safety
mechanisms.

Listing 8: C code invocation using CGO.

⬇

1/* #include <stdio.h>

2void cFunction() {

3 // malicious code here

4} */

5import "C"

6func main() {

7 C.cFunction()

8}

#### E6. Assembly Static Code Linking

Go supports static linking of assembly code (The Go Programming Language,
2024f). Developers can write package-level assembly functions using Go
Assembly. When compiling, any assembly file is assembled into object files by
the Go assembler, and then linked into the Go objects. These functions can be
used in Go files by defining a function stub with the same name as that
specified in the assembly file. The assembly functions can then be called as
any normal function in Go (shown in Listing 9). Assembly is commonly leveraged
for code optimization or in security-critical domains such as cryptography
(Pike, 2016).

##### Attack

The complex syntax of assembly allows attackers to embed malicious code that
can be challenging to audit due to its low-level nature. A lack of analysis
support for assembly and the requirement of specialized knowledge
(specifically in the go-specific assembly syntax) can leave this code poorly
or completely unaudited. The use of empty functions as an assembly definition
further conceals its underlying implementation. This makes assembly code a
potent method for hiding malicious code.

Listing 9: Invocation of package-level defined assembly code.

⬇

1func AsmFunction() int

2func main() {

3 AsmFunction() // malicious function

4}

#### E7. Dynamic Library Linking

In Go, developers can use the plugin package for dynamic loading and executing
of external shared libraries within the main process of a running program. A
plugin is a Go package with exported functions and variables built using the
`’-buildmode=plugin’` option. The Go runtime resolves the function and
variable symbols provided by the plugin dynamically, enabling direct function
calls between the main program and plugin. The use of plugins eliminates the
overhead of inter-process communication connected to e.g. file system
operations.

##### Attack.

While providing flexibility, plugins also introduce security risks. Plugins
allow for changing the main program’s behavior without recompiling it. An
attacker can replace a plugin with a malicious version as shown in Listing 10.
This allows an untrusted, potentially dangerous library to be loaded into the
main process stealthily without the developer’s knowledge.

Listing 10: Simplified example call to dynamic library function.

⬇

1func main() {

2 pluginPath := "./malicious_plugin.so"

3 p := plugin.Open(pluginPath)

4 sym := p.Lookup("pFunc") // runtime resolution

5 fn := sym.((func()))

6 fn() // malicious function

7}

#### E8. Dynamic External Execution

Dynamic external execution involves running an executable as a separate
process. In Go. this can be achieved through the os and syscall packages,
offering methods like ’exec.Command’ and ’exec.CommandContext’. Additionally,
lower-level functions such as ’syscall.ForkExec’, ’syscall.Exec’, and
’os.StartProcess’ provide more control over process execution. This feature is
intended for directly execute an external binary or shell commands.

##### Attack

Dynamic external execution poses security threats. For example, binaries
executed through them may be opaque and limit human and static analyses,
facilitating the hiding of malicious code. In addition, the possibility of
invoking arbitrary commands that are potentially constructed dynamically can
lead to OS command injection. This also introduces risks for denial-of-service
attacks through fork bombs, i.e. recursive replication of processes.

###  3.4. Attack Vectors in the Malware Lifecycle

Based on the specific characteristics of each attack vector that we define,
they can map to different stages of malware execution. During the infection
stage, attack vectors categorized in the pre-build phase (Sec. 3.1), like
using generators and testing functions, can be exploited to download
preliminary malicious scripts or code, exfiltrate sensitive system
information, or install backdoors. Subsequently, during the persistence stage,
attack vectors exploitable at the initialization time (Sec. 3.2) like `init()`
functions and global variables initialization can be used to establish a
persistent presence on the infected system, for example, setting some registry
entries or creating new scheduled tasks. As these functions are invoked
automatically early in program execution, such malicious actions can occur
stealthily without the developer’s explicit invocation. Finally, all attack
vectors we categorized as execution time (Sec. 3.3) can be exploited for the
payload execution stage of malware, where the primary malicious activities are
carried out.

##  4\. GoSurf

In this paper, we introduce _GoSurf_ 3.5mm, a novel tool that analyzes the
attack surface of Go modules according to the taxonomy of attack vectors
described in Sec. 3. It reports the usage of language-specific features and
programming idioms that can be leveraged for the corresponding attack vectors.
_GoSurf_ is meant to aid open-source dependency analysis and audit to find
malicious code.

###  4.1. Design

_GoSurf_ follows a modular architecture consisting of three main parts: a
parser, static analysis engine, and reporting component. The workflow is shown
in Figure 2.

Figure 2. _GoSurf_ Tool Architecture

GoSurf tool architecture design

The parser component parses Go source code and constructs an Abstract Syntax
Tree (AST). The static analysis engine uses the AST and a set of rules to
identify attack vectors based on the defined taxonomy. The reporting component
consolidates the analysis results presenting them in a user-friendly format.

We implemented _GoSurf_ in Go, using 793 lines of code, and leveraging Go
standard libraries. The details of each component are explained in the
following.

####  4.1.1. Parser

The _GoSurf_ parser component enables analysis of Go modules, potentially
composed of several packages.

First, the parser collects all packages for analysis, starting with a root
module path specified as a local directory. It recursively traverses all
subdirectories to identify the module packages, recognizing them by the
presence of `.go` files containing valid package declarations (directive
`package`).

After gathering all packages from the root module path, the parser generates
the abstract syntax tree (AST) representation of each Go file, using the Go
standard libraries go/token and go/parser.

####  4.1.2. Static Analysis

_GoSurf_ implements 12 analyzers to identify occurrences of every attack
vector from our proposed taxonomy (Sec. 3). The analyzers use the AST
representation to inspect its nodes, primarily using the `’ast.Inspect’`
function from the go/ast package. The analyzers can be divided into three
groups based on their targeted nodes for inspection: an invocation,
declaration, or comment.

##### Invocation

The first group of analyzers (Constructor, Interfaces, Unsafe, CGO, Assembly,
Plugin, Exec) identify specific package and function invocation calls by
inspecting call expressions (`’CallExpr’`). These analyzers search for
function expressions belonging to an identifier (`’Ident’`) or selector
expression (`’SelectorExpr’`). An identity is a standalone name, while a
selector expression is a qualified identifier on the form `’X.Sel’`, where
`’X’` is the package or receiver expression (e.g., a variable), and `’Sel’` is
the identifier being selected (e.g., a function name). For example, the
`Plugin` (Sec. 3.3) analyzer matches the AST node pattern of a call expression
(`’CallExpr’`) with a selector expression having the package name `’plugin’`,
selecting the method `’Open’` (`’plugin.Open()’`).

Two invocation analyzers include a pre-processing step to identify the target
method signature for inspection. The `Interfaces` (Sec. 3.3) analyzer collects
all polymorphic methods by looking for functions with multiple receiver types.
Conversely, the `Assembly` (Sec. 3.3) analyzer collects all package-level
assembly functions through regular expression matching in assembly files.
Then, the invocation inspection technique is applied to these analyzers,
identifying method calls to the previously collected method signatures.

##### Declaration

The second group of analyzers (`GoTest`,
`InitFunc`, `GlobalVar`, `Reflect`) analyze declarations in the AST. The
`GoTest` analyzer (Sec. 3.1) matches function declaration (`’FuncDecl’`) nodes
that have names starting with `’Test’`, `’Fuzz’`, `’Benchmark’` or
`’Example’`. Similarly, the `InitFunc` (Sec. 3.2) analyzer identifies function
declarations named `’init’`. The `GlobalVar` (Sec. 3.2) and `Reflect` (Sec.
3.3) analyzers inspect generic declaration (`’GenDecl’`) nodes, looking for
variable and constant declarations, and import declarations, respectively.

##### Comment

The third group inspects comments in the AST. This technique concerns the
`GoGenerate` analyzer (Sec. 3.1), which identifies generators in `Comment`
nodes with the prefix `’//go:generate’`.

####  4.1.3. Reporting

The reporting component of _GoSurf_ records and reports the occurrences of an
attack vector. When encountering a match in an analyzer, the location
information is logged, including package name, file path, and line number.
Additional context-specific information is included, depending on the attack
vector, such as the name of a method invocation. All occurrences are
aggregated into JSON format and counted before presenting the results in the
CLI. Listing 11 shows a _GoSurf_ report identifying usage of CGO and assembly.

Listing 11: Example JSON report.

⬇

[{

"PackageName": "issue27340",

"Type": "cgo",

"FilePath": ".../issue27340/a.go",

"LineNumber": 41,

"MethodInvoked": "C.issue27340CFunc"

},

{

"PackageName": "main",

"Type": "assembly",

"FilePath": ".../testdata/main.go",

"LineNumber": 8,

"MethodInvoked": "linefrompc"

}]

###  4.2. Usage of _GoSurf_

_GoSurf_ is a tool meant to be integrated into supply chain security
processes. We outline two primary use cases.

####  4.2.1. Pre-integration Auditing

Before integrating a third-party dependency, developers should conduct a
thorough security audit. _GoSurf_ offers a comprehensive overview of the risks
associated with a dependency by reporting on its attack surface. This helps
dependency consumers make informed decisions about which dependencies to
include or avoid. Additionally, _GoSurf_ streamlines the security auditing
process by highlighting riskier portions of the codebase, characterized by a
higher density of attack vectors, in order to prioritize auditing efforts.

####  4.2.2. Version Update Monitoring

The attack surface of a software package is dynamic, with new attack vectors
potentially introduced with each new version release. _GoSurf_ can be used to
continuously monitor these changes, by comparing the attack surface of
different versions of a package. This use case is particularly meaningful when
a dependency with an initial trusted codebase is updated. Updates from
untrusted authors with new attack vectors should be scrutinized. By using
_GoSurf_ , upstream dependencies can be effectively monitored, ensuring
consumers stay informed about risks.

To sum up, _GoSurf_ enhances the security posture in the Go ecosystem and
identifies potential risks before actual software supply chain attacks.

##  5\. Evaluation

In this section, we present initial experimental results obtained from
analyzing _GoSurf_ on real-world Go projects.

###  5.1. Attack Surface in Real-World Go Modules

In Sec. 3 we demonstrated the feasibility of the identified attack vectors,
with executable proof of concepts. In this section, we conduct an experiment
to evaluate the applicability of our proposed taxonomy, that is, that the 12
attack vectors can be successfully identified in real-world Go projects.

##### Methodology

To analyze the applicability of our taxonomy, we conducted a comprehensive
analysis using _GoSurf_ across 10 Go projects. We carefully selected these
projects based on their number of dependents, indicating their relevance and
popularity. The selected projects cover diverse categories such as cloud
orchestration, networking, blockchain, monitoring, and CLI applications,
sourced from libraries.io. This ensures that we focus on modules with
widespread real-world usage, which if exploited, would have large security
implications. To limit sampling bias issues we performed the same analysis on
a sample of the top 500 most imported packages obtaining the same findings as
described in the following. Due to space constraints, detailed results on the
entire dataset are available in the project repository.

##### Results

Using _GoSurf_ , we identified occurrences of some of the 12 attack vectors in
each of the chosen Go modules. These are summarized in Table 1. The results
indicate that P2 (testing functions), E1 (constructors methods), and E3
(interfaces) are the most frequently occurring attack vectors. Although their
high prevalence may be connected to common benign Go programming practices,
their usage call for additional auditing in a security-sensitive context.
Overall, Table 1 shows that malicious actors have ample room to choose where
to hide malicious code execution, as argued in Sec. 3.

Table 1. Usage of Taxonomy Attack Vectors in Popular Go Projects Go Module | Dep. | LOC | P1 | P2 | I1 | I2 | E1 | E2 | E3 | E4 | E5 | E6 | E7 | E8
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---
<EMAIL_ADDRESS>| 1,384 | 377,115 | 38 | 2,899 | 238 | 50 | 4,383 | 121 | 3,707 | 10 | 0 | 0 | 0 | 17
<EMAIL_ADDRESS>| 2,024 | 3,728,835 | 119 | 10,339 | 13,941 | 1,108 | 38,813 | 1,528 | 85,277 | 8,105 | 803 | 1,495 | 1 | 230
<EMAIL_ADDRESS>| 3,965 | 235,019 | 34 | 2,132 | 1,116 | 76 | 6,470 | 128 | 6,343 | 26 | 21 | 12 | 0 | 22
<EMAIL_ADDRESS>| 642 | 52,348 | 3 | 534 | 118 | 98 | 784 | 18 | 603 | 3 | 0 | 0 | 0 | 1
<EMAIL_ADDRESS>| 15,010 | 40,768 | 0 | 90 | 283 | 7 | 447 | 12 | 734 | 23 | 2 | 0 | 0 | 24
testify @v1.7.1 | 90,337 | 14,084 | 4 | 426 | 24 | 0 | 225 | 7 | 277 | 6 | 0 | 0 | 0 | 1
<EMAIL_ADDRESS>| 1,235 | 436,387 | 42 | 3,132 | 362 | 55 | 4,351 | 207 | 5,683 | 0 | 0 | 0 | 0 | 14
<EMAIL_ADDRESS>| 38,020 | 4,414 | 0 | 127 | 1 | 4 | 73 | 1 | 161 | 0 | 0 | 0 | 0 | 1
<EMAIL_ADDRESS>| 1,097 | 173,422 | 5 | 1,186 | 178 | 50 | 2,206 | 18 | 3,961 | 11 | 0 | 0 | 0 | 12
<EMAIL_ADDRESS>| 33,747 | 11,861 | 0 | 271 | 1 | 1 | 12 | 2 | 277 | 0 | 0 | 0 | 0 | 2
<EMAIL_ADDRESS>| 617 | 22,693 | 6 | 3 | 9 | 0 | 7 | 2 | 0 | 0 | 0 | 0 | 0 | 2

Furthermore, the use of cross-language features such as C code and assembly
(E5 and E6) in projects like Kubernetes and Go-Ethereum suggests that
developers may trade the built-in security mechanisms in Go for less secure
code, growing the attack surface. Although less common, the use of plugins
(E7) is also present in the Kubernetes project. Here, the loading and
execution of externally pre-built code requires thorough security analysis.

Listing 12: Real-world attack vector.

⬇

1func (c *command) Run(arg ...string) ([][]string, error) {

2 cmd := exec.Command(c.Command, arg...)

3 ...

4}

Now let us consider Listing 12, demonstrating a real-world example of a
dynamic external execution attack vector (E8) discovered by _GoSurf_ in a
Kubernetes dependency mistifyio/go-zfs. This small library implements a
wrapper for ZFS command line tools. It is a third-party dependency maintained
by 23 common GitHub users with no affiliation to the Kubernetes project. A
malicious actor could gain access and inject malicious code to subvert the
arguments of the `Run` function (line 1), leading to arbitrary code execution.
This potential supply chain attack would have a cascading impact on all the
dependents of the `go-zfs` library, including Kubernetes.

For attackers, it is tempting to target widely-used dependencies to maximize
the impact on downstream applications. Dependencies exhibiting a large attack
surface may be easier targets for attackers due to their feasibility. Based on
our sample of targets, we observe that the project size tends to correlate
with the size of the attack surface. However, we also note that `go-textseg`
exhibits a significantly smaller attack surface compared to `testify`, despite
having a larger codebase. This suggests that Go modules can adhere closely to
coding practices that minimize security risks defined by our taxonomy.

To sum up, _GoSurf_ ’s results validated the applicability of our taxonomy. In
Sec. 3 we demonstrated the feasibility of each attack vector developing PoCs
for them. In this section, we confirm the increased security risks associated
with all 12 vectors by showing their prevalence in real-world Go projects.
This effectively motivates the need for additional scrutiny and prioritization
of these vectors during security audits.

###  5.2. Attack Surface over Versions

The attack surface of a software package is dynamic, with new attack vectors
potentially introduced with each new version release. _GoSurf_ can be
leveraged to continuously monitor these changes, by analyzing and comparing
multiple versions of a package. In this experiment, we aim to demonstrate the
dynamic nature of the attack surface and highlight the importance of regularly
auditing dependencies for emerging threats, reasoning the _GoSurf_ ’s use case
described in 4.2.2.

##### Methodology

To examine how the attack surface evolves in an actively developed project and
understand the impact of software updates on supply chain security, we used
_GoSurf_ for differential analysis. We evaluated the last five major releases
of the popular Kubernetes project (versions 1.26 through 1.30) to investigate
the prevalence of different attack vectors as the codebase evolved. The
selected releases cover a time window of around 16 months.

##### Results

The results obtained from running _GoSurf_ on five different versions of
Kubernetes are shown in Table 2. The reported occurrences suggest that the
attack surface is not static and can significantly vary as new features are
added, and code is refactored over release cycles.

For example, the introduction of structured authorization configuration likely
contributed to the increase in interfaces (E3) from Kubernetes v1.28 to v1.29
(Kubernetes, 2024), as this feature allows for an extensible and decoupled
authorization mechanism. Similarly, the in-place update of pod resources,
introduced in the same upgrade, potentially contributed to the increased usage
of reflection (E2), as it might require dynamically accessing pod structures.

On the other hand, it is reasonable to observe a decrease in the attack
vectors between Kubernetes versions 1.29 and 1.30, as the codebase has reduced
in size. This reduction can often be attributed to the stabilization of
features and the removal of redundant code, suggesting that refactoring can
reduce significantly the attack surface for Go packages.

Table 2. Vector usage in 5 different releases of Kubernetes | v1.26 | v1.27 | v1.28 | v1.29 | v1.30
---|---|---|---|---|---
P1 | 106 | 95 | 104 | 147 | 119
P1 | 10,067 | 10,291 | 10,501 | 10,770 | 10,339
I1 | 35 | 35 | 36 | 37 | 36
I2 | 8319 | 8,304 | 8,320 | 8,336 | 1,108
E1 | 43,701 | 43,102 | 43,822 | 44,389 | 38813
E2 | 1,543 | 1,526 | 1,540 | 1,568 | 1,528
E3 | 93,076 | 87,700 | 89,849 | 90,901 | 85277
E4 | 7,769 | 7,744 | 7,883 | 7,865 | 8,105
E5 | 820 | 792 | 795 | 797 | 803
E6 | 1,495 | 1,494 | 1,494 | 1,495 | 1,495
E7 | 1 | 1 | 1 | 1 | 1
E8 | 242 | 236 | 236 | 244 | 230
LOC | 3,967,186 | 3,855,352 | 3,934,131 | 4,004,993 | 3,728,835

In conclusion, _GoSurf_ is a tool for auditors to continuously monitor the
attack surface of dependencies. As software dependencies are updated over
time, new attack vectors may be introduced, potentially increasing the risk
for downstream consumers. Continuously monitoring these changes is crucial for
maintaining a secure software supply chain.

##  6\. Related Work

##### Generic Supply Chain Attack Taxonomies

Taxonomies aim to categorize and classify threats, establishing comprehensive
knowledge for software supply chains.

Ohm et al. (Ohm et al., 2020) collect and analyze 174 malicious real-world
packages exploited in supply chain attacks for JavaScript, Java, Python, PHP,
and Ruby packages. The analysis grounds two taxonomies. The first taxonomy
classifies 18 attack methods for injecting malicious code into a dependency
tree. The second one classifies 8 vectors for executing the injected malicious
code, belonging to one of two branches: a software lifecycle phase (install
scripts, test case, and runtime), or a conditional trigger. The second
taxonomy classifying execution vectors is analogous to ours. However, they do
not consider any Go knowledge and only provide a general taxonomy, without
concrete attacker methods. Our taxonomy is the first one specialized for Go,
based on deep analysis of Go-specific triggers within the Go-specific package
lifecycle.

Ladisa et al. (Ladisa et al., 2023a) extend the Ohm et al. taxonomy for
malicious code injection to 107 attack vectors. They survey grey and white
literature, considering exploited and non-exploited attacks, but do not
categorize execution strategies for malicious code after injection.
Conversely, our taxonomy focuses on the execution strategies of already
injected malicious code, in the context of Go.

In another work, Ladisa et al. (Ladisa et al., 2023b) consider language and
package manager features that can be exploited for executing malicious code.
They find 7 general execution techniques, divided into 3 install-time, and 4
runtime methods. They map each technique’s applicability in 7 languages,
including Go. This taxonomy over the execution of malicious code is related to
ours. However, they only identify three runtime vectors for Go, missing 9
possible attack vectors. Our work proposes a novel and useful taxonomy of 12
Go-specific attack vectors throughout 3 package lifecycle phases. Most
importantly, we implement a prototype tool, _GoSurf_ , which is able to find
all instances of attack vectors from our proposed taxonomy.

##### Go Security Tools

There are security tools for Go that focus on static analysis of dependencies.

Go vet (Packages, 2024b) reports code quality and correctness issues such as
unused variables and potential race conditions. However, it only identifies
unsafe pointer conversions (Sec. 3.3) as an attack vector and does not detect
any other vectors that _GoSurf_ does. Gosec (Packages, 2024c) identifies
violations of 36 security rules (e.g., URL injection in HTTP requests and path
traversal vulnerabilities). However, it does not report on any of the 12
attack vectors defined in our taxonomy. Go AST (Packages, 2024e) provide a
generic Rego policy engine, that allows users to create customized analysis
rules. This tool can be used to identify simple attack vectors but does not
suffice for more complex analysis, like the ones for assembly (E6) and
interfaces (E3) that _GoSurf_ supports.

Govulncheck (Packages, 2024d) and Nancy (Sonatype, 2024) are vulnerability
scanning tools that report the occurrences of vulnerable dependencies within a
project. While Govulncheck uses Go Vulnerability Databases for vulnerability
matching, Nancy is powered by Sonatype OSS Index. In contrast, _GoSurf_ does
not report known vulnerabilities based on a database, but rather detects the
attack surface of a Go module according to its attack vector taxonomy.

Capslock (Packages, 2024a) is a capability analysis tool reporting privileged
operations in a given package and its dependencies. The tool combines analysis
of the AST and SSA (static single assignment) representations to compute the
call graph for the Go program. By analyzing the call graph, Capslock
identifies capabilities such as network and file system access. In addition,
Capslock identifies reflection, CGO, plugins, and external execution, that
align with four of our attack vectors. _GoSurf_ complements Capslock by
providing more information about the attack surface of a Go module according
to the taxonomy.

##  7\. Conclusion

Software supply chain attacks targeting open-source dependencies pose a
significant threat. We propose a novel taxonomy categorizing 12 distinct
attack vectors for the Go ecosystem, spanning the pre-build, initialization,
and execution phases of the Go package lifecycle. To aid in identifying these
vectors, we developed _GoSurf_ , a static analysis tool that analyzes Go
modules and reports occurrences of the defined vectors. Our evaluation
demonstrates the applicability of the taxonomy on real-world Go projects and
the effectiveness of _GoSurf_ in highlighting potential supply chain risks. By
raising awareness and providing tools like _GoSurf_ , we aim to empower the
community to proactively address these risks and foster a more secure software
supply chain in the Go ecosystem.

## References

  * (1)
  * Baines (2023) VulnCheck Jacob Baines. 2023.  Hijackable Go Module Repositories.  https://vulncheck.com/blog/go-repojacking.  Online; accessed 9 June 2024.
  * Cimpanu (2021) Catalin Cimpanu. 2021.  Go malware is now common, having been adopted by both APTs and e-crime groups.  https://www.zdnet.com/article/go-malware-is-now-common-having-been-adopted-by-both-apts-and-e-crime-groups/.  Online; accessed 18 June 2024.
  * Costa et al. (2022) D. Costa, S. Mujahid, R. Abdalkareem, and E. Shihab. 2022.  Breaking Type Safety in Go: An Empirical Study on the Usage of the unsafe Package.  _IEEE Transactions on Software Engineering_ 48, 07 (jul 2022), 2277–2294.  https://doi.org/10.1109/TSE.2021.3057720
  * (5) Synopsys Cybersecurity Research Center (CyRC). 2024.  Open Source Security and Risk Analysis (OSSRA) Report.
  * Ethereum (2024) Go Ethereum. 2024.  go-ethereum - Official Go implementation of the Ethereum protocol.  https://geth.ethereum.org/
  * for Cyber Security Denmark (2021) Centre for Cyber Security Denmark. 2021.  SolarWinds: State-sponsored global software supply chain attack.  https://www.cfcs.dk/globalassets/cfcs/dokumenter/rapporter/en/CFCS-solarwinds-report-EN.pdf.  Online; accessed 22 June 2024.
  * github user (2024) github user. 2024.  CasaOS.  https://github.com/IceWhaleTech/CasaOS/blob/main/main.go.  Online; accessed 9 June 2024.
  * HackerNews (2024) HackerNews. 2024.  JAVS Courtroom Recording Software Backdoored.  https://thehackernews.com/2024/05/courtroom-software-backdoored-to.html.  Online; accessed 26 June 2024.
  * Hashicorp (2024a) Hashicorp. 2024a.  Terraform.  https://github.com/hashicorp/terraform/blob/8e4d4663fe37bbae3c0dc1c0bb20b6bcb17b637c/Makefile#L5.  Online; accessed 9 June 2024.
  * Hashicorp (2024b) Hashicorp. 2024b.  Vault.  https://github.com/hashicorp/vault/blob/f7c16796ed2482b5a595b5d89673e4ec5ff8e7fe/Makefile#L162.  Online; accessed 9 June 2024.
  * Istio (2024) Istio. 2024.  istio.  https://github.com/istio/istio/blob/8d200be6d52283c806dfce37ae2241efa915f8fd/Makefile.core.mk#L313.  Online; accessed 9 June 2024.
  * Kubernetes (2024) Kubernetes. 2024.  CHANGELOG v1.29.  https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.29.md.  Online; accessed 21 June 2024.
  * Kubernetes (2024) Kubernetes. 2024.  Kubernetes - Production-Grade Container Orchestration.  https://kubernetes.io/
  * Labs (2022) Reversing Labs. 2022.  Update: IconBurst npm software supply chain attack grabs data from apps and websites.  https://www.reversinglabs.com/blog/iconburst-npm-software-supply-chain-attack-grabs-data-from-apps-websites.  Online; accessed 9 June 2024.
  * Ladisa et al. (2023a) Piergiorgio Ladisa, Henrik Plate, Matias Martinez, and Olivier Barais. 2023a.  Sok: Taxonomy of attacks on open-source software supply chains. In _2023 IEEE Symposium on Security and Privacy (SP)_. IEEE, 1509–1526.
  * Ladisa et al. (2023b) Piergiorgio Ladisa, Merve Sahin, Serena Elisa Ponta, Marco Rosa, Matias Martinez, and Olivier Barais. 2023b.  The Hitchhiker’s Guide to Malicious Third-Party Dependencies. In _Proceedings of the 2023 Workshop on Software Supply Chain Offensive Research and Ecosystem Defenses_. 65–74.
  * Lauinger et al. (2020) J. Lauinger, L. Baumgartner, A. Wickert, and M. Mezini. 2020.  Uncovering the Hidden Dangers: Finding Unsafe Go Code in the Wild. In _2020 IEEE 19th International Conference on Trust, Security and Privacy in Computing and Communications (TrustCom)_. IEEE Computer Society, Los Alamitos, CA, USA, 410–417.  https://doi.org/10.1109/TrustCom50675.2020.00063
  * Lindsay Smith (2021) Ben Read Lindsay Smith, Jonathan Leathery. 2021.  New SUNSHUTTLE Second-Stage Backdoor Uncovered Targeting U.S.-Based Entity.  https://cloud.google.com/blog/topics/threat-intelligence/sunshuttle-second-stage-backdoor-targeting-us-based-entity/.  Online; accessed 24 June 2024.
  * Ohm et al. (2020) Marc Ohm, Henrik Plate, Arnold Sykosch, and Michael Meier. 2020.  Backstabber’s knife collection: A review of open source software supply chain attacks. In _Detection of Intrusions and Malware, and Vulnerability Assessment: 17th International Conference, DIMVA 2020, Lisbon, Portugal, June 24–26, 2020, Proceedings 17_. Springer, 23–43.
  * Packages (2024a) Go Packages. 2024a.  capslock package.  https://pkg.go.dev/github.com/google/capslock.  Online; accessed 10 June 2024.
  * Packages (2024b) Go Packages. 2024b.  go vet package.  https://pkg.go.dev/cmd/vet.  Online; accessed 20 June 2024.
  * Packages (2024c) Go Packages. 2024c.  gosec package.  https://pkg.go.dev/github.com/securego/gosec/v2.  Online; accessed 20 June 2024.
  * Packages (2024d) Go Packages. 2024d.  govulncheck.  https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck.  Online; accessed 20 June 2024; Language version go1.22.
  * Packages (2024e) Go Packages. 2024e.  m-mizutani/goast.  https://pkg.go.dev/github.com/m-mizutani/goast.  Online; accessed 20 June 2024.
  * Pike (2012) Rob Pike. 2012.  Go at Google: Language Design in the Service of Software Engineering.  https://go.dev/talks/2012/splash.article.  Online; accessed 27 May 2024.
  * Pike (2014) Rob Pike. 2014.  Generating code - The Go Programming Language.  https://go.dev/blog/generate.  Online; accessed 29 May 2024.
  * Pike (2016) Rob Pike. 2016.  The Design of the Go Assembler.  https://go.dev/talks/2016/asm Presented at Gophercon. Online; accessed 12 June 2024.
  * Sayar et al. (2023) Imen Sayar, Alexandre Bartel, Eric Bodden, and Yves Le Traon. 2023.  An in-depth study of java deserialization remote-code execution exploits and vulnerabilities.  _ACM Transactions on Software Engineering and Methodology_ 32, 1 (2023), 1–45.
  * Sonatype (2024) Sonatype. 2024.  Nancy.  https://pkg.go.dev/github.com/sonatype-nexus-community/nancy.  Online; accessed 20 June 2024; Language version go1.22.
  * Sonatype (2024) Sonatype. 2024.  PyPI crypto-stealer targets Windows users, revives malware campaign.  https://www.sonatype.com/blog/pypi-crypto-stealer-targets-windows-users-revives-malware-campaign.  Online; accessed 9 June 2024.
  * Sorniotti et al. (2023) Alessandro Sorniotti, Michael Weissbacher, and Anil Kurmus. 2023.  Go or No Go: Differential Fuzzing of Native and C Libraries. In _2023 IEEE Security and Privacy Workshops (SPW)_. IEEE, 349–363.
  * Stack Overflow (2023) Stack Overflow. 2023.  Annual Developer Survey.  https://survey.stackoverflow.co/.  Online; accessed 26 June 2024.
  * The Go Programming Language (2024a) The Go Programming Language. 2024a.  Go Doc - Effective Go.  https://go.dev/doc/effective_go.  Online; accessed 2 July 2024.
  * The Go Programming Language (2024b) The Go Programming Language. 2024b.  Go Module Mirror.  https://proxy.golang.org/.  Online; accessed 27 May 2024.
  * The Go Programming Language (2024c) The Go Programming Language. 2024c.  Go Modules Reference.  https://go.dev/ref/mod.  Online; accessed 24 May 2024.
  * The Go Programming Language (2024d) The Go Programming Language. 2024d.  The Go Programming Language Specification.  https://go.dev/ref.  Online; accessed 10 June 2024; Language version go1.22.
  * The Go Programming Language (2024e) The Go Programming Language. 2024e.  Package names.  https://go.dev/blog/package-names.  Online; accessed 28 May 2024.
  * The Go Programming Language (2024f) The Go Programming Language. 2024f.  A Quick Guide to Go’s Assembler.  https://go.dev/doc/asm Online; accessed 3 June 2024.
  * The Go Programming Language (2024g) The Go Programming Language. 2024g.  The Go Compiler.  https://go.dev/src/cmd/compile/README.  Online; accessed 27 May 2024.
  * The Go Programming Language (2024h) The Go Programming Language. 2024h.  The Laws of Reflection.  https://go.dev/blog/laws-of-reflection.  Online; accessed 28 May 2024.