mirror of
https://github.com/kerberos-io/agent.git
synced 2026-09-10 20:26:51 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e77af9e2c0 | ||
|
|
cc5c0253ed | ||
|
|
4c5a107d29 | ||
|
|
3b07c754f8 | ||
|
|
d151d0ce24 | ||
|
|
a32af4fe50 | ||
|
|
434cdf8a7f | ||
|
|
d3f53e4b6b | ||
|
|
8ea84d87db | ||
|
|
860acd3a6e | ||
|
|
4b935d97c8 |
@@ -65,6 +65,7 @@ There are a myriad of cameras out there (USB, IP and other cameras), and it migh
|
||||
|
||||
### Contributing
|
||||
|
||||
1. [Security vulnerability reporting](#security-vulnerability-reporting)
|
||||
1. [Contribute with Codespaces](#contribute-with-codespaces)
|
||||
2. [Develop and build](#develop-and-build)
|
||||
3. [Building from source](#building-from-source)
|
||||
@@ -301,6 +302,10 @@ If we talk about video encoders and decoders (codecs) there are 2 major video co
|
||||
|
||||
Conclusion: depending on the use case you might choose one over the other, and you can use both at the same time. For example you can use H264 (main stream) for livestreaming, and H265 (sub stream) for recording. If you wish to play recordings in a cross-platform and cross-browser environment, you might opt for H264 for better support.
|
||||
|
||||
## Security vulnerability reporting
|
||||
|
||||
If you found a potential security vulnerability, please use the private channels described in [SECURITY.md](SECURITY.md). Avoid opening public GitHub issues for sensitive findings.
|
||||
|
||||
## Contribute with Codespaces
|
||||
|
||||
One of the major blockers for letting you contribute to an Open Source project is to set up your local development machine. Why? Because you might already have some tools and libraries installed that are used for other projects, and the libraries you would need for Kerberos Agent, for example FFmpeg, might require a different version. Welcome to dependency hell...
|
||||
|
||||
40
SECURITY.md
Normal file
40
SECURITY.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We only provide security fixes for the latest release series on the `master` branch.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please do **not** open a public GitHub issue for potential security vulnerabilities.
|
||||
|
||||
Use one of the private channels below:
|
||||
|
||||
1. Preferred: GitHub private vulnerability reporting
|
||||
- https://github.com/kerberos-io/agent/security/advisories/new
|
||||
2. Fallback: Email
|
||||
- support@kerberos.io
|
||||
- Optional CC: support@uug.ai
|
||||
|
||||
Please include:
|
||||
|
||||
- A short summary and impact.
|
||||
- Reproduction steps or proof of concept.
|
||||
- Affected version(s), commit hash, or deployment details.
|
||||
- Any proposed mitigation/workaround.
|
||||
- Your preferred attribution name.
|
||||
|
||||
For faster triage, use this subject format in email:
|
||||
|
||||
`[Security][Kerberos Agent] <short title>`
|
||||
|
||||
## Response Expectations
|
||||
|
||||
- Acknowledgement target: within 3 business days.
|
||||
- Triage/update target: within 7 business days after acknowledgement.
|
||||
|
||||
If you do not receive a response in time, please resend your report and include your original timestamp.
|
||||
|
||||
## Disclosure and Credits
|
||||
|
||||
We follow coordinated disclosure. After a fix is available, we will credit reporters unless they prefer to stay anonymous.
|
||||
@@ -22,4 +22,8 @@ https://brianmacdonald.github.io/Ethonate/address#0xf4a759C9436E2280Ea9cdd23d314
|
||||
|
||||
[**Docker Hub**](https://hub.docker.com/r/kerberos/agent) | [**Documentation**](https://doc.kerberos.io) | [**Website**](https://kerberos.io)
|
||||
|
||||
Kerberos Open source (v3) is a cutting edge video surveillance management system made available as Open Source under the MIT License. This means that all the source code is available for you or your company, and you can use, transform and distribute the source code; as long you keep a reference of the original license. Kerberos Open Source (v3) can be used for commercial usage (which was not the case for v2). Read more [about the license here](LICENSE).
|
||||
Kerberos Open source (v3) is a cutting edge video surveillance management system made available as Open Source under the MIT License. This means that all the source code is available for you or your company, and you can use, transform and distribute the source code; as long you keep a reference of the original license. Kerberos Open Source (v3) can be used for commercial usage (which was not the case for v2). Read more [about the license here](LICENSE).
|
||||
|
||||
## Security reporting
|
||||
|
||||
For sensitive vulnerabilities, use private disclosure channels documented in [../SECURITY.md](../SECURITY.md).
|
||||
|
||||
@@ -32,15 +32,6 @@ const MacEpochOffset uint64 = 2082844800
|
||||
// resulting in ~3 second fragments (assuming a typical GOP interval).
|
||||
const FragmentDurationMs = 3000
|
||||
|
||||
// MinNormalGOPMs is the minimum spacing we expect between two consecutive
|
||||
// IDRs of a healthy source (typical encoders produce IDRs every 1000ms or
|
||||
// more). When two keyframes arrive closer than this, we treat the second one
|
||||
// as an upstream restart/loop-seam and force a fresh fragment so the seam
|
||||
// IDR cannot end up as a mid-fragment sync sample. The check only runs when
|
||||
// the current fragment has not yet reached FragmentDurationMs, so it never
|
||||
// fires during normal multi-GOP fragments at intended GOP boundaries.
|
||||
const MinNormalGOPMs = 950
|
||||
|
||||
type MP4 struct {
|
||||
// FileName is the name of the file
|
||||
FileName string
|
||||
@@ -65,7 +56,6 @@ type MP4 struct {
|
||||
FreeBoxSize int64
|
||||
FragmentStartRawPTS uint64 // Raw PTS for timing when to flush fragments
|
||||
FragmentStartDTS uint64 // Accumulated VideoTotalDuration at fragment start (matches tfdt)
|
||||
LastKeyframeRawPTS uint64 // Raw PTS of the most recently seen keyframe (in any fragment)
|
||||
MoofBoxes int64 // Number of moof boxes in the file
|
||||
MoofBoxSizes []int64 // Sizes of each moof box
|
||||
SegmentDurations []uint64 // Duration of each segment in timescale units
|
||||
@@ -243,24 +233,6 @@ func (mp4 *MP4) flushPendingVideoSample(nextPTS uint64) bool {
|
||||
var duration uint64
|
||||
if nextPTS > 0 && nextPTS > mp4.VideoFullSample.DecodeTime {
|
||||
duration = nextPTS - mp4.VideoFullSample.DecodeTime
|
||||
// Guard against forward PTS jumps (e.g. when looping a source MP4
|
||||
// through virtual-rtsp the upstream ffmpeg may insert a large offset
|
||||
// at the loop boundary, or the RTSP stream may stall briefly).
|
||||
// Without this clamp the sample gets a huge duration which appears
|
||||
// as a discontinuity in the trun/sidx/mvhd and causes browsers
|
||||
// (Video.js / MSE) to abort playback with a "media corruption"
|
||||
// error around the loop boundary.
|
||||
var maxPlausible uint64 = 1000 // 1 second hard ceiling
|
||||
if mp4.LastVideoSampleDTS > 0 && mp4.LastVideoSampleDTS*10 < maxPlausible {
|
||||
maxPlausible = mp4.LastVideoSampleDTS * 10
|
||||
}
|
||||
if duration > maxPlausible {
|
||||
log.Log.Warning(fmt.Sprintf("mp4.flushPendingVideoSample(): video PTS jumped forward (nextPTS=%d, prevDTS=%d, gap=%d ms) - clamping to %d ms (likely source loop/stall discontinuity)", nextPTS, mp4.VideoFullSample.DecodeTime, duration, maxPlausible))
|
||||
duration = mp4.LastVideoSampleDTS
|
||||
if duration == 0 {
|
||||
duration = 33
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No valid nextPTS (Close case) or PTS went backwards (jitter/discontinuity)
|
||||
if nextPTS > 0 {
|
||||
@@ -317,27 +289,6 @@ func (mp4 *MP4) AddSampleToTrack(trackID uint32, isKeyframe bool, data []byte, p
|
||||
}
|
||||
shouldFlush := !mp4.Start || elapsed >= FragmentDurationMs
|
||||
|
||||
// Detect upstream source-loop / restart discontinuity. When an MP4 is
|
||||
// looped through virtual-rtsp (ffmpeg `-stream_loop -1 -re`) the loop
|
||||
// seam emits a fresh IDR much sooner than a normal GOP would. PTS keeps
|
||||
// growing monotonically, so the timing-only `elapsed` check above does
|
||||
// not catch it and the seam IDR ends up as a mid-fragment sync sample.
|
||||
// MSE-based players (Video.js / Chromium / Firefox) reject the resulting
|
||||
// fragment with a "media corruption" error because the inner IDR resets
|
||||
// frame_num/POC inside what they expect to be a single GOP. Force a
|
||||
// fragment boundary whenever two consecutive keyframes arrive much
|
||||
// closer than a normal GOP (here: < 500 ms apart). This isolates the
|
||||
// seam IDR into its own fragment so each fragment stays a clean GOP.
|
||||
if !shouldFlush && trackID == uint32(mp4.VideoTrack) && mp4.Start &&
|
||||
mp4.LastKeyframeRawPTS > 0 && pts > mp4.LastKeyframeRawPTS &&
|
||||
pts-mp4.LastKeyframeRawPTS < MinNormalGOPMs {
|
||||
log.Log.Warning(fmt.Sprintf("mp4.AddSampleToTrack(): forcing fragment flush at unexpectedly close keyframe (gap=%d ms, fragment elapsed=%d ms) - likely upstream loop/restart discontinuity", pts-mp4.LastKeyframeRawPTS, elapsed))
|
||||
shouldFlush = true
|
||||
}
|
||||
if trackID == uint32(mp4.VideoTrack) {
|
||||
mp4.LastKeyframeRawPTS = pts
|
||||
}
|
||||
|
||||
if shouldFlush {
|
||||
// Write the previous segment to the file
|
||||
if mp4.Start {
|
||||
@@ -442,14 +393,6 @@ func (mp4 *MP4) AddSampleToTrack(trackID uint32, isKeyframe bool, data []byte, p
|
||||
if started {
|
||||
dts = 1
|
||||
}
|
||||
// Guard against forward PTS jumps (e.g. virtual-rtsp loop
|
||||
// boundary or upstream stalls). Without this clamp the
|
||||
// audio trun would carry an enormous sample duration that
|
||||
// renders the recording unplayable in browsers.
|
||||
if mp4.LastAudioSampleDTS > 0 && dts > mp4.LastAudioSampleDTS*10 {
|
||||
log.Log.Warning(fmt.Sprintf("mp4.AddSampleToTrack(): audio PTS jumped forward (pts=%d, prevDTS=%d, gap=%d) - clamping to last known duration", pts, mp4.AudioFullSample.DecodeTime, dts))
|
||||
dts = mp4.LastAudioSampleDTS
|
||||
}
|
||||
mp4.LastAudioSampleDTS = dts
|
||||
//fmt.Printf("Adding sample to track %d, PTS: %d, Duration: %d, size: %d\n", trackID, pts, dts, len(aac[7:]))
|
||||
mp4.AudioTotalDuration += dts
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
package video
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
mp4ff "github.com/Eyevinn/mp4ff/mp4"
|
||||
"github.com/kerberos-io/agent/machinery/src/models"
|
||||
)
|
||||
|
||||
// TestMP4LoopSeamIsolation reproduces the loop-seam pattern from the
|
||||
// failing virtual-rtsp recordings: ~1s GOPs, but at the source-MP4
|
||||
// loop boundary an IDR arrives prematurely (~200-870ms after the
|
||||
// previous IDR). Without the fix this seam IDR ends up bunched into
|
||||
// the same fragment as the prior GOP's IDR which trips macOS
|
||||
// VideoToolbox (kVTVideoDecoderBadDataErr / -12909). The fix forces
|
||||
// a fragment flush whenever two IDRs arrive closer than MinNormalGOPMs.
|
||||
func TestMP4LoopSeamIsolation(t *testing.T) {
|
||||
tmpFile := "/tmp/test_loop_seam.mp4"
|
||||
defer os.Remove(tmpFile)
|
||||
|
||||
sps := []byte{0x67, 0x42, 0xc0, 0x1e, 0xd9, 0x00, 0xa0, 0x47, 0xfe, 0xc8}
|
||||
pps := []byte{0x68, 0xce, 0x38, 0x80}
|
||||
mp4Video := NewMP4(tmpFile, [][]byte{sps}, [][]byte{pps}, nil, 30)
|
||||
mp4Video.SetWidth(1920)
|
||||
mp4Video.SetHeight(1080)
|
||||
v := mp4Video.AddVideoTrack("H264")
|
||||
|
||||
mk := func(k bool) []byte {
|
||||
nt := byte(0x01)
|
||||
if k {
|
||||
nt = 0x65
|
||||
}
|
||||
f := []byte{0, 0, 0, 1, nt}
|
||||
for i := 0; i < 200; i++ {
|
||||
f = append(f, byte(i))
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
frameDur := uint64(33)
|
||||
pts := uint64(0)
|
||||
emit := func(n int, gopLen int) {
|
||||
for f := 0; f < n; f++ {
|
||||
isKey := (f % gopLen) == 0
|
||||
mp4Video.AddSampleToTrack(v, isKey, mk(isKey), pts)
|
||||
pts += frameDur
|
||||
}
|
||||
}
|
||||
|
||||
// 17 seconds of normal content (last "good" IDR at sec 17).
|
||||
emit(17*30, 30)
|
||||
// Seam: IDR arrives ~867ms after previous (vs normal 1000ms).
|
||||
pts -= 100
|
||||
emit(13*30, 30)
|
||||
|
||||
mp4Video.Close(&models.Config{Signing: &models.Signing{PrivateKey: ""}})
|
||||
|
||||
f, _ := os.Open(tmpFile)
|
||||
defer f.Close()
|
||||
parsed, err := mp4ff.DecodeFile(f)
|
||||
if err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
|
||||
fragIdx := 0
|
||||
for _, seg := range parsed.Segments {
|
||||
for _, fr := range seg.Fragments {
|
||||
for _, traf := range fr.Moof.Trafs {
|
||||
if traf.Tfhd.TrackID != 1 {
|
||||
continue
|
||||
}
|
||||
tfdt := traf.Tfdt.BaseMediaDecodeTime()
|
||||
offset := uint64(0)
|
||||
var keys []uint64
|
||||
for _, trun := range traf.Truns {
|
||||
for _, s := range trun.Samples {
|
||||
if (s.Flags>>24)&0x03 == 0x02 {
|
||||
keys = append(keys, offset)
|
||||
}
|
||||
offset += uint64(s.Dur)
|
||||
}
|
||||
}
|
||||
fmt.Printf("frag %d tfdt=%d samples_dur=%d keys@%v\n",
|
||||
fragIdx, tfdt, offset, keys)
|
||||
for i := 1; i < len(keys); i++ {
|
||||
gap := keys[i] - keys[i-1]
|
||||
if gap < MinNormalGOPMs {
|
||||
t.Errorf("frag %d (tfdt=%d): two IDRs only %d ms apart "+
|
||||
"in same fragment (< %d) - seam was not isolated",
|
||||
fragIdx, tfdt, gap, MinNormalGOPMs)
|
||||
}
|
||||
}
|
||||
fragIdx++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user