← back to Norma Platform
Gate live IG delete behind IG_LIVE_DELETE flag (default off, tombstone-only)
ec918718b88b1181780cabfdfb4010842766ca78 · 2026-08-17 07:17:23 -0700 · steve
- Server refuses live deletes with 403 unless IG_LIVE_DELETE=1
- /api/posts reports liveEnabled; viewer hides the Live toggle + forces
tombstone mode when disabled, with an inline note on how to enable
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M agents/instagram-agent/posts-api.jsM agents/instagram-agent/public/posts-viewer.html
Diff
commit ec918718b88b1181780cabfdfb4010842766ca78
Author: steve <steve@designerwallcoverings.com>
Date: Mon Aug 17 07:17:23 2026 -0700
Gate live IG delete behind IG_LIVE_DELETE flag (default off, tombstone-only)
- Server refuses live deletes with 403 unless IG_LIVE_DELETE=1
- /api/posts reports liveEnabled; viewer hides the Live toggle + forces
tombstone mode when disabled, with an inline note on how to enable
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
agents/instagram-agent/posts-api.js | 11 +++++++++--
agents/instagram-agent/public/posts-viewer.html | 13 +++++++++++--
2 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/agents/instagram-agent/posts-api.js b/agents/instagram-agent/posts-api.js
index e992be7..c0d6f6e 100644
--- a/agents/instagram-agent/posts-api.js
+++ b/agents/instagram-agent/posts-api.js
@@ -35,6 +35,11 @@ const VIEWER = path.join(DIR, 'public', 'posts-viewer.html');
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
+// Live (real Instagram) delete is OFF by default — the viewer is tombstone-only until
+// Steve explicitly enables it with IG_LIVE_DELETE=1 in this agent's .env. Everything for
+// live delete is built and tested; this flag is the safety gate on the irreversible path.
+const liveEnabled = () => /^(1|true|yes|on)$/i.test(process.env.IG_LIVE_DELETE || '');
+
// ── ledger + tombstone helpers ───────────────────────────────────────────────
function readJsonl(file) {
if (!fs.existsSync(file)) return [];
@@ -141,7 +146,7 @@ function registerPostRoutes(app, agentName = 'instagram-agent') {
});
app.get('/api/posts', (req, res) => {
- try { res.json(getPosts()); }
+ try { res.json({ ...getPosts(), liveEnabled: liveEnabled() }); }
catch (e) { res.status(500).json({ error: e.message }); }
});
@@ -162,7 +167,9 @@ function registerPostRoutes(app, agentName = 'instagram-agent') {
catch (e) { return res.status(500).json({ ok: false, error: e.message }); }
}
- // live delete — serialized, verified, tombstone ONLY on confirmed removal
+ // live delete — gated OFF by default until IG_LIVE_DELETE=1
+ if (!liveEnabled()) return res.status(403).json({ ok: false, error: 'Live delete is disabled (tombstone-only). Set IG_LIVE_DELETE=1 to enable.' });
+ // serialized, verified, tombstone ONLY on confirmed removal
if (LIVE_LOCK) return res.status(409).json({ ok: false, error: 'a live delete is already running — one at a time' });
LIVE_LOCK = true;
try {
diff --git a/agents/instagram-agent/public/posts-viewer.html b/agents/instagram-agent/public/posts-viewer.html
index 6739d80..02a014a 100644
--- a/agents/instagram-agent/public/posts-viewer.html
+++ b/agents/instagram-agent/public/posts-viewer.html
@@ -88,17 +88,25 @@ main{padding:14px 16px 60px}
const $=s=>document.querySelector(s);
const esc=s=>String(s==null?'':s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
const LS={get:(k,d)=>{try{return localStorage.getItem('igposts.'+k)??d}catch{return d}},set:(k,v)=>{try{localStorage.setItem('igposts.'+k,v)}catch{}}};
-let ALL=[], MODE=LS.get('mode','tombstone');
+let ALL=[], MODE=LS.get('mode','tombstone'), LIVE_ENABLED=false;
function fmtWhen(t){ if(!t) return '—'; const d=new Date(t); return isNaN(d)?esc(t):d.toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}); }
function toast(msg,cls){ const el=$('#toast'); el.textContent=msg; el.className='toast show '+(cls||''); setTimeout(()=>el.className='toast',2600); }
function applyMode(){
+ if(!LIVE_ENABLED){ // live delete gated off → tombstone-only
+ MODE='tombstone';
+ $('#mLive').style.display='none';
+ $('#oc').innerHTML='Live delete on Instagram is <b>disabled</b> (tombstone-only). Enable with <code>IG_LIVE_DELETE=1</code>.';
+ $('#warn').classList.remove('show');
+ }else{
+ $('#mLive').style.display='';
+ }
$('#mList').classList.toggle('on',MODE==='tombstone');
$('#mLive').classList.toggle('on',MODE==='live');
$('#warn').classList.toggle('show',MODE==='live');
- if(MODE==='live') probeOc(); else $('#oc').textContent='';
+ if(LIVE_ENABLED){ if(MODE==='live') probeOc(); else $('#oc').textContent=''; }
}
async function probeOc(){
$('#oc').innerHTML='checking Chrome login…';
@@ -174,6 +182,7 @@ async function forceRemove(id){
async function load(){
const d=await fetch('api/posts').then(r=>r.json());
ALL=d.posts||[];
+ LIVE_ENABLED=!!d.liveEnabled; applyMode();
const sel=$('#acct');
sel.innerHTML='<option value="">All accounts</option>'+(d.accounts||[]).map(a=>`<option value="${esc(a.handle)}">@${esc(a.handle)} (${a.count})</option>`).join('');
view();
← 8529d5a IG auto-post viewer with inline delete (GET /posts on :9810)
·
back to Norma Platform
·
IG reshare: blocklist Spoonflower (competitor) fleet-wide + b5cd441 →