Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,36 @@ RUN chmod +x /opt/secy/secy.sh /opt/secy/entrypoint.sh /opt/secy/watch.sh /opt/s
# entrypoint.sh copies this into place.
COPY agent/conf/srt-settings.json /opt/secy/conf/srt-settings.json

# Generate integrity manifest for security-critical files.
# Verified at container startup by entrypoint.sh.
RUN sha256sum \
/opt/secy/AGENT.md \
/opt/secy/WATCH.md \
/opt/secy/PATROL.md \
/opt/secy/C2.md \
/opt/secy/conf/agent.conf \
/opt/secy/conf/srt-settings.json \
/opt/secy/entrypoint.sh \
/opt/secy/secy.sh \
/opt/secy/watch.sh \
/opt/secy/patrol.sh \
/opt/secy/c2.sh \
/opt/secy/lib/secy-common.sh \
/opt/secy/lib/agent-common.sh \
/opt/secy/lib/c2-common.sh \
/opt/secy/lib/patrol-common.sh \
/opt/secy/lib/watch-common.sh \
/opt/secy/lib/inotify-watch.sh \
/opt/secy/lib/format-stream.sh \
/usr/local/lib/sread/conf/blocked_paths \
/usr/local/lib/sread/conf/redact_patterns \
/usr/local/lib/sread/conf/allowed_mimetypes \
/usr/local/lib/sread/lib/common.sh \
/usr/local/lib/sread/lib/blocklist.sh \
/usr/local/lib/sread/lib/redact.sh \
/usr/local/bin/sread \
> /opt/secy/integrity.sha256

# State directory — mount a volume here for persistent findings
RUN mkdir -p /var/lib/secy/state

Expand Down
22 changes: 22 additions & 0 deletions agent/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,27 @@ if [[ -f /mnt/claude-credentials.json ]]; then
cp /mnt/claude-credentials.json /root/.claude/.credentials.json
fi

# ── Integrity check ──────────────────────────────────────────────
# Verify that security-critical files match the hashes baked at build time.
# If any file was modified (tampered prompts, weakened blocklists, etc.),
# refuse to start.

INTEGRITY_MANIFEST="/opt/secy/integrity.sha256"

if [[ -f "$INTEGRITY_MANIFEST" ]]; then
if ! sha256sum --check --strict "$INTEGRITY_MANIFEST" > /dev/null 2>&1; then
echo "INTEGRITY CHECK FAILED — security-critical files have been modified:" >&2
sha256sum --check "$INTEGRITY_MANIFEST" 2>&1 | grep -v ': OK$' >&2
echo "" >&2
echo "This likely means the image was not rebuilt after changing prompts," >&2
echo "configs, or blocklists. Rebuild with: docker compose build" >&2
echo "" >&2
echo "If this is unexpected, the image may have been tampered with." >&2
exit 1
fi
else
echo "WARNING: integrity manifest not found — skipping verification" >&2
fi

# ── Hand off to secy ─────────────────────────────────────────────
exec /opt/secy/secy.sh "$@"
60 changes: 60 additions & 0 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ do_install() {
info "This may take a few minutes on first build..."
docker compose -f "${SECY_DIR}/docker-compose.yml" build

# Store image digest for later verification
_save_image_digest
info "Image built successfully"

# ── 4. Start daemon services ──────────────────────────────────
Expand Down Expand Up @@ -275,6 +277,9 @@ do_start() {
exit 1
fi

# Verify image hasn't been replaced since install
_verify_image_digest

_start_containers

# Start notification service
Expand Down Expand Up @@ -380,6 +385,24 @@ do_status() {
warn " data directory not found — run ./setup.sh install"
fi

# ── Integrity ─────────────────────────────────────────────────
echo ""
echo -e "${BOLD}Integrity:${RESET}"
if [[ -f "${SECY_DATA_DIR}/.image-digest" ]]; then
local saved current
saved="$(cat "${SECY_DATA_DIR}/.image-digest")"
current="$(docker inspect secy --format '{{.Id}}' 2>/dev/null)" || current=""
if [[ -n "$current" ]] && [[ "$saved" == "$current" ]]; then
info " Image digest: verified"
elif [[ -n "$current" ]]; then
error " Image digest: MISMATCH (rebuilt or tampered since install)"
else
warn " Image digest: saved but image not found"
fi
else
warn " Image digest: not saved (run ./setup.sh install)"
fi

# ── Auth ──────────────────────────────────────────────────────
echo ""
echo -e "${BOLD}Auth:${RESET}"
Expand All @@ -405,6 +428,43 @@ _start_containers() {
info "Containers started"
}

DIGEST_FILE="${SECY_DATA_DIR}/.image-digest"

_save_image_digest() {
local digest
digest="$(docker inspect secy --format '{{.Id}}' 2>/dev/null)" || return 0
mkdir -p "$(dirname "$DIGEST_FILE")"
echo "$digest" > "$DIGEST_FILE"
info "Image digest saved"
}

_verify_image_digest() {
[[ -f "$DIGEST_FILE" ]] || return 0

local saved current
saved="$(cat "$DIGEST_FILE")"
current="$(docker inspect secy --format '{{.Id}}' 2>/dev/null)" || return 0

if [[ "$saved" != "$current" ]]; then
warn "Image digest mismatch — image was rebuilt or replaced since install"
warn " Expected: ${saved:0:20}..."
warn " Current: ${current:0:20}..."
warn " If you rebuilt intentionally, run: ./setup.sh install"
echo ""
if [[ -t 0 ]]; then
read -rp "Continue anyway? [y/N] " answer
if ! [[ "$answer" =~ ^[Yy]$ ]]; then
error "Aborted. Run ./setup.sh install to update the stored digest."
exit 1
fi
else
error "Image digest mismatch in non-interactive mode. Aborting."
error "Run ./setup.sh install to update the stored digest."
exit 1
fi
fi
}

_check_containers() {
local containers="secy-secy-watch-1 secy-secy-patrol-1 secy-secy-c2-1"
local all_running=true
Expand Down
39 changes: 30 additions & 9 deletions sread/lib/modules/secyhealth.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
# Check secy notification infrastructure: systemd service and cron health check
# Check secy infrastructure: integrity verification, notification service, cron health check
# Usage: sread secyhealth

run() {
local root=""
[[ -d "/host/home" ]] && root="/host"

section_header "SECY NOTIFICATION HEALTH"
section_header "SECY INFRASTRUCTURE HEALTH"

# ── 0. Container integrity ────────────────────────────────────
echo "--- Container integrity ---"
local integrity_issues=0
local manifest="/opt/secy/integrity.sha256"

if [[ -f "$manifest" ]]; then
if sha256sum --check --strict "$manifest" > /dev/null 2>&1; then
echo " Integrity manifest: verified (all files match)"
else
echo " [!] INTEGRITY CHECK FAILED — files modified since build:"
sha256sum --check "$manifest" 2>&1 | grep -v ': OK$' | sed 's/^/ /'
integrity_issues=$((integrity_issues + 1))
fi
else
echo " [!] Integrity manifest: NOT FOUND at ${manifest}"
echo " Image may have been built without integrity support"
integrity_issues=$((integrity_issues + 1))
fi
echo ""

# Discover the user's home directory
local user_home=""
Expand Down Expand Up @@ -55,7 +75,7 @@ run() {
service_issues=$((service_issues + 1))
fi
else
echo " [!] Unit file: NOT FOUND at ${service_file#${root}}"
echo " [!] Unit file: NOT FOUND at ${service_file#"${root}"}"
echo " Run: ./scripts/setup-notify.sh"
service_issues=$((service_issues + 1))
fi
Expand Down Expand Up @@ -115,32 +135,33 @@ run() {
perms="$(stat -c%a "$issues_dir" 2>/dev/null || echo "?")"
local issue_count
issue_count="$(find "$issues_dir" -maxdepth 1 -name '*.md' -type f 2>/dev/null | wc -l)"
echo " Path: ${issues_dir#${root}}"
echo " Path: ${issues_dir#"${root}"}"
echo " Owner: ${owner}, mode: ${perms}"
echo " Issues on file: ${issue_count}"

if [[ "$owner" == "root" ]]; then
echo " [!] Owned by root — notify.sh may not be able to read new files"
fi
elif [[ -n "$issues_dir" ]]; then
echo " [!] Issues directory not found: ${issues_dir#${root}}"
echo " [!] Issues directory not found: ${issues_dir#"${root}"}"
else
echo " (could not determine issues directory path)"
fi
echo ""

# ── Summary ────────────────────────────────────────────────────
echo "--- Summary ---"
echo " Integrity issues: ${integrity_issues}"
echo " Service issues: ${service_issues}"
echo " Cron issues: ${cron_issues}"

local total=$((service_issues + cron_issues))
local total=$((integrity_issues + service_issues + cron_issues))
if [[ $total -eq 0 ]]; then
echo " Notification infrastructure: OK"
echo " secy infrastructure: OK"
else
echo " [!] Notification infrastructure: DEGRADED (${total} issue(s))"
echo " [!] secy infrastructure: DEGRADED (${total} issue(s))"
fi

echo ""
log_ok "Notification health check complete (service_issues: ${service_issues}, cron_issues: ${cron_issues})"
log_ok "Health check complete (integrity: ${integrity_issues}, service: ${service_issues}, cron: ${cron_issues})"
}
Loading