diff --git a/Generators/FaserCosmicGenerator/CMakeLists.txt b/Generators/FaserCosmicGenerator/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..5bea0687466c0e7a4d51398e3fc9ed718215a6c6
--- /dev/null
+++ b/Generators/FaserCosmicGenerator/CMakeLists.txt
@@ -0,0 +1,10 @@
+################################################################################
+# Package: FaserCosmicGenerator
+################################################################################
+
+# Declare the package name:
+atlas_subdir( FaserCosmicGenerator )
+
+# Install files from the package:
+atlas_install_python_modules( python/*.py POST_BUILD_CMD ${ATLAS_FLAKE8} )
+
diff --git a/Generators/FaserCosmicGenerator/README.md b/Generators/FaserCosmicGenerator/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4a1e19c73dbd391e77de2b05907fa5778b006165
--- /dev/null
+++ b/Generators/FaserCosmicGenerator/README.md
@@ -0,0 +1,3 @@
+Faser cosmic-ray generator
+
+Documentation goes here...
\ No newline at end of file
diff --git a/Generators/FaserCosmicGenerator/python/__init__.py b/Generators/FaserCosmicGenerator/python/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..42ea59fd7bebf56ad3497e2c7352f475aaba0950
--- /dev/null
+++ b/Generators/FaserCosmicGenerator/python/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (C) 2021 CERN for the benefit of the FASER collaboration
+
+from FaserCosmicGenerator.cosmicSampler import CosmicSampler
+
diff --git a/Generators/FaserCosmicGenerator/python/cosmicSampler.py b/Generators/FaserCosmicGenerator/python/cosmicSampler.py
new file mode 100644
index 0000000000000000000000000000000000000000..4dca1fed08bdd7321d9bba772677af9af7b5bfa2
--- /dev/null
+++ b/Generators/FaserCosmicGenerator/python/cosmicSampler.py
@@ -0,0 +1,47 @@
+# Copyright (C) 2021 CERN for the benefit of the FASER collaboration
+
+import ROOT,math,random
+
+PI = math.pi
+TWOPI = 2*math.pi
+
+from ParticleGun.samplers import Sampler, SampledParticle
+
+class CosmicSampler(Sampler):
+    "Cosmic ray sampler"
+
+    def __init__(self, maxMuonEnergyGeV = 10000, targetDepthMeters = 85, targetRadiusMm = 3000, thetaMinDegree = 0, thetaMaxDegree = 80):
+        if thetaMinDegree > thetaMaxDegree :
+            return RuntimeError("thetaMin must be less than or equal to thetaMax")
+
+        self.maxEnergy = maxMuonEnergyGeV
+        self.depth = targetDepthMeters
+        self.radiusMm = targetRadiusMm        # used for generating start position
+        self.radiusCm = self.radiusMm/10      # used for calculating rates
+        self.cosThMax = math.cos(thetaMinDegree*PI/180)
+        self.cosThMin = math.cos(thetaMaxDegree*PI/180)
+        # read range data
+        # compute rate
+
+    def shoot(self):
+        # generate one event here
+        self.genPosition = ROOT.TLorentzVector()
+        self.genMomentum = ROOT.TLorentzVector()
+
+        # placeholders to test class structure - should create a vertically down 1 TeV mu- starting above the detector
+        self.genPosition.SetY(self.radiusMm)
+        self.genMomentum.SetPxPyPzE(0,-math.sqrt((1.0e6)**2-(105.71)**2),0,1.0e6)
+        self.pdgCode = 13
+        # end placeholders
+
+        particles = []
+        p = SampledParticle(self.pdgCode)
+        p.mom = self.genMomentum
+        p.pos = self.genPosition
+        particles.append(p)
+        return particles
+
+    def __call__(self):
+        return self.shoot()
+
+
diff --git a/Simulation/G4Faser/G4FaserAlg/test/runG4Cosmics.py b/Simulation/G4Faser/G4FaserAlg/test/runG4Cosmics.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad04571192f49a3500e5de4eca60b3a2e12019f5
--- /dev/null
+++ b/Simulation/G4Faser/G4FaserAlg/test/runG4Cosmics.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+if __name__ == "__main__":
+    import os
+    import sys
+    import GaudiPython
+    import ParticleGun as PG
+    from FaserCosmicGenerator import CosmicSampler
+    from AthenaConfiguration.ComponentAccumulator import ComponentAccumulator
+    from AthenaConfiguration.ComponentFactory import CompFactory
+    from AthenaCommon.AppMgr import *
+    from AthenaCommon.Logging import log, logging
+    from AthenaCommon.SystemOfUnits import TeV
+    from AthenaCommon.PhysicalConstants import pi
+    from AthenaCommon.Constants import VERBOSE, INFO
+    from AthenaCommon.Configurable import Configurable
+    from CalypsoConfiguration.AllConfigFlags import ConfigFlags
+    from CalypsoConfiguration.MainServicesConfig import MainServicesCfg
+    from AthenaPoolCnvSvc.PoolReadConfig import PoolReadCfg
+    from McEventSelector.McEventSelectorConfig import McEventSelectorCfg
+    from OutputStreamAthenaPool.OutputStreamConfig import OutputStreamCfg
+    from FaserGeoModel.FaserGeoModelConfig import FaserGeometryCfg
+    from G4FaserAlg.G4FaserAlgConfigNew import G4FaserAlgCfg
+    from G4FaserServices.G4FaserServicesConfigNew import G4GeometryNotifierSvcCfg
+#
+# Set up logging and new style config
+#
+    log.setLevel(VERBOSE)
+    Configurable.configurableRun3Behavior = True
+#
+# Input settings (Generator file)
+#
+#   from AthenaConfiguration.TestDefaults import defaultTestFiles
+#   ConfigFlags.Input.Files = defaultTestFiles.EVNT
+#
+# Alternatively, these must ALL be explicitly set to run without an input file
+# (if missing, it will try to read metadata from a non-existent file and crash)
+#
+    ConfigFlags.Input.Files = [""]
+    ConfigFlags.Input.isMC = True
+    ConfigFlags.Input.RunNumber = 12345
+    ConfigFlags.Input.Collections = [""]
+    ConfigFlags.Input.ProjectName = "mc19"
+    ConfigFlags.Common.isOnline = False
+    ConfigFlags.Beam.Type = "collisions"
+    ConfigFlags.Beam.Energy = 7*TeV                              # Informational, does not affect simulation
+    ConfigFlags.GeoModel.FaserVersion = "FASER-01"               # Always needed
+    ConfigFlags.IOVDb.GlobalTag = "OFLCOND-XXXX-XXX-XX"          # Always needed; only the OFLCOND part matters
+# Workaround for bug/missing flag; unimportant otherwise 
+    ConfigFlags.addFlag("Input.InitialTimeStamp", 0)
+# Workaround to avoid problematic ISF code
+    ConfigFlags.GeoModel.Layout = "Development"
+#
+# Output settings
+#
+    ConfigFlags.Output.HITSFileName = "cosmics.HITS.pool.root"
+    ConfigFlags.GeoModel.GeoExportFile = "faserGeo.db" # Optional dump of geometry for browsing in vp1light
+#
+# Geometry-related settings
+# Do not change!
+#
+    ConfigFlags.Detector.SimulateVeto   = True
+    ConfigFlags.Detector.GeometryVeto   = True
+    ConfigFlags.Detector.SimulateTrigger= True
+    ConfigFlags.Detector.GeometryTrigger= True
+    ConfigFlags.Detector.SimulatePreshower   = True
+    ConfigFlags.Detector.GeometryPreshower   = True
+    ConfigFlags.Detector.SimulateFaserSCT   = True
+    ConfigFlags.Detector.GeometryFaserSCT   = True
+    ConfigFlags.Detector.SimulateUpstreamDipole = True
+    ConfigFlags.Detector.SimulateCentralDipole = True
+    ConfigFlags.Detector.SimulateDownstreamDipole = True
+    ConfigFlags.Detector.GeometryUpstreamDipole = True
+    ConfigFlags.Detector.GeometryCentralDipole = True
+    ConfigFlags.Detector.GeometryDownstreamDipole = True
+    ConfigFlags.GeoModel.Align.Dynamic  = False
+    ConfigFlags.Sim.ReleaseGeoModel     = False
+#
+# All flags should be set before calling lock
+#
+    ConfigFlags.lock()
+#
+# Construct ComponentAccumulator
+#
+    acc = MainServicesCfg(ConfigFlags)
+#
+# Particle Gun generator (comment out to read generator file)
+# Raw energies (without units given) are interpreted as MeV
+#
+    pg = PG.ParticleGun()
+    pg.McEventKey = "GEN_EVENT"
+    pg.randomSeed = 123456
+
+    pg.sampler = CosmicSampler()
+    acc.addEventAlgo(pg, "AthBeginSeq") # to run *before* G4
+#
+# Only one of these two should be used in a given job
+# (MCEventSelectorCfg for generating events with no input file,
+#  PoolReadCfg when reading generator data from an input file)
+#    
+    acc.merge(McEventSelectorCfg(ConfigFlags))
+    # acc.merge(PoolReadCfg(ConfigFlags))
+#
+#  Output stream configuration
+#
+    acc.merge(OutputStreamCfg(ConfigFlags, 
+                              "HITS", 
+                             ["EventInfo#*",
+                              "McEventCollection#TruthEvent",
+                              "McEventCollection#GEN_EVENT",
+                              "ScintHitCollection#*",
+                              "FaserSiHitCollection#*"
+                            ], disableEventTag=True))
+    acc.getEventAlgo("OutputStreamHITS").AcceptAlgs = ["G4FaserAlg"]               # optional
+    acc.getEventAlgo("OutputStreamHITS").WritingTool.ProcessingTag = "StreamHITS"  # required
+#
+#  Here is the configuration of the Geant4 pieces
+#    
+    acc.merge(FaserGeometryCfg(ConfigFlags))
+    acc.merge(G4FaserAlgCfg(ConfigFlags))
+    acc.addService(G4GeometryNotifierSvcCfg(ConfigFlags, ActivateLVNotifier=True))
+#
+# Verbosity
+#
+#    ConfigFlags.dump()
+#    logging.getLogger('forcomps').setLevel(VERBOSE)
+#    acc.foreach_component("*").OutputLevel = VERBOSE
+#    acc.foreach_component("*ClassID*").OutputLevel = INFO
+#    acc.getService("StoreGateSvc").Dump=True
+#    acc.getService("ConditionStore").Dump=True
+#    acc.printConfig()
+    f=open('FaserG4AppCfg_EVNT.pkl','wb')
+    acc.store(f)
+    f.close()
+#
+# Execute and finish
+#
+    sys.exit(int(acc.run(maxEvents=500).isFailure()))